mustflow 2.75.2 → 2.85.4

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 (70) hide show
  1. package/README.md +40 -3
  2. package/dist/cli/commands/docs.js +86 -2
  3. package/dist/cli/commands/script-pack.js +9 -0
  4. package/dist/cli/i18n/en.js +180 -2
  5. package/dist/cli/i18n/es.js +180 -2
  6. package/dist/cli/i18n/fr.js +180 -2
  7. package/dist/cli/i18n/hi.js +180 -2
  8. package/dist/cli/i18n/ko.js +180 -2
  9. package/dist/cli/i18n/zh.js +180 -2
  10. package/dist/cli/lib/repo-map.js +27 -6
  11. package/dist/cli/lib/run-root-trust.js +15 -1
  12. package/dist/cli/lib/script-pack-registry.js +275 -6
  13. package/dist/cli/lib/validation/index.js +2 -2
  14. package/dist/cli/lib/validation/primitives.js +4 -1
  15. package/dist/cli/script-packs/code-change-impact.js +172 -0
  16. package/dist/cli/script-packs/code-dependency-graph.js +181 -0
  17. package/dist/cli/script-packs/code-export-diff.js +160 -0
  18. package/dist/cli/script-packs/code-outline.js +33 -5
  19. package/dist/cli/script-packs/code-route-outline.js +155 -0
  20. package/dist/cli/script-packs/docs-reference-drift.js +150 -0
  21. package/dist/cli/script-packs/repo-config-chain.js +163 -0
  22. package/dist/cli/script-packs/repo-env-contract.js +156 -0
  23. package/dist/cli/script-packs/repo-related-files.js +161 -0
  24. package/dist/cli/script-packs/repo-secret-risk-scan.js +147 -0
  25. package/dist/core/change-impact.js +383 -0
  26. package/dist/core/change-verification.js +32 -5
  27. package/dist/core/code-outline.js +460 -79
  28. package/dist/core/config-chain.js +595 -0
  29. package/dist/core/config-loading.js +121 -4
  30. package/dist/core/dependency-graph.js +490 -0
  31. package/dist/core/env-contract.js +450 -0
  32. package/dist/core/export-diff.js +359 -0
  33. package/dist/core/line-endings.js +26 -13
  34. package/dist/core/public-json-contracts.js +126 -0
  35. package/dist/core/reference-drift.js +388 -0
  36. package/dist/core/related-files.js +493 -0
  37. package/dist/core/route-outline.js +964 -0
  38. package/dist/core/script-pack-suggestions.js +131 -5
  39. package/dist/core/secret-risk-scan.js +440 -0
  40. package/dist/core/source-anchors.js +13 -1
  41. package/package.json +1 -1
  42. package/schemas/README.md +44 -6
  43. package/schemas/change-impact-report.schema.json +150 -0
  44. package/schemas/code-outline-report.schema.json +1 -1
  45. package/schemas/code-symbol-read-report.schema.json +64 -4
  46. package/schemas/commands.schema.json +12 -0
  47. package/schemas/config-chain-report.schema.json +187 -0
  48. package/schemas/dependency-graph-report.schema.json +149 -0
  49. package/schemas/env-contract-report.schema.json +203 -0
  50. package/schemas/export-diff-report.schema.json +220 -0
  51. package/schemas/reference-drift-report.schema.json +166 -0
  52. package/schemas/related-files-report.schema.json +145 -0
  53. package/schemas/route-outline-report.schema.json +200 -0
  54. package/schemas/secret-risk-scan-report.schema.json +152 -0
  55. package/templates/default/common/.mustflow/config/commands.toml +21 -0
  56. package/templates/default/i18n.toml +21 -9
  57. package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +1 -1
  58. package/templates/default/locales/en/.mustflow/skills/INDEX.md +8 -2
  59. package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +28 -11
  60. package/templates/default/locales/en/.mustflow/skills/astro-code-change/SKILL.md +71 -27
  61. package/templates/default/locales/en/.mustflow/skills/cross-agent-session-reference/SKILL.md +146 -0
  62. package/templates/default/locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md +3 -1
  63. package/templates/default/locales/en/.mustflow/skills/github-contribution-quality-gate/SKILL.md +48 -11
  64. package/templates/default/locales/en/.mustflow/skills/javascript-code-change/SKILL.md +15 -13
  65. package/templates/default/locales/en/.mustflow/skills/node-code-change/SKILL.md +16 -14
  66. package/templates/default/locales/en/.mustflow/skills/routes.toml +21 -9
  67. package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +3 -1
  68. package/templates/default/locales/en/.mustflow/skills/test-suite-performance-review/SKILL.md +314 -0
  69. package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +13 -10
  70. package/templates/default/manifest.toml +15 -1
@@ -0,0 +1,181 @@
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 { DEPENDENCY_GRAPH_SCRIPT_REF, inspectDependencyGraph, } from '../../core/dependency-graph.js';
6
+ const DEPENDENCY_GRAPH_OPTIONS = [
7
+ { name: '--json', kind: 'boolean' },
8
+ { name: '--max-files', kind: 'string' },
9
+ { name: '--max-file-bytes', kind: 'string' },
10
+ { name: '--max-depth', kind: 'string' },
11
+ { name: '--max-nodes', kind: 'string' },
12
+ { name: '--max-edges', 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, 'dependencyGraph.error.invalidPositiveInteger', { option, value }) };
20
+ }
21
+ const parsed = Number(value);
22
+ if (!Number.isSafeInteger(parsed)) {
23
+ return { value: null, error: t(lang, 'dependencyGraph.error.invalidPositiveInteger', { option, value }) };
24
+ }
25
+ return { value: parsed };
26
+ }
27
+ export function getCodeDependencyGraphHelp(lang = 'en') {
28
+ return renderHelp({
29
+ usage: 'mf script-pack run code/dependency-graph scan <path...> [options]',
30
+ summary: t(lang, 'dependencyGraph.help.summary'),
31
+ options: [
32
+ { label: '--max-depth <count>', description: t(lang, 'dependencyGraph.help.option.maxDepth') },
33
+ { label: '--max-files <count>', description: t(lang, 'dependencyGraph.help.option.maxFiles') },
34
+ { label: '--max-file-bytes <bytes>', description: t(lang, 'dependencyGraph.help.option.maxFileBytes') },
35
+ { label: '--max-nodes <count>', description: t(lang, 'dependencyGraph.help.option.maxNodes') },
36
+ { label: '--max-edges <count>', description: t(lang, 'dependencyGraph.help.option.maxEdges') },
37
+ { label: '--json', description: t(lang, 'cli.option.json') },
38
+ { label: '-h, --help', description: t(lang, 'cli.option.help') },
39
+ ],
40
+ examples: [
41
+ 'mf script-pack run code/dependency-graph scan src/cli/index.ts --json',
42
+ 'mf script-pack run code/dependency-graph scan src/core --max-depth 3 --json',
43
+ 'mf script-pack run code/dependency-graph scan src tests --max-nodes 120 --max-edges 300 --json',
44
+ ],
45
+ exitCodes: [
46
+ { label: '0', description: t(lang, 'dependencyGraph.help.exit.ok') },
47
+ { label: '1', description: t(lang, 'dependencyGraph.help.exit.fail') },
48
+ ],
49
+ }, lang);
50
+ }
51
+ function parseDependencyGraphOptions(args, lang) {
52
+ const [action, ...rest] = args;
53
+ const parsed = parseCliOptions(rest, DEPENDENCY_GRAPH_OPTIONS, { allowPositionals: true });
54
+ const json = hasParsedCliOption(parsed, '--json');
55
+ const maxFiles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-files'), '--max-files', lang);
56
+ const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
57
+ const maxDepth = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-depth'), '--max-depth', lang);
58
+ const maxNodes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-nodes'), '--max-nodes', lang);
59
+ const maxEdges = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-edges'), '--max-edges', lang);
60
+ const positiveOptions = [maxFiles, maxFileBytes, maxDepth, maxNodes, maxEdges];
61
+ if (action !== 'scan') {
62
+ return {
63
+ action: 'scan',
64
+ json,
65
+ paths: parsed.positionals,
66
+ maxFiles: maxFiles.value,
67
+ maxFileBytes: maxFileBytes.value,
68
+ maxDepth: maxDepth.value,
69
+ maxNodes: maxNodes.value,
70
+ maxEdges: maxEdges.value,
71
+ error: action ? t(lang, 'dependencyGraph.error.unknownAction', { action }) : t(lang, 'dependencyGraph.error.missingAction'),
72
+ };
73
+ }
74
+ if (parsed.error) {
75
+ return {
76
+ action,
77
+ json,
78
+ paths: parsed.positionals,
79
+ maxFiles: maxFiles.value,
80
+ maxFileBytes: maxFileBytes.value,
81
+ maxDepth: maxDepth.value,
82
+ maxNodes: maxNodes.value,
83
+ maxEdges: maxEdges.value,
84
+ error: formatCliOptionParseError(parsed.error, lang),
85
+ };
86
+ }
87
+ for (const candidate of positiveOptions) {
88
+ if (candidate.error) {
89
+ return {
90
+ action,
91
+ json,
92
+ paths: parsed.positionals,
93
+ maxFiles: maxFiles.value,
94
+ maxFileBytes: maxFileBytes.value,
95
+ maxDepth: maxDepth.value,
96
+ maxNodes: maxNodes.value,
97
+ maxEdges: maxEdges.value,
98
+ error: candidate.error,
99
+ };
100
+ }
101
+ }
102
+ if (parsed.positionals.length === 0) {
103
+ return {
104
+ action,
105
+ json,
106
+ paths: parsed.positionals,
107
+ maxFiles: maxFiles.value,
108
+ maxFileBytes: maxFileBytes.value,
109
+ maxDepth: maxDepth.value,
110
+ maxNodes: maxNodes.value,
111
+ maxEdges: maxEdges.value,
112
+ error: t(lang, 'dependencyGraph.error.missingPath'),
113
+ };
114
+ }
115
+ return {
116
+ action,
117
+ json,
118
+ paths: parsed.positionals,
119
+ maxFiles: maxFiles.value,
120
+ maxFileBytes: maxFileBytes.value,
121
+ maxDepth: maxDepth.value,
122
+ maxNodes: maxNodes.value,
123
+ maxEdges: maxEdges.value,
124
+ };
125
+ }
126
+ function renderDependencyGraphSummary(report, lang) {
127
+ const lines = [
128
+ t(lang, 'dependencyGraph.title'),
129
+ `${t(lang, 'scriptPack.label.script')}: ${DEPENDENCY_GRAPH_SCRIPT_REF}`,
130
+ `${t(lang, 'label.status')}: ${report.status}`,
131
+ `${t(lang, 'dependencyGraph.label.targets')}: ${report.targets.length}`,
132
+ `${t(lang, 'dependencyGraph.label.nodes')}: ${report.nodes.length}`,
133
+ `${t(lang, 'dependencyGraph.label.edges')}: ${report.edges.length}`,
134
+ `${t(lang, 'dependencyGraph.label.cycles')}: ${report.cycles.length}`,
135
+ `${t(lang, 'dependencyGraph.label.truncated')}: ${report.truncated ? t(lang, 'value.yes') : t(lang, 'value.no')}`,
136
+ ];
137
+ for (const edge of report.edges.slice(0, 40)) {
138
+ lines.push(`- ${edge.source_path}:${edge.line} -> ${edge.target_path} (${edge.kind}, ${edge.specifier})`);
139
+ }
140
+ if (report.cycles.length > 0) {
141
+ lines.push(t(lang, 'dependencyGraph.label.cycleList'));
142
+ for (const cycle of report.cycles) {
143
+ lines.push(`- ${cycle.join(' -> ')}`);
144
+ }
145
+ }
146
+ if (report.findings.length > 0) {
147
+ lines.push(t(lang, 'dependencyGraph.label.findings'), ...report.findings.map((finding) => `- ${finding.path}: ${finding.code} (${finding.message})`));
148
+ }
149
+ if (report.issues.length > 0) {
150
+ lines.push(t(lang, 'dependencyGraph.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
151
+ }
152
+ if (report.edges.length === 0 && report.findings.length === 0 && report.issues.length === 0) {
153
+ lines.push(t(lang, 'dependencyGraph.clean'));
154
+ }
155
+ return lines.join('\n');
156
+ }
157
+ export function runCodeDependencyGraphScript(args, reporter, lang = 'en') {
158
+ if (hasCliOptionToken(args, '--help', ['-h'])) {
159
+ reporter.stdout(getCodeDependencyGraphHelp(lang));
160
+ return 0;
161
+ }
162
+ const options = parseDependencyGraphOptions(args, lang);
163
+ if (options.error) {
164
+ printUsageError(reporter, options.error, 'mf script-pack run code/dependency-graph --help', getCodeDependencyGraphHelp(lang), lang);
165
+ return 1;
166
+ }
167
+ const report = inspectDependencyGraph(resolveMustflowRoot(), {
168
+ paths: options.paths,
169
+ maxFiles: options.maxFiles ?? undefined,
170
+ maxFileBytes: options.maxFileBytes ?? undefined,
171
+ maxDepth: options.maxDepth ?? undefined,
172
+ maxNodes: options.maxNodes ?? undefined,
173
+ maxEdges: options.maxEdges ?? undefined,
174
+ });
175
+ if (options.json) {
176
+ reporter.stdout(JSON.stringify(report, null, 2));
177
+ return report.ok ? 0 : 1;
178
+ }
179
+ reporter.stdout(renderDependencyGraphSummary(report, lang));
180
+ return report.ok ? 0 : 1;
181
+ }
@@ -0,0 +1,160 @@
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_EXPORT_DIFF_SCRIPT_REF, inspectExportDiff, } from '../../core/export-diff.js';
6
+ const EXPORT_DIFF_OPTIONS = [
7
+ { name: '--json', kind: 'boolean' },
8
+ { name: '--base', kind: 'string' },
9
+ { name: '--head', kind: 'string' },
10
+ { name: '--max-files', kind: 'string' },
11
+ { name: '--max-file-bytes', kind: 'string' },
12
+ ];
13
+ function parsePositiveInteger(value, option, lang) {
14
+ if (value === null) {
15
+ return { value: null };
16
+ }
17
+ if (!/^[1-9]\d*$/u.test(value)) {
18
+ return { value: null, error: t(lang, 'exportDiff.error.invalidPositiveInteger', { option, value }) };
19
+ }
20
+ const parsed = Number(value);
21
+ if (!Number.isSafeInteger(parsed)) {
22
+ return { value: null, error: t(lang, 'exportDiff.error.invalidPositiveInteger', { option, value }) };
23
+ }
24
+ return { value: parsed };
25
+ }
26
+ export function getCodeExportDiffHelp(lang = 'en') {
27
+ return renderHelp({
28
+ usage: 'mf script-pack run code/export-diff compare [path...] [options]',
29
+ summary: t(lang, 'exportDiff.help.summary'),
30
+ options: [
31
+ { label: '--base <ref>', description: t(lang, 'exportDiff.help.option.base') },
32
+ { label: '--head <ref>', description: t(lang, 'exportDiff.help.option.head') },
33
+ { label: '--max-files <count>', description: t(lang, 'exportDiff.help.option.maxFiles') },
34
+ { label: '--max-file-bytes <bytes>', description: t(lang, 'exportDiff.help.option.maxFileBytes') },
35
+ { label: '--json', description: t(lang, 'cli.option.json') },
36
+ { label: '-h, --help', description: t(lang, 'cli.option.help') },
37
+ ],
38
+ examples: [
39
+ 'mf script-pack run code/export-diff compare --base HEAD --json',
40
+ 'mf script-pack run code/export-diff compare src --base HEAD~1 --head HEAD --json',
41
+ 'mf script-pack run code/export-diff compare src/index.ts --max-files 20 --json',
42
+ ],
43
+ exitCodes: [
44
+ { label: '0', description: t(lang, 'exportDiff.help.exit.ok') },
45
+ { label: '1', description: t(lang, 'exportDiff.help.exit.fail') },
46
+ ],
47
+ }, lang);
48
+ }
49
+ function parseExportDiffOptions(args, lang) {
50
+ const [action, ...rest] = args;
51
+ const parsed = parseCliOptions(rest, EXPORT_DIFF_OPTIONS, { allowPositionals: true });
52
+ const json = hasParsedCliOption(parsed, '--json');
53
+ const baseRef = getParsedCliStringOption(parsed, '--base');
54
+ const headRef = getParsedCliStringOption(parsed, '--head');
55
+ const maxFiles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-files'), '--max-files', lang);
56
+ const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
57
+ if (action !== 'compare') {
58
+ return {
59
+ action: 'compare',
60
+ json,
61
+ baseRef,
62
+ headRef,
63
+ paths: parsed.positionals,
64
+ maxFiles: maxFiles.value,
65
+ maxFileBytes: maxFileBytes.value,
66
+ error: action ? t(lang, 'exportDiff.error.unknownAction', { action }) : t(lang, 'exportDiff.error.missingAction'),
67
+ };
68
+ }
69
+ if (parsed.error) {
70
+ return {
71
+ action,
72
+ json,
73
+ baseRef,
74
+ headRef,
75
+ paths: parsed.positionals,
76
+ maxFiles: maxFiles.value,
77
+ maxFileBytes: maxFileBytes.value,
78
+ error: formatCliOptionParseError(parsed.error, lang),
79
+ };
80
+ }
81
+ for (const candidate of [maxFiles, maxFileBytes]) {
82
+ if (candidate.error) {
83
+ return {
84
+ action,
85
+ json,
86
+ baseRef,
87
+ headRef,
88
+ paths: parsed.positionals,
89
+ maxFiles: maxFiles.value,
90
+ maxFileBytes: maxFileBytes.value,
91
+ error: candidate.error,
92
+ };
93
+ }
94
+ }
95
+ return {
96
+ action,
97
+ json,
98
+ baseRef,
99
+ headRef,
100
+ paths: parsed.positionals,
101
+ maxFiles: maxFiles.value,
102
+ maxFileBytes: maxFileBytes.value,
103
+ };
104
+ }
105
+ function renderExportDiffSummary(report, lang) {
106
+ const lines = [
107
+ t(lang, 'exportDiff.title'),
108
+ `${t(lang, 'scriptPack.label.script')}: ${CODE_EXPORT_DIFF_SCRIPT_REF}`,
109
+ `${t(lang, 'label.status')}: ${report.status}`,
110
+ `${t(lang, 'exportDiff.label.files')}: ${report.summary.files_changed}`,
111
+ `${t(lang, 'exportDiff.label.added')}: ${report.summary.added}`,
112
+ `${t(lang, 'exportDiff.label.removed')}: ${report.summary.removed}`,
113
+ `${t(lang, 'exportDiff.label.changed')}: ${report.summary.changed}`,
114
+ ];
115
+ if (report.exports.length > 0) {
116
+ lines.push(t(lang, 'exportDiff.label.exports'));
117
+ for (const entry of report.exports.filter((candidate) => candidate.change !== 'unchanged')) {
118
+ const beforeReturn = entry.before?.return_type ?? t(lang, 'value.none');
119
+ const afterReturn = entry.after?.return_type ?? t(lang, 'value.none');
120
+ lines.push(`- ${entry.path}: ${entry.change} ${entry.kind} ${entry.name} (${entry.compatibility}, ${beforeReturn} -> ${afterReturn})`);
121
+ }
122
+ }
123
+ if (report.findings.length > 0) {
124
+ lines.push(t(lang, 'exportDiff.label.findings'));
125
+ for (const finding of report.findings) {
126
+ lines.push(`- ${finding.path}: ${finding.code} (${finding.message})`);
127
+ }
128
+ }
129
+ if (report.issues.length > 0) {
130
+ lines.push(t(lang, 'exportDiff.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
131
+ }
132
+ if (report.exports.length === 0 && report.findings.length === 0 && report.issues.length === 0) {
133
+ lines.push(t(lang, 'exportDiff.clean'));
134
+ }
135
+ return lines.join('\n');
136
+ }
137
+ export function runCodeExportDiffScript(args, reporter, lang = 'en') {
138
+ if (hasCliOptionToken(args, '--help', ['-h'])) {
139
+ reporter.stdout(getCodeExportDiffHelp(lang));
140
+ return 0;
141
+ }
142
+ const options = parseExportDiffOptions(args, lang);
143
+ if (options.error) {
144
+ printUsageError(reporter, options.error, 'mf script-pack run code/export-diff --help', getCodeExportDiffHelp(lang), lang);
145
+ return 1;
146
+ }
147
+ const report = inspectExportDiff(resolveMustflowRoot(), {
148
+ baseRef: options.baseRef ?? undefined,
149
+ headRef: options.headRef,
150
+ paths: options.paths,
151
+ maxFiles: options.maxFiles ?? undefined,
152
+ maxFileBytes: options.maxFileBytes ?? undefined,
153
+ });
154
+ if (options.json) {
155
+ reporter.stdout(JSON.stringify(report, null, 2));
156
+ return report.ok ? 0 : 1;
157
+ }
158
+ reporter.stdout(renderExportDiffSummary(report, lang));
159
+ return report.ok ? 0 : 1;
160
+ }
@@ -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
+ }