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,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
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
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 { ENV_CONTRACT_SCRIPT_REF, inspectEnvContract } from '../../core/env-contract.js';
|
|
6
|
+
const ENV_CONTRACT_OPTIONS = [
|
|
7
|
+
{ name: '--json', kind: 'boolean' },
|
|
8
|
+
{ name: '--max-files', kind: 'string' },
|
|
9
|
+
{ name: '--max-file-bytes', kind: 'string' },
|
|
10
|
+
{ name: '--max-keys', kind: 'string' },
|
|
11
|
+
];
|
|
12
|
+
function parsePositiveInteger(value, option, lang) {
|
|
13
|
+
if (value === null) {
|
|
14
|
+
return { value: null };
|
|
15
|
+
}
|
|
16
|
+
if (!/^[1-9]\d*$/u.test(value)) {
|
|
17
|
+
return { value: null, error: t(lang, 'envContract.error.invalidPositiveInteger', { option, value }) };
|
|
18
|
+
}
|
|
19
|
+
const parsed = Number(value);
|
|
20
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
21
|
+
return { value: null, error: t(lang, 'envContract.error.invalidPositiveInteger', { option, value }) };
|
|
22
|
+
}
|
|
23
|
+
return { value: parsed };
|
|
24
|
+
}
|
|
25
|
+
export function getRepoEnvContractHelp(lang = 'en') {
|
|
26
|
+
return renderHelp({
|
|
27
|
+
usage: 'mf script-pack run repo/env-contract scan [path...] [options]',
|
|
28
|
+
summary: t(lang, 'envContract.help.summary'),
|
|
29
|
+
options: [
|
|
30
|
+
{ label: '--max-files <count>', description: t(lang, 'envContract.help.option.maxFiles') },
|
|
31
|
+
{ label: '--max-file-bytes <bytes>', description: t(lang, 'envContract.help.option.maxFileBytes') },
|
|
32
|
+
{ label: '--max-keys <count>', description: t(lang, 'envContract.help.option.maxKeys') },
|
|
33
|
+
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
34
|
+
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
35
|
+
],
|
|
36
|
+
examples: [
|
|
37
|
+
'mf script-pack run repo/env-contract scan --json',
|
|
38
|
+
'mf script-pack run repo/env-contract scan src .env.example --json',
|
|
39
|
+
'mf script-pack run repo/env-contract scan .github/workflows README.md --max-keys 120 --json',
|
|
40
|
+
],
|
|
41
|
+
exitCodes: [
|
|
42
|
+
{ label: '0', description: t(lang, 'envContract.help.exit.ok') },
|
|
43
|
+
{ label: '1', description: t(lang, 'envContract.help.exit.fail') },
|
|
44
|
+
],
|
|
45
|
+
}, lang);
|
|
46
|
+
}
|
|
47
|
+
function parseEnvContractOptions(args, lang) {
|
|
48
|
+
const [action, ...rest] = args;
|
|
49
|
+
const parsed = parseCliOptions(rest, ENV_CONTRACT_OPTIONS, { allowPositionals: true });
|
|
50
|
+
const json = hasParsedCliOption(parsed, '--json');
|
|
51
|
+
const maxFiles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-files'), '--max-files', lang);
|
|
52
|
+
const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
|
|
53
|
+
const maxKeys = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-keys'), '--max-keys', lang);
|
|
54
|
+
if (action !== 'scan') {
|
|
55
|
+
return {
|
|
56
|
+
action: 'scan',
|
|
57
|
+
json,
|
|
58
|
+
paths: parsed.positionals,
|
|
59
|
+
maxFiles: maxFiles.value,
|
|
60
|
+
maxFileBytes: maxFileBytes.value,
|
|
61
|
+
maxKeys: maxKeys.value,
|
|
62
|
+
error: action ? t(lang, 'envContract.error.unknownAction', { action }) : t(lang, 'envContract.error.missingAction'),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (parsed.error) {
|
|
66
|
+
return {
|
|
67
|
+
action,
|
|
68
|
+
json,
|
|
69
|
+
paths: parsed.positionals,
|
|
70
|
+
maxFiles: maxFiles.value,
|
|
71
|
+
maxFileBytes: maxFileBytes.value,
|
|
72
|
+
maxKeys: maxKeys.value,
|
|
73
|
+
error: formatCliOptionParseError(parsed.error, lang),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
for (const candidate of [maxFiles, maxFileBytes, maxKeys]) {
|
|
77
|
+
if (candidate.error) {
|
|
78
|
+
return {
|
|
79
|
+
action,
|
|
80
|
+
json,
|
|
81
|
+
paths: parsed.positionals,
|
|
82
|
+
maxFiles: maxFiles.value,
|
|
83
|
+
maxFileBytes: maxFileBytes.value,
|
|
84
|
+
maxKeys: maxKeys.value,
|
|
85
|
+
error: candidate.error,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
action,
|
|
91
|
+
json,
|
|
92
|
+
paths: parsed.positionals,
|
|
93
|
+
maxFiles: maxFiles.value,
|
|
94
|
+
maxFileBytes: maxFileBytes.value,
|
|
95
|
+
maxKeys: maxKeys.value,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function renderEnvContractSummary(report, lang) {
|
|
99
|
+
const lines = [
|
|
100
|
+
t(lang, 'envContract.title'),
|
|
101
|
+
`${t(lang, 'scriptPack.label.script')}: ${ENV_CONTRACT_SCRIPT_REF}`,
|
|
102
|
+
`${t(lang, 'label.status')}: ${report.status}`,
|
|
103
|
+
`${t(lang, 'envContract.label.files')}: ${report.summary.file_count}`,
|
|
104
|
+
`${t(lang, 'envContract.label.keys')}: ${report.summary.key_count}`,
|
|
105
|
+
`${t(lang, 'envContract.label.findings')}: ${report.findings.length}`,
|
|
106
|
+
`${t(lang, 'envContract.label.truncated')}: ${report.truncated ? t(lang, 'value.yes') : t(lang, 'value.no')}`,
|
|
107
|
+
];
|
|
108
|
+
for (const entry of report.keys.slice(0, 40)) {
|
|
109
|
+
const flags = [
|
|
110
|
+
entry.used_in_code ? 'code' : null,
|
|
111
|
+
entry.declared_in_example ? 'example' : null,
|
|
112
|
+
entry.documented ? 'docs' : null,
|
|
113
|
+
entry.referenced_in_ci ? 'ci' : null,
|
|
114
|
+
entry.secret_like ? 'secret-like' : null,
|
|
115
|
+
entry.public_like ? 'public-like' : null,
|
|
116
|
+
].filter((flag) => flag !== null);
|
|
117
|
+
lines.push(`- ${entry.key}: ${flags.join(', ') || t(lang, 'value.none')} (${entry.source_count})`);
|
|
118
|
+
}
|
|
119
|
+
if (report.findings.length > 0) {
|
|
120
|
+
lines.push(t(lang, 'envContract.label.findings'));
|
|
121
|
+
for (const finding of report.findings.slice(0, 40)) {
|
|
122
|
+
const key = finding.key ? ` ${finding.key}` : '';
|
|
123
|
+
lines.push(`- ${finding.path}:${key} ${finding.code} (${finding.message})`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (report.issues.length > 0) {
|
|
127
|
+
lines.push(t(lang, 'envContract.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
|
|
128
|
+
}
|
|
129
|
+
if (report.keys.length === 0 && report.findings.length === 0 && report.issues.length === 0) {
|
|
130
|
+
lines.push(t(lang, 'envContract.clean'));
|
|
131
|
+
}
|
|
132
|
+
return lines.join('\n');
|
|
133
|
+
}
|
|
134
|
+
export function runRepoEnvContractScript(args, reporter, lang = 'en') {
|
|
135
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
136
|
+
reporter.stdout(getRepoEnvContractHelp(lang));
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
const options = parseEnvContractOptions(args, lang);
|
|
140
|
+
if (options.error) {
|
|
141
|
+
printUsageError(reporter, options.error, 'mf script-pack run repo/env-contract --help', getRepoEnvContractHelp(lang), lang);
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
const report = inspectEnvContract(resolveMustflowRoot(), {
|
|
145
|
+
paths: options.paths,
|
|
146
|
+
maxFiles: options.maxFiles ?? undefined,
|
|
147
|
+
maxFileBytes: options.maxFileBytes ?? undefined,
|
|
148
|
+
maxKeys: options.maxKeys ?? undefined,
|
|
149
|
+
});
|
|
150
|
+
if (options.json) {
|
|
151
|
+
reporter.stdout(JSON.stringify(report, null, 2));
|
|
152
|
+
return report.ok ? 0 : 1;
|
|
153
|
+
}
|
|
154
|
+
reporter.stdout(renderEnvContractSummary(report, lang));
|
|
155
|
+
return report.ok ? 0 : 1;
|
|
156
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
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 { inspectRelatedFiles, RELATED_FILES_SCRIPT_REF, } from '../../core/related-files.js';
|
|
6
|
+
const RELATED_FILES_OPTIONS = [
|
|
7
|
+
{ name: '--json', kind: 'boolean' },
|
|
8
|
+
{ name: '--max-files', kind: 'string' },
|
|
9
|
+
{ name: '--max-file-bytes', kind: 'string' },
|
|
10
|
+
{ name: '--max-candidates', kind: 'string' },
|
|
11
|
+
];
|
|
12
|
+
function parsePositiveInteger(value, option, lang) {
|
|
13
|
+
if (value === null) {
|
|
14
|
+
return { value: null };
|
|
15
|
+
}
|
|
16
|
+
if (!/^[1-9]\d*$/u.test(value)) {
|
|
17
|
+
return { value: null, error: t(lang, 'relatedFiles.error.invalidPositiveInteger', { option, value }) };
|
|
18
|
+
}
|
|
19
|
+
const parsed = Number(value);
|
|
20
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
21
|
+
return { value: null, error: t(lang, 'relatedFiles.error.invalidPositiveInteger', { option, value }) };
|
|
22
|
+
}
|
|
23
|
+
return { value: parsed };
|
|
24
|
+
}
|
|
25
|
+
export function getRepoRelatedFilesHelp(lang = 'en') {
|
|
26
|
+
return renderHelp({
|
|
27
|
+
usage: 'mf script-pack run repo/related-files map <path...> [options]',
|
|
28
|
+
summary: t(lang, 'relatedFiles.help.summary'),
|
|
29
|
+
options: [
|
|
30
|
+
{ label: '--max-files <count>', description: t(lang, 'relatedFiles.help.option.maxFiles') },
|
|
31
|
+
{ label: '--max-file-bytes <bytes>', description: t(lang, 'relatedFiles.help.option.maxFileBytes') },
|
|
32
|
+
{ label: '--max-candidates <count>', description: t(lang, 'relatedFiles.help.option.maxCandidates') },
|
|
33
|
+
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
34
|
+
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
35
|
+
],
|
|
36
|
+
examples: [
|
|
37
|
+
'mf script-pack run repo/related-files map src/cli/index.ts --json',
|
|
38
|
+
'mf script-pack run repo/related-files map src/core/code-outline.ts --max-candidates 50 --json',
|
|
39
|
+
'mf script-pack run repo/related-files map src tests/cli/package.test.js --max-files 500 --json',
|
|
40
|
+
],
|
|
41
|
+
exitCodes: [
|
|
42
|
+
{ label: '0', description: t(lang, 'relatedFiles.help.exit.ok') },
|
|
43
|
+
{ label: '1', description: t(lang, 'relatedFiles.help.exit.fail') },
|
|
44
|
+
],
|
|
45
|
+
}, lang);
|
|
46
|
+
}
|
|
47
|
+
function parseRelatedFilesOptions(args, lang) {
|
|
48
|
+
const [action, ...rest] = args;
|
|
49
|
+
const parsed = parseCliOptions(rest, RELATED_FILES_OPTIONS, { allowPositionals: true });
|
|
50
|
+
const json = hasParsedCliOption(parsed, '--json');
|
|
51
|
+
const maxFiles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-files'), '--max-files', lang);
|
|
52
|
+
const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
|
|
53
|
+
const maxCandidates = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-candidates'), '--max-candidates', lang);
|
|
54
|
+
if (action !== 'map') {
|
|
55
|
+
return {
|
|
56
|
+
action: 'map',
|
|
57
|
+
json,
|
|
58
|
+
paths: parsed.positionals,
|
|
59
|
+
maxFiles: maxFiles.value,
|
|
60
|
+
maxFileBytes: maxFileBytes.value,
|
|
61
|
+
maxCandidates: maxCandidates.value,
|
|
62
|
+
error: action ? t(lang, 'relatedFiles.error.unknownAction', { action }) : t(lang, 'relatedFiles.error.missingAction'),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (parsed.error) {
|
|
66
|
+
return {
|
|
67
|
+
action,
|
|
68
|
+
json,
|
|
69
|
+
paths: parsed.positionals,
|
|
70
|
+
maxFiles: maxFiles.value,
|
|
71
|
+
maxFileBytes: maxFileBytes.value,
|
|
72
|
+
maxCandidates: maxCandidates.value,
|
|
73
|
+
error: formatCliOptionParseError(parsed.error, lang),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
for (const candidate of [maxFiles, maxFileBytes, maxCandidates]) {
|
|
77
|
+
if (candidate.error) {
|
|
78
|
+
return {
|
|
79
|
+
action,
|
|
80
|
+
json,
|
|
81
|
+
paths: parsed.positionals,
|
|
82
|
+
maxFiles: maxFiles.value,
|
|
83
|
+
maxFileBytes: maxFileBytes.value,
|
|
84
|
+
maxCandidates: maxCandidates.value,
|
|
85
|
+
error: candidate.error,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (parsed.positionals.length === 0) {
|
|
90
|
+
return {
|
|
91
|
+
action,
|
|
92
|
+
json,
|
|
93
|
+
paths: parsed.positionals,
|
|
94
|
+
maxFiles: maxFiles.value,
|
|
95
|
+
maxFileBytes: maxFileBytes.value,
|
|
96
|
+
maxCandidates: maxCandidates.value,
|
|
97
|
+
error: t(lang, 'relatedFiles.error.missingPath'),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
action,
|
|
102
|
+
json,
|
|
103
|
+
paths: parsed.positionals,
|
|
104
|
+
maxFiles: maxFiles.value,
|
|
105
|
+
maxFileBytes: maxFileBytes.value,
|
|
106
|
+
maxCandidates: maxCandidates.value,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function renderRelatedFilesSummary(report, lang) {
|
|
110
|
+
const lines = [
|
|
111
|
+
t(lang, 'relatedFiles.title'),
|
|
112
|
+
`${t(lang, 'scriptPack.label.script')}: ${RELATED_FILES_SCRIPT_REF}`,
|
|
113
|
+
`${t(lang, 'label.status')}: ${report.status}`,
|
|
114
|
+
`${t(lang, 'relatedFiles.label.targets')}: ${report.targets.length}`,
|
|
115
|
+
`${t(lang, 'relatedFiles.label.candidates')}: ${report.candidates.length}`,
|
|
116
|
+
`${t(lang, 'relatedFiles.label.truncated')}: ${report.truncated ? t(lang, 'value.yes') : t(lang, 'value.no')}`,
|
|
117
|
+
];
|
|
118
|
+
if (report.candidates.length > 0) {
|
|
119
|
+
lines.push(t(lang, 'relatedFiles.label.related'));
|
|
120
|
+
for (const candidate of report.candidates) {
|
|
121
|
+
const line = candidate.line === null ? '' : `:${candidate.line}`;
|
|
122
|
+
lines.push(`- ${candidate.path}: ${candidate.relationship}, ${t(lang, 'relatedFiles.label.confidence')} ${candidate.confidence} (${candidate.source_path}${line})`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (report.findings.length > 0) {
|
|
126
|
+
lines.push(t(lang, 'relatedFiles.label.findings'));
|
|
127
|
+
for (const finding of report.findings) {
|
|
128
|
+
lines.push(`- ${finding.path}: ${finding.code} (${finding.message})`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (report.issues.length > 0) {
|
|
132
|
+
lines.push(t(lang, 'relatedFiles.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
|
|
133
|
+
}
|
|
134
|
+
if (report.candidates.length === 0 && report.findings.length === 0 && report.issues.length === 0) {
|
|
135
|
+
lines.push(t(lang, 'relatedFiles.clean'));
|
|
136
|
+
}
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
export function runRepoRelatedFilesScript(args, reporter, lang = 'en') {
|
|
140
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
141
|
+
reporter.stdout(getRepoRelatedFilesHelp(lang));
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
const options = parseRelatedFilesOptions(args, lang);
|
|
145
|
+
if (options.error) {
|
|
146
|
+
printUsageError(reporter, options.error, 'mf script-pack run repo/related-files --help', getRepoRelatedFilesHelp(lang), lang);
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
const report = inspectRelatedFiles(resolveMustflowRoot(), {
|
|
150
|
+
paths: options.paths,
|
|
151
|
+
maxFiles: options.maxFiles ?? undefined,
|
|
152
|
+
maxFileBytes: options.maxFileBytes ?? undefined,
|
|
153
|
+
maxCandidates: options.maxCandidates ?? undefined,
|
|
154
|
+
});
|
|
155
|
+
if (options.json) {
|
|
156
|
+
reporter.stdout(JSON.stringify(report, null, 2));
|
|
157
|
+
return report.ok ? 0 : 1;
|
|
158
|
+
}
|
|
159
|
+
reporter.stdout(renderRelatedFilesSummary(report, lang));
|
|
160
|
+
return report.ok ? 0 : 1;
|
|
161
|
+
}
|