@astrosheep/keiyaku 0.1.47 → 0.1.49

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 (58) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/round-runner.js +1 -1
  3. package/build/agents/selector.js +1 -1
  4. package/build/common/constants.js +6 -1
  5. package/build/common/errors.js +1 -1
  6. package/build/common/response-style.js +4 -1
  7. package/build/config/term-presets/constants.js +24 -0
  8. package/build/config/term-presets/default-preset.js +119 -0
  9. package/build/config/term-presets/index.js +5 -0
  10. package/build/config/term-presets/mischief-preset.js +105 -0
  11. package/build/config/term-presets/pocket-preset.js +105 -0
  12. package/build/config/term-presets/resolver.js +52 -0
  13. package/build/config/term-presets/types.js +1 -0
  14. package/build/handlers/ask.js +10 -120
  15. package/build/handlers/close.js +2 -9
  16. package/build/handlers/drive.js +1 -15
  17. package/build/handlers/shared.js +1 -2
  18. package/build/handlers/start.js +0 -17
  19. package/build/handlers/status.js +2 -6
  20. package/build/index.js +2 -2
  21. package/build/types/git-diff.js +1 -0
  22. package/build/utils/ask-history.js +75 -0
  23. package/build/utils/draft.js +22 -0
  24. package/build/utils/git-diff/constants.js +9 -0
  25. package/build/utils/git-diff/filter.js +70 -0
  26. package/build/utils/git-diff/incremental.js +111 -0
  27. package/build/utils/git-diff/index.js +3 -0
  28. package/build/utils/git-diff/parsers.js +160 -0
  29. package/build/utils/git-diff/preview.js +157 -0
  30. package/build/utils/git-diff/stat.js +27 -0
  31. package/build/utils/git-diff/types.js +1 -0
  32. package/build/utils/git-ops.js +9 -0
  33. package/build/utils/git.js +1 -1
  34. package/build/utils/keiyaku-document/index.js +13 -0
  35. package/build/utils/keiyaku-document/lex.js +178 -0
  36. package/build/utils/keiyaku-document/parser.js +242 -0
  37. package/build/utils/keiyaku-document/render.js +68 -0
  38. package/build/utils/keiyaku-document/sections.js +105 -0
  39. package/build/utils/keiyaku-document/types.js +6 -0
  40. package/build/utils/keiyaku-document.test.js +12 -1
  41. package/build/utils/trace.js +6 -7
  42. package/build/workflow/ask-execution.js +81 -0
  43. package/build/workflow/drive.js +10 -5
  44. package/build/workflow/iterate-plan.js +1 -1
  45. package/build/workflow/keiyaku-draft.js +1 -1
  46. package/build/workflow/{contract.js → keiyaku.js} +2 -2
  47. package/build/workflow/markdown-normalization.js +3 -2
  48. package/build/workflow/present.js +68 -13
  49. package/build/workflow/response-builders.js +75 -44
  50. package/build/workflow/response-meta.js +15 -0
  51. package/build/workflow/response-renderer.js +2 -13
  52. package/build/workflow/round-summary.js +1 -1
  53. package/build/workflow/start.js +8 -3
  54. package/build/workflow/status.js +129 -62
  55. package/package.json +1 -1
  56. package/build/config/term-presets.js +0 -398
  57. package/build/utils/git-diff.js +0 -519
  58. package/build/utils/keiyaku-document.js +0 -539
@@ -1,539 +0,0 @@
1
- export class KeiyakuParseError extends Error {
2
- constructor(message, cause) {
3
- super(message, cause === undefined ? undefined : { cause });
4
- this.name = "KeiyakuParseError";
5
- }
6
- }
7
- function countLeadingSpaces(line) {
8
- let idx = 0;
9
- while (idx < line.length && line[idx] === " ") {
10
- idx += 1;
11
- }
12
- return idx;
13
- }
14
- function parseFence(trimmedLine) {
15
- if (!trimmedLine.startsWith("```"))
16
- return null;
17
- let idx = 0;
18
- while (idx < trimmedLine.length && trimmedLine[idx] === "`") {
19
- idx += 1;
20
- }
21
- if (idx < 3)
22
- return null;
23
- return {
24
- length: idx,
25
- info: trimmedLine.slice(idx).trim(),
26
- };
27
- }
28
- function isAsciiDigit(char) {
29
- return char !== undefined && char >= "0" && char <= "9";
30
- }
31
- function isSpaceOrTab(char) {
32
- return char === " " || char === "\t";
33
- }
34
- function parseHeader(trimmedLine) {
35
- let level = 0;
36
- while (level < trimmedLine.length && trimmedLine[level] === "#") {
37
- level += 1;
38
- }
39
- if (level === 0)
40
- return null;
41
- if (!isSpaceOrTab(trimmedLine[level]))
42
- return null;
43
- const text = trimmedLine.slice(level).trim();
44
- if (!text)
45
- return null;
46
- return { level, text };
47
- }
48
- function stripUtf8Bom(content) {
49
- if (!content.startsWith("\uFEFF"))
50
- return content;
51
- return content.slice(1);
52
- }
53
- function parseListMarker(line) {
54
- let indent = 0;
55
- while (indent < line.length && line[indent] === " ") {
56
- indent += 1;
57
- }
58
- const markerStart = indent;
59
- const markerChar = line[markerStart];
60
- let markerEnd = markerStart;
61
- let ordered = false;
62
- if (markerChar === "-" || markerChar === "*" || markerChar === "+") {
63
- markerEnd += 1;
64
- }
65
- else if (isAsciiDigit(markerChar)) {
66
- while (isAsciiDigit(line[markerEnd])) {
67
- markerEnd += 1;
68
- }
69
- const orderedSuffix = line[markerEnd];
70
- if (orderedSuffix !== "." && orderedSuffix !== ")")
71
- return null;
72
- markerEnd += 1;
73
- ordered = true;
74
- }
75
- else {
76
- return null;
77
- }
78
- if (!isSpaceOrTab(line[markerEnd]))
79
- return null;
80
- let contentStart = markerEnd;
81
- while (isSpaceOrTab(line[contentStart])) {
82
- contentStart += 1;
83
- }
84
- return {
85
- indent,
86
- ordered,
87
- marker: line.slice(markerStart, markerEnd),
88
- body: line.slice(contentStart),
89
- };
90
- }
91
- function isBlankLine(line) {
92
- return line.trim().length === 0;
93
- }
94
- function stripTrailingBlankLines(lines) {
95
- const trimmed = [...lines];
96
- while (trimmed.length > 0 && isBlankLine(trimmed[trimmed.length - 1])) {
97
- trimmed.pop();
98
- }
99
- return trimmed;
100
- }
101
- function normalizeSectionTitle(title) {
102
- return title.trim().toLowerCase().replace(/\s+/g, " ");
103
- }
104
- function isSectionHeaderToken(token) {
105
- return token.type === "header" && (token.level === 1 || token.level === 2);
106
- }
107
- function isFenceClosingToken(token, fence) {
108
- return token.type === "fence" && token.length >= fence.length;
109
- }
110
- function lexMarkdown(content) {
111
- const lines = stripUtf8Bom(content).split(/\r?\n/);
112
- const tokens = [];
113
- for (const line of lines) {
114
- const leadingSpaces = countLeadingSpaces(line);
115
- if (leadingSpaces <= 3) {
116
- const trimmedLine = line.trimStart();
117
- const fence = parseFence(trimmedLine);
118
- if (fence) {
119
- tokens.push({
120
- type: "fence",
121
- raw: line,
122
- leadingSpaces,
123
- length: fence.length,
124
- info: fence.info,
125
- });
126
- continue;
127
- }
128
- const header = parseHeader(trimmedLine);
129
- if (header) {
130
- tokens.push({
131
- type: "header",
132
- raw: line,
133
- leadingSpaces,
134
- level: header.level,
135
- text: header.text,
136
- });
137
- continue;
138
- }
139
- }
140
- const marker = parseListMarker(line);
141
- if (marker) {
142
- tokens.push({
143
- type: "list_marker",
144
- raw: line,
145
- leadingSpaces,
146
- indent: marker.indent,
147
- ordered: marker.ordered,
148
- marker: marker.marker,
149
- body: marker.body,
150
- });
151
- continue;
152
- }
153
- tokens.push({
154
- type: "text",
155
- raw: line,
156
- leadingSpaces,
157
- });
158
- }
159
- return tokens;
160
- }
161
- class MarkdownParser {
162
- tokens;
163
- options;
164
- index = 0;
165
- constructor(tokens, options) {
166
- this.tokens = tokens;
167
- this.options = options;
168
- }
169
- parseDocument() {
170
- return {
171
- type: "document",
172
- children: this.parseBlocks(() => false),
173
- };
174
- }
175
- parseBlocks(shouldStop) {
176
- const blocks = [];
177
- while (!this.isEof()) {
178
- if (shouldStop())
179
- break;
180
- const token = this.peek();
181
- if (!token)
182
- break;
183
- if (this.options.allowSections && isSectionHeaderToken(token)) {
184
- blocks.push(this.parseSection());
185
- continue;
186
- }
187
- if (token.type === "fence") {
188
- blocks.push(this.parseCodeBlock());
189
- continue;
190
- }
191
- if (token.type === "header") {
192
- blocks.push(this.parseHeading());
193
- continue;
194
- }
195
- if (token.type === "list_marker" && token.indent <= 3) {
196
- blocks.push(this.parseList(token.indent, token.ordered));
197
- continue;
198
- }
199
- blocks.push(this.parseText(shouldStop));
200
- }
201
- return blocks;
202
- }
203
- parseSection() {
204
- const header = this.consume();
205
- if (!header || !isSectionHeaderToken(header)) {
206
- throw new KeiyakuParseError("invalid markdown parse state: expected section header");
207
- }
208
- const children = this.parseBlocks(() => {
209
- const next = this.peek();
210
- return next ? this.options.allowSections && isSectionHeaderToken(next) : false;
211
- });
212
- return {
213
- type: "section",
214
- level: header.level,
215
- title: header.text,
216
- children,
217
- };
218
- }
219
- parseCodeBlock() {
220
- const opener = this.consume();
221
- if (!opener || opener.type !== "fence") {
222
- throw new KeiyakuParseError("invalid markdown parse state: expected code fence");
223
- }
224
- const lines = [opener.raw];
225
- const fence = { length: opener.length };
226
- while (!this.isEof()) {
227
- const token = this.consume();
228
- if (!token)
229
- break;
230
- lines.push(token.raw);
231
- if (isFenceClosingToken(token, fence)) {
232
- break;
233
- }
234
- }
235
- return {
236
- type: "code_block",
237
- fenceLength: opener.length,
238
- info: opener.info,
239
- lines,
240
- };
241
- }
242
- parseHeading() {
243
- const header = this.consume();
244
- if (!header || header.type !== "header") {
245
- throw new KeiyakuParseError("invalid markdown parse state: expected heading");
246
- }
247
- return {
248
- type: "heading",
249
- level: header.level,
250
- text: header.text,
251
- };
252
- }
253
- parseList(indent, ordered) {
254
- const items = [];
255
- while (!this.isEof()) {
256
- const token = this.peek();
257
- if (!token || token.type !== "list_marker")
258
- break;
259
- if (token.indent !== indent)
260
- break;
261
- if (token.ordered !== ordered)
262
- break;
263
- this.consume();
264
- items.push(this.parseListItem(token, indent));
265
- }
266
- return {
267
- type: "list",
268
- ordered,
269
- indent,
270
- items,
271
- };
272
- }
273
- parseListItem(markerToken, listIndent) {
274
- const lines = [markerToken.body];
275
- let itemFence = null;
276
- while (!this.isEof()) {
277
- const token = this.peek();
278
- if (!token)
279
- break;
280
- if (itemFence) {
281
- const consumed = this.consume();
282
- if (!consumed)
283
- break;
284
- lines.push(consumed.raw);
285
- if (isFenceClosingToken(consumed, itemFence)) {
286
- itemFence = null;
287
- }
288
- continue;
289
- }
290
- if (this.options.allowSections && isSectionHeaderToken(token)) {
291
- break;
292
- }
293
- if (token.type === "header" && token.leadingSpaces <= listIndent) {
294
- break;
295
- }
296
- if (token.type === "fence" && token.leadingSpaces <= listIndent) {
297
- break;
298
- }
299
- if (token.type === "list_marker") {
300
- if (token.indent < listIndent) {
301
- break;
302
- }
303
- if (token.indent === listIndent) {
304
- if (token.ordered !== markerToken.ordered) {
305
- break;
306
- }
307
- break;
308
- }
309
- }
310
- const consumed = this.consume();
311
- if (!consumed)
312
- break;
313
- lines.push(consumed.raw);
314
- if (consumed.type === "fence") {
315
- itemFence = { length: consumed.length };
316
- }
317
- }
318
- const normalizedLines = stripTrailingBlankLines(lines);
319
- const body = normalizedLines.join("\n");
320
- const children = parseToAST(body, { allowSections: false }).children;
321
- return {
322
- type: "list_item",
323
- marker: markerToken.marker,
324
- indent: markerToken.indent,
325
- children,
326
- };
327
- }
328
- parseText(shouldStop) {
329
- const lines = [];
330
- while (!this.isEof()) {
331
- if (shouldStop())
332
- break;
333
- const token = this.peek();
334
- if (!token)
335
- break;
336
- if (token.type === "fence")
337
- break;
338
- if (token.type === "header")
339
- break;
340
- if (token.type === "list_marker" && token.indent <= 3)
341
- break;
342
- if (this.options.allowSections && isSectionHeaderToken(token))
343
- break;
344
- lines.push(token.raw);
345
- this.consume();
346
- }
347
- if (lines.length === 0) {
348
- const fallback = this.consume();
349
- if (fallback) {
350
- lines.push(fallback.raw);
351
- }
352
- }
353
- return {
354
- type: "text",
355
- lines,
356
- value: lines.join("\n"),
357
- };
358
- }
359
- peek() {
360
- return this.tokens[this.index];
361
- }
362
- consume() {
363
- const token = this.tokens[this.index];
364
- this.index += 1;
365
- return token;
366
- }
367
- isEof() {
368
- return this.index >= this.tokens.length;
369
- }
370
- }
371
- function renderListItemFirstLine(item) {
372
- const content = renderNodeContent(item);
373
- if (!content) {
374
- return `${" ".repeat(item.indent)}${item.marker}`;
375
- }
376
- const [firstLine, ...rest] = content.split("\n");
377
- const prefix = `${" ".repeat(item.indent)}${item.marker}${firstLine.length > 0 ? " " : ""}${firstLine}`;
378
- if (rest.length === 0) {
379
- return prefix;
380
- }
381
- return [prefix, ...rest].join("\n");
382
- }
383
- function renderBlock(node) {
384
- switch (node.type) {
385
- case "section": {
386
- const header = `${"#".repeat(node.level)} ${node.title}`;
387
- const body = renderNodeContent(node);
388
- return body ? `${header}\n${body}` : header;
389
- }
390
- case "list":
391
- return node.items.map((item) => renderListItemFirstLine(item)).join("\n");
392
- case "code_block":
393
- return node.lines.join("\n");
394
- case "text":
395
- return node.value;
396
- case "heading":
397
- return `${"#".repeat(node.level)} ${node.text}`;
398
- }
399
- }
400
- export function parseToAST(content, options = {}) {
401
- const parser = new MarkdownParser(lexMarkdown(content), {
402
- allowSections: options.allowSections ?? true,
403
- });
404
- return parser.parseDocument();
405
- }
406
- export function renderNodeContent(node) {
407
- switch (node.type) {
408
- case "document":
409
- return node.children.map((child) => renderBlock(child)).join("\n");
410
- case "section":
411
- return node.children.map((child) => renderBlock(child)).join("\n");
412
- case "list":
413
- return renderBlock(node);
414
- case "list_item":
415
- return node.children.map((child) => renderBlock(child)).join("\n");
416
- case "code_block":
417
- return node.lines.join("\n");
418
- case "text":
419
- return node.value;
420
- case "heading":
421
- return `${"#".repeat(node.level)} ${node.text}`;
422
- }
423
- }
424
- export function renderSectionContent(sectionNode) {
425
- return sectionNode.children.map((child) => renderBlock(child)).join("\n");
426
- }
427
- export function extractListItems(sectionNode) {
428
- const items = [];
429
- for (const child of sectionNode.children) {
430
- if (child.type !== "list")
431
- continue;
432
- for (const item of child.items) {
433
- const rendered = renderNodeContent(item).trimEnd();
434
- if (rendered.trim().length > 0) {
435
- items.push(rendered);
436
- }
437
- }
438
- }
439
- return items;
440
- }
441
- function splitByHeadingBlocks(sectionNode) {
442
- const groups = [];
443
- let currentGroup = [];
444
- const commitGroup = () => {
445
- if (currentGroup.length === 0)
446
- return;
447
- const rendered = currentGroup.map((node) => renderBlock(node)).join("\n").trim();
448
- if (rendered.length > 0) {
449
- groups.push(rendered);
450
- }
451
- currentGroup = [];
452
- };
453
- for (const child of sectionNode.children) {
454
- if (child.type === "heading" && child.level >= 2) {
455
- commitGroup();
456
- currentGroup = [child];
457
- continue;
458
- }
459
- currentGroup.push(child);
460
- }
461
- commitGroup();
462
- return groups;
463
- }
464
- export function hasTopLevelHeaders(text) {
465
- const ast = parseToAST(text, { allowSections: false });
466
- return (() => {
467
- const stack = [ast];
468
- while (stack.length > 0) {
469
- const node = stack.pop();
470
- if (!node)
471
- continue;
472
- if (node.type === "heading" && node.level <= 2)
473
- return true;
474
- // We intentionally do not inspect code block lines for headings.
475
- if (node.type === "code_block" || node.type === "text")
476
- continue;
477
- if (node.type === "document" || node.type === "section" || node.type === "list_item") {
478
- stack.push(...node.children);
479
- }
480
- else if (node.type === "list") {
481
- stack.push(...node.items);
482
- }
483
- }
484
- return false;
485
- })();
486
- }
487
- export function parseMarkdownListSection(text) {
488
- const ast = parseToAST(text, { allowSections: false });
489
- const pseudoSection = {
490
- type: "section",
491
- level: 2,
492
- title: "",
493
- children: ast.children,
494
- };
495
- const listItems = extractListItems(pseudoSection);
496
- if (listItems.length > 0) {
497
- return listItems;
498
- }
499
- const nonHeadingSection = {
500
- type: "section",
501
- level: 2,
502
- title: "",
503
- children: pseudoSection.children.filter((child) => child.type !== "heading"),
504
- };
505
- const fallback = renderSectionContent(nonHeadingSection).trim();
506
- return fallback ? [fallback] : [];
507
- }
508
- export function parseMarkdownSections(text) {
509
- const ast = parseToAST(text, { allowSections: false });
510
- const pseudoSection = {
511
- type: "section",
512
- level: 2,
513
- title: "",
514
- children: ast.children,
515
- };
516
- return splitByHeadingBlocks(pseudoSection);
517
- }
518
- export function parseMarkdownStructure(content) {
519
- const ast = parseToAST(content);
520
- const sections = new Map();
521
- let title;
522
- for (const node of ast.children) {
523
- if (node.type !== "section")
524
- continue;
525
- if (node.level === 1 && !title) {
526
- title = node.title.trim() || undefined;
527
- continue;
528
- }
529
- if (node.level !== 2)
530
- continue;
531
- const normalizedSection = normalizeSectionTitle(node.title);
532
- if (sections.has(normalizedSection)) {
533
- throw new KeiyakuParseError(`invalid keiyaku draft: duplicate section header '${normalizedSection}'`);
534
- }
535
- const body = renderSectionContent(node).trim();
536
- sections.set(normalizedSection, body ? body.split("\n") : []);
537
- }
538
- return { title, sections };
539
- }