@fractalsynapse/dsh-chapters 0.0.0-stage → 0.2.20

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.
Files changed (195) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +76 -2
  3. package/cordis.patch.yml +363 -0
  4. package/docs/architecture.md +692 -0
  5. package/docs/autorun-guide.md +248 -0
  6. package/docs/backlog.md +2022 -0
  7. package/docs/contract.md +938 -0
  8. package/docs/development.md +990 -0
  9. package/docs/host-compaction-seam.md +225 -0
  10. package/docs/host-protected-head-ask.md +80 -0
  11. package/docs/knowledge-repo.md +1332 -0
  12. package/docs/plans-carriage.md +571 -0
  13. package/docs/project-state.md +642 -0
  14. package/docs/provider.md +98 -0
  15. package/docs/superpowers/plans/2026-10-17-chapters-save.md +198 -0
  16. package/docs/superpowers/plans/2026-11-02-chapter-removal.md +151 -0
  17. package/docs/superpowers/specs/2026-11-02-chapter-removal-design.md +115 -0
  18. package/docs/verify.md +561 -0
  19. package/lib/archive.d.ts +161 -0
  20. package/lib/archive.js +229 -0
  21. package/lib/archive.js.map +1 -0
  22. package/lib/arrival.d.ts +23 -0
  23. package/lib/arrival.js +125 -0
  24. package/lib/arrival.js.map +1 -0
  25. package/lib/artifact-query.d.ts +62 -0
  26. package/lib/artifact-query.js +187 -0
  27. package/lib/artifact-query.js.map +1 -0
  28. package/lib/attach.d.ts +46 -0
  29. package/lib/attach.js +120 -0
  30. package/lib/attach.js.map +1 -0
  31. package/lib/auto-save.d.ts +145 -0
  32. package/lib/auto-save.js +334 -0
  33. package/lib/auto-save.js.map +1 -0
  34. package/lib/aux-message.d.ts +17 -0
  35. package/lib/aux-message.js +20 -0
  36. package/lib/aux-message.js.map +1 -0
  37. package/lib/client.js +1419 -0
  38. package/lib/client.js.map +1 -0
  39. package/lib/codegraph.d.ts +76 -0
  40. package/lib/codegraph.js +190 -0
  41. package/lib/codegraph.js.map +1 -0
  42. package/lib/commands.d.ts +52 -0
  43. package/lib/commands.js +946 -0
  44. package/lib/commands.js.map +1 -0
  45. package/lib/compose.d.ts +30 -0
  46. package/lib/compose.js +185 -0
  47. package/lib/compose.js.map +1 -0
  48. package/lib/continue-core.d.ts +245 -0
  49. package/lib/continue-core.js +405 -0
  50. package/lib/continue-core.js.map +1 -0
  51. package/lib/diag.d.ts +55 -0
  52. package/lib/diag.js +186 -0
  53. package/lib/diag.js.map +1 -0
  54. package/lib/engine-core.d.ts +301 -0
  55. package/lib/engine-core.js +531 -0
  56. package/lib/engine-core.js.map +1 -0
  57. package/lib/engine-note.d.ts +35 -0
  58. package/lib/engine-note.js +32 -0
  59. package/lib/engine-note.js.map +1 -0
  60. package/lib/engine.d.ts +329 -0
  61. package/lib/engine.js +1179 -0
  62. package/lib/engine.js.map +1 -0
  63. package/lib/enrich-queue.d.ts +66 -0
  64. package/lib/enrich-queue.js +126 -0
  65. package/lib/enrich-queue.js.map +1 -0
  66. package/lib/enrich-store.d.ts +31 -0
  67. package/lib/enrich-store.js +103 -0
  68. package/lib/enrich-store.js.map +1 -0
  69. package/lib/enrich-wire.d.ts +163 -0
  70. package/lib/enrich-wire.js +353 -0
  71. package/lib/enrich-wire.js.map +1 -0
  72. package/lib/enrich.d.ts +102 -0
  73. package/lib/enrich.js +267 -0
  74. package/lib/enrich.js.map +1 -0
  75. package/lib/gitops.d.ts +107 -0
  76. package/lib/gitops.js +539 -0
  77. package/lib/gitops.js.map +1 -0
  78. package/lib/index.d.ts +241 -0
  79. package/lib/index.js +1162 -0
  80. package/lib/index.js.map +1 -0
  81. package/lib/indexing.d.ts +98 -0
  82. package/lib/indexing.js +223 -0
  83. package/lib/indexing.js.map +1 -0
  84. package/lib/injections.d.ts +90 -0
  85. package/lib/injections.js +165 -0
  86. package/lib/injections.js.map +1 -0
  87. package/lib/last-resort.d.ts +24 -0
  88. package/lib/last-resort.js +27 -0
  89. package/lib/last-resort.js.map +1 -0
  90. package/lib/log-shape.d.ts +38 -0
  91. package/lib/log-shape.js +93 -0
  92. package/lib/log-shape.js.map +1 -0
  93. package/lib/models-command.d.ts +74 -0
  94. package/lib/models-command.js +100 -0
  95. package/lib/models-command.js.map +1 -0
  96. package/lib/notice.d.ts +74 -0
  97. package/lib/notice.js +92 -0
  98. package/lib/notice.js.map +1 -0
  99. package/lib/plans.d.ts +154 -0
  100. package/lib/plans.js +315 -0
  101. package/lib/plans.js.map +1 -0
  102. package/lib/preset.d.ts +186 -0
  103. package/lib/preset.js +212 -0
  104. package/lib/preset.js.map +1 -0
  105. package/lib/prompt-keys.d.ts +7 -0
  106. package/lib/prompt-keys.js +8 -0
  107. package/lib/prompt-keys.js.map +1 -0
  108. package/lib/prompt-loop.d.ts +73 -0
  109. package/lib/prompt-loop.js +147 -0
  110. package/lib/prompt-loop.js.map +1 -0
  111. package/lib/prompt-push.d.ts +47 -0
  112. package/lib/prompt-push.js +115 -0
  113. package/lib/prompt-push.js.map +1 -0
  114. package/lib/prompt-runner.d.ts +48 -0
  115. package/lib/prompt-runner.js +90 -0
  116. package/lib/prompt-runner.js.map +1 -0
  117. package/lib/prompt-state.d.ts +142 -0
  118. package/lib/prompt-state.js +101 -0
  119. package/lib/prompt-state.js.map +1 -0
  120. package/lib/prompt.d.ts +270 -0
  121. package/lib/prompt.js +408 -0
  122. package/lib/prompt.js.map +1 -0
  123. package/lib/provider.d.ts +44 -0
  124. package/lib/provider.js +66 -0
  125. package/lib/provider.js.map +1 -0
  126. package/lib/redact.d.ts +24 -0
  127. package/lib/redact.js +85 -0
  128. package/lib/redact.js.map +1 -0
  129. package/lib/registry.d.ts +187 -0
  130. package/lib/registry.js +235 -0
  131. package/lib/registry.js.map +1 -0
  132. package/lib/removal.d.ts +159 -0
  133. package/lib/removal.js +556 -0
  134. package/lib/removal.js.map +1 -0
  135. package/lib/render.d.ts +97 -0
  136. package/lib/render.js +530 -0
  137. package/lib/render.js.map +1 -0
  138. package/lib/repair-run.d.ts +76 -0
  139. package/lib/repair-run.js +506 -0
  140. package/lib/repair-run.js.map +1 -0
  141. package/lib/repair.d.ts +208 -0
  142. package/lib/repair.js +357 -0
  143. package/lib/repair.js.map +1 -0
  144. package/lib/repo.d.ts +78 -0
  145. package/lib/repo.js +184 -0
  146. package/lib/repo.js.map +1 -0
  147. package/lib/rules-commands.d.ts +46 -0
  148. package/lib/rules-commands.js +379 -0
  149. package/lib/rules-commands.js.map +1 -0
  150. package/lib/rules.d.ts +267 -0
  151. package/lib/rules.js +478 -0
  152. package/lib/rules.js.map +1 -0
  153. package/lib/save-core.d.ts +72 -0
  154. package/lib/save-core.js +220 -0
  155. package/lib/save-core.js.map +1 -0
  156. package/lib/search.d.ts +112 -0
  157. package/lib/search.js +282 -0
  158. package/lib/search.js.map +1 -0
  159. package/lib/session-rules.d.ts +222 -0
  160. package/lib/session-rules.js +454 -0
  161. package/lib/session-rules.js.map +1 -0
  162. package/lib/signature.d.ts +45 -0
  163. package/lib/signature.js +199 -0
  164. package/lib/signature.js.map +1 -0
  165. package/lib/store.d.ts +156 -0
  166. package/lib/store.js +368 -0
  167. package/lib/store.js.map +1 -0
  168. package/lib/sync.d.ts +220 -0
  169. package/lib/sync.js +810 -0
  170. package/lib/sync.js.map +1 -0
  171. package/lib/tools.d.ts +113 -0
  172. package/lib/tools.js +1090 -0
  173. package/lib/tools.js.map +1 -0
  174. package/lib/treedx/client.d.ts +57 -0
  175. package/lib/treedx/client.js +82 -0
  176. package/lib/treedx/client.js.map +1 -0
  177. package/lib/treedx/provider.d.ts +39 -0
  178. package/lib/treedx/provider.js +479 -0
  179. package/lib/treedx/provider.js.map +1 -0
  180. package/lib/treedx/state.d.ts +27 -0
  181. package/lib/treedx/state.js +65 -0
  182. package/lib/treedx/state.js.map +1 -0
  183. package/lib/types.d.ts +210 -0
  184. package/lib/types.js +31 -0
  185. package/lib/types.js.map +1 -0
  186. package/lib/version.d.ts +9 -0
  187. package/lib/version.js +10 -0
  188. package/lib/version.js.map +1 -0
  189. package/lib/vocabulary.d.ts +41 -0
  190. package/lib/vocabulary.js +118 -0
  191. package/lib/vocabulary.js.map +1 -0
  192. package/lib/warnings.d.ts +10 -0
  193. package/lib/warnings.js +18 -0
  194. package/lib/warnings.js.map +1 -0
  195. package/package.json +151 -4
@@ -0,0 +1,165 @@
1
+ /**
2
+ * The one boundary that decides which bytes of the durable log are CONVERSATION
3
+ * and which are host-injected context (knowledge-repo.md §15 amendment,
4
+ * "host-injected context is not conversation").
5
+ *
6
+ * Why this exists: the harness injects project context — workspace instruction
7
+ * files (whatever a given project's AGENTS.md holds), the runtime-context
8
+ * snapshot, the skill catalog — and injected events arrive in the log either as
9
+ * their own `user/message` (source.kind `agent-instructions`, `plugin`, …) or
10
+ * riding INSIDE human messages as `<system-reminder>` spans. A target project's
11
+ * instruction text is that project's business: it must not be archived into the
12
+ * shared knowledge corpus, searched by other workspaces, re-quoted into
13
+ * enrichment input, or bloating a child's re-archive of its own TOC notice
14
+ * generation after generation.
15
+ *
16
+ * Precedents this rule follows, not invents:
17
+ * - `signature.ts` (r28) already excluded these events from per-turn topic
18
+ * signatures after measuring their poison: every signature gained the
19
+ * injected filenames and drowned the real topic signal.
20
+ * - `tests/e2e/model-proxy.ts` (user decision 2026-09-20, AGENTS-BLIND) already
21
+ * masks the same classes for tape matching, on the same reasoning:
22
+ * development artifacts, every real project ships its own.
23
+ *
24
+ * What is NOT touched: the durable session log (invariant: append-only, always —
25
+ * the originals live on in the parent session), and genuine conversation bytes
26
+ * (a human typing the words "AGENTS.md" in prose is conversation and stays
27
+ * verbatim; only the structural injected forms are removed).
28
+ *
29
+ * Pure module: no cordis, no fs, no network.
30
+ */
31
+ /** A user/message is human-authored when it carries no source, or source.kind
32
+ * 'user' (unlabelled keeps working for seeded fixtures — same rule the
33
+ * signature layer has used since r28). Everything else — 'agent-instructions',
34
+ * 'plugin' (the TOC notice, runtime snapshots), 'tool', 'system' — is injected
35
+ * context, not conversation. Exported so signature.ts imports the SAME
36
+ * predicate; the two layers must never drift on what counts as a turn. */
37
+ export function isHumanAuthored(data) {
38
+ const source = data.source;
39
+ return source?.kind === undefined || source.kind === 'user';
40
+ }
41
+ /**
42
+ * The ONE targeted exception to the screen (human decision 2026-10-10): the harness's
43
+ * own mode-switch narration — `source: { kind: 'plugin', plugin: 'plan-mode',
44
+ * form: 'notice' }`, "The user switched this session to plan mode." — IS conversation.
45
+ *
46
+ * The screen exists so a project's private instruction files never travel in a shared
47
+ * corpus. A mode flip the human performed is not that: it is a fact about THIS session,
48
+ * and it is the seam that answers "was this approved before or after we started
49
+ * implementing?". Named by plugin AND form, so it cannot be widened by accident into
50
+ * the general plugin-source exemption — an instruction file or a runtime snapshot
51
+ * arriving with a different form still screens.
52
+ */
53
+ export function isSessionStateNarration(data) {
54
+ const source = data.source;
55
+ // v4 (issue #43, measured against dsh-plan-mode/lib/index.js:395-410 on
56
+ // 0.2.0-rc.2): the producer owns the kind — `{ kind: 'plan-mode', form: 'notice' }`.
57
+ if (source?.kind === 'plan-mode' && source.form === 'notice')
58
+ return true;
59
+ // #61 Amendment B — a delegate's report is conversation about THIS session's work, not a project's
60
+ // private bytes. Measured shapes (0.2.x logs): `{ kind: 'agent-message', form: 'relay' }` carries
61
+ // "Agent <id> sent a message: <the result>", and `{ kind: 'subagent-settled', form: 'notice' }` is
62
+ // when that delegate finished. The old rule screened both, which cost 44 markers / 248,150 chars
63
+ // (~62K est tokens) in the first project built on this preset — delegated work vanished through BOTH
64
+ // doors meant to capture it, leaving chapters that recorded a delegation with no outcome.
65
+ // Named by producer AND form like the plan-mode arm above, so it cannot widen: the workspace
66
+ // instruction file (`{ kind: 'agent-instructions', form: 'instructions' }`) is still screened, and
67
+ // `isHumanAuthored` stays strict, because the topic-signature layer must not let a 2 KB report drag a
68
+ // signature toward the filenames it mentions (record r28).
69
+ if (source?.kind === 'agent-message' && source.form === 'relay')
70
+ return true;
71
+ if (source?.kind === 'subagent-settled' && source.form === 'notice')
72
+ return true;
73
+ // #113: a Suggested Next Step the human armed. It is conversation the human authorized — a chapter that
74
+ // records an autorun response without the prompt that produced it is unexplainable — so it is captured like
75
+ // the others. Deliberately NOT `kind: 'user'`: the host's goal tool decides "a direct human turn exists" by
76
+ // looking for exactly that (measured, dsh-tool-goal:49-65), and laundering our loop through it would let a
77
+ // suggestion authorize model-side goal operations. Named by producer AND form like every arm above: the TOC
78
+ // notice (`form: 'snapshot'`) and any other plugin's message stay screened.
79
+ if (source?.kind === 'plugin:dsh-chapters' && source.form === 'notice')
80
+ return true;
81
+ // v3 logs, still replayed from disk: the retired wrapper the exception was
82
+ // written against. Both arms stay named by producer AND form — neither widens.
83
+ return source?.kind === 'plugin' && source.plugin === 'plan-mode' && source.form === 'notice';
84
+ }
85
+ /**
86
+ * Does this event belong in the archive as conversation?
87
+ *
88
+ * Deliberately NARROWER than `isHumanAuthored`: the topic-signature layer keeps using
89
+ * the strict predicate, because the r28 lesson was that injected text poisons topic
90
+ * merging (every signature gained the injected filenames). A mode narration is one
91
+ * short sentence, so archiving it is worth more than the risk of letting it move a
92
+ * chapter boundary — and the two uses are separate functions on purpose.
93
+ */
94
+ export function isArchivableConversation(data) {
95
+ return isHumanAuthored(data) || isSessionStateNarration(data);
96
+ }
97
+ /** Label for the omission marker: kind, plus plugin name when the source
98
+ * carries one (so `plugin:dsh-chapters` is recognizable in a chapter). */
99
+ export function sourceLabel(data) {
100
+ const source = data.source;
101
+ if (source === undefined)
102
+ return 'unlabelled';
103
+ if (source.kind === 'plugin:dsh-chapters' && source.form === 'notice')
104
+ return 'suggested-prompt';
105
+ return source.kind === 'plugin' && typeof source.plugin === 'string'
106
+ ? `plugin:${source.plugin}`
107
+ : String(source.kind ?? 'unknown');
108
+ }
109
+ /**
110
+ * The structural injection span. Anchored on the exact harness tag, case
111
+ * sensitive (the tape's own skill-catalog rule is case sensitive for the same
112
+ * reason: ordinary prose quoting the tag is vanishingly rare, and the cost of a
113
+ * false positive is a marked, countable omission — never a silent rewrite of
114
+ * history, the original bytes stay in the session log). The unclosed-tail form
115
+ * `[\s\S]*?(</system-reminder>|$)` mirrors the tape's masking of truncated
116
+ * injections.
117
+ */
118
+ /**
119
+ * The ONE predicate for "this text is host-injected context, not something the human wrote".
120
+ *
121
+ * `source.kind` remains the primary gate (`isHumanAuthored`, `screenConversation`) — but some consumers
122
+ * see only flattened provider text with NO source at all, first among them `deriveIdentity` choosing a
123
+ * chapter TITLE. Until #59 that path carried a private regex (`HARNESS_PROSE`) that had silently fallen
124
+ * behind the host's injected kinds, so a `time-context` snapshot became a chapter title, slug, FILENAME
125
+ * and a live TOC citation (`020-time-sampled-while-preparing-turn-11-step-24-202.md`, cited by a
126
+ * checkpoint and never written). AGENTS.md: one predicate per semantic decision, and a source-shape row
127
+ * fails if a second list reappears.
128
+ *
129
+ * Anchored at the START on purpose: a human quoting "time sampled while preparing" mid-sentence is
130
+ * conversation and stays verbatim — the same line the body screen draws.
131
+ *
132
+ * `this is an automatically generated checkpoint` joined 2026-10-23 (#87): it is the host's own
133
+ * in-place relief preamble — machine state that replaces earlier turns, never something a human asked.
134
+ * It had to join THIS list rather than get a second one, because #87's fix asks the same question
135
+ * ("is this a human turn?") at the compaction seam, and a parallel list is exactly the #59 drift this
136
+ * comment already records.
137
+ */
138
+ const INJECTED_PROSE = /^\s*(<system-reminder>|<codegraph_context|system reminder\b|current runtime context\.|time sampled while preparing|browser time zone for this request|\[workspace instructions?\b|this session is running under|the following workspace instructions|agent [A-Za-z0-9_-]{8,} sent a message|background subagent [A-Za-z0-9_-]{8,} finished|this is an automatically generated checkpoint)/i;
139
+ export function isInjectedProse(text) {
140
+ return INJECTED_PROSE.test(text);
141
+ }
142
+ const REMINDER_SPAN = /<system-reminder>[\s\S]*?(?:<\/system-reminder>|$)/g;
143
+ /** Strip every injected span from a message's text; deterministic, idempotent
144
+ * (the marker we emit below cannot re-trigger this rule). */
145
+ export function stripReminderSpans(text) {
146
+ const removed = text.match(REMINDER_SPAN)?.join('').length ?? 0;
147
+ if (removed === 0)
148
+ return { text, removedChars: 0 };
149
+ return { text: text.replace(REMINDER_SPAN, '').trim(), removedChars: removed };
150
+ }
151
+ /** The visible, greppable, countable marker an omission leaves behind. One
152
+ * shape for every class so downstream assertions (and the sentinel integration
153
+ * test) pin a single prefix. */
154
+ export const omittedMarker = (what, chars) => `⟦omitted:host-injected ${what}, ${chars} chars — project state, not conversation⟧`;
155
+ export function screenConversation(data, rawText, role) {
156
+ if (role === 'user' && !isArchivableConversation(data)) {
157
+ return { text: '', omittedEvent: true, removedChars: rawText.length, sourceKind: sourceLabel(data) };
158
+ }
159
+ // Assistant text is model output, but a runtime snapshot has been observed
160
+ // appended to role user|system by the harness — screening assistant spans
161
+ // too costs one regex and can only ever mark, never invent, text.
162
+ const { text, removedChars } = stripReminderSpans(rawText);
163
+ return { text, omittedEvent: false, removedChars, sourceKind: 'user' };
164
+ }
165
+ //# sourceMappingURL=injections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injections.js","sourceRoot":"","sources":["../src/injections.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The last-resort deferral's arithmetic (#78 option A) — kept apart from the engine so the rule that
3
+ * decides WHEN an oversized node may override the operator's arrival floor is testable without a
4
+ * session, a meter, or a model.
5
+ *
6
+ * The number is DERIVED, never invented: a node can only make relief impossible if it cannot fit
7
+ * beside the content relief is obliged to keep. That obligation is the base engine's own verbatim
8
+ * retain tail plus its reserved completion budget, both of which it already computes — so the
9
+ * ceiling is `window − retainBudget − reservedCompletion`, and no new tunable appears (AGENTS: no
10
+ * hardcoded tunables; `cordis.yml` can only change what config declares).
11
+ */
12
+ export interface LastResortInputs {
13
+ windowTokens: number;
14
+ retainRatio: number;
15
+ retainTokens?: number;
16
+ reservedCompletionTokens: number;
17
+ }
18
+ /**
19
+ * The token size above which an inline `tool/result` cannot coexist with the mandatory tail, or
20
+ * `null` when nothing can be concluded. Returning `null` — rather than 0, or a guess — is the point:
21
+ * a wrong ceiling here silently overrides an operator's configured economy knob, which is the one
22
+ * thing this feature must never do.
23
+ */
24
+ export declare function lastResortCeilingTokens(v: LastResortInputs): number | null;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The token size above which an inline `tool/result` cannot coexist with the mandatory tail, or
3
+ * `null` when nothing can be concluded. Returning `null` — rather than 0, or a guess — is the point:
4
+ * a wrong ceiling here silently overrides an operator's configured economy knob, which is the one
5
+ * thing this feature must never do.
6
+ */
7
+ export function lastResortCeilingTokens(v) {
8
+ const { windowTokens, retainRatio, retainTokens, reservedCompletionTokens } = v;
9
+ const finite = (n) => typeof n === 'number' && Number.isFinite(n);
10
+ if (!finite(windowTokens) || !finite(reservedCompletionTokens))
11
+ return null;
12
+ if (windowTokens <= 0 || reservedCompletionTokens < 0)
13
+ return null;
14
+ // An explicit retainTokens wins over the ratio (both are base config the host may carry either way).
15
+ let retainBudget;
16
+ if (finite(retainTokens) && retainTokens > 0) {
17
+ retainBudget = retainTokens;
18
+ }
19
+ else {
20
+ if (!finite(retainRatio) || retainRatio <= 0 || retainRatio >= 1)
21
+ return null;
22
+ retainBudget = Math.floor(windowTokens * retainRatio);
23
+ }
24
+ const ceiling = windowTokens - retainBudget - reservedCompletionTokens;
25
+ return ceiling > 0 ? Math.floor(ceiling) : null;
26
+ }
27
+ //# sourceMappingURL=last-resort.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"last-resort.js","sourceRoot":"","sources":["../src/last-resort.ts"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,CAAmB;IACzD,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,wBAAwB,EAAE,GAAG,CAAC,CAAA;IAC/E,MAAM,MAAM,GAAG,CAAC,CAAU,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;IACvF,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,wBAAwB,CAAC;QAAE,OAAO,IAAI,CAAA;IAC3E,IAAI,YAAY,IAAI,CAAC,IAAI,wBAAwB,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAClE,qGAAqG;IACrG,IAAI,YAAoB,CAAA;IACxB,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QAC7C,YAAY,GAAG,YAAY,CAAA;IAC7B,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,WAAW,IAAI,CAAC,IAAI,WAAW,IAAI,CAAC;YAAE,OAAO,IAAI,CAAA;QAC7E,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,WAAW,CAAC,CAAA;IACvD,CAAC;IACD,MAAM,OAAO,GAAG,YAAY,GAAG,YAAY,GAAG,wBAAwB,CAAA;IACtE,OAAO,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACjD,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Event-shape readers — the single place that knows how DSH nests payloads in
3
+ * the durable log.
4
+ *
5
+ * Extracted from `render.ts` (docs/plans-carriage.md § 1: the chapter renderer and the plan detector
6
+ * must agree on which `tool/result` answers which `tool/call`, or a plan's verdict could be read
7
+ * differently in two places). Shapes here were MEASURED from real logs, never guessed — see
8
+ * tests/unit/render.test.ts:18-22 for the fixture mirror and spikes/probe/FINDINGS.md.
9
+ *
10
+ * Pure module: no cordis, no fs, no DSH imports.
11
+ */
12
+ import type { ContentBlock, SessionEventLike } from './types.ts';
13
+ export declare const blocksOf: (value: unknown) => ContentBlock[];
14
+ export declare const textOfBlocks: (value: unknown) => string;
15
+ /**
16
+ * Flatten a tool-result payload, which nests a content array inside a tool-result block.
17
+ * REAL shape (measured e2e, 2026-09-19): `message.content` blocks are
18
+ * `{ type: 'tool-result', toolCallId, isError, content: [{ type:'text', text }] }`.
19
+ */
20
+ export declare function toolResultText(data: Record<string, unknown>): string;
21
+ /** True when the paired tool-result carries an error flag (a failed call). */
22
+ export declare function toolResultIsError(data: Record<string, unknown>): boolean;
23
+ /** The callId a `tool/result` answers (measured: it rides on `message.source.callId`). */
24
+ export declare function resultCallId(ev: SessionEventLike): string | undefined;
25
+ /** The callId a `tool/call` carries. */
26
+ export declare const callIdOf: (ev: SessionEventLike) => string | undefined;
27
+ /** Does this result answer this call? Both sides must name a callId — never guess by adjacency. */
28
+ export declare function sameCall(call: SessionEventLike, result: SessionEventLike): boolean;
29
+ /**
30
+ * The LAST result event for one callId, over an already-range-filtered list.
31
+ * Host redelivery is real (measured 2026-09-28: 25 calls carried TWO tool/result
32
+ * events), so the newest copy is the authoritative verdict.
33
+ */
34
+ export declare function lastResultFor(results: readonly SessionEventLike[], callId: string): SessionEventLike | undefined;
35
+ /** `tool/call` arguments as one serialized string (object or pre-serialized string both occur). */
36
+ export declare function callArgsText(data: Record<string, unknown>): string;
37
+ /** One named argument from either shape (string JSON or object). */
38
+ export declare function callArg(data: Record<string, unknown>, name: string): string | undefined;
@@ -0,0 +1,93 @@
1
+ export const blocksOf = (value) => Array.isArray(value) ? value : [];
2
+ export const textOfBlocks = (value) => blocksOf(value)
3
+ .filter((b) => b.type === 'text')
4
+ .map((b) => b.text ?? '')
5
+ .join('\n');
6
+ /**
7
+ * Flatten a tool-result payload, which nests a content array inside a tool-result block.
8
+ * REAL shape (measured e2e, 2026-09-19): `message.content` blocks are
9
+ * `{ type: 'tool-result', toolCallId, isError, content: [{ type:'text', text }] }`.
10
+ */
11
+ export function toolResultText(data) {
12
+ const message = data.message;
13
+ const inner = blocksOf(message?.content ?? data.content);
14
+ const parts = [];
15
+ for (const block of inner) {
16
+ if (block.type === 'text')
17
+ parts.push(block.text ?? '');
18
+ else if (block.type === 'tool-result') {
19
+ const nested = block.content;
20
+ parts.push(typeof nested === 'string' ? nested : textOfBlocks(nested));
21
+ }
22
+ }
23
+ return parts.filter((p) => p.length > 0).join('\n');
24
+ }
25
+ /** True when the paired tool-result carries an error flag (a failed call). */
26
+ export function toolResultIsError(data) {
27
+ const message = data.message;
28
+ const inner = blocksOf(message?.content ?? data.content);
29
+ return inner.some((b) => b.isError === true);
30
+ }
31
+ /** The callId a `tool/result` answers (measured: it rides on `message.source.callId`). */
32
+ export function resultCallId(ev) {
33
+ const msg = ev.data?.message;
34
+ const cid = msg?.source?.callId;
35
+ if (typeof cid === 'string')
36
+ return cid;
37
+ const flat = ev.data?.callId;
38
+ return typeof flat === 'string' ? flat : undefined;
39
+ }
40
+ /** The callId a `tool/call` carries. */
41
+ export const callIdOf = (ev) => {
42
+ const cid = ev.data?.callId;
43
+ return typeof cid === 'string' ? cid : undefined;
44
+ };
45
+ /** Does this result answer this call? Both sides must name a callId — never guess by adjacency. */
46
+ export function sameCall(call, result) {
47
+ const cid = callIdOf(call);
48
+ const rcid = resultCallId(result);
49
+ return cid !== undefined && rcid !== undefined && String(cid) === String(rcid);
50
+ }
51
+ /**
52
+ * The LAST result event for one callId, over an already-range-filtered list.
53
+ * Host redelivery is real (measured 2026-09-28: 25 calls carried TWO tool/result
54
+ * events), so the newest copy is the authoritative verdict.
55
+ */
56
+ export function lastResultFor(results, callId) {
57
+ let last;
58
+ for (const ev of results)
59
+ if (resultCallId(ev) === callId)
60
+ last = ev;
61
+ return last;
62
+ }
63
+ /** `tool/call` arguments as one serialized string (object or pre-serialized string both occur). */
64
+ export function callArgsText(data) {
65
+ const args = data.arguments;
66
+ if (typeof args === 'string')
67
+ return args;
68
+ try {
69
+ return JSON.stringify(args ?? {});
70
+ }
71
+ catch {
72
+ return String(args);
73
+ }
74
+ }
75
+ /** One named argument from either shape (string JSON or object). */
76
+ export function callArg(data, name) {
77
+ const args = data.arguments;
78
+ let obj;
79
+ if (typeof args === 'string') {
80
+ try {
81
+ obj = JSON.parse(args);
82
+ }
83
+ catch {
84
+ obj = undefined;
85
+ }
86
+ }
87
+ else if (args !== null && typeof args === 'object') {
88
+ obj = args;
89
+ }
90
+ const value = obj?.[name];
91
+ return typeof value === 'string' ? value : undefined;
92
+ }
93
+ //# sourceMappingURL=log-shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-shape.js","sourceRoot":"","sources":["../src/log-shape.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * #173 — the /chapters-models pure core: discovery for the three model settings.
3
+ *
4
+ * The three keys (`enrichment.model`, `rules.model`, the prompt model) all take a `provider/model`
5
+ * route, and before this command nothing in the product told a human which identifiers this host
6
+ * accepts — a wrong guess failed at call time as a routing error. This mirrors the host's own
7
+ * sanctioned discovery tool (`list_subagent_models` in
8
+ * examples/dsh/packages/subagent/tool-subagent/src/list-models.ts): `listProviders()` is SYNC,
9
+ * `listModels(provider)` is the ADVISORY catalog, and `resolveModelInfo(provider, model)` is the
10
+ * exact-route drill-down that adds what only the adapter knows (context window, reasoning efforts).
11
+ *
12
+ * Two honesty rules the rendering never drops: catalog membership is advisory — the host routes
13
+ * unlisted ids, so an empty catalog means "no selection offered", never "nothing works" — and a
14
+ * capability the adapter does not advertise is REPORTED ABSENT, not guessed. Like every
15
+ * /chapters-* command this is deterministic text: it never enters the model's context.
16
+ * Pure core; the thin registration lives in commands.ts and the wiring in index.ts.
17
+ */
18
+ export interface ModelsProviderInfo {
19
+ readonly id: string;
20
+ readonly name: string;
21
+ }
22
+ /** Host-shaped `LlmModelInfo` (examples/dsh/packages/llm/llm/src/types.ts:348). */
23
+ export interface ModelsModelInfo {
24
+ readonly provider: string;
25
+ readonly id: string;
26
+ readonly name: string;
27
+ readonly description?: string | undefined;
28
+ }
29
+ /** Host-shaped `LlmResolvedModelInfo` — the fields this command consumes, no more. */
30
+ export interface ModelsResolvedInfo extends ModelsModelInfo {
31
+ readonly context?: {
32
+ readonly contextWindow?: number;
33
+ } | undefined;
34
+ readonly reasoning?: {
35
+ readonly efforts?: readonly {
36
+ readonly id: string;
37
+ readonly name: string;
38
+ readonly description?: string;
39
+ }[] | undefined;
40
+ readonly defaultEffort?: string | undefined;
41
+ } | undefined;
42
+ }
43
+ /** The `llm` service surface this command rides — stub it in the HOST's shape, never an easier one. */
44
+ export interface ModelsLlm {
45
+ listProviders(): readonly ModelsProviderInfo[];
46
+ listModels(provider: string): Promise<readonly ModelsModelInfo[]>;
47
+ resolveModelInfo(provider: string, model: string, signal?: AbortSignal): Promise<ModelsResolvedInfo>;
48
+ }
49
+ /** Where the value the human sees right now came from — rendered, because "set" and "fell back" differ. */
50
+ export type CurrentSource = 'settings' | 'config' | 'conversation-route' | 'rides-enrichment' | 'off';
51
+ export interface ModelsIo {
52
+ /** Absent on planes without the host llm service — the command refuses cleanly, never crashes. */
53
+ llm?: ModelsLlm | undefined;
54
+ /** The three settings' effective values, pre-resolved by the wiring (config → settings → fallback). */
55
+ current: {
56
+ enrichment: {
57
+ value: string;
58
+ source: CurrentSource;
59
+ };
60
+ rules: {
61
+ value: string;
62
+ source: CurrentSource;
63
+ };
64
+ prompt: {
65
+ value: string;
66
+ source: CurrentSource;
67
+ };
68
+ };
69
+ }
70
+ export interface ModelsReply {
71
+ kind: 'success' | 'error';
72
+ text: string;
73
+ }
74
+ export declare function modelsCommand(io: ModelsIo, args: string, signal?: AbortSignal): Promise<ModelsReply>;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * #173 — the /chapters-models pure core: discovery for the three model settings.
3
+ *
4
+ * The three keys (`enrichment.model`, `rules.model`, the prompt model) all take a `provider/model`
5
+ * route, and before this command nothing in the product told a human which identifiers this host
6
+ * accepts — a wrong guess failed at call time as a routing error. This mirrors the host's own
7
+ * sanctioned discovery tool (`list_subagent_models` in
8
+ * examples/dsh/packages/subagent/tool-subagent/src/list-models.ts): `listProviders()` is SYNC,
9
+ * `listModels(provider)` is the ADVISORY catalog, and `resolveModelInfo(provider, model)` is the
10
+ * exact-route drill-down that adds what only the adapter knows (context window, reasoning efforts).
11
+ *
12
+ * Two honesty rules the rendering never drops: catalog membership is advisory — the host routes
13
+ * unlisted ids, so an empty catalog means "no selection offered", never "nothing works" — and a
14
+ * capability the adapter does not advertise is REPORTED ABSENT, not guessed. Like every
15
+ * /chapters-* command this is deterministic text: it never enters the model's context.
16
+ * Pure core; the thin registration lives in commands.ts and the wiring in index.ts.
17
+ */
18
+ const USAGE = 'usage: /chapters-models [<provider> [<model>]] — a paste-ready provider/model id works as one arg';
19
+ const ADVISORY = 'catalog membership is advisory: the host routes unlisted model ids too, and an empty catalog means no selection is offered — not that nothing works.';
20
+ function currentFooter(current) {
21
+ const line = (label, v, unset, setter) => ` ${label.padEnd(16)} ${v.value !== '' ? `${v.value} (from ${v.source})` : unset}\n ${''.padEnd(16)} set with: ${setter}`;
22
+ return 'current model settings:\n'
23
+ + line('enrichment.model', current.enrichment, '(unset — rides the conversation route)', '/chapters-enrich model <provider/model> | clear') + '\n'
24
+ + line('rules.model', current.rules, '(unset — mining rides the enrichment call)', '/chapters-rule model <provider/model> | clear') + '\n'
25
+ + line('prompt model', current.prompt, 'OFF — setting a model IS the enable (§28)', '/chapters-prompt model <provider/model> | off');
26
+ }
27
+ function modelLine(provider, m) {
28
+ return ` ${provider}/${m.id} — ${m.name}${m.description === undefined || m.description === '' ? '' : `: ${m.description}`}`;
29
+ }
30
+ export async function modelsCommand(io, args, signal) {
31
+ const llm = io.llm;
32
+ if (llm === undefined) {
33
+ return { kind: 'error', text: `the llm service is unavailable on this plane — cannot enumerate models.\n${currentFooter(io.current)}` };
34
+ }
35
+ const parts = args.trim().split(/\s+/).filter((p) => p.length > 0);
36
+ // One arg carrying a slash is a paste-ready id — this command's own output format.
37
+ let provider;
38
+ let model;
39
+ if (parts.length === 1 && parts[0].includes('/')) {
40
+ const i = parts[0].indexOf('/');
41
+ provider = parts[0].slice(0, i);
42
+ model = parts[0].slice(i + 1);
43
+ }
44
+ else if (parts.length === 1) {
45
+ provider = parts[0];
46
+ }
47
+ else if (parts.length === 2) {
48
+ provider = parts[0];
49
+ model = parts[1];
50
+ }
51
+ else if (parts.length > 2) {
52
+ return { kind: 'error', text: USAGE };
53
+ }
54
+ const providers = llm.listProviders();
55
+ if (provider === undefined) {
56
+ const sections = [];
57
+ for (const p of providers) {
58
+ const models = await llm.listModels(p.id);
59
+ sections.push(`provider ${p.id} — ${p.name}\n`
60
+ + (models.length === 0
61
+ ? ` (no advertised models for ${p.id} — ${ADVISORY})`
62
+ : models.map((m) => modelLine(p.id, m)).join('\n')));
63
+ }
64
+ const body = providers.length === 0
65
+ ? `(no LLM providers are registered on this host — ${ADVISORY})`
66
+ : sections.join('\n\n');
67
+ return { kind: 'success', text: `${body}\n\n${ADVISORY}\n\n${currentFooter(io.current)}` };
68
+ }
69
+ if (provider.length === 0 || (model !== undefined && model.length === 0)) {
70
+ return { kind: 'error', text: USAGE };
71
+ }
72
+ const known = providers.find((p) => p.id === provider);
73
+ if (known === undefined) {
74
+ const available = providers.map((p) => p.id).join(', ') || '(none)';
75
+ return { kind: 'error', text: `LLM provider "${provider}" is not registered; available providers: ${available}` };
76
+ }
77
+ if (model === undefined) {
78
+ const models = await llm.listModels(known.id);
79
+ const body = models.length === 0
80
+ ? `(no advertised models for ${known.id} — ${ADVISORY})`
81
+ : `provider ${known.id} — ${known.name}\n${models.map((m) => modelLine(known.id, m)).join('\n')}\n\n${ADVISORY}`;
82
+ return { kind: 'success', text: `${body}\n\n${currentFooter(io.current)}` };
83
+ }
84
+ try {
85
+ const info = await llm.resolveModelInfo(known.id, model, signal);
86
+ const efforts = info.reasoning?.efforts ?? [];
87
+ const lines = [
88
+ `${known.id}/${info.id} — ${info.name}${info.description === undefined || info.description === '' ? '' : `: ${info.description}`}`,
89
+ `context window: ${info.context?.contextWindow !== undefined ? `${String(info.context.contextWindow)} tokens` : 'not advertised by this adapter'}`,
90
+ efforts.length === 0
91
+ ? 'reasoning efforts: (no advertised reasoning efforts)'
92
+ : `reasoning efforts:\n${efforts.map((e) => ` ${e.id}${info.reasoning?.defaultEffort === e.id ? ' (default)' : ''} — ${e.name}${e.description === undefined || e.description === '' ? '' : `: ${e.description}`}`).join('\n')}`,
93
+ ];
94
+ return { kind: 'success', text: `${lines.join('\n')}\n\n${currentFooter(io.current)}` };
95
+ }
96
+ catch (error) {
97
+ return { kind: 'error', text: `resolveModelInfo(${known.id}, ${model}) failed: ${String(error?.message ?? error).slice(0, 200)}` };
98
+ }
99
+ }
100
+ //# sourceMappingURL=models-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models-command.js","sourceRoot":"","sources":["../src/models-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The TOC notice: the single synthetic `user/message` that seeds a continuation.
3
+ *
4
+ * This module exists because of the 66-log lesson from `dsh-session-fork`
5
+ * (docs/development.md § L1): a message source outside the kernel's accepted
6
+ * vocabulary is not a cosmetic bug — stored logs that fail replay refuse to
7
+ * load, retroactively. The kernel's seed validator is not published as a
8
+ * callable, so the vocabulary here is pinned two ways and guarded by
9
+ * test/notice.test.ts + the v4 admission rows in tests/unit/host-seed-contract.test.ts:
10
+ *
11
+ * 1. Behaviorally, from the Phase 0 seed probes and the 2026-10-18 v4 drift
12
+ * (issue #43) — the retired forms are reproduced as refusals, not dropped:
13
+ * a string `source` was rejected as `invalid source`; session format v4
14
+ * rejects the bare `kind: 'plugin'` wrapper with the kernel's own words,
15
+ * "format v4 message requires a producer-owned source kind"
16
+ * (`@deepseek-ai/dsh-session-format-v3-to-v4/lib/index.js:126`,
17
+ * `dsh-session-persistence-jsonl/lib/worker.cjs:10902` on 0.2.0-rc.2).
18
+ * 2. Structurally, against the installed kernel: `role: 'user'`; a non-empty
19
+ * string `id`; array `content`; contiguous seed from `seq = 0`;
20
+ * `source.kind` a non-empty producer-owned string; `ContextForm` values
21
+ * `instructions | catalog | notice | relay | recall | snapshot`
22
+ * (`dsh-llm/lib/types/message.d.ts` — verified against 0.2.0-rc.2).
23
+ *
24
+ * v4 source attribution: each producer OWNS its kind. Third-party producers
25
+ * use the `plugin:<id>` form — measured live, the 0.2.0-rc.2 host writes its
26
+ * own logs with `plugin:dsh-codegraph` + `form: 'notice'`. So the notice
27
+ * carries `{ kind: 'plugin:dsh-chapters', form: 'snapshot', sections: [...] }`
28
+ * and NO `plugin` field (the v3→v4 migration's own `rewritePluginSource`
29
+ * drops it: "kind is the producer's own kind and whose `plugin` field is
30
+ * dropped"). `sourceLabel` renders `plugin:dsh-chapters` for BOTH the retired
31
+ * wrapper and the colon kind — byte-identically — so omission markers in
32
+ * already-archived chapters keep matching.
33
+ * INVARIANT 2: exactly one event, at seq 0. Never a slice of the parent's log.
34
+ */
35
+ import type { SessionEventLike } from './types.ts';
36
+ /** Our producer id. Changing this orphans every already-seeded continuation. */
37
+ export declare const NOTICE_PLUGIN = "dsh-chapters";
38
+ /** The v4 producer-owned source kind (issue #43). `plugin:<producer>` is the
39
+ * form the 0.2.x kernel itself admits for third-party producers; the bare
40
+ * `plugin` wrapper it retired. */
41
+ export declare const NOTICE_SOURCE_KIND = "plugin:dsh-chapters";
42
+ /** The section name our TOC rides under inside the snapshot. */
43
+ export declare const NOTICE_SECTION = "chapters:toc";
44
+ /**
45
+ * The kinds this plugin may emit, pinned to what the installed v4 kernel
46
+ * admits (seed/load boundary + persistence row admission). The retired v3
47
+ * `plugin` wrapper is deliberately NOT in this list — it is refused below
48
+ * with the kernel's own refusal text.
49
+ */
50
+ export declare const ACCEPTED_SOURCE_KINDS: readonly ["user", "plugin:dsh-chapters"];
51
+ /** `ContextForm` values, exactly as published (semantic, never visual). */
52
+ export declare const ACCEPTED_CONTEXT_FORMS: readonly ["instructions", "catalog", "snapshot", "notice", "relay", "recall"];
53
+ export interface TocNoticeOptions {
54
+ /** Fresh unique id per notice — the kernel rejects a seed message without one. */
55
+ newId: () => string;
56
+ time?: number;
57
+ }
58
+ /**
59
+ * Build the one seed event for a continuation. Throws rather than emit an
60
+ * event the kernel would reject at seed time — a refusal here is cheap, a
61
+ * bricked log is not.
62
+ */
63
+ export declare function buildTocNotice(tocText: string, opts: TocNoticeOptions): SessionEventLike;
64
+ /**
65
+ * Transcription of the kernel's seed-time checks (message-shape + v4 source
66
+ * admission + the recorded rejections). Kept as an exported predicate so tests
67
+ * pin our output against the same rules the host applies, and any future
68
+ * notice variant fails HERE, at the boundary of authorship. The v4 rows are
69
+ * drift-gated against the kernel's own admission function
70
+ * (`dsh-session-format-v3-to-v4` `assertV4RowAdmission`) in
71
+ * tests/unit/host-seed-contract.test.ts — this transcription is the fast lane,
72
+ * the kernel is the authority.
73
+ */
74
+ export declare function assertKernelAcceptedUserMessage(event: SessionEventLike): void;