mustflow 2.74.7 → 2.75.1

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.
@@ -0,0 +1,354 @@
1
+ import { printUsageError, renderHelp } from '../lib/cli-output.js';
2
+ import { t } from '../lib/i18n.js';
3
+ import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
4
+ import { resolveMustflowRoot } from '../lib/project-root.js';
5
+ import { CODE_OUTLINE_SCRIPT_REF, CODE_SYMBOL_READ_SCRIPT_REF, inspectCodeOutline, readCodeSymbol, } from '../../core/code-outline.js';
6
+ const CODE_OUTLINE_OPTIONS = [
7
+ { name: '--json', kind: 'boolean' },
8
+ { name: '--max-files', kind: 'string' },
9
+ { name: '--max-file-bytes', kind: 'string' },
10
+ ];
11
+ const CODE_SYMBOL_READ_OPTIONS = [
12
+ { name: '--json', kind: 'boolean' },
13
+ { name: '--start-line', kind: 'string' },
14
+ { name: '--end-line', kind: 'string' },
15
+ { name: '--context-lines', kind: 'string' },
16
+ { name: '--max-file-bytes', kind: 'string' },
17
+ { name: '--max-snippet-lines', kind: 'string' },
18
+ ];
19
+ function parsePositiveInteger(value, option, lang) {
20
+ if (value === null) {
21
+ return { value: null };
22
+ }
23
+ if (!/^[1-9]\d*$/u.test(value)) {
24
+ return { value: null, error: t(lang, 'codeOutline.error.invalidPositiveInteger', { option, value }) };
25
+ }
26
+ const parsed = Number(value);
27
+ if (!Number.isSafeInteger(parsed)) {
28
+ return { value: null, error: t(lang, 'codeOutline.error.invalidPositiveInteger', { option, value }) };
29
+ }
30
+ return { value: parsed };
31
+ }
32
+ function parseNonNegativeInteger(value, option, lang) {
33
+ if (value === null) {
34
+ return { value: null };
35
+ }
36
+ if (!/^(?:0|[1-9]\d*)$/u.test(value)) {
37
+ return { value: null, error: t(lang, 'codeOutline.error.invalidNonNegativeInteger', { option, value }) };
38
+ }
39
+ const parsed = Number(value);
40
+ if (!Number.isSafeInteger(parsed)) {
41
+ return { value: null, error: t(lang, 'codeOutline.error.invalidNonNegativeInteger', { option, value }) };
42
+ }
43
+ return { value: parsed };
44
+ }
45
+ export function getCodeOutlineHelp(lang = 'en') {
46
+ return renderHelp({
47
+ usage: 'mf script-pack run code/outline scan <path...> [options]',
48
+ summary: t(lang, 'codeOutline.help.summary'),
49
+ options: [
50
+ { label: '--max-files <count>', description: t(lang, 'codeOutline.help.option.maxFiles') },
51
+ { label: '--max-file-bytes <bytes>', description: t(lang, 'codeOutline.help.option.maxFileBytes') },
52
+ { label: '--json', description: t(lang, 'cli.option.json') },
53
+ { label: '-h, --help', description: t(lang, 'cli.option.help') },
54
+ ],
55
+ examples: [
56
+ 'mf script-pack run code/outline scan src --json',
57
+ 'mf script-pack run code/outline scan src/cli/commands/script-pack.ts --max-files 20',
58
+ 'mf script-pack run code/outline scan src tests --max-file-bytes 262144 --json',
59
+ ],
60
+ exitCodes: [
61
+ { label: '0', description: t(lang, 'codeOutline.help.exit.ok') },
62
+ { label: '1', description: t(lang, 'codeOutline.help.exit.fail') },
63
+ ],
64
+ }, lang);
65
+ }
66
+ export function getCodeSymbolReadHelp(lang = 'en') {
67
+ return renderHelp({
68
+ usage: 'mf script-pack run code/symbol-read read <path> --start-line <line> [options]',
69
+ summary: t(lang, 'codeSymbolRead.help.summary'),
70
+ options: [
71
+ { label: '--start-line <line>', description: t(lang, 'codeSymbolRead.help.option.startLine') },
72
+ { label: '--end-line <line>', description: t(lang, 'codeSymbolRead.help.option.endLine') },
73
+ { label: '--context-lines <count>', description: t(lang, 'codeSymbolRead.help.option.contextLines') },
74
+ { label: '--max-file-bytes <bytes>', description: t(lang, 'codeOutline.help.option.maxFileBytes') },
75
+ { label: '--max-snippet-lines <count>', description: t(lang, 'codeSymbolRead.help.option.maxSnippetLines') },
76
+ { label: '--json', description: t(lang, 'cli.option.json') },
77
+ { label: '-h, --help', description: t(lang, 'cli.option.help') },
78
+ ],
79
+ examples: [
80
+ 'mf script-pack run code/symbol-read read src/cli/commands/script-pack.ts --start-line 100',
81
+ 'mf script-pack run code/symbol-read read src/core/code-outline.ts --start-line 320 --context-lines 2 --json',
82
+ 'mf script-pack run code/symbol-read read src/core/code-outline.ts --start-line 1 --end-line 40 --json',
83
+ ],
84
+ exitCodes: [
85
+ { label: '0', description: t(lang, 'codeSymbolRead.help.exit.ok') },
86
+ { label: '1', description: t(lang, 'codeSymbolRead.help.exit.fail') },
87
+ ],
88
+ }, lang);
89
+ }
90
+ function parseCodeOutlineOptions(args, lang) {
91
+ const [action, ...rest] = args;
92
+ const parsed = parseCliOptions(rest, CODE_OUTLINE_OPTIONS, { allowPositionals: true });
93
+ const json = hasParsedCliOption(parsed, '--json');
94
+ const maxFiles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-files'), '--max-files', lang);
95
+ const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
96
+ if (action !== 'scan') {
97
+ return {
98
+ action: 'scan',
99
+ json,
100
+ paths: parsed.positionals,
101
+ maxFiles: maxFiles.value,
102
+ maxFileBytes: maxFileBytes.value,
103
+ error: action ? t(lang, 'codeOutline.error.unknownAction', { action }) : t(lang, 'codeOutline.error.missingAction'),
104
+ };
105
+ }
106
+ if (parsed.error) {
107
+ return {
108
+ action,
109
+ json,
110
+ paths: parsed.positionals,
111
+ maxFiles: maxFiles.value,
112
+ maxFileBytes: maxFileBytes.value,
113
+ error: formatCliOptionParseError(parsed.error, lang),
114
+ };
115
+ }
116
+ for (const candidate of [maxFiles, maxFileBytes]) {
117
+ if (candidate.error) {
118
+ return {
119
+ action,
120
+ json,
121
+ paths: parsed.positionals,
122
+ maxFiles: maxFiles.value,
123
+ maxFileBytes: maxFileBytes.value,
124
+ error: candidate.error,
125
+ };
126
+ }
127
+ }
128
+ if (parsed.positionals.length === 0) {
129
+ return {
130
+ action,
131
+ json,
132
+ paths: parsed.positionals,
133
+ maxFiles: maxFiles.value,
134
+ maxFileBytes: maxFileBytes.value,
135
+ error: t(lang, 'codeOutline.error.missingPath'),
136
+ };
137
+ }
138
+ return {
139
+ action,
140
+ json,
141
+ paths: parsed.positionals,
142
+ maxFiles: maxFiles.value,
143
+ maxFileBytes: maxFileBytes.value,
144
+ };
145
+ }
146
+ function parseCodeSymbolReadOptions(args, lang) {
147
+ const [action, ...rest] = args;
148
+ const parsed = parseCliOptions(rest, CODE_SYMBOL_READ_OPTIONS, { allowPositionals: true });
149
+ const json = hasParsedCliOption(parsed, '--json');
150
+ const startLine = parsePositiveInteger(getParsedCliStringOption(parsed, '--start-line'), '--start-line', lang);
151
+ const endLine = parsePositiveInteger(getParsedCliStringOption(parsed, '--end-line'), '--end-line', lang);
152
+ const contextLines = parseNonNegativeInteger(getParsedCliStringOption(parsed, '--context-lines'), '--context-lines', lang);
153
+ const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
154
+ const maxSnippetLines = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-snippet-lines'), '--max-snippet-lines', lang);
155
+ const inputPath = parsed.positionals[0] ?? null;
156
+ if (action !== 'read') {
157
+ return {
158
+ action: 'read',
159
+ json,
160
+ path: inputPath,
161
+ startLine: startLine.value,
162
+ endLine: endLine.value,
163
+ contextLines: contextLines.value,
164
+ maxFileBytes: maxFileBytes.value,
165
+ maxSnippetLines: maxSnippetLines.value,
166
+ error: action ? t(lang, 'codeSymbolRead.error.unknownAction', { action }) : t(lang, 'codeSymbolRead.error.missingAction'),
167
+ };
168
+ }
169
+ if (parsed.error) {
170
+ return {
171
+ action,
172
+ json,
173
+ path: inputPath,
174
+ startLine: startLine.value,
175
+ endLine: endLine.value,
176
+ contextLines: contextLines.value,
177
+ maxFileBytes: maxFileBytes.value,
178
+ maxSnippetLines: maxSnippetLines.value,
179
+ error: formatCliOptionParseError(parsed.error, lang),
180
+ };
181
+ }
182
+ for (const candidate of [startLine, endLine, contextLines, maxFileBytes, maxSnippetLines]) {
183
+ if (candidate.error) {
184
+ return {
185
+ action,
186
+ json,
187
+ path: inputPath,
188
+ startLine: startLine.value,
189
+ endLine: endLine.value,
190
+ contextLines: contextLines.value,
191
+ maxFileBytes: maxFileBytes.value,
192
+ maxSnippetLines: maxSnippetLines.value,
193
+ error: candidate.error,
194
+ };
195
+ }
196
+ }
197
+ if (parsed.positionals.length === 0) {
198
+ return {
199
+ action,
200
+ json,
201
+ path: null,
202
+ startLine: startLine.value,
203
+ endLine: endLine.value,
204
+ contextLines: contextLines.value,
205
+ maxFileBytes: maxFileBytes.value,
206
+ maxSnippetLines: maxSnippetLines.value,
207
+ error: t(lang, 'codeSymbolRead.error.missingPath'),
208
+ };
209
+ }
210
+ if (parsed.positionals.length > 1) {
211
+ return {
212
+ action,
213
+ json,
214
+ path: inputPath,
215
+ startLine: startLine.value,
216
+ endLine: endLine.value,
217
+ contextLines: contextLines.value,
218
+ maxFileBytes: maxFileBytes.value,
219
+ maxSnippetLines: maxSnippetLines.value,
220
+ error: t(lang, 'codeSymbolRead.error.tooManyPaths'),
221
+ };
222
+ }
223
+ if (startLine.value === null) {
224
+ return {
225
+ action,
226
+ json,
227
+ path: inputPath,
228
+ startLine: null,
229
+ endLine: endLine.value,
230
+ contextLines: contextLines.value,
231
+ maxFileBytes: maxFileBytes.value,
232
+ maxSnippetLines: maxSnippetLines.value,
233
+ error: t(lang, 'codeSymbolRead.error.missingStartLine'),
234
+ };
235
+ }
236
+ return {
237
+ action,
238
+ json,
239
+ path: inputPath,
240
+ startLine: startLine.value,
241
+ endLine: endLine.value,
242
+ contextLines: contextLines.value,
243
+ maxFileBytes: maxFileBytes.value,
244
+ maxSnippetLines: maxSnippetLines.value,
245
+ };
246
+ }
247
+ function renderCodeOutlineSummary(report, lang) {
248
+ const lines = [
249
+ t(lang, 'codeOutline.title'),
250
+ `${t(lang, 'scriptPack.label.script')}: ${CODE_OUTLINE_SCRIPT_REF}`,
251
+ `${t(lang, 'label.status')}: ${report.status}`,
252
+ `${t(lang, 'codeOutline.label.files')}: ${report.files.length}`,
253
+ `${t(lang, 'codeOutline.label.symbols')}: ${report.symbols.length}`,
254
+ `${t(lang, 'codeOutline.label.findings')}: ${report.findings.length}`,
255
+ ];
256
+ if (report.symbols.length > 0) {
257
+ lines.push(t(lang, 'codeOutline.label.outline'));
258
+ for (const symbol of report.symbols) {
259
+ const exported = symbol.exported ? ' export' : '';
260
+ const asyncFlag = symbol.async ? ' async' : '';
261
+ lines.push(`- ${symbol.path}:${symbol.start_line}-${symbol.end_line} ${symbol.kind}${exported}${asyncFlag} ${symbol.name}: ${symbol.signature}`);
262
+ }
263
+ }
264
+ if (report.findings.length > 0) {
265
+ lines.push(t(lang, 'codeOutline.label.findings'));
266
+ for (const finding of report.findings) {
267
+ lines.push(`- ${finding.path}: ${finding.code} (${finding.message})`);
268
+ }
269
+ }
270
+ if (report.issues.length > 0) {
271
+ lines.push(t(lang, 'codeOutline.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
272
+ }
273
+ if (report.findings.length === 0 && report.issues.length === 0) {
274
+ lines.push(t(lang, 'codeOutline.clean'));
275
+ }
276
+ return lines.join('\n');
277
+ }
278
+ function renderCodeSymbolReadSummary(report, lang) {
279
+ const target = report.target;
280
+ const targetLabel = target === null || target.resolved_start_line === null || target.resolved_end_line === null
281
+ ? t(lang, 'value.none')
282
+ : `${target.path}:${target.resolved_start_line}-${target.resolved_end_line}`;
283
+ const lines = [
284
+ t(lang, 'codeSymbolRead.title'),
285
+ `${t(lang, 'scriptPack.label.script')}: ${CODE_SYMBOL_READ_SCRIPT_REF}`,
286
+ `${t(lang, 'label.status')}: ${report.status}`,
287
+ `${t(lang, 'codeSymbolRead.label.target')}: ${targetLabel}`,
288
+ `${t(lang, 'codeSymbolRead.label.findings')}: ${report.findings.length}`,
289
+ ];
290
+ if (target?.symbol) {
291
+ lines.push(`${t(lang, 'codeSymbolRead.label.symbol')}: ${target.symbol.kind} ${target.symbol.name} (${target.symbol.start_line}-${target.symbol.end_line})`);
292
+ }
293
+ if (report.snippet) {
294
+ lines.push(t(lang, 'codeSymbolRead.label.snippet'));
295
+ lines.push(report.snippet.text);
296
+ }
297
+ if (report.findings.length > 0) {
298
+ lines.push(t(lang, 'codeSymbolRead.label.findings'));
299
+ for (const finding of report.findings) {
300
+ lines.push(`- ${finding.path}: ${finding.code} (${finding.message})`);
301
+ }
302
+ }
303
+ if (report.issues.length > 0) {
304
+ lines.push(t(lang, 'codeOutline.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
305
+ }
306
+ return lines.join('\n');
307
+ }
308
+ export function runCodeOutlineScript(args, reporter, lang = 'en') {
309
+ if (hasCliOptionToken(args, '--help', ['-h'])) {
310
+ reporter.stdout(getCodeOutlineHelp(lang));
311
+ return 0;
312
+ }
313
+ const options = parseCodeOutlineOptions(args, lang);
314
+ if (options.error) {
315
+ printUsageError(reporter, options.error, 'mf script-pack run code/outline --help', getCodeOutlineHelp(lang), lang);
316
+ return 1;
317
+ }
318
+ const report = inspectCodeOutline(resolveMustflowRoot(), {
319
+ paths: options.paths,
320
+ maxFiles: options.maxFiles ?? undefined,
321
+ maxFileBytes: options.maxFileBytes ?? undefined,
322
+ });
323
+ if (options.json) {
324
+ reporter.stdout(JSON.stringify(report, null, 2));
325
+ return report.ok ? 0 : 1;
326
+ }
327
+ reporter.stdout(renderCodeOutlineSummary(report, lang));
328
+ return report.ok ? 0 : 1;
329
+ }
330
+ export function runCodeSymbolReadScript(args, reporter, lang = 'en') {
331
+ if (hasCliOptionToken(args, '--help', ['-h'])) {
332
+ reporter.stdout(getCodeSymbolReadHelp(lang));
333
+ return 0;
334
+ }
335
+ const options = parseCodeSymbolReadOptions(args, lang);
336
+ if (options.error || options.path === null || options.startLine === null) {
337
+ printUsageError(reporter, options.error ?? t(lang, 'cli.common.invalidInput'), 'mf script-pack run code/symbol-read --help', getCodeSymbolReadHelp(lang), lang);
338
+ return 1;
339
+ }
340
+ const report = readCodeSymbol(resolveMustflowRoot(), {
341
+ path: options.path,
342
+ startLine: options.startLine,
343
+ endLine: options.endLine,
344
+ contextLines: options.contextLines ?? undefined,
345
+ maxFileBytes: options.maxFileBytes ?? undefined,
346
+ maxSnippetLines: options.maxSnippetLines ?? undefined,
347
+ });
348
+ if (options.json) {
349
+ reporter.stdout(JSON.stringify(report, null, 2));
350
+ return report.ok ? 0 : 1;
351
+ }
352
+ reporter.stdout(renderCodeSymbolReadSummary(report, lang));
353
+ return report.ok ? 0 : 1;
354
+ }