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.
- package/README.md +40 -3
- package/dist/cli/commands/docs.js +86 -2
- package/dist/cli/commands/script-pack.js +9 -0
- package/dist/cli/i18n/en.js +180 -2
- package/dist/cli/i18n/es.js +180 -2
- package/dist/cli/i18n/fr.js +180 -2
- package/dist/cli/i18n/hi.js +180 -2
- package/dist/cli/i18n/ko.js +180 -2
- package/dist/cli/i18n/zh.js +180 -2
- package/dist/cli/lib/repo-map.js +27 -6
- package/dist/cli/lib/run-root-trust.js +15 -1
- package/dist/cli/lib/script-pack-registry.js +275 -6
- package/dist/cli/lib/validation/index.js +2 -2
- package/dist/cli/lib/validation/primitives.js +4 -1
- package/dist/cli/script-packs/code-change-impact.js +172 -0
- package/dist/cli/script-packs/code-dependency-graph.js +181 -0
- 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-env-contract.js +156 -0
- package/dist/cli/script-packs/repo-related-files.js +161 -0
- package/dist/cli/script-packs/repo-secret-risk-scan.js +147 -0
- package/dist/core/change-impact.js +383 -0
- package/dist/core/change-verification.js +32 -5
- package/dist/core/code-outline.js +460 -79
- package/dist/core/config-chain.js +595 -0
- package/dist/core/config-loading.js +121 -4
- package/dist/core/dependency-graph.js +490 -0
- package/dist/core/env-contract.js +450 -0
- package/dist/core/export-diff.js +359 -0
- package/dist/core/line-endings.js +26 -13
- package/dist/core/public-json-contracts.js +126 -0
- package/dist/core/reference-drift.js +388 -0
- package/dist/core/related-files.js +493 -0
- package/dist/core/route-outline.js +964 -0
- package/dist/core/script-pack-suggestions.js +131 -5
- package/dist/core/secret-risk-scan.js +440 -0
- package/dist/core/source-anchors.js +13 -1
- package/package.json +1 -1
- package/schemas/README.md +44 -6
- package/schemas/change-impact-report.schema.json +150 -0
- package/schemas/code-outline-report.schema.json +1 -1
- package/schemas/code-symbol-read-report.schema.json +64 -4
- package/schemas/commands.schema.json +12 -0
- package/schemas/config-chain-report.schema.json +187 -0
- package/schemas/dependency-graph-report.schema.json +149 -0
- package/schemas/env-contract-report.schema.json +203 -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/schemas/secret-risk-scan-report.schema.json +152 -0
- package/templates/default/common/.mustflow/config/commands.toml +21 -0
- package/templates/default/i18n.toml +21 -9
- package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +1 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +8 -2
- package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +28 -11
- package/templates/default/locales/en/.mustflow/skills/astro-code-change/SKILL.md +71 -27
- package/templates/default/locales/en/.mustflow/skills/cross-agent-session-reference/SKILL.md +146 -0
- package/templates/default/locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md +3 -1
- package/templates/default/locales/en/.mustflow/skills/github-contribution-quality-gate/SKILL.md +48 -11
- package/templates/default/locales/en/.mustflow/skills/javascript-code-change/SKILL.md +15 -13
- package/templates/default/locales/en/.mustflow/skills/node-code-change/SKILL.md +16 -14
- package/templates/default/locales/en/.mustflow/skills/routes.toml +21 -9
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +3 -1
- package/templates/default/locales/en/.mustflow/skills/test-suite-performance-review/SKILL.md +314 -0
- package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +13 -10
- 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
|
-
|
|
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
|
+
}
|