nightralph 0.0.53 → 0.0.54
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.
- package/README.md +82 -9
- package/dist/index.js +1947 -230
- package/dist/index.js.map +4 -4
- package/dist/learnings.js +356 -0
- package/dist/learnings.js.map +7 -0
- package/dist/meta.json +232 -12
- package/dist/orchestrator.js +442 -109
- package/dist/orchestrator.js.map +3 -3
- package/dist/preflight.js +34 -0
- package/dist/preflight.js.map +7 -0
- package/dist/progress.js +5 -4
- package/dist/progress.js.map +2 -2
- package/dist/retry.js +83 -0
- package/dist/retry.js.map +7 -0
- package/dist/review.js +662 -0
- package/dist/review.js.map +7 -0
- package/dist/rules.js +54 -0
- package/dist/rules.js.map +7 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/learnings.d.ts +68 -0
- package/dist/src/learnings.d.ts.map +1 -0
- package/dist/src/orchestrator.d.ts +14 -11
- package/dist/src/orchestrator.d.ts.map +1 -1
- package/dist/src/preflight.d.ts +6 -0
- package/dist/src/preflight.d.ts.map +1 -0
- package/dist/src/progress.d.ts +1 -1
- package/dist/src/progress.d.ts.map +1 -1
- package/dist/src/retry.d.ts +26 -0
- package/dist/src/retry.d.ts.map +1 -0
- package/dist/src/review.d.ts +127 -0
- package/dist/src/review.d.ts.map +1 -0
- package/dist/src/rules.d.ts +10 -0
- package/dist/src/rules.d.ts.map +1 -0
- package/dist/src/testcmd.d.ts +1 -0
- package/dist/src/testcmd.d.ts.map +1 -1
- package/dist/src/worktree.d.ts +30 -1
- package/dist/src/worktree.d.ts.map +1 -1
- package/dist/testcmd.js +13 -0
- package/dist/testcmd.js.map +2 -2
- package/dist/worktree.js +236 -32
- package/dist/worktree.js.map +2 -2
- package/package.json +4 -3
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import {
|
|
4
|
+
appendFileSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
writeFileSync
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { basename, dirname, join } from "node:path";
|
|
12
|
+
const MAX_OWN_ENTRIES = 4;
|
|
13
|
+
const MAX_ENTRY_CHARS = 4e3;
|
|
14
|
+
const LOG_TAIL_LINES = 40;
|
|
15
|
+
const NOTES_PATH = ".nightralph/notes.md";
|
|
16
|
+
const SENTINEL_PREFIX = "<!-- nightralph:entry";
|
|
17
|
+
const TRUNCATED_MARKER = "[truncated]";
|
|
18
|
+
const LEARNING_OUTCOMES = [
|
|
19
|
+
"done",
|
|
20
|
+
"exit",
|
|
21
|
+
"tests",
|
|
22
|
+
"install",
|
|
23
|
+
"no-commits",
|
|
24
|
+
"error",
|
|
25
|
+
"merge"
|
|
26
|
+
];
|
|
27
|
+
function encodeKey(key) {
|
|
28
|
+
return key.replace(/%/g, "%25").replace(/\s/g, (c) => encodeURIComponent(c));
|
|
29
|
+
}
|
|
30
|
+
__name(encodeKey, "encodeKey");
|
|
31
|
+
function fence(content) {
|
|
32
|
+
const longest = Math.max(
|
|
33
|
+
0,
|
|
34
|
+
...[...content.matchAll(/`+/g)].map((m) => m[0].length)
|
|
35
|
+
);
|
|
36
|
+
const ticks = "`".repeat(Math.max(3, longest + 1));
|
|
37
|
+
return `${ticks}
|
|
38
|
+
${content}
|
|
39
|
+
${ticks}`;
|
|
40
|
+
}
|
|
41
|
+
__name(fence, "fence");
|
|
42
|
+
function sanitize(text) {
|
|
43
|
+
if (!text) return "";
|
|
44
|
+
return text.replace(
|
|
45
|
+
new RegExp(SENTINEL_PREFIX, "g"),
|
|
46
|
+
"<!-- nightralph-entry"
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
__name(sanitize, "sanitize");
|
|
50
|
+
function renderAgentSummary(run, effort) {
|
|
51
|
+
const effortLine = effort ? `Effort: ${effort}.` : "";
|
|
52
|
+
const exitLine = `Agent exit: ${run.exitCode}.`;
|
|
53
|
+
const itemsText = run.completedItems.length > 0 ? run.completedItems.map(
|
|
54
|
+
(item) => `"${sanitize(item)}"`
|
|
55
|
+
).join(", ") + "." : "none.";
|
|
56
|
+
const itemsLine = `Checked items: ${itemsText}`;
|
|
57
|
+
return [
|
|
58
|
+
effortLine,
|
|
59
|
+
exitLine,
|
|
60
|
+
itemsLine
|
|
61
|
+
].filter(Boolean).join(" ");
|
|
62
|
+
}
|
|
63
|
+
__name(renderAgentSummary, "renderAgentSummary");
|
|
64
|
+
function renderNotesSection(notes) {
|
|
65
|
+
const notesSanitized = sanitize(notes);
|
|
66
|
+
return notesSanitized.trim() ? `### Agent notes
|
|
67
|
+
${notesSanitized}` : "The agent left no notes.";
|
|
68
|
+
}
|
|
69
|
+
__name(renderNotesSection, "renderNotesSection");
|
|
70
|
+
function formatDate(date) {
|
|
71
|
+
const year = date.getFullYear();
|
|
72
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
73
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
74
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
75
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
76
|
+
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
77
|
+
}
|
|
78
|
+
__name(formatDate, "formatDate");
|
|
79
|
+
function isLearningOutcome(value) {
|
|
80
|
+
return LEARNING_OUTCOMES.some((o) => o === value);
|
|
81
|
+
}
|
|
82
|
+
__name(isLearningOutcome, "isLearningOutcome");
|
|
83
|
+
function renderEntry(input) {
|
|
84
|
+
const encodedKey = encodeKey(input.key);
|
|
85
|
+
const hasRun = "run" in input;
|
|
86
|
+
const timedOut = hasRun && input.run.timedOut;
|
|
87
|
+
const timedOutStr = timedOut ? " timedout=true" : "";
|
|
88
|
+
const sentinel = `<!-- nightralph:entry ticket=${encodedKey} attempt=${input.attempt} outcome=${input.outcome}${timedOutStr} -->`;
|
|
89
|
+
const timestamp = formatDate(input.now);
|
|
90
|
+
const attemptStr = input.outcome === "merge" ? "" : ` attempt ${input.attempt}`;
|
|
91
|
+
let description;
|
|
92
|
+
switch (input.outcome) {
|
|
93
|
+
case "done":
|
|
94
|
+
description = "done";
|
|
95
|
+
break;
|
|
96
|
+
case "exit":
|
|
97
|
+
description = `failed (exit ${input.run.exitCode})`;
|
|
98
|
+
break;
|
|
99
|
+
case "tests":
|
|
100
|
+
description = "failed (tests)";
|
|
101
|
+
break;
|
|
102
|
+
case "install":
|
|
103
|
+
description = "failed (install)";
|
|
104
|
+
break;
|
|
105
|
+
case "no-commits":
|
|
106
|
+
description = "no commits";
|
|
107
|
+
break;
|
|
108
|
+
case "error":
|
|
109
|
+
description = "error";
|
|
110
|
+
break;
|
|
111
|
+
case "merge":
|
|
112
|
+
description = "merge failed";
|
|
113
|
+
break;
|
|
114
|
+
default: {
|
|
115
|
+
const _exhaustive = input;
|
|
116
|
+
description = _exhaustive;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const timedOutPrefix = timedOut ? "timed out, then " : "";
|
|
120
|
+
const heading = `## ${timestamp} -- ${input.label}${attemptStr} -- ${timedOutPrefix}${description}`;
|
|
121
|
+
let body;
|
|
122
|
+
switch (input.outcome) {
|
|
123
|
+
case "done":
|
|
124
|
+
case "exit":
|
|
125
|
+
case "no-commits": {
|
|
126
|
+
const run = input.run;
|
|
127
|
+
const summary = renderAgentSummary(run, input.effort);
|
|
128
|
+
const notesSection = renderNotesSection(run.notes);
|
|
129
|
+
body = summary + "\n\n" + notesSection;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "tests": {
|
|
133
|
+
const logTail = sanitize(input.logTail);
|
|
134
|
+
const fenced = fence(logTail);
|
|
135
|
+
const summary = renderAgentSummary(
|
|
136
|
+
input.run,
|
|
137
|
+
input.effort
|
|
138
|
+
);
|
|
139
|
+
const notesSection = renderNotesSection(input.run.notes);
|
|
140
|
+
const lastNLines = `Last ${LOG_TAIL_LINES} lines of \`${sanitize(input.logName)}\`:`;
|
|
141
|
+
body = summary + `
|
|
142
|
+
|
|
143
|
+
Test gate failed.
|
|
144
|
+
${lastNLines}
|
|
145
|
+
${fenced}
|
|
146
|
+
|
|
147
|
+
` + notesSection;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "install": {
|
|
151
|
+
const logTail = sanitize(input.logTail);
|
|
152
|
+
const fenced = fence(logTail);
|
|
153
|
+
const lastNLines = `Last ${LOG_TAIL_LINES} lines of \`${sanitize(input.logName)}\`:`;
|
|
154
|
+
body = `Install failed.
|
|
155
|
+
${lastNLines}
|
|
156
|
+
` + fenced;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "error": {
|
|
160
|
+
const errorText = sanitize(input.error);
|
|
161
|
+
const fenced = fence(errorText);
|
|
162
|
+
body = fenced;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case "merge": {
|
|
166
|
+
const stderrText = sanitize(input.stderr);
|
|
167
|
+
const fenced = fence(stderrText);
|
|
168
|
+
body = `Merge failed.
|
|
169
|
+
${fenced}`;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
default: {
|
|
173
|
+
const unknown = input;
|
|
174
|
+
throw new Error(`Unknown outcome: ${JSON.stringify(unknown)}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return `${sentinel}
|
|
178
|
+
${heading}
|
|
179
|
+
|
|
180
|
+
${body}
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
`;
|
|
184
|
+
}
|
|
185
|
+
__name(renderEntry, "renderEntry");
|
|
186
|
+
function parseEntries(text) {
|
|
187
|
+
const lines = text.split("\n").map((line) => {
|
|
188
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
189
|
+
});
|
|
190
|
+
const entries = [];
|
|
191
|
+
let currentSentinel = null;
|
|
192
|
+
let currentBody = [];
|
|
193
|
+
const sentinelRegex = /^<!-- nightralph:entry (.*) -->$/;
|
|
194
|
+
for (const line of lines) {
|
|
195
|
+
const match = line.match(sentinelRegex);
|
|
196
|
+
if (match) {
|
|
197
|
+
if (currentSentinel !== null) {
|
|
198
|
+
const entry = parseSentinel(currentSentinel, currentBody);
|
|
199
|
+
if (entry) {
|
|
200
|
+
entries.push(entry);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
currentSentinel = match[1];
|
|
204
|
+
currentBody = [];
|
|
205
|
+
} else if (currentSentinel !== null) {
|
|
206
|
+
currentBody.push(line);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (currentSentinel !== null) {
|
|
210
|
+
const entry = parseSentinel(currentSentinel, currentBody);
|
|
211
|
+
if (entry) {
|
|
212
|
+
entries.push(entry);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return entries;
|
|
216
|
+
}
|
|
217
|
+
__name(parseEntries, "parseEntries");
|
|
218
|
+
function parseSentinel(sentinel, bodyLines) {
|
|
219
|
+
const parts = sentinel.split(/\s+/).filter(Boolean);
|
|
220
|
+
const attrs = {};
|
|
221
|
+
for (const part of parts) {
|
|
222
|
+
const idx = part.indexOf("=");
|
|
223
|
+
if (idx > 0) {
|
|
224
|
+
const key2 = part.slice(0, idx);
|
|
225
|
+
const value = part.slice(idx + 1);
|
|
226
|
+
if (value) {
|
|
227
|
+
attrs[key2] = value;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
let key = attrs.ticket;
|
|
232
|
+
if (!key) return null;
|
|
233
|
+
try {
|
|
234
|
+
key = decodeURIComponent(key);
|
|
235
|
+
} catch (_e) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
if (!key.trim()) return null;
|
|
239
|
+
const attemptStr = attrs.attempt;
|
|
240
|
+
if (!attemptStr) return null;
|
|
241
|
+
if (!/^\d+$/.test(attemptStr)) return null;
|
|
242
|
+
const attempt = Number(attemptStr);
|
|
243
|
+
const outcome = attrs.outcome;
|
|
244
|
+
if (!outcome || !isLearningOutcome(outcome)) return null;
|
|
245
|
+
const timedOut = attrs.timedout === "true";
|
|
246
|
+
let body = bodyLines.join("\n").trim();
|
|
247
|
+
const lines = body.split("\n");
|
|
248
|
+
if (lines.length > 0 && lines[lines.length - 1] === "---") {
|
|
249
|
+
body = lines.slice(0, -1).join("\n").trim();
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
key,
|
|
253
|
+
attempt,
|
|
254
|
+
outcome,
|
|
255
|
+
timedOut,
|
|
256
|
+
body
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
__name(parseSentinel, "parseSentinel");
|
|
260
|
+
function selectHistory(entries, opts) {
|
|
261
|
+
const own = entries.filter((e) => e.key === opts.key).slice(-MAX_OWN_ENTRIES);
|
|
262
|
+
const blockers = opts.blockerKeys.flatMap((k) => {
|
|
263
|
+
const done = entries.filter((e) => {
|
|
264
|
+
return e.key === k && e.outcome === "done";
|
|
265
|
+
});
|
|
266
|
+
const last = done[done.length - 1];
|
|
267
|
+
return last ? [last] : [];
|
|
268
|
+
});
|
|
269
|
+
return { own, blockers };
|
|
270
|
+
}
|
|
271
|
+
__name(selectHistory, "selectHistory");
|
|
272
|
+
function renderHistory(selection) {
|
|
273
|
+
if (selection.own.length === 0 && selection.blockers.length === 0) {
|
|
274
|
+
return "";
|
|
275
|
+
}
|
|
276
|
+
const framing = "These are records of earlier runs. The ticket or spec may have changed since then; where they disagree, follow the ticket and spec. Do not repeat an approach these notes say failed unless you can see why it failed.";
|
|
277
|
+
const parts = [framing, ""];
|
|
278
|
+
if (selection.own.length > 0) {
|
|
279
|
+
parts.push("---- PREVIOUS ATTEMPTS AT THIS TICKET ----");
|
|
280
|
+
parts.push(selection.own.map((e) => capEntry(e)).join("\n\n"));
|
|
281
|
+
parts.push("");
|
|
282
|
+
}
|
|
283
|
+
if (selection.blockers.length > 0) {
|
|
284
|
+
parts.push("---- WHAT THE BLOCKING TICKETS DID ----");
|
|
285
|
+
parts.push(selection.blockers.map((e) => capEntry(e)).join("\n\n"));
|
|
286
|
+
}
|
|
287
|
+
return parts.join("\n");
|
|
288
|
+
}
|
|
289
|
+
__name(renderHistory, "renderHistory");
|
|
290
|
+
function capEntry(entry) {
|
|
291
|
+
if (entry.body.length <= MAX_ENTRY_CHARS) {
|
|
292
|
+
return entry.body;
|
|
293
|
+
}
|
|
294
|
+
return entry.body.slice(0, MAX_ENTRY_CHARS) + "\n" + TRUNCATED_MARKER;
|
|
295
|
+
}
|
|
296
|
+
__name(capEntry, "capEntry");
|
|
297
|
+
function tailLines(text, n) {
|
|
298
|
+
let lines = text.split("\n");
|
|
299
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
300
|
+
lines = lines.slice(0, -1);
|
|
301
|
+
}
|
|
302
|
+
if (lines.length > n) {
|
|
303
|
+
lines = lines.slice(-n);
|
|
304
|
+
}
|
|
305
|
+
return lines.join("\n");
|
|
306
|
+
}
|
|
307
|
+
__name(tailLines, "tailLines");
|
|
308
|
+
function learningsPathFor(progressPath) {
|
|
309
|
+
return join(dirname(progressPath), "learnings.md");
|
|
310
|
+
}
|
|
311
|
+
__name(learningsPathFor, "learningsPathFor");
|
|
312
|
+
function readEntries(path) {
|
|
313
|
+
if (!existsSync(path)) return [];
|
|
314
|
+
return parseEntries(readFileSync(path, "utf8"));
|
|
315
|
+
}
|
|
316
|
+
__name(readEntries, "readEntries");
|
|
317
|
+
function appendEntry(path, text) {
|
|
318
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
319
|
+
appendFileSync(path, text);
|
|
320
|
+
}
|
|
321
|
+
__name(appendEntry, "appendEntry");
|
|
322
|
+
function collectNotes(worktreePath, file = basename(NOTES_PATH)) {
|
|
323
|
+
const dir = join(worktreePath, dirname(NOTES_PATH));
|
|
324
|
+
const path = join(dir, file);
|
|
325
|
+
const text = existsSync(path) ? readFileSync(path, "utf8").trim() : "";
|
|
326
|
+
rmSync(dir, { recursive: true, force: true });
|
|
327
|
+
return text || null;
|
|
328
|
+
}
|
|
329
|
+
__name(collectNotes, "collectNotes");
|
|
330
|
+
function ensureNotesIgnored(worktreePath) {
|
|
331
|
+
const dir = join(worktreePath, dirname(NOTES_PATH));
|
|
332
|
+
mkdirSync(dir, { recursive: true });
|
|
333
|
+
writeFileSync(join(dir, ".gitignore"), "*\n");
|
|
334
|
+
}
|
|
335
|
+
__name(ensureNotesIgnored, "ensureNotesIgnored");
|
|
336
|
+
export {
|
|
337
|
+
LEARNING_OUTCOMES,
|
|
338
|
+
LOG_TAIL_LINES,
|
|
339
|
+
MAX_ENTRY_CHARS,
|
|
340
|
+
MAX_OWN_ENTRIES,
|
|
341
|
+
NOTES_PATH,
|
|
342
|
+
TRUNCATED_MARKER,
|
|
343
|
+
appendEntry,
|
|
344
|
+
collectNotes,
|
|
345
|
+
ensureNotesIgnored,
|
|
346
|
+
formatDate,
|
|
347
|
+
isLearningOutcome,
|
|
348
|
+
learningsPathFor,
|
|
349
|
+
parseEntries,
|
|
350
|
+
readEntries,
|
|
351
|
+
renderEntry,
|
|
352
|
+
renderHistory,
|
|
353
|
+
selectHistory,
|
|
354
|
+
tailLines
|
|
355
|
+
};
|
|
356
|
+
//# sourceMappingURL=learnings.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/learnings.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n appendFileSync,\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from 'node:fs'\nimport { basename, dirname, join } from 'node:path'\n\n// -- Constants and types --\n\nexport const MAX_OWN_ENTRIES = 4\nexport const MAX_ENTRY_CHARS = 4000\nexport const LOG_TAIL_LINES = 40\nexport const NOTES_PATH = '.nightralph/notes.md'\n\nconst SENTINEL_PREFIX = '<!-- nightralph:entry'\nexport const TRUNCATED_MARKER = '[truncated]'\n\nexport const LEARNING_OUTCOMES = [\n 'done',\n 'exit',\n 'tests',\n 'install',\n 'no-commits',\n 'error',\n 'merge',\n] as const\nexport type LearningOutcome = typeof LEARNING_OUTCOMES[number]\n\nexport type LearningEntry = {\n key:string\n attempt:number\n outcome:LearningOutcome\n timedOut:boolean\n // Everything after the sentinel line, trimmed, without\n // the trailing `---` separator\n body:string\n}\n\ntype EntryBase = {\n key:string // ticketKey(feature, num)\n label:string // ticketLabel(ticket), e.g. \"my-feature/04\"\n attempt:number // 0 for merge entries\n now:Date\n}\n\n// What one agent process did: its exit code, the items it reported,\n// whether it was killed at the timeout, and the notes it left.\nexport type AgentRunRecord = {\n exitCode:number\n completedItems:string[]\n timedOut:boolean\n notes:string|null\n}\n\ntype AgentFields = { run:AgentRunRecord; effort?:string }\n\ntype LogTail = { logName:string; logTail:string }\n\nexport type EntryInput = EntryBase&(\n | (AgentFields&{ outcome:'done'|'exit'|'no-commits' })\n | (AgentFields&LogTail&{ outcome:'tests' })\n | (LogTail&{ outcome:'install' })\n | { outcome:'error'; error:string }\n | { outcome:'merge'; stderr:string }\n)\n\nexport type HistorySelection = {\n own:LearningEntry[]\n blockers:LearningEntry[]\n}\n\n// -- Helpers --\n\nfunction encodeKey (key:string):string {\n return key\n .replace(/%/g, '%25')\n .replace(/\\s/g, c => encodeURIComponent(c))\n}\n\nfunction fence (content:string):string {\n const longest = Math.max(\n 0,\n ...[...content.matchAll(/`+/g)].map(m => m[0].length)\n )\n const ticks = '`'.repeat(Math.max(3, longest + 1))\n return `${ticks}\\n${content}\\n${ticks}`\n}\n\nfunction sanitize (text:string|null):string {\n if (!text) return ''\n return text.replace(\n new RegExp(SENTINEL_PREFIX, 'g'),\n '<!-- nightralph-entry'\n )\n}\n\nfunction renderAgentSummary (\n run:AgentRunRecord,\n effort?:string\n):string {\n const effortLine = effort ?\n `Effort: ${effort}.` :\n ''\n\n const exitLine = `Agent exit: ${run.exitCode}.`\n\n const itemsText = run.completedItems.length > 0 ?\n run.completedItems.map(item =>\n `\"${sanitize(item)}\"`\n ).join(', ') + '.' :\n 'none.'\n\n const itemsLine = `Checked items: ${itemsText}`\n\n return [\n effortLine,\n exitLine,\n itemsLine,\n ].filter(Boolean).join(' ')\n}\n\nfunction renderNotesSection (notes:string|null):string {\n const notesSanitized = sanitize(notes)\n return notesSanitized.trim() ?\n `### Agent notes\\n${notesSanitized}` :\n 'The agent left no notes.'\n}\n\nexport function formatDate (date:Date):string {\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n const hours = String(date.getHours()).padStart(2, '0')\n const minutes = String(date.getMinutes()).padStart(2, '0')\n return `${year}-${month}-${day} ${hours}:${minutes}`\n}\n\nexport function isLearningOutcome (\n value:string\n):value is LearningOutcome {\n return LEARNING_OUTCOMES.some(o => o === value)\n}\n\nexport function renderEntry (input:EntryInput):string {\n const encodedKey = encodeKey(input.key)\n\n const hasRun = 'run' in input\n const timedOut = hasRun && input.run.timedOut\n\n const timedOutStr = timedOut ?\n ' timedout=true' :\n ''\n\n const sentinel =\n `<!-- nightralph:entry ticket=${encodedKey} ` +\n `attempt=${input.attempt} outcome=${input.outcome}` +\n `${timedOutStr} -->`\n\n const timestamp = formatDate(input.now)\n const attemptStr = input.outcome === 'merge' ?\n '' :\n ` attempt ${input.attempt}`\n\n let description:string\n\n switch (input.outcome) {\n case 'done':\n description = 'done'\n break\n case 'exit':\n description = `failed (exit ${input.run.exitCode})`\n break\n case 'tests':\n description = 'failed (tests)'\n break\n case 'install':\n description = 'failed (install)'\n break\n case 'no-commits':\n description = 'no commits'\n break\n case 'error':\n description = 'error'\n break\n case 'merge':\n description = 'merge failed'\n break\n default: {\n const _exhaustive:never = input\n description = _exhaustive\n }\n }\n\n const timedOutPrefix = timedOut ?\n 'timed out, then ' :\n ''\n\n const heading =\n `## ${timestamp} -- ${input.label}${attemptStr} -- ` +\n `${timedOutPrefix}${description}`\n\n let body:string\n\n switch (input.outcome) {\n case 'done':\n case 'exit':\n case 'no-commits': {\n const run = input.run\n const summary = renderAgentSummary(run, input.effort)\n const notesSection = renderNotesSection(run.notes)\n body = summary + '\\n\\n' + notesSection\n break\n }\n case 'tests': {\n const logTail = sanitize(input.logTail)\n const fenced = fence(logTail)\n const summary = renderAgentSummary(\n input.run,\n input.effort\n )\n const notesSection = renderNotesSection(input.run.notes)\n\n const lastNLines =\n `Last ${LOG_TAIL_LINES} lines of ` +\n `\\`${sanitize(input.logName)}\\`:`\n\n body =\n summary + '\\n\\n' +\n 'Test gate failed.\\n' +\n `${lastNLines}\\n` +\n `${fenced}\\n\\n` +\n notesSection\n break\n }\n case 'install': {\n const logTail = sanitize(input.logTail)\n const fenced = fence(logTail)\n\n const lastNLines =\n `Last ${LOG_TAIL_LINES} lines of ` +\n `\\`${sanitize(input.logName)}\\`:`\n\n body =\n 'Install failed.\\n' +\n `${lastNLines}\\n` +\n fenced\n break\n }\n case 'error': {\n const errorText = sanitize(input.error)\n const fenced = fence(errorText)\n body = fenced\n break\n }\n case 'merge': {\n const stderrText = sanitize(input.stderr)\n const fenced = fence(stderrText)\n body = `Merge failed.\\n${fenced}`\n break\n }\n default: {\n const unknown:never = input\n throw new Error(`Unknown outcome: ${JSON.stringify(unknown)}`)\n }\n }\n\n return `${sentinel}\\n${heading}\\n\\n${body}\\n\\n---\\n`\n}\n\nexport function parseEntries (text:string):LearningEntry[] {\n const lines = text.split('\\n').map(line => {\n // Strip trailing \\r for CRLF support\n return line.endsWith('\\r') ? line.slice(0, -1) : line\n })\n const entries:LearningEntry[] = []\n\n let currentSentinel:string|null = null\n let currentBody:string[] = []\n\n const sentinelRegex = /^<!-- nightralph:entry (.*) -->$/\n\n for (const line of lines) {\n const match = line.match(sentinelRegex)\n\n if (match) {\n // Process previous entry if exists\n if (currentSentinel !== null) {\n const entry = parseSentinel(currentSentinel, currentBody)\n if (entry) {\n entries.push(entry)\n }\n }\n\n currentSentinel = match[1]\n currentBody = []\n } else if (currentSentinel !== null) {\n currentBody.push(line)\n }\n }\n\n // Process last entry\n if (currentSentinel !== null) {\n const entry = parseSentinel(currentSentinel, currentBody)\n if (entry) {\n entries.push(entry)\n }\n }\n\n return entries\n}\n\nfunction parseSentinel (\n sentinel:string,\n bodyLines:string[]\n):LearningEntry|null {\n const parts = sentinel.split(/\\s+/).filter(Boolean)\n const attrs:Record<string, string> = {}\n\n for (const part of parts) {\n const idx = part.indexOf('=')\n if (idx > 0) {\n const key = part.slice(0, idx)\n const value = part.slice(idx + 1)\n if (value) {\n attrs[key] = value\n }\n }\n }\n\n // Validate ticket\n let key = attrs.ticket\n if (!key) return null\n\n try {\n key = decodeURIComponent(key)\n } catch (_e) {\n return null\n }\n\n if (!key.trim()) return null\n\n // Validate attempt\n const attemptStr = attrs.attempt\n if (!attemptStr) return null\n\n if (!/^\\d+$/.test(attemptStr)) return null\n const attempt = Number(attemptStr)\n\n // Validate outcome\n const outcome = attrs.outcome\n if (!outcome || !isLearningOutcome(outcome)) return null\n\n // Parse timedOut\n const timedOut = attrs.timedout === 'true'\n\n // Parse body\n let body = bodyLines.join('\\n').trim()\n\n // Remove trailing --- line (full line only)\n const lines = body.split('\\n')\n if (lines.length > 0 && lines[lines.length - 1] === '---') {\n body = lines.slice(0, -1).join('\\n').trim()\n }\n\n return {\n key,\n attempt,\n outcome,\n timedOut,\n body,\n }\n}\n\nexport function selectHistory (\n entries:LearningEntry[],\n opts:{ key:string; blockerKeys:string[] }\n):HistorySelection {\n const own = entries\n .filter(e => e.key === opts.key)\n .slice(-MAX_OWN_ENTRIES)\n\n const blockers = opts.blockerKeys.flatMap(k => {\n const done = entries.filter(e => {\n return e.key === k && e.outcome === 'done'\n })\n const last = done[done.length - 1]\n return last ? [last] : []\n })\n\n return { own, blockers }\n}\n\nexport function renderHistory (selection:HistorySelection):string {\n if (selection.own.length === 0 && selection.blockers.length === 0) {\n return ''\n }\n\n const framing =\n 'These are records of earlier runs. The ticket or spec may ' +\n 'have changed since then; where they disagree, follow the ' +\n 'ticket and spec. Do not repeat an approach these notes say ' +\n 'failed unless you can see why it failed.'\n\n const parts = [framing, '']\n\n if (selection.own.length > 0) {\n parts.push('---- PREVIOUS ATTEMPTS AT THIS TICKET ----')\n parts.push(selection.own.map(e => capEntry(e)).join('\\n\\n'))\n parts.push('')\n }\n\n if (selection.blockers.length > 0) {\n parts.push('---- WHAT THE BLOCKING TICKETS DID ----')\n parts.push(selection.blockers.map(e => capEntry(e)).join('\\n\\n'))\n }\n\n return parts.join('\\n')\n}\n\nfunction capEntry (entry:LearningEntry):string {\n if (entry.body.length <= MAX_ENTRY_CHARS) {\n return entry.body\n }\n\n return entry.body.slice(0, MAX_ENTRY_CHARS) + '\\n' + TRUNCATED_MARKER\n}\n\nexport function tailLines (text:string, n:number):string {\n let lines = text.split('\\n')\n\n // Remove empty string at end if text ended with newline\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines = lines.slice(0, -1)\n }\n\n // Keep last n\n if (lines.length > n) {\n lines = lines.slice(-n)\n }\n\n return lines.join('\\n')\n}\n\nexport function learningsPathFor (progressPath:string):string {\n return join(dirname(progressPath), 'learnings.md')\n}\n\n// -- IO functions --\n\n// A missing log means no history yet. Any other read error (e.g.\n// the path is a directory) is thrown for the caller to report.\nexport function readEntries (path:string):LearningEntry[] {\n if (!existsSync(path)) return []\n return parseEntries(readFileSync(path, 'utf8'))\n}\n\n// Synchronous on purpose: tickets in a wave run concurrently in one\n// Node thread, so sync appends can never interleave.\nexport function appendEntry (path:string, text:string):void {\n mkdirSync(dirname(path), { recursive:true })\n appendFileSync(path, text)\n}\n\n// Reads one file from the worktree's notes directory (the notes\n// by default), then removes the whole directory (also when the\n// file is missing), so nothing in it can reach a commit. Blank\n// text counts as none.\nexport function collectNotes (\n worktreePath:string,\n file:string = basename(NOTES_PATH)\n):string|null {\n const dir = join(worktreePath, dirname(NOTES_PATH))\n const path = join(dir, file)\n const text = existsSync(path) ?\n readFileSync(path, 'utf8').trim() :\n ''\n rmSync(dir, { recursive:true, force:true })\n return text || null\n}\n\n// Makes the notes directory ignore itself, so an agent's\n// `git add -A` cannot stage its notes. Deliberately not\n// info/exclude: that file can be a symlink shared by every repo on\n// the machine (e.g. via init.templateDir).\nexport function ensureNotesIgnored (worktreePath:string):void {\n const dir = join(worktreePath, dirname(NOTES_PATH))\n mkdirSync(dir, { recursive:true })\n writeFileSync(join(dir, '.gitignore'), '*\\n')\n}\n"],
|
|
5
|
+
"mappings": ";;AAAA;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP,SAAS,UAAU,SAAS,YAAY;AAIjC,MAAM,kBAAkB;AACxB,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AAE1B,MAAM,kBAAkB;AACjB,MAAM,mBAAmB;AAEzB,MAAM,oBAAoB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAgDA,SAAS,UAAW,KAAmB;AACnC,SAAO,IACF,QAAQ,MAAM,KAAK,EACnB,QAAQ,OAAO,OAAK,mBAAmB,CAAC,CAAC;AAClD;AAJS;AAMT,SAAS,MAAO,SAAuB;AACnC,QAAM,UAAU,KAAK;AAAA,IACjB;AAAA,IACA,GAAG,CAAC,GAAG,QAAQ,SAAS,KAAK,CAAC,EAAE,IAAI,OAAK,EAAE,CAAC,EAAE,MAAM;AAAA,EACxD;AACA,QAAM,QAAQ,IAAI,OAAO,KAAK,IAAI,GAAG,UAAU,CAAC,CAAC;AACjD,SAAO,GAAG,KAAK;AAAA,EAAK,OAAO;AAAA,EAAK,KAAK;AACzC;AAPS;AAST,SAAS,SAAU,MAAyB;AACxC,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KAAK;AAAA,IACR,IAAI,OAAO,iBAAiB,GAAG;AAAA,IAC/B;AAAA,EACJ;AACJ;AANS;AAQT,SAAS,mBACL,KACA,QACK;AACL,QAAM,aAAa,SACf,WAAW,MAAM,MACjB;AAEJ,QAAM,WAAW,eAAe,IAAI,QAAQ;AAE5C,QAAM,YAAY,IAAI,eAAe,SAAS,IAC1C,IAAI,eAAe;AAAA,IAAI,UACnB,IAAI,SAAS,IAAI,CAAC;AAAA,EACtB,EAAE,KAAK,IAAI,IAAI,MACf;AAEJ,QAAM,YAAY,kBAAkB,SAAS;AAE7C,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,EACJ,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAC9B;AAvBS;AAyBT,SAAS,mBAAoB,OAA0B;AACnD,QAAM,iBAAiB,SAAS,KAAK;AACrC,SAAO,eAAe,KAAK,IACvB;AAAA,EAAoB,cAAc,KAClC;AACR;AALS;AAOF,SAAS,WAAY,MAAkB;AAC1C,QAAM,OAAO,KAAK,YAAY;AAC9B,QAAM,QAAQ,OAAO,KAAK,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AACzD,QAAM,MAAM,OAAO,KAAK,QAAQ,CAAC,EAAE,SAAS,GAAG,GAAG;AAClD,QAAM,QAAQ,OAAO,KAAK,SAAS,CAAC,EAAE,SAAS,GAAG,GAAG;AACrD,QAAM,UAAU,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACzD,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,OAAO;AACtD;AAPgB;AAST,SAAS,kBACZ,OACuB;AACvB,SAAO,kBAAkB,KAAK,OAAK,MAAM,KAAK;AAClD;AAJgB;AAMT,SAAS,YAAa,OAAyB;AAClD,QAAM,aAAa,UAAU,MAAM,GAAG;AAEtC,QAAM,SAAS,SAAS;AACxB,QAAM,WAAW,UAAU,MAAM,IAAI;AAErC,QAAM,cAAc,WAChB,mBACA;AAEJ,QAAM,WACF,gCAAgC,UAAU,YAC/B,MAAM,OAAO,YAAY,MAAM,OAAO,GAC9C,WAAW;AAElB,QAAM,YAAY,WAAW,MAAM,GAAG;AACtC,QAAM,aAAa,MAAM,YAAY,UACjC,KACA,YAAY,MAAM,OAAO;AAE7B,MAAI;AAEJ,UAAQ,MAAM,SAAS;AAAA,IACnB,KAAK;AACD,oBAAc;AACd;AAAA,IACJ,KAAK;AACD,oBAAc,gBAAgB,MAAM,IAAI,QAAQ;AAChD;AAAA,IACJ,KAAK;AACD,oBAAc;AACd;AAAA,IACJ,KAAK;AACD,oBAAc;AACd;AAAA,IACJ,KAAK;AACD,oBAAc;AACd;AAAA,IACJ,KAAK;AACD,oBAAc;AACd;AAAA,IACJ,KAAK;AACD,oBAAc;AACd;AAAA,IACJ,SAAS;AACL,YAAM,cAAoB;AAC1B,oBAAc;AAAA,IAClB;AAAA,EACJ;AAEA,QAAM,iBAAiB,WACnB,qBACA;AAEJ,QAAM,UACF,MAAM,SAAS,OAAO,MAAM,KAAK,GAAG,UAAU,OAC3C,cAAc,GAAG,WAAW;AAEnC,MAAI;AAEJ,UAAQ,MAAM,SAAS;AAAA,IACnB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,cAAc;AACf,YAAM,MAAM,MAAM;AAClB,YAAM,UAAU,mBAAmB,KAAK,MAAM,MAAM;AACpD,YAAM,eAAe,mBAAmB,IAAI,KAAK;AACjD,aAAO,UAAU,SAAS;AAC1B;AAAA,IACJ;AAAA,IACA,KAAK,SAAS;AACV,YAAM,UAAU,SAAS,MAAM,OAAO;AACtC,YAAM,SAAS,MAAM,OAAO;AAC5B,YAAM,UAAU;AAAA,QACZ,MAAM;AAAA,QACN,MAAM;AAAA,MACV;AACA,YAAM,eAAe,mBAAmB,MAAM,IAAI,KAAK;AAEvD,YAAM,aACF,QAAQ,cAAc,eACjB,SAAS,MAAM,OAAO,CAAC;AAEhC,aACI,UAAU;AAAA;AAAA;AAAA,EAEP,UAAU;AAAA,EACV,MAAM;AAAA;AAAA,IACT;AACJ;AAAA,IACJ;AAAA,IACA,KAAK,WAAW;AACZ,YAAM,UAAU,SAAS,MAAM,OAAO;AACtC,YAAM,SAAS,MAAM,OAAO;AAE5B,YAAM,aACF,QAAQ,cAAc,eACjB,SAAS,MAAM,OAAO,CAAC;AAEhC,aACI;AAAA,EACG,UAAU;AAAA,IACb;AACJ;AAAA,IACJ;AAAA,IACA,KAAK,SAAS;AACV,YAAM,YAAY,SAAS,MAAM,KAAK;AACtC,YAAM,SAAS,MAAM,SAAS;AAC9B,aAAO;AACP;AAAA,IACJ;AAAA,IACA,KAAK,SAAS;AACV,YAAM,aAAa,SAAS,MAAM,MAAM;AACxC,YAAM,SAAS,MAAM,UAAU;AAC/B,aAAO;AAAA,EAAkB,MAAM;AAC/B;AAAA,IACJ;AAAA,IACA,SAAS;AACL,YAAM,UAAgB;AACtB,YAAM,IAAI,MAAM,oBAAoB,KAAK,UAAU,OAAO,CAAC,EAAE;AAAA,IACjE;AAAA,EACJ;AAEA,SAAO,GAAG,QAAQ;AAAA,EAAK,OAAO;AAAA;AAAA,EAAO,IAAI;AAAA;AAAA;AAAA;AAC7C;AA5HgB;AA8HT,SAAS,aAAc,MAA6B;AACvD,QAAM,QAAQ,KAAK,MAAM,IAAI,EAAE,IAAI,UAAQ;AAEvC,WAAO,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AAAA,EACrD,CAAC;AACD,QAAM,UAA0B,CAAC;AAEjC,MAAI,kBAA8B;AAClC,MAAI,cAAuB,CAAC;AAE5B,QAAM,gBAAgB;AAEtB,aAAW,QAAQ,OAAO;AACtB,UAAM,QAAQ,KAAK,MAAM,aAAa;AAEtC,QAAI,OAAO;AAEP,UAAI,oBAAoB,MAAM;AAC1B,cAAM,QAAQ,cAAc,iBAAiB,WAAW;AACxD,YAAI,OAAO;AACP,kBAAQ,KAAK,KAAK;AAAA,QACtB;AAAA,MACJ;AAEA,wBAAkB,MAAM,CAAC;AACzB,oBAAc,CAAC;AAAA,IACnB,WAAW,oBAAoB,MAAM;AACjC,kBAAY,KAAK,IAAI;AAAA,IACzB;AAAA,EACJ;AAGA,MAAI,oBAAoB,MAAM;AAC1B,UAAM,QAAQ,cAAc,iBAAiB,WAAW;AACxD,QAAI,OAAO;AACP,cAAQ,KAAK,KAAK;AAAA,IACtB;AAAA,EACJ;AAEA,SAAO;AACX;AAxCgB;AA0ChB,SAAS,cACL,UACA,WACiB;AACjB,QAAM,QAAQ,SAAS,MAAM,KAAK,EAAE,OAAO,OAAO;AAClD,QAAM,QAA+B,CAAC;AAEtC,aAAW,QAAQ,OAAO;AACtB,UAAM,MAAM,KAAK,QAAQ,GAAG;AAC5B,QAAI,MAAM,GAAG;AACT,YAAMA,OAAM,KAAK,MAAM,GAAG,GAAG;AAC7B,YAAM,QAAQ,KAAK,MAAM,MAAM,CAAC;AAChC,UAAI,OAAO;AACP,cAAMA,IAAG,IAAI;AAAA,MACjB;AAAA,IACJ;AAAA,EACJ;AAGA,MAAI,MAAM,MAAM;AAChB,MAAI,CAAC,IAAK,QAAO;AAEjB,MAAI;AACA,UAAM,mBAAmB,GAAG;AAAA,EAChC,SAAS,IAAI;AACT,WAAO;AAAA,EACX;AAEA,MAAI,CAAC,IAAI,KAAK,EAAG,QAAO;AAGxB,QAAM,aAAa,MAAM;AACzB,MAAI,CAAC,WAAY,QAAO;AAExB,MAAI,CAAC,QAAQ,KAAK,UAAU,EAAG,QAAO;AACtC,QAAM,UAAU,OAAO,UAAU;AAGjC,QAAM,UAAU,MAAM;AACtB,MAAI,CAAC,WAAW,CAAC,kBAAkB,OAAO,EAAG,QAAO;AAGpD,QAAM,WAAW,MAAM,aAAa;AAGpC,MAAI,OAAO,UAAU,KAAK,IAAI,EAAE,KAAK;AAGrC,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,MAAI,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,CAAC,MAAM,OAAO;AACvD,WAAO,MAAM,MAAM,GAAG,EAAE,EAAE,KAAK,IAAI,EAAE,KAAK;AAAA,EAC9C;AAEA,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AACJ;AA5DS;AA8DF,SAAS,cACZ,SACA,MACe;AACf,QAAM,MAAM,QACP,OAAO,OAAK,EAAE,QAAQ,KAAK,GAAG,EAC9B,MAAM,CAAC,eAAe;AAE3B,QAAM,WAAW,KAAK,YAAY,QAAQ,OAAK;AAC3C,UAAM,OAAO,QAAQ,OAAO,OAAK;AAC7B,aAAO,EAAE,QAAQ,KAAK,EAAE,YAAY;AAAA,IACxC,CAAC;AACD,UAAM,OAAO,KAAK,KAAK,SAAS,CAAC;AACjC,WAAO,OAAO,CAAC,IAAI,IAAI,CAAC;AAAA,EAC5B,CAAC;AAED,SAAO,EAAE,KAAK,SAAS;AAC3B;AAjBgB;AAmBT,SAAS,cAAe,WAAmC;AAC9D,MAAI,UAAU,IAAI,WAAW,KAAK,UAAU,SAAS,WAAW,GAAG;AAC/D,WAAO;AAAA,EACX;AAEA,QAAM,UACF;AAKJ,QAAM,QAAQ,CAAC,SAAS,EAAE;AAE1B,MAAI,UAAU,IAAI,SAAS,GAAG;AAC1B,UAAM,KAAK,4CAA4C;AACvD,UAAM,KAAK,UAAU,IAAI,IAAI,OAAK,SAAS,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC;AAC3D,UAAM,KAAK,EAAE;AAAA,EACjB;AAEA,MAAI,UAAU,SAAS,SAAS,GAAG;AAC/B,UAAM,KAAK,yCAAyC;AACpD,UAAM,KAAK,UAAU,SAAS,IAAI,OAAK,SAAS,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC;AAAA,EACpE;AAEA,SAAO,MAAM,KAAK,IAAI;AAC1B;AAzBgB;AA2BhB,SAAS,SAAU,OAA4B;AAC3C,MAAI,MAAM,KAAK,UAAU,iBAAiB;AACtC,WAAO,MAAM;AAAA,EACjB;AAEA,SAAO,MAAM,KAAK,MAAM,GAAG,eAAe,IAAI,OAAO;AACzD;AANS;AAQF,SAAS,UAAW,MAAa,GAAiB;AACrD,MAAI,QAAQ,KAAK,MAAM,IAAI;AAG3B,MAAI,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,CAAC,MAAM,IAAI;AACpD,YAAQ,MAAM,MAAM,GAAG,EAAE;AAAA,EAC7B;AAGA,MAAI,MAAM,SAAS,GAAG;AAClB,YAAQ,MAAM,MAAM,CAAC,CAAC;AAAA,EAC1B;AAEA,SAAO,MAAM,KAAK,IAAI;AAC1B;AAdgB;AAgBT,SAAS,iBAAkB,cAA4B;AAC1D,SAAO,KAAK,QAAQ,YAAY,GAAG,cAAc;AACrD;AAFgB;AAQT,SAAS,YAAa,MAA6B;AACtD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO,CAAC;AAC/B,SAAO,aAAa,aAAa,MAAM,MAAM,CAAC;AAClD;AAHgB;AAOT,SAAS,YAAa,MAAa,MAAkB;AACxD,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAU,KAAK,CAAC;AAC3C,iBAAe,MAAM,IAAI;AAC7B;AAHgB;AAST,SAAS,aACZ,cACA,OAAc,SAAS,UAAU,GACvB;AACV,QAAM,MAAM,KAAK,cAAc,QAAQ,UAAU,CAAC;AAClD,QAAM,OAAO,KAAK,KAAK,IAAI;AAC3B,QAAM,OAAO,WAAW,IAAI,IACxB,aAAa,MAAM,MAAM,EAAE,KAAK,IAChC;AACJ,SAAO,KAAK,EAAE,WAAU,MAAM,OAAM,KAAK,CAAC;AAC1C,SAAO,QAAQ;AACnB;AAXgB;AAiBT,SAAS,mBAAoB,cAA0B;AAC1D,QAAM,MAAM,KAAK,cAAc,QAAQ,UAAU,CAAC;AAClD,YAAU,KAAK,EAAE,WAAU,KAAK,CAAC;AACjC,gBAAc,KAAK,KAAK,YAAY,GAAG,KAAK;AAChD;AAJgB;",
|
|
6
|
+
"names": ["key"]
|
|
7
|
+
}
|