@open-agent-toolkit/cli 0.3.36 → 0.3.38

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 (38) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/provider-sync/pilot-with-a-team.md +6 -6
  3. package/assets/docs/provider-sync/providers.md +1 -0
  4. package/assets/docs/reference/cli-reference.md +3 -3
  5. package/assets/docs/reference/project-artifacts.md +12 -3
  6. package/assets/docs/reference/what-oat-writes.md +14 -11
  7. package/assets/docs/skills/repo-improve.md +23 -19
  8. package/assets/docs/workflows/advanced/workflow-gates.md +7 -1
  9. package/assets/docs/workflows/projects/closeout/closeout-skills.md +3 -1
  10. package/assets/docs/workflows/projects/lifecycle.md +3 -1
  11. package/assets/public-package-versions.json +4 -4
  12. package/assets/skills/oat-project-brief/SKILL.md +1 -1
  13. package/assets/skills/oat-project-brief/references/brief-contract.md +32 -9
  14. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  15. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +14 -4
  16. package/assets/skills/oat-project-pr-progress/SKILL.md +23 -12
  17. package/assets/skills/oat-repo-knowledge-index/SKILL.md +226 -131
  18. package/dist/commands/project/closeout-check/index.d.ts.map +1 -1
  19. package/dist/commands/project/closeout-check/index.js +4 -8
  20. package/dist/commands/project/delivery/check.d.ts.map +1 -1
  21. package/dist/commands/project/delivery/check.js +32 -14
  22. package/dist/commands/project/delivery/evidence.d.ts +39 -0
  23. package/dist/commands/project/delivery/evidence.d.ts.map +1 -1
  24. package/dist/commands/project/delivery/evidence.js +163 -17
  25. package/dist/commands/project/delivery/refresh.d.ts.map +1 -1
  26. package/dist/commands/project/delivery/refresh.js +13 -16
  27. package/dist/commands/project/journal-check/index.d.ts +2 -0
  28. package/dist/commands/project/journal-check/index.d.ts.map +1 -1
  29. package/dist/commands/project/journal-check/index.js +78 -1
  30. package/dist/commands/project/journal-check/types.d.ts +1 -1
  31. package/dist/commands/project/journal-check/types.d.ts.map +1 -1
  32. package/dist/providers/codex/codec/config-merge.d.ts +5 -1
  33. package/dist/providers/codex/codec/config-merge.d.ts.map +1 -1
  34. package/dist/providers/codex/codec/config-merge.js +111 -62
  35. package/dist/providers/codex/codec/config-toml-edit.d.ts +8 -0
  36. package/dist/providers/codex/codec/config-toml-edit.d.ts.map +1 -0
  37. package/dist/providers/codex/codec/config-toml-edit.js +718 -0
  38. package/package.json +3 -3
@@ -0,0 +1,718 @@
1
+ import { CliError } from '../../../errors/index.js';
2
+ const BARE_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
3
+ const BARE_VALUE_TERMINATORS = new Set([
4
+ ' ',
5
+ '\t',
6
+ ',',
7
+ ']',
8
+ '}',
9
+ '#',
10
+ '\r',
11
+ '\n',
12
+ ]);
13
+ const SIMPLE_ESCAPES = {
14
+ b: '\b',
15
+ t: '\t',
16
+ n: '\n',
17
+ f: '\f',
18
+ r: '\r',
19
+ '"': '"',
20
+ '\\': '\\',
21
+ };
22
+ const MAX_EDIT_PASSES = 10_000;
23
+ function unsupported(detail) {
24
+ return new CliError(`Cannot edit .codex/config.toml without rewriting user content: ${detail}. Edit the file by hand and rerun sync.`);
25
+ }
26
+ function findLast(items, predicate) {
27
+ for (let index = items.length - 1; index >= 0; index--) {
28
+ if (predicate(items[index])) {
29
+ return items[index];
30
+ }
31
+ }
32
+ return undefined;
33
+ }
34
+ function pathsEqual(left, right) {
35
+ return (left.length === right.length &&
36
+ left.every((segment, index) => segment === right[index]));
37
+ }
38
+ function startsWithPath(path, prefix) {
39
+ return (path.length >= prefix.length &&
40
+ prefix.every((segment, index) => segment === path[index]));
41
+ }
42
+ function isStrictPrefix(prefix, path) {
43
+ return prefix.length < path.length && startsWithPath(path, prefix);
44
+ }
45
+ class TomlLexer {
46
+ text;
47
+ pos = 0;
48
+ sections = [];
49
+ entries = [];
50
+ comments = [];
51
+ constructor(text) {
52
+ this.text = text;
53
+ }
54
+ lex() {
55
+ let section = {
56
+ header: null,
57
+ path: [],
58
+ isArray: false,
59
+ lastEnd: 0,
60
+ };
61
+ this.sections.push(section);
62
+ while (this.pos < this.text.length) {
63
+ const lineStart = this.pos;
64
+ this.skipInlineWhitespace();
65
+ if (this.pos >= this.text.length) {
66
+ break;
67
+ }
68
+ if (this.atNewline()) {
69
+ this.skipNewline();
70
+ continue;
71
+ }
72
+ if (this.peek() === '#') {
73
+ const start = this.pos;
74
+ this.skipComment();
75
+ this.comments.push({ lineStart, start, end: this.pos });
76
+ continue;
77
+ }
78
+ if (this.peek() === '[') {
79
+ const isArray = this.text.startsWith('[[', this.pos);
80
+ this.pos += isArray ? 2 : 1;
81
+ const path = this.lexKey();
82
+ this.expect(isArray ? ']]' : ']');
83
+ const end = this.finishLine();
84
+ section = {
85
+ header: { path, isArray, lineStart, end },
86
+ path,
87
+ isArray,
88
+ lastEnd: end,
89
+ };
90
+ this.sections.push(section);
91
+ continue;
92
+ }
93
+ const entry = this.lexKeyValue(section, null, section.path, lineStart);
94
+ entry.end = this.finishLine();
95
+ section.lastEnd = entry.end;
96
+ }
97
+ return {
98
+ sections: this.sections,
99
+ entries: this.entries,
100
+ blocks: findMarkerBlocks(this.text, this.comments),
101
+ };
102
+ }
103
+ peek(offset = 0) {
104
+ return this.text[this.pos + offset];
105
+ }
106
+ fail(detail) {
107
+ throw unsupported(`${detail} at offset ${this.pos}`);
108
+ }
109
+ atNewline() {
110
+ return (this.peek() === '\n' || (this.peek() === '\r' && this.peek(1) === '\n'));
111
+ }
112
+ skipNewline() {
113
+ this.pos += this.peek() === '\r' ? 2 : 1;
114
+ }
115
+ skipInlineWhitespace() {
116
+ while (this.peek() === ' ' || this.peek() === '\t') {
117
+ this.pos += 1;
118
+ }
119
+ }
120
+ skipComment() {
121
+ while (this.pos < this.text.length && !this.atNewline()) {
122
+ this.pos += 1;
123
+ }
124
+ }
125
+ skipArrayWhitespace() {
126
+ for (;;) {
127
+ this.skipInlineWhitespace();
128
+ if (this.atNewline()) {
129
+ this.skipNewline();
130
+ }
131
+ else if (this.peek() === '#') {
132
+ this.skipComment();
133
+ }
134
+ else {
135
+ return;
136
+ }
137
+ }
138
+ }
139
+ expect(token) {
140
+ this.skipInlineWhitespace();
141
+ if (!this.text.startsWith(token, this.pos)) {
142
+ this.fail(`expected "${token}"`);
143
+ }
144
+ this.pos += token.length;
145
+ }
146
+ finishLine() {
147
+ this.skipInlineWhitespace();
148
+ if (this.peek() === '#') {
149
+ this.skipComment();
150
+ }
151
+ if (this.pos < this.text.length && !this.atNewline()) {
152
+ this.fail('expected end of line');
153
+ }
154
+ return this.pos;
155
+ }
156
+ lexKey() {
157
+ const segments = [];
158
+ for (;;) {
159
+ this.skipInlineWhitespace();
160
+ const char = this.peek();
161
+ if (char === '"') {
162
+ segments.push(this.lexBasicKey());
163
+ }
164
+ else if (char === "'") {
165
+ const close = this.text.indexOf("'", this.pos + 1);
166
+ if (close < 0) {
167
+ this.fail('unterminated literal key');
168
+ }
169
+ segments.push(this.text.slice(this.pos + 1, close));
170
+ this.pos = close + 1;
171
+ }
172
+ else {
173
+ const start = this.pos;
174
+ while (/[A-Za-z0-9_-]/.test(this.peek() ?? '')) {
175
+ this.pos += 1;
176
+ }
177
+ if (start === this.pos) {
178
+ this.fail('expected a key');
179
+ }
180
+ segments.push(this.text.slice(start, this.pos));
181
+ }
182
+ this.skipInlineWhitespace();
183
+ if (this.peek() !== '.') {
184
+ return segments;
185
+ }
186
+ this.pos += 1;
187
+ }
188
+ }
189
+ lexBasicKey() {
190
+ let value = '';
191
+ this.pos += 1;
192
+ for (;;) {
193
+ const char = this.peek();
194
+ if (char === undefined || char === '\n' || char === '\r') {
195
+ this.fail('unterminated quoted key');
196
+ }
197
+ this.pos += 1;
198
+ if (char === '"') {
199
+ return value;
200
+ }
201
+ if (char !== '\\') {
202
+ value += char;
203
+ continue;
204
+ }
205
+ const escape = this.peek() ?? '';
206
+ this.pos += 1;
207
+ if (escape in SIMPLE_ESCAPES) {
208
+ value += SIMPLE_ESCAPES[escape];
209
+ continue;
210
+ }
211
+ const width = escape === 'u' ? 4 : escape === 'U' ? 8 : 0;
212
+ const hex = this.text.slice(this.pos, this.pos + width);
213
+ if (width === 0 || !/^[0-9A-Fa-f]+$/.test(hex) || hex.length !== width) {
214
+ this.fail('invalid escape in quoted key');
215
+ }
216
+ value += String.fromCodePoint(Number.parseInt(hex, 16));
217
+ this.pos += width;
218
+ }
219
+ }
220
+ lexKeyValue(section, inline, containerPath, lineStart) {
221
+ this.skipInlineWhitespace();
222
+ const start = this.pos;
223
+ const keyPath = this.lexKey();
224
+ this.expect('=');
225
+ this.skipInlineWhitespace();
226
+ const entry = {
227
+ path: [...containerPath, ...keyPath],
228
+ keyPath,
229
+ containerPath,
230
+ section,
231
+ inline,
232
+ start,
233
+ lineStart,
234
+ valueStart: this.pos,
235
+ valueEnd: this.pos,
236
+ end: this.pos,
237
+ table: null,
238
+ };
239
+ if (!section.isArray) {
240
+ this.entries.push(entry);
241
+ }
242
+ entry.table = this.lexValue(entry.path, section);
243
+ entry.valueEnd = this.pos;
244
+ entry.end = this.pos;
245
+ return entry;
246
+ }
247
+ lexValue(path, section) {
248
+ const char = this.peek();
249
+ if (this.text.startsWith('"""', this.pos)) {
250
+ this.lexMultilineString('"""', true);
251
+ }
252
+ else if (this.text.startsWith("'''", this.pos)) {
253
+ this.lexMultilineString("'''", false);
254
+ }
255
+ else if (char === '"') {
256
+ this.lexSingleLineString('"', true);
257
+ }
258
+ else if (char === "'") {
259
+ this.lexSingleLineString("'", false);
260
+ }
261
+ else if (char === '[') {
262
+ this.lexArray(section);
263
+ }
264
+ else if (char === '{') {
265
+ return this.lexInlineTable(path, section);
266
+ }
267
+ else {
268
+ this.lexBareValue();
269
+ }
270
+ return null;
271
+ }
272
+ lexMultilineString(delimiter, escapes) {
273
+ this.pos += 3;
274
+ for (;;) {
275
+ if (this.pos >= this.text.length) {
276
+ this.fail('unterminated multiline string');
277
+ }
278
+ if (escapes && this.peek() === '\\') {
279
+ this.pos += 2;
280
+ continue;
281
+ }
282
+ if (this.text.startsWith(delimiter, this.pos)) {
283
+ this.pos += 3;
284
+ for (let extra = 0; extra < 2 && this.peek() === delimiter[0]; extra++) {
285
+ this.pos += 1;
286
+ }
287
+ return;
288
+ }
289
+ this.pos += 1;
290
+ }
291
+ }
292
+ lexSingleLineString(delimiter, escapes) {
293
+ this.pos += 1;
294
+ for (;;) {
295
+ const char = this.peek();
296
+ if (char === undefined || char === '\n' || char === '\r') {
297
+ this.fail('unterminated string');
298
+ }
299
+ if (escapes && char === '\\') {
300
+ this.pos += 2;
301
+ continue;
302
+ }
303
+ this.pos += 1;
304
+ if (char === delimiter) {
305
+ return;
306
+ }
307
+ }
308
+ }
309
+ lexArray(section) {
310
+ this.pos += 1;
311
+ for (;;) {
312
+ this.skipArrayWhitespace();
313
+ if (this.peek() === ']') {
314
+ this.pos += 1;
315
+ return;
316
+ }
317
+ if (this.pos >= this.text.length) {
318
+ this.fail('unterminated array');
319
+ }
320
+ this.lexValue(null, section);
321
+ this.skipArrayWhitespace();
322
+ if (this.peek() === ',') {
323
+ this.pos += 1;
324
+ }
325
+ else if (this.peek() !== ']') {
326
+ this.fail('expected "," or "]" in array');
327
+ }
328
+ }
329
+ }
330
+ lexInlineTable(path, section) {
331
+ const table = {
332
+ path: path ?? [],
333
+ open: this.pos,
334
+ close: this.pos,
335
+ entries: [],
336
+ };
337
+ this.pos += 1;
338
+ this.skipInlineWhitespace();
339
+ if (this.peek() !== '}') {
340
+ for (;;) {
341
+ if (path === null) {
342
+ this.lexValueOnlyInlineEntry(section);
343
+ }
344
+ else {
345
+ table.entries.push(this.lexKeyValue(section, table, path, table.open));
346
+ }
347
+ this.skipInlineWhitespace();
348
+ if (this.peek() === ',') {
349
+ this.pos += 1;
350
+ continue;
351
+ }
352
+ if (this.peek() !== '}') {
353
+ this.fail('expected "," or "}" in inline table');
354
+ }
355
+ break;
356
+ }
357
+ }
358
+ table.close = this.pos;
359
+ this.pos += 1;
360
+ return path === null ? null : table;
361
+ }
362
+ /** Inline tables inside arrays are array elements, never table definitions. */
363
+ lexValueOnlyInlineEntry(section) {
364
+ this.skipInlineWhitespace();
365
+ this.lexKey();
366
+ this.expect('=');
367
+ this.skipInlineWhitespace();
368
+ this.lexValue(null, section);
369
+ }
370
+ lexBareValue() {
371
+ const start = this.pos;
372
+ while (this.pos < this.text.length &&
373
+ !BARE_VALUE_TERMINATORS.has(this.peek())) {
374
+ this.pos += 1;
375
+ }
376
+ if (start === this.pos) {
377
+ this.fail('expected a value');
378
+ }
379
+ if (/^\d{4}-\d{2}-\d{2}$/.test(this.text.slice(start, this.pos)) &&
380
+ /^ \d{2}:/.test(this.text.slice(this.pos, this.pos + 4))) {
381
+ this.pos += 1;
382
+ this.lexBareValue();
383
+ }
384
+ }
385
+ }
386
+ function lexToml(text) {
387
+ return new TomlLexer(text).lex();
388
+ }
389
+ /** Renders a TOML basic string; JSON escaping is not TOML-safe for all input. */
390
+ export function renderTomlString(value) {
391
+ let rendered = '"';
392
+ for (const char of value) {
393
+ const code = char.codePointAt(0);
394
+ if (char === '"' || char === '\\') {
395
+ rendered += `\\${char}`;
396
+ }
397
+ else if (char === '\b') {
398
+ rendered += '\\b';
399
+ }
400
+ else if (char === '\t') {
401
+ rendered += '\\t';
402
+ }
403
+ else if (char === '\n') {
404
+ rendered += '\\n';
405
+ }
406
+ else if (char === '\f') {
407
+ rendered += '\\f';
408
+ }
409
+ else if (char === '\r') {
410
+ rendered += '\\r';
411
+ }
412
+ else if (code < 0x20 || code === 0x7f) {
413
+ rendered += `\\u${code.toString(16).padStart(4, '0')}`;
414
+ }
415
+ else if (code >= 0xd800 && code <= 0xdfff) {
416
+ throw unsupported('a value contains an unpaired surrogate');
417
+ }
418
+ else {
419
+ rendered += char;
420
+ }
421
+ }
422
+ return `${rendered}"`;
423
+ }
424
+ function renderKey(segment) {
425
+ return BARE_KEY_PATTERN.test(segment) ? segment : renderTomlString(segment);
426
+ }
427
+ function renderKeyPath(path) {
428
+ return path.map(renderKey).join('.');
429
+ }
430
+ function detectEol(text) {
431
+ const newline = text.indexOf('\n');
432
+ return newline > 0 && text[newline - 1] === '\r' ? '\r\n' : '\n';
433
+ }
434
+ function splice(text, start, end, replacement) {
435
+ return `${text.slice(0, start)}${replacement}${text.slice(end)}`;
436
+ }
437
+ function lineIndentAt(text, lineStart) {
438
+ return /^[ \t]*/.exec(text.slice(lineStart))[0];
439
+ }
440
+ function previousLineStart(text, lineStart) {
441
+ if (lineStart === 0) {
442
+ return null;
443
+ }
444
+ return text.lastIndexOf('\n', lineStart - 2) + 1;
445
+ }
446
+ function lineEndIncludingNewline(text, contentEnd) {
447
+ if (text.startsWith('\r\n', contentEnd)) {
448
+ return contentEnd + 2;
449
+ }
450
+ return text[contentEnd] === '\n' ? contentEnd + 1 : contentEnd;
451
+ }
452
+ function findDefinition(document, tablePath) {
453
+ if (tablePath.length === 0) {
454
+ return { kind: 'root' };
455
+ }
456
+ const section = document.sections.find((candidate) => candidate.header !== null &&
457
+ !candidate.isArray &&
458
+ pathsEqual(candidate.path, tablePath));
459
+ if (section) {
460
+ return { kind: 'header', section };
461
+ }
462
+ const inline = document.entries.find((entry) => pathsEqual(entry.path, tablePath));
463
+ if (inline) {
464
+ if (!inline.table) {
465
+ throw unsupported(`${renderKeyPath(tablePath)} is not a table`);
466
+ }
467
+ return { kind: 'inline', table: inline.table };
468
+ }
469
+ const dotted = document.entries.filter((entry) => isStrictPrefix(tablePath, entry.path) &&
470
+ isStrictPrefix(entry.containerPath, tablePath));
471
+ if (dotted.length > 0) {
472
+ return { kind: 'dotted', anchor: dotted.at(-1) };
473
+ }
474
+ return document.sections.some((candidate) => candidate.header !== null && isStrictPrefix(tablePath, candidate.path))
475
+ ? { kind: 'implicit' }
476
+ : null;
477
+ }
478
+ function insertIntoInlineTable(text, document, table, path, member) {
479
+ const block = markerBlockAt(document, table.close);
480
+ if (block) {
481
+ throw insideMarkerBlock(path, block);
482
+ }
483
+ const last = table.entries.at(-1);
484
+ if (!last) {
485
+ return splice(text, table.open + 1, table.close, ` ${member} `);
486
+ }
487
+ return splice(text, last.valueEnd, last.valueEnd, `, ${member}`);
488
+ }
489
+ function insertAfterStatement(text, lineStart, contentEnd, line) {
490
+ return splice(text, contentEnd, contentEnd, `${detectEol(text)}${lineIndentAt(text, lineStart)}${line}`);
491
+ }
492
+ /*
493
+ * Other tools own `# BEGIN <label>` ... `# END <label>` blocks and delete every
494
+ * line between the pair, so nothing OAT writes may be inserted inside one.
495
+ * Only standalone comment lines count; text inside strings is not a marker.
496
+ */
497
+ const MARKER_PATTERN = /^#[ \t]*(BEGIN|END)[ \t]+(\S(?:.*\S)?)[ \t]*$/;
498
+ function findMarkerBlocks(text, comments) {
499
+ const markers = comments.map((comment) => ({
500
+ comment,
501
+ match: MARKER_PATTERN.exec(text.slice(comment.start, comment.end)),
502
+ }));
503
+ const blocks = [];
504
+ markers.forEach(({ comment, match }, index) => {
505
+ if (match?.[1] !== 'BEGIN') {
506
+ return;
507
+ }
508
+ const label = match[2];
509
+ const end = markers
510
+ .slice(index + 1)
511
+ .find((candidate) => candidate.match?.[1] === 'END' && candidate.match[2] === label);
512
+ if (end) {
513
+ blocks.push({
514
+ label,
515
+ beginLineStart: comment.lineStart,
516
+ beginEnd: comment.end,
517
+ endLineStart: end.comment.lineStart,
518
+ });
519
+ }
520
+ });
521
+ return blocks;
522
+ }
523
+ function markerBlockAt(document, offset) {
524
+ return document.blocks.find((block) => block.beginEnd <= offset && offset <= block.endLineStart);
525
+ }
526
+ function outsideMarkerBlocks(document, offset) {
527
+ return offset !== null && markerBlockAt(document, offset) ? null : offset;
528
+ }
529
+ function insideMarkerBlock(path, block) {
530
+ return unsupported(`${renderKeyPath(path)} would be written inside the "# BEGIN ${block.label}" block`);
531
+ }
532
+ /** Appends after `anchor`, or after the last statement before a marker block. */
533
+ function insertIntoSection(text, document, section, anchor, path, line) {
534
+ let { lineStart, end } = anchor;
535
+ for (;;) {
536
+ const block = markerBlockAt(document, end);
537
+ if (!block) {
538
+ return insertAfterStatement(text, lineStart, end, line);
539
+ }
540
+ const previous = findLast(document.entries, (entry) => entry.section === section &&
541
+ entry.inline === null &&
542
+ entry.end <= block.beginLineStart);
543
+ const header = section.header;
544
+ if (previous) {
545
+ ({ lineStart, end } = previous);
546
+ }
547
+ else if (header && header.end <= block.beginLineStart) {
548
+ ({ lineStart, end } = header);
549
+ }
550
+ else {
551
+ throw insideMarkerBlock(path, block);
552
+ }
553
+ }
554
+ }
555
+ function isCommentLine(line) {
556
+ return /^[ \t]*#/.test(line);
557
+ }
558
+ function isBlankLine(line) {
559
+ return /^[ \t]*(?:\r?\n)?$/.test(line);
560
+ }
561
+ /*
562
+ * Other tools wrap tables in marker comments. A comment run with content
563
+ * directly on both sides may be the `# END` of the block above or the
564
+ * `# BEGIN` of the block below, so the two helpers below return null rather
565
+ * than guess, and the caller appends at the end of the file instead.
566
+ */
567
+ function leadingCommentStart(text, lineStart) {
568
+ let start = lineStart;
569
+ for (;;) {
570
+ const previous = previousLineStart(text, start);
571
+ if (previous === null) {
572
+ return start;
573
+ }
574
+ const line = text.slice(previous, start);
575
+ if (!isCommentLine(line)) {
576
+ return isBlankLine(line) || start === lineStart ? start : null;
577
+ }
578
+ start = previous;
579
+ }
580
+ }
581
+ function trailingCommentEnd(text, contentEnd) {
582
+ let end = contentEnd;
583
+ let cursor = lineEndIncludingNewline(text, contentEnd);
584
+ while (cursor < text.length) {
585
+ const newline = text.indexOf('\n', cursor);
586
+ const next = newline < 0 ? text.length : newline + 1;
587
+ const line = text.slice(cursor, next);
588
+ if (!isCommentLine(line)) {
589
+ return isBlankLine(line) || end === contentEnd ? end : null;
590
+ }
591
+ end = cursor + line.replace(/\r?\n$/, '').length;
592
+ cursor = next;
593
+ }
594
+ return end;
595
+ }
596
+ function insertSection(text, document, tablePath, lines) {
597
+ const eol = detectEol(text);
598
+ const block = [`[${renderKeyPath(tablePath)}]`, ...lines].join(eol);
599
+ const headers = document.sections.filter((section) => section.header !== null);
600
+ const firstSubTable = headers.find((section) => isStrictPrefix(tablePath, section.path));
601
+ const start = firstSubTable
602
+ ? outsideMarkerBlocks(document, leadingCommentStart(text, firstSubTable.header.lineStart))
603
+ : null;
604
+ if (start !== null) {
605
+ return splice(text, start, start, `${block}${eol}${eol}`);
606
+ }
607
+ const familyDepth = firstSubTable ? 0 : tablePath.length - 1;
608
+ for (let depth = familyDepth; depth >= 1; depth--) {
609
+ const family = tablePath.slice(0, depth);
610
+ const lastInFamily = findLast(headers, (section) => startsWithPath(section.path, family));
611
+ if (lastInFamily) {
612
+ const end = outsideMarkerBlocks(document, trailingCommentEnd(text, lastInFamily.lastEnd));
613
+ if (end !== null) {
614
+ return splice(text, end, end, `${eol}${eol}${block}`);
615
+ }
616
+ break;
617
+ }
618
+ }
619
+ const contentEnd = text.length - /(?:\r?\n[ \t]*)*$/.exec(text)[0].length;
620
+ if (contentEnd === 0) {
621
+ return splice(text, 0, 0, `${block}${eol}`);
622
+ }
623
+ return splice(text, contentEnd, contentEnd, `${eol}${eol}${block}`);
624
+ }
625
+ /** `renderedValue` must already be a valid TOML value literal. */
626
+ export function setTomlValue(text, path, renderedValue) {
627
+ const document = lexToml(text);
628
+ const existing = document.entries.find((entry) => pathsEqual(entry.path, path));
629
+ if (existing) {
630
+ return splice(text, existing.valueStart, existing.valueEnd, renderedValue);
631
+ }
632
+ const tablePath = path.slice(0, -1);
633
+ let depth = tablePath.length;
634
+ let definition = findDefinition(document, tablePath);
635
+ while (definition === null) {
636
+ depth -= 1;
637
+ definition = findDefinition(document, tablePath.slice(0, depth));
638
+ }
639
+ const rest = path.slice(depth);
640
+ switch (definition.kind) {
641
+ case 'header':
642
+ if (rest.length === 1) {
643
+ const { section } = definition;
644
+ const lastEntry = findLast(document.entries, (entry) => entry.section === section && entry.inline === null);
645
+ return insertIntoSection(text, document, section, {
646
+ lineStart: lastEntry?.lineStart ?? section.header.lineStart,
647
+ end: section.lastEnd,
648
+ }, path, `${renderKey(rest[0])} = ${renderedValue}`);
649
+ }
650
+ break;
651
+ case 'inline': {
652
+ const member = rest
653
+ .slice(0, -1)
654
+ .reduceRight((inner, segment) => `${renderKey(segment)} = { ${inner} }`, `${renderKey(rest.at(-1))} = ${renderedValue}`);
655
+ return insertIntoInlineTable(text, document, definition.table, path, member);
656
+ }
657
+ case 'dotted': {
658
+ const { anchor } = definition;
659
+ const member = `${renderKeyPath(path.slice(anchor.containerPath.length))} = ${renderedValue}`;
660
+ if (anchor.inline) {
661
+ return insertIntoInlineTable(text, document, anchor.inline, path, member);
662
+ }
663
+ return insertIntoSection(text, document, anchor.section, anchor, path, member);
664
+ }
665
+ case 'root':
666
+ case 'implicit':
667
+ break;
668
+ }
669
+ if (tablePath.length === 0) {
670
+ throw unsupported(`top-level key ${renderKeyPath(path)} is not editable`);
671
+ }
672
+ return insertSection(text, document, tablePath, [
673
+ `${renderKey(path.at(-1))} = ${renderedValue}`,
674
+ ]);
675
+ }
676
+ function removeLines(text, start, contentEnd) {
677
+ let removeStart = start;
678
+ const removeEnd = lineEndIncludingNewline(text, contentEnd);
679
+ const previous = previousLineStart(text, start);
680
+ const following = text.slice(removeEnd);
681
+ if (previous !== null &&
682
+ /^[ \t]*\r?\n$/.test(text.slice(previous, start)) &&
683
+ /^[ \t]*(?:\r?\n|$)/.test(following)) {
684
+ removeStart = previous;
685
+ }
686
+ return splice(text, removeStart, removeEnd, '');
687
+ }
688
+ function removeInlineEntry(text, entry) {
689
+ const table = entry.inline;
690
+ const index = table.entries.indexOf(entry);
691
+ if (table.entries.length === 1) {
692
+ return splice(text, table.open + 1, table.close, '');
693
+ }
694
+ const next = table.entries[index + 1];
695
+ if (next) {
696
+ return splice(text, entry.start, next.start, '');
697
+ }
698
+ return splice(text, table.entries[index - 1].valueEnd, entry.valueEnd, '');
699
+ }
700
+ export function removeTomlTable(text, path) {
701
+ let current = text;
702
+ for (let pass = 0; pass < MAX_EDIT_PASSES; pass++) {
703
+ const document = lexToml(current);
704
+ const section = document.sections.find((candidate) => candidate.header !== null && startsWithPath(candidate.path, path));
705
+ if (section) {
706
+ current = removeLines(current, section.header.lineStart, section.lastEnd);
707
+ continue;
708
+ }
709
+ const entry = document.entries.find((candidate) => startsWithPath(candidate.path, path));
710
+ if (!entry) {
711
+ return current;
712
+ }
713
+ current = entry.inline
714
+ ? removeInlineEntry(current, entry)
715
+ : removeLines(current, entry.lineStart, entry.end);
716
+ }
717
+ throw unsupported(`${renderKeyPath(path)} could not be removed`);
718
+ }