make-it-done 0.1.0
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 +263 -0
- package/agents/.gitkeep +0 -0
- package/agents/mid-debugger.md +88 -0
- package/agents/mid-executor.md +69 -0
- package/agents/mid-planner.md +97 -0
- package/agents/mid-researcher.md +101 -0
- package/agents/mid-verifier.md +92 -0
- package/bin/install.js +122 -0
- package/commands/mid/.gitkeep +0 -0
- package/commands/mid/debug.md +19 -0
- package/commands/mid/do.md +24 -0
- package/commands/mid/help.md +77 -0
- package/commands/mid/init.md +18 -0
- package/commands/mid/next.md +18 -0
- package/commands/mid/plan.md +21 -0
- package/commands/mid/quick.md +24 -0
- package/commands/mid/report.md +16 -0
- package/commands/mid/status.md +16 -0
- package/commands/mid/verify.md +19 -0
- package/makeitdone/bin/mid-tools.cjs +2048 -0
- package/makeitdone/steps/agent-contracts.md +184 -0
- package/makeitdone/steps/anti-patterns.md +291 -0
- package/makeitdone/steps/context-budget.md +157 -0
- package/makeitdone/steps/init-gate.md +42 -0
- package/makeitdone/steps/model-route.md +70 -0
- package/makeitdone/steps/state-read.md +56 -0
- package/makeitdone/templates/plan.md +94 -0
- package/makeitdone/templates/project.md +62 -0
- package/makeitdone/templates/requirements.md +97 -0
- package/makeitdone/templates/roadmap.md +111 -0
- package/makeitdone/templates/state.md +28 -0
- package/makeitdone/templates/summary.md +58 -0
- package/makeitdone/workflows/debug.md +135 -0
- package/makeitdone/workflows/execute.md +218 -0
- package/makeitdone/workflows/init.md +113 -0
- package/makeitdone/workflows/next.md +114 -0
- package/makeitdone/workflows/plan.md +231 -0
- package/makeitdone/workflows/quick.md +151 -0
- package/makeitdone/workflows/report.md +138 -0
- package/makeitdone/workflows/status.md +135 -0
- package/makeitdone/workflows/verify.md +177 -0
- package/package.json +50 -0
|
@@ -0,0 +1,2048 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// src/commands/toon.ts
|
|
5
|
+
var import_fs = require("fs");
|
|
6
|
+
|
|
7
|
+
// node_modules/@toon-format/toon/dist/index.mjs
|
|
8
|
+
var LIST_ITEM_MARKER = "-";
|
|
9
|
+
var LIST_ITEM_PREFIX = "- ";
|
|
10
|
+
var COMMA = ",";
|
|
11
|
+
var COLON = ":";
|
|
12
|
+
var SPACE = " ";
|
|
13
|
+
var PIPE = "|";
|
|
14
|
+
var DOT = ".";
|
|
15
|
+
var OPEN_BRACKET = "[";
|
|
16
|
+
var CLOSE_BRACKET = "]";
|
|
17
|
+
var OPEN_BRACE = "{";
|
|
18
|
+
var CLOSE_BRACE = "}";
|
|
19
|
+
var NULL_LITERAL = "null";
|
|
20
|
+
var TRUE_LITERAL = "true";
|
|
21
|
+
var FALSE_LITERAL = "false";
|
|
22
|
+
var BACKSLASH = "\\";
|
|
23
|
+
var DOUBLE_QUOTE = '"';
|
|
24
|
+
var NEWLINE = "\n";
|
|
25
|
+
var CARRIAGE_RETURN = "\r";
|
|
26
|
+
var TAB = " ";
|
|
27
|
+
var DELIMITERS = {
|
|
28
|
+
comma: COMMA,
|
|
29
|
+
tab: TAB,
|
|
30
|
+
pipe: PIPE
|
|
31
|
+
};
|
|
32
|
+
var DEFAULT_DELIMITER = DELIMITERS.comma;
|
|
33
|
+
function escapeString(value) {
|
|
34
|
+
return value.replace(/\\/g, `${BACKSLASH}${BACKSLASH}`).replace(/"/g, `${BACKSLASH}${DOUBLE_QUOTE}`).replace(/\n/g, `${BACKSLASH}n`).replace(/\r/g, `${BACKSLASH}r`).replace(/\t/g, `${BACKSLASH}t`);
|
|
35
|
+
}
|
|
36
|
+
function unescapeString(value) {
|
|
37
|
+
let unescaped = "";
|
|
38
|
+
let i = 0;
|
|
39
|
+
while (i < value.length) {
|
|
40
|
+
if (value[i] === BACKSLASH) {
|
|
41
|
+
if (i + 1 >= value.length)
|
|
42
|
+
throw new SyntaxError("Invalid escape sequence: backslash at end of string");
|
|
43
|
+
const next = value[i + 1];
|
|
44
|
+
if (next === "n") {
|
|
45
|
+
unescaped += NEWLINE;
|
|
46
|
+
i += 2;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (next === "t") {
|
|
50
|
+
unescaped += TAB;
|
|
51
|
+
i += 2;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (next === "r") {
|
|
55
|
+
unescaped += CARRIAGE_RETURN;
|
|
56
|
+
i += 2;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (next === BACKSLASH) {
|
|
60
|
+
unescaped += BACKSLASH;
|
|
61
|
+
i += 2;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (next === DOUBLE_QUOTE) {
|
|
65
|
+
unescaped += DOUBLE_QUOTE;
|
|
66
|
+
i += 2;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
throw new SyntaxError(`Invalid escape sequence: \\${next}`);
|
|
70
|
+
}
|
|
71
|
+
unescaped += value[i];
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
return unescaped;
|
|
75
|
+
}
|
|
76
|
+
function findClosingQuote(content, start) {
|
|
77
|
+
let i = start + 1;
|
|
78
|
+
while (i < content.length) {
|
|
79
|
+
if (content[i] === BACKSLASH && i + 1 < content.length) {
|
|
80
|
+
i += 2;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (content[i] === DOUBLE_QUOTE)
|
|
84
|
+
return i;
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
return -1;
|
|
88
|
+
}
|
|
89
|
+
function findUnquotedChar(content, char, start = 0) {
|
|
90
|
+
let inQuotes = false;
|
|
91
|
+
let i = start;
|
|
92
|
+
while (i < content.length) {
|
|
93
|
+
if (content[i] === BACKSLASH && i + 1 < content.length && inQuotes) {
|
|
94
|
+
i += 2;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (content[i] === DOUBLE_QUOTE) {
|
|
98
|
+
inQuotes = !inQuotes;
|
|
99
|
+
i++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (content[i] === char && !inQuotes)
|
|
103
|
+
return i;
|
|
104
|
+
i++;
|
|
105
|
+
}
|
|
106
|
+
return -1;
|
|
107
|
+
}
|
|
108
|
+
function isBooleanOrNullLiteral(token) {
|
|
109
|
+
return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
|
|
110
|
+
}
|
|
111
|
+
function isNumericLiteral(token) {
|
|
112
|
+
if (!token)
|
|
113
|
+
return false;
|
|
114
|
+
if (token.length > 1 && token[0] === "0" && token[1] !== ".")
|
|
115
|
+
return false;
|
|
116
|
+
const numericValue = Number(token);
|
|
117
|
+
return !Number.isNaN(numericValue) && Number.isFinite(numericValue);
|
|
118
|
+
}
|
|
119
|
+
function parseArrayHeaderLine(content, defaultDelimiter) {
|
|
120
|
+
const trimmedToken = content.trimStart();
|
|
121
|
+
let bracketStart = -1;
|
|
122
|
+
if (trimmedToken.startsWith(DOUBLE_QUOTE)) {
|
|
123
|
+
const closingQuoteIndex = findClosingQuote(trimmedToken, 0);
|
|
124
|
+
if (closingQuoteIndex === -1)
|
|
125
|
+
return;
|
|
126
|
+
if (!trimmedToken.slice(closingQuoteIndex + 1).startsWith(OPEN_BRACKET))
|
|
127
|
+
return;
|
|
128
|
+
const keyEndIndex = content.length - trimmedToken.length + closingQuoteIndex + 1;
|
|
129
|
+
bracketStart = content.indexOf(OPEN_BRACKET, keyEndIndex);
|
|
130
|
+
} else
|
|
131
|
+
bracketStart = content.indexOf(OPEN_BRACKET);
|
|
132
|
+
if (bracketStart === -1)
|
|
133
|
+
return;
|
|
134
|
+
const bracketEnd = content.indexOf(CLOSE_BRACKET, bracketStart);
|
|
135
|
+
if (bracketEnd === -1)
|
|
136
|
+
return;
|
|
137
|
+
let colonIndex = bracketEnd + 1;
|
|
138
|
+
let braceEnd = colonIndex;
|
|
139
|
+
const braceStart = content.indexOf(OPEN_BRACE, bracketEnd);
|
|
140
|
+
if (braceStart !== -1 && braceStart < content.indexOf(COLON, bracketEnd)) {
|
|
141
|
+
const foundBraceEnd = content.indexOf(CLOSE_BRACE, braceStart);
|
|
142
|
+
if (foundBraceEnd !== -1)
|
|
143
|
+
braceEnd = foundBraceEnd + 1;
|
|
144
|
+
}
|
|
145
|
+
colonIndex = content.indexOf(COLON, Math.max(bracketEnd, braceEnd));
|
|
146
|
+
if (colonIndex === -1)
|
|
147
|
+
return;
|
|
148
|
+
let key;
|
|
149
|
+
if (bracketStart > 0) {
|
|
150
|
+
const rawKey = content.slice(0, bracketStart).trim();
|
|
151
|
+
key = rawKey.startsWith(DOUBLE_QUOTE) ? parseStringLiteral(rawKey) : rawKey;
|
|
152
|
+
}
|
|
153
|
+
const afterColon = content.slice(colonIndex + 1).trim();
|
|
154
|
+
const bracketContent = content.slice(bracketStart + 1, bracketEnd);
|
|
155
|
+
let parsedBracket;
|
|
156
|
+
try {
|
|
157
|
+
parsedBracket = parseBracketSegment(bracketContent, defaultDelimiter);
|
|
158
|
+
} catch {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const { length, delimiter } = parsedBracket;
|
|
162
|
+
let fields;
|
|
163
|
+
if (braceStart !== -1 && braceStart < colonIndex) {
|
|
164
|
+
const foundBraceEnd = content.indexOf(CLOSE_BRACE, braceStart);
|
|
165
|
+
if (foundBraceEnd !== -1 && foundBraceEnd < colonIndex)
|
|
166
|
+
fields = parseDelimitedValues(content.slice(braceStart + 1, foundBraceEnd), delimiter).map((field) => parseStringLiteral(field.trim()));
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
header: {
|
|
170
|
+
key,
|
|
171
|
+
length,
|
|
172
|
+
delimiter,
|
|
173
|
+
fields
|
|
174
|
+
},
|
|
175
|
+
inlineValues: afterColon || void 0
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function parseBracketSegment(seg, defaultDelimiter) {
|
|
179
|
+
let content = seg;
|
|
180
|
+
let delimiter = defaultDelimiter;
|
|
181
|
+
if (content.endsWith(TAB)) {
|
|
182
|
+
delimiter = DELIMITERS.tab;
|
|
183
|
+
content = content.slice(0, -1);
|
|
184
|
+
} else if (content.endsWith(PIPE)) {
|
|
185
|
+
delimiter = DELIMITERS.pipe;
|
|
186
|
+
content = content.slice(0, -1);
|
|
187
|
+
}
|
|
188
|
+
const length = Number.parseInt(content, 10);
|
|
189
|
+
if (Number.isNaN(length))
|
|
190
|
+
throw new TypeError(`Invalid array length: ${seg}`);
|
|
191
|
+
return {
|
|
192
|
+
length,
|
|
193
|
+
delimiter
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function parseDelimitedValues(input, delimiter) {
|
|
197
|
+
const values = [];
|
|
198
|
+
let valueBuffer = "";
|
|
199
|
+
let inQuotes = false;
|
|
200
|
+
let i = 0;
|
|
201
|
+
while (i < input.length) {
|
|
202
|
+
const char = input[i];
|
|
203
|
+
if (char === BACKSLASH && i + 1 < input.length && inQuotes) {
|
|
204
|
+
valueBuffer += char + input[i + 1];
|
|
205
|
+
i += 2;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (char === DOUBLE_QUOTE) {
|
|
209
|
+
inQuotes = !inQuotes;
|
|
210
|
+
valueBuffer += char;
|
|
211
|
+
i++;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (char === delimiter && !inQuotes) {
|
|
215
|
+
values.push(valueBuffer.trim());
|
|
216
|
+
valueBuffer = "";
|
|
217
|
+
i++;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
valueBuffer += char;
|
|
221
|
+
i++;
|
|
222
|
+
}
|
|
223
|
+
if (valueBuffer || values.length > 0)
|
|
224
|
+
values.push(valueBuffer.trim());
|
|
225
|
+
return values;
|
|
226
|
+
}
|
|
227
|
+
function mapRowValuesToPrimitives(values) {
|
|
228
|
+
return values.map((v) => parsePrimitiveToken(v));
|
|
229
|
+
}
|
|
230
|
+
function parsePrimitiveToken(token) {
|
|
231
|
+
const trimmedToken = token.trim();
|
|
232
|
+
if (!trimmedToken)
|
|
233
|
+
return "";
|
|
234
|
+
if (trimmedToken.startsWith(DOUBLE_QUOTE))
|
|
235
|
+
return parseStringLiteral(trimmedToken);
|
|
236
|
+
if (isBooleanOrNullLiteral(trimmedToken)) {
|
|
237
|
+
if (trimmedToken === TRUE_LITERAL)
|
|
238
|
+
return true;
|
|
239
|
+
if (trimmedToken === FALSE_LITERAL)
|
|
240
|
+
return false;
|
|
241
|
+
if (trimmedToken === NULL_LITERAL)
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
if (isNumericLiteral(trimmedToken)) {
|
|
245
|
+
const parsedNumber = Number.parseFloat(trimmedToken);
|
|
246
|
+
return Object.is(parsedNumber, -0) ? 0 : parsedNumber;
|
|
247
|
+
}
|
|
248
|
+
return trimmedToken;
|
|
249
|
+
}
|
|
250
|
+
function parseStringLiteral(token) {
|
|
251
|
+
const trimmedToken = token.trim();
|
|
252
|
+
if (trimmedToken.startsWith(DOUBLE_QUOTE)) {
|
|
253
|
+
const closingQuoteIndex = findClosingQuote(trimmedToken, 0);
|
|
254
|
+
if (closingQuoteIndex === -1)
|
|
255
|
+
throw new SyntaxError("Unterminated string: missing closing quote");
|
|
256
|
+
if (closingQuoteIndex !== trimmedToken.length - 1)
|
|
257
|
+
throw new SyntaxError("Unexpected characters after closing quote");
|
|
258
|
+
return unescapeString(trimmedToken.slice(1, closingQuoteIndex));
|
|
259
|
+
}
|
|
260
|
+
return trimmedToken;
|
|
261
|
+
}
|
|
262
|
+
function parseUnquotedKey(content, start) {
|
|
263
|
+
let parsePosition = start;
|
|
264
|
+
while (parsePosition < content.length && content[parsePosition] !== COLON)
|
|
265
|
+
parsePosition++;
|
|
266
|
+
if (parsePosition >= content.length || content[parsePosition] !== COLON)
|
|
267
|
+
throw new SyntaxError("Missing colon after key");
|
|
268
|
+
const key = content.slice(start, parsePosition).trim();
|
|
269
|
+
parsePosition++;
|
|
270
|
+
return {
|
|
271
|
+
key,
|
|
272
|
+
end: parsePosition
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function parseQuotedKey(content, start) {
|
|
276
|
+
const closingQuoteIndex = findClosingQuote(content, start);
|
|
277
|
+
if (closingQuoteIndex === -1)
|
|
278
|
+
throw new SyntaxError("Unterminated quoted key");
|
|
279
|
+
const key = unescapeString(content.slice(start + 1, closingQuoteIndex));
|
|
280
|
+
let parsePosition = closingQuoteIndex + 1;
|
|
281
|
+
if (parsePosition >= content.length || content[parsePosition] !== COLON)
|
|
282
|
+
throw new SyntaxError("Missing colon after key");
|
|
283
|
+
parsePosition++;
|
|
284
|
+
return {
|
|
285
|
+
key,
|
|
286
|
+
end: parsePosition
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function parseKeyToken(content, start) {
|
|
290
|
+
const isQuoted = content[start] === DOUBLE_QUOTE;
|
|
291
|
+
return {
|
|
292
|
+
...isQuoted ? parseQuotedKey(content, start) : parseUnquotedKey(content, start),
|
|
293
|
+
isQuoted
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function isArrayHeaderContent(content) {
|
|
297
|
+
return content.trim().startsWith(OPEN_BRACKET) && findUnquotedChar(content, COLON) !== -1;
|
|
298
|
+
}
|
|
299
|
+
function isKeyValueContent(content) {
|
|
300
|
+
return findUnquotedChar(content, COLON) !== -1;
|
|
301
|
+
}
|
|
302
|
+
function createScanState() {
|
|
303
|
+
return {
|
|
304
|
+
lineNumber: 0,
|
|
305
|
+
blankLines: []
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function parseLineIncremental(raw, state, indentSize, strict) {
|
|
309
|
+
state.lineNumber++;
|
|
310
|
+
const lineNumber = state.lineNumber;
|
|
311
|
+
let indent = 0;
|
|
312
|
+
while (indent < raw.length && raw[indent] === SPACE)
|
|
313
|
+
indent++;
|
|
314
|
+
const content = raw.slice(indent);
|
|
315
|
+
if (!content.trim()) {
|
|
316
|
+
const depth$1 = computeDepthFromIndent(indent, indentSize);
|
|
317
|
+
state.blankLines.push({
|
|
318
|
+
lineNumber,
|
|
319
|
+
indent,
|
|
320
|
+
depth: depth$1
|
|
321
|
+
});
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const depth = computeDepthFromIndent(indent, indentSize);
|
|
325
|
+
if (strict) {
|
|
326
|
+
let whitespaceEndIndex = 0;
|
|
327
|
+
while (whitespaceEndIndex < raw.length && (raw[whitespaceEndIndex] === SPACE || raw[whitespaceEndIndex] === TAB))
|
|
328
|
+
whitespaceEndIndex++;
|
|
329
|
+
if (raw.slice(0, whitespaceEndIndex).includes(TAB))
|
|
330
|
+
throw new SyntaxError(`Line ${lineNumber}: Tabs are not allowed in indentation in strict mode`);
|
|
331
|
+
if (indent > 0 && indent % indentSize !== 0)
|
|
332
|
+
throw new SyntaxError(`Line ${lineNumber}: Indentation must be exact multiple of ${indentSize}, but found ${indent} spaces`);
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
raw,
|
|
336
|
+
indent,
|
|
337
|
+
content,
|
|
338
|
+
depth,
|
|
339
|
+
lineNumber
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function* parseLinesSync(source, indentSize, strict, state) {
|
|
343
|
+
for (const raw of source) {
|
|
344
|
+
const parsedLine = parseLineIncremental(raw, state, indentSize, strict);
|
|
345
|
+
if (parsedLine !== void 0)
|
|
346
|
+
yield parsedLine;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function computeDepthFromIndent(indentSpaces, indentSize) {
|
|
350
|
+
return Math.floor(indentSpaces / indentSize);
|
|
351
|
+
}
|
|
352
|
+
function assertExpectedCount(actual, expected, itemType, options) {
|
|
353
|
+
if (options.strict && actual !== expected)
|
|
354
|
+
throw new RangeError(`Expected ${expected} ${itemType}, but got ${actual}`);
|
|
355
|
+
}
|
|
356
|
+
function validateNoExtraListItems(nextLine, itemDepth, expectedCount) {
|
|
357
|
+
if (nextLine?.depth === itemDepth && nextLine.content.startsWith(LIST_ITEM_PREFIX))
|
|
358
|
+
throw new RangeError(`Expected ${expectedCount} list array items, but found more`);
|
|
359
|
+
}
|
|
360
|
+
function validateNoExtraTabularRows(nextLine, rowDepth, header) {
|
|
361
|
+
if (nextLine?.depth === rowDepth && !nextLine.content.startsWith(LIST_ITEM_PREFIX) && isDataRow(nextLine.content, header.delimiter))
|
|
362
|
+
throw new RangeError(`Expected ${header.length} tabular rows, but found more`);
|
|
363
|
+
}
|
|
364
|
+
function validateNoBlankLinesInRange(startLine, endLine, blankLines, strict, context) {
|
|
365
|
+
if (!strict)
|
|
366
|
+
return;
|
|
367
|
+
const firstBlank = blankLines.find((blank) => blank.lineNumber > startLine && blank.lineNumber < endLine);
|
|
368
|
+
if (firstBlank)
|
|
369
|
+
throw new SyntaxError(`Line ${firstBlank.lineNumber}: Blank lines inside ${context} are not allowed in strict mode`);
|
|
370
|
+
}
|
|
371
|
+
function isDataRow(content, delimiter) {
|
|
372
|
+
const colonPos = content.indexOf(COLON);
|
|
373
|
+
const delimiterPos = content.indexOf(delimiter);
|
|
374
|
+
if (colonPos === -1)
|
|
375
|
+
return true;
|
|
376
|
+
if (delimiterPos !== -1 && delimiterPos < colonPos)
|
|
377
|
+
return true;
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
var StreamingLineCursor = class {
|
|
381
|
+
buffer = [];
|
|
382
|
+
generator;
|
|
383
|
+
done = false;
|
|
384
|
+
lastLine;
|
|
385
|
+
scanState;
|
|
386
|
+
constructor(generator, scanState) {
|
|
387
|
+
this.generator = generator;
|
|
388
|
+
this.scanState = scanState;
|
|
389
|
+
}
|
|
390
|
+
getBlankLines() {
|
|
391
|
+
return this.scanState.blankLines;
|
|
392
|
+
}
|
|
393
|
+
async peek() {
|
|
394
|
+
if (this.buffer.length > 0)
|
|
395
|
+
return this.buffer[0];
|
|
396
|
+
if (this.done)
|
|
397
|
+
return;
|
|
398
|
+
const result = await this.generator.next();
|
|
399
|
+
if (result.done) {
|
|
400
|
+
this.done = true;
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
this.buffer.push(result.value);
|
|
404
|
+
return result.value;
|
|
405
|
+
}
|
|
406
|
+
async next() {
|
|
407
|
+
const line = await this.peek();
|
|
408
|
+
if (line !== void 0) {
|
|
409
|
+
this.buffer.shift();
|
|
410
|
+
this.lastLine = line;
|
|
411
|
+
}
|
|
412
|
+
return line;
|
|
413
|
+
}
|
|
414
|
+
async advance() {
|
|
415
|
+
await this.next();
|
|
416
|
+
}
|
|
417
|
+
current() {
|
|
418
|
+
return this.lastLine;
|
|
419
|
+
}
|
|
420
|
+
async atEnd() {
|
|
421
|
+
return await this.peek() === void 0;
|
|
422
|
+
}
|
|
423
|
+
peekSync() {
|
|
424
|
+
if (this.buffer.length > 0)
|
|
425
|
+
return this.buffer[0];
|
|
426
|
+
if (this.done)
|
|
427
|
+
return;
|
|
428
|
+
const result = this.generator.next();
|
|
429
|
+
if (result.done) {
|
|
430
|
+
this.done = true;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
this.buffer.push(result.value);
|
|
434
|
+
return result.value;
|
|
435
|
+
}
|
|
436
|
+
nextSync() {
|
|
437
|
+
const line = this.peekSync();
|
|
438
|
+
if (line !== void 0) {
|
|
439
|
+
this.buffer.shift();
|
|
440
|
+
this.lastLine = line;
|
|
441
|
+
}
|
|
442
|
+
return line;
|
|
443
|
+
}
|
|
444
|
+
advanceSync() {
|
|
445
|
+
this.nextSync();
|
|
446
|
+
}
|
|
447
|
+
atEndSync() {
|
|
448
|
+
return this.peekSync() === void 0;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
function* decodeStreamSync$1(source, options) {
|
|
452
|
+
if (options?.expandPaths !== void 0)
|
|
453
|
+
throw new Error("expandPaths is not supported in streaming decode");
|
|
454
|
+
const resolvedOptions = {
|
|
455
|
+
indent: options?.indent ?? 2,
|
|
456
|
+
strict: options?.strict ?? true
|
|
457
|
+
};
|
|
458
|
+
const scanState = createScanState();
|
|
459
|
+
const cursor = new StreamingLineCursor(parseLinesSync(source, resolvedOptions.indent, resolvedOptions.strict, scanState), scanState);
|
|
460
|
+
const first = cursor.peekSync();
|
|
461
|
+
if (!first) {
|
|
462
|
+
yield { type: "startObject" };
|
|
463
|
+
yield { type: "endObject" };
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (isArrayHeaderContent(first.content)) {
|
|
467
|
+
const headerInfo = parseArrayHeaderLine(first.content, DEFAULT_DELIMITER);
|
|
468
|
+
if (headerInfo) {
|
|
469
|
+
cursor.advanceSync();
|
|
470
|
+
yield* decodeArrayFromHeaderSync(headerInfo.header, headerInfo.inlineValues, cursor, 0, resolvedOptions);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
cursor.advanceSync();
|
|
475
|
+
if (!!cursor.atEndSync() && !isKeyValueLineSync(first)) {
|
|
476
|
+
yield {
|
|
477
|
+
type: "primitive",
|
|
478
|
+
value: parsePrimitiveToken(first.content.trim())
|
|
479
|
+
};
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
yield { type: "startObject" };
|
|
483
|
+
yield* decodeKeyValueSync(first.content, cursor, 0, resolvedOptions);
|
|
484
|
+
while (!cursor.atEndSync()) {
|
|
485
|
+
const line = cursor.peekSync();
|
|
486
|
+
if (!line || line.depth !== 0)
|
|
487
|
+
break;
|
|
488
|
+
cursor.advanceSync();
|
|
489
|
+
yield* decodeKeyValueSync(line.content, cursor, 0, resolvedOptions);
|
|
490
|
+
}
|
|
491
|
+
yield { type: "endObject" };
|
|
492
|
+
}
|
|
493
|
+
function* decodeKeyValueSync(content, cursor, baseDepth, options) {
|
|
494
|
+
const arrayHeader = parseArrayHeaderLine(content, DEFAULT_DELIMITER);
|
|
495
|
+
if (arrayHeader && arrayHeader.header.key) {
|
|
496
|
+
yield {
|
|
497
|
+
type: "key",
|
|
498
|
+
key: arrayHeader.header.key
|
|
499
|
+
};
|
|
500
|
+
yield* decodeArrayFromHeaderSync(arrayHeader.header, arrayHeader.inlineValues, cursor, baseDepth, options);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const { key, isQuoted } = parseKeyToken(content, 0);
|
|
504
|
+
const colonIndex = content.indexOf(COLON, key.length);
|
|
505
|
+
const rest = colonIndex >= 0 ? content.slice(colonIndex + 1).trim() : "";
|
|
506
|
+
yield isQuoted ? {
|
|
507
|
+
type: "key",
|
|
508
|
+
key,
|
|
509
|
+
wasQuoted: true
|
|
510
|
+
} : {
|
|
511
|
+
type: "key",
|
|
512
|
+
key
|
|
513
|
+
};
|
|
514
|
+
if (!rest) {
|
|
515
|
+
const nextLine = cursor.peekSync();
|
|
516
|
+
if (nextLine && nextLine.depth > baseDepth) {
|
|
517
|
+
yield { type: "startObject" };
|
|
518
|
+
yield* decodeObjectFieldsSync(cursor, baseDepth + 1, options);
|
|
519
|
+
yield { type: "endObject" };
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
yield { type: "startObject" };
|
|
523
|
+
yield { type: "endObject" };
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
yield {
|
|
527
|
+
type: "primitive",
|
|
528
|
+
value: parsePrimitiveToken(rest)
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function* decodeObjectFieldsSync(cursor, baseDepth, options) {
|
|
532
|
+
let computedDepth;
|
|
533
|
+
while (!cursor.atEndSync()) {
|
|
534
|
+
const line = cursor.peekSync();
|
|
535
|
+
if (!line || line.depth < baseDepth)
|
|
536
|
+
break;
|
|
537
|
+
if (computedDepth === void 0 && line.depth >= baseDepth)
|
|
538
|
+
computedDepth = line.depth;
|
|
539
|
+
if (line.depth === computedDepth) {
|
|
540
|
+
cursor.advanceSync();
|
|
541
|
+
yield* decodeKeyValueSync(line.content, cursor, computedDepth, options);
|
|
542
|
+
} else
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function* decodeArrayFromHeaderSync(header, inlineValues, cursor, baseDepth, options) {
|
|
547
|
+
yield {
|
|
548
|
+
type: "startArray",
|
|
549
|
+
length: header.length
|
|
550
|
+
};
|
|
551
|
+
if (inlineValues) {
|
|
552
|
+
yield* decodeInlinePrimitiveArraySync(header, inlineValues, options);
|
|
553
|
+
yield { type: "endArray" };
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (header.fields && header.fields.length > 0) {
|
|
557
|
+
yield* decodeTabularArraySync(header, cursor, baseDepth, options);
|
|
558
|
+
yield { type: "endArray" };
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
yield* decodeListArraySync(header, cursor, baseDepth, options);
|
|
562
|
+
yield { type: "endArray" };
|
|
563
|
+
}
|
|
564
|
+
function* decodeInlinePrimitiveArraySync(header, inlineValues, options) {
|
|
565
|
+
if (!inlineValues.trim()) {
|
|
566
|
+
assertExpectedCount(0, header.length, "inline array items", options);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const primitives = mapRowValuesToPrimitives(parseDelimitedValues(inlineValues, header.delimiter));
|
|
570
|
+
assertExpectedCount(primitives.length, header.length, "inline array items", options);
|
|
571
|
+
for (const primitive of primitives)
|
|
572
|
+
yield {
|
|
573
|
+
type: "primitive",
|
|
574
|
+
value: primitive
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function* decodeTabularArraySync(header, cursor, baseDepth, options) {
|
|
578
|
+
const rowDepth = baseDepth + 1;
|
|
579
|
+
let rowCount = 0;
|
|
580
|
+
let startLine;
|
|
581
|
+
let endLine;
|
|
582
|
+
while (!cursor.atEndSync() && rowCount < header.length) {
|
|
583
|
+
const line = cursor.peekSync();
|
|
584
|
+
if (!line || line.depth < rowDepth)
|
|
585
|
+
break;
|
|
586
|
+
if (line.depth === rowDepth) {
|
|
587
|
+
if (startLine === void 0)
|
|
588
|
+
startLine = line.lineNumber;
|
|
589
|
+
endLine = line.lineNumber;
|
|
590
|
+
cursor.advanceSync();
|
|
591
|
+
const values = parseDelimitedValues(line.content, header.delimiter);
|
|
592
|
+
assertExpectedCount(values.length, header.fields.length, "tabular row values", options);
|
|
593
|
+
const primitives = mapRowValuesToPrimitives(values);
|
|
594
|
+
yield* yieldObjectFromFields(header.fields, primitives);
|
|
595
|
+
rowCount++;
|
|
596
|
+
} else
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
assertExpectedCount(rowCount, header.length, "tabular rows", options);
|
|
600
|
+
if (options.strict && startLine !== void 0 && endLine !== void 0)
|
|
601
|
+
validateNoBlankLinesInRange(startLine, endLine, cursor.getBlankLines(), options.strict, "tabular array");
|
|
602
|
+
if (options.strict)
|
|
603
|
+
validateNoExtraTabularRows(cursor.peekSync(), rowDepth, header);
|
|
604
|
+
}
|
|
605
|
+
function* decodeListArraySync(header, cursor, baseDepth, options) {
|
|
606
|
+
const itemDepth = baseDepth + 1;
|
|
607
|
+
let itemCount = 0;
|
|
608
|
+
let startLine;
|
|
609
|
+
let endLine;
|
|
610
|
+
while (!cursor.atEndSync() && itemCount < header.length) {
|
|
611
|
+
const line = cursor.peekSync();
|
|
612
|
+
if (!line || line.depth < itemDepth)
|
|
613
|
+
break;
|
|
614
|
+
const isListItem = line.content.startsWith(LIST_ITEM_PREFIX) || line.content === LIST_ITEM_MARKER;
|
|
615
|
+
if (line.depth === itemDepth && isListItem) {
|
|
616
|
+
if (startLine === void 0)
|
|
617
|
+
startLine = line.lineNumber;
|
|
618
|
+
endLine = line.lineNumber;
|
|
619
|
+
yield* decodeListItemSync(cursor, itemDepth, options);
|
|
620
|
+
const currentLine = cursor.current();
|
|
621
|
+
if (currentLine)
|
|
622
|
+
endLine = currentLine.lineNumber;
|
|
623
|
+
itemCount++;
|
|
624
|
+
} else
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
assertExpectedCount(itemCount, header.length, "list array items", options);
|
|
628
|
+
if (options.strict && startLine !== void 0 && endLine !== void 0)
|
|
629
|
+
validateNoBlankLinesInRange(startLine, endLine, cursor.getBlankLines(), options.strict, "list array");
|
|
630
|
+
if (options.strict)
|
|
631
|
+
validateNoExtraListItems(cursor.peekSync(), itemDepth, header.length);
|
|
632
|
+
}
|
|
633
|
+
function* decodeListItemSync(cursor, baseDepth, options) {
|
|
634
|
+
const line = cursor.nextSync();
|
|
635
|
+
if (!line)
|
|
636
|
+
throw new ReferenceError("Expected list item");
|
|
637
|
+
let afterHyphen;
|
|
638
|
+
if (line.content === LIST_ITEM_MARKER) {
|
|
639
|
+
const followDepth = baseDepth + 1;
|
|
640
|
+
const nextLine = cursor.peekSync();
|
|
641
|
+
if (!nextLine || nextLine.depth < followDepth) {
|
|
642
|
+
yield { type: "startObject" };
|
|
643
|
+
yield { type: "endObject" };
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (nextLine.depth === followDepth && !nextLine.content.startsWith(LIST_ITEM_PREFIX)) {
|
|
647
|
+
yield { type: "startObject" };
|
|
648
|
+
while (!cursor.atEndSync()) {
|
|
649
|
+
const fieldLine = cursor.peekSync();
|
|
650
|
+
if (!fieldLine || fieldLine.depth < followDepth)
|
|
651
|
+
break;
|
|
652
|
+
if (fieldLine.depth === followDepth && !fieldLine.content.startsWith(LIST_ITEM_PREFIX)) {
|
|
653
|
+
cursor.advanceSync();
|
|
654
|
+
yield* decodeKeyValueSync(fieldLine.content, cursor, followDepth, options);
|
|
655
|
+
} else
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
yield { type: "endObject" };
|
|
659
|
+
return;
|
|
660
|
+
} else {
|
|
661
|
+
yield { type: "startObject" };
|
|
662
|
+
yield { type: "endObject" };
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
} else if (line.content.startsWith(LIST_ITEM_PREFIX))
|
|
666
|
+
afterHyphen = line.content.slice(LIST_ITEM_PREFIX.length);
|
|
667
|
+
else
|
|
668
|
+
throw new SyntaxError(`Expected list item to start with "${LIST_ITEM_PREFIX}"`);
|
|
669
|
+
if (!afterHyphen.trim()) {
|
|
670
|
+
yield { type: "startObject" };
|
|
671
|
+
yield { type: "endObject" };
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (isArrayHeaderContent(afterHyphen)) {
|
|
675
|
+
const arrayHeader = parseArrayHeaderLine(afterHyphen, DEFAULT_DELIMITER);
|
|
676
|
+
if (arrayHeader) {
|
|
677
|
+
yield* decodeArrayFromHeaderSync(arrayHeader.header, arrayHeader.inlineValues, cursor, baseDepth, options);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const headerInfo = parseArrayHeaderLine(afterHyphen, DEFAULT_DELIMITER);
|
|
682
|
+
if (headerInfo && headerInfo.header.key && headerInfo.header.fields) {
|
|
683
|
+
const header = headerInfo.header;
|
|
684
|
+
yield { type: "startObject" };
|
|
685
|
+
yield {
|
|
686
|
+
type: "key",
|
|
687
|
+
key: header.key
|
|
688
|
+
};
|
|
689
|
+
yield* decodeArrayFromHeaderSync(header, headerInfo.inlineValues, cursor, baseDepth + 1, options);
|
|
690
|
+
const followDepth = baseDepth + 1;
|
|
691
|
+
while (!cursor.atEndSync()) {
|
|
692
|
+
const nextLine = cursor.peekSync();
|
|
693
|
+
if (!nextLine || nextLine.depth < followDepth)
|
|
694
|
+
break;
|
|
695
|
+
if (nextLine.depth === followDepth && !nextLine.content.startsWith(LIST_ITEM_PREFIX)) {
|
|
696
|
+
cursor.advanceSync();
|
|
697
|
+
yield* decodeKeyValueSync(nextLine.content, cursor, followDepth, options);
|
|
698
|
+
} else
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
yield { type: "endObject" };
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
if (isKeyValueContent(afterHyphen)) {
|
|
705
|
+
yield { type: "startObject" };
|
|
706
|
+
yield* decodeKeyValueSync(afterHyphen, cursor, baseDepth + 1, options);
|
|
707
|
+
const followDepth = baseDepth + 1;
|
|
708
|
+
while (!cursor.atEndSync()) {
|
|
709
|
+
const nextLine = cursor.peekSync();
|
|
710
|
+
if (!nextLine || nextLine.depth < followDepth)
|
|
711
|
+
break;
|
|
712
|
+
if (nextLine.depth === followDepth && !nextLine.content.startsWith(LIST_ITEM_PREFIX)) {
|
|
713
|
+
cursor.advanceSync();
|
|
714
|
+
yield* decodeKeyValueSync(nextLine.content, cursor, followDepth, options);
|
|
715
|
+
} else
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
yield { type: "endObject" };
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
yield {
|
|
722
|
+
type: "primitive",
|
|
723
|
+
value: parsePrimitiveToken(afterHyphen)
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function isKeyValueLineSync(line) {
|
|
727
|
+
const content = line.content;
|
|
728
|
+
if (content.startsWith('"')) {
|
|
729
|
+
const closingQuoteIndex = findClosingQuote(content, 0);
|
|
730
|
+
if (closingQuoteIndex === -1)
|
|
731
|
+
return false;
|
|
732
|
+
return content.slice(closingQuoteIndex + 1).includes(COLON);
|
|
733
|
+
} else
|
|
734
|
+
return content.includes(COLON);
|
|
735
|
+
}
|
|
736
|
+
function* yieldObjectFromFields(fields, primitives) {
|
|
737
|
+
yield { type: "startObject" };
|
|
738
|
+
for (let i = 0; i < fields.length; i++) {
|
|
739
|
+
yield {
|
|
740
|
+
type: "key",
|
|
741
|
+
key: fields[i]
|
|
742
|
+
};
|
|
743
|
+
yield {
|
|
744
|
+
type: "primitive",
|
|
745
|
+
value: primitives[i]
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
yield { type: "endObject" };
|
|
749
|
+
}
|
|
750
|
+
function normalizeValue(value) {
|
|
751
|
+
if (value === null)
|
|
752
|
+
return null;
|
|
753
|
+
if (typeof value === "object" && value !== null && "toJSON" in value && typeof value.toJSON === "function") {
|
|
754
|
+
const next = value.toJSON();
|
|
755
|
+
if (next !== value)
|
|
756
|
+
return normalizeValue(next);
|
|
757
|
+
}
|
|
758
|
+
if (typeof value === "string" || typeof value === "boolean")
|
|
759
|
+
return value;
|
|
760
|
+
if (typeof value === "number") {
|
|
761
|
+
if (Object.is(value, -0))
|
|
762
|
+
return 0;
|
|
763
|
+
if (!Number.isFinite(value))
|
|
764
|
+
return null;
|
|
765
|
+
return value;
|
|
766
|
+
}
|
|
767
|
+
if (typeof value === "bigint") {
|
|
768
|
+
if (value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER)
|
|
769
|
+
return Number(value);
|
|
770
|
+
return value.toString();
|
|
771
|
+
}
|
|
772
|
+
if (value instanceof Date)
|
|
773
|
+
return value.toISOString();
|
|
774
|
+
if (Array.isArray(value))
|
|
775
|
+
return value.map(normalizeValue);
|
|
776
|
+
if (value instanceof Set)
|
|
777
|
+
return Array.from(value).map(normalizeValue);
|
|
778
|
+
if (value instanceof Map)
|
|
779
|
+
return Object.fromEntries(Array.from(value, ([k, v]) => [String(k), normalizeValue(v)]));
|
|
780
|
+
if (isPlainObject(value)) {
|
|
781
|
+
const normalized = {};
|
|
782
|
+
for (const key in value)
|
|
783
|
+
if (Object.prototype.hasOwnProperty.call(value, key))
|
|
784
|
+
normalized[key] = normalizeValue(value[key]);
|
|
785
|
+
return normalized;
|
|
786
|
+
}
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
function isJsonPrimitive(value) {
|
|
790
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
791
|
+
}
|
|
792
|
+
function isJsonArray(value) {
|
|
793
|
+
return Array.isArray(value);
|
|
794
|
+
}
|
|
795
|
+
function isJsonObject(value) {
|
|
796
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
797
|
+
}
|
|
798
|
+
function isEmptyObject(value) {
|
|
799
|
+
return Object.keys(value).length === 0;
|
|
800
|
+
}
|
|
801
|
+
function isPlainObject(value) {
|
|
802
|
+
if (value === null || typeof value !== "object")
|
|
803
|
+
return false;
|
|
804
|
+
const prototype = Object.getPrototypeOf(value);
|
|
805
|
+
return prototype === null || prototype === Object.prototype;
|
|
806
|
+
}
|
|
807
|
+
function isArrayOfPrimitives(value) {
|
|
808
|
+
return value.length === 0 || value.every((item) => isJsonPrimitive(item));
|
|
809
|
+
}
|
|
810
|
+
function isArrayOfArrays(value) {
|
|
811
|
+
return value.length === 0 || value.every((item) => isJsonArray(item));
|
|
812
|
+
}
|
|
813
|
+
function isArrayOfObjects(value) {
|
|
814
|
+
return value.length === 0 || value.every((item) => isJsonObject(item));
|
|
815
|
+
}
|
|
816
|
+
function isValidUnquotedKey(key) {
|
|
817
|
+
return /^[A-Z_][\w.]*$/i.test(key);
|
|
818
|
+
}
|
|
819
|
+
function isIdentifierSegment(key) {
|
|
820
|
+
return /^[A-Z_]\w*$/i.test(key);
|
|
821
|
+
}
|
|
822
|
+
function isSafeUnquoted(value, delimiter = DEFAULT_DELIMITER) {
|
|
823
|
+
if (!value)
|
|
824
|
+
return false;
|
|
825
|
+
if (value !== value.trim())
|
|
826
|
+
return false;
|
|
827
|
+
if (isBooleanOrNullLiteral(value) || isNumericLike(value))
|
|
828
|
+
return false;
|
|
829
|
+
if (value.includes(":"))
|
|
830
|
+
return false;
|
|
831
|
+
if (value.includes('"') || value.includes("\\"))
|
|
832
|
+
return false;
|
|
833
|
+
if (/[[\]{}]/.test(value))
|
|
834
|
+
return false;
|
|
835
|
+
if (/[\n\r\t]/.test(value))
|
|
836
|
+
return false;
|
|
837
|
+
if (value.includes(delimiter))
|
|
838
|
+
return false;
|
|
839
|
+
if (value.startsWith(LIST_ITEM_MARKER))
|
|
840
|
+
return false;
|
|
841
|
+
return true;
|
|
842
|
+
}
|
|
843
|
+
function isNumericLike(value) {
|
|
844
|
+
return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value) || /^0\d+$/.test(value);
|
|
845
|
+
}
|
|
846
|
+
var QUOTED_KEY_MARKER = Symbol("quotedKey");
|
|
847
|
+
function expandPathsSafe(value, strict) {
|
|
848
|
+
if (Array.isArray(value))
|
|
849
|
+
return value.map((item) => expandPathsSafe(item, strict));
|
|
850
|
+
if (isJsonObject(value)) {
|
|
851
|
+
const expandedObject = {};
|
|
852
|
+
const quotedKeys = value[QUOTED_KEY_MARKER];
|
|
853
|
+
for (const [key, keyValue] of Object.entries(value)) {
|
|
854
|
+
const isQuoted = quotedKeys?.has(key);
|
|
855
|
+
if (key.includes(DOT) && !isQuoted) {
|
|
856
|
+
const segments = key.split(DOT);
|
|
857
|
+
if (segments.every((seg) => isIdentifierSegment(seg))) {
|
|
858
|
+
insertPathSafe(expandedObject, segments, expandPathsSafe(keyValue, strict), strict);
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const expandedValue = expandPathsSafe(keyValue, strict);
|
|
863
|
+
if (key in expandedObject) {
|
|
864
|
+
const conflictingValue = expandedObject[key];
|
|
865
|
+
if (canMerge(conflictingValue, expandedValue))
|
|
866
|
+
mergeObjects(conflictingValue, expandedValue, strict);
|
|
867
|
+
else {
|
|
868
|
+
if (strict)
|
|
869
|
+
throw new TypeError(`Path expansion conflict at key "${key}": cannot merge ${typeof conflictingValue} with ${typeof expandedValue}`);
|
|
870
|
+
expandedObject[key] = expandedValue;
|
|
871
|
+
}
|
|
872
|
+
} else
|
|
873
|
+
expandedObject[key] = expandedValue;
|
|
874
|
+
}
|
|
875
|
+
return expandedObject;
|
|
876
|
+
}
|
|
877
|
+
return value;
|
|
878
|
+
}
|
|
879
|
+
function insertPathSafe(target, segments, value, strict) {
|
|
880
|
+
let currentNode = target;
|
|
881
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
882
|
+
const currentSegment = segments[i];
|
|
883
|
+
const segmentValue = currentNode[currentSegment];
|
|
884
|
+
if (segmentValue === void 0) {
|
|
885
|
+
const newObj = {};
|
|
886
|
+
currentNode[currentSegment] = newObj;
|
|
887
|
+
currentNode = newObj;
|
|
888
|
+
} else if (isJsonObject(segmentValue))
|
|
889
|
+
currentNode = segmentValue;
|
|
890
|
+
else {
|
|
891
|
+
if (strict)
|
|
892
|
+
throw new TypeError(`Path expansion conflict at segment "${currentSegment}": expected object but found ${typeof segmentValue}`);
|
|
893
|
+
const newObj = {};
|
|
894
|
+
currentNode[currentSegment] = newObj;
|
|
895
|
+
currentNode = newObj;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const lastSeg = segments[segments.length - 1];
|
|
899
|
+
const destinationValue = currentNode[lastSeg];
|
|
900
|
+
if (destinationValue === void 0)
|
|
901
|
+
currentNode[lastSeg] = value;
|
|
902
|
+
else if (canMerge(destinationValue, value))
|
|
903
|
+
mergeObjects(destinationValue, value, strict);
|
|
904
|
+
else {
|
|
905
|
+
if (strict)
|
|
906
|
+
throw new TypeError(`Path expansion conflict at key "${lastSeg}": cannot merge ${typeof destinationValue} with ${typeof value}`);
|
|
907
|
+
currentNode[lastSeg] = value;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
function mergeObjects(target, source, strict) {
|
|
911
|
+
for (const [key, sourceValue] of Object.entries(source)) {
|
|
912
|
+
const targetValue = target[key];
|
|
913
|
+
if (targetValue === void 0)
|
|
914
|
+
target[key] = sourceValue;
|
|
915
|
+
else if (canMerge(targetValue, sourceValue))
|
|
916
|
+
mergeObjects(targetValue, sourceValue, strict);
|
|
917
|
+
else {
|
|
918
|
+
if (strict)
|
|
919
|
+
throw new TypeError(`Path expansion conflict at key "${key}": cannot merge ${typeof targetValue} with ${typeof sourceValue}`);
|
|
920
|
+
target[key] = sourceValue;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
function canMerge(a, b) {
|
|
925
|
+
return isJsonObject(a) && isJsonObject(b);
|
|
926
|
+
}
|
|
927
|
+
function buildValueFromEvents(events) {
|
|
928
|
+
const state = {
|
|
929
|
+
stack: [],
|
|
930
|
+
root: void 0
|
|
931
|
+
};
|
|
932
|
+
for (const event of events)
|
|
933
|
+
applyEvent(state, event);
|
|
934
|
+
return finalizeState(state);
|
|
935
|
+
}
|
|
936
|
+
function applyEvent(state, event) {
|
|
937
|
+
const { stack } = state;
|
|
938
|
+
switch (event.type) {
|
|
939
|
+
case "startObject": {
|
|
940
|
+
const obj = {};
|
|
941
|
+
const quotedKeys = /* @__PURE__ */ new Set();
|
|
942
|
+
if (stack.length === 0)
|
|
943
|
+
stack.push({
|
|
944
|
+
type: "object",
|
|
945
|
+
obj,
|
|
946
|
+
quotedKeys
|
|
947
|
+
});
|
|
948
|
+
else {
|
|
949
|
+
const parent = stack[stack.length - 1];
|
|
950
|
+
if (parent.type === "object") {
|
|
951
|
+
if (parent.currentKey === void 0)
|
|
952
|
+
throw new Error("Object startObject event without preceding key");
|
|
953
|
+
parent.obj[parent.currentKey] = obj;
|
|
954
|
+
parent.currentKey = void 0;
|
|
955
|
+
} else if (parent.type === "array")
|
|
956
|
+
parent.arr.push(obj);
|
|
957
|
+
stack.push({
|
|
958
|
+
type: "object",
|
|
959
|
+
obj,
|
|
960
|
+
quotedKeys
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
case "endObject": {
|
|
966
|
+
if (stack.length === 0)
|
|
967
|
+
throw new Error("Unexpected endObject event");
|
|
968
|
+
const context = stack.pop();
|
|
969
|
+
if (context.type !== "object")
|
|
970
|
+
throw new Error("Mismatched endObject event");
|
|
971
|
+
if (context.quotedKeys.size > 0)
|
|
972
|
+
Object.defineProperty(context.obj, QUOTED_KEY_MARKER, {
|
|
973
|
+
value: context.quotedKeys,
|
|
974
|
+
enumerable: false,
|
|
975
|
+
writable: false,
|
|
976
|
+
configurable: false
|
|
977
|
+
});
|
|
978
|
+
if (stack.length === 0)
|
|
979
|
+
state.root = context.obj;
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
case "startArray": {
|
|
983
|
+
const arr = [];
|
|
984
|
+
if (stack.length === 0)
|
|
985
|
+
stack.push({
|
|
986
|
+
type: "array",
|
|
987
|
+
arr
|
|
988
|
+
});
|
|
989
|
+
else {
|
|
990
|
+
const parent = stack[stack.length - 1];
|
|
991
|
+
if (parent.type === "object") {
|
|
992
|
+
if (parent.currentKey === void 0)
|
|
993
|
+
throw new Error("Array startArray event without preceding key");
|
|
994
|
+
parent.obj[parent.currentKey] = arr;
|
|
995
|
+
parent.currentKey = void 0;
|
|
996
|
+
} else if (parent.type === "array")
|
|
997
|
+
parent.arr.push(arr);
|
|
998
|
+
stack.push({
|
|
999
|
+
type: "array",
|
|
1000
|
+
arr
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
1005
|
+
case "endArray": {
|
|
1006
|
+
if (stack.length === 0)
|
|
1007
|
+
throw new Error("Unexpected endArray event");
|
|
1008
|
+
const context = stack.pop();
|
|
1009
|
+
if (context.type !== "array")
|
|
1010
|
+
throw new Error("Mismatched endArray event");
|
|
1011
|
+
if (stack.length === 0)
|
|
1012
|
+
state.root = context.arr;
|
|
1013
|
+
break;
|
|
1014
|
+
}
|
|
1015
|
+
case "key": {
|
|
1016
|
+
if (stack.length === 0)
|
|
1017
|
+
throw new Error("Key event outside of object context");
|
|
1018
|
+
const parent = stack[stack.length - 1];
|
|
1019
|
+
if (parent.type !== "object")
|
|
1020
|
+
throw new Error("Key event in non-object context");
|
|
1021
|
+
parent.currentKey = event.key;
|
|
1022
|
+
if (event.wasQuoted)
|
|
1023
|
+
parent.quotedKeys.add(event.key);
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
case "primitive":
|
|
1027
|
+
if (stack.length === 0)
|
|
1028
|
+
state.root = event.value;
|
|
1029
|
+
else {
|
|
1030
|
+
const parent = stack[stack.length - 1];
|
|
1031
|
+
if (parent.type === "object") {
|
|
1032
|
+
if (parent.currentKey === void 0)
|
|
1033
|
+
throw new Error("Primitive event without preceding key in object");
|
|
1034
|
+
parent.obj[parent.currentKey] = event.value;
|
|
1035
|
+
parent.currentKey = void 0;
|
|
1036
|
+
} else if (parent.type === "array")
|
|
1037
|
+
parent.arr.push(event.value);
|
|
1038
|
+
}
|
|
1039
|
+
break;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function finalizeState(state) {
|
|
1043
|
+
if (state.stack.length !== 0)
|
|
1044
|
+
throw new Error("Incomplete event stream: stack not empty at end");
|
|
1045
|
+
if (state.root === void 0)
|
|
1046
|
+
throw new Error("No root value built from events");
|
|
1047
|
+
return state.root;
|
|
1048
|
+
}
|
|
1049
|
+
function tryFoldKeyChain(key, value, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
|
|
1050
|
+
if (options.keyFolding !== "safe")
|
|
1051
|
+
return;
|
|
1052
|
+
if (!isJsonObject(value))
|
|
1053
|
+
return;
|
|
1054
|
+
const { segments, tail, leafValue } = collectSingleKeyChain(key, value, flattenDepth ?? options.flattenDepth);
|
|
1055
|
+
if (segments.length < 2)
|
|
1056
|
+
return;
|
|
1057
|
+
if (!segments.every((seg) => isIdentifierSegment(seg)))
|
|
1058
|
+
return;
|
|
1059
|
+
const foldedKey = buildFoldedKey(segments);
|
|
1060
|
+
const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
|
|
1061
|
+
if (siblings.includes(foldedKey))
|
|
1062
|
+
return;
|
|
1063
|
+
if (rootLiteralKeys && rootLiteralKeys.has(absolutePath))
|
|
1064
|
+
return;
|
|
1065
|
+
return {
|
|
1066
|
+
foldedKey,
|
|
1067
|
+
remainder: tail,
|
|
1068
|
+
leafValue,
|
|
1069
|
+
segmentCount: segments.length
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
function collectSingleKeyChain(startKey, startValue, maxDepth) {
|
|
1073
|
+
const segments = [startKey];
|
|
1074
|
+
let currentValue = startValue;
|
|
1075
|
+
while (segments.length < maxDepth) {
|
|
1076
|
+
if (!isJsonObject(currentValue))
|
|
1077
|
+
break;
|
|
1078
|
+
const keys = Object.keys(currentValue);
|
|
1079
|
+
if (keys.length !== 1)
|
|
1080
|
+
break;
|
|
1081
|
+
const nextKey = keys[0];
|
|
1082
|
+
const nextValue = currentValue[nextKey];
|
|
1083
|
+
segments.push(nextKey);
|
|
1084
|
+
currentValue = nextValue;
|
|
1085
|
+
}
|
|
1086
|
+
if (!isJsonObject(currentValue) || isEmptyObject(currentValue))
|
|
1087
|
+
return {
|
|
1088
|
+
segments,
|
|
1089
|
+
tail: void 0,
|
|
1090
|
+
leafValue: currentValue
|
|
1091
|
+
};
|
|
1092
|
+
return {
|
|
1093
|
+
segments,
|
|
1094
|
+
tail: currentValue,
|
|
1095
|
+
leafValue: currentValue
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
function buildFoldedKey(segments) {
|
|
1099
|
+
return segments.join(DOT);
|
|
1100
|
+
}
|
|
1101
|
+
function encodePrimitive(value, delimiter) {
|
|
1102
|
+
if (value === null)
|
|
1103
|
+
return NULL_LITERAL;
|
|
1104
|
+
if (typeof value === "boolean")
|
|
1105
|
+
return String(value);
|
|
1106
|
+
if (typeof value === "number")
|
|
1107
|
+
return String(value);
|
|
1108
|
+
return encodeStringLiteral(value, delimiter);
|
|
1109
|
+
}
|
|
1110
|
+
function encodeStringLiteral(value, delimiter = DEFAULT_DELIMITER) {
|
|
1111
|
+
if (isSafeUnquoted(value, delimiter))
|
|
1112
|
+
return value;
|
|
1113
|
+
return `${DOUBLE_QUOTE}${escapeString(value)}${DOUBLE_QUOTE}`;
|
|
1114
|
+
}
|
|
1115
|
+
function encodeKey(key) {
|
|
1116
|
+
if (isValidUnquotedKey(key))
|
|
1117
|
+
return key;
|
|
1118
|
+
return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
|
|
1119
|
+
}
|
|
1120
|
+
function encodeAndJoinPrimitives(values, delimiter = DEFAULT_DELIMITER) {
|
|
1121
|
+
return values.map((v) => encodePrimitive(v, delimiter)).join(delimiter);
|
|
1122
|
+
}
|
|
1123
|
+
function formatHeader(length, options) {
|
|
1124
|
+
const key = options?.key;
|
|
1125
|
+
const fields = options?.fields;
|
|
1126
|
+
const delimiter = options?.delimiter ?? COMMA;
|
|
1127
|
+
let header = "";
|
|
1128
|
+
if (key)
|
|
1129
|
+
header += encodeKey(key);
|
|
1130
|
+
header += `[${length}${delimiter !== DEFAULT_DELIMITER ? delimiter : ""}]`;
|
|
1131
|
+
if (fields) {
|
|
1132
|
+
const quotedFields = fields.map((f) => encodeKey(f));
|
|
1133
|
+
header += `{${quotedFields.join(delimiter)}}`;
|
|
1134
|
+
}
|
|
1135
|
+
header += ":";
|
|
1136
|
+
return header;
|
|
1137
|
+
}
|
|
1138
|
+
function* encodeJsonValue(value, options, depth) {
|
|
1139
|
+
if (isJsonPrimitive(value)) {
|
|
1140
|
+
const encodedPrimitive = encodePrimitive(value, options.delimiter);
|
|
1141
|
+
if (encodedPrimitive !== "")
|
|
1142
|
+
yield encodedPrimitive;
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (isJsonArray(value))
|
|
1146
|
+
yield* encodeArrayLines(void 0, value, depth, options);
|
|
1147
|
+
else if (isJsonObject(value))
|
|
1148
|
+
yield* encodeObjectLines(value, depth, options);
|
|
1149
|
+
}
|
|
1150
|
+
function* encodeObjectLines(value, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
|
|
1151
|
+
const keys = Object.keys(value);
|
|
1152
|
+
if (depth === 0 && !rootLiteralKeys)
|
|
1153
|
+
rootLiteralKeys = new Set(keys.filter((k) => k.includes(".")));
|
|
1154
|
+
const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
|
|
1155
|
+
for (const [key, val] of Object.entries(value))
|
|
1156
|
+
yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
|
|
1157
|
+
}
|
|
1158
|
+
function* encodeKeyValuePairLines(key, value, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
|
|
1159
|
+
const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
|
|
1160
|
+
const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
|
|
1161
|
+
if (options.keyFolding === "safe" && siblings) {
|
|
1162
|
+
const foldResult = tryFoldKeyChain(key, value, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
|
|
1163
|
+
if (foldResult) {
|
|
1164
|
+
const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
|
|
1165
|
+
const encodedFoldedKey = encodeKey(foldedKey);
|
|
1166
|
+
if (remainder === void 0) {
|
|
1167
|
+
if (isJsonPrimitive(leafValue)) {
|
|
1168
|
+
yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
|
|
1169
|
+
return;
|
|
1170
|
+
} else if (isJsonArray(leafValue)) {
|
|
1171
|
+
yield* encodeArrayLines(foldedKey, leafValue, depth, options);
|
|
1172
|
+
return;
|
|
1173
|
+
} else if (isJsonObject(leafValue) && isEmptyObject(leafValue)) {
|
|
1174
|
+
yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
if (isJsonObject(remainder)) {
|
|
1179
|
+
yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
|
|
1180
|
+
const remainingDepth = effectiveFlattenDepth - segmentCount;
|
|
1181
|
+
const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
|
|
1182
|
+
yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
const encodedKey = encodeKey(key);
|
|
1188
|
+
if (isJsonPrimitive(value))
|
|
1189
|
+
yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value, options.delimiter)}`, options.indent);
|
|
1190
|
+
else if (isJsonArray(value))
|
|
1191
|
+
yield* encodeArrayLines(key, value, depth, options);
|
|
1192
|
+
else if (isJsonObject(value)) {
|
|
1193
|
+
yield indentedLine(depth, `${encodedKey}:`, options.indent);
|
|
1194
|
+
if (!isEmptyObject(value))
|
|
1195
|
+
yield* encodeObjectLines(value, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
function* encodeArrayLines(key, value, depth, options) {
|
|
1199
|
+
if (value.length === 0) {
|
|
1200
|
+
yield indentedLine(depth, formatHeader(0, {
|
|
1201
|
+
key,
|
|
1202
|
+
delimiter: options.delimiter
|
|
1203
|
+
}), options.indent);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
if (isArrayOfPrimitives(value)) {
|
|
1207
|
+
yield indentedLine(depth, encodeInlineArrayLine(value, options.delimiter, key), options.indent);
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
if (isArrayOfArrays(value)) {
|
|
1211
|
+
if (value.every((arr) => isArrayOfPrimitives(arr))) {
|
|
1212
|
+
yield* encodeArrayOfArraysAsListItemsLines(key, value, depth, options);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (isArrayOfObjects(value)) {
|
|
1217
|
+
const header = extractTabularHeader(value);
|
|
1218
|
+
if (header)
|
|
1219
|
+
yield* encodeArrayOfObjectsAsTabularLines(key, value, header, depth, options);
|
|
1220
|
+
else
|
|
1221
|
+
yield* encodeMixedArrayAsListItemsLines(key, value, depth, options);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
yield* encodeMixedArrayAsListItemsLines(key, value, depth, options);
|
|
1225
|
+
}
|
|
1226
|
+
function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
|
|
1227
|
+
yield indentedLine(depth, formatHeader(values.length, {
|
|
1228
|
+
key: prefix,
|
|
1229
|
+
delimiter: options.delimiter
|
|
1230
|
+
}), options.indent);
|
|
1231
|
+
for (const arr of values)
|
|
1232
|
+
if (isArrayOfPrimitives(arr)) {
|
|
1233
|
+
const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
|
|
1234
|
+
yield indentedListItem(depth + 1, arrayLine, options.indent);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
function encodeInlineArrayLine(values, delimiter, prefix) {
|
|
1238
|
+
const header = formatHeader(values.length, {
|
|
1239
|
+
key: prefix,
|
|
1240
|
+
delimiter
|
|
1241
|
+
});
|
|
1242
|
+
const joinedValue = encodeAndJoinPrimitives(values, delimiter);
|
|
1243
|
+
if (values.length === 0)
|
|
1244
|
+
return header;
|
|
1245
|
+
return `${header} ${joinedValue}`;
|
|
1246
|
+
}
|
|
1247
|
+
function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
|
|
1248
|
+
yield indentedLine(depth, formatHeader(rows.length, {
|
|
1249
|
+
key: prefix,
|
|
1250
|
+
fields: header,
|
|
1251
|
+
delimiter: options.delimiter
|
|
1252
|
+
}), options.indent);
|
|
1253
|
+
yield* writeTabularRowsLines(rows, header, depth + 1, options);
|
|
1254
|
+
}
|
|
1255
|
+
function extractTabularHeader(rows) {
|
|
1256
|
+
if (rows.length === 0)
|
|
1257
|
+
return;
|
|
1258
|
+
const firstRow = rows[0];
|
|
1259
|
+
const firstKeys = Object.keys(firstRow);
|
|
1260
|
+
if (firstKeys.length === 0)
|
|
1261
|
+
return;
|
|
1262
|
+
if (isTabularArray(rows, firstKeys))
|
|
1263
|
+
return firstKeys;
|
|
1264
|
+
}
|
|
1265
|
+
function isTabularArray(rows, header) {
|
|
1266
|
+
for (const row of rows) {
|
|
1267
|
+
if (Object.keys(row).length !== header.length)
|
|
1268
|
+
return false;
|
|
1269
|
+
for (const key of header) {
|
|
1270
|
+
if (!(key in row))
|
|
1271
|
+
return false;
|
|
1272
|
+
if (!isJsonPrimitive(row[key]))
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return true;
|
|
1277
|
+
}
|
|
1278
|
+
function* writeTabularRowsLines(rows, header, depth, options) {
|
|
1279
|
+
for (const row of rows)
|
|
1280
|
+
yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
|
|
1281
|
+
}
|
|
1282
|
+
function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
|
|
1283
|
+
yield indentedLine(depth, formatHeader(items.length, {
|
|
1284
|
+
key: prefix,
|
|
1285
|
+
delimiter: options.delimiter
|
|
1286
|
+
}), options.indent);
|
|
1287
|
+
for (const item of items)
|
|
1288
|
+
yield* encodeListItemValueLines(item, depth + 1, options);
|
|
1289
|
+
}
|
|
1290
|
+
function* encodeObjectAsListItemLines(obj, depth, options) {
|
|
1291
|
+
if (isEmptyObject(obj)) {
|
|
1292
|
+
yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const entries = Object.entries(obj);
|
|
1296
|
+
const [firstKey, firstValue] = entries[0];
|
|
1297
|
+
const restEntries = entries.slice(1);
|
|
1298
|
+
if (isJsonArray(firstValue) && isArrayOfObjects(firstValue)) {
|
|
1299
|
+
const header = extractTabularHeader(firstValue);
|
|
1300
|
+
if (header) {
|
|
1301
|
+
yield indentedListItem(depth, formatHeader(firstValue.length, {
|
|
1302
|
+
key: firstKey,
|
|
1303
|
+
fields: header,
|
|
1304
|
+
delimiter: options.delimiter
|
|
1305
|
+
}), options.indent);
|
|
1306
|
+
yield* writeTabularRowsLines(firstValue, header, depth + 2, options);
|
|
1307
|
+
if (restEntries.length > 0)
|
|
1308
|
+
yield* encodeObjectLines(Object.fromEntries(restEntries), depth + 1, options);
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
const encodedKey = encodeKey(firstKey);
|
|
1313
|
+
if (isJsonPrimitive(firstValue))
|
|
1314
|
+
yield indentedListItem(depth, `${encodedKey}: ${encodePrimitive(firstValue, options.delimiter)}`, options.indent);
|
|
1315
|
+
else if (isJsonArray(firstValue))
|
|
1316
|
+
if (firstValue.length === 0)
|
|
1317
|
+
yield indentedListItem(depth, `${encodedKey}${formatHeader(0, { delimiter: options.delimiter })}`, options.indent);
|
|
1318
|
+
else if (isArrayOfPrimitives(firstValue))
|
|
1319
|
+
yield indentedListItem(depth, `${encodedKey}${encodeInlineArrayLine(firstValue, options.delimiter)}`, options.indent);
|
|
1320
|
+
else {
|
|
1321
|
+
yield indentedListItem(depth, `${encodedKey}${formatHeader(firstValue.length, { delimiter: options.delimiter })}`, options.indent);
|
|
1322
|
+
for (const item of firstValue)
|
|
1323
|
+
yield* encodeListItemValueLines(item, depth + 2, options);
|
|
1324
|
+
}
|
|
1325
|
+
else if (isJsonObject(firstValue)) {
|
|
1326
|
+
yield indentedListItem(depth, `${encodedKey}:`, options.indent);
|
|
1327
|
+
if (!isEmptyObject(firstValue))
|
|
1328
|
+
yield* encodeObjectLines(firstValue, depth + 2, options);
|
|
1329
|
+
}
|
|
1330
|
+
if (restEntries.length > 0)
|
|
1331
|
+
yield* encodeObjectLines(Object.fromEntries(restEntries), depth + 1, options);
|
|
1332
|
+
}
|
|
1333
|
+
function* encodeListItemValueLines(value, depth, options) {
|
|
1334
|
+
if (isJsonPrimitive(value))
|
|
1335
|
+
yield indentedListItem(depth, encodePrimitive(value, options.delimiter), options.indent);
|
|
1336
|
+
else if (isJsonArray(value))
|
|
1337
|
+
if (isArrayOfPrimitives(value))
|
|
1338
|
+
yield indentedListItem(depth, encodeInlineArrayLine(value, options.delimiter), options.indent);
|
|
1339
|
+
else {
|
|
1340
|
+
yield indentedListItem(depth, formatHeader(value.length, { delimiter: options.delimiter }), options.indent);
|
|
1341
|
+
for (const item of value)
|
|
1342
|
+
yield* encodeListItemValueLines(item, depth + 1, options);
|
|
1343
|
+
}
|
|
1344
|
+
else if (isJsonObject(value))
|
|
1345
|
+
yield* encodeObjectAsListItemLines(value, depth, options);
|
|
1346
|
+
}
|
|
1347
|
+
function indentedLine(depth, content, indentSize) {
|
|
1348
|
+
return " ".repeat(indentSize * depth) + content;
|
|
1349
|
+
}
|
|
1350
|
+
function indentedListItem(depth, content, indentSize) {
|
|
1351
|
+
return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
|
|
1352
|
+
}
|
|
1353
|
+
function applyReplacer(root, replacer) {
|
|
1354
|
+
const replacedRoot = replacer("", root, []);
|
|
1355
|
+
if (replacedRoot === void 0)
|
|
1356
|
+
return transformChildren(root, replacer, []);
|
|
1357
|
+
return transformChildren(normalizeValue(replacedRoot), replacer, []);
|
|
1358
|
+
}
|
|
1359
|
+
function transformChildren(value, replacer, path) {
|
|
1360
|
+
if (isJsonObject(value))
|
|
1361
|
+
return transformObject(value, replacer, path);
|
|
1362
|
+
if (isJsonArray(value))
|
|
1363
|
+
return transformArray(value, replacer, path);
|
|
1364
|
+
return value;
|
|
1365
|
+
}
|
|
1366
|
+
function transformObject(obj, replacer, path) {
|
|
1367
|
+
const result = {};
|
|
1368
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1369
|
+
const childPath = [...path, key];
|
|
1370
|
+
const replacedValue = replacer(key, value, childPath);
|
|
1371
|
+
if (replacedValue === void 0)
|
|
1372
|
+
continue;
|
|
1373
|
+
result[key] = transformChildren(normalizeValue(replacedValue), replacer, childPath);
|
|
1374
|
+
}
|
|
1375
|
+
return result;
|
|
1376
|
+
}
|
|
1377
|
+
function transformArray(arr, replacer, path) {
|
|
1378
|
+
const result = [];
|
|
1379
|
+
for (let i = 0; i < arr.length; i++) {
|
|
1380
|
+
const value = arr[i];
|
|
1381
|
+
const childPath = [...path, i];
|
|
1382
|
+
const replacedValue = replacer(String(i), value, childPath);
|
|
1383
|
+
if (replacedValue === void 0)
|
|
1384
|
+
continue;
|
|
1385
|
+
const normalizedValue = normalizeValue(replacedValue);
|
|
1386
|
+
result.push(transformChildren(normalizedValue, replacer, childPath));
|
|
1387
|
+
}
|
|
1388
|
+
return result;
|
|
1389
|
+
}
|
|
1390
|
+
function encode(input, options) {
|
|
1391
|
+
return Array.from(encodeLines(input, options)).join("\n");
|
|
1392
|
+
}
|
|
1393
|
+
function decode(input, options) {
|
|
1394
|
+
return decodeFromLines(input.split("\n"), options);
|
|
1395
|
+
}
|
|
1396
|
+
function encodeLines(input, options) {
|
|
1397
|
+
const normalizedValue = normalizeValue(input);
|
|
1398
|
+
const resolvedOptions = resolveOptions(options);
|
|
1399
|
+
return encodeJsonValue(resolvedOptions.replacer ? applyReplacer(normalizedValue, resolvedOptions.replacer) : normalizedValue, resolvedOptions, 0);
|
|
1400
|
+
}
|
|
1401
|
+
function decodeFromLines(lines, options) {
|
|
1402
|
+
const resolvedOptions = resolveDecodeOptions(options);
|
|
1403
|
+
const decodedValue = buildValueFromEvents(decodeStreamSync$1(lines, {
|
|
1404
|
+
indent: resolvedOptions.indent,
|
|
1405
|
+
strict: resolvedOptions.strict
|
|
1406
|
+
}));
|
|
1407
|
+
if (resolvedOptions.expandPaths === "safe")
|
|
1408
|
+
return expandPathsSafe(decodedValue, resolvedOptions.strict);
|
|
1409
|
+
return decodedValue;
|
|
1410
|
+
}
|
|
1411
|
+
function resolveOptions(options) {
|
|
1412
|
+
return {
|
|
1413
|
+
indent: options?.indent ?? 2,
|
|
1414
|
+
delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
|
|
1415
|
+
keyFolding: options?.keyFolding ?? "off",
|
|
1416
|
+
flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY,
|
|
1417
|
+
replacer: options?.replacer
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
function resolveDecodeOptions(options) {
|
|
1421
|
+
return {
|
|
1422
|
+
indent: options?.indent ?? 2,
|
|
1423
|
+
strict: options?.strict ?? true,
|
|
1424
|
+
expandPaths: options?.expandPaths ?? "off"
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/commands/toon.ts
|
|
1429
|
+
async function toonEncode(args2) {
|
|
1430
|
+
const file = args2[0];
|
|
1431
|
+
if (!file || file === "--help" || file === "-h") {
|
|
1432
|
+
console.error(`USAGE: mid-tools toon <file|->`);
|
|
1433
|
+
process.exit(1);
|
|
1434
|
+
}
|
|
1435
|
+
let input;
|
|
1436
|
+
if (file === "-") {
|
|
1437
|
+
input = await readStdin();
|
|
1438
|
+
} else {
|
|
1439
|
+
input = (0, import_fs.readFileSync)(file, "utf-8");
|
|
1440
|
+
}
|
|
1441
|
+
const isToon = input.trim().split("\n")[0]?.includes(":") && !input.startsWith("{") && !input.startsWith("[");
|
|
1442
|
+
try {
|
|
1443
|
+
if (isToon) {
|
|
1444
|
+
const decoded = decode(input);
|
|
1445
|
+
console.log(JSON.stringify(decoded, null, 2));
|
|
1446
|
+
} else {
|
|
1447
|
+
const parsed = JSON.parse(input);
|
|
1448
|
+
const encoded = encode(parsed, {
|
|
1449
|
+
indent: 2
|
|
1450
|
+
});
|
|
1451
|
+
console.log(encoded);
|
|
1452
|
+
}
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
if (error instanceof SyntaxError) {
|
|
1455
|
+
console.error(`Parse error: ${error.message}`);
|
|
1456
|
+
} else if (error instanceof Error) {
|
|
1457
|
+
console.error(`Error: ${error.message}`);
|
|
1458
|
+
}
|
|
1459
|
+
process.exit(1);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
async function readStdin() {
|
|
1463
|
+
return new Promise((resolve3, reject) => {
|
|
1464
|
+
let data = "";
|
|
1465
|
+
process.stdin.setEncoding("utf8");
|
|
1466
|
+
process.stdin.on("readable", () => {
|
|
1467
|
+
let chunk;
|
|
1468
|
+
while ((chunk = process.stdin.read()) !== null) {
|
|
1469
|
+
data += chunk;
|
|
1470
|
+
}
|
|
1471
|
+
});
|
|
1472
|
+
process.stdin.on("end", () => {
|
|
1473
|
+
resolve3(data);
|
|
1474
|
+
});
|
|
1475
|
+
process.stdin.on("error", reject);
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
// src/commands/state.ts
|
|
1480
|
+
var import_fs3 = require("fs");
|
|
1481
|
+
|
|
1482
|
+
// src/utils.ts
|
|
1483
|
+
var import_fs2 = require("fs");
|
|
1484
|
+
var import_path = require("path");
|
|
1485
|
+
var import_process = require("process");
|
|
1486
|
+
function getPlanningDir() {
|
|
1487
|
+
const local = (0, import_path.resolve)((0, import_process.cwd)(), ".planning");
|
|
1488
|
+
if ((0, import_fs2.existsSync)(local)) {
|
|
1489
|
+
return local;
|
|
1490
|
+
}
|
|
1491
|
+
return local;
|
|
1492
|
+
}
|
|
1493
|
+
function getConfigPath() {
|
|
1494
|
+
return (0, import_path.resolve)(getPlanningDir(), "config.json");
|
|
1495
|
+
}
|
|
1496
|
+
function getStateFile() {
|
|
1497
|
+
return (0, import_path.resolve)(getPlanningDir(), "STATE.md");
|
|
1498
|
+
}
|
|
1499
|
+
function getRoadmapFile() {
|
|
1500
|
+
return (0, import_path.resolve)(getPlanningDir(), "ROADMAP.md");
|
|
1501
|
+
}
|
|
1502
|
+
function readJSON(path) {
|
|
1503
|
+
if (!(0, import_fs2.existsSync)(path)) {
|
|
1504
|
+
return {};
|
|
1505
|
+
}
|
|
1506
|
+
try {
|
|
1507
|
+
return JSON.parse((0, import_fs2.readFileSync)(path, "utf-8"));
|
|
1508
|
+
} catch {
|
|
1509
|
+
return {};
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
function extractFrontmatter(content) {
|
|
1513
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1514
|
+
if (!match) {
|
|
1515
|
+
return {};
|
|
1516
|
+
}
|
|
1517
|
+
const fm = {};
|
|
1518
|
+
const lines = match[1].split("\n");
|
|
1519
|
+
for (const line of lines) {
|
|
1520
|
+
const colonIndex = line.indexOf(":");
|
|
1521
|
+
if (colonIndex === -1)
|
|
1522
|
+
continue;
|
|
1523
|
+
const key = line.substring(0, colonIndex).trim();
|
|
1524
|
+
const value = line.substring(colonIndex + 1).trim();
|
|
1525
|
+
if (value === "true") {
|
|
1526
|
+
fm[key] = true;
|
|
1527
|
+
} else if (value === "false") {
|
|
1528
|
+
fm[key] = false;
|
|
1529
|
+
} else if (value === "null" || value === "") {
|
|
1530
|
+
fm[key] = null;
|
|
1531
|
+
} else if (/^\d+$/.test(value)) {
|
|
1532
|
+
fm[key] = parseInt(value, 10);
|
|
1533
|
+
} else if (/^\d+\.\d+$/.test(value)) {
|
|
1534
|
+
fm[key] = parseFloat(value);
|
|
1535
|
+
} else if (value.startsWith("[") && value.endsWith("]")) {
|
|
1536
|
+
try {
|
|
1537
|
+
fm[key] = JSON.parse(value);
|
|
1538
|
+
} catch {
|
|
1539
|
+
fm[key] = value;
|
|
1540
|
+
}
|
|
1541
|
+
} else {
|
|
1542
|
+
fm[key] = value.replace(/^["']|["']$/g, "");
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
return fm;
|
|
1546
|
+
}
|
|
1547
|
+
function getContextWindow() {
|
|
1548
|
+
try {
|
|
1549
|
+
const config = readJSON(getConfigPath());
|
|
1550
|
+
return config.context_window || 2e5;
|
|
1551
|
+
} catch {
|
|
1552
|
+
return 2e5;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
function getModelProfile() {
|
|
1556
|
+
try {
|
|
1557
|
+
const config = readJSON(getConfigPath());
|
|
1558
|
+
return config.model_profile || "balanced";
|
|
1559
|
+
} catch {
|
|
1560
|
+
return "balanced";
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// src/commands/state.ts
|
|
1565
|
+
function stateCommand(args2) {
|
|
1566
|
+
const subcommand = args2[0];
|
|
1567
|
+
switch (subcommand) {
|
|
1568
|
+
case "get":
|
|
1569
|
+
stateGet();
|
|
1570
|
+
break;
|
|
1571
|
+
case "set":
|
|
1572
|
+
stateSet(args2[1], args2.slice(2).join(" "));
|
|
1573
|
+
break;
|
|
1574
|
+
case "advance":
|
|
1575
|
+
stateAdvance(args2[1]);
|
|
1576
|
+
break;
|
|
1577
|
+
default:
|
|
1578
|
+
console.error(`Unknown state command: ${subcommand}`);
|
|
1579
|
+
console.error(`Usage: mid-tools state <get|set|advance>`);
|
|
1580
|
+
process.exit(1);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
function stateGet() {
|
|
1584
|
+
const stateFile = getStateFile();
|
|
1585
|
+
if (!(0, import_fs3.existsSync)(stateFile)) {
|
|
1586
|
+
console.log(`phase_number: 0
|
|
1587
|
+
status: uninitialized`);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
const content = (0, import_fs3.readFileSync)(stateFile, "utf-8");
|
|
1591
|
+
const fm = extractFrontmatter(content);
|
|
1592
|
+
const toon = encode(fm, { keyFolding: "safe", indent: 2 });
|
|
1593
|
+
console.log(toon);
|
|
1594
|
+
}
|
|
1595
|
+
function stateSet(key, value) {
|
|
1596
|
+
const stateFile = getStateFile();
|
|
1597
|
+
const planningDir = getPlanningDir();
|
|
1598
|
+
if (!(0, import_fs3.existsSync)(planningDir)) {
|
|
1599
|
+
(0, import_fs3.mkdirSync)(planningDir, { recursive: true });
|
|
1600
|
+
}
|
|
1601
|
+
let content = "";
|
|
1602
|
+
let fm = {};
|
|
1603
|
+
if ((0, import_fs3.existsSync)(stateFile)) {
|
|
1604
|
+
content = (0, import_fs3.readFileSync)(stateFile, "utf-8");
|
|
1605
|
+
fm = extractFrontmatter(content);
|
|
1606
|
+
const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
1607
|
+
content = match ? match[1] : "";
|
|
1608
|
+
}
|
|
1609
|
+
let parsedValue = value;
|
|
1610
|
+
if (value === "true")
|
|
1611
|
+
parsedValue = true;
|
|
1612
|
+
else if (value === "false")
|
|
1613
|
+
parsedValue = false;
|
|
1614
|
+
else if (value === "null")
|
|
1615
|
+
parsedValue = null;
|
|
1616
|
+
else if (/^\d+$/.test(value))
|
|
1617
|
+
parsedValue = parseInt(value, 10);
|
|
1618
|
+
else if (/^\d+\.\d+$/.test(value))
|
|
1619
|
+
parsedValue = parseFloat(value);
|
|
1620
|
+
else if ((value.startsWith("[") || value.startsWith("{")) && value.endsWith("]") || value.endsWith("}")) {
|
|
1621
|
+
try {
|
|
1622
|
+
parsedValue = JSON.parse(value);
|
|
1623
|
+
} catch {
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
fm[key] = parsedValue;
|
|
1627
|
+
const fmLines = Object.entries(fm).map(([k, v]) => {
|
|
1628
|
+
if (typeof v === "string") {
|
|
1629
|
+
return `${k}: ${v}`;
|
|
1630
|
+
} else if (v === null) {
|
|
1631
|
+
return `${k}:`;
|
|
1632
|
+
} else if (typeof v === "object") {
|
|
1633
|
+
return `${k}: ${JSON.stringify(v)}`;
|
|
1634
|
+
} else {
|
|
1635
|
+
return `${k}: ${String(v)}`;
|
|
1636
|
+
}
|
|
1637
|
+
});
|
|
1638
|
+
const newContent = `---
|
|
1639
|
+
${fmLines.join("\n")}
|
|
1640
|
+
---
|
|
1641
|
+
${content}`;
|
|
1642
|
+
(0, import_fs3.writeFileSync)(stateFile, newContent, "utf-8");
|
|
1643
|
+
}
|
|
1644
|
+
function stateAdvance(phase) {
|
|
1645
|
+
const stateFile = getStateFile();
|
|
1646
|
+
if (!(0, import_fs3.existsSync)(stateFile)) {
|
|
1647
|
+
console.error(`STATE.md not found at ${stateFile}`);
|
|
1648
|
+
process.exit(1);
|
|
1649
|
+
}
|
|
1650
|
+
stateSet("last_completed_phase", phase);
|
|
1651
|
+
console.log(`Advanced to phase: ${phase}`);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// src/commands/frontmatter.ts
|
|
1655
|
+
var import_fs4 = require("fs");
|
|
1656
|
+
function frontmatterCommand(args2) {
|
|
1657
|
+
const subcommand = args2[0];
|
|
1658
|
+
switch (subcommand) {
|
|
1659
|
+
case "get":
|
|
1660
|
+
frontmatterGet(args2.slice(1));
|
|
1661
|
+
break;
|
|
1662
|
+
case "list":
|
|
1663
|
+
frontmatterList(args2[1]);
|
|
1664
|
+
break;
|
|
1665
|
+
default:
|
|
1666
|
+
console.error(`Unknown frontmatter command: ${subcommand}`);
|
|
1667
|
+
console.error(`Usage: mid-tools fm <get|list>`);
|
|
1668
|
+
process.exit(1);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
function frontmatterGet(args2) {
|
|
1672
|
+
const file = args2[0];
|
|
1673
|
+
let field = null;
|
|
1674
|
+
for (let i = 0; i < args2.length; i++) {
|
|
1675
|
+
if (args2[i] === "--field" && i + 1 < args2.length) {
|
|
1676
|
+
field = args2[i + 1];
|
|
1677
|
+
break;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
if (!file) {
|
|
1681
|
+
console.error(`Usage: mid-tools fm get <file> --field <fieldname>`);
|
|
1682
|
+
process.exit(1);
|
|
1683
|
+
}
|
|
1684
|
+
if (!(0, import_fs4.existsSync)(file)) {
|
|
1685
|
+
console.error(`File not found: ${file}`);
|
|
1686
|
+
process.exit(1);
|
|
1687
|
+
}
|
|
1688
|
+
const content = (0, import_fs4.readFileSync)(file, "utf-8");
|
|
1689
|
+
const fm = extractFrontmatter(content);
|
|
1690
|
+
if (!field) {
|
|
1691
|
+
console.error(`Usage: mid-tools fm get <file> --field <fieldname>`);
|
|
1692
|
+
process.exit(1);
|
|
1693
|
+
}
|
|
1694
|
+
const value = fm[field];
|
|
1695
|
+
if (value === void 0) {
|
|
1696
|
+
console.log(``);
|
|
1697
|
+
} else if (typeof value === "object") {
|
|
1698
|
+
console.log(JSON.stringify(value));
|
|
1699
|
+
} else {
|
|
1700
|
+
console.log(String(value));
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
function frontmatterList(file) {
|
|
1704
|
+
if (!file) {
|
|
1705
|
+
console.error(`Usage: mid-tools fm list <file>`);
|
|
1706
|
+
process.exit(1);
|
|
1707
|
+
}
|
|
1708
|
+
if (!(0, import_fs4.existsSync)(file)) {
|
|
1709
|
+
console.error(`File not found: ${file}`);
|
|
1710
|
+
process.exit(1);
|
|
1711
|
+
}
|
|
1712
|
+
const content = (0, import_fs4.readFileSync)(file, "utf-8");
|
|
1713
|
+
const fm = extractFrontmatter(content);
|
|
1714
|
+
const toon = encode(fm, { keyFolding: "safe", indent: 2 });
|
|
1715
|
+
console.log(toon);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// src/commands/roadmap.ts
|
|
1719
|
+
var import_fs5 = require("fs");
|
|
1720
|
+
function roadmapCommand(args2) {
|
|
1721
|
+
const subcommand = args2[0];
|
|
1722
|
+
switch (subcommand) {
|
|
1723
|
+
case "phases":
|
|
1724
|
+
roadmapPhases();
|
|
1725
|
+
break;
|
|
1726
|
+
case "current":
|
|
1727
|
+
roadmapCurrent();
|
|
1728
|
+
break;
|
|
1729
|
+
case "phase":
|
|
1730
|
+
roadmapPhase(args2[1]);
|
|
1731
|
+
break;
|
|
1732
|
+
default:
|
|
1733
|
+
console.error(`Unknown roadmap command: ${subcommand}`);
|
|
1734
|
+
console.error(`Usage: mid-tools roadmap <phases|current|phase>`);
|
|
1735
|
+
process.exit(1);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
function roadmapPhases() {
|
|
1739
|
+
const roadmapFile = getRoadmapFile();
|
|
1740
|
+
if (!(0, import_fs5.existsSync)(roadmapFile)) {
|
|
1741
|
+
console.log(`phases[0]:`);
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
const content = (0, import_fs5.readFileSync)(roadmapFile, "utf-8");
|
|
1745
|
+
const phases = extractPhases(content);
|
|
1746
|
+
const toon = encode({ phases }, { keyFolding: "safe", indent: 2 });
|
|
1747
|
+
console.log(toon);
|
|
1748
|
+
}
|
|
1749
|
+
function roadmapCurrent() {
|
|
1750
|
+
const roadmapFile = getRoadmapFile();
|
|
1751
|
+
if (!(0, import_fs5.existsSync)(roadmapFile)) {
|
|
1752
|
+
console.log(`phase_number: 0
|
|
1753
|
+
phase_name: ""
|
|
1754
|
+
status: pending`);
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
const content = (0, import_fs5.readFileSync)(roadmapFile, "utf-8");
|
|
1758
|
+
const phases = extractPhases(content);
|
|
1759
|
+
const currentPhase = phases.find((p) => p.status !== "complete") || phases[0];
|
|
1760
|
+
const toon = encode(currentPhase || {}, { keyFolding: "safe", indent: 2 });
|
|
1761
|
+
console.log(toon);
|
|
1762
|
+
}
|
|
1763
|
+
function roadmapPhase(phaseNumber) {
|
|
1764
|
+
if (!phaseNumber) {
|
|
1765
|
+
console.error(`Usage: mid-tools roadmap phase <number>`);
|
|
1766
|
+
process.exit(1);
|
|
1767
|
+
}
|
|
1768
|
+
const roadmapFile = getRoadmapFile();
|
|
1769
|
+
if (!(0, import_fs5.existsSync)(roadmapFile)) {
|
|
1770
|
+
console.error(`ROADMAP.md not found at ${roadmapFile}`);
|
|
1771
|
+
process.exit(1);
|
|
1772
|
+
}
|
|
1773
|
+
const content = (0, import_fs5.readFileSync)(roadmapFile, "utf-8");
|
|
1774
|
+
const phases = extractPhases(content);
|
|
1775
|
+
const num = parseInt(phaseNumber, 10);
|
|
1776
|
+
const phase = phases.find((p) => p.number === num);
|
|
1777
|
+
if (!phase) {
|
|
1778
|
+
console.error(`Phase ${num} not found`);
|
|
1779
|
+
process.exit(1);
|
|
1780
|
+
}
|
|
1781
|
+
const toon = encode(phase, { keyFolding: "safe", indent: 2 });
|
|
1782
|
+
console.log(toon);
|
|
1783
|
+
}
|
|
1784
|
+
function extractPhases(content) {
|
|
1785
|
+
const phases = [];
|
|
1786
|
+
const lines = content.split("\n");
|
|
1787
|
+
let phaseNumber = 0;
|
|
1788
|
+
let phaseName = "";
|
|
1789
|
+
let phaseStatus = "pending";
|
|
1790
|
+
for (const line of lines) {
|
|
1791
|
+
const phaseMatch = line.match(/^##\s+(\d+)\s*-\s*([^[\]]+)\s*\[([^\]]+)\]?/);
|
|
1792
|
+
if (phaseMatch) {
|
|
1793
|
+
if (phaseName) {
|
|
1794
|
+
phases.push({
|
|
1795
|
+
number: phaseNumber,
|
|
1796
|
+
name: phaseName,
|
|
1797
|
+
status: phaseStatus
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
phaseNumber = parseInt(phaseMatch[1], 10);
|
|
1801
|
+
phaseName = phaseMatch[2].trim();
|
|
1802
|
+
phaseStatus = phaseMatch[3]?.toLowerCase() || "pending";
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
if (phaseName) {
|
|
1806
|
+
phases.push({
|
|
1807
|
+
number: phaseNumber,
|
|
1808
|
+
name: phaseName,
|
|
1809
|
+
status: phaseStatus
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
return phases;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// src/commands/config.ts
|
|
1816
|
+
var import_fs6 = require("fs");
|
|
1817
|
+
function configCommand(args2) {
|
|
1818
|
+
const subcommand = args2[0];
|
|
1819
|
+
switch (subcommand) {
|
|
1820
|
+
case "get":
|
|
1821
|
+
configGet(args2[1]);
|
|
1822
|
+
break;
|
|
1823
|
+
case "set":
|
|
1824
|
+
configSet(args2[1], args2.slice(2).join(" "));
|
|
1825
|
+
break;
|
|
1826
|
+
default:
|
|
1827
|
+
console.error(`Unknown config command: ${subcommand}`);
|
|
1828
|
+
console.error(`Usage: mid-tools config <get|set>`);
|
|
1829
|
+
process.exit(1);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
function configGet(key) {
|
|
1833
|
+
if (!key) {
|
|
1834
|
+
console.error(`Usage: mid-tools config get <key>`);
|
|
1835
|
+
process.exit(1);
|
|
1836
|
+
}
|
|
1837
|
+
const config = readJSON(getConfigPath());
|
|
1838
|
+
const value = config[key];
|
|
1839
|
+
if (value === void 0) {
|
|
1840
|
+
console.log(``);
|
|
1841
|
+
} else if (typeof value === "object") {
|
|
1842
|
+
console.log(JSON.stringify(value));
|
|
1843
|
+
} else {
|
|
1844
|
+
console.log(String(value));
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
function configSet(key, value) {
|
|
1848
|
+
if (!key) {
|
|
1849
|
+
console.error(`Usage: mid-tools config set <key> <value>`);
|
|
1850
|
+
process.exit(1);
|
|
1851
|
+
}
|
|
1852
|
+
const configPath = getConfigPath();
|
|
1853
|
+
const planningDir = getPlanningDir();
|
|
1854
|
+
if (!(0, import_fs6.existsSync)(planningDir)) {
|
|
1855
|
+
(0, import_fs6.mkdirSync)(planningDir, { recursive: true });
|
|
1856
|
+
}
|
|
1857
|
+
const config = readJSON(configPath);
|
|
1858
|
+
let parsedValue = value;
|
|
1859
|
+
if (value === "true")
|
|
1860
|
+
parsedValue = true;
|
|
1861
|
+
else if (value === "false")
|
|
1862
|
+
parsedValue = false;
|
|
1863
|
+
else if (value === "null")
|
|
1864
|
+
parsedValue = null;
|
|
1865
|
+
else if (/^\d+$/.test(value))
|
|
1866
|
+
parsedValue = parseInt(value, 10);
|
|
1867
|
+
else if (/^\d+\.\d+$/.test(value))
|
|
1868
|
+
parsedValue = parseFloat(value);
|
|
1869
|
+
else if ((value.startsWith("[") || value.startsWith("{")) && (value.endsWith("]") || value.endsWith("}"))) {
|
|
1870
|
+
try {
|
|
1871
|
+
parsedValue = JSON.parse(value);
|
|
1872
|
+
} catch {
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
config[key] = parsedValue;
|
|
1876
|
+
(0, import_fs6.writeFileSync)(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// src/commands/init.ts
|
|
1880
|
+
var import_fs7 = require("fs");
|
|
1881
|
+
var import_path2 = require("path");
|
|
1882
|
+
async function initCommand(args2) {
|
|
1883
|
+
const workflow = args2[0];
|
|
1884
|
+
const phaseArg = args2[1];
|
|
1885
|
+
if (!workflow) {
|
|
1886
|
+
console.error(`Usage: mid-tools init <workflow> [phase]`);
|
|
1887
|
+
process.exit(1);
|
|
1888
|
+
}
|
|
1889
|
+
const payload = getInitPayload(workflow, phaseArg);
|
|
1890
|
+
const toon = encode(payload, { keyFolding: "safe", indent: 2 });
|
|
1891
|
+
console.log(toon);
|
|
1892
|
+
}
|
|
1893
|
+
function getInitPayload(workflow, phaseArg) {
|
|
1894
|
+
const planningDir = getPlanningDir();
|
|
1895
|
+
const contextWindow = getContextWindow();
|
|
1896
|
+
const modelProfile = getModelProfile();
|
|
1897
|
+
let phaseNumber = 1;
|
|
1898
|
+
if (phaseArg) {
|
|
1899
|
+
phaseNumber = parseInt(phaseArg, 10) || 1;
|
|
1900
|
+
}
|
|
1901
|
+
const phaseName = `Phase ${phaseNumber}`;
|
|
1902
|
+
const phaseDir = (0, import_path2.resolve)(planningDir, `phases`, `0${phaseNumber}`);
|
|
1903
|
+
const branchName = `phase-${phaseNumber}`;
|
|
1904
|
+
const { executor, verifier } = routeModels(modelProfile);
|
|
1905
|
+
const { plans, incompletePlans } = getPlanList(phaseDir);
|
|
1906
|
+
const parallelization = incompletePlans.length > 1;
|
|
1907
|
+
return {
|
|
1908
|
+
workflow,
|
|
1909
|
+
phase_number: phaseNumber,
|
|
1910
|
+
phase_name: phaseName,
|
|
1911
|
+
phase_dir: phaseDir,
|
|
1912
|
+
plans,
|
|
1913
|
+
incomplete_plans: incompletePlans,
|
|
1914
|
+
branch_name: branchName,
|
|
1915
|
+
parallelization,
|
|
1916
|
+
executor_model: executor,
|
|
1917
|
+
verifier_model: verifier,
|
|
1918
|
+
context_window: contextWindow,
|
|
1919
|
+
model_profile: modelProfile
|
|
1920
|
+
};
|
|
1921
|
+
}
|
|
1922
|
+
function routeModels(profile) {
|
|
1923
|
+
switch (profile) {
|
|
1924
|
+
case "budget":
|
|
1925
|
+
return { executor: "claude-haiku-4-5", verifier: "claude-haiku-4-5" };
|
|
1926
|
+
case "balanced":
|
|
1927
|
+
return { executor: "claude-sonnet-4-6", verifier: "claude-haiku-4-5" };
|
|
1928
|
+
case "quality":
|
|
1929
|
+
return { executor: "claude-opus-4-6", verifier: "claude-sonnet-4-6" };
|
|
1930
|
+
default:
|
|
1931
|
+
return { executor: "claude-sonnet-4-6", verifier: "claude-haiku-4-5" };
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
function getPlanList(phaseDir) {
|
|
1935
|
+
const plans = [];
|
|
1936
|
+
const incompletePlans = [];
|
|
1937
|
+
if (!(0, import_fs7.existsSync)(phaseDir)) {
|
|
1938
|
+
return { plans, incompletePlans };
|
|
1939
|
+
}
|
|
1940
|
+
const fs = require("fs");
|
|
1941
|
+
try {
|
|
1942
|
+
const files = fs.readdirSync(phaseDir);
|
|
1943
|
+
const planFiles = files.filter((f) => f.match(/^\d+-\d+-PLAN\.md$/));
|
|
1944
|
+
for (const file of planFiles) {
|
|
1945
|
+
plans.push(file);
|
|
1946
|
+
const summaryFile = file.replace("PLAN", "SUMMARY");
|
|
1947
|
+
if (!files.includes(summaryFile)) {
|
|
1948
|
+
incompletePlans.push(file);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
} catch {
|
|
1952
|
+
}
|
|
1953
|
+
return { plans, incompletePlans };
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
// src/index.ts
|
|
1957
|
+
var args = process.argv.slice(2);
|
|
1958
|
+
async function main() {
|
|
1959
|
+
if (args.length === 0) {
|
|
1960
|
+
printHelp();
|
|
1961
|
+
process.exit(0);
|
|
1962
|
+
}
|
|
1963
|
+
const command = args[0];
|
|
1964
|
+
const commandArgs = args.slice(1);
|
|
1965
|
+
try {
|
|
1966
|
+
switch (command) {
|
|
1967
|
+
case "toon":
|
|
1968
|
+
await toonEncode(commandArgs);
|
|
1969
|
+
break;
|
|
1970
|
+
case "init":
|
|
1971
|
+
await initCommand(commandArgs);
|
|
1972
|
+
break;
|
|
1973
|
+
case "state":
|
|
1974
|
+
stateCommand(commandArgs);
|
|
1975
|
+
break;
|
|
1976
|
+
case "fm":
|
|
1977
|
+
case "frontmatter":
|
|
1978
|
+
frontmatterCommand(commandArgs);
|
|
1979
|
+
break;
|
|
1980
|
+
case "roadmap":
|
|
1981
|
+
roadmapCommand(commandArgs);
|
|
1982
|
+
break;
|
|
1983
|
+
case "config":
|
|
1984
|
+
configCommand(commandArgs);
|
|
1985
|
+
break;
|
|
1986
|
+
case "help":
|
|
1987
|
+
case "--help":
|
|
1988
|
+
case "-h":
|
|
1989
|
+
printHelp();
|
|
1990
|
+
break;
|
|
1991
|
+
default:
|
|
1992
|
+
console.error(`Unknown command: ${command}`);
|
|
1993
|
+
printHelp();
|
|
1994
|
+
process.exit(1);
|
|
1995
|
+
}
|
|
1996
|
+
} catch (error) {
|
|
1997
|
+
if (error instanceof Error) {
|
|
1998
|
+
console.error(`Error: ${error.message}`);
|
|
1999
|
+
} else {
|
|
2000
|
+
console.error(`Unknown error: ${error}`);
|
|
2001
|
+
}
|
|
2002
|
+
process.exit(1);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
function printHelp() {
|
|
2006
|
+
console.log(`
|
|
2007
|
+
makeitdone tools \u2014 token-optimized utility for Claude Code orchestration
|
|
2008
|
+
|
|
2009
|
+
USAGE:
|
|
2010
|
+
mid-tools <command> [options]
|
|
2011
|
+
|
|
2012
|
+
COMMANDS:
|
|
2013
|
+
toon <file|-> Convert JSON to TOON or vice versa
|
|
2014
|
+
Use '-' for stdin
|
|
2015
|
+
|
|
2016
|
+
init <workflow> [phase] Get workflow initialization payload (returns TOON)
|
|
2017
|
+
|
|
2018
|
+
state <subcommand> STATE.md operations
|
|
2019
|
+
get Read STATE.md as TOON
|
|
2020
|
+
set <key> <value> Atomic field update
|
|
2021
|
+
advance <phase> Mark phase as complete
|
|
2022
|
+
|
|
2023
|
+
fm|frontmatter <subcommand> YAML frontmatter operations
|
|
2024
|
+
get <file> --field <f> Extract single frontmatter field
|
|
2025
|
+
list <file> List all frontmatter fields (TOON)
|
|
2026
|
+
|
|
2027
|
+
roadmap <subcommand> ROADMAP.md queries
|
|
2028
|
+
phases List all phases (TOON)
|
|
2029
|
+
current Current active phase (TOON)
|
|
2030
|
+
phase <n> Get phase details by number (TOON)
|
|
2031
|
+
|
|
2032
|
+
config <subcommand> .planning/config.json
|
|
2033
|
+
get <key> Read config value
|
|
2034
|
+
set <key> <value> Write config value
|
|
2035
|
+
|
|
2036
|
+
help Show this help
|
|
2037
|
+
|
|
2038
|
+
EXAMPLES:
|
|
2039
|
+
node mid-tools.cjs toon data.json
|
|
2040
|
+
node mid-tools.cjs init execute 1
|
|
2041
|
+
node mid-tools.cjs state get
|
|
2042
|
+
node mid-tools.cjs roadmap phases
|
|
2043
|
+
`);
|
|
2044
|
+
}
|
|
2045
|
+
main().catch((err) => {
|
|
2046
|
+
console.error(err);
|
|
2047
|
+
process.exit(1);
|
|
2048
|
+
});
|