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.
Files changed (41) hide show
  1. package/README.md +31 -3
  2. package/dist/cli/commands/docs.js +86 -2
  3. package/dist/cli/commands/script-pack.js +5 -0
  4. package/dist/cli/i18n/en.js +101 -2
  5. package/dist/cli/i18n/es.js +101 -2
  6. package/dist/cli/i18n/fr.js +101 -2
  7. package/dist/cli/i18n/hi.js +101 -2
  8. package/dist/cli/i18n/ko.js +101 -2
  9. package/dist/cli/i18n/zh.js +101 -2
  10. package/dist/cli/lib/script-pack-registry.js +162 -7
  11. package/dist/cli/script-packs/code-export-diff.js +160 -0
  12. package/dist/cli/script-packs/code-outline.js +33 -5
  13. package/dist/cli/script-packs/code-route-outline.js +155 -0
  14. package/dist/cli/script-packs/docs-reference-drift.js +150 -0
  15. package/dist/cli/script-packs/repo-config-chain.js +163 -0
  16. package/dist/cli/script-packs/repo-related-files.js +161 -0
  17. package/dist/core/code-outline.js +527 -80
  18. package/dist/core/config-chain.js +595 -0
  19. package/dist/core/export-diff.js +359 -0
  20. package/dist/core/public-json-contracts.js +75 -0
  21. package/dist/core/reference-drift.js +388 -0
  22. package/dist/core/related-files.js +493 -0
  23. package/dist/core/route-outline.js +912 -0
  24. package/dist/core/script-pack-suggestions.js +111 -5
  25. package/dist/core/source-anchors.js +13 -1
  26. package/package.json +1 -1
  27. package/schemas/README.md +28 -5
  28. package/schemas/code-outline-report.schema.json +47 -1
  29. package/schemas/code-symbol-read-report.schema.json +64 -4
  30. package/schemas/config-chain-report.schema.json +187 -0
  31. package/schemas/export-diff-report.schema.json +220 -0
  32. package/schemas/reference-drift-report.schema.json +166 -0
  33. package/schemas/related-files-report.schema.json +145 -0
  34. package/schemas/route-outline-report.schema.json +200 -0
  35. package/templates/default/common/.mustflow/config/commands.toml +21 -0
  36. package/templates/default/i18n.toml +7 -1
  37. package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +1 -1
  38. package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -1
  39. package/templates/default/locales/en/.mustflow/skills/cross-agent-session-reference/SKILL.md +131 -0
  40. package/templates/default/locales/en/.mustflow/skills/routes.toml +6 -0
  41. 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
- if (parsed.positionals.length === 0) {
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
+ }