mustflow 2.75.1 → 2.84.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 +31 -3
- package/dist/cli/commands/docs.js +86 -2
- package/dist/cli/commands/script-pack.js +5 -0
- package/dist/cli/i18n/en.js +101 -2
- package/dist/cli/i18n/es.js +101 -2
- package/dist/cli/i18n/fr.js +101 -2
- package/dist/cli/i18n/hi.js +101 -2
- package/dist/cli/i18n/ko.js +101 -2
- package/dist/cli/i18n/zh.js +101 -2
- package/dist/cli/lib/script-pack-registry.js +162 -7
- package/dist/cli/script-packs/code-export-diff.js +160 -0
- package/dist/cli/script-packs/code-outline.js +33 -5
- package/dist/cli/script-packs/code-route-outline.js +155 -0
- package/dist/cli/script-packs/docs-reference-drift.js +150 -0
- package/dist/cli/script-packs/repo-config-chain.js +163 -0
- package/dist/cli/script-packs/repo-related-files.js +161 -0
- package/dist/core/code-outline.js +527 -80
- package/dist/core/config-chain.js +595 -0
- package/dist/core/export-diff.js +359 -0
- package/dist/core/public-json-contracts.js +75 -0
- package/dist/core/reference-drift.js +388 -0
- package/dist/core/related-files.js +493 -0
- package/dist/core/route-outline.js +912 -0
- package/dist/core/script-pack-suggestions.js +111 -5
- package/dist/core/source-anchors.js +13 -1
- package/package.json +1 -1
- package/schemas/README.md +28 -5
- package/schemas/code-outline-report.schema.json +47 -1
- package/schemas/code-symbol-read-report.schema.json +64 -4
- package/schemas/config-chain-report.schema.json +187 -0
- package/schemas/export-diff-report.schema.json +220 -0
- package/schemas/reference-drift-report.schema.json +166 -0
- package/schemas/related-files-report.schema.json +145 -0
- package/schemas/route-outline-report.schema.json +200 -0
- package/templates/default/common/.mustflow/config/commands.toml +21 -0
- package/templates/default/i18n.toml +7 -1
- package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +1 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -1
- package/templates/default/locales/en/.mustflow/skills/cross-agent-session-reference/SKILL.md +131 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +6 -0
- package/templates/default/manifest.toml +8 -1
|
@@ -10,6 +10,7 @@ const CODE_OUTLINE_OPTIONS = [
|
|
|
10
10
|
];
|
|
11
11
|
const CODE_SYMBOL_READ_OPTIONS = [
|
|
12
12
|
{ name: '--json', kind: 'boolean' },
|
|
13
|
+
{ name: '--anchor', kind: 'string' },
|
|
13
14
|
{ name: '--start-line', kind: 'string' },
|
|
14
15
|
{ name: '--end-line', kind: 'string' },
|
|
15
16
|
{ name: '--context-lines', kind: 'string' },
|
|
@@ -65,9 +66,10 @@ export function getCodeOutlineHelp(lang = 'en') {
|
|
|
65
66
|
}
|
|
66
67
|
export function getCodeSymbolReadHelp(lang = 'en') {
|
|
67
68
|
return renderHelp({
|
|
68
|
-
usage: 'mf script-pack run code/symbol-read read <path> --start-line <line> [options]',
|
|
69
|
+
usage: 'mf script-pack run code/symbol-read read (<path> --start-line <line> | --anchor <id>) [options]',
|
|
69
70
|
summary: t(lang, 'codeSymbolRead.help.summary'),
|
|
70
71
|
options: [
|
|
72
|
+
{ label: '--anchor <id>', description: t(lang, 'codeSymbolRead.help.option.anchor') },
|
|
71
73
|
{ label: '--start-line <line>', description: t(lang, 'codeSymbolRead.help.option.startLine') },
|
|
72
74
|
{ label: '--end-line <line>', description: t(lang, 'codeSymbolRead.help.option.endLine') },
|
|
73
75
|
{ label: '--context-lines <count>', description: t(lang, 'codeSymbolRead.help.option.contextLines') },
|
|
@@ -78,6 +80,7 @@ export function getCodeSymbolReadHelp(lang = 'en') {
|
|
|
78
80
|
],
|
|
79
81
|
examples: [
|
|
80
82
|
'mf script-pack run code/symbol-read read src/cli/commands/script-pack.ts --start-line 100',
|
|
83
|
+
'mf script-pack run code/symbol-read read --anchor auth.session.resolve --json',
|
|
81
84
|
'mf script-pack run code/symbol-read read src/core/code-outline.ts --start-line 320 --context-lines 2 --json',
|
|
82
85
|
'mf script-pack run code/symbol-read read src/core/code-outline.ts --start-line 1 --end-line 40 --json',
|
|
83
86
|
],
|
|
@@ -147,6 +150,7 @@ function parseCodeSymbolReadOptions(args, lang) {
|
|
|
147
150
|
const [action, ...rest] = args;
|
|
148
151
|
const parsed = parseCliOptions(rest, CODE_SYMBOL_READ_OPTIONS, { allowPositionals: true });
|
|
149
152
|
const json = hasParsedCliOption(parsed, '--json');
|
|
153
|
+
const anchorId = getParsedCliStringOption(parsed, '--anchor');
|
|
150
154
|
const startLine = parsePositiveInteger(getParsedCliStringOption(parsed, '--start-line'), '--start-line', lang);
|
|
151
155
|
const endLine = parsePositiveInteger(getParsedCliStringOption(parsed, '--end-line'), '--end-line', lang);
|
|
152
156
|
const contextLines = parseNonNegativeInteger(getParsedCliStringOption(parsed, '--context-lines'), '--context-lines', lang);
|
|
@@ -158,6 +162,7 @@ function parseCodeSymbolReadOptions(args, lang) {
|
|
|
158
162
|
action: 'read',
|
|
159
163
|
json,
|
|
160
164
|
path: inputPath,
|
|
165
|
+
anchorId,
|
|
161
166
|
startLine: startLine.value,
|
|
162
167
|
endLine: endLine.value,
|
|
163
168
|
contextLines: contextLines.value,
|
|
@@ -171,6 +176,7 @@ function parseCodeSymbolReadOptions(args, lang) {
|
|
|
171
176
|
action,
|
|
172
177
|
json,
|
|
173
178
|
path: inputPath,
|
|
179
|
+
anchorId,
|
|
174
180
|
startLine: startLine.value,
|
|
175
181
|
endLine: endLine.value,
|
|
176
182
|
contextLines: contextLines.value,
|
|
@@ -185,6 +191,7 @@ function parseCodeSymbolReadOptions(args, lang) {
|
|
|
185
191
|
action,
|
|
186
192
|
json,
|
|
187
193
|
path: inputPath,
|
|
194
|
+
anchorId,
|
|
188
195
|
startLine: startLine.value,
|
|
189
196
|
endLine: endLine.value,
|
|
190
197
|
contextLines: contextLines.value,
|
|
@@ -194,11 +201,28 @@ function parseCodeSymbolReadOptions(args, lang) {
|
|
|
194
201
|
};
|
|
195
202
|
}
|
|
196
203
|
}
|
|
197
|
-
|
|
204
|
+
const hasAnchor = anchorId !== null;
|
|
205
|
+
const hasLineSelection = startLine.value !== null || endLine.value !== null;
|
|
206
|
+
if (hasAnchor && (parsed.positionals.length > 0 || hasLineSelection)) {
|
|
207
|
+
return {
|
|
208
|
+
action,
|
|
209
|
+
json,
|
|
210
|
+
path: inputPath,
|
|
211
|
+
anchorId,
|
|
212
|
+
startLine: startLine.value,
|
|
213
|
+
endLine: endLine.value,
|
|
214
|
+
contextLines: contextLines.value,
|
|
215
|
+
maxFileBytes: maxFileBytes.value,
|
|
216
|
+
maxSnippetLines: maxSnippetLines.value,
|
|
217
|
+
error: t(lang, 'codeSymbolRead.error.anchorConflict'),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (!hasAnchor && parsed.positionals.length === 0) {
|
|
198
221
|
return {
|
|
199
222
|
action,
|
|
200
223
|
json,
|
|
201
224
|
path: null,
|
|
225
|
+
anchorId,
|
|
202
226
|
startLine: startLine.value,
|
|
203
227
|
endLine: endLine.value,
|
|
204
228
|
contextLines: contextLines.value,
|
|
@@ -207,11 +231,12 @@ function parseCodeSymbolReadOptions(args, lang) {
|
|
|
207
231
|
error: t(lang, 'codeSymbolRead.error.missingPath'),
|
|
208
232
|
};
|
|
209
233
|
}
|
|
210
|
-
if (parsed.positionals.length > 1) {
|
|
234
|
+
if (!hasAnchor && parsed.positionals.length > 1) {
|
|
211
235
|
return {
|
|
212
236
|
action,
|
|
213
237
|
json,
|
|
214
238
|
path: inputPath,
|
|
239
|
+
anchorId,
|
|
215
240
|
startLine: startLine.value,
|
|
216
241
|
endLine: endLine.value,
|
|
217
242
|
contextLines: contextLines.value,
|
|
@@ -220,11 +245,12 @@ function parseCodeSymbolReadOptions(args, lang) {
|
|
|
220
245
|
error: t(lang, 'codeSymbolRead.error.tooManyPaths'),
|
|
221
246
|
};
|
|
222
247
|
}
|
|
223
|
-
if (startLine.value === null) {
|
|
248
|
+
if (!hasAnchor && startLine.value === null) {
|
|
224
249
|
return {
|
|
225
250
|
action,
|
|
226
251
|
json,
|
|
227
252
|
path: inputPath,
|
|
253
|
+
anchorId,
|
|
228
254
|
startLine: null,
|
|
229
255
|
endLine: endLine.value,
|
|
230
256
|
contextLines: contextLines.value,
|
|
@@ -237,6 +263,7 @@ function parseCodeSymbolReadOptions(args, lang) {
|
|
|
237
263
|
action,
|
|
238
264
|
json,
|
|
239
265
|
path: inputPath,
|
|
266
|
+
anchorId,
|
|
240
267
|
startLine: startLine.value,
|
|
241
268
|
endLine: endLine.value,
|
|
242
269
|
contextLines: contextLines.value,
|
|
@@ -333,12 +360,13 @@ export function runCodeSymbolReadScript(args, reporter, lang = 'en') {
|
|
|
333
360
|
return 0;
|
|
334
361
|
}
|
|
335
362
|
const options = parseCodeSymbolReadOptions(args, lang);
|
|
336
|
-
if (options.error || options.path === null || options.startLine === null) {
|
|
363
|
+
if (options.error || (options.anchorId === null && (options.path === null || options.startLine === null))) {
|
|
337
364
|
printUsageError(reporter, options.error ?? t(lang, 'cli.common.invalidInput'), 'mf script-pack run code/symbol-read --help', getCodeSymbolReadHelp(lang), lang);
|
|
338
365
|
return 1;
|
|
339
366
|
}
|
|
340
367
|
const report = readCodeSymbol(resolveMustflowRoot(), {
|
|
341
368
|
path: options.path,
|
|
369
|
+
anchorId: options.anchorId,
|
|
342
370
|
startLine: options.startLine,
|
|
343
371
|
endLine: options.endLine,
|
|
344
372
|
contextLines: options.contextLines ?? undefined,
|
|
@@ -0,0 +1,155 @@
|
|
|
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_ROUTE_OUTLINE_SCRIPT_REF, inspectRouteOutline, } from '../../core/route-outline.js';
|
|
6
|
+
const CODE_ROUTE_OUTLINE_OPTIONS = [
|
|
7
|
+
{ name: '--json', kind: 'boolean' },
|
|
8
|
+
{ name: '--max-files', kind: 'string' },
|
|
9
|
+
{ name: '--max-file-bytes', kind: 'string' },
|
|
10
|
+
];
|
|
11
|
+
function parsePositiveInteger(value, option, lang) {
|
|
12
|
+
if (value === null) {
|
|
13
|
+
return { value: null };
|
|
14
|
+
}
|
|
15
|
+
if (!/^[1-9]\d*$/u.test(value)) {
|
|
16
|
+
return { value: null, error: t(lang, 'codeRouteOutline.error.invalidPositiveInteger', { option, value }) };
|
|
17
|
+
}
|
|
18
|
+
const parsed = Number(value);
|
|
19
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
20
|
+
return { value: null, error: t(lang, 'codeRouteOutline.error.invalidPositiveInteger', { option, value }) };
|
|
21
|
+
}
|
|
22
|
+
return { value: parsed };
|
|
23
|
+
}
|
|
24
|
+
export function getCodeRouteOutlineHelp(lang = 'en') {
|
|
25
|
+
return renderHelp({
|
|
26
|
+
usage: 'mf script-pack run code/route-outline scan <path...> [options]',
|
|
27
|
+
summary: t(lang, 'codeRouteOutline.help.summary'),
|
|
28
|
+
options: [
|
|
29
|
+
{ label: '--max-files <count>', description: t(lang, 'codeRouteOutline.help.option.maxFiles') },
|
|
30
|
+
{ label: '--max-file-bytes <bytes>', description: t(lang, 'codeRouteOutline.help.option.maxFileBytes') },
|
|
31
|
+
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
32
|
+
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
33
|
+
],
|
|
34
|
+
examples: [
|
|
35
|
+
'mf script-pack run code/route-outline scan src --json',
|
|
36
|
+
'mf script-pack run code/route-outline scan src/server.ts --max-files 20',
|
|
37
|
+
'mf script-pack run code/route-outline scan src apps/api --max-file-bytes 262144 --json',
|
|
38
|
+
],
|
|
39
|
+
exitCodes: [
|
|
40
|
+
{ label: '0', description: t(lang, 'codeRouteOutline.help.exit.ok') },
|
|
41
|
+
{ label: '1', description: t(lang, 'codeRouteOutline.help.exit.fail') },
|
|
42
|
+
],
|
|
43
|
+
}, lang);
|
|
44
|
+
}
|
|
45
|
+
function parseCodeRouteOutlineOptions(args, lang) {
|
|
46
|
+
const [action, ...rest] = args;
|
|
47
|
+
const parsed = parseCliOptions(rest, CODE_ROUTE_OUTLINE_OPTIONS, { allowPositionals: true });
|
|
48
|
+
const json = hasParsedCliOption(parsed, '--json');
|
|
49
|
+
const maxFiles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-files'), '--max-files', lang);
|
|
50
|
+
const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
|
|
51
|
+
if (action !== 'scan') {
|
|
52
|
+
return {
|
|
53
|
+
action: 'scan',
|
|
54
|
+
json,
|
|
55
|
+
paths: parsed.positionals,
|
|
56
|
+
maxFiles: maxFiles.value,
|
|
57
|
+
maxFileBytes: maxFileBytes.value,
|
|
58
|
+
error: action
|
|
59
|
+
? t(lang, 'codeRouteOutline.error.unknownAction', { action })
|
|
60
|
+
: t(lang, 'codeRouteOutline.error.missingAction'),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (parsed.error) {
|
|
64
|
+
return {
|
|
65
|
+
action,
|
|
66
|
+
json,
|
|
67
|
+
paths: parsed.positionals,
|
|
68
|
+
maxFiles: maxFiles.value,
|
|
69
|
+
maxFileBytes: maxFileBytes.value,
|
|
70
|
+
error: formatCliOptionParseError(parsed.error, lang),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
for (const candidate of [maxFiles, maxFileBytes]) {
|
|
74
|
+
if (candidate.error) {
|
|
75
|
+
return {
|
|
76
|
+
action,
|
|
77
|
+
json,
|
|
78
|
+
paths: parsed.positionals,
|
|
79
|
+
maxFiles: maxFiles.value,
|
|
80
|
+
maxFileBytes: maxFileBytes.value,
|
|
81
|
+
error: candidate.error,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (parsed.positionals.length === 0) {
|
|
86
|
+
return {
|
|
87
|
+
action,
|
|
88
|
+
json,
|
|
89
|
+
paths: parsed.positionals,
|
|
90
|
+
maxFiles: maxFiles.value,
|
|
91
|
+
maxFileBytes: maxFileBytes.value,
|
|
92
|
+
error: t(lang, 'codeRouteOutline.error.missingPath'),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
action,
|
|
97
|
+
json,
|
|
98
|
+
paths: parsed.positionals,
|
|
99
|
+
maxFiles: maxFiles.value,
|
|
100
|
+
maxFileBytes: maxFileBytes.value,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function renderCodeRouteOutlineSummary(report, lang) {
|
|
104
|
+
const lines = [
|
|
105
|
+
t(lang, 'codeRouteOutline.title'),
|
|
106
|
+
`${t(lang, 'scriptPack.label.script')}: ${CODE_ROUTE_OUTLINE_SCRIPT_REF}`,
|
|
107
|
+
`${t(lang, 'label.status')}: ${report.status}`,
|
|
108
|
+
`${t(lang, 'codeRouteOutline.label.files')}: ${report.files.length}`,
|
|
109
|
+
`${t(lang, 'codeRouteOutline.label.routes')}: ${report.routes.length}`,
|
|
110
|
+
`${t(lang, 'codeRouteOutline.label.findings')}: ${report.findings.length}`,
|
|
111
|
+
];
|
|
112
|
+
if (report.routes.length > 0) {
|
|
113
|
+
lines.push(t(lang, 'codeRouteOutline.label.outline'));
|
|
114
|
+
for (const route of report.routes) {
|
|
115
|
+
const routePath = route.route_path ?? '<dynamic>';
|
|
116
|
+
const lifecycle = route.lifecycle.length > 0 ? ` [${route.lifecycle.join(' > ')}]` : '';
|
|
117
|
+
lines.push(`- ${route.path}:${route.line} ${route.framework} ${route.method.toUpperCase()} ${routePath}${lifecycle}: ${route.signature}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (report.findings.length > 0) {
|
|
121
|
+
lines.push(t(lang, 'codeRouteOutline.label.findings'));
|
|
122
|
+
for (const finding of report.findings) {
|
|
123
|
+
lines.push(`- ${finding.path}: ${finding.code} (${finding.message})`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (report.issues.length > 0) {
|
|
127
|
+
lines.push(t(lang, 'codeRouteOutline.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
|
|
128
|
+
}
|
|
129
|
+
if (report.routes.length === 0 && report.findings.length === 0 && report.issues.length === 0) {
|
|
130
|
+
lines.push(t(lang, 'codeRouteOutline.clean'));
|
|
131
|
+
}
|
|
132
|
+
return lines.join('\n');
|
|
133
|
+
}
|
|
134
|
+
export function runCodeRouteOutlineScript(args, reporter, lang = 'en') {
|
|
135
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
136
|
+
reporter.stdout(getCodeRouteOutlineHelp(lang));
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
const options = parseCodeRouteOutlineOptions(args, lang);
|
|
140
|
+
if (options.error) {
|
|
141
|
+
printUsageError(reporter, options.error, 'mf script-pack run code/route-outline --help', getCodeRouteOutlineHelp(lang), lang);
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
const report = inspectRouteOutline(resolveMustflowRoot(), {
|
|
145
|
+
paths: options.paths,
|
|
146
|
+
maxFiles: options.maxFiles ?? undefined,
|
|
147
|
+
maxFileBytes: options.maxFileBytes ?? undefined,
|
|
148
|
+
});
|
|
149
|
+
if (options.json) {
|
|
150
|
+
reporter.stdout(JSON.stringify(report, null, 2));
|
|
151
|
+
return report.ok ? 0 : 1;
|
|
152
|
+
}
|
|
153
|
+
reporter.stdout(renderCodeRouteOutlineSummary(report, lang));
|
|
154
|
+
return report.ok ? 0 : 1;
|
|
155
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
2
|
+
import { COMMAND_DEFINITIONS } from '../lib/command-registry.js';
|
|
3
|
+
import { t } from '../lib/i18n.js';
|
|
4
|
+
import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
5
|
+
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
6
|
+
import { listScriptPackScripts } from '../lib/script-pack-registry.js';
|
|
7
|
+
import { checkReferenceDrift, REFERENCE_DRIFT_SCRIPT_REF, } from '../../core/reference-drift.js';
|
|
8
|
+
import { getPublicJsonSchemaFiles } from '../../core/public-json-contracts.js';
|
|
9
|
+
const REFERENCE_DRIFT_OPTIONS = [
|
|
10
|
+
{ name: '--json', kind: 'boolean' },
|
|
11
|
+
{ name: '--max-files', kind: 'string' },
|
|
12
|
+
{ name: '--max-file-bytes', kind: 'string' },
|
|
13
|
+
];
|
|
14
|
+
function parsePositiveInteger(value, option, lang) {
|
|
15
|
+
if (value === null) {
|
|
16
|
+
return { value: null };
|
|
17
|
+
}
|
|
18
|
+
if (!/^[1-9]\d*$/u.test(value)) {
|
|
19
|
+
return { value: null, error: t(lang, 'referenceDrift.error.invalidPositiveInteger', { option, value }) };
|
|
20
|
+
}
|
|
21
|
+
const parsed = Number(value);
|
|
22
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
23
|
+
return { value: null, error: t(lang, 'referenceDrift.error.invalidPositiveInteger', { option, value }) };
|
|
24
|
+
}
|
|
25
|
+
return { value: parsed };
|
|
26
|
+
}
|
|
27
|
+
export function getDocsReferenceDriftHelp(lang = 'en') {
|
|
28
|
+
return renderHelp({
|
|
29
|
+
usage: 'mf script-pack run docs/reference-drift check [path...] [options]',
|
|
30
|
+
summary: t(lang, 'referenceDrift.help.summary'),
|
|
31
|
+
options: [
|
|
32
|
+
{ label: '--max-files <count>', description: t(lang, 'referenceDrift.help.option.maxFiles') },
|
|
33
|
+
{ label: '--max-file-bytes <bytes>', description: t(lang, 'referenceDrift.help.option.maxFileBytes') },
|
|
34
|
+
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
35
|
+
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
36
|
+
],
|
|
37
|
+
examples: [
|
|
38
|
+
'mf script-pack run docs/reference-drift check --json',
|
|
39
|
+
'mf script-pack run docs/reference-drift check README.md schemas/README.md --json',
|
|
40
|
+
'mf script-pack run docs/reference-drift check docs-site/src/content/docs --max-files 80 --json',
|
|
41
|
+
],
|
|
42
|
+
exitCodes: [
|
|
43
|
+
{ label: '0', description: t(lang, 'referenceDrift.help.exit.ok') },
|
|
44
|
+
{ label: '1', description: t(lang, 'referenceDrift.help.exit.fail') },
|
|
45
|
+
],
|
|
46
|
+
}, lang);
|
|
47
|
+
}
|
|
48
|
+
function parseReferenceDriftOptions(args, lang) {
|
|
49
|
+
const [action, ...rest] = args;
|
|
50
|
+
const parsed = parseCliOptions(rest, REFERENCE_DRIFT_OPTIONS, { allowPositionals: true });
|
|
51
|
+
const json = hasParsedCliOption(parsed, '--json');
|
|
52
|
+
const maxFiles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-files'), '--max-files', lang);
|
|
53
|
+
const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
|
|
54
|
+
if (action !== 'check') {
|
|
55
|
+
return {
|
|
56
|
+
action: 'check',
|
|
57
|
+
json,
|
|
58
|
+
paths: parsed.positionals,
|
|
59
|
+
maxFiles: maxFiles.value,
|
|
60
|
+
maxFileBytes: maxFileBytes.value,
|
|
61
|
+
error: action
|
|
62
|
+
? t(lang, 'referenceDrift.error.unknownAction', { action })
|
|
63
|
+
: t(lang, 'referenceDrift.error.missingAction'),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (parsed.error) {
|
|
67
|
+
return {
|
|
68
|
+
action,
|
|
69
|
+
json,
|
|
70
|
+
paths: parsed.positionals,
|
|
71
|
+
maxFiles: maxFiles.value,
|
|
72
|
+
maxFileBytes: maxFileBytes.value,
|
|
73
|
+
error: formatCliOptionParseError(parsed.error, lang),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
for (const candidate of [maxFiles, maxFileBytes]) {
|
|
77
|
+
if (candidate.error) {
|
|
78
|
+
return {
|
|
79
|
+
action,
|
|
80
|
+
json,
|
|
81
|
+
paths: parsed.positionals,
|
|
82
|
+
maxFiles: maxFiles.value,
|
|
83
|
+
maxFileBytes: maxFileBytes.value,
|
|
84
|
+
error: candidate.error,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
action,
|
|
90
|
+
json,
|
|
91
|
+
paths: parsed.positionals,
|
|
92
|
+
maxFiles: maxFiles.value,
|
|
93
|
+
maxFileBytes: maxFileBytes.value,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function renderReferenceDriftSummary(report, lang) {
|
|
97
|
+
const lines = [
|
|
98
|
+
t(lang, 'referenceDrift.title'),
|
|
99
|
+
`${t(lang, 'scriptPack.label.script')}: ${REFERENCE_DRIFT_SCRIPT_REF}`,
|
|
100
|
+
`${t(lang, 'label.status')}: ${report.status}`,
|
|
101
|
+
`${t(lang, 'referenceDrift.label.files')}: ${report.files.length}`,
|
|
102
|
+
`${t(lang, 'referenceDrift.label.references')}: ${report.references.length}`,
|
|
103
|
+
`${t(lang, 'referenceDrift.label.findings')}: ${report.findings.length}`,
|
|
104
|
+
];
|
|
105
|
+
if (report.references.length > 0) {
|
|
106
|
+
lines.push(t(lang, 'referenceDrift.label.references'));
|
|
107
|
+
for (const reference of report.references.slice(0, 40)) {
|
|
108
|
+
lines.push(`- ${reference.path}:${reference.line}: ${reference.kind} ${reference.target} (${reference.status})`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (report.findings.length > 0) {
|
|
112
|
+
lines.push(t(lang, 'referenceDrift.label.findings'));
|
|
113
|
+
for (const finding of report.findings) {
|
|
114
|
+
const line = finding.line === undefined ? '' : `:${finding.line}`;
|
|
115
|
+
lines.push(`- ${finding.path}${line}: ${finding.code} (${finding.message})`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (report.issues.length > 0) {
|
|
119
|
+
lines.push(t(lang, 'referenceDrift.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
|
|
120
|
+
}
|
|
121
|
+
if (report.references.length === 0 && report.findings.length === 0 && report.issues.length === 0) {
|
|
122
|
+
lines.push(t(lang, 'referenceDrift.clean'));
|
|
123
|
+
}
|
|
124
|
+
return lines.join('\n');
|
|
125
|
+
}
|
|
126
|
+
export function runDocsReferenceDriftScript(args, reporter, lang = 'en') {
|
|
127
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
128
|
+
reporter.stdout(getDocsReferenceDriftHelp(lang));
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
const options = parseReferenceDriftOptions(args, lang);
|
|
132
|
+
if (options.error) {
|
|
133
|
+
printUsageError(reporter, options.error, 'mf script-pack run docs/reference-drift --help', getDocsReferenceDriftHelp(lang), lang);
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
const report = checkReferenceDrift(resolveMustflowRoot(), {
|
|
137
|
+
paths: options.paths,
|
|
138
|
+
commandNames: COMMAND_DEFINITIONS.map((definition) => definition.id),
|
|
139
|
+
scriptRefs: listScriptPackScripts().map((script) => script.ref),
|
|
140
|
+
schemaFiles: getPublicJsonSchemaFiles(),
|
|
141
|
+
maxFiles: options.maxFiles ?? undefined,
|
|
142
|
+
maxFileBytes: options.maxFileBytes ?? undefined,
|
|
143
|
+
});
|
|
144
|
+
if (options.json) {
|
|
145
|
+
reporter.stdout(JSON.stringify(report, null, 2));
|
|
146
|
+
return report.ok ? 0 : 1;
|
|
147
|
+
}
|
|
148
|
+
reporter.stdout(renderReferenceDriftSummary(report, lang));
|
|
149
|
+
return report.ok ? 0 : 1;
|
|
150
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
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 { CONFIG_CHAIN_SCRIPT_REF, inspectConfigChain, } from '../../core/config-chain.js';
|
|
6
|
+
const CONFIG_CHAIN_OPTIONS = [
|
|
7
|
+
{ name: '--json', kind: 'boolean' },
|
|
8
|
+
{ name: '--max-configs', kind: 'string' },
|
|
9
|
+
{ name: '--max-file-bytes', kind: 'string' },
|
|
10
|
+
];
|
|
11
|
+
function parsePositiveInteger(value, option, lang) {
|
|
12
|
+
if (value === null) {
|
|
13
|
+
return { value: null };
|
|
14
|
+
}
|
|
15
|
+
if (!/^[1-9]\d*$/u.test(value)) {
|
|
16
|
+
return { value: null, error: t(lang, 'configChain.error.invalidPositiveInteger', { option, value }) };
|
|
17
|
+
}
|
|
18
|
+
const parsed = Number(value);
|
|
19
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
20
|
+
return { value: null, error: t(lang, 'configChain.error.invalidPositiveInteger', { option, value }) };
|
|
21
|
+
}
|
|
22
|
+
return { value: parsed };
|
|
23
|
+
}
|
|
24
|
+
export function getRepoConfigChainHelp(lang = 'en') {
|
|
25
|
+
return renderHelp({
|
|
26
|
+
usage: 'mf script-pack run repo/config-chain inspect <path...> [options]',
|
|
27
|
+
summary: t(lang, 'configChain.help.summary'),
|
|
28
|
+
options: [
|
|
29
|
+
{ label: '--max-configs <count>', description: t(lang, 'configChain.help.option.maxConfigs') },
|
|
30
|
+
{ label: '--max-file-bytes <bytes>', description: t(lang, 'configChain.help.option.maxFileBytes') },
|
|
31
|
+
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
32
|
+
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
33
|
+
],
|
|
34
|
+
examples: [
|
|
35
|
+
'mf script-pack run repo/config-chain inspect src --json',
|
|
36
|
+
'mf script-pack run repo/config-chain inspect tsconfig.json src/index.ts --json',
|
|
37
|
+
'mf script-pack run repo/config-chain inspect . --max-configs 40 --json',
|
|
38
|
+
],
|
|
39
|
+
exitCodes: [
|
|
40
|
+
{ label: '0', description: t(lang, 'configChain.help.exit.ok') },
|
|
41
|
+
{ label: '1', description: t(lang, 'configChain.help.exit.fail') },
|
|
42
|
+
],
|
|
43
|
+
}, lang);
|
|
44
|
+
}
|
|
45
|
+
function parseConfigChainOptions(args, lang) {
|
|
46
|
+
const [action, ...rest] = args;
|
|
47
|
+
const parsed = parseCliOptions(rest, CONFIG_CHAIN_OPTIONS, { allowPositionals: true });
|
|
48
|
+
const json = hasParsedCliOption(parsed, '--json');
|
|
49
|
+
const maxConfigs = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-configs'), '--max-configs', lang);
|
|
50
|
+
const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
|
|
51
|
+
if (action !== 'inspect') {
|
|
52
|
+
return {
|
|
53
|
+
action: 'inspect',
|
|
54
|
+
json,
|
|
55
|
+
paths: parsed.positionals,
|
|
56
|
+
maxConfigs: maxConfigs.value,
|
|
57
|
+
maxFileBytes: maxFileBytes.value,
|
|
58
|
+
error: action ? t(lang, 'configChain.error.unknownAction', { action }) : t(lang, 'configChain.error.missingAction'),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (parsed.error) {
|
|
62
|
+
return {
|
|
63
|
+
action,
|
|
64
|
+
json,
|
|
65
|
+
paths: parsed.positionals,
|
|
66
|
+
maxConfigs: maxConfigs.value,
|
|
67
|
+
maxFileBytes: maxFileBytes.value,
|
|
68
|
+
error: formatCliOptionParseError(parsed.error, lang),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
for (const candidate of [maxConfigs, maxFileBytes]) {
|
|
72
|
+
if (candidate.error) {
|
|
73
|
+
return {
|
|
74
|
+
action,
|
|
75
|
+
json,
|
|
76
|
+
paths: parsed.positionals,
|
|
77
|
+
maxConfigs: maxConfigs.value,
|
|
78
|
+
maxFileBytes: maxFileBytes.value,
|
|
79
|
+
error: candidate.error,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (parsed.positionals.length === 0) {
|
|
84
|
+
return {
|
|
85
|
+
action,
|
|
86
|
+
json,
|
|
87
|
+
paths: parsed.positionals,
|
|
88
|
+
maxConfigs: maxConfigs.value,
|
|
89
|
+
maxFileBytes: maxFileBytes.value,
|
|
90
|
+
error: t(lang, 'configChain.error.missingPath'),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
action,
|
|
95
|
+
json,
|
|
96
|
+
paths: parsed.positionals,
|
|
97
|
+
maxConfigs: maxConfigs.value,
|
|
98
|
+
maxFileBytes: maxFileBytes.value,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function renderConfigChainSummary(report, lang) {
|
|
102
|
+
const lines = [
|
|
103
|
+
t(lang, 'configChain.title'),
|
|
104
|
+
`${t(lang, 'scriptPack.label.script')}: ${CONFIG_CHAIN_SCRIPT_REF}`,
|
|
105
|
+
`${t(lang, 'label.status')}: ${report.status}`,
|
|
106
|
+
`${t(lang, 'configChain.label.targets')}: ${report.targets.length}`,
|
|
107
|
+
`${t(lang, 'configChain.label.configs')}: ${report.configs.length}`,
|
|
108
|
+
`${t(lang, 'configChain.label.edges')}: ${report.edges.length}`,
|
|
109
|
+
`${t(lang, 'configChain.label.findings')}: ${report.findings.length}`,
|
|
110
|
+
];
|
|
111
|
+
if (report.configs.length > 0) {
|
|
112
|
+
lines.push(t(lang, 'configChain.label.configs'));
|
|
113
|
+
for (const config of report.configs) {
|
|
114
|
+
const dynamic = config.dynamic ? `, ${t(lang, 'configChain.label.dynamic')}` : '';
|
|
115
|
+
lines.push(`- ${config.path}: ${config.kind}, ${config.format}${dynamic}`);
|
|
116
|
+
for (const summary of config.summary.slice(0, 4)) {
|
|
117
|
+
lines.push(` - ${summary}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (report.edges.length > 0) {
|
|
122
|
+
lines.push(t(lang, 'configChain.label.edges'));
|
|
123
|
+
for (const edge of report.edges) {
|
|
124
|
+
const target = edge.to_path ?? edge.specifier;
|
|
125
|
+
lines.push(`- ${edge.from_path} -> ${target}: ${edge.kind}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (report.findings.length > 0) {
|
|
129
|
+
lines.push(t(lang, 'configChain.label.findings'));
|
|
130
|
+
for (const finding of report.findings) {
|
|
131
|
+
lines.push(`- ${finding.path}: ${finding.code} (${finding.message})`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (report.issues.length > 0) {
|
|
135
|
+
lines.push(t(lang, 'configChain.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
|
|
136
|
+
}
|
|
137
|
+
if (report.configs.length === 0 && report.findings.length === 0 && report.issues.length === 0) {
|
|
138
|
+
lines.push(t(lang, 'configChain.clean'));
|
|
139
|
+
}
|
|
140
|
+
return lines.join('\n');
|
|
141
|
+
}
|
|
142
|
+
export function runRepoConfigChainScript(args, reporter, lang = 'en') {
|
|
143
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
144
|
+
reporter.stdout(getRepoConfigChainHelp(lang));
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
const options = parseConfigChainOptions(args, lang);
|
|
148
|
+
if (options.error) {
|
|
149
|
+
printUsageError(reporter, options.error, 'mf script-pack run repo/config-chain --help', getRepoConfigChainHelp(lang), lang);
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
const report = inspectConfigChain(resolveMustflowRoot(), {
|
|
153
|
+
paths: options.paths,
|
|
154
|
+
maxConfigs: options.maxConfigs ?? undefined,
|
|
155
|
+
maxFileBytes: options.maxFileBytes ?? undefined,
|
|
156
|
+
});
|
|
157
|
+
if (options.json) {
|
|
158
|
+
reporter.stdout(JSON.stringify(report, null, 2));
|
|
159
|
+
return report.ok ? 0 : 1;
|
|
160
|
+
}
|
|
161
|
+
reporter.stdout(renderConfigChainSummary(report, lang));
|
|
162
|
+
return report.ok ? 0 : 1;
|
|
163
|
+
}
|