mustflow 2.23.0 → 2.24.2
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 +12 -2
- package/dist/cli/commands/adapters.js +11 -9
- package/dist/cli/commands/api.js +263 -113
- package/dist/cli/commands/check.js +11 -7
- package/dist/cli/commands/classify.js +16 -42
- package/dist/cli/commands/context.js +18 -31
- package/dist/cli/commands/contract-lint.js +12 -7
- package/dist/cli/commands/dashboard.js +65 -114
- package/dist/cli/commands/docs.js +43 -26
- package/dist/cli/commands/doctor.js +11 -7
- package/dist/cli/commands/evidence.js +642 -0
- package/dist/cli/commands/explain-verify.js +1 -59
- package/dist/cli/commands/explain.js +84 -36
- package/dist/cli/commands/handoff.js +13 -17
- package/dist/cli/commands/impact.js +14 -20
- package/dist/cli/commands/index.js +15 -9
- package/dist/cli/commands/init.js +56 -70
- package/dist/cli/commands/line-endings.js +15 -9
- package/dist/cli/commands/map.js +30 -42
- package/dist/cli/commands/next.js +300 -0
- package/dist/cli/commands/onboard.js +136 -0
- package/dist/cli/commands/run.js +47 -42
- package/dist/cli/commands/search.js +43 -69
- package/dist/cli/commands/status.js +9 -6
- package/dist/cli/commands/update.js +16 -10
- package/dist/cli/commands/upgrade.js +9 -6
- package/dist/cli/commands/verify/args.js +55 -249
- package/dist/cli/commands/verify.js +2 -1
- package/dist/cli/commands/version-sources.js +9 -6
- package/dist/cli/commands/version.js +9 -6
- package/dist/cli/commands/workspace.js +564 -0
- package/dist/cli/i18n/en.js +60 -1
- package/dist/cli/i18n/es.js +60 -1
- package/dist/cli/i18n/fr.js +60 -1
- package/dist/cli/i18n/hi.js +60 -1
- package/dist/cli/i18n/ko.js +60 -1
- package/dist/cli/i18n/zh.js +60 -1
- package/dist/cli/index.js +28 -25
- package/dist/cli/lib/agent-context.js +8 -9
- package/dist/cli/lib/command-registry.js +24 -0
- package/dist/cli/lib/dashboard-html/client-script.js +1 -1
- package/dist/cli/lib/local-index/database-path.js +5 -0
- package/dist/cli/lib/local-index/database-read.js +88 -0
- package/dist/cli/lib/local-index/effect-graph-read-model.js +112 -0
- package/dist/cli/lib/local-index/freshness.js +60 -0
- package/dist/cli/lib/local-index/index.js +12 -1866
- package/dist/cli/lib/local-index/path-surface-read-model.js +134 -0
- package/dist/cli/lib/local-index/populate.js +474 -0
- package/dist/cli/lib/local-index/schema.js +413 -0
- package/dist/cli/lib/local-index/search-read-model.js +533 -0
- package/dist/cli/lib/local-index/search-text.js +79 -0
- package/dist/cli/lib/option-parser.js +93 -0
- package/dist/cli/lib/repo-map.js +2 -2
- package/dist/cli/lib/run-plan.js +5 -22
- package/dist/core/change-verification.js +11 -5
- package/dist/core/command-effects.js +1 -3
- package/dist/core/command-intent-eligibility.js +14 -0
- package/dist/core/command-preconditions.js +8 -4
- package/dist/core/command-run-constraints.js +43 -0
- package/dist/core/public-json-contracts.js +57 -0
- package/dist/core/test-selection.js +8 -2
- package/dist/core/verification-plan.js +32 -4
- package/package.json +1 -1
- package/schemas/README.md +16 -0
- package/schemas/api-serve-response.schema.json +89 -0
- package/schemas/change-verification-report.schema.json +4 -1
- package/schemas/contract-lint-report.schema.json +1 -0
- package/schemas/evidence-report.schema.json +287 -0
- package/schemas/explain-report.schema.json +4 -0
- package/schemas/next-report.schema.json +121 -0
- package/schemas/onboard-commands-report.schema.json +100 -0
- package/schemas/workspace-command-catalog.schema.json +172 -0
- package/schemas/workspace-status.schema.json +141 -0
- package/schemas/workspace-verification-plan.schema.json +195 -0
- package/templates/default/manifest.toml +1 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { lintCommandContract, } from '../../core/contract-lint.js';
|
|
4
|
+
import { isRecord, readCommandContract, } from '../../core/config-loading.js';
|
|
5
|
+
import { releaseVersioningIsEnabled } from '../../core/version-sources.js';
|
|
6
|
+
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
7
|
+
import { t } from '../lib/i18n.js';
|
|
8
|
+
import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
9
|
+
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
10
|
+
import { readMustflowTomlFile } from '../lib/toml.js';
|
|
11
|
+
const ONBOARD_COMMANDS_SCHEMA_VERSION = '1';
|
|
12
|
+
const COMMAND_CONTRACT_PATH = '.mustflow/config/commands.toml';
|
|
13
|
+
const ONBOARD_COMMANDS_OPTIONS = [
|
|
14
|
+
{ name: '--json', kind: 'boolean' },
|
|
15
|
+
];
|
|
16
|
+
export function getOnboardHelp(lang = 'en') {
|
|
17
|
+
return renderHelp({
|
|
18
|
+
usage: 'mf onboard commands [options]',
|
|
19
|
+
summary: t(lang, 'onboard.help.summary'),
|
|
20
|
+
options: [
|
|
21
|
+
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
22
|
+
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
23
|
+
],
|
|
24
|
+
examples: ['mf onboard commands', 'mf onboard commands --json'],
|
|
25
|
+
exitCodes: [
|
|
26
|
+
{ label: '0', description: t(lang, 'onboard.help.exit.ok') },
|
|
27
|
+
{ label: '1', description: t(lang, 'onboard.help.exit.fail') },
|
|
28
|
+
],
|
|
29
|
+
}, lang);
|
|
30
|
+
}
|
|
31
|
+
function readPreferences(projectRoot) {
|
|
32
|
+
const preferencesPath = path.join(projectRoot, '.mustflow', 'config', 'preferences.toml');
|
|
33
|
+
if (!existsSync(preferencesPath)) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const preferences = readMustflowTomlFile(projectRoot, '.mustflow/config/preferences.toml');
|
|
37
|
+
return isRecord(preferences) ? preferences : undefined;
|
|
38
|
+
}
|
|
39
|
+
function countSuggestionsByKind(suggestions, sourceKind) {
|
|
40
|
+
return suggestions.filter((suggestion) => suggestion.sourceKind === sourceKind).length;
|
|
41
|
+
}
|
|
42
|
+
function createNextSteps(suggestions) {
|
|
43
|
+
if (suggestions.length === 0) {
|
|
44
|
+
return [
|
|
45
|
+
'No package.json scripts, Makefile targets, or justfile recipes need review-only command-intent suggestions.',
|
|
46
|
+
'Keep command execution authority in .mustflow/config/commands.toml.',
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
return [
|
|
50
|
+
'Review each suggested unknown intent before copying it into .mustflow/config/commands.toml.',
|
|
51
|
+
'Do not add argv, lifecycle, run_policy, stdin, timeout_seconds, writes, network, or destructive fields until a maintainer has reviewed the command behavior.',
|
|
52
|
+
'After accepting any snippet, run command-contract validation through the configured mustflow check intent.',
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
function createOnboardCommandsOutput(projectRoot) {
|
|
56
|
+
const report = lintCommandContract(readCommandContract(projectRoot), {
|
|
57
|
+
suggest: true,
|
|
58
|
+
projectRoot,
|
|
59
|
+
releaseVersioningEnabled: releaseVersioningIsEnabled(readPreferences(projectRoot)),
|
|
60
|
+
});
|
|
61
|
+
const suggestions = report.suggestions ?? [];
|
|
62
|
+
return {
|
|
63
|
+
schema_version: ONBOARD_COMMANDS_SCHEMA_VERSION,
|
|
64
|
+
command: 'onboard commands',
|
|
65
|
+
mustflow_root: projectRoot,
|
|
66
|
+
command_contract_path: COMMAND_CONTRACT_PATH,
|
|
67
|
+
policy: {
|
|
68
|
+
command_authority: COMMAND_CONTRACT_PATH,
|
|
69
|
+
suggestions_grant_command_authority: false,
|
|
70
|
+
suggestions_are_review_only: true,
|
|
71
|
+
writes_files: false,
|
|
72
|
+
},
|
|
73
|
+
summary: {
|
|
74
|
+
total_intents: report.summary.totalIntents,
|
|
75
|
+
runnable_intents: report.summary.runnable,
|
|
76
|
+
manual_only_intents: report.summary.manualOnly,
|
|
77
|
+
unknown_intents: report.summary.unknown,
|
|
78
|
+
suggestions: suggestions.length,
|
|
79
|
+
package_scripts: countSuggestionsByKind(suggestions, 'package_script'),
|
|
80
|
+
make_targets: countSuggestionsByKind(suggestions, 'make_target'),
|
|
81
|
+
just_recipes: countSuggestionsByKind(suggestions, 'just_recipe'),
|
|
82
|
+
contract_errors: report.summary.errors,
|
|
83
|
+
contract_warnings: report.summary.warnings,
|
|
84
|
+
},
|
|
85
|
+
suggestions,
|
|
86
|
+
next_steps: createNextSteps(suggestions),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function renderOnboardCommandsOutput(output, lang) {
|
|
90
|
+
const reviewOnlyValue = output.policy.suggestions_are_review_only ? t(lang, 'value.yes') : t(lang, 'value.no');
|
|
91
|
+
const writesFilesValue = output.policy.writes_files ? t(lang, 'value.yes') : t(lang, 'value.no');
|
|
92
|
+
const lines = [
|
|
93
|
+
t(lang, 'onboard.title'),
|
|
94
|
+
`${t(lang, 'label.mustflowRoot')}: ${output.mustflow_root}`,
|
|
95
|
+
`${t(lang, 'label.commandContract')}: ${output.command_contract_path}`,
|
|
96
|
+
`${t(lang, 'onboard.label.suggestions')}: ${output.summary.suggestions}`,
|
|
97
|
+
`${t(lang, 'onboard.label.reviewOnly')}: ${reviewOnlyValue}`,
|
|
98
|
+
`${t(lang, 'onboard.label.writesFiles')}: ${writesFilesValue}`,
|
|
99
|
+
];
|
|
100
|
+
if (output.suggestions.length > 0) {
|
|
101
|
+
lines.push('', t(lang, 'onboard.label.reviewSnippets'));
|
|
102
|
+
for (const suggestion of output.suggestions) {
|
|
103
|
+
lines.push(`- ${suggestion.sourceFile}:${suggestion.sourceName} -> ${suggestion.suggestedIntent}`);
|
|
104
|
+
lines.push(...suggestion.snippet.split('\n').map((line) => ` ${line}`));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
lines.push('', t(lang, 'onboard.label.nextSteps'));
|
|
108
|
+
for (const step of output.next_steps) {
|
|
109
|
+
lines.push(`- ${step}`);
|
|
110
|
+
}
|
|
111
|
+
return lines.join('\n');
|
|
112
|
+
}
|
|
113
|
+
export function runOnboard(args, reporter, lang = 'en') {
|
|
114
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
115
|
+
reporter.stdout(getOnboardHelp(lang));
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
const [action, ...rest] = args;
|
|
119
|
+
if (action !== 'commands') {
|
|
120
|
+
printUsageError(reporter, t(lang, action ? 'onboard.error.unknownAction' : 'onboard.error.missingAction', { action: action ?? '' }), 'mf onboard --help', getOnboardHelp(lang), lang);
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
const options = parseCliOptions(rest, ONBOARD_COMMANDS_OPTIONS);
|
|
124
|
+
if (options.error) {
|
|
125
|
+
printUsageError(reporter, formatCliOptionParseError(options.error, lang), 'mf onboard --help', getOnboardHelp(lang), lang);
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
const output = createOnboardCommandsOutput(resolveMustflowRoot());
|
|
129
|
+
if (hasParsedCliOption(options, '--json')) {
|
|
130
|
+
reporter.stdout(JSON.stringify(output, null, 2));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
reporter.stdout(renderOnboardCommandsOutput(output, lang));
|
|
134
|
+
}
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -7,6 +7,7 @@ import { printUsageError, renderCliError, renderHelp } from '../lib/cli-output.j
|
|
|
7
7
|
import { readCommandContract, readMustflowConfigIfExists } from '../../core/config-loading.js';
|
|
8
8
|
import { resolveRunReceiptRetentionPolicy } from '../../core/retention-policy.js';
|
|
9
9
|
import { t } from '../lib/i18n.js';
|
|
10
|
+
import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
10
11
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
11
12
|
import { ALLOW_UNTRUSTED_ROOT_OPTION, assessRunRootTrust } from '../lib/run-root-trust.js';
|
|
12
13
|
import { createRunPlan, createRunPreview, renderRunPreviewText, } from '../lib/run-plan.js';
|
|
@@ -20,6 +21,14 @@ import { createPendingTimeoutTermination, getKillMethod, terminateProcessTree }
|
|
|
20
21
|
import { assembleRunReceipt } from './run/receipt.js';
|
|
21
22
|
const DEFAULT_ACTIVE_LOCK_WAIT_TIMEOUT_SECONDS = 300;
|
|
22
23
|
const ACTIVE_LOCK_WAIT_POLL_MS = 1_000;
|
|
24
|
+
const RUN_OPTIONS = [
|
|
25
|
+
{ name: '--json', kind: 'boolean' },
|
|
26
|
+
{ name: '--dry-run', kind: 'boolean' },
|
|
27
|
+
{ name: '--plan-only', kind: 'boolean' },
|
|
28
|
+
{ name: '--wait', kind: 'boolean' },
|
|
29
|
+
{ name: ALLOW_UNTRUSTED_ROOT_OPTION, kind: 'boolean' },
|
|
30
|
+
{ name: '--wait-timeout', kind: 'string' },
|
|
31
|
+
];
|
|
23
32
|
function delay(milliseconds) {
|
|
24
33
|
return new Promise((resolve) => {
|
|
25
34
|
setTimeout(resolve, milliseconds);
|
|
@@ -125,51 +134,44 @@ function renderActiveLockConflictMessage(intentName, conflicts, lang) {
|
|
|
125
134
|
return t(lang, 'run.error.activeLockConflict', { intent: intentName, detail });
|
|
126
135
|
}
|
|
127
136
|
function parseRunArguments(args) {
|
|
128
|
-
const
|
|
129
|
-
const supportedValueOptions = new Set(['--wait-timeout']);
|
|
130
|
-
const positional = [];
|
|
131
|
-
const unsupported = [];
|
|
137
|
+
const parsed = parseCliOptions(args, RUN_OPTIONS, { allowPositionals: true });
|
|
132
138
|
let waitTimeoutSeconds = DEFAULT_ACTIVE_LOCK_WAIT_TIMEOUT_SECONDS;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
continue;
|
|
139
|
+
if (parsed.error) {
|
|
140
|
+
const [intentName, ...extra] = parsed.positionals;
|
|
141
|
+
return {
|
|
142
|
+
json: hasParsedCliOption(parsed, '--json'),
|
|
143
|
+
dryRun: hasParsedCliOption(parsed, '--dry-run'),
|
|
144
|
+
planOnly: hasParsedCliOption(parsed, '--plan-only'),
|
|
145
|
+
allowUntrustedRoot: hasParsedCliOption(parsed, ALLOW_UNTRUSTED_ROOT_OPTION),
|
|
146
|
+
wait: hasParsedCliOption(parsed, '--wait'),
|
|
147
|
+
waitTimeoutSeconds,
|
|
148
|
+
intentName: intentName ?? null,
|
|
149
|
+
extra,
|
|
150
|
+
error: parsed.error,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const waitTimeoutValue = getParsedCliStringOption(parsed, '--wait-timeout');
|
|
154
|
+
let error = null;
|
|
155
|
+
if (waitTimeoutValue !== null) {
|
|
156
|
+
const parsedWaitTimeout = Number(waitTimeoutValue);
|
|
157
|
+
if (!Number.isInteger(parsedWaitTimeout) || parsedWaitTimeout <= 0) {
|
|
158
|
+
error = 'invalid_wait_timeout';
|
|
154
159
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
continue;
|
|
160
|
+
else {
|
|
161
|
+
waitTimeoutSeconds = parsedWaitTimeout;
|
|
158
162
|
}
|
|
159
|
-
positional.push(arg);
|
|
160
163
|
}
|
|
161
|
-
const [intentName, ...extra] =
|
|
164
|
+
const [intentName, ...extra] = parsed.positionals;
|
|
162
165
|
return {
|
|
163
|
-
json:
|
|
164
|
-
dryRun:
|
|
165
|
-
planOnly:
|
|
166
|
-
allowUntrustedRoot:
|
|
167
|
-
wait:
|
|
166
|
+
json: hasParsedCliOption(parsed, '--json'),
|
|
167
|
+
dryRun: hasParsedCliOption(parsed, '--dry-run'),
|
|
168
|
+
planOnly: hasParsedCliOption(parsed, '--plan-only'),
|
|
169
|
+
allowUntrustedRoot: hasParsedCliOption(parsed, ALLOW_UNTRUSTED_ROOT_OPTION),
|
|
170
|
+
wait: hasParsedCliOption(parsed, '--wait'),
|
|
168
171
|
waitTimeoutSeconds,
|
|
169
172
|
intentName: intentName ?? null,
|
|
170
173
|
extra,
|
|
171
|
-
|
|
172
|
-
invalidWaitTimeout,
|
|
174
|
+
error,
|
|
173
175
|
};
|
|
174
176
|
}
|
|
175
177
|
async function acquireActiveRunLockWithOptionalWait(input) {
|
|
@@ -256,17 +258,20 @@ export function getRunHelp(lang = 'en') {
|
|
|
256
258
|
export async function runRun(args, reporter, lang = 'en', options = {}) {
|
|
257
259
|
const executorStartedAtMs = performance.now();
|
|
258
260
|
const profiler = new RunProfiler();
|
|
259
|
-
if (args
|
|
261
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
260
262
|
reporter.stdout(getRunHelp(lang));
|
|
261
263
|
return 0;
|
|
262
264
|
}
|
|
263
265
|
const parsedArgs = parseRunArguments(args);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
266
|
+
if (parsedArgs.error && parsedArgs.error !== 'invalid_wait_timeout') {
|
|
267
|
+
if (parsedArgs.error.kind === 'missing_value' && parsedArgs.error.option === '--wait-timeout') {
|
|
268
|
+
printUsageError(reporter, t(lang, 'run.error.invalidWaitTimeout'), 'mf run --help', getRunHelp(lang), lang);
|
|
269
|
+
return 1;
|
|
270
|
+
}
|
|
271
|
+
printUsageError(reporter, formatCliOptionParseError(parsedArgs.error, lang), 'mf run --help', getRunHelp(lang), lang);
|
|
267
272
|
return 1;
|
|
268
273
|
}
|
|
269
|
-
if (parsedArgs.
|
|
274
|
+
if (parsedArgs.error === 'invalid_wait_timeout') {
|
|
270
275
|
printUsageError(reporter, t(lang, 'run.error.invalidWaitTimeout'), 'mf run --help', getRunHelp(lang), lang);
|
|
271
276
|
return 1;
|
|
272
277
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
2
2
|
import { t } from '../lib/i18n.js';
|
|
3
3
|
import { searchLocalIndex } from '../lib/local-index.js';
|
|
4
|
+
import { getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
4
5
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
6
|
+
const SEARCH_OPTIONS = [
|
|
7
|
+
{ name: '--limit', kind: 'string' },
|
|
8
|
+
{ name: '--scope', kind: 'string' },
|
|
9
|
+
{ name: '--json', kind: 'boolean' },
|
|
10
|
+
];
|
|
5
11
|
export function getSearchHelp(lang = 'en') {
|
|
6
12
|
return renderHelp({
|
|
7
13
|
usage: 'mf search <query> [options]',
|
|
@@ -37,80 +43,48 @@ export function getSearchHelp(lang = 'en') {
|
|
|
37
43
|
],
|
|
38
44
|
}, lang);
|
|
39
45
|
}
|
|
46
|
+
function toSearchOptionError(error) {
|
|
47
|
+
if (error.kind === 'missing_value' && error.option === '--limit') {
|
|
48
|
+
return { key: 'search.error.missingLimit' };
|
|
49
|
+
}
|
|
50
|
+
if (error.kind === 'missing_value' && error.option === '--scope') {
|
|
51
|
+
return { key: 'search.error.missingScope' };
|
|
52
|
+
}
|
|
53
|
+
if (error.kind === 'missing_value') {
|
|
54
|
+
return { key: 'cli.error.missingValue', params: { option: error.option } };
|
|
55
|
+
}
|
|
56
|
+
return { key: 'cli.error.unknownOption', params: { option: error.option } };
|
|
57
|
+
}
|
|
40
58
|
function parseSearchOptions(args) {
|
|
41
|
-
const
|
|
59
|
+
const parsed = parseCliOptions(args, SEARCH_OPTIONS, { allowPositionals: true });
|
|
42
60
|
let limit = 10;
|
|
43
61
|
let scope = 'workflow';
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
if (arg === '--limit') {
|
|
55
|
-
const rawLimit = args[index + 1];
|
|
56
|
-
if (!rawLimit || rawLimit.startsWith('-')) {
|
|
57
|
-
return { query: '', limit, scope, json, error: { key: 'search.error.missingLimit' } };
|
|
58
|
-
}
|
|
59
|
-
const parsedLimit = Number.parseInt(rawLimit, 10);
|
|
60
|
-
if (!Number.isInteger(parsedLimit) || parsedLimit < 1 || parsedLimit > 50) {
|
|
61
|
-
return { query: '', limit, scope, json, error: { key: 'search.error.invalidLimit' } };
|
|
62
|
-
}
|
|
63
|
-
limit = parsedLimit;
|
|
64
|
-
index += 1;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (arg.startsWith('--limit=')) {
|
|
68
|
-
const rawLimit = arg.slice('--limit='.length);
|
|
69
|
-
const parsedLimit = Number.parseInt(rawLimit, 10);
|
|
70
|
-
if (!Number.isInteger(parsedLimit) || parsedLimit < 1 || parsedLimit > 50) {
|
|
71
|
-
return { query: '', limit, scope, json, error: { key: 'search.error.invalidLimit' } };
|
|
72
|
-
}
|
|
73
|
-
limit = parsedLimit;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (arg === '--scope') {
|
|
77
|
-
const rawScope = args[index + 1];
|
|
78
|
-
if (!rawScope || rawScope.startsWith('-')) {
|
|
79
|
-
return { query: '', limit, scope, json, error: { key: 'search.error.missingScope' } };
|
|
80
|
-
}
|
|
81
|
-
if (!isSearchScope(rawScope)) {
|
|
82
|
-
return {
|
|
83
|
-
query: '',
|
|
84
|
-
limit,
|
|
85
|
-
scope,
|
|
86
|
-
json,
|
|
87
|
-
error: { key: 'search.error.invalidScope', params: { scope: rawScope } },
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
scope = rawScope;
|
|
91
|
-
index += 1;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (arg.startsWith('--scope=')) {
|
|
95
|
-
const rawScope = arg.slice('--scope='.length);
|
|
96
|
-
if (!isSearchScope(rawScope)) {
|
|
97
|
-
return {
|
|
98
|
-
query: '',
|
|
99
|
-
limit,
|
|
100
|
-
scope,
|
|
101
|
-
json,
|
|
102
|
-
error: { key: 'search.error.invalidScope', params: { scope: rawScope } },
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
scope = rawScope;
|
|
106
|
-
continue;
|
|
62
|
+
const json = hasParsedCliOption(parsed, '--json');
|
|
63
|
+
if (parsed.error) {
|
|
64
|
+
return { query: '', limit, scope, json, error: toSearchOptionError(parsed.error) };
|
|
65
|
+
}
|
|
66
|
+
const rawLimit = getParsedCliStringOption(parsed, '--limit');
|
|
67
|
+
if (rawLimit !== null) {
|
|
68
|
+
const parsedLimit = Number.parseInt(rawLimit, 10);
|
|
69
|
+
if (!Number.isInteger(parsedLimit) || parsedLimit < 1 || parsedLimit > 50) {
|
|
70
|
+
return { query: '', limit, scope, json, error: { key: 'search.error.invalidLimit' } };
|
|
107
71
|
}
|
|
108
|
-
|
|
109
|
-
|
|
72
|
+
limit = parsedLimit;
|
|
73
|
+
}
|
|
74
|
+
const rawScope = getParsedCliStringOption(parsed, '--scope');
|
|
75
|
+
if (rawScope !== null) {
|
|
76
|
+
if (!isSearchScope(rawScope)) {
|
|
77
|
+
return {
|
|
78
|
+
query: '',
|
|
79
|
+
limit,
|
|
80
|
+
scope,
|
|
81
|
+
json,
|
|
82
|
+
error: { key: 'search.error.invalidScope', params: { scope: rawScope } },
|
|
83
|
+
};
|
|
110
84
|
}
|
|
111
|
-
|
|
85
|
+
scope = rawScope;
|
|
112
86
|
}
|
|
113
|
-
const query =
|
|
87
|
+
const query = parsed.positionals.join(' ').trim();
|
|
114
88
|
if (query.length === 0) {
|
|
115
89
|
return { query, limit, scope, json, error: { key: 'search.error.missingQuery' } };
|
|
116
90
|
}
|
|
@@ -140,7 +114,7 @@ function renderSearchSummary(result, lang) {
|
|
|
140
114
|
return lines.join('\n');
|
|
141
115
|
}
|
|
142
116
|
export async function runSearch(args, reporter, lang = 'en') {
|
|
143
|
-
if (args
|
|
117
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
144
118
|
reporter.stdout(getSearchHelp(lang));
|
|
145
119
|
return 0;
|
|
146
120
|
}
|
|
@@ -3,7 +3,11 @@ import path from 'node:path';
|
|
|
3
3
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
4
4
|
import { t } from '../lib/i18n.js';
|
|
5
5
|
import { inspectManifestLock } from '../lib/manifest-lock.js';
|
|
6
|
+
import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
6
7
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
8
|
+
const STATUS_OPTIONS = [
|
|
9
|
+
{ name: '--json', kind: 'boolean' },
|
|
10
|
+
];
|
|
7
11
|
export function getStatusHelp(lang = 'en') {
|
|
8
12
|
return renderHelp({
|
|
9
13
|
usage: 'mf status [options]',
|
|
@@ -36,19 +40,18 @@ function getStatusSnapshot(projectRoot) {
|
|
|
36
40
|
};
|
|
37
41
|
}
|
|
38
42
|
export function runStatus(args, reporter, lang = 'en') {
|
|
39
|
-
if (args
|
|
43
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
40
44
|
reporter.stdout(getStatusHelp(lang));
|
|
41
45
|
return 0;
|
|
42
46
|
}
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
printUsageError(reporter, t(lang, 'cli.error.unknownOption', { option: unsupported[0] }), 'mf status --help', getStatusHelp(lang), lang);
|
|
47
|
+
const options = parseCliOptions(args, STATUS_OPTIONS);
|
|
48
|
+
if (options.error) {
|
|
49
|
+
printUsageError(reporter, formatCliOptionParseError(options.error, lang), 'mf status --help', getStatusHelp(lang), lang);
|
|
47
50
|
return 1;
|
|
48
51
|
}
|
|
49
52
|
const projectRoot = resolveMustflowRoot();
|
|
50
53
|
const status = getStatusSnapshot(projectRoot);
|
|
51
|
-
if (
|
|
54
|
+
if (hasParsedCliOption(options, '--json')) {
|
|
52
55
|
reporter.stdout(JSON.stringify(status, null, 2));
|
|
53
56
|
return 0;
|
|
54
57
|
}
|
|
@@ -5,12 +5,19 @@ import { copyFileInsideWithoutSymlinks, ensureFileTargetInsideWithoutSymlinks, e
|
|
|
5
5
|
import { MANIFEST_LOCK_RELATIVE_PATH, readManifestLock, sha256File } from '../lib/manifest-lock.js';
|
|
6
6
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
7
7
|
import { t } from '../lib/i18n.js';
|
|
8
|
+
import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
8
9
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
9
10
|
import { getDefaultTemplate, getTemplateFiles, skillNameForTemplatePath } from '../lib/templates.js';
|
|
10
11
|
import { readMustflowTomlFile, stringifyToml } from '../lib/toml.js';
|
|
11
12
|
import { createUpdateDiffPreview, shouldPreviewUpdateDiff } from '../lib/update-diff-preview.js';
|
|
12
13
|
const UPDATE_SCHEMA_VERSION = '1';
|
|
13
14
|
const CUSTOMIZED_LOCK_ACTION = 'customized';
|
|
15
|
+
const UPDATE_OPTIONS = [
|
|
16
|
+
{ name: '--dry-run', kind: 'boolean' },
|
|
17
|
+
{ name: '--apply', kind: 'boolean' },
|
|
18
|
+
{ name: '--json', kind: 'boolean' },
|
|
19
|
+
{ name: '--diff', kind: 'boolean' },
|
|
20
|
+
];
|
|
14
21
|
const UPDATE_POLICY = {
|
|
15
22
|
baseline: 'manifest_lock_content_hash',
|
|
16
23
|
allowed_apply_actions: ['update', 'create'],
|
|
@@ -381,21 +388,20 @@ function printDiffPreviews(items, reporter, lang) {
|
|
|
381
388
|
}
|
|
382
389
|
}
|
|
383
390
|
export function runUpdate(args, reporter, lang = 'en') {
|
|
384
|
-
if (args
|
|
391
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
385
392
|
reporter.stdout(getUpdateHelp(lang));
|
|
386
393
|
return 0;
|
|
387
394
|
}
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const wantsDryRun = args.includes('--dry-run');
|
|
392
|
-
const wantsApply = args.includes('--apply');
|
|
393
|
-
const wantsDiff = args.includes('--diff');
|
|
394
|
-
const requestedMode = getRequestedMode(wantsDryRun, wantsApply);
|
|
395
|
-
if (unsupported.length > 0) {
|
|
396
|
-
printUsageError(reporter, t(lang, 'cli.error.unknownOption', { option: unsupported[0] }), 'mf update --help', getUpdateHelp(lang), lang);
|
|
395
|
+
const parsed = parseCliOptions(args, UPDATE_OPTIONS);
|
|
396
|
+
if (parsed.error) {
|
|
397
|
+
printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf update --help', getUpdateHelp(lang), lang);
|
|
397
398
|
return 1;
|
|
398
399
|
}
|
|
400
|
+
const wantsJson = hasParsedCliOption(parsed, '--json');
|
|
401
|
+
const wantsDryRun = hasParsedCliOption(parsed, '--dry-run');
|
|
402
|
+
const wantsApply = hasParsedCliOption(parsed, '--apply');
|
|
403
|
+
const wantsDiff = hasParsedCliOption(parsed, '--diff');
|
|
404
|
+
const requestedMode = getRequestedMode(wantsDryRun, wantsApply);
|
|
399
405
|
if (wantsDryRun && wantsApply) {
|
|
400
406
|
const error = t(lang, 'update.error.cannotCombineModes');
|
|
401
407
|
if (wantsJson) {
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { printUsageError, renderCliError, renderHelp } from '../lib/cli-output.js';
|
|
2
2
|
import { t } from '../lib/i18n.js';
|
|
3
3
|
import { checkNpmLatestVersion } from '../lib/npm-version-check.js';
|
|
4
|
+
import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
4
5
|
import { readPackageMetadata } from '../lib/package-info.js';
|
|
5
6
|
import { runUpdate } from './update.js';
|
|
7
|
+
const UPGRADE_OPTIONS = [
|
|
8
|
+
{ name: '--dry-run', kind: 'boolean' },
|
|
9
|
+
];
|
|
6
10
|
export function getUpgradeHelp(lang = 'en') {
|
|
7
11
|
return renderHelp({
|
|
8
12
|
usage: 'mf upgrade [options]',
|
|
@@ -32,17 +36,16 @@ function printPackageCheck(check, reporter, lang) {
|
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
export async function runUpgrade(args, reporter, lang = 'en') {
|
|
35
|
-
if (args
|
|
39
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
36
40
|
reporter.stdout(getUpgradeHelp(lang));
|
|
37
41
|
return 0;
|
|
38
42
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
printUsageError(reporter, t(lang, 'cli.error.unknownOption', { option: unsupported[0] }), 'mf upgrade --help', getUpgradeHelp(lang), lang);
|
|
43
|
+
const options = parseCliOptions(args, UPGRADE_OPTIONS);
|
|
44
|
+
if (options.error) {
|
|
45
|
+
printUsageError(reporter, formatCliOptionParseError(options.error, lang), 'mf upgrade --help', getUpgradeHelp(lang), lang);
|
|
43
46
|
return 1;
|
|
44
47
|
}
|
|
45
|
-
const dryRun =
|
|
48
|
+
const dryRun = hasParsedCliOption(options, '--dry-run');
|
|
46
49
|
reporter.stdout(t(lang, 'upgrade.title'));
|
|
47
50
|
reporter.stdout('');
|
|
48
51
|
reporter.stdout(t(lang, 'upgrade.packageSection'));
|