@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
package/lib/diag.js ADDED
@@ -0,0 +1,186 @@
1
+ /**
2
+ * The one diagnostics seam for the whole plugin (#54).
3
+ *
4
+ * WHY THIS FILE EXISTS. `DSH_CHAPTERS_ENGINE_ERRORS` was written by two copies of the same
5
+ * three lines — one in the realm engine, one in the subagent auto-save listener — and by
6
+ * NEITHER of the places that actually fail a user: the host-plane doors and the shared
7
+ * continuation core. Measured consequence on host 0.2.0-rc.2: `chapters-fork refused: child
8
+ * creation refused: format v4 message requires a producer-owned source kind` ended the entire
9
+ * acceptance chain, and the sink — the one artifact a human or an agent could read without a
10
+ * browser — captured nothing at all. A diagnostic that misses the failure it exists to record
11
+ * is worse than none, because it is trusted.
12
+ *
13
+ * So there is now ONE writer, called from every plane. Rules it upholds:
14
+ *
15
+ * - **Never throws, never blocks.** Diagnostics must not turn a handled failure into an
16
+ * unhandled one, so every filesystem touch is swallowed. The real failure keeps its path.
17
+ * - **Never silent when opted in.** With no sink configured, `diagnostic` is a no-op by
18
+ * design — but `diagnosticRequested` lets a boot-time health line say "no sink is
19
+ * configured", so the absence is visible rather than discovered during an incident.
20
+ * - **Full detail on disk, short detail on screen.** The UI text is sliced to fit; the sink
21
+ * gets the whole message plus the stack when there is one. Refusals that get truncated to
22
+ * 90 characters for the composer are exactly the ones worth reading in full later.
23
+ */
24
+ import fs from 'node:fs';
25
+ /**
26
+ * The sink, in resolution order: `DSH_CHAPTERS_ENGINE_ERRORS` (explicit wins), then the plugin-configured
27
+ * default, then NO file only when the operator wrote the literal `off`.
28
+ *
29
+ * #61 changed the default from silence to a file. Every compaction finalization failure in the SAIP field
30
+ * data left zero trace, and door 5 logged `saved` to a logger nobody reads for three days — a sink that
31
+ * must be configured before it works is a sink that is not configured at the moment you need it.
32
+ */
33
+ let configured;
34
+ /** Called once per plane at boot with `<store-root>/diagnostics.log` (config `diagnosticsLog`). */
35
+ export function configureDiagnostics(path) { configured = path; }
36
+ /**
37
+ * First door wins: the sink lives in the project whose store we are already writing to, so a session
38
+ * that never touches a door leaves no stray file, and a session that does gets its failures next to its
39
+ * chapters. Idempotent — later projects do not relocate the log mid-process.
40
+ */
41
+ export function ensureDiagnosticsDefault(path, capBytes) {
42
+ if (configured === undefined)
43
+ configured = path;
44
+ if (capBytes !== undefined && Number.isFinite(capBytes) && capBytes >= 0)
45
+ maxBytes = capBytes;
46
+ }
47
+ /**
48
+ * The sink cap (#95 follow-up). The diagnostics sink defaults ON and every fix this month made it
49
+ * chattier — a line per enrichment no-op, per abstention, per declined relief — while `diagnostic()` was
50
+ * an uncapped `appendFileSync`. Loudness that costs unbounded disk is a bug the operator meets later, on
51
+ * someone else's machine. The cap is a declared Config key (`diagnosticsMaxBytes`); this constant is only
52
+ * its default. `0` (or a non-finite value) means uncapped, which stays the operator's explicit choice.
53
+ */
54
+ export const DEFAULT_DIAGNOSTICS_MAX_BYTES = 8_388_608;
55
+ let maxBytes = DEFAULT_DIAGNOSTICS_MAX_BYTES;
56
+ /**
57
+ * The size decision, kept pure so a row can prove it without a filesystem: append below the cap, rotate
58
+ * at it. A diagnostic is NEVER dropped — the file is what makes detection count as delivery (#61a).
59
+ */
60
+ export function sinkAction(sizeBytes, capBytes) {
61
+ if (!Number.isFinite(capBytes) || capBytes <= 0)
62
+ return 'append';
63
+ if (!Number.isFinite(sizeBytes) || sizeBytes < 0)
64
+ return 'append';
65
+ return sizeBytes >= capBytes ? 'rotate' : 'append';
66
+ }
67
+ /** Set the cap from plugin config; absent keeps the default. */
68
+ export function configureDiagnosticsCap(bytes) {
69
+ if (bytes !== undefined && Number.isFinite(bytes) && bytes >= 0)
70
+ maxBytes = bytes;
71
+ }
72
+ export const DIAGNOSTICS_OFF = 'off';
73
+ export function diagnosticSink() {
74
+ const p = process.env.DSH_CHAPTERS_ENGINE_ERRORS;
75
+ if (p !== undefined && p !== '')
76
+ return p === DIAGNOSTICS_OFF ? undefined : p;
77
+ return configured;
78
+ }
79
+ /**
80
+ * The status line: says out loud whether a failure WOULD be recorded. Discovered the hard way
81
+ * (#54) — an unconfigured sink is invisible until the one moment you need it, which is exactly
82
+ * when a human is asking "did the feature run, and what did the kernel say?".
83
+ */
84
+ export function diagnosticStatusLine() {
85
+ const sink = diagnosticSink();
86
+ const envSet = process.env.DSH_CHAPTERS_ENGINE_ERRORS;
87
+ if (sink === undefined) {
88
+ return envSet === DIAGNOSTICS_OFF
89
+ // the operator chose silence — say THAT, not "you forgot to configure it"
90
+ ? 'Diagnostics: file sink explicitly disabled (DSH_CHAPTERS_ENGINE_ERRORS=off); door/child failures reach only the profile logger'
91
+ : 'Diagnostics: NO SINK AT ALL — no store root was resolved, so door/child failures reach only the profile logger (this is a bug, not a setting)';
92
+ }
93
+ return `Diagnostics: sink → ${sink}${envSet !== undefined && envSet !== '' ? ' (env override)' : ''}`;
94
+ }
95
+ /** True when SOME diagnostic was written since import — for the status line's honest "no sink" arm. */
96
+ let everWritten = false;
97
+ export function diagnosticEverWritten() { return everWritten; }
98
+ /** Reset for tests only; production never calls it. */
99
+ export function resetDiagnosticStateForTests() { everWritten = false; }
100
+ /**
101
+ * Append one diagnostic record. Returns the composed line even when the write fails, so a
102
+ * caller can ALSO log or include it in a refusal without recomposing it.
103
+ *
104
+ * `where` is the seam that saw it (`'door:fork'`, `'realm:compaction'`, `'auto-save'`) — a
105
+ * timestamp and a message without an origin is how an incident report becomes a guess.
106
+ */
107
+ function statSize(p) {
108
+ try {
109
+ return fs.statSync(p).size;
110
+ }
111
+ catch {
112
+ return 0;
113
+ }
114
+ }
115
+ export function diagnostic(where, message, error) {
116
+ const line = `[${new Date().toISOString()}] [${where}] ${message}` +
117
+ (error === undefined ? '' : ` :: ${describe(error)}`);
118
+ const sink = diagnosticSink();
119
+ if (sink === undefined)
120
+ return line;
121
+ try {
122
+ // appendFileSync, not async: a refused child creation is often followed by the process
123
+ // tearing the session down, and a pending write can be lost exactly when it matters.
124
+ // The cap is checked BEFORE the write, and the write then always happens. Rotation renames the
125
+ // current file to `<sink>.1` (keeping one previous generation) — this is a LOG policy for a file the
126
+ // plugin owns; nothing under `chapters/` is ever moved or truncated by this path, and the
127
+ // append-only archive invariant stays where it lives.
128
+ if (sinkAction(statSize(sink), maxBytes) === 'rotate') {
129
+ try {
130
+ fs.renameSync(sink, sink + '.1');
131
+ }
132
+ catch { /* rotation failure must never lose the line */ }
133
+ }
134
+ fs.appendFileSync(sink, line + '\n');
135
+ everWritten = true;
136
+ }
137
+ catch { /* diagnostics never mask the real failure */ }
138
+ return line;
139
+ }
140
+ /** The full error text a sink record needs: message, then name/code/stack — in that order. */
141
+ export function describe(error) {
142
+ if (error === null || error === undefined)
143
+ return '(no error object)';
144
+ if (typeof error === 'string')
145
+ return error;
146
+ const e = error;
147
+ const bits = [];
148
+ if (typeof e.message === 'string' && e.message !== '')
149
+ bits.push(e.message);
150
+ if (typeof e.name === 'string' && e.name !== 'Error' && !bits[0]?.startsWith(e.name))
151
+ bits.push(`name=${e.name}`);
152
+ if (typeof e.code === 'string')
153
+ bits.push(`code=${e.code}`);
154
+ if (typeof e.stack === 'string' && e.stack !== '')
155
+ bits.push(e.stack.split('\n').slice(0, 6).join(' | '));
156
+ return bits.join(' :: ') || String(error);
157
+ }
158
+ /**
159
+ * A logger warn PLUS the sink, for planes that have a Cordis logger. The logger alone is not
160
+ * enough (r19: headless profiles route `ctx.logger` where nobody can see it), and the sink
161
+ * alone loses the operator channel — so do both, in that order.
162
+ */
163
+ /**
164
+ * The same delivery as `diagnosticWarn`, at INFO — for an OBSERVATION the field needs on disk
165
+ * without anyone reading it as a failure. #78 is the reason this exists: the acceptance suite had
166
+ * always been the caller of `compactIfNeeded`, so "did the host invoke context relief at all?"
167
+ * had no answer in any artifact. A line in the sink answers it, and `info` keeps it out of the
168
+ * warning vocabulary the composer and `/chapters-status` use.
169
+ */
170
+ export function diagnosticNote(logger, where, message) {
171
+ const line = diagnostic(where, message);
172
+ try {
173
+ logger?.info?.(line);
174
+ }
175
+ catch { /* a hostile logger must not eat the diagnostic */ }
176
+ return line;
177
+ }
178
+ export function diagnosticWarn(logger, where, message, error) {
179
+ const line = diagnostic(where, message, error);
180
+ try {
181
+ logger?.warn?.(line);
182
+ }
183
+ catch { /* a hostile logger must not eat the diagnostic */ }
184
+ return line;
185
+ }
186
+ //# sourceMappingURL=diag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diag.js","sourceRoot":"","sources":["../src/diag.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Engine core — the deterministic logic behind ChaptersCompactionEngine, as
3
+ * pure functions over minimal structural types. NO cordis, NO @deepseek-ai
4
+ * imports: this file runs under `node --test` with no harness, which is where
5
+ * the two scariest engine-path bugs get caught — a lost TOC bullet (silently
6
+ * unreachable chapter) and a mis-resolved citation (wrong text archived).
7
+ *
8
+ * The correlation model (docs/host-compaction-seam.md + FINDINGS § Phase 0b):
9
+ * summarize() — sees region CONTENT (no seqs); reserves numbers keyed by the
10
+ * OPEN transaction's compactionId (found by scanning the log:
11
+ * the last `compaction/start` without a matching `end` — the
12
+ * durable lock guarantees uniqueness) and emits the TOC text
13
+ * citing deterministic paths.
14
+ * finalize() — post-commit, from authoritative CompactionResult.shadowedSeqs,
15
+ * renders bodies from the DURABLE LOG, following each
16
+ * `tool/result` replacement's `sourceEventSeqs` chain back to
17
+ * the pre-prune original. Never from summarize's input.
18
+ *
19
+ * Checkpoint merge-forward: the next compaction's span usually includes the
20
+ * previous checkpoint node (r12 measured it). Its TOC bullets are extracted
21
+ * and re-emitted; an embedded LLM-style summary (a host `basic` checkpoint
22
+ * from before this engine was mounted) is carried verbatim ONCE — honest,
23
+ * bounded, and never silently dropped.
24
+ */
25
+ import type { ChapterRange, RenderedChapter } from './types.ts';
26
+ import type { ChapterRecord } from './archive.ts';
27
+ import type { PlanChapter, SessionState } from './registry.ts';
28
+ /** provider tag written into `compaction/summary` — our event-scan discriminator. */
29
+ export declare const CHAPTERS_PROVIDER = "dsh-chapters";
30
+ export declare const DETERMINISTIC_MODEL = "deterministic";
31
+ /** Framing tags the host's summarizer wraps checkpoints in (compaction-basic summarizer.ts:21-22). */
32
+ export declare const SUMMARY_OPEN_TAG = "<compacted-summary>";
33
+ export declare const SUMMARY_CLOSE_TAG = "</compacted-summary>";
34
+ /** One TOC bullet, as re-parsed from prior checkpoints — the accumulation unit. */
35
+ export interface TocBullet {
36
+ title: string;
37
+ path: string;
38
+ summary: string;
39
+ }
40
+ export interface EngineConfig {
41
+ /** Workspace cwd, so a resolved-absolute store root still yields workspace-relative records (#69). */
42
+ workspaceCwd?: string | undefined;
43
+ /** Workspace-relative archive root; must be reachable by the `read` tool. */
44
+ artifactStoreRoot: string;
45
+ /**
46
+ * #85: where approved rules and the knowledge pool are read from when a notice is composed.
47
+ * Defaults to the historical `.dsh-knowledge` for real installs; an e2e boot pins it per port,
48
+ * because a tape is a claim about ONE world and a fixed path inside the developer's repo is not
49
+ * part of that world.
50
+ */
51
+ knowledgeMirrorDir: string;
52
+ /** Lines of the chapter index a continuation notice lists; 0 lists all of them. Keeps the notice's SIZE
53
+ * settled so a leg parked on the compaction boundary can be captured and replayed (#56). */
54
+ noticeIndexCap: number;
55
+ /** #87: how much of the newest human turn the summary carries verbatim when relief
56
+ * shadows the turn that asked it. A cap, never a silent cut — the block says how much was left. */
57
+ openRequestCarryMaxChars: number;
58
+ /** #78 A: the survivability switch. See `docs/host-compaction-seam.md` § the last-resort deferral. */
59
+ lastResortArtifact: boolean;
60
+ /** Diagnostics sink size cap in bytes; 0 = uncapped (docs/verify.md, #61a). */
61
+ diagnosticsMaxBytes: number;
62
+ chapterTokenTarget: number;
63
+ toolResultDeferFloorTokens: number;
64
+ /** Composer tunables (record §4.2/§12) — same register, same defaults as the host plugin's. */
65
+ mergeThreshold: number;
66
+ chapterLimit: number;
67
+ /** #61 Amendment A — the seventh archive moment (see auto-save.ts `shouldForceChildArchive`). */
68
+ childAutoArchive?: boolean | undefined;
69
+ childAutoArchiveMinTurns?: number | undefined;
70
+ /** Arrival-time artifacting floor (architecture.md amendment 2026-09-19):
71
+ * tool results at/above this many estimated tokens are stored as artifacts
72
+ * and replaced by reference stubs BEFORE the next request is composed. */
73
+ toolResultArtifactTokens: number;
74
+ /** Verbatim plan characters printed in a chapter's Plans block (plans-carriage § 2).
75
+ * The full plan is ALWAYS artifact-backed, so this is display economy, not data. */
76
+ planInlineChars: number;
77
+ /** Above this many estimated tokens a tool-call ARGUMENT is artifact-backed and the
78
+ * inline text is a bounded head. 0 = off (default): enabling it changes chapter
79
+ * bytes, hence the model tapes — a deployment opts in deliberately. */
80
+ callArgArtifactTokens: number;
81
+ /** Elicit a plot note with one bounded call when the model wrote none. */
82
+ elicitedPlot: boolean;
83
+ /**
84
+ * Carry the operative session plan into every in-place checkpoint (the PLOT
85
+ * mechanism, applied to plans — docs/plans-carriage.md § 9). Shadowing can hide the
86
+ * chapter that holds a plan; a directive that survives only in a file the agent may
87
+ * never open is a directive it drifts from.
88
+ */
89
+ plansInCheckpoint: boolean;
90
+ /** Bounded plan characters carried in the checkpoint text; the archive keeps all. */
91
+ planCheckpointChars: number;
92
+ }
93
+ export declare const ENGINE_CONFIG_DEFAULTS: EngineConfig;
94
+ export interface SummarizeInputLike {
95
+ readonly tools?: readonly unknown[];
96
+ readonly messages: readonly EngineMessage[];
97
+ }
98
+ export interface EngineMessage {
99
+ readonly role: string;
100
+ readonly content?: readonly {
101
+ type: string;
102
+ text?: string;
103
+ }[];
104
+ }
105
+ export interface SummarizeResultLike {
106
+ summary: {
107
+ type: 'text';
108
+ text: string;
109
+ }[];
110
+ provider: string;
111
+ model: string;
112
+ }
113
+ /** The slice of `Session` the core reads. Kept small so fakes are honest. */
114
+ export interface EngineSession {
115
+ readonly id: string;
116
+ readonly seq: number;
117
+ eventAt(seq: number): EngineSessionEvent | undefined;
118
+ /**
119
+ * Present on the real host Session (a class) — `reconstructShadowedSeqs`
120
+ * needs them to map summarize-input messages back to log seqs. Absence of
121
+ * either simply means: no reconstruction, legacy single-chapter compaction.
122
+ */
123
+ readonly surface?: {
124
+ readonly nodes?: readonly {
125
+ readonly seq: number;
126
+ }[];
127
+ };
128
+ deriveEventMessage?(event: EngineSessionEvent): EngineMessage | null | undefined;
129
+ }
130
+ export interface EngineSessionEvent {
131
+ readonly type: string;
132
+ readonly seq: number;
133
+ readonly time?: number;
134
+ readonly sourceEventSeqs?: readonly number[];
135
+ readonly data?: Record<string, unknown>;
136
+ }
137
+ /** Every `<compacted-summary>…</compacted-summary>` block in the region, in order. */
138
+ export declare function extractCheckpointBlocks(messages: readonly EngineMessage[]): string[];
139
+ /**
140
+ * Merge-forward: parse prior bullets out of every checkpoint block. Lines that
141
+ * are not our bullet format (e.g. an LLM summary produced by `basic` before
142
+ * this engine took over) are returned as verbatim carry-overs — dropping them
143
+ * would be exactly the loss this plugin exists to prevent.
144
+ */
145
+ export declare function parseTocState(blocks: readonly string[]): {
146
+ bullets: TocBullet[];
147
+ carriedProse: string[];
148
+ };
149
+ /**
150
+ * The open transaction's compactionId: the latest `compaction/start` with no
151
+ * later `compaction/end` carrying the same id. The base appends the start
152
+ * BEFORE awaiting summarize (region.ts:211) and the durable lock admits only
153
+ * one open transaction per session — so a backward scan finds exactly it.
154
+ */
155
+ export declare function findOpenCompactionId(session: EngineSession): string | null;
156
+ /** All OUR finalized-or-not summary events (provider-tagged), oldest first. */
157
+ export declare function scanChaptersSummaries(session: EngineSession): Array<{
158
+ compactionId: string;
159
+ summarySeq: number;
160
+ shadowedSeqs: number[];
161
+ }>;
162
+ /**
163
+ * Follow `tool/result` replacement chains to the first version (the un-pruned
164
+ * original). Only single-cited tool results qualify — a compaction checkpoint
165
+ * `user/message` also carries sourceEventSeqs, and following THOSE would pull
166
+ * lifecycle bookkeeping into chapter bodies.
167
+ */
168
+ export declare function resolveOriginalEvent(session: EngineSession, seq: number): EngineSessionEvent | undefined;
169
+ export interface SummarizePlan {
170
+ /** TOC text for the checkpoint (what summarize returns). */
171
+ tocText: string;
172
+ /** Chapter numbers reserved during planning — attemptId `compaction:${id}`. */
173
+ numbers: number[];
174
+ /** Everything finalize needs, since finalize cannot re-derive the title. */
175
+ chapter: {
176
+ title: string;
177
+ summary: string;
178
+ path: string;
179
+ };
180
+ /**
181
+ * Topic-composed plan (r29): when the composer ran over the span, one
182
+ * manifest chapter per composed range — finalize renders exactly these,
183
+ * it never recomposes (the TOC cited them; determinism is the contract).
184
+ */
185
+ chapters?: PlanChapter[];
186
+ }
187
+ /** Deterministic title/summary from region content. Never model-authored (invariant 4 — the model is absent). */
188
+ export declare function deriveIdentity(messages: readonly EngineMessage[], hasCheckpoint: boolean,
189
+ /**
190
+ * A plan submitted inside this span, when there is one (docs/plans-carriage.md
191
+ * § 6): the submission IS the identity of a planning chapter, and it beats the
192
+ * first prose line the same way a file's own header beats a guess about it.
193
+ * Callers that hold events pass it; the summarize path, which sees provider
194
+ * messages only, passes nothing and keeps the old derivation.
195
+ */
196
+ planTitle?: string): {
197
+ title: string;
198
+ summary: string;
199
+ };
200
+ /** Workspace-relative chapter path for a reserved number. */
201
+ export declare const chapterPathFor: (config: EngineConfig, rootSession: string, number: number, title: string) => string;
202
+ /**
203
+ * Map the prepared summarize input back to the log seqs it was built from
204
+ * (the host builds region messages as `deriveEventMessage(eventAt(seq))` for
205
+ * the shadowed selection). Returns null — meaning "use the legacy
206
+ * single-chapter path" — whenever the Session lacks the mapping API, nothing
207
+ * matches, or MORE THAN ONE contiguous run matches (duplicate small
208
+ * messages are common; guessing a wrong span would cite chapters that never
209
+ * cover their seqs, and an honest legacy chapter beats that).
210
+ */
211
+ export declare function reconstructShadowedSeqs(session: EngineSession, input: SummarizeInputLike): number[] | null;
212
+ /**
213
+ * The summarize-time plan: merge prior bullets, derive this span's identity,
214
+ * reserve numbers (attempt-keyed by the OPEN transaction's compactionId),
215
+ * build the TOC. Pure given the state; the caller persists the returned
216
+ * state change BEFORE this text can ever be cited.
217
+ *
218
+ * `composition` (r29): topic-sequential ranges from the composer (record
219
+ * §4.2), spanning the whole selected region head-to-tail (the adapter
220
+ * absorbs any strays). Present => reserve one number per chapter and cite
221
+ * them all; absent => the legacy single-chapter identity path, unchanged.
222
+ */
223
+ export declare function planSummarize(session: EngineSession, state: SessionState, input: SummarizeInputLike, config: EngineConfig, reserveFn: (state: SessionState, attemptId: string, count: number) => {
224
+ state: SessionState;
225
+ numbers: number[];
226
+ },
227
+ /** Required: the caller fails loudly when no `compaction/start` is open. */
228
+ compactionId: string, composition?: readonly ChapterRange[] | null,
229
+ /** Model-authored plot note carried into this checkpoint (may be null). */
230
+ plot?: string | null,
231
+ /**
232
+ * The operative plan, already rendered as checkpoint prose by `carryActivePlan`
233
+ * (plans.ts). Carried on BOTH the composed and the legacy path, because the
234
+ * composed path is where real pressure compaction lands. Note the asymmetry this
235
+ * deliberately does NOT touch: `plot` is still only prefixed on the legacy path
236
+ * (`:415`), and changing that would rewrite checkpoint bytes for every plot-carrying
237
+ * session — i.e. the committed tapes. Plans are new, so carrying them is free.
238
+ */
239
+ planCarry?: string | null,
240
+ /**
241
+ * #50 §7.6 — the rules block carried across this checkpoint, already rendered
242
+ * by the ONE builder that renders it for the notice too (`rules.buildRulesSection`).
243
+ * This is the hole the round exists to close: `leadSections` was
244
+ * `[planSection, plotSection]`, so a plan and a plot survived a compaction and a
245
+ * RULE did not. Null (nothing active, or the corpus unreadable) renders ZERO
246
+ * bytes — which is what keeps every committed plot-carrying checkpoint intact.
247
+ */
248
+ rulesCarry?: string | null): {
249
+ plan: SummarizePlan;
250
+ state: SessionState;
251
+ };
252
+ /**
253
+ * The plot note (architecture.md amendment 2026-09-19): a short forward-
254
+ * maintained statement of what the agent is MID-WAY through, authored by the
255
+ * model itself and carried forward across every in-place checkpoint.
256
+ */
257
+ export declare const PLOT_MARKER = "PLOT:";
258
+ export declare function extractPlot(messages: readonly EngineMessage[], maxChars?: number): string | null;
259
+ /**
260
+ * Post-commit: render the chapter body for one compaction from the durable
261
+ * log (citation-resolved), and return the record to commit. The caller writes
262
+ * files (via archive.writeArchive) and persists state; this stays pure over
263
+ * the event data so the pruner-composition rule is testable.
264
+ */
265
+ export declare function buildFinalizedChapter(session: EngineSession, shadowedSeqs: readonly number[], plan: SummarizePlan, config: EngineConfig): RenderedChapter;
266
+ /**
267
+ * Render the manifest's chapters (r29) — authoritative ranges, no
268
+ * recomposition (the TOC cited them). A plan without composed chapters
269
+ * returns the legacy single render. Mismatch between manifest and the
270
+ * committed shadowed span is a LOUD error: the finalizer defers and warns,
271
+ * never silently archives the wrong content.
272
+ */
273
+ export declare function buildFinalizedChapters(session: EngineSession, shadowedSeqs: readonly number[], plan: SummarizePlan, config: EngineConfig): RenderedChapter[];
274
+ /** The registry record shape written post-writeArchive for an engine chapter. */
275
+ export declare function engineRecord(plan: SummarizePlan, shadowedSeqs: readonly number[], rendered: RenderedChapter, writtenPath: string, sha256hex: string): ChapterRecord;
276
+ /** Numbers the plan reserved, or null when the manifest disagrees (corruption). */
277
+ /**
278
+ * Walk the provider-tagged summaries of one session and finalize each that is not yet finalized,
279
+ * ONE AT A TIME (#59): an item whose manifest is gone is reported and skipped, never allowed to
280
+ * abort the items behind it. Pure over injected effects so the poison-pill case is testable without
281
+ * a host.
282
+ */
283
+ export declare function scanAndFinalize(state: SessionState, summaries: readonly {
284
+ compactionId: string;
285
+ summarySeq: number;
286
+ shadowedSeqs: number[];
287
+ }[], finalizeOne: (found: {
288
+ compactionId: string;
289
+ summarySeq: number;
290
+ shadowedSeqs: number[];
291
+ }) => Promise<void>, onSkip?: (reason: {
292
+ compactionId: string;
293
+ reason: string;
294
+ }, error: unknown) => void): Promise<{
295
+ done: string[];
296
+ skipped: Array<{
297
+ compactionId: string;
298
+ reason: string;
299
+ }>;
300
+ }>;
301
+ export declare function assertReservationMatchesPlan(state: SessionState, compactionId: string, plan: SummarizePlan): boolean;