mustflow 2.32.0 → 2.37.1
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/dist/cli/commands/api/actions.js +55 -0
- package/dist/cli/commands/api/report-runner.js +62 -0
- package/dist/cli/commands/api/serve.js +149 -0
- package/dist/cli/commands/api/workspace-recommendations.js +13 -0
- package/dist/cli/commands/api.js +15 -275
- package/dist/cli/lib/local-index/search-read-model.js +44 -7
- package/dist/cli/lib/validation/frontmatter.js +75 -0
- package/dist/cli/lib/validation/index.js +4 -86
- package/dist/cli/lib/validation/safe-read.js +13 -0
- package/dist/core/active-run-locks.js +110 -10
- package/dist/core/run-performance-history.js +14 -1
- package/dist/core/validation-ratchet.js +1 -1
- package/package.json +1 -1
- package/templates/default/i18n.toml +55 -19
- package/templates/default/locales/en/.mustflow/docs/agent-workflow.md +15 -7
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +20 -8
- package/templates/default/locales/en/.mustflow/skills/adapter-boundary/SKILL.md +9 -2
- package/templates/default/locales/en/.mustflow/skills/ai-generated-code-hardening/SKILL.md +249 -0
- package/templates/default/locales/en/.mustflow/skills/api-contract-change/SKILL.md +16 -11
- package/templates/default/locales/en/.mustflow/skills/auth-permission-change/SKILL.md +11 -4
- package/templates/default/locales/en/.mustflow/skills/backend-reliability-change/SKILL.md +289 -0
- package/templates/default/locales/en/.mustflow/skills/css-code-change/SKILL.md +24 -14
- package/templates/default/locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md +18 -7
- package/templates/default/locales/en/.mustflow/skills/frontend-render-stability/SKILL.md +144 -0
- package/templates/default/locales/en/.mustflow/skills/go-code-change/SKILL.md +70 -18
- package/templates/default/locales/en/.mustflow/skills/html-code-change/SKILL.md +20 -13
- package/templates/default/locales/en/.mustflow/skills/http-delivery-streaming/SKILL.md +205 -0
- package/templates/default/locales/en/.mustflow/skills/performance-budget-check/SKILL.md +9 -7
- package/templates/default/locales/en/.mustflow/skills/proactive-risk-surfacing/SKILL.md +198 -0
- package/templates/default/locales/en/.mustflow/skills/python-code-change/SKILL.md +27 -11
- package/templates/default/locales/en/.mustflow/skills/routes.toml +37 -1
- package/templates/default/locales/en/.mustflow/skills/rust-code-change/SKILL.md +41 -17
- package/templates/default/locales/en/.mustflow/skills/service-boundary-architecture/SKILL.md +167 -0
- package/templates/default/locales/en/.mustflow/skills/tailwind-code-change/SKILL.md +37 -23
- package/templates/default/locales/en/.mustflow/skills/tauri-code-change/SKILL.md +27 -10
- package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +22 -4
- package/templates/default/locales/en/.mustflow/skills/unocss-code-change/SKILL.md +34 -15
- package/templates/default/locales/en/.mustflow/skills/version-freshness-check/SKILL.md +29 -5
- package/templates/default/locales/en/AGENTS.md +3 -2
- package/templates/default/locales/ko/.mustflow/docs/agent-workflow.md +13 -8
- package/templates/default/locales/ko/AGENTS.md +2 -2
- package/templates/default/manifest.toml +37 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const API_REPORT_ACTIONS = [
|
|
2
|
+
{
|
|
3
|
+
action: 'workspace-summary',
|
|
4
|
+
requiresChanged: false,
|
|
5
|
+
helpKey: 'api.help.action.workspaceSummary',
|
|
6
|
+
example: 'mf api workspace-summary --json',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
action: 'command-catalog',
|
|
10
|
+
requiresChanged: false,
|
|
11
|
+
helpKey: 'api.help.action.commandCatalog',
|
|
12
|
+
example: 'mf api command-catalog --json',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
action: 'verification-plan',
|
|
16
|
+
requiresChanged: true,
|
|
17
|
+
helpKey: 'api.help.action.verificationPlan',
|
|
18
|
+
example: 'mf api verification-plan --changed --json',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
action: 'latest-evidence',
|
|
22
|
+
requiresChanged: false,
|
|
23
|
+
helpKey: 'api.help.action.latestEvidence',
|
|
24
|
+
example: 'mf api latest-evidence --json',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
action: 'diff-risk',
|
|
28
|
+
requiresChanged: true,
|
|
29
|
+
helpKey: 'api.help.action.diffRisk',
|
|
30
|
+
example: 'mf api diff-risk --changed --json',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
action: 'health',
|
|
34
|
+
requiresChanged: false,
|
|
35
|
+
helpKey: 'api.help.action.health',
|
|
36
|
+
example: 'mf api health --json',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
action: 'locks',
|
|
40
|
+
requiresChanged: false,
|
|
41
|
+
helpKey: 'api.help.action.locks',
|
|
42
|
+
example: 'mf api locks --json',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
const API_REPORT_ACTION_NAMES = new Set(API_REPORT_ACTIONS.map((spec) => spec.action));
|
|
46
|
+
export function isApiReportAction(value) {
|
|
47
|
+
return API_REPORT_ACTION_NAMES.has(value);
|
|
48
|
+
}
|
|
49
|
+
export function apiReportActionSpec(action) {
|
|
50
|
+
const spec = API_REPORT_ACTIONS.find((candidate) => candidate.action === action);
|
|
51
|
+
if (!spec) {
|
|
52
|
+
throw new Error(`Unknown API report action: ${action}`);
|
|
53
|
+
}
|
|
54
|
+
return spec;
|
|
55
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { printUsageError } from '../../lib/cli-output.js';
|
|
2
|
+
import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../../lib/option-parser.js';
|
|
3
|
+
import { t } from '../../lib/i18n.js';
|
|
4
|
+
const API_JSON_ONLY_OPTIONS = [
|
|
5
|
+
{ name: '--json', kind: 'boolean' },
|
|
6
|
+
{ name: '--help', kind: 'boolean', aliases: ['-h'] },
|
|
7
|
+
];
|
|
8
|
+
const API_CHANGED_JSON_OPTIONS = [
|
|
9
|
+
{ name: '--changed', kind: 'boolean' },
|
|
10
|
+
{ name: '--json', kind: 'boolean' },
|
|
11
|
+
{ name: '--help', kind: 'boolean', aliases: ['-h'] },
|
|
12
|
+
];
|
|
13
|
+
function validateJsonOnlyAction(action, args, reporter, lang, runtime) {
|
|
14
|
+
const parsed = parseCliOptions(args, API_JSON_ONLY_OPTIONS);
|
|
15
|
+
if (hasParsedCliOption(parsed, '--help')) {
|
|
16
|
+
reporter.stdout(runtime.getHelp(lang));
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (parsed.error) {
|
|
20
|
+
printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf api --help', runtime.getHelp(lang), lang);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (!hasParsedCliOption(parsed, '--json')) {
|
|
24
|
+
printUsageError(reporter, t(lang, 'api.error.actionRequiresJson', { action }), 'mf api --help', runtime.getHelp(lang), lang);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
function validateChangedJsonAction(action, args, reporter, lang, runtime) {
|
|
30
|
+
const parsed = parseCliOptions(args, API_CHANGED_JSON_OPTIONS);
|
|
31
|
+
if (hasParsedCliOption(parsed, '--help')) {
|
|
32
|
+
reporter.stdout(runtime.getHelp(lang));
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (parsed.error) {
|
|
36
|
+
printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf api --help', runtime.getHelp(lang), lang);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (!hasParsedCliOption(parsed, '--json')) {
|
|
40
|
+
printUsageError(reporter, t(lang, 'api.error.actionRequiresJson', { action }), 'mf api --help', runtime.getHelp(lang), lang);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (!hasParsedCliOption(parsed, '--changed')) {
|
|
44
|
+
printUsageError(reporter, t(lang, 'api.error.actionRequiresChanged', { action }), 'mf api --help', runtime.getHelp(lang), lang);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
export function runJsonOnlyApiReport(action, args, reporter, lang, runtime) {
|
|
50
|
+
if (!validateJsonOnlyAction(action, args, reporter, lang, runtime)) {
|
|
51
|
+
return hasCliOptionToken(args, '--help', ['-h']) ? 0 : 1;
|
|
52
|
+
}
|
|
53
|
+
runtime.writeReport(action, reporter);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
export function runChangedApiReport(action, args, reporter, lang, runtime) {
|
|
57
|
+
if (!validateChangedJsonAction(action, args, reporter, lang, runtime)) {
|
|
58
|
+
return hasCliOptionToken(args, '--help', ['-h']) ? 0 : 1;
|
|
59
|
+
}
|
|
60
|
+
runtime.writeReport(action, reporter);
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { apiReportActionSpec, isApiReportAction } from './actions.js';
|
|
3
|
+
import { printUsageError } from '../../lib/cli-output.js';
|
|
4
|
+
import { formatCliOptionParseError, hasParsedCliOption, parseCliOptions, } from '../../lib/option-parser.js';
|
|
5
|
+
import { isRecord } from '../../lib/command-contract.js';
|
|
6
|
+
import { t } from '../../lib/i18n.js';
|
|
7
|
+
const API_SERVE_SCHEMA_VERSION = '1';
|
|
8
|
+
const API_SERVE_OPTIONS = [
|
|
9
|
+
{ name: '--stdio', kind: 'boolean' },
|
|
10
|
+
{ name: '--help', kind: 'boolean', aliases: ['-h'] },
|
|
11
|
+
];
|
|
12
|
+
function createApiServePolicy() {
|
|
13
|
+
return {
|
|
14
|
+
mode: 'read_only',
|
|
15
|
+
executes_commands: false,
|
|
16
|
+
direct_commands_allowed: false,
|
|
17
|
+
raw_output_included: false,
|
|
18
|
+
hidden_reasoning_included: false,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function createApiServeError(id, code, message) {
|
|
22
|
+
return {
|
|
23
|
+
schema_version: API_SERVE_SCHEMA_VERSION,
|
|
24
|
+
command: 'api serve',
|
|
25
|
+
transport: 'stdio',
|
|
26
|
+
id,
|
|
27
|
+
ok: false,
|
|
28
|
+
policy: createApiServePolicy(),
|
|
29
|
+
error: {
|
|
30
|
+
code,
|
|
31
|
+
message,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function createApiServeSuccess(id, result) {
|
|
36
|
+
return {
|
|
37
|
+
schema_version: API_SERVE_SCHEMA_VERSION,
|
|
38
|
+
command: 'api serve',
|
|
39
|
+
transport: 'stdio',
|
|
40
|
+
id,
|
|
41
|
+
ok: true,
|
|
42
|
+
policy: createApiServePolicy(),
|
|
43
|
+
result,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function readApiServeId(request) {
|
|
47
|
+
if (!isRecord(request) || !Object.hasOwn(request, 'id')) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const value = request.id;
|
|
51
|
+
if (typeof value === 'string' || typeof value === 'number' || value === null) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function parseApiServeRequestLine(line) {
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(line);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
63
|
+
return {
|
|
64
|
+
request: null,
|
|
65
|
+
error: createApiServeError(null, 'invalid_json', `Invalid JSON request: ${message}`),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const id = readApiServeId(parsed);
|
|
69
|
+
if (!isRecord(parsed)) {
|
|
70
|
+
return {
|
|
71
|
+
request: null,
|
|
72
|
+
error: createApiServeError(id, 'invalid_request', 'Request must be a JSON object.'),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
request: {
|
|
77
|
+
id,
|
|
78
|
+
action: parsed.action,
|
|
79
|
+
changed: parsed.changed,
|
|
80
|
+
},
|
|
81
|
+
error: null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function createApiServeResponse(request, runtime) {
|
|
85
|
+
const id = typeof request.id === 'string' || typeof request.id === 'number' || request.id === null ? request.id : null;
|
|
86
|
+
if (typeof request.action !== 'string') {
|
|
87
|
+
return createApiServeError(id, 'invalid_request', 'Request field "action" must be a string.');
|
|
88
|
+
}
|
|
89
|
+
if (!isApiReportAction(request.action)) {
|
|
90
|
+
return createApiServeError(id, 'unknown_action', `Unknown api action: ${request.action}`);
|
|
91
|
+
}
|
|
92
|
+
const spec = apiReportActionSpec(request.action);
|
|
93
|
+
if (spec.requiresChanged && request.changed !== true) {
|
|
94
|
+
return createApiServeError(id, 'action_requires_changed', `${request.action} requires changed: true.`);
|
|
95
|
+
}
|
|
96
|
+
if (!spec.requiresChanged && request.changed === true) {
|
|
97
|
+
return createApiServeError(id, 'action_does_not_accept_changed', `${request.action} does not accept changed: true.`);
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
return createApiServeSuccess(id, runtime.createReport(request.action));
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
return createApiServeError(id, 'report_unavailable', error instanceof Error ? error.message : String(error));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function writeApiServeResponse(response, reporter) {
|
|
107
|
+
const line = `${JSON.stringify(response)}\n`;
|
|
108
|
+
if (reporter.writeStdout) {
|
|
109
|
+
reporter.writeStdout(line);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
reporter.stdout(line.trimEnd());
|
|
113
|
+
}
|
|
114
|
+
export async function runApiServe(args, reporter, lang, runtime) {
|
|
115
|
+
const parsed = parseCliOptions(args, API_SERVE_OPTIONS);
|
|
116
|
+
if (hasParsedCliOption(parsed, '--help')) {
|
|
117
|
+
reporter.stdout(runtime.getHelp(lang));
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
if (parsed.error) {
|
|
121
|
+
printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf api --help', runtime.getHelp(lang), lang);
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
if (!hasParsedCliOption(parsed, '--stdio')) {
|
|
125
|
+
printUsageError(reporter, t(lang, 'api.error.serveRequiresStdio'), 'mf api --help', runtime.getHelp(lang), lang);
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
const input = createInterface({
|
|
129
|
+
input: process.stdin,
|
|
130
|
+
crlfDelay: Infinity,
|
|
131
|
+
});
|
|
132
|
+
for await (const rawLine of input) {
|
|
133
|
+
const line = rawLine.trim();
|
|
134
|
+
if (line.length === 0) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const parsed = parseApiServeRequestLine(line);
|
|
138
|
+
if (parsed.error) {
|
|
139
|
+
writeApiServeResponse(parsed.error, reporter);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (!parsed.request) {
|
|
143
|
+
writeApiServeResponse(createApiServeError(null, 'invalid_request', 'Request must be a JSON object.'), reporter);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
writeApiServeResponse(createApiServeResponse(parsed.request, runtime), reporter);
|
|
147
|
+
}
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function createRecommendedNextCommands(output) {
|
|
2
|
+
if (!output.installed) {
|
|
3
|
+
return ['mf init --dry-run', 'mf init --yes'];
|
|
4
|
+
}
|
|
5
|
+
if (!output.check.ok) {
|
|
6
|
+
return ['mf check', 'mf status --json', 'mf update --dry-run'];
|
|
7
|
+
}
|
|
8
|
+
const commands = ['mf context --json', 'mf doctor --json', 'mf check --strict'];
|
|
9
|
+
if (output.git_state.status === 'available' && output.git_state.changed_file_count !== null && output.git_state.changed_file_count > 0) {
|
|
10
|
+
commands.push('mf classify --changed --json', 'mf verify --changed --plan-only --json');
|
|
11
|
+
}
|
|
12
|
+
return commands;
|
|
13
|
+
}
|
package/dist/cli/commands/api.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { createInterface } from 'node:readline';
|
|
4
3
|
import { createClassifyOutput } from './classify.js';
|
|
4
|
+
import { API_REPORT_ACTIONS, apiReportActionSpec, isApiReportAction, } from './api/actions.js';
|
|
5
|
+
import { runChangedApiReport, runJsonOnlyApiReport } from './api/report-runner.js';
|
|
6
|
+
import { runApiServe } from './api/serve.js';
|
|
7
|
+
import { createRecommendedNextCommands } from './api/workspace-recommendations.js';
|
|
5
8
|
import { listActiveRunLocks } from '../../core/active-run-locks.js';
|
|
6
9
|
import { createChangeVerificationReport, } from '../../core/change-verification.js';
|
|
7
10
|
import { readUtf8FileInsideWithoutSymlinks } from '../../core/safe-filesystem.js';
|
|
8
11
|
import { createVerificationPlanId } from '../../core/verification-plan-id.js';
|
|
9
12
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
10
|
-
import {
|
|
13
|
+
import { hasCliOptionToken } from '../lib/option-parser.js';
|
|
11
14
|
import { getAgentContext } from '../lib/agent-context.js';
|
|
12
15
|
import { isRecord, readCommandContract, readPositiveInteger, readString, readStringArray, } from '../lib/command-contract.js';
|
|
13
16
|
import { readGitChangedFiles } from '../lib/git-changes.js';
|
|
@@ -22,79 +25,10 @@ const API_LATEST_EVIDENCE_SCHEMA_VERSION = '1';
|
|
|
22
25
|
const API_DIFF_RISK_SCHEMA_VERSION = '1';
|
|
23
26
|
const API_HEALTH_SCHEMA_VERSION = '1';
|
|
24
27
|
const API_LOCKS_SCHEMA_VERSION = '1';
|
|
25
|
-
const API_SERVE_SCHEMA_VERSION = '1';
|
|
26
28
|
const COMMANDS_RELATIVE_PATH = '.mustflow/config/commands.toml';
|
|
27
29
|
const LATEST_RUN_RELATIVE_PATH = '.mustflow/state/runs/latest.json';
|
|
28
30
|
const LOCKS_RELATIVE_PATH = '.mustflow/state/locks';
|
|
29
31
|
const MUSTFLOW_JSON_MAX_BYTES = 1024 * 1024;
|
|
30
|
-
const API_JSON_ONLY_OPTIONS = [
|
|
31
|
-
{ name: '--json', kind: 'boolean' },
|
|
32
|
-
{ name: '--help', kind: 'boolean', aliases: ['-h'] },
|
|
33
|
-
];
|
|
34
|
-
const API_CHANGED_JSON_OPTIONS = [
|
|
35
|
-
{ name: '--changed', kind: 'boolean' },
|
|
36
|
-
{ name: '--json', kind: 'boolean' },
|
|
37
|
-
{ name: '--help', kind: 'boolean', aliases: ['-h'] },
|
|
38
|
-
];
|
|
39
|
-
const API_SERVE_OPTIONS = [
|
|
40
|
-
{ name: '--stdio', kind: 'boolean' },
|
|
41
|
-
{ name: '--help', kind: 'boolean', aliases: ['-h'] },
|
|
42
|
-
];
|
|
43
|
-
const API_REPORT_ACTIONS = [
|
|
44
|
-
{
|
|
45
|
-
action: 'workspace-summary',
|
|
46
|
-
requiresChanged: false,
|
|
47
|
-
helpKey: 'api.help.action.workspaceSummary',
|
|
48
|
-
example: 'mf api workspace-summary --json',
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
action: 'command-catalog',
|
|
52
|
-
requiresChanged: false,
|
|
53
|
-
helpKey: 'api.help.action.commandCatalog',
|
|
54
|
-
example: 'mf api command-catalog --json',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
action: 'verification-plan',
|
|
58
|
-
requiresChanged: true,
|
|
59
|
-
helpKey: 'api.help.action.verificationPlan',
|
|
60
|
-
example: 'mf api verification-plan --changed --json',
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
action: 'latest-evidence',
|
|
64
|
-
requiresChanged: false,
|
|
65
|
-
helpKey: 'api.help.action.latestEvidence',
|
|
66
|
-
example: 'mf api latest-evidence --json',
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
action: 'diff-risk',
|
|
70
|
-
requiresChanged: true,
|
|
71
|
-
helpKey: 'api.help.action.diffRisk',
|
|
72
|
-
example: 'mf api diff-risk --changed --json',
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
action: 'health',
|
|
76
|
-
requiresChanged: false,
|
|
77
|
-
helpKey: 'api.help.action.health',
|
|
78
|
-
example: 'mf api health --json',
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
action: 'locks',
|
|
82
|
-
requiresChanged: false,
|
|
83
|
-
helpKey: 'api.help.action.locks',
|
|
84
|
-
example: 'mf api locks --json',
|
|
85
|
-
},
|
|
86
|
-
];
|
|
87
|
-
const API_REPORT_ACTION_NAMES = new Set(API_REPORT_ACTIONS.map((spec) => spec.action));
|
|
88
|
-
function isApiReportAction(value) {
|
|
89
|
-
return API_REPORT_ACTION_NAMES.has(value);
|
|
90
|
-
}
|
|
91
|
-
function apiReportActionSpec(action) {
|
|
92
|
-
const spec = API_REPORT_ACTIONS.find((candidate) => candidate.action === action);
|
|
93
|
-
if (!spec) {
|
|
94
|
-
throw new Error(`Unknown API report action: ${action}`);
|
|
95
|
-
}
|
|
96
|
-
return spec;
|
|
97
|
-
}
|
|
98
32
|
export function getApiHelp(lang = 'en') {
|
|
99
33
|
return renderHelp({
|
|
100
34
|
usage: 'mf api <action> [options]',
|
|
@@ -194,19 +128,6 @@ function createRecommendedReadSurfaces(context) {
|
|
|
194
128
|
];
|
|
195
129
|
return [...new Set(surfaces)];
|
|
196
130
|
}
|
|
197
|
-
function createRecommendedNextCommands(output) {
|
|
198
|
-
if (!output.installed) {
|
|
199
|
-
return ['mf init --dry-run', 'mf init --yes'];
|
|
200
|
-
}
|
|
201
|
-
if (!output.check.ok) {
|
|
202
|
-
return ['mf check', 'mf status --json', 'mf update --dry-run'];
|
|
203
|
-
}
|
|
204
|
-
const commands = ['mf context --json', 'mf doctor --json', 'mf check --strict'];
|
|
205
|
-
if (output.git_state.status === 'available' && output.git_state.changed_file_count !== null && output.git_state.changed_file_count > 0) {
|
|
206
|
-
commands.push('mf classify --changed --json', 'mf verify --changed --plan-only --json');
|
|
207
|
-
}
|
|
208
|
-
return commands;
|
|
209
|
-
}
|
|
210
131
|
function isSafeIntentName(value) {
|
|
211
132
|
return /^[A-Za-z0-9_-]+$/u.test(value);
|
|
212
133
|
}
|
|
@@ -1045,194 +966,6 @@ function createApiReport(action) {
|
|
|
1045
966
|
function writeApiReport(action, reporter) {
|
|
1046
967
|
reporter.stdout(JSON.stringify(createApiReport(action), null, 2));
|
|
1047
968
|
}
|
|
1048
|
-
function createApiServePolicy() {
|
|
1049
|
-
return {
|
|
1050
|
-
mode: 'read_only',
|
|
1051
|
-
executes_commands: false,
|
|
1052
|
-
direct_commands_allowed: false,
|
|
1053
|
-
raw_output_included: false,
|
|
1054
|
-
hidden_reasoning_included: false,
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
function createApiServeError(id, code, message) {
|
|
1058
|
-
return {
|
|
1059
|
-
schema_version: API_SERVE_SCHEMA_VERSION,
|
|
1060
|
-
command: 'api serve',
|
|
1061
|
-
transport: 'stdio',
|
|
1062
|
-
id,
|
|
1063
|
-
ok: false,
|
|
1064
|
-
policy: createApiServePolicy(),
|
|
1065
|
-
error: {
|
|
1066
|
-
code,
|
|
1067
|
-
message,
|
|
1068
|
-
},
|
|
1069
|
-
};
|
|
1070
|
-
}
|
|
1071
|
-
function createApiServeSuccess(id, result) {
|
|
1072
|
-
return {
|
|
1073
|
-
schema_version: API_SERVE_SCHEMA_VERSION,
|
|
1074
|
-
command: 'api serve',
|
|
1075
|
-
transport: 'stdio',
|
|
1076
|
-
id,
|
|
1077
|
-
ok: true,
|
|
1078
|
-
policy: createApiServePolicy(),
|
|
1079
|
-
result,
|
|
1080
|
-
};
|
|
1081
|
-
}
|
|
1082
|
-
function readApiServeId(request) {
|
|
1083
|
-
if (!isRecord(request) || !Object.hasOwn(request, 'id')) {
|
|
1084
|
-
return null;
|
|
1085
|
-
}
|
|
1086
|
-
const value = request.id;
|
|
1087
|
-
if (typeof value === 'string' || typeof value === 'number' || value === null) {
|
|
1088
|
-
return value;
|
|
1089
|
-
}
|
|
1090
|
-
return null;
|
|
1091
|
-
}
|
|
1092
|
-
function parseApiServeRequestLine(line) {
|
|
1093
|
-
let parsed;
|
|
1094
|
-
try {
|
|
1095
|
-
parsed = JSON.parse(line);
|
|
1096
|
-
}
|
|
1097
|
-
catch (error) {
|
|
1098
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1099
|
-
return {
|
|
1100
|
-
request: null,
|
|
1101
|
-
error: createApiServeError(null, 'invalid_json', `Invalid JSON request: ${message}`),
|
|
1102
|
-
};
|
|
1103
|
-
}
|
|
1104
|
-
const id = readApiServeId(parsed);
|
|
1105
|
-
if (!isRecord(parsed)) {
|
|
1106
|
-
return {
|
|
1107
|
-
request: null,
|
|
1108
|
-
error: createApiServeError(id, 'invalid_request', 'Request must be a JSON object.'),
|
|
1109
|
-
};
|
|
1110
|
-
}
|
|
1111
|
-
return {
|
|
1112
|
-
request: {
|
|
1113
|
-
id,
|
|
1114
|
-
action: parsed.action,
|
|
1115
|
-
changed: parsed.changed,
|
|
1116
|
-
},
|
|
1117
|
-
error: null,
|
|
1118
|
-
};
|
|
1119
|
-
}
|
|
1120
|
-
function createApiServeResponse(request) {
|
|
1121
|
-
const id = typeof request.id === 'string' || typeof request.id === 'number' || request.id === null ? request.id : null;
|
|
1122
|
-
if (typeof request.action !== 'string') {
|
|
1123
|
-
return createApiServeError(id, 'invalid_request', 'Request field "action" must be a string.');
|
|
1124
|
-
}
|
|
1125
|
-
if (!isApiReportAction(request.action)) {
|
|
1126
|
-
return createApiServeError(id, 'unknown_action', `Unknown api action: ${request.action}`);
|
|
1127
|
-
}
|
|
1128
|
-
const spec = apiReportActionSpec(request.action);
|
|
1129
|
-
if (spec.requiresChanged && request.changed !== true) {
|
|
1130
|
-
return createApiServeError(id, 'action_requires_changed', `${request.action} requires changed: true.`);
|
|
1131
|
-
}
|
|
1132
|
-
if (!spec.requiresChanged && request.changed === true) {
|
|
1133
|
-
return createApiServeError(id, 'action_does_not_accept_changed', `${request.action} does not accept changed: true.`);
|
|
1134
|
-
}
|
|
1135
|
-
try {
|
|
1136
|
-
return createApiServeSuccess(id, createApiReport(request.action));
|
|
1137
|
-
}
|
|
1138
|
-
catch (error) {
|
|
1139
|
-
return createApiServeError(id, 'report_unavailable', error instanceof Error ? error.message : String(error));
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
function writeApiServeResponse(response, reporter) {
|
|
1143
|
-
const line = `${JSON.stringify(response)}\n`;
|
|
1144
|
-
if (reporter.writeStdout) {
|
|
1145
|
-
reporter.writeStdout(line);
|
|
1146
|
-
return;
|
|
1147
|
-
}
|
|
1148
|
-
reporter.stdout(line.trimEnd());
|
|
1149
|
-
}
|
|
1150
|
-
async function runApiServe(args, reporter, lang) {
|
|
1151
|
-
const parsed = parseCliOptions(args, API_SERVE_OPTIONS);
|
|
1152
|
-
if (hasParsedCliOption(parsed, '--help')) {
|
|
1153
|
-
reporter.stdout(getApiHelp(lang));
|
|
1154
|
-
return 0;
|
|
1155
|
-
}
|
|
1156
|
-
if (parsed.error) {
|
|
1157
|
-
printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf api --help', getApiHelp(lang), lang);
|
|
1158
|
-
return 1;
|
|
1159
|
-
}
|
|
1160
|
-
if (!hasParsedCliOption(parsed, '--stdio')) {
|
|
1161
|
-
printUsageError(reporter, t(lang, 'api.error.serveRequiresStdio'), 'mf api --help', getApiHelp(lang), lang);
|
|
1162
|
-
return 1;
|
|
1163
|
-
}
|
|
1164
|
-
const input = createInterface({
|
|
1165
|
-
input: process.stdin,
|
|
1166
|
-
crlfDelay: Infinity,
|
|
1167
|
-
});
|
|
1168
|
-
for await (const rawLine of input) {
|
|
1169
|
-
const line = rawLine.trim();
|
|
1170
|
-
if (line.length === 0) {
|
|
1171
|
-
continue;
|
|
1172
|
-
}
|
|
1173
|
-
const parsed = parseApiServeRequestLine(line);
|
|
1174
|
-
if (parsed.error) {
|
|
1175
|
-
writeApiServeResponse(parsed.error, reporter);
|
|
1176
|
-
continue;
|
|
1177
|
-
}
|
|
1178
|
-
if (!parsed.request) {
|
|
1179
|
-
writeApiServeResponse(createApiServeError(null, 'invalid_request', 'Request must be a JSON object.'), reporter);
|
|
1180
|
-
continue;
|
|
1181
|
-
}
|
|
1182
|
-
writeApiServeResponse(createApiServeResponse(parsed.request), reporter);
|
|
1183
|
-
}
|
|
1184
|
-
return 0;
|
|
1185
|
-
}
|
|
1186
|
-
function validateJsonOnlyAction(action, args, reporter, lang) {
|
|
1187
|
-
const parsed = parseCliOptions(args, API_JSON_ONLY_OPTIONS);
|
|
1188
|
-
if (hasParsedCliOption(parsed, '--help')) {
|
|
1189
|
-
reporter.stdout(getApiHelp(lang));
|
|
1190
|
-
return false;
|
|
1191
|
-
}
|
|
1192
|
-
if (parsed.error) {
|
|
1193
|
-
printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf api --help', getApiHelp(lang), lang);
|
|
1194
|
-
return false;
|
|
1195
|
-
}
|
|
1196
|
-
if (!hasParsedCliOption(parsed, '--json')) {
|
|
1197
|
-
printUsageError(reporter, t(lang, 'api.error.actionRequiresJson', { action }), 'mf api --help', getApiHelp(lang), lang);
|
|
1198
|
-
return false;
|
|
1199
|
-
}
|
|
1200
|
-
return true;
|
|
1201
|
-
}
|
|
1202
|
-
function validateChangedJsonAction(action, args, reporter, lang) {
|
|
1203
|
-
const parsed = parseCliOptions(args, API_CHANGED_JSON_OPTIONS);
|
|
1204
|
-
if (hasParsedCliOption(parsed, '--help')) {
|
|
1205
|
-
reporter.stdout(getApiHelp(lang));
|
|
1206
|
-
return false;
|
|
1207
|
-
}
|
|
1208
|
-
if (parsed.error) {
|
|
1209
|
-
printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf api --help', getApiHelp(lang), lang);
|
|
1210
|
-
return false;
|
|
1211
|
-
}
|
|
1212
|
-
if (!hasParsedCliOption(parsed, '--json')) {
|
|
1213
|
-
printUsageError(reporter, t(lang, 'api.error.actionRequiresJson', { action }), 'mf api --help', getApiHelp(lang), lang);
|
|
1214
|
-
return false;
|
|
1215
|
-
}
|
|
1216
|
-
if (!hasParsedCliOption(parsed, '--changed')) {
|
|
1217
|
-
printUsageError(reporter, t(lang, 'api.error.actionRequiresChanged', { action }), 'mf api --help', getApiHelp(lang), lang);
|
|
1218
|
-
return false;
|
|
1219
|
-
}
|
|
1220
|
-
return true;
|
|
1221
|
-
}
|
|
1222
|
-
function runJsonOnlyApiReport(action, args, reporter, lang) {
|
|
1223
|
-
if (!validateJsonOnlyAction(action, args, reporter, lang)) {
|
|
1224
|
-
return hasCliOptionToken(args, '--help', ['-h']) ? 0 : 1;
|
|
1225
|
-
}
|
|
1226
|
-
writeApiReport(action, reporter);
|
|
1227
|
-
return 0;
|
|
1228
|
-
}
|
|
1229
|
-
function runChangedApiReport(action, args, reporter, lang) {
|
|
1230
|
-
if (!validateChangedJsonAction(action, args, reporter, lang)) {
|
|
1231
|
-
return hasCliOptionToken(args, '--help', ['-h']) ? 0 : 1;
|
|
1232
|
-
}
|
|
1233
|
-
writeApiReport(action, reporter);
|
|
1234
|
-
return 0;
|
|
1235
|
-
}
|
|
1236
969
|
export function runApi(args, reporter, lang = 'en') {
|
|
1237
970
|
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
1238
971
|
reporter.stdout(getApiHelp(lang));
|
|
@@ -1248,12 +981,19 @@ export function runApi(args, reporter, lang = 'en') {
|
|
|
1248
981
|
return 1;
|
|
1249
982
|
}
|
|
1250
983
|
if (action === 'serve') {
|
|
1251
|
-
return runApiServe(rest, reporter, lang
|
|
984
|
+
return runApiServe(rest, reporter, lang, {
|
|
985
|
+
createReport: createApiReport,
|
|
986
|
+
getHelp: getApiHelp,
|
|
987
|
+
});
|
|
1252
988
|
}
|
|
1253
989
|
if (isApiReportAction(action)) {
|
|
990
|
+
const reportRuntime = {
|
|
991
|
+
getHelp: getApiHelp,
|
|
992
|
+
writeReport: writeApiReport,
|
|
993
|
+
};
|
|
1254
994
|
return apiReportActionSpec(action).requiresChanged
|
|
1255
|
-
? runChangedApiReport(action, rest, reporter, lang)
|
|
1256
|
-
: runJsonOnlyApiReport(action, rest, reporter, lang);
|
|
995
|
+
? runChangedApiReport(action, rest, reporter, lang, reportRuntime)
|
|
996
|
+
: runJsonOnlyApiReport(action, rest, reporter, lang, reportRuntime);
|
|
1257
997
|
}
|
|
1258
998
|
printUsageError(reporter, t(lang, 'api.error.unknownAction', { action }), 'mf api --help', getApiHelp(lang), lang);
|
|
1259
999
|
return 1;
|
|
@@ -107,11 +107,45 @@ function sourceAnchorAuthority() {
|
|
|
107
107
|
can_instruct_agent: false,
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
|
-
function
|
|
111
|
-
return
|
|
110
|
+
function createEmptyDocumentSearchMetadata() {
|
|
111
|
+
return { sectionHeadings: [], documentTerms: [] };
|
|
112
112
|
}
|
|
113
|
-
function
|
|
114
|
-
|
|
113
|
+
function queryRowsByDocumentPath(database, sqlPrefix, documentPaths) {
|
|
114
|
+
const rows = [];
|
|
115
|
+
const chunkSize = 200;
|
|
116
|
+
for (let start = 0; start < documentPaths.length; start += chunkSize) {
|
|
117
|
+
const chunk = documentPaths.slice(start, start + chunkSize);
|
|
118
|
+
rows.push(...queryRows(database, `${sqlPrefix} WHERE document_path IN (${sqlPlaceholders(chunk)})`, chunk));
|
|
119
|
+
}
|
|
120
|
+
return rows;
|
|
121
|
+
}
|
|
122
|
+
function readDocumentSearchMetadata(database, documentPaths) {
|
|
123
|
+
const uniquePaths = [...new Set(documentPaths)].filter((documentPath) => documentPath.length > 0);
|
|
124
|
+
const metadata = new Map(uniquePaths.map((documentPath) => [documentPath, { sectionHeadings: [], documentTerms: [] }]));
|
|
125
|
+
if (uniquePaths.length === 0) {
|
|
126
|
+
return metadata;
|
|
127
|
+
}
|
|
128
|
+
for (const row of queryRowsByDocumentPath(database, 'SELECT document_path, ordinal, heading FROM sections', uniquePaths).sort((left, right) => {
|
|
129
|
+
const leftPath = toSearchString(left.document_path);
|
|
130
|
+
const rightPath = toSearchString(right.document_path);
|
|
131
|
+
return leftPath.localeCompare(rightPath) || Number(left.ordinal ?? 0) - Number(right.ordinal ?? 0);
|
|
132
|
+
})) {
|
|
133
|
+
const entry = metadata.get(toSearchString(row.document_path));
|
|
134
|
+
if (entry) {
|
|
135
|
+
entry.sectionHeadings.push(toSearchString(row.heading));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const row of queryRowsByDocumentPath(database, 'SELECT document_path, term FROM document_terms', uniquePaths).sort((left, right) => {
|
|
139
|
+
const leftPath = toSearchString(left.document_path);
|
|
140
|
+
const rightPath = toSearchString(right.document_path);
|
|
141
|
+
return leftPath.localeCompare(rightPath) || toSearchString(left.term).localeCompare(toSearchString(right.term));
|
|
142
|
+
})) {
|
|
143
|
+
const entry = metadata.get(toSearchString(row.document_path));
|
|
144
|
+
if (entry) {
|
|
145
|
+
entry.documentTerms.push(toSearchString(row.term));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return metadata;
|
|
115
149
|
}
|
|
116
150
|
function commandEffectFromRow(row) {
|
|
117
151
|
return {
|
|
@@ -363,13 +397,16 @@ export async function searchLocalIndex(projectRoot, query, options = {}) {
|
|
|
363
397
|
throw new Error(`Local mustflow index is stale: ${stalePaths.join(', ')}. Run \`mf index\` before searching. Refresh command: mf index`);
|
|
364
398
|
}
|
|
365
399
|
if (scope === 'workflow' || scope === 'all') {
|
|
366
|
-
|
|
400
|
+
const documentRows = queryCandidateRows(database, 'SELECT path, type, title, content_snippet FROM documents', 'path', indexedMatches.documents, indexedMatches);
|
|
401
|
+
const metadataByDocument = readDocumentSearchMetadata(database, documentRows.map((row) => toSearchString(row.path)));
|
|
402
|
+
for (const row of documentRows) {
|
|
367
403
|
const pathValue = toSearchString(row.path);
|
|
368
404
|
const typeValue = toSearchString(row.type);
|
|
369
405
|
const title = toSearchString(row.title);
|
|
370
406
|
const contentSnippet = toSearchString(row.content_snippet);
|
|
371
|
-
const
|
|
372
|
-
const
|
|
407
|
+
const metadata = metadataByDocument.get(pathValue) ?? createEmptyDocumentSearchMetadata();
|
|
408
|
+
const sectionHeadings = metadata.sectionHeadings;
|
|
409
|
+
const documentTerms = metadata.documentTerms;
|
|
373
410
|
const primaryFields = [pathValue, title];
|
|
374
411
|
const secondaryFields = [typeValue, contentSnippet, ...sectionHeadings, ...documentTerms];
|
|
375
412
|
const fields = [...primaryFields, ...secondaryFields];
|