mustflow 2.84.0 → 2.99.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +11 -2
  2. package/dist/cli/commands/script-pack.js +14 -0
  3. package/dist/cli/i18n/en.js +262 -0
  4. package/dist/cli/i18n/es.js +262 -0
  5. package/dist/cli/i18n/fr.js +262 -0
  6. package/dist/cli/i18n/hi.js +262 -0
  7. package/dist/cli/i18n/ko.js +262 -0
  8. package/dist/cli/i18n/zh.js +262 -0
  9. package/dist/cli/lib/repo-map.js +27 -6
  10. package/dist/cli/lib/run-root-trust.js +15 -1
  11. package/dist/cli/lib/script-pack-registry.js +397 -0
  12. package/dist/cli/lib/validation/index.js +2 -2
  13. package/dist/cli/lib/validation/primitives.js +4 -1
  14. package/dist/cli/script-packs/code-change-impact.js +178 -0
  15. package/dist/cli/script-packs/code-dependency-graph.js +181 -0
  16. package/dist/cli/script-packs/code-import-cycle.js +193 -0
  17. package/dist/cli/script-packs/docs-link-integrity.js +145 -0
  18. package/dist/cli/script-packs/repo-approval-gate.js +100 -0
  19. package/dist/cli/script-packs/repo-env-contract.js +156 -0
  20. package/dist/cli/script-packs/repo-git-ignore-audit.js +119 -0
  21. package/dist/cli/script-packs/repo-manifest-lock-drift.js +122 -0
  22. package/dist/cli/script-packs/repo-merge-conflict-scan.js +123 -0
  23. package/dist/cli/script-packs/repo-secret-risk-scan.js +147 -0
  24. package/dist/cli/script-packs/repo-skill-route-audit.js +86 -0
  25. package/dist/cli/script-packs/repo-version-source.js +92 -0
  26. package/dist/cli/script-packs/test-performance-report.js +247 -0
  27. package/dist/cli/script-packs/test-regression-selector.js +167 -0
  28. package/dist/core/change-impact.js +355 -0
  29. package/dist/core/change-surface-classification.js +198 -0
  30. package/dist/core/change-verification.js +32 -5
  31. package/dist/core/config-loading.js +121 -4
  32. package/dist/core/dependency-graph.js +490 -0
  33. package/dist/core/docs-link-integrity.js +443 -0
  34. package/dist/core/env-contract.js +450 -0
  35. package/dist/core/import-cycle.js +152 -0
  36. package/dist/core/line-endings.js +26 -13
  37. package/dist/core/public-json-contracts.js +167 -0
  38. package/dist/core/repo-approval-gate.js +116 -0
  39. package/dist/core/repo-git-ignore-audit.js +302 -0
  40. package/dist/core/repo-manifest-lock-drift.js +321 -0
  41. package/dist/core/repo-merge-conflict-scan.js +335 -0
  42. package/dist/core/repo-version-source.js +82 -0
  43. package/dist/core/route-outline.js +57 -5
  44. package/dist/core/script-pack-suggestions.js +97 -1
  45. package/dist/core/secret-risk-scan.js +440 -0
  46. package/dist/core/skill-route-audit.js +354 -0
  47. package/dist/core/test-performance-report.js +697 -0
  48. package/dist/core/test-regression-selector.js +335 -0
  49. package/package.json +1 -1
  50. package/schemas/README.md +54 -0
  51. package/schemas/change-impact-report.schema.json +184 -0
  52. package/schemas/commands.schema.json +12 -0
  53. package/schemas/dependency-graph-report.schema.json +149 -0
  54. package/schemas/env-contract-report.schema.json +203 -0
  55. package/schemas/import-cycle-report.schema.json +157 -0
  56. package/schemas/link-integrity-report.schema.json +176 -0
  57. package/schemas/repo-approval-gate-report.schema.json +115 -0
  58. package/schemas/repo-git-ignore-audit-report.schema.json +201 -0
  59. package/schemas/repo-manifest-lock-drift-report.schema.json +202 -0
  60. package/schemas/repo-merge-conflict-scan-report.schema.json +169 -0
  61. package/schemas/repo-version-source-report.schema.json +127 -0
  62. package/schemas/secret-risk-scan-report.schema.json +152 -0
  63. package/schemas/skill-route-audit-report.schema.json +144 -0
  64. package/schemas/test-performance-report.schema.json +319 -0
  65. package/schemas/test-regression-selector-report.schema.json +187 -0
  66. package/templates/default/i18n.toml +80 -26
  67. package/templates/default/locales/en/.mustflow/skills/INDEX.md +51 -9
  68. package/templates/default/locales/en/.mustflow/skills/api-access-control-review/SKILL.md +48 -27
  69. package/templates/default/locales/en/.mustflow/skills/api-failure-triage/SKILL.md +270 -0
  70. package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +28 -11
  71. package/templates/default/locales/en/.mustflow/skills/astro-code-change/SKILL.md +71 -27
  72. package/templates/default/locales/en/.mustflow/skills/auth-flow-triage/SKILL.md +192 -0
  73. package/templates/default/locales/en/.mustflow/skills/auth-permission-change/SKILL.md +59 -13
  74. package/templates/default/locales/en/.mustflow/skills/backend-log-evidence-review/SKILL.md +14 -5
  75. package/templates/default/locales/en/.mustflow/skills/cache-integrity-review/SKILL.md +30 -15
  76. package/templates/default/locales/en/.mustflow/skills/change-blast-radius-review/SKILL.md +45 -32
  77. package/templates/default/locales/en/.mustflow/skills/ci-pipeline-triage/SKILL.md +200 -0
  78. package/templates/default/locales/en/.mustflow/skills/clarifying-question-gate/SKILL.md +87 -13
  79. package/templates/default/locales/en/.mustflow/skills/cross-agent-session-reference/SKILL.md +23 -8
  80. package/templates/default/locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md +3 -1
  81. package/templates/default/locales/en/.mustflow/skills/docker-runtime-triage/SKILL.md +191 -0
  82. package/templates/default/locales/en/.mustflow/skills/github-contribution-quality-gate/SKILL.md +48 -11
  83. package/templates/default/locales/en/.mustflow/skills/go-code-change/SKILL.md +18 -13
  84. package/templates/default/locales/en/.mustflow/skills/javascript-code-change/SKILL.md +15 -13
  85. package/templates/default/locales/en/.mustflow/skills/line-ending-hygiene/SKILL.md +18 -10
  86. package/templates/default/locales/en/.mustflow/skills/llm-hallucination-control-review/SKILL.md +4 -1
  87. package/templates/default/locales/en/.mustflow/skills/motion-system-contract-review/SKILL.md +155 -0
  88. package/templates/default/locales/en/.mustflow/skills/next-action-menu/SKILL.md +177 -0
  89. package/templates/default/locales/en/.mustflow/skills/node-code-change/SKILL.md +16 -14
  90. package/templates/default/locales/en/.mustflow/skills/observability-debuggability-review/SKILL.md +15 -7
  91. package/templates/default/locales/en/.mustflow/skills/payment-integrity-review/SKILL.md +59 -35
  92. package/templates/default/locales/en/.mustflow/skills/powershell-code-change/SKILL.md +16 -6
  93. package/templates/default/locales/en/.mustflow/skills/prompt-contract-quality-review/SKILL.md +4 -1
  94. package/templates/default/locales/en/.mustflow/skills/python-code-change/SKILL.md +19 -10
  95. package/templates/default/locales/en/.mustflow/skills/rag-pipeline-triage/SKILL.md +206 -0
  96. package/templates/default/locales/en/.mustflow/skills/routes.toml +69 -9
  97. package/templates/default/locales/en/.mustflow/skills/rust-code-change/SKILL.md +10 -4
  98. package/templates/default/locales/en/.mustflow/skills/search-index-integrity-review/SKILL.md +181 -0
  99. package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +3 -1
  100. package/templates/default/locales/en/.mustflow/skills/service-boundary-architecture/SKILL.md +37 -23
  101. package/templates/default/locales/en/.mustflow/skills/test-suite-performance-review/SKILL.md +323 -0
  102. package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +18 -10
  103. package/templates/default/locales/en/.mustflow/skills/vector-search-integrity-review/SKILL.md +209 -0
  104. package/templates/default/locales/en/.mustflow/skills/version-freshness-check/SKILL.md +16 -14
  105. package/templates/default/manifest.toml +71 -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,193 @@
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 { IMPORT_CYCLE_SCRIPT_REF, inspectImportCycles } from '../../core/import-cycle.js';
6
+ const IMPORT_CYCLE_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
+ { name: '--max-cycles', kind: 'string' },
14
+ ];
15
+ function parsePositiveInteger(value, option, lang) {
16
+ if (value === null) {
17
+ return { value: null };
18
+ }
19
+ if (!/^[1-9]\d*$/u.test(value)) {
20
+ return { value: null, error: t(lang, 'importCycle.error.invalidPositiveInteger', { option, value }) };
21
+ }
22
+ const parsed = Number(value);
23
+ if (!Number.isSafeInteger(parsed)) {
24
+ return { value: null, error: t(lang, 'importCycle.error.invalidPositiveInteger', { option, value }) };
25
+ }
26
+ return { value: parsed };
27
+ }
28
+ export function getCodeImportCycleHelp(lang = 'en') {
29
+ return renderHelp({
30
+ usage: 'mf script-pack run code/import-cycle check <path...> [options]',
31
+ summary: t(lang, 'importCycle.help.summary'),
32
+ options: [
33
+ { label: '--max-depth <count>', description: t(lang, 'importCycle.help.option.maxDepth') },
34
+ { label: '--max-files <count>', description: t(lang, 'importCycle.help.option.maxFiles') },
35
+ { label: '--max-file-bytes <bytes>', description: t(lang, 'importCycle.help.option.maxFileBytes') },
36
+ { label: '--max-nodes <count>', description: t(lang, 'importCycle.help.option.maxNodes') },
37
+ { label: '--max-edges <count>', description: t(lang, 'importCycle.help.option.maxEdges') },
38
+ { label: '--max-cycles <count>', description: t(lang, 'importCycle.help.option.maxCycles') },
39
+ { label: '--json', description: t(lang, 'cli.option.json') },
40
+ { label: '-h, --help', description: t(lang, 'cli.option.help') },
41
+ ],
42
+ examples: [
43
+ 'mf script-pack run code/import-cycle check src --json',
44
+ 'mf script-pack run code/import-cycle check src/core --max-depth 30 --json',
45
+ 'mf script-pack run code/import-cycle check src tests --max-cycles 20 --json',
46
+ ],
47
+ exitCodes: [
48
+ { label: '0', description: t(lang, 'importCycle.help.exit.ok') },
49
+ { label: '1', description: t(lang, 'importCycle.help.exit.fail') },
50
+ ],
51
+ }, lang);
52
+ }
53
+ function parseImportCycleOptions(args, lang) {
54
+ const [action, ...rest] = args;
55
+ const parsed = parseCliOptions(rest, IMPORT_CYCLE_OPTIONS, { allowPositionals: true });
56
+ const json = hasParsedCliOption(parsed, '--json');
57
+ const maxFiles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-files'), '--max-files', lang);
58
+ const maxFileBytes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-file-bytes'), '--max-file-bytes', lang);
59
+ const maxDepth = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-depth'), '--max-depth', lang);
60
+ const maxNodes = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-nodes'), '--max-nodes', lang);
61
+ const maxEdges = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-edges'), '--max-edges', lang);
62
+ const maxCycles = parsePositiveInteger(getParsedCliStringOption(parsed, '--max-cycles'), '--max-cycles', lang);
63
+ const positiveOptions = [maxFiles, maxFileBytes, maxDepth, maxNodes, maxEdges, maxCycles];
64
+ if (action !== 'check') {
65
+ return {
66
+ action: 'check',
67
+ json,
68
+ paths: parsed.positionals,
69
+ maxFiles: maxFiles.value,
70
+ maxFileBytes: maxFileBytes.value,
71
+ maxDepth: maxDepth.value,
72
+ maxNodes: maxNodes.value,
73
+ maxEdges: maxEdges.value,
74
+ maxCycles: maxCycles.value,
75
+ error: action ? t(lang, 'importCycle.error.unknownAction', { action }) : t(lang, 'importCycle.error.missingAction'),
76
+ };
77
+ }
78
+ if (parsed.error) {
79
+ return {
80
+ action,
81
+ json,
82
+ paths: parsed.positionals,
83
+ maxFiles: maxFiles.value,
84
+ maxFileBytes: maxFileBytes.value,
85
+ maxDepth: maxDepth.value,
86
+ maxNodes: maxNodes.value,
87
+ maxEdges: maxEdges.value,
88
+ maxCycles: maxCycles.value,
89
+ error: formatCliOptionParseError(parsed.error, lang),
90
+ };
91
+ }
92
+ for (const candidate of positiveOptions) {
93
+ if (candidate.error) {
94
+ return {
95
+ action,
96
+ json,
97
+ paths: parsed.positionals,
98
+ maxFiles: maxFiles.value,
99
+ maxFileBytes: maxFileBytes.value,
100
+ maxDepth: maxDepth.value,
101
+ maxNodes: maxNodes.value,
102
+ maxEdges: maxEdges.value,
103
+ maxCycles: maxCycles.value,
104
+ error: candidate.error,
105
+ };
106
+ }
107
+ }
108
+ if (parsed.positionals.length === 0) {
109
+ return {
110
+ action,
111
+ json,
112
+ paths: parsed.positionals,
113
+ maxFiles: maxFiles.value,
114
+ maxFileBytes: maxFileBytes.value,
115
+ maxDepth: maxDepth.value,
116
+ maxNodes: maxNodes.value,
117
+ maxEdges: maxEdges.value,
118
+ maxCycles: maxCycles.value,
119
+ error: t(lang, 'importCycle.error.missingPath'),
120
+ };
121
+ }
122
+ return {
123
+ action,
124
+ json,
125
+ paths: parsed.positionals,
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
+ };
133
+ }
134
+ function renderImportCycleSummary(report, lang) {
135
+ const lines = [
136
+ t(lang, 'importCycle.title'),
137
+ `${t(lang, 'scriptPack.label.script')}: ${IMPORT_CYCLE_SCRIPT_REF}`,
138
+ `${t(lang, 'label.status')}: ${report.status}`,
139
+ `${t(lang, 'importCycle.label.targets')}: ${report.targets.length}`,
140
+ `${t(lang, 'importCycle.label.nodes')}: ${report.graph.node_count}`,
141
+ `${t(lang, 'importCycle.label.edges')}: ${report.graph.edge_count}`,
142
+ `${t(lang, 'importCycle.label.cycles')}: ${report.cycles.length}`,
143
+ `${t(lang, 'importCycle.label.truncated')}: ${report.truncated ? t(lang, 'value.yes') : t(lang, 'value.no')}`,
144
+ ];
145
+ if (report.cycles.length > 0) {
146
+ lines.push(t(lang, 'importCycle.label.cycleList'));
147
+ for (const cycle of report.cycles) {
148
+ lines.push(`- ${cycle.cycle_id}: ${cycle.paths.join(' -> ')}`);
149
+ for (const edge of cycle.edges) {
150
+ lines.push(` - ${edge.source_path}:${edge.line} -> ${edge.target_path} (${edge.kind}, ${edge.specifier})`);
151
+ }
152
+ }
153
+ }
154
+ if (report.findings.length > 0) {
155
+ lines.push(t(lang, 'importCycle.label.findings'));
156
+ for (const finding of report.findings) {
157
+ lines.push(`- ${finding.path}: ${finding.code} (${finding.message})`);
158
+ }
159
+ }
160
+ if (report.issues.length > 0) {
161
+ lines.push(t(lang, 'importCycle.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
162
+ }
163
+ if (report.cycles.length === 0 && report.findings.length === 0 && report.issues.length === 0) {
164
+ lines.push(t(lang, 'importCycle.clean'));
165
+ }
166
+ return lines.join('\n');
167
+ }
168
+ export function runCodeImportCycleScript(args, reporter, lang = 'en') {
169
+ if (hasCliOptionToken(args, '--help', ['-h'])) {
170
+ reporter.stdout(getCodeImportCycleHelp(lang));
171
+ return 0;
172
+ }
173
+ const options = parseImportCycleOptions(args, lang);
174
+ if (options.error) {
175
+ printUsageError(reporter, options.error, 'mf script-pack run code/import-cycle --help', getCodeImportCycleHelp(lang), lang);
176
+ return 1;
177
+ }
178
+ const report = inspectImportCycles(resolveMustflowRoot(), {
179
+ paths: options.paths,
180
+ maxFiles: options.maxFiles ?? undefined,
181
+ maxFileBytes: options.maxFileBytes ?? undefined,
182
+ maxDepth: options.maxDepth ?? undefined,
183
+ maxNodes: options.maxNodes ?? undefined,
184
+ maxEdges: options.maxEdges ?? undefined,
185
+ maxCycles: options.maxCycles ?? undefined,
186
+ });
187
+ if (options.json) {
188
+ reporter.stdout(JSON.stringify(report, null, 2));
189
+ return report.ok ? 0 : 1;
190
+ }
191
+ reporter.stdout(renderImportCycleSummary(report, lang));
192
+ return report.ok ? 0 : 1;
193
+ }
@@ -0,0 +1,145 @@
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 { checkLinkIntegrity, LINK_INTEGRITY_SCRIPT_REF, } from '../../core/docs-link-integrity.js';
6
+ const LINK_INTEGRITY_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, 'linkIntegrity.error.invalidPositiveInteger', { option, value }) };
17
+ }
18
+ const parsed = Number(value);
19
+ if (!Number.isSafeInteger(parsed)) {
20
+ return { value: null, error: t(lang, 'linkIntegrity.error.invalidPositiveInteger', { option, value }) };
21
+ }
22
+ return { value: parsed };
23
+ }
24
+ export function getDocsLinkIntegrityHelp(lang = 'en') {
25
+ return renderHelp({
26
+ usage: 'mf script-pack run docs/link-integrity check [path...] [options]',
27
+ summary: t(lang, 'linkIntegrity.help.summary'),
28
+ options: [
29
+ { label: '--max-files <count>', description: t(lang, 'linkIntegrity.help.option.maxFiles') },
30
+ { label: '--max-file-bytes <bytes>', description: t(lang, 'linkIntegrity.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 docs/link-integrity check --json',
36
+ 'mf script-pack run docs/link-integrity check README.md schemas/README.md --json',
37
+ 'mf script-pack run docs/link-integrity check docs-site/src/content/docs --max-files 80 --json',
38
+ ],
39
+ exitCodes: [
40
+ { label: '0', description: t(lang, 'linkIntegrity.help.exit.ok') },
41
+ { label: '1', description: t(lang, 'linkIntegrity.help.exit.fail') },
42
+ ],
43
+ }, lang);
44
+ }
45
+ function parseLinkIntegrityOptions(args, lang) {
46
+ const [action, ...rest] = args;
47
+ const parsed = parseCliOptions(rest, LINK_INTEGRITY_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 !== 'check') {
52
+ return {
53
+ action: 'check',
54
+ json,
55
+ paths: parsed.positionals,
56
+ maxFiles: maxFiles.value,
57
+ maxFileBytes: maxFileBytes.value,
58
+ error: action
59
+ ? t(lang, 'linkIntegrity.error.unknownAction', { action })
60
+ : t(lang, 'linkIntegrity.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
+ return {
86
+ action,
87
+ json,
88
+ paths: parsed.positionals,
89
+ maxFiles: maxFiles.value,
90
+ maxFileBytes: maxFileBytes.value,
91
+ };
92
+ }
93
+ function renderLinkIntegritySummary(report, lang) {
94
+ const lines = [
95
+ t(lang, 'linkIntegrity.title'),
96
+ `${t(lang, 'scriptPack.label.script')}: ${LINK_INTEGRITY_SCRIPT_REF}`,
97
+ `${t(lang, 'label.status')}: ${report.status}`,
98
+ `${t(lang, 'linkIntegrity.label.files')}: ${report.files.length}`,
99
+ `${t(lang, 'linkIntegrity.label.links')}: ${report.links.length}`,
100
+ `${t(lang, 'linkIntegrity.label.findings')}: ${report.findings.length}`,
101
+ ];
102
+ if (report.links.length > 0) {
103
+ lines.push(t(lang, 'linkIntegrity.label.links'));
104
+ for (const link of report.links.slice(0, 40)) {
105
+ const resolved = link.resolved_path === null ? link.target : link.resolved_path;
106
+ lines.push(`- ${link.path}:${link.line}: ${link.kind} ${resolved} (${link.status})`);
107
+ }
108
+ }
109
+ if (report.findings.length > 0) {
110
+ lines.push(t(lang, 'linkIntegrity.label.findings'));
111
+ for (const finding of report.findings) {
112
+ const line = finding.line === undefined ? '' : `:${finding.line}`;
113
+ lines.push(`- ${finding.path}${line}: ${finding.code} (${finding.message})`);
114
+ }
115
+ }
116
+ if (report.issues.length > 0) {
117
+ lines.push(t(lang, 'linkIntegrity.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
118
+ }
119
+ if (report.links.length === 0 && report.findings.length === 0 && report.issues.length === 0) {
120
+ lines.push(t(lang, 'linkIntegrity.clean'));
121
+ }
122
+ return lines.join('\n');
123
+ }
124
+ export function runDocsLinkIntegrityScript(args, reporter, lang = 'en') {
125
+ if (hasCliOptionToken(args, '--help', ['-h'])) {
126
+ reporter.stdout(getDocsLinkIntegrityHelp(lang));
127
+ return 0;
128
+ }
129
+ const options = parseLinkIntegrityOptions(args, lang);
130
+ if (options.error) {
131
+ printUsageError(reporter, options.error, 'mf script-pack run docs/link-integrity --help', getDocsLinkIntegrityHelp(lang), lang);
132
+ return 1;
133
+ }
134
+ const report = checkLinkIntegrity(resolveMustflowRoot(), {
135
+ paths: options.paths,
136
+ maxFiles: options.maxFiles ?? undefined,
137
+ maxFileBytes: options.maxFileBytes ?? undefined,
138
+ });
139
+ if (options.json) {
140
+ reporter.stdout(JSON.stringify(report, null, 2));
141
+ return report.ok ? 0 : 1;
142
+ }
143
+ reporter.stdout(renderLinkIntegritySummary(report, lang));
144
+ return report.ok ? 0 : 1;
145
+ }
@@ -0,0 +1,100 @@
1
+ import { printUsageError, renderHelp } from '../lib/cli-output.js';
2
+ import { t } from '../lib/i18n.js';
3
+ import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
4
+ import { resolveMustflowRoot } from '../lib/project-root.js';
5
+ import { checkRepoApprovalGate, REPO_APPROVAL_GATE_SCRIPT_REF, } from '../../core/repo-approval-gate.js';
6
+ const REPO_APPROVAL_GATE_OPTIONS = [
7
+ { name: '--json', kind: 'boolean' },
8
+ { name: '--action', kind: 'string' },
9
+ ];
10
+ export function getRepoApprovalGateHelp(lang = 'en') {
11
+ return renderHelp({
12
+ usage: 'mf script-pack run repo/approval-gate check --action <type> [options]',
13
+ summary: t(lang, 'approvalGate.help.summary'),
14
+ options: [
15
+ { label: '--action <type>', description: t(lang, 'approvalGate.help.option.action') },
16
+ { label: '--json', description: t(lang, 'cli.option.json') },
17
+ { label: '-h, --help', description: t(lang, 'cli.option.help') },
18
+ ],
19
+ examples: [
20
+ 'mf script-pack run repo/approval-gate check --action git_commit',
21
+ 'mf script-pack run repo/approval-gate check --action dependency_install --json',
22
+ ],
23
+ exitCodes: [
24
+ { label: '0', description: t(lang, 'approvalGate.help.exit.ok') },
25
+ { label: '1', description: t(lang, 'approvalGate.help.exit.fail') },
26
+ ],
27
+ }, lang);
28
+ }
29
+ function parsedStringOccurrences(parsed, name) {
30
+ return parsed.occurrences
31
+ .filter((occurrence) => occurrence.name === name && typeof occurrence.value === 'string')
32
+ .map((occurrence) => String(occurrence.value));
33
+ }
34
+ function parseRepoApprovalGateOptions(args, lang) {
35
+ const [action, ...rest] = args;
36
+ const parsed = parseCliOptions(rest, REPO_APPROVAL_GATE_OPTIONS, { allowPositionals: false });
37
+ const json = hasParsedCliOption(parsed, '--json');
38
+ const actionTypes = parsedStringOccurrences(parsed, '--action').map((entry) => entry.trim()).filter(Boolean);
39
+ if (action !== 'check') {
40
+ return {
41
+ action: 'check',
42
+ json,
43
+ actionTypes,
44
+ error: action
45
+ ? t(lang, 'approvalGate.error.unknownAction', { action })
46
+ : t(lang, 'approvalGate.error.missingAction'),
47
+ };
48
+ }
49
+ if (parsed.error) {
50
+ return { action, json, actionTypes, error: formatCliOptionParseError(parsed.error, lang) };
51
+ }
52
+ if (actionTypes.length === 0) {
53
+ return { action, json, actionTypes, error: t(lang, 'approvalGate.error.missingActionType') };
54
+ }
55
+ return { action, json, actionTypes };
56
+ }
57
+ function renderRepoApprovalGateSummary(report, lang) {
58
+ const lines = [
59
+ t(lang, 'approvalGate.title'),
60
+ `${t(lang, 'scriptPack.label.script')}: ${REPO_APPROVAL_GATE_SCRIPT_REF}`,
61
+ `${t(lang, 'label.status')}: ${report.status}`,
62
+ `${t(lang, 'approvalGate.label.approvalRequired')}: ${report.approval_required ? t(lang, 'value.yes') : t(lang, 'value.no')}`,
63
+ `${t(lang, 'approvalGate.label.configuredRequiredActions')}: ${report.policy.required_for.length}`,
64
+ ];
65
+ if (report.decisions.length > 0) {
66
+ lines.push(t(lang, 'approvalGate.label.decisions'));
67
+ for (const decision of report.decisions) {
68
+ const state = decision.approval_required ? t(lang, 'value.yes') : t(lang, 'value.no');
69
+ lines.push(`- ${decision.action_type}: ${state} (${decision.reason})`);
70
+ }
71
+ }
72
+ if (report.findings.length > 0) {
73
+ lines.push(t(lang, 'approvalGate.label.findings'));
74
+ for (const finding of report.findings) {
75
+ lines.push(`- ${finding.code}: ${finding.message}`);
76
+ }
77
+ }
78
+ if (report.issues.length > 0) {
79
+ lines.push(t(lang, 'approvalGate.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
80
+ }
81
+ return lines.join('\n');
82
+ }
83
+ export function runRepoApprovalGateScript(args, reporter, lang = 'en') {
84
+ if (hasCliOptionToken(args, '--help', ['-h'])) {
85
+ reporter.stdout(getRepoApprovalGateHelp(lang));
86
+ return 0;
87
+ }
88
+ const options = parseRepoApprovalGateOptions(args, lang);
89
+ if (options.error) {
90
+ printUsageError(reporter, options.error, 'mf script-pack run repo/approval-gate --help', getRepoApprovalGateHelp(lang), lang);
91
+ return 1;
92
+ }
93
+ const report = checkRepoApprovalGate(resolveMustflowRoot(), options.actionTypes);
94
+ if (options.json) {
95
+ reporter.stdout(JSON.stringify(report, null, 2));
96
+ return report.ok ? 0 : 1;
97
+ }
98
+ reporter.stdout(renderRepoApprovalGateSummary(report, lang));
99
+ return report.ok ? 0 : 1;
100
+ }