mustflow 2.103.16 → 2.103.21

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 (39) hide show
  1. package/README.md +2 -0
  2. package/dist/cli/commands/run/args.js +83 -0
  3. package/dist/cli/commands/run/execution.js +334 -0
  4. package/dist/cli/commands/run/preview.js +29 -0
  5. package/dist/cli/commands/run/profile.js +6 -0
  6. package/dist/cli/commands/run.js +19 -425
  7. package/dist/cli/commands/script-pack.js +1 -0
  8. package/dist/cli/commands/verify.js +15 -18
  9. package/dist/cli/i18n/en.js +27 -0
  10. package/dist/cli/i18n/es.js +27 -0
  11. package/dist/cli/i18n/fr.js +27 -0
  12. package/dist/cli/i18n/hi.js +27 -0
  13. package/dist/cli/i18n/ko.js +27 -0
  14. package/dist/cli/i18n/zh.js +27 -0
  15. package/dist/cli/lib/command-registry.js +92 -0
  16. package/dist/cli/lib/option-parser.js +26 -0
  17. package/dist/cli/lib/script-pack-registry.js +39 -0
  18. package/dist/cli/script-packs/code-module-boundary.js +210 -0
  19. package/dist/cli/script-packs/repo-env-contract.js +4 -17
  20. package/dist/cli/script-packs/repo-secret-risk-scan.js +4 -17
  21. package/dist/cli/script-packs/repo-security-pattern-scan.js +4 -17
  22. package/dist/core/module-boundary.js +523 -0
  23. package/dist/core/public-json-contracts.js +50 -0
  24. package/dist/core/script-pack-suggestions.js +5 -0
  25. package/package.json +1 -1
  26. package/schemas/README.md +12 -0
  27. package/schemas/check-report.schema.json +52 -0
  28. package/schemas/index-report.schema.json +103 -0
  29. package/schemas/module-boundary-report.schema.json +210 -0
  30. package/schemas/search-report.schema.json +102 -0
  31. package/schemas/status-report.schema.json +50 -0
  32. package/templates/default/i18n.toml +10 -4
  33. package/templates/default/locales/en/.mustflow/skills/INDEX.md +7 -1
  34. package/templates/default/locales/en/.mustflow/skills/database-migration-change/SKILL.md +16 -2
  35. package/templates/default/locales/en/.mustflow/skills/http-api-semantics-review/SKILL.md +286 -0
  36. package/templates/default/locales/en/.mustflow/skills/module-boundary-review/SKILL.md +12 -1
  37. package/templates/default/locales/en/.mustflow/skills/payment-integrity-review/SKILL.md +17 -10
  38. package/templates/default/locales/en/.mustflow/skills/routes.toml +6 -0
  39. package/templates/default/manifest.toml +8 -1
@@ -0,0 +1,210 @@
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 { inspectModuleBoundaries, MODULE_BOUNDARY_SCRIPT_REF, } from '../../core/module-boundary.js';
6
+ const MODULE_BOUNDARY_OPTIONS = [
7
+ { name: '--json', kind: 'boolean' },
8
+ { name: '--config', kind: 'string' },
9
+ { name: '--max-files', kind: 'string' },
10
+ { name: '--max-file-bytes', kind: 'string' },
11
+ { name: '--max-depth', kind: 'string' },
12
+ { name: '--max-nodes', kind: 'string' },
13
+ { name: '--max-edges', kind: 'string' },
14
+ { name: '--max-cycles', kind: 'string' },
15
+ { name: '--max-shared-files', kind: 'string' },
16
+ ];
17
+ function parsePositiveInteger(value, option, lang) {
18
+ if (value === null) {
19
+ return { value: null };
20
+ }
21
+ if (!/^[1-9]\d*$/u.test(value)) {
22
+ return { value: null, error: t(lang, 'moduleBoundary.error.invalidPositiveInteger', { option, value }) };
23
+ }
24
+ const parsed = Number(value);
25
+ if (!Number.isSafeInteger(parsed)) {
26
+ return { value: null, error: t(lang, 'moduleBoundary.error.invalidPositiveInteger', { option, value }) };
27
+ }
28
+ return { value: parsed };
29
+ }
30
+ export function getCodeModuleBoundaryHelp(lang = 'en') {
31
+ return renderHelp({
32
+ usage: 'mf script-pack run code/module-boundary check <path...> [options]',
33
+ summary: t(lang, 'moduleBoundary.help.summary'),
34
+ options: [
35
+ { label: '--config <path>', description: t(lang, 'moduleBoundary.help.option.config') },
36
+ { label: '--max-depth <count>', description: t(lang, 'moduleBoundary.help.option.maxDepth') },
37
+ { label: '--max-files <count>', description: t(lang, 'moduleBoundary.help.option.maxFiles') },
38
+ { label: '--max-file-bytes <bytes>', description: t(lang, 'moduleBoundary.help.option.maxFileBytes') },
39
+ { label: '--max-nodes <count>', description: t(lang, 'moduleBoundary.help.option.maxNodes') },
40
+ { label: '--max-edges <count>', description: t(lang, 'moduleBoundary.help.option.maxEdges') },
41
+ { label: '--max-cycles <count>', description: t(lang, 'moduleBoundary.help.option.maxCycles') },
42
+ { label: '--max-shared-files <count>', description: t(lang, 'moduleBoundary.help.option.maxSharedFiles') },
43
+ { label: '--json', description: t(lang, 'cli.option.json') },
44
+ { label: '-h, --help', description: t(lang, 'cli.option.help') },
45
+ ],
46
+ examples: [
47
+ 'mf script-pack run code/module-boundary check src --json',
48
+ 'mf script-pack run code/module-boundary check src --config .mustflow/config/module-boundaries.toml --json',
49
+ 'mf script-pack run code/module-boundary check src/features --max-depth 30 --json',
50
+ ],
51
+ exitCodes: [
52
+ { label: '0', description: t(lang, 'moduleBoundary.help.exit.ok') },
53
+ { label: '1', description: t(lang, 'moduleBoundary.help.exit.fail') },
54
+ ],
55
+ }, lang);
56
+ }
57
+ function parseModuleBoundaryOptions(args, lang) {
58
+ const [action, ...rest] = args;
59
+ const parsed = parseCliOptions(rest, MODULE_BOUNDARY_OPTIONS, { allowPositionals: true });
60
+ const json = hasParsedCliOption(parsed, '--json');
61
+ const configPath = getParsedCliStringOption(parsed, '--config');
62
+ const maxFiles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-files'), '--max-files', lang);
63
+ const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
64
+ const maxDepth = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-depth'), '--max-depth', lang);
65
+ const maxNodes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-nodes'), '--max-nodes', lang);
66
+ const maxEdges = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-edges'), '--max-edges', lang);
67
+ const maxCycles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-cycles'), '--max-cycles', lang);
68
+ const maxSharedFiles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-shared-files'), '--max-shared-files', lang);
69
+ const positiveOptions = [maxFiles, maxFileBytes, maxDepth, maxNodes, maxEdges, maxCycles, maxSharedFiles];
70
+ if (action !== 'check') {
71
+ return {
72
+ action: 'check',
73
+ json,
74
+ paths: parsed.positionals,
75
+ configPath,
76
+ maxFiles: maxFiles.value,
77
+ maxFileBytes: maxFileBytes.value,
78
+ maxDepth: maxDepth.value,
79
+ maxNodes: maxNodes.value,
80
+ maxEdges: maxEdges.value,
81
+ maxCycles: maxCycles.value,
82
+ maxSharedFiles: maxSharedFiles.value,
83
+ error: action ? t(lang, 'moduleBoundary.error.unknownAction', { action }) : t(lang, 'moduleBoundary.error.missingAction'),
84
+ };
85
+ }
86
+ if (parsed.error) {
87
+ return {
88
+ action,
89
+ json,
90
+ paths: parsed.positionals,
91
+ configPath,
92
+ maxFiles: maxFiles.value,
93
+ maxFileBytes: maxFileBytes.value,
94
+ maxDepth: maxDepth.value,
95
+ maxNodes: maxNodes.value,
96
+ maxEdges: maxEdges.value,
97
+ maxCycles: maxCycles.value,
98
+ maxSharedFiles: maxSharedFiles.value,
99
+ error: formatCliOptionParseError(parsed.error, lang),
100
+ };
101
+ }
102
+ for (const candidate of positiveOptions) {
103
+ if (candidate.error) {
104
+ return {
105
+ action,
106
+ json,
107
+ paths: parsed.positionals,
108
+ configPath,
109
+ maxFiles: maxFiles.value,
110
+ maxFileBytes: maxFileBytes.value,
111
+ maxDepth: maxDepth.value,
112
+ maxNodes: maxNodes.value,
113
+ maxEdges: maxEdges.value,
114
+ maxCycles: maxCycles.value,
115
+ maxSharedFiles: maxSharedFiles.value,
116
+ error: candidate.error,
117
+ };
118
+ }
119
+ }
120
+ if (parsed.positionals.length === 0) {
121
+ return {
122
+ action,
123
+ json,
124
+ paths: parsed.positionals,
125
+ configPath,
126
+ maxFiles: maxFiles.value,
127
+ maxFileBytes: maxFileBytes.value,
128
+ maxDepth: maxDepth.value,
129
+ maxNodes: maxNodes.value,
130
+ maxEdges: maxEdges.value,
131
+ maxCycles: maxCycles.value,
132
+ maxSharedFiles: maxSharedFiles.value,
133
+ error: t(lang, 'moduleBoundary.error.missingPath'),
134
+ };
135
+ }
136
+ return {
137
+ action,
138
+ json,
139
+ paths: parsed.positionals,
140
+ configPath,
141
+ maxFiles: maxFiles.value,
142
+ maxFileBytes: maxFileBytes.value,
143
+ maxDepth: maxDepth.value,
144
+ maxNodes: maxNodes.value,
145
+ maxEdges: maxEdges.value,
146
+ maxCycles: maxCycles.value,
147
+ maxSharedFiles: maxSharedFiles.value,
148
+ };
149
+ }
150
+ function renderModuleBoundarySummary(report, lang) {
151
+ const lines = [
152
+ t(lang, 'moduleBoundary.title'),
153
+ `${t(lang, 'scriptPack.label.script')}: ${MODULE_BOUNDARY_SCRIPT_REF}`,
154
+ `${t(lang, 'label.status')}: ${report.status}`,
155
+ `${t(lang, 'moduleBoundary.label.config')}: ${report.config.path} (${report.config.status})`,
156
+ `${t(lang, 'moduleBoundary.label.targets')}: ${report.targets.length}`,
157
+ `${t(lang, 'moduleBoundary.label.edges')}: ${report.graph.edge_count}`,
158
+ `${t(lang, 'moduleBoundary.label.rules')}: ${report.rules.length}`,
159
+ `${t(lang, 'moduleBoundary.label.findings')}: ${report.findings.length}`,
160
+ `${t(lang, 'moduleBoundary.label.truncated')}: ${report.truncated ? t(lang, 'value.yes') : t(lang, 'value.no')}`,
161
+ ];
162
+ if (report.findings.length > 0) {
163
+ lines.push(t(lang, 'moduleBoundary.label.findingList'));
164
+ for (const finding of report.findings.slice(0, 80)) {
165
+ const location = finding.line ? `${finding.path}:${finding.line}` : finding.path;
166
+ lines.push(`- ${location}: ${finding.code} (${finding.message})`);
167
+ }
168
+ }
169
+ if (report.shared_metrics.length > 0) {
170
+ lines.push(t(lang, 'moduleBoundary.label.sharedMetrics'));
171
+ for (const metric of report.shared_metrics) {
172
+ lines.push(`- ${metric.path}: ${metric.file_count} files, ${metric.export_count} exports`);
173
+ }
174
+ }
175
+ if (report.issues.length > 0) {
176
+ lines.push(t(lang, 'moduleBoundary.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
177
+ }
178
+ if (report.findings.length === 0 && report.issues.length === 0) {
179
+ lines.push(t(lang, 'moduleBoundary.clean'));
180
+ }
181
+ return lines.join('\n');
182
+ }
183
+ export function runCodeModuleBoundaryScript(args, reporter, lang = 'en') {
184
+ if (hasCliOptionToken(args, '--help', ['-h'])) {
185
+ reporter.stdout(getCodeModuleBoundaryHelp(lang));
186
+ return 0;
187
+ }
188
+ const options = parseModuleBoundaryOptions(args, lang);
189
+ if (options.error) {
190
+ printUsageError(reporter, options.error, 'mf script-pack run code/module-boundary --help', getCodeModuleBoundaryHelp(lang), lang);
191
+ return 1;
192
+ }
193
+ const report = inspectModuleBoundaries(resolveMustflowRoot(), {
194
+ paths: options.paths,
195
+ configPath: options.configPath ?? undefined,
196
+ maxFiles: options.maxFiles ?? undefined,
197
+ maxFileBytes: options.maxFileBytes ?? undefined,
198
+ maxDepth: options.maxDepth ?? undefined,
199
+ maxNodes: options.maxNodes ?? undefined,
200
+ maxEdges: options.maxEdges ?? undefined,
201
+ maxCycles: options.maxCycles ?? undefined,
202
+ maxSharedFiles: options.maxSharedFiles ?? undefined,
203
+ });
204
+ if (options.json) {
205
+ reporter.stdout(JSON.stringify(report, null, 2));
206
+ return report.ok ? 0 : 1;
207
+ }
208
+ reporter.stdout(renderModuleBoundarySummary(report, lang));
209
+ return report.ok ? 0 : 1;
210
+ }
@@ -1,6 +1,6 @@
1
1
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
2
2
  import { t } from '../lib/i18n.js';
3
- import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
3
+ import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parsePositiveIntegerCliOption, parseCliOptions, } from '../lib/option-parser.js';
4
4
  import { resolveMustflowRoot } from '../lib/project-root.js';
5
5
  import { ENV_CONTRACT_SCRIPT_REF, inspectEnvContract } from '../../core/env-contract.js';
6
6
  const ENV_CONTRACT_OPTIONS = [
@@ -9,19 +9,6 @@ const ENV_CONTRACT_OPTIONS = [
9
9
  { name: '--max-file-bytes', kind: 'string' },
10
10
  { name: '--max-keys', kind: 'string' },
11
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
12
  export function getRepoEnvContractHelp(lang = 'en') {
26
13
  return renderHelp({
27
14
  usage: 'mf script-pack run repo/env-contract scan [path...] [options]',
@@ -48,9 +35,9 @@ function parseEnvContractOptions(args, lang) {
48
35
  const [action, ...rest] = args;
49
36
  const parsed = parseCliOptions(rest, ENV_CONTRACT_OPTIONS, { allowPositionals: true });
50
37
  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);
38
+ const maxFiles = parsePositiveIntegerCliOption(getParsedCliStringOption(parsed, '--max-files'), '--max-files', 'envContract.error.invalidPositiveInteger', lang);
39
+ const maxFileBytes = parsePositiveIntegerCliOption(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', 'envContract.error.invalidPositiveInteger', lang);
40
+ const maxKeys = parsePositiveIntegerCliOption(getParsedCliStringOption(parsed, '--max-keys'), '--max-keys', 'envContract.error.invalidPositiveInteger', lang);
54
41
  if (action !== 'scan') {
55
42
  return {
56
43
  action: 'scan',
@@ -1,6 +1,6 @@
1
1
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
2
2
  import { t } from '../lib/i18n.js';
3
- import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
3
+ import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parsePositiveIntegerCliOption, parseCliOptions, } from '../lib/option-parser.js';
4
4
  import { resolveMustflowRoot } from '../lib/project-root.js';
5
5
  import { inspectSecretRiskScan, SECRET_RISK_SCAN_SCRIPT_REF } from '../../core/secret-risk-scan.js';
6
6
  const SECRET_RISK_SCAN_OPTIONS = [
@@ -9,19 +9,6 @@ const SECRET_RISK_SCAN_OPTIONS = [
9
9
  { name: '--max-file-bytes', kind: 'string' },
10
10
  { name: '--max-findings', kind: 'string' },
11
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, 'secretRiskScan.error.invalidPositiveInteger', { option, value }) };
18
- }
19
- const parsed = Number(value);
20
- if (!Number.isSafeInteger(parsed)) {
21
- return { value: null, error: t(lang, 'secretRiskScan.error.invalidPositiveInteger', { option, value }) };
22
- }
23
- return { value: parsed };
24
- }
25
12
  export function getRepoSecretRiskScanHelp(lang = 'en') {
26
13
  return renderHelp({
27
14
  usage: 'mf script-pack run repo/secret-risk-scan scan [path...] [options]',
@@ -48,9 +35,9 @@ function parseSecretRiskScanOptions(args, lang) {
48
35
  const [action, ...rest] = args;
49
36
  const parsed = parseCliOptions(rest, SECRET_RISK_SCAN_OPTIONS, { allowPositionals: true });
50
37
  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 maxFindings = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-findings'), '--max-findings', lang);
38
+ const maxFiles = parsePositiveIntegerCliOption(getParsedCliStringOption(parsed, '--max-files'), '--max-files', 'secretRiskScan.error.invalidPositiveInteger', lang);
39
+ const maxFileBytes = parsePositiveIntegerCliOption(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', 'secretRiskScan.error.invalidPositiveInteger', lang);
40
+ const maxFindings = parsePositiveIntegerCliOption(getParsedCliStringOption(parsed, '--max-findings'), '--max-findings', 'secretRiskScan.error.invalidPositiveInteger', lang);
54
41
  if (action !== 'scan') {
55
42
  return {
56
43
  action: 'scan',
@@ -1,6 +1,6 @@
1
1
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
2
2
  import { t } from '../lib/i18n.js';
3
- import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
3
+ import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parsePositiveIntegerCliOption, parseCliOptions, } from '../lib/option-parser.js';
4
4
  import { resolveMustflowRoot } from '../lib/project-root.js';
5
5
  import { inspectSecurityPatternScan, SECURITY_PATTERN_SCAN_SCRIPT_REF, } from '../../core/security-pattern-scan.js';
6
6
  const SECURITY_PATTERN_SCAN_OPTIONS = [
@@ -9,19 +9,6 @@ const SECURITY_PATTERN_SCAN_OPTIONS = [
9
9
  { name: '--max-file-bytes', kind: 'string' },
10
10
  { name: '--max-findings', kind: 'string' },
11
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, 'securityPatternScan.error.invalidPositiveInteger', { option, value }) };
18
- }
19
- const parsed = Number(value);
20
- if (!Number.isSafeInteger(parsed)) {
21
- return { value: null, error: t(lang, 'securityPatternScan.error.invalidPositiveInteger', { option, value }) };
22
- }
23
- return { value: parsed };
24
- }
25
12
  export function getRepoSecurityPatternScanHelp(lang = 'en') {
26
13
  return renderHelp({
27
14
  usage: 'mf script-pack run repo/security-pattern-scan scan [path...] [options]',
@@ -48,9 +35,9 @@ function parseSecurityPatternScanOptions(args, lang) {
48
35
  const [action, ...rest] = args;
49
36
  const parsed = parseCliOptions(rest, SECURITY_PATTERN_SCAN_OPTIONS, { allowPositionals: true });
50
37
  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 maxFindings = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-findings'), '--max-findings', lang);
38
+ const maxFiles = parsePositiveIntegerCliOption(getParsedCliStringOption(parsed, '--max-files'), '--max-files', 'securityPatternScan.error.invalidPositiveInteger', lang);
39
+ const maxFileBytes = parsePositiveIntegerCliOption(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', 'securityPatternScan.error.invalidPositiveInteger', lang);
40
+ const maxFindings = parsePositiveIntegerCliOption(getParsedCliStringOption(parsed, '--max-findings'), '--max-findings', 'securityPatternScan.error.invalidPositiveInteger', lang);
54
41
  if (action !== 'scan') {
55
42
  return {
56
43
  action: 'scan',