@treeseed/dsh-chapters 0.1.16 → 0.1.17
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/cordis.patch.yml +30 -7
- package/docs/backlog.md +72 -0
- package/docs/contract.md +99 -6
- package/docs/development.md +28 -1
- package/docs/plans-carriage.md +1 -1
- package/docs/verify.md +1 -1
- package/lib/index.d.ts +52 -30
- package/lib/index.js +199 -104
- package/lib/index.js.map +1 -1
- package/lib/preset.d.ts +191 -0
- package/lib/preset.js +282 -0
- package/lib/preset.js.map +1 -0
- package/package.json +1 -3
- package/presets/chapters/agent.cordis.yml +0 -272
- package/presets/chapters/preset.yml +0 -3
- package/presets/probe-roots/agent.cordis.yml +0 -11
- package/presets/probe-roots/preset.yml +0 -3
package/cordis.patch.yml
CHANGED
|
@@ -10,17 +10,40 @@
|
|
|
10
10
|
# Two planes, two surfaces:
|
|
11
11
|
# HOST PLANE — the `config:` block below (defaults shown commented; set any
|
|
12
12
|
# under your profile's plugin entry).
|
|
13
|
-
# REALM PLANE — the compaction ENGINE row
|
|
14
|
-
# (
|
|
15
|
-
#
|
|
13
|
+
# REALM PLANE — the compaction ENGINE row inside the preset's composition
|
|
14
|
+
# (src/preset.ts for hosts that register rows; for pre-0.1.7
|
|
15
|
+
# hosts, presets/chapters/agent.cordis.yml); shared names behave
|
|
16
|
+
# per plane. Design record: docs/knowledge-repo.md §12.
|
|
17
|
+
#
|
|
18
|
+
# PRESET DELIVERY — one mechanism, and the host floor is 0.2.0:
|
|
19
|
+
# Hosts from 0.1.7-alpha.1 compose presets from '@deepseek-ai/dsh-agent-preset'
|
|
20
|
+
# DECLARATION ROWS and read nothing in $DSH_HOME/.agent-presets. This plugin registers
|
|
21
|
+
# the `chapters` row itself at activation, through the host's own agentPresets.register()
|
|
22
|
+
# — the same call the host's declaration-row plugin makes (src/preset.ts; why not a
|
|
23
|
+
# shipped patch layer is recorded there with the boot-breaking failures measured on
|
|
24
|
+
# 0.1.5-rc.1). Rows this host cannot import are PRUNED and named in the mount line,
|
|
25
|
+
# because ONE unresolvable row makes the whole preset broken and every chapters
|
|
26
|
+
# session unopenable. Hosts below 0.2.0 have no register(): the mount line says
|
|
27
|
+
# `delivery: REFUSED`, the tools still work, and the preset cannot exist there.
|
|
28
|
+
# Either way the boot then ASKS THE ROSTER — file presence is not preset existence,
|
|
29
|
+
# and "declared" is not "usable". Read the `dsh-chapters:` lines at startup.
|
|
16
30
|
#
|
|
17
31
|
# config:
|
|
18
32
|
# # ---- identity & install --------------------------------------------
|
|
19
|
-
# installChaptersPreset: true #
|
|
20
|
-
# # .
|
|
21
|
-
# #
|
|
33
|
+
# installChaptersPreset: true # register the chapters declaration row at
|
|
34
|
+
# # activation. Turning it off means a preset you
|
|
35
|
+
# # declare yourself (a patch layer in this or
|
|
36
|
+
# # another bundle); ours then stands down as
|
|
37
|
+
# # "already-declared" rather than throwing a
|
|
38
|
+
# # duplicate-preset error through our own row.
|
|
39
|
+
# compactionThresholdRatio: 0.9 # the registered preset's engine row: share of the
|
|
40
|
+
# # window that triggers a chapter checkpoint. This is
|
|
41
|
+
# # the knob a preset YAML used to carry in the home.
|
|
22
42
|
# fallbackPreset: 'chapters' # preset mounted for children when the
|
|
23
|
-
# # caller's own preset cannot be resolved
|
|
43
|
+
# # caller's own preset cannot be resolved — and
|
|
44
|
+
# # the id the roster check looks for. This id is
|
|
45
|
+
# # DURABLE STATE: every child's log records it,
|
|
46
|
+
# # so renaming it orphans those sessions.
|
|
24
47
|
# harnessId: '<hostname>' # per-machine identity: git commit author,
|
|
25
48
|
# # token scoping, edits/<harness>/ curation
|
|
26
49
|
# # partition, rule-approval authorship
|
package/docs/backlog.md
CHANGED
|
@@ -95,3 +95,75 @@ about.
|
|
|
95
95
|
machine-only when you expected the pool, that note is the first thing to read.
|
|
96
96
|
- **`/chapters-continue` vs `/chapters-fork`** is the intent line: continue carries the operative plan
|
|
97
97
|
and may seed plan mode; fork carries nothing.
|
|
98
|
+
|
|
99
|
+
## Preset delivery on 0.1.7+/0.2.x — landed, and what it exposed (2026-10-16)
|
|
100
|
+
|
|
101
|
+
**Landed:** the `chapters` preset now EXISTS on a declaration-row host, by `agentPresets.register()` at
|
|
102
|
+
activation (`src/preset.ts`), with rows the host cannot import dropped and named, a held disposer, and a
|
|
103
|
+
deferred ROSTER check (`present` / `DECLARED BUT BROKEN` / `absent` / `unknown`) as the only health signal.
|
|
104
|
+
**Support floor is 0.2.0** (user decision 2026-10-16): the legacy `.agent-presets` copy, the
|
|
105
|
+
`presets/chapters/` tree, `installChaptersPreset`'s copy arm and `preset-install.test.ts` are DELETED; a
|
|
106
|
+
pre-0.2.0 host gets `delivery: REFUSED` with its tools intact. The engine row's `thresholdRatio` became
|
|
107
|
+
the host-plane config `compactionThresholdRatio` (no hardcoded tunables). Verified on real boots:
|
|
108
|
+
0.2.0-rc.2 → `register: pruned, roster: present, inventoryRows: 28, unusableRows: []`;
|
|
109
|
+
0.2.1-alpha.4 → `register: registered, roster: present, inventoryRows: 29, unusableRows: []`
|
|
110
|
+
(after dropping `toolFilter`, which alpha.4 THROWS on — see the config-key row of the drift test). `tests/unit/preset-declaration.test.ts`
|
|
111
|
+
(13 rows, `src/preset.ts` at 100%) is the drift gate, and it now RUNS on this box instead of skipping.
|
|
112
|
+
|
|
113
|
+
**Why the row is not a shipped patch layer** — measured, because it looks like the obvious fix and it is a
|
|
114
|
+
boot-breaking one: `dsh.bundle.patch` as an ARRAY throws `ERR_INVALID_ARG_TYPE` at
|
|
115
|
+
`dsh-app-boot/lib/index.js:853` on every host before 0.1.7-alpha.1, and a row naming
|
|
116
|
+
`@deepseek-ai/dsh-agent-preset` aborts that host's plugin tree (`Cannot find package`). `disabled: !!js`
|
|
117
|
+
cannot gate it either: the Loader evaluates `disabled` before services mount, so the row is disabled on the
|
|
118
|
+
NEW host too (measured). Full record: `docs/contract.md` § Presets are declaration rows.
|
|
119
|
+
|
|
120
|
+
**Open, in order:**
|
|
121
|
+
|
|
122
|
+
0. **Re-anchor the e2e host, then re-distill the tapes.** `tests/e2e/boot.ts` now refuses by name
|
|
123
|
+
while `dev/dsh-host-lock` pins a pre-0.2.0 host, and the refusal lifts itself when the lock moves —
|
|
124
|
+
that is the upgrade path, not a TODO. The per-boot row pins (`thresholdRatio`, `enrichmentEnabled`,
|
|
125
|
+
`auto`, `elicitedPlot`, `toolResultArtifactTokens`) and the `[e2e] boot pins:` line were deleted with
|
|
126
|
+
the YAML they patched; they come back expressed as CONFIG on the registered row (`compactionThresholdRatio`
|
|
127
|
+
exists now; the rest need the same treatment). Until then the browser acceptance chain does not run, and
|
|
128
|
+
`docs/verify.md`'s P23 is retired rather than passing.
|
|
129
|
+
1. **The roster check is the only evidence we have that a preset composes — and it is a boot-time log
|
|
130
|
+
line.** A user cannot see it without a terminal. Candidate: surface `delivery:` + the roster verdict in
|
|
131
|
+
`/chapters-status` (and a warning row in the Settings plugin panel), so "installed and running" stops
|
|
132
|
+
being the whole answer. Nothing else in the plugin reports health to a human today.
|
|
133
|
+
2. **`compositionInventory()` is used by the dev witness only.** Making the boot check itself compare
|
|
134
|
+
mounted rows would catch a row that resolves but never activates — the `broken` flag does not always
|
|
135
|
+
cover that, and the witness proves the shape of the answer already.
|
|
136
|
+
3. **A reset registry can overwrite a chapter — no guard exists today.** `writeArchive`
|
|
137
|
+
(`src/archive.ts:106`) reserves numbers from the registry and writes them; nothing checks whether that
|
|
138
|
+
path already exists on disk. So the exact incident shape — registry gone (host reinstall) while chapter
|
|
139
|
+
files remain, or restored by rehydration — lets a NEW archive reuse number 001 and clobber a surviving
|
|
140
|
+
file, which breaks the append-only rule from the inside. Two rows are missing: at boot, report files the
|
|
141
|
+
registry does not know (`<store>/<root>/chapters/*.md` vs registry rows, counted); and refuse a
|
|
142
|
+
reservation whose target path exists, with the numbers.
|
|
143
|
+
4. **Rehydration restores bytes, not rows.** `rehydrateStoreFromMirror` (`src/sync.ts:476`) fills absent
|
|
144
|
+
chapter and artifact files only. Search works off the files' own frontmatter, but numbering, ancestry and
|
|
145
|
+
collections are registry state, so a rehydrated store can be searched and cannot be continued cleanly.
|
|
146
|
+
Decide: rebuild rows from frontmatter (honest — the bytes are the ground truth), or refuse continuation
|
|
147
|
+
over a partially restored store with numbers. Do not let it look like a fresh chain.
|
|
148
|
+
|
|
149
|
+
## Host bug we cannot fix: the workspace directory-picker modal (2026-10-16)
|
|
150
|
+
|
|
151
|
+
Reported by the user as "the directory selection modal crashes before I can select a workspace", and it
|
|
152
|
+
survived a host upgrade. Root-caused with real boots (`var/picker-probe/FINDINGS.md`, kept out of the
|
|
153
|
+
repo's claims — it is host code): the picker's CLIENT bundle and the shell's client module table must come
|
|
154
|
+
from the same host generation. The 0.1.x picker asks for `IconFolderOpen16`/`IconFolderClose16`;
|
|
155
|
+
0.2.0-rc.2's shell exports only the `…Regular` spellings, with no compat alias in either direction. The
|
|
156
|
+
slot boundary catches it, so the symptom is a rendered-then-dead dialog:
|
|
157
|
+
`Error: Minified React error #130 … got: undefined`, logged as
|
|
158
|
+
`slot entry crashed in 'sidebar.workspaces.directoryFlow'`
|
|
159
|
+
(`@deepseek-ai/dsh-client-ui-renderer/lib/client.js:618`). A stale profile-local copy of
|
|
160
|
+
`@deepseek-ai/dsh-client-ui-directory-picker-*` under `$DSH_HOME/profiles/<p>/node_modules` shadows the
|
|
161
|
+
upgraded global install and reproduces "the new host did not fix it" exactly.
|
|
162
|
+
|
|
163
|
+
Nothing in this plugin contributes to that slot (its client half registers
|
|
164
|
+
`conversation.chat.assistant-actions` only), so the repo can ship no fix — only the recipe, which the
|
|
165
|
+
site documents: purge the stale picker copies, and if the auto-resolved backend is not the one you want,
|
|
166
|
+
pin the pair deliberately in the profile patch (`disabled: true` on `directory-picker`, insert
|
|
167
|
+
`@deepseek-ai/dsh-host-directory-picker-browse` + `@deepseek-ai/dsh-client-ui-directory-picker-browse`).
|
|
168
|
+
Upstream ask: compat aliases for the renamed icon exports, or a boot-time shell↔plugin version handshake
|
|
169
|
+
that rejects a mismatched bundle instead of crashing at render.
|
package/docs/contract.md
CHANGED
|
@@ -244,12 +244,13 @@ The subclass seam, with everything measured by rounds 12-17 (sources: the instal
|
|
|
244
244
|
`resolveConfig` **rejects unknown keys at runtime** (`BasicCompactionConfig:
|
|
245
245
|
unknown key "artifactStoreRoot"`, r18) — peel engine-specific keys in the
|
|
246
246
|
subclass constructor before `super(ctx, baseConfig)`.
|
|
247
|
-
- Preset delivery:
|
|
248
|
-
`
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
247
|
+
- Preset delivery: the plugin registers the `chapters` declaration row at activation
|
|
248
|
+
(`src/preset.ts` -> `ctx.get('agentPresets').register(...)`). The old copy of
|
|
249
|
+
`presets/chapters/` into `$DSH_HOME/.agent-presets/` is GONE with the 0.2.0 support
|
|
250
|
+
floor; the section "Presets are declaration rows" records why the row is not a shipped
|
|
251
|
+
patch layer (`!!js` evaluates `disabled` before services mount, and an array
|
|
252
|
+
`dsh.bundle.patch` throws on a pre-0.1.7 host). Related: profile `!!js` reaches
|
|
253
|
+
`dshHomePath`/`process` but NOT `require` (r20 boot-killer).
|
|
253
254
|
- `ctx.logger` output is invisible to headless `dsh web` (r19); engine failures
|
|
254
255
|
mirror to `$DSH_CHAPTERS_ENGINE_ERRORS` when set. Engine finalization runs on
|
|
255
256
|
BOTH the success and rejection paths of `compactIfNeeded`/`compactNow` — the
|
|
@@ -354,6 +355,98 @@ Entry `apply(ctx, config)`. Build with `tsc -p tsconfig.json` and **build before
|
|
|
354
355
|
UI (later phase) additionally needs `dsh.client` and a `tsdown.client.config.ts` — see
|
|
355
356
|
`dsh-session-fork/package.json` for the shape.
|
|
356
357
|
|
|
358
|
+
### Presets are declaration rows now (host ≥ 0.1.7-alpha.1) — measured, with the incident
|
|
359
|
+
|
|
360
|
+
A user preset used to be a directory `$DSH_HOME/.agent-presets/<id>/` holding `preset.yml` +
|
|
361
|
+
`agent.cordis.yml`. The host's own migration note is blunt: *"Nothing reads that directory any more."*
|
|
362
|
+
(`@deepseek-ai/dsh-agent-preset/skills/editing-cordis-compositions/SKILL.md`). A preset is now an
|
|
363
|
+
`@deepseek-ai/dsh-agent-preset` **declaration row** (`config: {id, name?, description?, order, plugins}`;
|
|
364
|
+
`dsh-agent-preset/lib/index.js:13-20`) carried by a patch layer, and `AgentPresetRegistry.register()` is
|
|
365
|
+
fed only by such rows (`dsh-agent-preset-registry/lib/index.js:470`, installed 0.2.0-rc.2). Nothing
|
|
366
|
+
crashes when a plugin misses this: the bundle boots, its tools register, Settings says "enabled and
|
|
367
|
+
running", the preset is simply absent — and a session's preset id is **durable state** (`resumeObserved()`
|
|
368
|
+
→ `presets.resolve(id)`, `dsh-api-session-controller/lib/index.js:404`, `:364`; `resolve()` throws
|
|
369
|
+
`agent-preset/not-found`, `dsh-agent-preset-registry/lib/index.js:596`), so every chapters session refuses
|
|
370
|
+
to OPEN while its log sits there intact.
|
|
371
|
+
|
|
372
|
+
**This bundle does NOT ship the row as a patch layer.** It registers the row itself at activation,
|
|
373
|
+
through the host's own `ctx.get('agentPresets').register(definition)` — the same call the host's
|
|
374
|
+
declaration-row plugin makes (`dsh-agent-preset/lib/index.js`, `[Service.init]`). The reason is two
|
|
375
|
+
boot-breaking failures measured on the pinned legacy closure (`@deepseek-ai/dsh 0.1.5-rc.1`, the host
|
|
376
|
+
`dev/dsh-host-lock` freezes for tape bytes) against the installed 0.2.0-rc.2:
|
|
377
|
+
|
|
378
|
+
- `dsh.bundle.patch` as an **array** (what a second layer requires) dies at profile load on a pre-0.1.7
|
|
379
|
+
host: `TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an
|
|
380
|
+
instance of Array` — legacy `dsh-app-boot/lib/index.js:853` does `join(packageDir, declared)`. Array
|
|
381
|
+
support (`bundlePatchFiles`) and declaration rows arrive in the SAME release, 0.1.7-alpha.1, verified
|
|
382
|
+
across the published `dsh-app-boot` tarballs. A two-layer manifest is therefore exactly as incompatible
|
|
383
|
+
with 0.1.5/0.1.6 as it looks, and the host does not warn — it just never comes up.
|
|
384
|
+
- The **row itself** in any layer a legacy host reads is fatal the same way:
|
|
385
|
+
`dsh: plugin tree failed to load: … failed to import loader entry preset-chapters
|
|
386
|
+
(@deepseek-ai/dsh-agent-preset): Cannot find package` (measured; `@deepseek-ai/dsh-agent-preset` is
|
|
387
|
+
first published at 0.1.7-alpha.1, and 0.1.5-rc.1 ships `dsh-agent-presets`, plural, whose service has
|
|
388
|
+
no `register()` — `this.ctx.agentPresets.register is not a function`).
|
|
389
|
+
- **`disabled: !!js` cannot gate the row either**, which is the tempting fix. The Loader evaluates a row's
|
|
390
|
+
`disabled` before services are mounted (`cordis-plugin-loader/lib/index.js:348` →
|
|
391
|
+
`evaluate(this.ctx, expr)` = `with (ctx) { eval(expr) }`), so `get('agentPresets')` is undefined *on the
|
|
392
|
+
modern host too* and the row is disabled everywhere — measured on 0.2.0-rc.2: `preset-v2 … failed to
|
|
393
|
+
import`, roster without it, while the same row with `disabled: !!js false` registered normally. Bare
|
|
394
|
+
`get(...)` also throws in that scope (`ctx` is bound, `get` is not).
|
|
395
|
+
|
|
396
|
+
The registration path is verified on both mechanisms with real boots: on 0.2.0-rc.2 the witness reports
|
|
397
|
+
`register: pruned, roster: present, inventoryRows: 28, unusableRows: []`; on 0.1.5-rc.1 it reports
|
|
398
|
+
and the same build on 0.1.5-rc.1 logs
|
|
399
|
+
`delivery: REFUSED — this host predates preset declaration rows (< 0.2.0)` — the tools work,
|
|
400
|
+
the preset cannot exist there, and 0.2.0 is now the support floor.
|
|
401
|
+
|
|
402
|
+
Three consequences of reading the registry rather than the filesystem:
|
|
403
|
+
|
|
404
|
+
- **A row that does not resolve is dropped, never mounted.** `mountPreset` throws if ANY enabled row failed
|
|
405
|
+
(`dsh-agent-preset-registry/lib/index.js:272`), the registry stores it as `record.broken` (`:541`), and
|
|
406
|
+
`retain()` then refuses with `agent-preset/invalid` (`:651`) — so one stale row makes the whole preset
|
|
407
|
+
unusable, which is the same user-visible failure as a missing preset. Measured on 0.2.0-rc.2 with a row
|
|
408
|
+
for `@deepseek-ai/dsh-tool-schedule` (published only from 0.2.1-alpha.1):
|
|
409
|
+
`preset "chapters" is DECLARED BUT BROKEN — tool-schedule …: never started`. `pruneResolvableRows`
|
|
410
|
+
(`src/preset.ts`) drops such rows and NAMES them in the mount line; `resolvableFromThisPackage` is the
|
|
411
|
+
probe, asked with the row's full specifier so a lost subpath export is caught too.
|
|
412
|
+
- **A held disposer, not an effect.** `register()` returns an unregister disposer, and wiring it through
|
|
413
|
+
`ctx.effect` un-registers the preset the moment our activation finishes — measured: `register()`
|
|
414
|
+
succeeded while the roster came back WITHOUT `chapters`. The same apply-fiber trap bit the storage
|
|
415
|
+
domain in r35. The disposer is held in a module array instead.
|
|
416
|
+
- **The roster is the health check, and it must be asked after activation.** The registry re-audits a mount
|
|
417
|
+
by awaiting the settled loader tree (`:562`), and its doc says callers "must not run inside a Host row's
|
|
418
|
+
own activation, which the settlement would wait on". So `reportPresetRoster()` runs deferred, and
|
|
419
|
+
`compositionInventory()` rows say which mounted rows are actually `active` — `broken` alone hides a row
|
|
420
|
+
that resolved but never started.
|
|
421
|
+
|
|
422
|
+
Rows are renamed and dropped between host versions, so re-anchor `src/preset.ts` against the installed
|
|
423
|
+
host's own `@deepseek-ai/dsh-web-app/presets/standard.patch.yml` after an upgrade. Measured drift across
|
|
424
|
+
the five published versions (rc.1, rc.2, alpha.1, alpha.2, alpha.4): alpha.1 ADDS `dsh-time-context` +
|
|
425
|
+
`dsh-tool-schedule` + `toolFilter.deny[schedule_*]`; alpha.2 DROPS the persona `suffix` (with its
|
|
426
|
+
`{{cwd}}`), `backgroundMode`, and the codex/claude-code/ralph rows; alpha.4 rewrites the plan-mode
|
|
427
|
+
sentence, drops `toolFilter`, adds `dsh-plugin-market`. rc.1 and rc.2 are byte-identical.
|
|
428
|
+
`tests/unit/preset-declaration.test.ts` is what turns the next rename into a red row instead of a broken
|
|
429
|
+
user session — and it pins that `dsh.bundle.patch` is still a string.
|
|
430
|
+
|
|
431
|
+
- **Prompt variables are a closed set, and violating them throws per turn, not at boot.** `dsh-agent-loop`
|
|
432
|
+
registers `provider`/`model`/`cwd` through 0.2.0-rc.2 (`lib/index.js:1564-1566`) and only `provider` and
|
|
433
|
+
`model` from 0.2.1-alpha.2 (`:1591-1592`); `interpolate()` rejects any other reference with `prompt
|
|
434
|
+
variable "{{x}}" has no value for this assembly` (`dsh-system-prompt/lib/index.js:171`). There is no
|
|
435
|
+
`{{cwd}}` worth writing: `dsh-working-directory` (host plane) already prints `Current working directory:
|
|
436
|
+
"…"` into the runtime context. This is what broke every new chapters session immediately AFTER the
|
|
437
|
+
delivery fix above — the preset composed fine, the legacy `persona.suffix` interpolated `{{cwd}}`, and
|
|
438
|
+
assembly threw on every turn. Pinned by the `{{variable}}` row of the test.
|
|
439
|
+
- **Some host plugins reject unknown config keys**, so a preset row's config is not free-form:
|
|
440
|
+
`dsh-compaction-tool-result-pruner`, `dsh-compaction-basic` and `dsh-plan-mode` throw on any key they do
|
|
441
|
+
not know (`PlanModeConfig has unknown key(s) …`); `dsh-tool-subagent` and `dsh-tool-ralph` are plain zod
|
|
442
|
+
objects that strip them. Keep exactly `{thresholdChars, headChars, tailChars}` on the pruner and exactly
|
|
443
|
+
`{section}` on plan mode.
|
|
444
|
+
- **Persona wording is load-bearing for the engine.** `src/engine-core.ts`'s `PLOT_TEMPLATE_ECHO` suppresses
|
|
445
|
+
a "plot" that is really our own instruction quoted back (the 2026-09-25 audit: eight checkpoints of a
|
|
446
|
+
real run inherited boilerplate, and because the result was non-null the bounded elicited fallback never
|
|
447
|
+
ran). It matches phrases inside the suffix, so reword the suffix and the guard in the same change — the
|
|
448
|
+
gate asserts that coupling by behaviour, not by copying the regex.
|
|
449
|
+
|
|
357
450
|
## Trigger Constraints
|
|
358
451
|
|
|
359
452
|
Two different actions, two different timings — Phase 0b measured the seam so this split is now factual,
|
package/docs/development.md
CHANGED
|
@@ -355,7 +355,7 @@ happened in these notes before it was caught.
|
|
|
355
355
|
| `src/notice.ts` + `test/notice.test.ts` — TOC-notice builder, kernel-rejection reproduction, vocabulary pins + drift guard (Phase 0b check 5) | **done**, 10 tests |
|
|
356
356
|
| `src/registry.ts` + `src/store.ts` — pure numbering/ancestry/plan state; zod domain spec, storage-domain + node-fs adapters | **done**, 13 + 7 tests; durability witness passed |
|
|
357
357
|
| `src/engine-core.ts` + `src/engine.ts` — ChaptersCompactionEngine | **done & boot-proven** (r18/r19: realm subpath row mounts; real cascade finalizes; catch-path fix — FINDINGS § Phase 1) |
|
|
358
|
-
| `presets/chapters/` + copy-on-boot
|
|
358
|
+
| preset delivery | **rewritten in 0.1.17**: the row is registered at activation (`src/preset.ts`). The `presets/chapters/` + copy-on-boot mechanism it replaced is deleted with the 0.2.0 support floor (docs/contract.md) |
|
|
359
359
|
| `npm test` (unit + integration) | **188 passing, <2 s** — unit imports no harness services; integration runs the sync loop against a fake git driver AND a real git smart-HTTP server (git-http-backend on loopback; the git binary is test-only scaffolding, the product stays pure-JS) |
|
|
360
360
|
| 20-check verify.md pass | **r26/26b**: 18 mechanical on `.dshdev2` (refusals with numbers, tamper marks, idempotent retry, cross-boot registry + resume); human rows per verify.md evidence map |
|
|
361
361
|
| dsh-session-fork coexistence | **moot at this version** (r26a pair-boot): the example fails to boot the installed host ALONE (unguarded `webServer` read in the installed dsh-client-connection) — assumed-neutral until a version-matched pair-boot |
|
|
@@ -465,3 +465,30 @@ commands were *verified*.
|
|
|
465
465
|
|
|
466
466
|
`docs/contract.md`'s file:line citations are from the `examples/` snapshot (rc.1-era), not the rc.2 fork —
|
|
467
467
|
re-anchor them against whichever version a test actually boots.
|
|
468
|
+
|
|
469
|
+
### Running the TS tests on this box (node 22, no native type stripping)
|
|
470
|
+
|
|
471
|
+
Measured 2026-10-16: `npm test` (`node --test tests/unit/*.test.ts`) needs node ≥ 23 for type
|
|
472
|
+
stripping, and this machine's `/usr/bin/node` is **v22.22.1 compiled WITHOUT TypeScript support**
|
|
473
|
+
(`ERR_NO_TYPESCRIPT`, not merely a missing flag). The package's `engines` floor is node 24, and CI
|
|
474
|
+
installs it; locally, run the same files through `tsx`:
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
npm install --no-save --no-audit --no-fund tsx unrun # both, in ONE command
|
|
478
|
+
node --import tsx --test tests/unit/*.test.ts tests/integration/*.test.ts
|
|
479
|
+
npx c8 --check-coverage --statements 85 --branches 85 --functions 85 --lines 85 \
|
|
480
|
+
node --import tsx --test tests/unit/*.test.ts tests/integration/*.test.ts
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Two traps measured while finding that:
|
|
484
|
+
|
|
485
|
+
- **`npm install --no-save A` then `--no-save B` removes A.** npm prunes what is not in the manifest,
|
|
486
|
+
so unsaved helpers must be installed in a single command (`unrun` disappeared the moment `tsx` was
|
|
487
|
+
added, and `npm run build` — which bundles the client through `tsdown` — failed with
|
|
488
|
+
`Cannot find module "unrun"`).
|
|
489
|
+
- `tsconfig.test.json` (committed) transpiles `src` + `tests` to `var/tsbuild` with `--noCheck` for
|
|
490
|
+
the rare case you want emitted JS instead of a loader. It is NOT how the suite is meant to run;
|
|
491
|
+
tests import `.ts` sources directly and are not typechecked by design (`npm run typecheck` covers
|
|
492
|
+
`src/` only).
|
|
493
|
+
|
|
494
|
+
Never fix a missing test runner by editing the tests. The suite is the contract; the runner is local.
|
package/docs/plans-carriage.md
CHANGED
|
@@ -4,7 +4,7 @@ Written 2026-10-09. **Read before writing code.** Human decisions for this featu
|
|
|
4
4
|
§ Decisions Taken and are binding; anything marked *deferred* is a human call, not ours.
|
|
5
5
|
|
|
6
6
|
DSh ships plan mode (`@deepseek-ai/dsh-plan-mode`) and our own preset mounts it
|
|
7
|
-
(`presets/chapters/agent.cordis.yml
|
|
7
|
+
(the `plan-mode` row of the registered preset: `PLAN_MODE_SECTION` in `src/preset.ts`; before the 0.2.0 floor it lived in `presets/chapters/agent.cordis.yml`). Today a session plan survives an archive only as the
|
|
8
8
|
first 400 characters of a tool-call line. That is the worst possible outcome for the one artifact in a
|
|
9
9
|
conversation that is *deliberately* decision-complete — and the reason the plugin exists at all: a
|
|
10
10
|
continuation that cannot see the approved plan either re-plans from recall (lossy) or implements
|
package/docs/verify.md
CHANGED
|
@@ -152,7 +152,7 @@ renderer, the segmentation tool, and the child's notice.
|
|
|
152
152
|
| P10 | Artifact citations resolve wherever they were written | a chapter's `artifacts/aa/<sha>.txt` reference resolves whether the blob sits at the STORE ROOT (arrival stubs) or inside the ANCESTRY TREE (`<store>/<root>/artifacts/…`, where `writeArchive` puts deferred results and every plan) — before this fix the second form was unreadable through `chapters_artifact`, which is what "every byte remains retrievable" actually promised | `artifact-query.test.ts` rows + integration `plans-carriage.test.ts` (`resolveArtifactWithMirror` over the real tree) |
|
|
153
153
|
| P11 | The browser plane runs in the chain | `/plan <message>` → a real `exit_plan_mode` call → the host's review card opened and answered by a real CLICK → fork; asserts the submission's bytes are the artifact's bytes (or provably only-redacted), `planMode: inactive` from the fold, a `kind: plan` document on disk, and that the BRANCH child carries **no** `## Active plan` and no call-out while still citing the archive; plus a guard that no `compaction/*` entered the journey | `tests/e2e/plans.spec.ts` = chain project `plans` (13 s replay, no LLM; 3-entry tape). **Scope, learned at cost:** the continuation leg was REMOVED from this journey because a tape freezes the model's tool ARGUMENTS — my scripted upstream derived `chapters_continue` ranges from the ceiling the live segment tool reported while distilling (30 here), and the runner's session lands at 29, so `validateRanges` refused the stale input (correctly). A journey whose recorded request embeds a machine-specific seq cannot be hardware-neutral. Continuation semantics — `## Active plan`, `## Previous plans`, the seeded `plan/mode`, the checkpoint carry — are covered by the deterministic plane (`continue-plans`, `plans-engine`, `plan-documents`, the § 10 kernel probe) and the in-place path by the heavy/arrival/enrich/fanout journeys. Tape provenance: scripted upstream, see `tests/fixtures/model-tape/plans/PROVENANCE.md` |
|
|
154
154
|
|
|
155
|
-
| P23 |
|
|
155
|
+
| P23 | **Retired in 0.1.17 with the legacy copy** (support floor 0.2.0): `installChaptersPreset` and `presets/chapters/` are deleted, so there is no installed file set to half-delete. The health evidence is the ROSTER check and the mounted rows — pinned by P24/P25 below | unit `preset-declaration.test.ts` (14 rows) + real boots on 0.2.0-rc.2 and 0.2.1-alpha.4 |
|
|
156
156
|
| P21 | The continuation has a **human-pulled door**, and the two doors cannot drift | `/chapters-continue` = `deterministicCommand({intent:'continue'})`, the same builder as `/chapters-fork`; only name/verbs/intent/no-directive prose differ; archive semantics identical (everything since the watermark). CLIENT SIDE, same discipline: the assistant row registers both doors from ONE runner (`src/client/doors.ts` `makeDoorRunner` + a two-row `DOOR_ACTIONS` table) — a composer-only continue left the user with a created child and no affordance | unit `continue-command.test.ts` (7 rows) incl. the **no-drift row**: same events in ⇒ same ranges, titles and plan facts out of both doors; the no-plan case, the refusal naming its own door, and approved ⇒ NOT seeded vs still-planning ⇒ seeded `['user/message','plan/mode']`. Unit `client-doors.test.ts` (7 rows) pins the two client rows (distinct id/order/label/command, the fork label frozen for the specs), that ONE child-id regex parses BOTH door wordings, that both rows fire their own command through the same body, and — by reading the server's own templates — that the reply wording the client parses is still what `deterministicCommand` emits. Browser: project `fork` asserts BOTH buttons render |
|
|
157
157
|
| P22 | The continuation shape is covered **in the browser**, on every push | the leg that a tape could not carry (§ 15) is back, driven by the command — no model request, no frozen `endSeq`; asserted from the child's durable log without switching the UI: `## Active plan`, the operative plan by title with its real verdict, `plan document:` and `chapters_artifact` citations, `## Finding things`, no seeded gate after approval, exactly one synthetic notice (invariant 2) | `tests/e2e/plans.spec.ts` project `plans` — replays in ~14 s with no LLM/GPU, eight-project chain green |
|
|
158
158
|
| P18 | Search answers **without a pool linked** — the findability audit | `buildLocalCorpus` walks the workspace store (chapters, `kind: plan` documents, rules) and answers through the SAME scorer as the mirror door (`rankEntries`), so ranking cannot drift between doors; artifacts are deliberately not indexed (they answer to `chapters_artifact`); the answer names its door ("searched this machine's store, N docs, no pool mirror") so a session never reads *this machine only* as *the whole project*; "nothing matched" (`documents>0`) is distinguished from "nothing here" (`empty`); a pool miss falls back to the local store before answering empty | unit `local-corpus.test.ts` (5) + integration `find-without-a-pool.test.ts` through the real tool wrapper — the empty answer used to be born in the wrapper, not the pure function |
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Schema from '@deepseek-ai/schemastery';
|
|
2
|
+
import { type RegisterOutcome } from './preset.ts';
|
|
2
3
|
import { type SyncScheduler } from './sync.ts';
|
|
3
4
|
/** The cordis surface this entry touches; widened in later stages. */
|
|
4
5
|
interface HostCtx {
|
|
@@ -26,6 +27,7 @@ interface HostCtx {
|
|
|
26
27
|
}
|
|
27
28
|
export interface Config {
|
|
28
29
|
installChaptersPreset: boolean;
|
|
30
|
+
compactionThresholdRatio: number;
|
|
29
31
|
artifactStoreRoot: string;
|
|
30
32
|
chapterTokenTarget: number;
|
|
31
33
|
toolResultDeferFloorTokens: number;
|
|
@@ -91,39 +93,59 @@ export declare function apply(ctx: HostCtx, config: Config): Promise<void>;
|
|
|
91
93
|
* before anything archives.
|
|
92
94
|
*/
|
|
93
95
|
export declare function createScheduler(store: import('./store.ts').RegistryStore, domain: import('./store.ts').DomainLike, config: Config, debounceOverride?: number, onSyncDone?: (cwd: string, ok: boolean) => void, warn?: (m: string) => void): SyncScheduler;
|
|
94
|
-
export type PresetInstallStatus = 'installed' | 'repaired' | 'complete' | 'failed' | 'no-source';
|
|
95
|
-
/** What the preset install actually did — the caller must not report success on a 'failed'. */
|
|
96
|
-
export interface PresetInstallReport {
|
|
97
|
-
status: PresetInstallStatus;
|
|
98
|
-
/** Shipped files absent from the installed copy (the partial-preset case). */
|
|
99
|
-
missing: string[];
|
|
100
|
-
dstDir: string;
|
|
101
|
-
error?: string;
|
|
102
|
-
}
|
|
103
96
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
97
|
+
* Why delivery is a `register()` call and not a file, in one incident: hosts from
|
|
98
|
+
* 0.1.7-alpha.1 stopped reading `$DSH_HOME/.agent-presets/` — a preset is an
|
|
99
|
+
* `@deepseek-ai/dsh-agent-preset` declaration row — while the plugin kept copying,
|
|
100
|
+
* kept logging that it had mounted, and the preset was simply GONE. That is worse
|
|
101
|
+
* than a crash, because `resume` resolves the preset id recorded in the session's
|
|
102
|
+
* own log and throws `agent-preset/not-found`, so every chapters session became
|
|
103
|
+
* unopenable while its durable log sat there intact and uncorrupted.
|
|
106
104
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
|
|
115
|
-
|
|
105
|
+
* Shipping the row as a patch layer instead is what a reader will reach for, and it
|
|
106
|
+
* is a boot-breaking choice on the hosts this plugin used to support: `dsh.bundle.patch`
|
|
107
|
+
* as an array throws `ERR_INVALID_ARG_TYPE` at legacy `dsh-app-boot/lib/index.js:853`,
|
|
108
|
+
* an unresolvable row aborts a legacy plugin tree, and `disabled: !!js` cannot gate the
|
|
109
|
+
* row because the Loader evaluates `disabled` before any service is mounted (all three
|
|
110
|
+
* measured). Support floor is 0.2.0 now, so the row is registered here through the
|
|
111
|
+
* host's own `agentPresets.register()` — the same call its declaration-row plugin makes.
|
|
112
|
+
*/
|
|
113
|
+
/**
|
|
114
|
+
* Whether a row's module can be imported from THIS package's resolution family.
|
|
116
115
|
*
|
|
117
|
-
* The
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* anything.
|
|
116
|
+
* The preset mount resolves its rows from the registry's context, which shares this Node
|
|
117
|
+
* resolution root; a false answer here means a row that would activate as failed — and one
|
|
118
|
+
* failed row makes the whole preset `broken`, which is worse than a missing tool.
|
|
121
119
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
120
|
+
* Never throws: an unresolvable specifier is the answer. Two limits of the probe are the
|
|
121
|
+
* host's own, not ours to paper over: a RELATIVE specifier resolves without touching the
|
|
122
|
+
* filesystem (Node answers the URL, not existence), so a file-URL row would always be
|
|
123
|
+
* reported resolvable; and a name that resolves is not yet a name that ACTIVATES — a plugin
|
|
124
|
+
* that rejects its config throws later, which is what the roster line reports as
|
|
125
|
+
* `DECLARED BUT BROKEN`. Subpath exports are the useful case, and they are the ones a host
|
|
126
|
+
* rename eats first: `@scope/pkg/no-longer-exported` answers `ERR_PACKAGE_PATH_NOT_EXPORTED`.
|
|
124
127
|
*/
|
|
125
|
-
export declare function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
export declare function resolvableFromThisPackage(specifier: string): boolean;
|
|
129
|
+
/** What the host roster says about our preset. */
|
|
130
|
+
export type RosterVerdict = {
|
|
131
|
+
state: 'present';
|
|
132
|
+
broken?: string;
|
|
133
|
+
} | {
|
|
134
|
+
state: 'absent';
|
|
135
|
+
rows: string[];
|
|
136
|
+
} | {
|
|
137
|
+
state: 'unknown';
|
|
138
|
+
error: string;
|
|
139
|
+
};
|
|
140
|
+
/** Ask the host which presets exist. Tolerates both shapes the service has
|
|
141
|
+
* offered: the bare row array (`list()`) and the remote envelope (`{presets}`). */
|
|
142
|
+
export declare function readRosterVerdict(presets: {
|
|
143
|
+
list?: () => Promise<unknown>;
|
|
144
|
+
} | undefined, presetId: string): Promise<RosterVerdict>;
|
|
145
|
+
/** The remedy, in the two shapes an absent preset can have on a supported host. */
|
|
146
|
+
export declare function rosterRemediation(presetId: string, outcome: RegisterOutcome): string;
|
|
147
|
+
/** Deferred boot check: one truthful line about the preset, plus the verdict for the witness.
|
|
148
|
+
* Exported so the four arms of that line are tested without booting a host — the line IS the
|
|
149
|
+
* health signal a user reads, and an untested health signal is how the silent case survived. */
|
|
150
|
+
export declare function reportPresetRoster(ctx: HostCtx, presetId: string, outcome: RegisterOutcome): Promise<RosterVerdict>;
|
|
129
151
|
export {};
|