mustflow 2.23.0 → 2.25.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.
- 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/i18n.toml +1 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -1
- package/templates/default/locales/en/.mustflow/skills/clarifying-question-gate/SKILL.md +183 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +7 -1
- package/templates/default/locales/en/.mustflow/skills/structure-discovery-gate/SKILL.md +63 -20
- package/templates/default/manifest.toml +8 -1
|
@@ -5,8 +5,14 @@ import { writeJsonFileInsideWithoutSymlinks } from '../../core/safe-filesystem.j
|
|
|
5
5
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
6
6
|
import { requireGitChangedFiles } from '../lib/git-changes.js';
|
|
7
7
|
import { t } from '../lib/i18n.js';
|
|
8
|
+
import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
8
9
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
9
10
|
const CLASSIFY_SCHEMA_VERSION = '1';
|
|
11
|
+
const CLASSIFY_OPTIONS = [
|
|
12
|
+
{ name: '--json', kind: 'boolean' },
|
|
13
|
+
{ name: '--changed', kind: 'boolean' },
|
|
14
|
+
{ name: '--write', kind: 'string' },
|
|
15
|
+
];
|
|
10
16
|
export function getClassifyHelp(lang = 'en') {
|
|
11
17
|
return renderHelp({
|
|
12
18
|
usage: 'mf classify --changed [options] | mf classify <path...> [options]',
|
|
@@ -29,43 +35,14 @@ export function getClassifyHelp(lang = 'en') {
|
|
|
29
35
|
}, lang);
|
|
30
36
|
}
|
|
31
37
|
function parseClassifyArgs(args) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (arg === '--changed') {
|
|
43
|
-
changed = true;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
if (arg === '--write') {
|
|
47
|
-
const value = args[index + 1];
|
|
48
|
-
if (!value || value.startsWith('-')) {
|
|
49
|
-
return { json, changed, writePath, paths, error: 'missing_write_value' };
|
|
50
|
-
}
|
|
51
|
-
writePath = value;
|
|
52
|
-
index += 1;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
if (arg.startsWith('--write=')) {
|
|
56
|
-
const value = arg.slice('--write='.length);
|
|
57
|
-
if (value.length === 0) {
|
|
58
|
-
return { json, changed, writePath, paths, error: 'missing_write_value' };
|
|
59
|
-
}
|
|
60
|
-
writePath = value;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (arg.startsWith('-')) {
|
|
64
|
-
return { json, changed, writePath, paths, error: arg };
|
|
65
|
-
}
|
|
66
|
-
paths.push(arg);
|
|
67
|
-
}
|
|
68
|
-
return { json, changed, writePath, paths };
|
|
38
|
+
const parsed = parseCliOptions(args, CLASSIFY_OPTIONS, { allowPositionals: true });
|
|
39
|
+
return {
|
|
40
|
+
json: hasParsedCliOption(parsed, '--json'),
|
|
41
|
+
changed: hasParsedCliOption(parsed, '--changed'),
|
|
42
|
+
writePath: getParsedCliStringOption(parsed, '--write'),
|
|
43
|
+
paths: parsed.positionals,
|
|
44
|
+
error: parsed.error,
|
|
45
|
+
};
|
|
69
46
|
}
|
|
70
47
|
export function createClassifyOutput(projectRoot, source, paths) {
|
|
71
48
|
const files = source === 'changed' ? requireGitChangedFiles(projectRoot) : paths;
|
|
@@ -116,16 +93,13 @@ function writeClassifyOutput(projectRoot, inputPath, output) {
|
|
|
116
93
|
writeJsonFileInsideWithoutSymlinks(projectRoot, outputPath, output);
|
|
117
94
|
}
|
|
118
95
|
export function runClassify(args, reporter, lang = 'en') {
|
|
119
|
-
if (args
|
|
96
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
120
97
|
reporter.stdout(getClassifyHelp(lang));
|
|
121
98
|
return 0;
|
|
122
99
|
}
|
|
123
100
|
const parsed = parseClassifyArgs(args);
|
|
124
101
|
if (parsed.error) {
|
|
125
|
-
|
|
126
|
-
? t(lang, 'cli.error.missingValue', { option: '--write' })
|
|
127
|
-
: t(lang, 'cli.error.unknownOption', { option: parsed.error });
|
|
128
|
-
printUsageError(reporter, message, 'mf classify --help', getClassifyHelp(lang), lang);
|
|
102
|
+
printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf classify --help', getClassifyHelp(lang), lang);
|
|
129
103
|
return 1;
|
|
130
104
|
}
|
|
131
105
|
if (parsed.changed && parsed.paths.length > 0) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
2
2
|
import { getAgentContext, getPromptCacheProfileContext } from '../lib/agent-context.js';
|
|
3
3
|
import { t } from '../lib/i18n.js';
|
|
4
|
+
import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
4
5
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
5
6
|
export function getContextHelp(lang = 'en') {
|
|
6
7
|
return renderHelp({
|
|
@@ -27,55 +28,41 @@ export function getContextHelp(lang = 'en') {
|
|
|
27
28
|
],
|
|
28
29
|
}, lang);
|
|
29
30
|
}
|
|
31
|
+
const CONTEXT_OPTIONS = [
|
|
32
|
+
{ name: '--json', kind: 'boolean' },
|
|
33
|
+
{ name: '--cache-profile', kind: 'string' },
|
|
34
|
+
];
|
|
30
35
|
const CACHE_PROFILES = new Set(['stable', 'task', 'volatile', 'all']);
|
|
31
|
-
function parseCacheProfile(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return { profile: null, error: null };
|
|
35
|
-
}
|
|
36
|
-
const value = args[index + 1];
|
|
37
|
-
if (!value || value.startsWith('-')) {
|
|
38
|
-
return { profile: null, error: 'missing' };
|
|
36
|
+
function parseCacheProfile(value) {
|
|
37
|
+
if (value === null) {
|
|
38
|
+
return { profile: null, invalid: false };
|
|
39
39
|
}
|
|
40
40
|
if (!CACHE_PROFILES.has(value)) {
|
|
41
|
-
return { profile: null,
|
|
41
|
+
return { profile: null, invalid: true };
|
|
42
42
|
}
|
|
43
|
-
return { profile: value,
|
|
43
|
+
return { profile: value, invalid: false };
|
|
44
44
|
}
|
|
45
45
|
export async function runContext(args, reporter, lang = 'en') {
|
|
46
|
-
if (args
|
|
46
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
47
47
|
reporter.stdout(getContextHelp(lang));
|
|
48
48
|
return 0;
|
|
49
49
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (arg === '--cache-profile') {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
if (index > 0 && args[index - 1] === '--cache-profile') {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
return !supported.has(arg);
|
|
60
|
-
});
|
|
61
|
-
if (unsupported.length > 0) {
|
|
62
|
-
printUsageError(reporter, t(lang, 'cli.error.unknownOption', { option: unsupported[0] }), 'mf context --help', getContextHelp(lang), lang);
|
|
63
|
-
return 1;
|
|
64
|
-
}
|
|
65
|
-
if (cacheProfile.error === 'missing') {
|
|
66
|
-
printUsageError(reporter, t(lang, 'cli.error.missingValue', { option: '--cache-profile' }), 'mf context --help', getContextHelp(lang), lang);
|
|
50
|
+
const parsed = parseCliOptions(args, CONTEXT_OPTIONS);
|
|
51
|
+
if (parsed.error) {
|
|
52
|
+
printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf context --help', getContextHelp(lang), lang);
|
|
67
53
|
return 1;
|
|
68
54
|
}
|
|
69
|
-
|
|
55
|
+
const cacheProfile = parseCacheProfile(getParsedCliStringOption(parsed, '--cache-profile'));
|
|
56
|
+
if (cacheProfile.invalid) {
|
|
70
57
|
printUsageError(reporter, t(lang, 'cli.error.unexpectedValue', { option: '--cache-profile' }), 'mf context --help', getContextHelp(lang), lang);
|
|
71
58
|
return 1;
|
|
72
59
|
}
|
|
73
|
-
if (cacheProfile.profile && !
|
|
60
|
+
if (cacheProfile.profile && !hasParsedCliOption(parsed, '--json')) {
|
|
74
61
|
printUsageError(reporter, t(lang, 'cli.error.unexpectedArgument', { argument: '--cache-profile' }), 'mf context --help', getContextHelp(lang), lang);
|
|
75
62
|
return 1;
|
|
76
63
|
}
|
|
77
64
|
const mustflowRoot = resolveMustflowRoot();
|
|
78
|
-
if (
|
|
65
|
+
if (hasParsedCliOption(parsed, '--json')) {
|
|
79
66
|
if (cacheProfile.profile) {
|
|
80
67
|
reporter.stdout(JSON.stringify(await getPromptCacheProfileContext(mustflowRoot, cacheProfile.profile), null, 2));
|
|
81
68
|
return 0;
|
|
@@ -5,9 +5,15 @@ import { readCommandContract, isRecord } from '../../core/config-loading.js';
|
|
|
5
5
|
import { releaseVersioningIsEnabled } from '../../core/version-sources.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 { readMustflowTomlFile } from '../lib/toml.js';
|
|
10
11
|
const CONTRACT_LINT_SCHEMA_VERSION = '1';
|
|
12
|
+
const CONTRACT_LINT_OPTIONS = [
|
|
13
|
+
{ name: '--coverage', kind: 'boolean' },
|
|
14
|
+
{ name: '--suggest', kind: 'boolean' },
|
|
15
|
+
{ name: '--json', kind: 'boolean' },
|
|
16
|
+
];
|
|
11
17
|
export function getContractLintHelp(lang = 'en') {
|
|
12
18
|
return renderHelp({
|
|
13
19
|
usage: 'mf contract-lint [options]',
|
|
@@ -82,18 +88,17 @@ function renderContractLintOutput(output, lang) {
|
|
|
82
88
|
return lines.join('\n');
|
|
83
89
|
}
|
|
84
90
|
export function runContractLint(args, reporter, lang = 'en') {
|
|
85
|
-
if (args
|
|
91
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
86
92
|
reporter.stdout(getContractLintHelp(lang));
|
|
87
93
|
return 0;
|
|
88
94
|
}
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
printUsageError(reporter, t(lang, 'cli.error.unknownOption', { option: unsupported[0] }), 'mf contract-lint --help', getContractLintHelp(lang), lang);
|
|
95
|
+
const options = parseCliOptions(args, CONTRACT_LINT_OPTIONS);
|
|
96
|
+
if (options.error) {
|
|
97
|
+
printUsageError(reporter, formatCliOptionParseError(options.error, lang), 'mf contract-lint --help', getContractLintHelp(lang), lang);
|
|
93
98
|
return 1;
|
|
94
99
|
}
|
|
95
|
-
const output = createContractLintOutput(resolveMustflowRoot(),
|
|
96
|
-
if (
|
|
100
|
+
const output = createContractLintOutput(resolveMustflowRoot(), hasParsedCliOption(options, '--coverage'), hasParsedCliOption(options, '--suggest'));
|
|
101
|
+
if (hasParsedCliOption(options, '--json')) {
|
|
97
102
|
reporter.stdout(JSON.stringify(output, null, 2));
|
|
98
103
|
}
|
|
99
104
|
else {
|
|
@@ -4,6 +4,7 @@ import http from 'node:http';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { openUrlInBrowser } from '../lib/browser-open.js';
|
|
6
6
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
7
|
+
import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
7
8
|
import { renderDashboardHtml, } from '../lib/dashboard-html.js';
|
|
8
9
|
import { DashboardExportPathError, writeDashboardExport, } from '../lib/dashboard-export.js';
|
|
9
10
|
import { DASHBOARD_VERIFICATION_MAX_FILE_MATCHES, createDashboardVerificationSnapshot, } from '../../core/dashboard-verification.js';
|
|
@@ -18,6 +19,7 @@ import { markDashboardDocReviewFromPayload, openDashboardMustflowFolder, updateD
|
|
|
18
19
|
import { MANIFEST_LOCK_RELATIVE_PATH, inspectManifestLock } from '../lib/manifest-lock.js';
|
|
19
20
|
import { getLocalIndexDatabasePath, readLatestLocalVerificationReadModelQueries, readLocalCommandEffectGraphs, } from '../lib/local-index.js';
|
|
20
21
|
import { readPackageMetadata } from '../lib/package-info.js';
|
|
22
|
+
import { createRunPlan } from '../lib/run-plan.js';
|
|
21
23
|
import { t } from '../lib/i18n.js';
|
|
22
24
|
import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFile, readMustflowTextFileIfExists, } from '../lib/mustflow-read.js';
|
|
23
25
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
@@ -27,6 +29,16 @@ const DEFAULT_DASHBOARD_HOST = '127.0.0.1';
|
|
|
27
29
|
const DEFAULT_DASHBOARD_PORT = 0;
|
|
28
30
|
const MAX_REQUEST_BYTES = 64 * 1024;
|
|
29
31
|
const LOCAL_DASHBOARD_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);
|
|
32
|
+
const DASHBOARD_OPTIONS = [
|
|
33
|
+
{ name: '--host', kind: 'string' },
|
|
34
|
+
{ name: '--port', kind: 'string' },
|
|
35
|
+
{ name: '--open', kind: 'boolean' },
|
|
36
|
+
{ name: '--no-open', kind: 'boolean' },
|
|
37
|
+
{ name: '--json', kind: 'boolean' },
|
|
38
|
+
{ name: '--export', kind: 'string' },
|
|
39
|
+
{ name: '--export-json', kind: 'string' },
|
|
40
|
+
{ name: '--help', kind: 'boolean', aliases: ['-h'] },
|
|
41
|
+
];
|
|
30
42
|
const RELEASE_FILE_PATTERNS = [
|
|
31
43
|
/^package\.json$/u,
|
|
32
44
|
/^bun\.lockb?$/u,
|
|
@@ -168,112 +180,34 @@ export function getDashboardHelp(lang = 'en') {
|
|
|
168
180
|
}, lang);
|
|
169
181
|
}
|
|
170
182
|
function parseDashboardOptions(args, lang) {
|
|
171
|
-
|
|
183
|
+
const parsed = parseCliOptions(args, DASHBOARD_OPTIONS);
|
|
184
|
+
if (parsed.error) {
|
|
185
|
+
return { error: formatCliOptionParseError(parsed.error, lang) };
|
|
186
|
+
}
|
|
187
|
+
const host = getParsedCliStringOption(parsed, '--host') ?? DEFAULT_DASHBOARD_HOST;
|
|
172
188
|
let port = DEFAULT_DASHBOARD_PORT;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
179
|
-
const arg = args[index];
|
|
180
|
-
if (!arg) {
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
if (arg === '--json') {
|
|
184
|
-
json = true;
|
|
185
|
-
openBrowser = false;
|
|
186
|
-
serverOptionUsed = true;
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
if (arg === '--open') {
|
|
190
|
-
openBrowser = true;
|
|
191
|
-
serverOptionUsed = true;
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
if (arg === '--no-open') {
|
|
195
|
-
openBrowser = false;
|
|
196
|
-
serverOptionUsed = true;
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
if (arg === '--host') {
|
|
200
|
-
const value = args[index + 1];
|
|
201
|
-
if (!value || value.startsWith('-')) {
|
|
202
|
-
return { error: t(lang, 'cli.error.missingValue', { option: '--host' }) };
|
|
203
|
-
}
|
|
204
|
-
host = value;
|
|
205
|
-
serverOptionUsed = true;
|
|
206
|
-
index += 1;
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
if (arg.startsWith('--host=')) {
|
|
210
|
-
host = arg.slice('--host='.length);
|
|
211
|
-
serverOptionUsed = true;
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
if (arg === '--port') {
|
|
215
|
-
const value = args[index + 1];
|
|
216
|
-
if (!value || value.startsWith('-')) {
|
|
217
|
-
return { error: t(lang, 'cli.error.missingValue', { option: '--port' }) };
|
|
218
|
-
}
|
|
219
|
-
const parsedPort = Number(value);
|
|
220
|
-
if (!Number.isInteger(parsedPort) || parsedPort < 0 || parsedPort > 65_535) {
|
|
221
|
-
return { error: t(lang, 'dashboard.error.invalidPort', { port: value }) };
|
|
222
|
-
}
|
|
223
|
-
port = parsedPort;
|
|
224
|
-
serverOptionUsed = true;
|
|
225
|
-
index += 1;
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (arg.startsWith('--port=')) {
|
|
229
|
-
const value = arg.slice('--port='.length);
|
|
230
|
-
const parsedPort = Number(value);
|
|
231
|
-
if (!Number.isInteger(parsedPort) || parsedPort < 0 || parsedPort > 65_535) {
|
|
232
|
-
return { error: t(lang, 'dashboard.error.invalidPort', { port: value }) };
|
|
233
|
-
}
|
|
234
|
-
port = parsedPort;
|
|
235
|
-
serverOptionUsed = true;
|
|
236
|
-
continue;
|
|
189
|
+
const portValue = getParsedCliStringOption(parsed, '--port');
|
|
190
|
+
if (portValue !== null) {
|
|
191
|
+
const parsedPort = Number(portValue);
|
|
192
|
+
if (!Number.isInteger(parsedPort) || parsedPort < 0 || parsedPort > 65_535) {
|
|
193
|
+
return { error: t(lang, 'dashboard.error.invalidPort', { port: portValue }) };
|
|
237
194
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
exportPath = arg.slice('--export='.length);
|
|
256
|
-
exportFormat = 'html';
|
|
257
|
-
if (!exportPath) {
|
|
258
|
-
return { error: t(lang, 'cli.error.missingValue', { option: '--export' }) };
|
|
259
|
-
}
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
if (arg.startsWith('--export-json=')) {
|
|
263
|
-
if (exportPath) {
|
|
264
|
-
return { error: t(lang, 'dashboard.error.conflictingExportModes') };
|
|
265
|
-
}
|
|
266
|
-
exportPath = arg.slice('--export-json='.length);
|
|
267
|
-
exportFormat = 'json';
|
|
268
|
-
if (!exportPath) {
|
|
269
|
-
return { error: t(lang, 'cli.error.missingValue', { option: '--export-json' }) };
|
|
270
|
-
}
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
if (arg.startsWith('-')) {
|
|
274
|
-
return { error: t(lang, 'cli.error.unknownOption', { option: arg }) };
|
|
275
|
-
}
|
|
276
|
-
return { error: t(lang, 'cli.error.unexpectedArgument', { argument: arg }) };
|
|
195
|
+
port = parsedPort;
|
|
196
|
+
}
|
|
197
|
+
const json = hasParsedCliOption(parsed, '--json');
|
|
198
|
+
const lastOpenOption = lastOptionTokenName(args, ['--open', '--no-open']);
|
|
199
|
+
const openBrowser = lastOpenOption === '--open' && !json;
|
|
200
|
+
const serverOptionUsed = hasParsedCliOption(parsed, '--json')
|
|
201
|
+
|| hasParsedCliOption(parsed, '--open')
|
|
202
|
+
|| hasParsedCliOption(parsed, '--no-open')
|
|
203
|
+
|| getParsedCliStringOption(parsed, '--host') !== null
|
|
204
|
+
|| portValue !== null;
|
|
205
|
+
const htmlExportPath = getParsedCliStringOption(parsed, '--export');
|
|
206
|
+
const jsonExportPath = getParsedCliStringOption(parsed, '--export-json');
|
|
207
|
+
const exportPath = htmlExportPath ?? jsonExportPath ?? undefined;
|
|
208
|
+
const exportFormat = jsonExportPath !== null ? 'json' : htmlExportPath !== null ? 'html' : undefined;
|
|
209
|
+
if (countOptionTokens(args, ['--export', '--export-json']) > 1) {
|
|
210
|
+
return { error: t(lang, 'dashboard.error.conflictingExportModes') };
|
|
277
211
|
}
|
|
278
212
|
if (exportPath && serverOptionUsed) {
|
|
279
213
|
return { error: t(lang, 'dashboard.error.exportServerOptions') };
|
|
@@ -284,11 +218,33 @@ function parseDashboardOptions(args, lang) {
|
|
|
284
218
|
if (!LOCAL_DASHBOARD_HOSTS.has(host)) {
|
|
285
219
|
return { error: t(lang, 'dashboard.error.nonLocalHost', { host }) };
|
|
286
220
|
}
|
|
287
|
-
if (json) {
|
|
288
|
-
openBrowser = false;
|
|
289
|
-
}
|
|
290
221
|
return { options: { host, port, json, openBrowser } };
|
|
291
222
|
}
|
|
223
|
+
function countOptionTokens(args, names) {
|
|
224
|
+
const nameSet = new Set(names);
|
|
225
|
+
let count = 0;
|
|
226
|
+
for (const arg of args) {
|
|
227
|
+
if (nameSet.has(optionTokenName(arg))) {
|
|
228
|
+
count += 1;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return count;
|
|
232
|
+
}
|
|
233
|
+
function lastOptionTokenName(args, names) {
|
|
234
|
+
const nameSet = new Set(names);
|
|
235
|
+
let lastName = null;
|
|
236
|
+
for (const arg of args) {
|
|
237
|
+
const name = optionTokenName(arg);
|
|
238
|
+
if (nameSet.has(name)) {
|
|
239
|
+
lastName = name;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return lastName;
|
|
243
|
+
}
|
|
244
|
+
function optionTokenName(arg) {
|
|
245
|
+
const separatorIndex = arg.indexOf('=');
|
|
246
|
+
return separatorIndex === -1 ? arg : arg.slice(0, separatorIndex);
|
|
247
|
+
}
|
|
292
248
|
function sendJson(response, statusCode, value) {
|
|
293
249
|
response.writeHead(statusCode, {
|
|
294
250
|
'cache-control': 'no-store',
|
|
@@ -510,12 +466,7 @@ async function renderCommandContractResponse(projectRoot, contract) {
|
|
|
510
466
|
const runPolicy = readString(intent, 'run_policy') ?? null;
|
|
511
467
|
const stdin = readString(intent, 'stdin') ?? null;
|
|
512
468
|
const timeoutSeconds = readPositiveInteger(intent, 'timeout_seconds') ?? null;
|
|
513
|
-
const runnable =
|
|
514
|
-
lifecycle === 'oneshot' &&
|
|
515
|
-
runPolicy === 'agent_allowed' &&
|
|
516
|
-
stdin === 'closed' &&
|
|
517
|
-
timeoutSeconds !== null &&
|
|
518
|
-
(readStringArray(intent, 'argv') !== undefined || readString(intent, 'cmd') !== undefined);
|
|
469
|
+
const runnable = createRunPlan(projectRoot, contract, name).ok;
|
|
519
470
|
return [
|
|
520
471
|
{
|
|
521
472
|
name,
|
|
@@ -753,7 +704,7 @@ function toDashboardUrl(host, port) {
|
|
|
753
704
|
return `http://${formattedHost}:${port}/`;
|
|
754
705
|
}
|
|
755
706
|
export async function runDashboard(args, reporter, lang = 'en') {
|
|
756
|
-
if (args
|
|
707
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
757
708
|
reporter.stdout(getDashboardHelp(lang));
|
|
758
709
|
return 0;
|
|
759
710
|
}
|
|
@@ -4,6 +4,7 @@ import { printUsageError, renderCliError, renderHelp } from '../lib/cli-output.j
|
|
|
4
4
|
import { DOC_REVIEW_LEDGER_RELATIVE_PATH, DOC_REVIEW_STATUSES, REVIEWER_KINDS, addDocReviewEntry, commentDocReviewEntry, isReviewerKind, listDocReviewEntries, markDocReviewEntry, } from '../lib/doc-review-ledger.js';
|
|
5
5
|
import { ensureInside, readUtf8FileInsideWithoutSymlinks } from '../lib/filesystem.js';
|
|
6
6
|
import { t } from '../lib/i18n.js';
|
|
7
|
+
import { getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
7
8
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
8
9
|
const LIST_FLAGS = new Set(['--json', '--all']);
|
|
9
10
|
const LIST_VALUE_OPTIONS = new Set(['--status']);
|
|
@@ -50,35 +51,51 @@ export function getDocsHelp(lang = 'en') {
|
|
|
50
51
|
}, lang);
|
|
51
52
|
}
|
|
52
53
|
function parseOptions(args, valueOptions, flags) {
|
|
54
|
+
const specs = createDocsOptionSpecs(valueOptions, flags);
|
|
55
|
+
const parsed = parseCliOptions(args, specs, { allowPositionals: true });
|
|
56
|
+
const values = parsedDocsValues(parsed, valueOptions);
|
|
57
|
+
const parsedFlags = parsedDocsFlags(parsed, flags);
|
|
58
|
+
if (parsed.error) {
|
|
59
|
+
return {
|
|
60
|
+
values,
|
|
61
|
+
flags: parsedFlags,
|
|
62
|
+
positionals: parsed.positionals,
|
|
63
|
+
error: {
|
|
64
|
+
option: parsed.error.option,
|
|
65
|
+
missingValue: parsed.error.kind === 'missing_value',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return { values, flags: parsedFlags, positionals: parsed.positionals };
|
|
70
|
+
}
|
|
71
|
+
function createDocsOptionSpecs(valueOptions, flags) {
|
|
72
|
+
const specs = new Map();
|
|
73
|
+
for (const option of valueOptions) {
|
|
74
|
+
specs.set(option, 'string');
|
|
75
|
+
}
|
|
76
|
+
for (const flag of flags) {
|
|
77
|
+
specs.set(flag, 'boolean');
|
|
78
|
+
}
|
|
79
|
+
return [...specs.entries()].map(([name, kind]) => ({ name, kind }));
|
|
80
|
+
}
|
|
81
|
+
function parsedDocsValues(parsed, valueOptions) {
|
|
53
82
|
const values = new Map();
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (flags.has(arg)) {
|
|
59
|
-
parsedFlags.add(arg);
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const equalsIndex = arg.indexOf('=');
|
|
63
|
-
const optionName = equalsIndex > -1 ? arg.slice(0, equalsIndex) : arg;
|
|
64
|
-
if (valueOptions.has(optionName)) {
|
|
65
|
-
const inlineValue = equalsIndex > -1 ? arg.slice(equalsIndex + 1) : undefined;
|
|
66
|
-
const nextValue = inlineValue ?? args[index + 1];
|
|
67
|
-
if (!nextValue || (!inlineValue && nextValue.startsWith('-'))) {
|
|
68
|
-
return { values, flags: parsedFlags, positionals, error: { option: optionName, missingValue: true } };
|
|
69
|
-
}
|
|
70
|
-
values.set(optionName, nextValue);
|
|
71
|
-
if (inlineValue === undefined) {
|
|
72
|
-
index += 1;
|
|
73
|
-
}
|
|
74
|
-
continue;
|
|
83
|
+
for (const option of valueOptions) {
|
|
84
|
+
const value = getParsedCliStringOption(parsed, option);
|
|
85
|
+
if (value !== null) {
|
|
86
|
+
values.set(option, value);
|
|
75
87
|
}
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
}
|
|
89
|
+
return values;
|
|
90
|
+
}
|
|
91
|
+
function parsedDocsFlags(parsed, flags) {
|
|
92
|
+
const parsedFlags = new Set();
|
|
93
|
+
for (const flag of flags) {
|
|
94
|
+
if (hasParsedCliOption(parsed, flag)) {
|
|
95
|
+
parsedFlags.add(flag);
|
|
78
96
|
}
|
|
79
|
-
positionals.push(arg);
|
|
80
97
|
}
|
|
81
|
-
return
|
|
98
|
+
return parsedFlags;
|
|
82
99
|
}
|
|
83
100
|
function parseStatus(value) {
|
|
84
101
|
if (value === undefined) {
|
|
@@ -352,7 +369,7 @@ function runReviewMark(args, status, reporter, lang) {
|
|
|
352
369
|
}
|
|
353
370
|
}
|
|
354
371
|
export function runDocs(args, reporter, lang = 'en') {
|
|
355
|
-
if (args
|
|
372
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
356
373
|
reporter.stdout(getDocsHelp(lang));
|
|
357
374
|
return 0;
|
|
358
375
|
}
|
|
@@ -4,12 +4,17 @@ import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
|
4
4
|
import { getAgentContext, } from '../lib/agent-context.js';
|
|
5
5
|
import { t } from '../lib/i18n.js';
|
|
6
6
|
import { getLocalIndexDatabasePath } from '../lib/local-index.js';
|
|
7
|
+
import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
7
8
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
8
9
|
import { checkMustflowProjectReport } from '../lib/validation.js';
|
|
9
10
|
import { findCommandEnvInheritanceWarnings } from '../../core/command-contract-validation.js';
|
|
10
11
|
import { readCommandContract } from '../../core/config-loading.js';
|
|
11
12
|
import { summarizeSkillRouteAlignment } from '../../core/skill-route-alignment.js';
|
|
12
13
|
const DOCTOR_SCHEMA_VERSION = '1';
|
|
14
|
+
const DOCTOR_OPTIONS = [
|
|
15
|
+
{ name: '--json', kind: 'boolean' },
|
|
16
|
+
{ name: '--strict', kind: 'boolean' },
|
|
17
|
+
];
|
|
13
18
|
export function getDoctorHelp(lang = 'en') {
|
|
14
19
|
return renderHelp({
|
|
15
20
|
usage: 'mf doctor [options]',
|
|
@@ -264,18 +269,17 @@ function renderDoctorOutput(output, lang) {
|
|
|
264
269
|
return lines.join('\n');
|
|
265
270
|
}
|
|
266
271
|
export function runDoctor(args, reporter, lang = 'en') {
|
|
267
|
-
if (args
|
|
272
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
268
273
|
reporter.stdout(getDoctorHelp(lang));
|
|
269
274
|
return 0;
|
|
270
275
|
}
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
printUsageError(reporter, t(lang, 'cli.error.unknownOption', { option: unsupported[0] }), 'mf doctor --help', getDoctorHelp(lang), lang);
|
|
276
|
+
const options = parseCliOptions(args, DOCTOR_OPTIONS);
|
|
277
|
+
if (options.error) {
|
|
278
|
+
printUsageError(reporter, formatCliOptionParseError(options.error, lang), 'mf doctor --help', getDoctorHelp(lang), lang);
|
|
275
279
|
return 1;
|
|
276
280
|
}
|
|
277
|
-
const output = createDoctorOutput(
|
|
278
|
-
if (
|
|
281
|
+
const output = createDoctorOutput(hasParsedCliOption(options, '--strict'));
|
|
282
|
+
if (hasParsedCliOption(options, '--json')) {
|
|
279
283
|
reporter.stdout(JSON.stringify(output, null, 2));
|
|
280
284
|
return output.ok ? 0 : 1;
|
|
281
285
|
}
|