agentfootprint 9.114.0 → 9.114.2

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 (33) hide show
  1. package/CAPABILITIES.md +82 -0
  2. package/ai-instructions/setup.sh +4 -4
  3. package/dist/core/agent/findings/answerText.js +847 -0
  4. package/dist/core/agent/findings/answerText.js.map +1 -0
  5. package/dist/core/agent/findings/reserved.js +55 -16
  6. package/dist/core/agent/findings/reserved.js.map +1 -1
  7. package/dist/core/agent/stages/callLLM.js +33 -6
  8. package/dist/core/agent/stages/callLLM.js.map +1 -1
  9. package/dist/core/agent/stages/route.js +95 -55
  10. package/dist/core/agent/stages/route.js.map +1 -1
  11. package/dist/esm/core/agent/findings/answerText.d.ts +78 -0
  12. package/dist/esm/core/agent/findings/answerText.js +842 -0
  13. package/dist/esm/core/agent/findings/answerText.js.map +1 -0
  14. package/dist/esm/core/agent/findings/reserved.d.ts +18 -5
  15. package/dist/esm/core/agent/findings/reserved.js +55 -16
  16. package/dist/esm/core/agent/findings/reserved.js.map +1 -1
  17. package/dist/esm/core/agent/stages/callLLM.d.ts +7 -4
  18. package/dist/esm/core/agent/stages/callLLM.js +33 -6
  19. package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
  20. package/dist/esm/core/agent/stages/route.d.ts +6 -4
  21. package/dist/esm/core/agent/stages/route.js +95 -55
  22. package/dist/esm/core/agent/stages/route.js.map +1 -1
  23. package/dist/types/core/agent/findings/answerText.d.ts +79 -0
  24. package/dist/types/core/agent/findings/answerText.d.ts.map +1 -0
  25. package/dist/types/core/agent/findings/reserved.d.ts +18 -5
  26. package/dist/types/core/agent/findings/reserved.d.ts.map +1 -1
  27. package/dist/types/core/agent/stages/callLLM.d.ts +7 -4
  28. package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
  29. package/dist/types/core/agent/stages/route.d.ts +6 -4
  30. package/dist/types/core/agent/stages/route.d.ts.map +1 -1
  31. package/package.json +10 -3
  32. package/CHANGELOG.md +0 -24738
  33. package/CLAUDE.md +0 -217
@@ -0,0 +1,847 @@
1
+ "use strict";
2
+ /**
3
+ * findings/answerText — the ONE reader of the reserved key in an answer's
4
+ * TEXT (9.114.2).
5
+ *
6
+ * Pattern: an incremental scanner — a character state machine with no scope
7
+ * and no I/O, fed chunk by chunk (`reservedMemberFilter`) or whole
8
+ * (`withoutReservedMembers`), with the same bytes out either way.
9
+ * Role: core/ layer leaf. `stages/callLLM.ts` feeds it the provider's
10
+ * stream under the arm, so `agentfootprint.stream.token` never
11
+ * carries the model's notes; `reserved.ts · peelAnswerFindings` feeds
12
+ * it the whole answer, so the answer the run returns is exactly the
13
+ * text the stream showed.
14
+ *
15
+ * THE RULE
16
+ * Every JSON object written in the text — the whole answer, one in a code
17
+ * block, one in the prose, one in a list — loses its own `_findings` member:
18
+ * the key, the value and the separator that joined it to its neighbours,
19
+ * and nothing else. A `_findings` inside another object's VALUE is data and
20
+ * stays. Each removed value is handed back (`removed`), as written, for the
21
+ * peel to read as the answer's declaration.
22
+ *
23
+ * - An object left with nothing in it goes whole — its line too — when it
24
+ * stood on a line of its own outside any list: the whole answer, a
25
+ * paragraph of its own, a code block of its own. A code block left empty
26
+ * goes with its fences. The removed line's indentation and the whole
27
+ * blank lines after it go; the next line keeps its own indentation. No
28
+ * blank lines are left at either end of the text. The whole answer, left with nothing, is `{}` (a JSON answer stays
29
+ * JSON). Anywhere else an emptied object stays as `{}`, so the JSON or
30
+ * code around it keeps its shape. A string inside a list is read as a
31
+ * string, so a `]` or `{` in it neither ends the list nor opens an object.
32
+ * - An answer that held nothing but notes — a code block of them, or
33
+ * several objects of them — is left empty; only an answer that was one
34
+ * bare JSON object becomes `{}`.
35
+ * - Text that stops being JSON part way (`{see below}`, a stray quote or a
36
+ * raw line break inside a `_findings` value) is given back exactly as it
37
+ * was written; nothing is removed from it except a `_findings` member
38
+ * that was already complete. An object that held nothing but such
39
+ * complete notes when the text stopped being JSON (a model one closing
40
+ * brace short) ends there, and goes as an emptied object does. Keys and
41
+ * values are read by JSON's own grammar (`JsonValue`), so the first
42
+ * character JSON cannot take decides it — not a count of brackets, which
43
+ * prose may never balance.
44
+ * - Text that ends inside a `_findings` value that is JSON so far (a
45
+ * cut-off stream) keeps it hidden and reports it `complete: false`.
46
+ * - Nothing removed ⇒ the text comes back byte for byte, whatever the
47
+ * chunking. What is held back while undecided is bounded by a line:
48
+ * whitespace, a key (a raw line break ends one — JSON allows none), `{`
49
+ * until its first key is read, a separator, a code fence line, the rest
50
+ * of an emptied object's line. The one exception is a `_findings` value:
51
+ * it is never shown while it is JSON so far, and one that stops being
52
+ * JSON is held until the character that breaks it and then given back as
53
+ * written — so that hold can span as many lines as the notes do.
54
+ */
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.withoutReservedMembers = exports.reservedMemberFilter = void 0;
57
+ const types_js_1 = require("./types.js");
58
+ /** A fresh scanner for one text (one provider call's content). */
59
+ function reservedMemberFilter(key = types_js_1.RESERVED_ANSWER_KEY) {
60
+ return new Scanner(key);
61
+ }
62
+ exports.reservedMemberFilter = reservedMemberFilter;
63
+ /** The whole-text form: the text without its reserved members, and what was removed. */
64
+ function withoutReservedMembers(text, key = types_js_1.RESERVED_ANSWER_KEY) {
65
+ const scanner = new Scanner(key);
66
+ const out = scanner.push(text) + scanner.end();
67
+ return { text: out, removed: scanner.removed };
68
+ }
69
+ exports.withoutReservedMembers = withoutReservedMembers;
70
+ // ─── The machine ───────────────────────────────────────────────────────
71
+ const WS = new Set([' ', '\t', '\n', '\r']);
72
+ /** A JSON number or literal — what a value that is not a string, object or array must be. */
73
+ const SCALAR = /^(?:-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)$/;
74
+ /** The start of one: a scalar that stops matching this cannot become JSON. */
75
+ const SCALAR_PREFIX = /^(?:-|-?(?:0|[1-9]\d*)(?:\.(?:\d+(?:[eE][+-]?\d*)?)?|[eE][+-]?\d*)?|t(?:r(?:ue?)?)?|f(?:a(?:l(?:se?)?)?)?|n(?:u(?:ll?)?)?)$/;
76
+ /** Characters that can continue a scalar; anything else ends it. */
77
+ const SCALAR_CHAR = /[0-9a-zA-Z.+-]/;
78
+ /** What may follow `\` in a JSON string (`u` takes four hex digits). */
79
+ const ESCAPES = new Set(['"', '\\', '/', 'b', 'f', 'n', 'r', 't']);
80
+ const HEX = /^[0-9a-fA-F]$/;
81
+ /**
82
+ * One character of a JSON string after its opening quote. `end` at the
83
+ * closing quote; `bad` where JSON cannot continue — a raw newline or other
84
+ * control character, or an escape JSON does not have.
85
+ */
86
+ function stringChar(s, ch) {
87
+ if (ch < ' ')
88
+ return 'bad';
89
+ if (s.esc === -1) {
90
+ if (ch === 'u')
91
+ s.esc = 4;
92
+ else if (ESCAPES.has(ch))
93
+ s.esc = 0;
94
+ else
95
+ return 'bad';
96
+ return 'more';
97
+ }
98
+ if (s.esc > 0) {
99
+ if (!HEX.test(ch))
100
+ return 'bad';
101
+ s.esc -= 1;
102
+ return 'more';
103
+ }
104
+ if (ch === '\\') {
105
+ s.esc = -1;
106
+ return 'more';
107
+ }
108
+ return ch === '"' ? 'end' : 'more';
109
+ }
110
+ /**
111
+ * One JSON value read character by character by JSON's own grammar, so a
112
+ * value that stops being JSON is known at its first wrong character — not
113
+ * only when its brackets fail to balance, which may never happen.
114
+ */
115
+ class JsonValue {
116
+ frames = [];
117
+ str = { esc: 0 };
118
+ inString = false;
119
+ scalar = '';
120
+ step(ch) {
121
+ if (this.inString)
122
+ return this.stringStep(ch);
123
+ if (this.scalar !== '') {
124
+ if (SCALAR_CHAR.test(ch))
125
+ return this.scalarStep(ch);
126
+ if (!SCALAR.test(this.scalar))
127
+ return 'bad';
128
+ this.scalar = '';
129
+ if (this.frames.length === 0)
130
+ return 'after';
131
+ }
132
+ const top = this.frames[this.frames.length - 1];
133
+ // Nothing open: this is the value's first character.
134
+ if (top === undefined)
135
+ return this.startValue(ch);
136
+ if (WS.has(ch))
137
+ return 'take';
138
+ if (ch === ',')
139
+ return this.expecting(top, 'commaOrClose', top.close === '}' ? 'key' : 'value');
140
+ if (ch === ':')
141
+ return this.expecting(top, 'colon', 'value');
142
+ if (ch === '}' || ch === ']')
143
+ return this.close(top, ch);
144
+ if (ch === '"' && (top.expect === 'keyOrClose' || top.expect === 'key')) {
145
+ top.expect = 'colon';
146
+ return this.openString();
147
+ }
148
+ return this.startValue(ch);
149
+ }
150
+ stringStep(ch) {
151
+ const r = stringChar(this.str, ch);
152
+ if (r !== 'end')
153
+ return r === 'bad' ? 'bad' : 'take';
154
+ this.inString = false;
155
+ return this.frames.length === 0 ? 'end' : 'take';
156
+ }
157
+ scalarStep(ch) {
158
+ if (!SCALAR_PREFIX.test(this.scalar + ch))
159
+ return 'bad';
160
+ this.scalar += ch;
161
+ return 'take';
162
+ }
163
+ expecting(top, want, next) {
164
+ if (top.expect !== want)
165
+ return 'bad';
166
+ top.expect = next;
167
+ return 'take';
168
+ }
169
+ close(top, ch) {
170
+ const closable = top.expect === 'keyOrClose' || top.expect === 'valueOrClose' || top.expect === 'commaOrClose';
171
+ if (top.close !== ch || !closable)
172
+ return 'bad';
173
+ this.frames.pop();
174
+ return this.frames.length === 0 ? 'end' : 'take';
175
+ }
176
+ openString() {
177
+ this.inString = true;
178
+ this.str.esc = 0;
179
+ return 'take';
180
+ }
181
+ /** A value begins — only where one is expected. */
182
+ startValue(ch) {
183
+ const top = this.frames[this.frames.length - 1];
184
+ if (top !== undefined) {
185
+ if (top.expect !== 'value' && top.expect !== 'valueOrClose')
186
+ return 'bad';
187
+ top.expect = 'commaOrClose';
188
+ }
189
+ if (ch === '"')
190
+ return this.openString();
191
+ if (ch === '{' || ch === '[') {
192
+ const obj = ch === '{';
193
+ this.frames.push({ close: obj ? '}' : ']', expect: obj ? 'keyOrClose' : 'valueOrClose' });
194
+ return 'take';
195
+ }
196
+ if (!SCALAR_PREFIX.test(ch))
197
+ return 'bad';
198
+ this.scalar = ch;
199
+ return 'take';
200
+ }
201
+ }
202
+ class Scanner {
203
+ key;
204
+ removed = [];
205
+ out = '';
206
+ ended = false;
207
+ mode = 'prose';
208
+ // Prose.
209
+ pendingWs = '';
210
+ /** A block was removed after `pendingWs` began: that whitespace is kept only if more text follows. */
211
+ pendingBeforeRemoved = false;
212
+ /** Line breaks dropped after the last removed block (`dropWs`). */
213
+ droppedNewlines = 0;
214
+ /** The whitespace right after a removed block is dropped — whole blank lines only. */
215
+ dropWs = false;
216
+ /**
217
+ * While `dropWs` is on: the spaces and tabs since the last newline — the
218
+ * next line's own indentation, which is not the removed block's to take.
219
+ */
220
+ indentWs = '';
221
+ lineStart = true;
222
+ textStart = true;
223
+ /** `[` written in the text and not yet closed — an object inside a list keeps its place. */
224
+ listDepth = 0;
225
+ /** Inside a string in a list (`listDepth > 0`). */
226
+ listString = false;
227
+ listStr = { esc: 0 };
228
+ /** The text began with an object that went whole — `{}` if nothing else follows. */
229
+ emptiedInitial = false;
230
+ // Code blocks.
231
+ inFence = false;
232
+ fenceTicks = 0;
233
+ ticks = '';
234
+ opener = '';
235
+ tail = '';
236
+ // Objects.
237
+ obj;
238
+ /** The emptied object whose line is being read (`lineRest`). */
239
+ emptied;
240
+ objects = 0;
241
+ constructor(key) {
242
+ this.key = key;
243
+ }
244
+ push(chunk) {
245
+ if (this.ended)
246
+ return '';
247
+ for (const ch of chunk)
248
+ this.step(ch);
249
+ return this.flush();
250
+ }
251
+ end() {
252
+ if (this.ended)
253
+ return '';
254
+ this.ended = true;
255
+ switch (this.mode) {
256
+ case 'ticks':
257
+ this.mode = 'prose';
258
+ this.text(this.takeTicks());
259
+ break;
260
+ case 'fenceInfo':
261
+ case 'fenceStart':
262
+ this.releaseWs();
263
+ this.emit(this.opener);
264
+ this.opener = '';
265
+ break;
266
+ case 'object':
267
+ this.endInObject();
268
+ break;
269
+ case 'lineRest': {
270
+ // The emptied object's line ended with the text: it stood alone — and
271
+ // a code block it began never closed, so the block goes too.
272
+ const o = this.emptied;
273
+ this.goesWhole(o);
274
+ if (o.fence !== undefined)
275
+ this.removeFencedBlock();
276
+ break;
277
+ }
278
+ case 'fenceAfterTicks':
279
+ if (this.ticks.length >= this.fenceTicks)
280
+ this.removeFencedBlock();
281
+ else
282
+ this.keepFencedBlock(this.takeTicks());
283
+ break;
284
+ case 'fenceAfter':
285
+ // The block never closed and held only the removed object.
286
+ this.removeFencedBlock();
287
+ break;
288
+ default:
289
+ break;
290
+ }
291
+ if (this.emptiedInitial) {
292
+ this.dropHeldWs();
293
+ this.emit('{}');
294
+ }
295
+ else if (this.pendingBeforeRemoved) {
296
+ this.dropHeldWs();
297
+ }
298
+ else {
299
+ this.releaseWs();
300
+ }
301
+ return this.flush();
302
+ }
303
+ // ─── Output ────────────────────────────────────────────────────────
304
+ flush() {
305
+ const out = this.out;
306
+ this.out = '';
307
+ return out;
308
+ }
309
+ emit(s) {
310
+ this.out += s;
311
+ }
312
+ /** Whitespace held before now goes out. */
313
+ releaseWs() {
314
+ // After a removed line, the blank line that followed it still keeps the
315
+ // text on either side apart — at most one blank line, and only where text
316
+ // came before the removed line.
317
+ let breaks = '';
318
+ if (this.pendingBeforeRemoved && this.pendingWs.includes('\n')) {
319
+ const have = this.pendingWs.split('\n').length - 1;
320
+ breaks = '\n'.repeat(Math.min(this.droppedNewlines, Math.max(0, 2 - have)));
321
+ }
322
+ this.emit(this.pendingWs + breaks + this.indentWs);
323
+ this.pendingWs = '';
324
+ this.indentWs = '';
325
+ this.droppedNewlines = 0;
326
+ this.pendingBeforeRemoved = false;
327
+ this.dropWs = false;
328
+ }
329
+ /** The text ended after a removed block: the whitespace held goes with it. */
330
+ dropHeldWs() {
331
+ this.pendingWs = '';
332
+ this.indentWs = '';
333
+ }
334
+ /**
335
+ * A removed line's indentation goes with it, so it is not carried onto the
336
+ * next line (four spaces there would make it a code block). The held
337
+ * whitespace is cut back to its last newline.
338
+ */
339
+ dropRemovedIndent() {
340
+ this.pendingWs = this.pendingWs.slice(0, this.pendingWs.lastIndexOf('\n') + 1);
341
+ this.indentWs = '';
342
+ }
343
+ /** Visible text: everything held before it goes out first. */
344
+ text(s) {
345
+ this.releaseWs();
346
+ this.emit(s);
347
+ this.lineStart = false;
348
+ this.textStart = false;
349
+ this.emptiedInitial = false;
350
+ }
351
+ takeTicks() {
352
+ const t = this.ticks;
353
+ this.ticks = '';
354
+ return t;
355
+ }
356
+ blockRemoved() {
357
+ this.pendingBeforeRemoved = true;
358
+ this.dropWs = true;
359
+ this.droppedNewlines = 0;
360
+ }
361
+ // ─── Dispatch ──────────────────────────────────────────────────────
362
+ step(ch) {
363
+ switch (this.mode) {
364
+ case 'prose':
365
+ return this.prose(ch);
366
+ case 'ticks':
367
+ return this.ticksStep(ch);
368
+ case 'fenceInfo':
369
+ return this.fenceInfo(ch);
370
+ case 'fenceStart':
371
+ return this.fenceStart(ch);
372
+ case 'object':
373
+ return this.objectStep(ch);
374
+ case 'lineRest':
375
+ return this.lineRest(ch);
376
+ case 'fenceAfter':
377
+ return this.fenceAfter(ch);
378
+ case 'fenceAfterTicks':
379
+ return this.fenceAfterTicks(ch);
380
+ }
381
+ }
382
+ // ─── Prose (a code block's body included) ──────────────────────────
383
+ prose(ch) {
384
+ if (this.listString)
385
+ return this.listStringStep(ch);
386
+ if (WS.has(ch)) {
387
+ if (ch === '\n')
388
+ this.lineStart = true;
389
+ if (!this.dropWs)
390
+ this.pendingWs += ch;
391
+ else if (ch === '\n' || ch === '\r') {
392
+ this.indentWs = '';
393
+ if (ch === '\n')
394
+ this.droppedNewlines += 1;
395
+ }
396
+ else if (this.lineStart)
397
+ this.indentWs += ch;
398
+ return;
399
+ }
400
+ if (ch === '`' && this.lineStart) {
401
+ this.mode = 'ticks';
402
+ this.ticks = ch;
403
+ this.textStart = false;
404
+ this.emptiedInitial = false;
405
+ return;
406
+ }
407
+ if (ch === '{')
408
+ return this.openObject(undefined);
409
+ if (ch === '[')
410
+ this.listDepth += 1;
411
+ else if (ch === ']' && this.listDepth > 0)
412
+ this.listDepth -= 1;
413
+ else if (ch === '"' && this.listDepth > 0) {
414
+ this.listString = true;
415
+ this.listStr.esc = 0;
416
+ }
417
+ this.text(ch);
418
+ }
419
+ /**
420
+ * A string inside a list: its `[`, `]` and `{` are words, not structure, so
421
+ * the list keeps its depth. A character no JSON string can hold (a raw
422
+ * newline) means this was never a JSON list: it is read as prose from here.
423
+ */
424
+ listStringStep(ch) {
425
+ const r = stringChar(this.listStr, ch);
426
+ if (r === 'bad') {
427
+ this.listString = false;
428
+ this.listDepth = 0;
429
+ return this.prose(ch);
430
+ }
431
+ if (r === 'end')
432
+ this.listString = false;
433
+ this.text(ch);
434
+ }
435
+ /** A run of backticks at the start of a line: a fence opens (or closes, inside one). */
436
+ ticksStep(ch) {
437
+ if (ch === '`') {
438
+ this.ticks += ch;
439
+ return;
440
+ }
441
+ const run = this.takeTicks();
442
+ this.mode = 'prose';
443
+ if (this.inFence) {
444
+ this.text(run);
445
+ if (run.length >= this.fenceTicks && WS.has(ch)) {
446
+ this.inFence = false;
447
+ this.fenceTicks = 0;
448
+ }
449
+ return this.prose(ch);
450
+ }
451
+ if (run.length >= 3) {
452
+ // A code block opens; its opening line is held until its first character.
453
+ this.fenceTicks = run.length;
454
+ this.opener = run;
455
+ this.mode = 'fenceInfo';
456
+ return this.fenceInfo(ch);
457
+ }
458
+ this.text(run);
459
+ this.prose(ch);
460
+ }
461
+ fenceInfo(ch) {
462
+ this.opener += ch;
463
+ if (ch === '\n')
464
+ this.mode = 'fenceStart';
465
+ }
466
+ fenceStart(ch) {
467
+ if (WS.has(ch)) {
468
+ this.opener += ch;
469
+ return;
470
+ }
471
+ if (ch === '{') {
472
+ const opener = this.opener;
473
+ this.opener = '';
474
+ this.lineStart = true;
475
+ return this.openObject(opener);
476
+ }
477
+ // The block does not open with an object: its opening line goes out as
478
+ // written, and its body is read as text.
479
+ this.releaseWs();
480
+ this.emit(this.opener);
481
+ this.opener = '';
482
+ this.inFence = true;
483
+ this.mode = 'prose';
484
+ this.lineStart = true;
485
+ this.prose(ch);
486
+ }
487
+ // ─── After an emptied object ───────────────────────────────────────
488
+ /** The rest of an emptied object's line: only whitespace up to the newline ⇒ it goes whole. */
489
+ lineRest(ch) {
490
+ const o = this.emptied;
491
+ if (ch === '\n') {
492
+ // Its line goes with it, newline and all.
493
+ this.goesWhole(o);
494
+ this.lineStart = true;
495
+ return;
496
+ }
497
+ if (WS.has(ch)) {
498
+ this.tail += ch;
499
+ return;
500
+ }
501
+ // Something else shares its line: it stays, as `{}`.
502
+ this.staysEmpty(o);
503
+ this.step(ch);
504
+ }
505
+ /** The emptied object stood alone on its line: it goes, and its line with it. */
506
+ goesWhole(o) {
507
+ this.emptied = undefined;
508
+ this.tail = '';
509
+ if (o.fence !== undefined) {
510
+ // The block's opening line stays held — without the removed line's indentation.
511
+ this.opener = o.fence.slice(0, o.fence.lastIndexOf('\n') + 1);
512
+ this.mode = 'fenceAfter';
513
+ this.lineStart = false;
514
+ return;
515
+ }
516
+ this.dropRemovedIndent();
517
+ if (o.initial)
518
+ this.emptiedInitial = true;
519
+ this.mode = 'prose';
520
+ this.lineStart = false;
521
+ this.blockRemoved();
522
+ }
523
+ /** The emptied object shares its line or sits in a list: it stays as `{}`. */
524
+ staysEmpty(o) {
525
+ this.emptied = undefined;
526
+ this.releaseWs();
527
+ if (o.fence !== undefined) {
528
+ this.emit(o.fence);
529
+ this.inFence = true;
530
+ }
531
+ this.emit('{}' + this.tail);
532
+ this.tail = '';
533
+ this.mode = 'prose';
534
+ this.lineStart = false;
535
+ this.textStart = false;
536
+ this.emptiedInitial = false;
537
+ }
538
+ fenceAfter(ch) {
539
+ if (WS.has(ch)) {
540
+ this.tail += ch;
541
+ if (ch === '\n')
542
+ this.lineStart = true;
543
+ return;
544
+ }
545
+ if (ch === '`' && this.lineStart) {
546
+ this.mode = 'fenceAfterTicks';
547
+ this.ticks = ch;
548
+ return;
549
+ }
550
+ if (ch === '{') {
551
+ // Another object in the same block: the block's opening line stays held
552
+ // with it, so a block of nothing but notes goes whole, fences and all.
553
+ const fence = this.opener + this.tail;
554
+ this.opener = '';
555
+ this.tail = '';
556
+ return this.openObject(fence);
557
+ }
558
+ this.keepFencedBlock('');
559
+ this.prose(ch);
560
+ }
561
+ fenceAfterTicks(ch) {
562
+ if (ch === '`') {
563
+ this.ticks += ch;
564
+ return;
565
+ }
566
+ if (this.ticks.length >= this.fenceTicks && WS.has(ch)) {
567
+ this.ticks = '';
568
+ this.removeFencedBlock();
569
+ return this.prose(ch);
570
+ }
571
+ this.keepFencedBlock(this.takeTicks());
572
+ this.prose(ch);
573
+ }
574
+ /** The code block held only the removed object: it goes, fences and all. */
575
+ removeFencedBlock() {
576
+ this.dropRemovedIndent();
577
+ this.opener = '';
578
+ this.tail = '';
579
+ this.inFence = false;
580
+ this.fenceTicks = 0;
581
+ this.mode = 'prose';
582
+ this.lineStart = false;
583
+ this.blockRemoved();
584
+ }
585
+ /** The code block holds more than the removed object's line: it stays, and is read on. */
586
+ keepFencedBlock(ticks) {
587
+ this.releaseWs();
588
+ this.emit(this.opener + this.tail + ticks);
589
+ this.opener = '';
590
+ this.tail = '';
591
+ this.inFence = true;
592
+ this.mode = 'prose';
593
+ this.lineStart = ticks === '' && this.lineStart;
594
+ this.textStart = false;
595
+ }
596
+ // ─── Objects ───────────────────────────────────────────────────────
597
+ openObject(fence) {
598
+ this.obj = {
599
+ index: this.objects++,
600
+ initial: this.textStart && fence === undefined,
601
+ ownLine: this.lineStart && this.listDepth === 0,
602
+ fence,
603
+ open: '{',
604
+ openHeld: true,
605
+ kept: 0,
606
+ removed: 0,
607
+ state: 'key?',
608
+ sep: '',
609
+ key: '',
610
+ keyStr: { esc: 0 },
611
+ reserved: false,
612
+ raw: '',
613
+ rawSep: 0,
614
+ value: '',
615
+ reader: undefined,
616
+ };
617
+ this.mode = 'object';
618
+ this.textStart = false;
619
+ this.lineStart = false;
620
+ this.emptiedInitial = false;
621
+ }
622
+ /** `{`, and the code block's opening line before it, go out. */
623
+ releaseOpen(o) {
624
+ if (!o.openHeld)
625
+ return;
626
+ this.releaseWs();
627
+ if (o.fence !== undefined) {
628
+ this.emit(o.fence);
629
+ this.inFence = true;
630
+ }
631
+ this.emit(o.open);
632
+ o.openHeld = false;
633
+ }
634
+ /** A character of the current member: shown when it is kept, held when it is reserved. */
635
+ member(o, ch, value) {
636
+ if (!o.reserved)
637
+ return this.emit(ch);
638
+ o.raw += ch;
639
+ if (value)
640
+ o.value += ch;
641
+ }
642
+ objectStep(ch) {
643
+ const o = this.obj;
644
+ switch (o.state) {
645
+ case 'key?':
646
+ if (WS.has(ch)) {
647
+ if (o.kept + o.removed === 0)
648
+ o.open += ch;
649
+ else
650
+ o.sep += ch;
651
+ return;
652
+ }
653
+ if (ch === '"') {
654
+ o.key = ch;
655
+ o.keyStr.esc = 0;
656
+ o.state = 'key';
657
+ return;
658
+ }
659
+ if (ch === '}' && o.kept + o.removed === 0) {
660
+ // `{}` — nothing to read, and nothing to remove.
661
+ this.releaseOpen(o);
662
+ this.emit(ch);
663
+ return this.finishObject();
664
+ }
665
+ return this.abort(o, ch);
666
+ case 'key': {
667
+ const r = stringChar(o.keyStr, ch);
668
+ if (r === 'bad')
669
+ return this.abort(o, ch);
670
+ o.key += ch;
671
+ if (r === 'end')
672
+ this.resolveKey(o);
673
+ return;
674
+ }
675
+ case 'colon':
676
+ if (WS.has(ch))
677
+ return this.member(o, ch, false);
678
+ if (ch === ':') {
679
+ this.member(o, ch, false);
680
+ o.state = 'value?';
681
+ return;
682
+ }
683
+ return this.abort(o, ch);
684
+ case 'value?':
685
+ if (WS.has(ch))
686
+ return this.member(o, ch, false);
687
+ return this.startValue(o, ch);
688
+ case 'value':
689
+ return this.valueStep(o, ch);
690
+ case 'after':
691
+ if (WS.has(ch)) {
692
+ o.sep += ch;
693
+ return;
694
+ }
695
+ if (ch === ',') {
696
+ o.sep += ch;
697
+ o.state = 'key?';
698
+ return;
699
+ }
700
+ if (ch === '}')
701
+ return this.closeObject(o);
702
+ return this.abort(o, ch);
703
+ }
704
+ }
705
+ resolveKey(o) {
706
+ let name;
707
+ try {
708
+ name = JSON.parse(o.key);
709
+ }
710
+ catch {
711
+ name = undefined;
712
+ }
713
+ if (name === this.key) {
714
+ // The separator that joined it to the member before goes with it.
715
+ o.reserved = true;
716
+ o.removed += 1;
717
+ o.raw = o.sep + o.key;
718
+ o.rawSep = o.sep.length;
719
+ o.value = '';
720
+ }
721
+ else {
722
+ o.reserved = false;
723
+ // The first member kept takes `{` and the whitespace after it; a
724
+ // separator left behind by removed members before it goes.
725
+ if (o.kept === 0)
726
+ this.releaseOpen(o);
727
+ else
728
+ this.emit(o.sep);
729
+ o.kept += 1;
730
+ this.emit(o.key);
731
+ }
732
+ o.sep = '';
733
+ o.key = '';
734
+ o.state = 'colon';
735
+ }
736
+ startValue(o, ch) {
737
+ o.reader = new JsonValue();
738
+ o.state = 'value';
739
+ this.valueStep(o, ch);
740
+ }
741
+ /** A character of a member's value, read by JSON's grammar: the first one JSON cannot take gives the text back. */
742
+ valueStep(o, ch) {
743
+ const r = o.reader.step(ch);
744
+ if (r === 'bad')
745
+ return this.abort(o, ch);
746
+ if (r === 'after') {
747
+ this.endValue(o);
748
+ return this.objectStep(ch);
749
+ }
750
+ this.member(o, ch, true);
751
+ if (r === 'end')
752
+ this.endValue(o);
753
+ }
754
+ endValue(o) {
755
+ if (o.reserved) {
756
+ this.removed.push({ object: o.index, valueText: o.value, complete: true });
757
+ o.reserved = false;
758
+ o.raw = '';
759
+ o.value = '';
760
+ }
761
+ o.reader = undefined;
762
+ o.state = 'after';
763
+ }
764
+ closeObject(o) {
765
+ if (o.kept > 0) {
766
+ this.emit(o.sep);
767
+ this.emit('}');
768
+ return this.finishObject();
769
+ }
770
+ // Every member was `_findings`: the object is empty now.
771
+ this.emptyObjectEnded(o);
772
+ }
773
+ /** An object that held nothing but notes is over: whole if it stood on its own line, else `{}`. */
774
+ emptyObjectEnded(o) {
775
+ this.obj = undefined;
776
+ this.lineStart = false;
777
+ this.tail = '';
778
+ if (o.ownLine) {
779
+ // Whether it goes whole depends on the rest of its line.
780
+ this.emptied = o;
781
+ this.mode = 'lineRest';
782
+ return;
783
+ }
784
+ this.staysEmpty(o);
785
+ }
786
+ finishObject() {
787
+ this.obj = undefined;
788
+ this.mode = 'prose';
789
+ this.lineStart = false;
790
+ }
791
+ /** Not JSON after all: everything held goes out as written, and the character is read again. */
792
+ abort(o, ch) {
793
+ // A `"` straight after a member is a missing comma before the next key,
794
+ // not a missing closing brace: that object goes on, so it is given back.
795
+ const missingComma = o.state === 'after' && ch === '"';
796
+ if (o.kept === 0 && o.removed > 0 && !o.reserved && !missingComma) {
797
+ // It held nothing but complete notes when the text stopped being JSON —
798
+ // a model a closing brace short. The notes object ends here and goes as
799
+ // an emptied one does; a trailing comma was its own, and the whitespace
800
+ // after it (and a key it had begun) is read as the text that follows.
801
+ const after = o.sep.slice(o.sep.lastIndexOf(',') + 1) + (o.state === 'key' ? o.key : '');
802
+ this.emptyObjectEnded(o);
803
+ for (const c of after)
804
+ this.step(c);
805
+ return this.step(ch);
806
+ }
807
+ this.releaseOpen(o);
808
+ // A separator after removed members with nothing kept before it is theirs
809
+ // (the rule `resolveKey` applies to the first kept member).
810
+ if (o.kept > 0)
811
+ this.emit(o.sep);
812
+ if (o.state === 'key')
813
+ this.emit(o.key);
814
+ // A reserved member given back: with nothing kept before it, the
815
+ // separator before it was a removed member's (the `resolveKey` rule).
816
+ if (o.reserved)
817
+ this.emit(o.kept > 0 ? o.raw : o.raw.slice(o.rawSep));
818
+ this.finishObject();
819
+ this.step(ch);
820
+ }
821
+ /** The text ended inside an object. */
822
+ endInObject() {
823
+ const o = this.obj;
824
+ this.obj = undefined;
825
+ this.mode = 'prose';
826
+ if (o.reserved) {
827
+ // Cut off inside `_findings`: its words stay hidden.
828
+ this.removed.push({ object: o.index, valueText: o.value, complete: false });
829
+ }
830
+ if (o.kept === 0 && o.removed > 0 && o.state !== 'key') {
831
+ // Nothing kept, something removed: as if it had closed there.
832
+ if (!o.ownLine)
833
+ return this.staysEmpty(o);
834
+ this.goesWhole(o);
835
+ if (o.fence !== undefined)
836
+ this.removeFencedBlock();
837
+ return;
838
+ }
839
+ // Everything else held goes out as written.
840
+ this.releaseOpen(o);
841
+ if (o.kept > 0)
842
+ this.emit(o.sep);
843
+ if (o.state === 'key')
844
+ this.emit(o.key);
845
+ }
846
+ }
847
+ //# sourceMappingURL=answerText.js.map