mustflow 1.30.0 → 2.11.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 +35 -11
- package/dist/cli/commands/classify.js +61 -6
- package/dist/cli/commands/contract-lint.js +13 -4
- package/dist/cli/commands/dashboard.js +6 -0
- package/dist/cli/commands/index.js +5 -0
- package/dist/cli/commands/run.js +224 -48
- package/dist/cli/commands/upgrade.js +65 -0
- package/dist/cli/commands/verify.js +550 -33
- package/dist/cli/i18n/en.js +73 -10
- package/dist/cli/i18n/es.js +73 -10
- package/dist/cli/i18n/fr.js +73 -10
- package/dist/cli/i18n/hi.js +73 -10
- package/dist/cli/i18n/ko.js +73 -10
- package/dist/cli/i18n/zh.js +73 -10
- package/dist/cli/index.js +27 -46
- package/dist/cli/lib/command-registry.js +5 -0
- package/dist/cli/lib/dashboard-export.js +62 -12
- package/dist/cli/lib/dashboard-html/client-script.js +1936 -0
- package/dist/cli/lib/dashboard-html/locale-bootstrap.js +8 -0
- package/dist/cli/lib/dashboard-html/styles.js +572 -0
- package/dist/cli/lib/dashboard-html/template.js +134 -0
- package/dist/cli/lib/dashboard-html/types.js +1 -0
- package/dist/cli/lib/dashboard-html.js +1 -1907
- package/dist/cli/lib/dashboard-locale.js +37 -0
- package/dist/cli/lib/local-index/constants.js +48 -0
- package/dist/cli/lib/local-index/index.js +2256 -0
- package/dist/cli/lib/local-index/sql.js +15 -0
- package/dist/cli/lib/local-index/types.js +1 -0
- package/dist/cli/lib/local-index.js +1 -1908
- package/dist/cli/lib/reporter.js +6 -0
- package/dist/cli/lib/run-plan.js +96 -4
- package/dist/cli/lib/templates.js +18 -1
- package/dist/cli/lib/validation/command-intents.js +11 -0
- package/dist/cli/lib/validation/constants.js +238 -0
- package/dist/cli/lib/validation/index.js +1384 -0
- package/dist/cli/lib/validation/primitives.js +198 -0
- package/dist/cli/lib/validation/test-selection.js +95 -0
- package/dist/cli/lib/validation/types.js +1 -0
- package/dist/cli/lib/validation.js +1 -1661
- package/dist/core/bounded-output.js +38 -0
- package/dist/core/change-classification.js +6 -2
- package/dist/core/change-verification.js +240 -6
- package/dist/core/check-issues.js +12 -0
- package/dist/core/command-contract-validation.js +20 -0
- package/dist/core/command-effects.js +13 -0
- package/dist/core/completion-verdict.js +209 -0
- package/dist/core/contract-lint.js +316 -7
- package/dist/core/dashboard-verification.js +8 -0
- package/dist/core/external-evidence.js +9 -0
- package/dist/core/public-json-contracts.js +28 -0
- package/dist/core/repeated-failure.js +17 -0
- package/dist/core/repro-evidence.js +53 -0
- package/dist/core/run-performance-history.js +307 -0
- package/dist/core/run-profile.js +87 -0
- package/dist/core/run-receipt.js +171 -4
- package/dist/core/run-write-drift.js +18 -2
- package/dist/core/scope-risk.js +64 -0
- package/dist/core/skill-route-alignment.js +110 -0
- package/dist/core/source-anchor-status.js +4 -1
- package/dist/core/test-selection.js +227 -0
- package/dist/core/validation-ratchet.js +52 -0
- package/dist/core/verification-decision-graph.js +67 -0
- package/dist/core/verification-evidence.js +249 -0
- package/dist/core/verification-scheduler.js +96 -2
- package/examples/README.md +12 -4
- package/package.json +1 -1
- package/schemas/README.md +18 -4
- package/schemas/change-verification-report.schema.json +169 -5
- package/schemas/commands.schema.json +51 -1
- package/schemas/contract-lint-report.schema.json +80 -0
- package/schemas/dashboard-export.schema.json +500 -0
- package/schemas/explain-report.schema.json +2 -0
- package/schemas/latest-run-pointer.schema.json +384 -0
- package/schemas/run-receipt.schema.json +113 -0
- package/schemas/test-selection.schema.json +81 -0
- package/schemas/verify-report.schema.json +361 -1
- package/schemas/verify-run-manifest.schema.json +410 -0
- package/templates/default/common/.mustflow/config/commands.toml +1 -1
- package/templates/default/i18n.toml +1 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +124 -29
- package/templates/default/locales/en/.mustflow/skills/routes.toml +289 -0
- package/templates/default/manifest.toml +29 -2
package/dist/cli/commands/run.js
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
import { runClassify } from './classify.js';
|
|
4
|
-
import { runContext } from './context.js';
|
|
5
|
-
import { runDoctor } from './doctor.js';
|
|
6
|
-
import { runHelp } from './help.js';
|
|
7
|
-
import { runImpact } from './impact.js';
|
|
8
|
-
import { runLineEndings } from './line-endings.js';
|
|
9
|
-
import { runMap } from './map.js';
|
|
10
|
-
import { runStatus } from './status.js';
|
|
11
|
-
import { runUpdate } from './update.js';
|
|
12
|
-
import { runVersionSources } from './version-sources.js';
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
13
3
|
import { canRunMustflowBuiltinInProcess, isMustflowBinName } from '../../core/command-classification.js';
|
|
14
4
|
import { createCommandEnv } from '../../core/command-env.js';
|
|
5
|
+
import { BoundedOutputBuffer } from '../../core/bounded-output.js';
|
|
15
6
|
import { printUsageError, renderCliError, renderHelp } from '../lib/cli-output.js';
|
|
16
7
|
import { readCommandContract, readMustflowConfigIfExists } from '../../core/config-loading.js';
|
|
17
8
|
import { resolveRunReceiptRetentionPolicy } from '../../core/retention-policy.js';
|
|
@@ -20,12 +11,14 @@ import { getPackageVersion } from '../lib/package-info.js';
|
|
|
20
11
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
21
12
|
import { createRunPlan, createRunPreview, isMustflowBuiltinIntent, renderRunPreviewText, } from '../lib/run-plan.js';
|
|
22
13
|
import { createRunReceipt, writeRunReceipt } from '../../core/run-receipt.js';
|
|
14
|
+
import { recordRunPerformanceHistory } from '../../core/run-performance-history.js';
|
|
15
|
+
import { RunProfiler } from '../../core/run-profile.js';
|
|
23
16
|
import { finishRunWriteTracking, startRunWriteTracking } from '../../core/run-write-drift.js';
|
|
24
17
|
function emitOutput(reporter, output, stream) {
|
|
25
18
|
if (!output) {
|
|
26
19
|
return;
|
|
27
20
|
}
|
|
28
|
-
const text = output.toString().trimEnd();
|
|
21
|
+
const text = (typeof output === 'object' && 'tail' in output ? output.tail : output.toString()).trimEnd();
|
|
29
22
|
if (text.length === 0) {
|
|
30
23
|
return;
|
|
31
24
|
}
|
|
@@ -94,37 +87,37 @@ async function runKnownBuiltinCommand(args, reporter, lang) {
|
|
|
94
87
|
return undefined;
|
|
95
88
|
}
|
|
96
89
|
if (command === 'check') {
|
|
97
|
-
return runCheck(commandArgs, reporter, lang);
|
|
90
|
+
return (await import('./check.js')).runCheck(commandArgs, reporter, lang);
|
|
98
91
|
}
|
|
99
92
|
if (command === 'classify') {
|
|
100
|
-
return runClassify(commandArgs, reporter, lang);
|
|
93
|
+
return (await import('./classify.js')).runClassify(commandArgs, reporter, lang);
|
|
101
94
|
}
|
|
102
95
|
if (command === 'context') {
|
|
103
|
-
return runContext(commandArgs, reporter, lang);
|
|
96
|
+
return (await import('./context.js')).runContext(commandArgs, reporter, lang);
|
|
104
97
|
}
|
|
105
98
|
if (command === 'doctor') {
|
|
106
|
-
return runDoctor(commandArgs, reporter, lang);
|
|
99
|
+
return (await import('./doctor.js')).runDoctor(commandArgs, reporter, lang);
|
|
107
100
|
}
|
|
108
101
|
if (command === 'help') {
|
|
109
|
-
return runHelp(commandArgs, reporter, lang);
|
|
102
|
+
return (await import('./help.js')).runHelp(commandArgs, reporter, lang);
|
|
110
103
|
}
|
|
111
104
|
if (command === 'impact') {
|
|
112
|
-
return runImpact(commandArgs, reporter, lang);
|
|
105
|
+
return (await import('./impact.js')).runImpact(commandArgs, reporter, lang);
|
|
113
106
|
}
|
|
114
107
|
if (command === 'line-endings') {
|
|
115
|
-
return runLineEndings(commandArgs, reporter, lang);
|
|
108
|
+
return (await import('./line-endings.js')).runLineEndings(commandArgs, reporter, lang);
|
|
116
109
|
}
|
|
117
110
|
if (command === 'map') {
|
|
118
|
-
return runMap(commandArgs, reporter, lang);
|
|
111
|
+
return (await import('./map.js')).runMap(commandArgs, reporter, lang);
|
|
119
112
|
}
|
|
120
113
|
if (command === 'status') {
|
|
121
|
-
return runStatus(commandArgs, reporter, lang);
|
|
114
|
+
return (await import('./status.js')).runStatus(commandArgs, reporter, lang);
|
|
122
115
|
}
|
|
123
116
|
if (command === 'update') {
|
|
124
|
-
return runUpdate(commandArgs, reporter, lang);
|
|
117
|
+
return (await import('./update.js')).runUpdate(commandArgs, reporter, lang);
|
|
125
118
|
}
|
|
126
119
|
if (command === 'version-sources') {
|
|
127
|
-
return runVersionSources(commandArgs, reporter, lang);
|
|
120
|
+
return (await import('./version-sources.js')).runVersionSources(commandArgs, reporter, lang);
|
|
128
121
|
}
|
|
129
122
|
return undefined;
|
|
130
123
|
}
|
|
@@ -179,6 +172,76 @@ function runArgvCommand(command, cwd, maxOutputBytes, env, timeoutSeconds) {
|
|
|
179
172
|
windowsHide: true,
|
|
180
173
|
});
|
|
181
174
|
}
|
|
175
|
+
function writeStreamChunk(reporter, stream, chunk) {
|
|
176
|
+
if (stream === 'stdout') {
|
|
177
|
+
if (reporter.writeStdout) {
|
|
178
|
+
reporter.writeStdout(chunk);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
reporter.stdout(chunk.toString().trimEnd());
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (reporter.writeStderr) {
|
|
185
|
+
reporter.writeStderr(chunk);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
reporter.stderr(chunk.toString().trimEnd());
|
|
189
|
+
}
|
|
190
|
+
function runArgvCommandStreaming(command, cwd, env, timeoutSeconds, stdoutTailBytes, stderrTailBytes, reporter) {
|
|
191
|
+
return new Promise((resolve) => {
|
|
192
|
+
const stdout = new BoundedOutputBuffer(stdoutTailBytes);
|
|
193
|
+
const stderr = new BoundedOutputBuffer(stderrTailBytes);
|
|
194
|
+
let settled = false;
|
|
195
|
+
let timedOut = false;
|
|
196
|
+
let childError;
|
|
197
|
+
let childPid;
|
|
198
|
+
let timeout;
|
|
199
|
+
const child = spawn(command?.executable ?? '', command?.args ?? [], {
|
|
200
|
+
cwd,
|
|
201
|
+
env,
|
|
202
|
+
shell: command?.shell ?? false,
|
|
203
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
204
|
+
windowsHide: true,
|
|
205
|
+
detached: process.platform !== 'win32',
|
|
206
|
+
});
|
|
207
|
+
childPid = child.pid;
|
|
208
|
+
const finish = (status, signal) => {
|
|
209
|
+
if (settled) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
settled = true;
|
|
213
|
+
if (timeout) {
|
|
214
|
+
clearTimeout(timeout);
|
|
215
|
+
}
|
|
216
|
+
resolve({
|
|
217
|
+
status,
|
|
218
|
+
signal,
|
|
219
|
+
error: timedOut ? Object.assign(new Error('Command timed out'), { code: 'ETIMEDOUT' }) : childError,
|
|
220
|
+
stdout: stdout.toSnapshot(),
|
|
221
|
+
stderr: stderr.toSnapshot(),
|
|
222
|
+
pid: childPid,
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
child.stdout?.on('data', (chunk) => {
|
|
226
|
+
stdout.append(chunk);
|
|
227
|
+
writeStreamChunk(reporter, 'stdout', chunk);
|
|
228
|
+
});
|
|
229
|
+
child.stderr?.on('data', (chunk) => {
|
|
230
|
+
stderr.append(chunk);
|
|
231
|
+
writeStreamChunk(reporter, 'stderr', chunk);
|
|
232
|
+
});
|
|
233
|
+
child.once('error', (error) => {
|
|
234
|
+
childError = error;
|
|
235
|
+
});
|
|
236
|
+
child.once('close', (status, signal) => {
|
|
237
|
+
finish(status, signal);
|
|
238
|
+
});
|
|
239
|
+
timeout = setTimeout(() => {
|
|
240
|
+
timedOut = true;
|
|
241
|
+
terminateProcessTree(childPid);
|
|
242
|
+
}, timeoutSeconds * 1000);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
182
245
|
function runShellCommand(command, cwd, maxOutputBytes, env, timeoutSeconds) {
|
|
183
246
|
return spawnSync(command ?? '', {
|
|
184
247
|
cwd,
|
|
@@ -192,6 +255,61 @@ function runShellCommand(command, cwd, maxOutputBytes, env, timeoutSeconds) {
|
|
|
192
255
|
windowsHide: true,
|
|
193
256
|
});
|
|
194
257
|
}
|
|
258
|
+
function runShellCommandStreaming(command, cwd, env, timeoutSeconds, stdoutTailBytes, stderrTailBytes, reporter) {
|
|
259
|
+
return new Promise((resolve) => {
|
|
260
|
+
const stdout = new BoundedOutputBuffer(stdoutTailBytes);
|
|
261
|
+
const stderr = new BoundedOutputBuffer(stderrTailBytes);
|
|
262
|
+
let settled = false;
|
|
263
|
+
let timedOut = false;
|
|
264
|
+
let childError;
|
|
265
|
+
let childPid;
|
|
266
|
+
let timeout;
|
|
267
|
+
const child = spawn(command ?? '', {
|
|
268
|
+
cwd,
|
|
269
|
+
env,
|
|
270
|
+
shell: true,
|
|
271
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
272
|
+
windowsHide: true,
|
|
273
|
+
detached: process.platform !== 'win32',
|
|
274
|
+
});
|
|
275
|
+
childPid = child.pid;
|
|
276
|
+
const finish = (status, signal) => {
|
|
277
|
+
if (settled) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
settled = true;
|
|
281
|
+
if (timeout) {
|
|
282
|
+
clearTimeout(timeout);
|
|
283
|
+
}
|
|
284
|
+
resolve({
|
|
285
|
+
status,
|
|
286
|
+
signal,
|
|
287
|
+
error: timedOut ? Object.assign(new Error('Command timed out'), { code: 'ETIMEDOUT' }) : childError,
|
|
288
|
+
stdout: stdout.toSnapshot(),
|
|
289
|
+
stderr: stderr.toSnapshot(),
|
|
290
|
+
pid: childPid,
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
child.stdout?.on('data', (chunk) => {
|
|
294
|
+
stdout.append(chunk);
|
|
295
|
+
writeStreamChunk(reporter, 'stdout', chunk);
|
|
296
|
+
});
|
|
297
|
+
child.stderr?.on('data', (chunk) => {
|
|
298
|
+
stderr.append(chunk);
|
|
299
|
+
writeStreamChunk(reporter, 'stderr', chunk);
|
|
300
|
+
});
|
|
301
|
+
child.once('error', (error) => {
|
|
302
|
+
childError = error;
|
|
303
|
+
});
|
|
304
|
+
child.once('close', (status, signal) => {
|
|
305
|
+
finish(status, signal);
|
|
306
|
+
});
|
|
307
|
+
timeout = setTimeout(() => {
|
|
308
|
+
timedOut = true;
|
|
309
|
+
terminateProcessTree(childPid);
|
|
310
|
+
}, timeoutSeconds * 1000);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
195
313
|
function getRunStatus(error, exitCode, successExitCodes) {
|
|
196
314
|
const errorWithCode = error;
|
|
197
315
|
if (errorWithCode?.code === 'ETIMEDOUT') {
|
|
@@ -249,6 +367,9 @@ function reportRunPlanFailure(plan, reporter, lang) {
|
|
|
249
367
|
message = t(lang, 'run.error.unknownIntent', { intent: plan.intentName });
|
|
250
368
|
break;
|
|
251
369
|
}
|
|
370
|
+
if (plan.suggestedIntentSnippet) {
|
|
371
|
+
message = `${message}\n\n${t(lang, 'run.label.suggestedIntentSnippet')}:\n${plan.suggestedIntentSnippet}`;
|
|
372
|
+
}
|
|
252
373
|
reporter.stderr(renderCliError(message, 'mf help commands', lang));
|
|
253
374
|
}
|
|
254
375
|
export function getRunHelp(lang = 'en') {
|
|
@@ -281,7 +402,9 @@ export function getRunHelp(lang = 'en') {
|
|
|
281
402
|
* invariant: Execution requires configured status, oneshot lifecycle, agent_allowed policy, and closed stdin.
|
|
282
403
|
* risk: config, security, state
|
|
283
404
|
*/
|
|
284
|
-
export async function runRun(args, reporter, lang = 'en') {
|
|
405
|
+
export async function runRun(args, reporter, lang = 'en', options = {}) {
|
|
406
|
+
const executorStartedAtMs = performance.now();
|
|
407
|
+
const profiler = new RunProfiler();
|
|
285
408
|
if (args.includes('--help') || args.includes('-h')) {
|
|
286
409
|
reporter.stdout(getRunHelp(lang));
|
|
287
410
|
return 0;
|
|
@@ -310,36 +433,69 @@ export async function runRun(args, reporter, lang = 'en') {
|
|
|
310
433
|
printUsageError(reporter, t(lang, 'cli.error.unexpectedArgument', { argument: extra[0] }), 'mf run --help', getRunHelp(lang), lang);
|
|
311
434
|
return 1;
|
|
312
435
|
}
|
|
313
|
-
const projectRoot = resolveMustflowRoot();
|
|
314
|
-
const contract = readCommandContract(projectRoot);
|
|
315
|
-
const plan = createRunPlan(projectRoot, contract, intentName);
|
|
436
|
+
const projectRoot = profiler.measure('root_detection', () => resolveMustflowRoot());
|
|
437
|
+
const contract = profiler.measure('command_contract', () => readCommandContract(projectRoot));
|
|
438
|
+
const plan = profiler.measure('plan_creation', () => createRunPlan(projectRoot, contract, intentName, { testTargets: options.testTargets }));
|
|
316
439
|
if (previewMode) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
440
|
+
profiler.measure('preview_render', () => {
|
|
441
|
+
if (json) {
|
|
442
|
+
reporter.stdout(JSON.stringify(createRunPreview(plan, previewMode), null, 2));
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
reporter.stdout(renderRunPreviewText(plan, previewMode, lang));
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
profiler.writeLatest({
|
|
449
|
+
projectRoot,
|
|
450
|
+
intent: intentName,
|
|
451
|
+
status: plan.ok ? 'previewed' : 'blocked',
|
|
452
|
+
previewMode,
|
|
453
|
+
});
|
|
323
454
|
return plan.ok ? 0 : 1;
|
|
324
455
|
}
|
|
325
456
|
if (!plan.ok) {
|
|
326
457
|
reportRunPlanFailure(plan, reporter, lang);
|
|
458
|
+
profiler.writeLatest({
|
|
459
|
+
projectRoot,
|
|
460
|
+
intent: intentName,
|
|
461
|
+
status: 'blocked',
|
|
462
|
+
previewMode: null,
|
|
463
|
+
});
|
|
327
464
|
return 1;
|
|
328
465
|
}
|
|
329
|
-
const runReceiptPolicy = resolveRunReceiptRetentionPolicy(readMustflowConfigIfExists(projectRoot));
|
|
330
|
-
const env = createCommandEnv(projectRoot, { policy: plan.envPolicy, allowlist: plan.envAllowlist });
|
|
466
|
+
const runReceiptPolicy = profiler.measure('retention_policy', () => resolveRunReceiptRetentionPolicy(readMustflowConfigIfExists(projectRoot)));
|
|
467
|
+
const env = profiler.measure('environment', () => createCommandEnv(projectRoot, { policy: plan.envPolicy, allowlist: plan.envAllowlist }));
|
|
331
468
|
const lifecycleValue = plan.lifecycle ?? 'oneshot';
|
|
332
469
|
const runPolicyValue = plan.runPolicy ?? 'agent_allowed';
|
|
333
|
-
const writeTracker = startRunWriteTracking(projectRoot, contract, intentName);
|
|
470
|
+
const writeTracker = profiler.measure('write_drift_before', () => startRunWriteTracking(projectRoot, contract, intentName));
|
|
471
|
+
const stdoutTailBytes = Math.min(runReceiptPolicy.stdoutTailBytes, plan.maxOutputBytes);
|
|
472
|
+
const stderrTailBytes = Math.min(runReceiptPolicy.stderrTailBytes, plan.maxOutputBytes);
|
|
473
|
+
let streamedOutput = false;
|
|
474
|
+
const childStartedAtMs = performance.now();
|
|
334
475
|
const startedAt = new Date();
|
|
335
|
-
const result =
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
476
|
+
const result = await profiler.measureAsync('child_command', async () => {
|
|
477
|
+
if (plan.commandArgv && isMustflowBuiltinIntent(plan.intent)) {
|
|
478
|
+
const builtinResult = await runBuiltinArgvInProcess(plan.commandArgv, plan.cwd, lang);
|
|
479
|
+
if (builtinResult) {
|
|
480
|
+
return builtinResult;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (plan.commandArgv) {
|
|
484
|
+
if (!json) {
|
|
485
|
+
streamedOutput = true;
|
|
486
|
+
return runArgvCommandStreaming(plan.argvCommand, plan.cwd, env, plan.timeoutSeconds, stdoutTailBytes, stderrTailBytes, reporter);
|
|
487
|
+
}
|
|
488
|
+
return runArgvCommand(plan.argvCommand, plan.cwd, plan.maxOutputBytes, env, plan.timeoutSeconds);
|
|
489
|
+
}
|
|
490
|
+
if (!json) {
|
|
491
|
+
streamedOutput = true;
|
|
492
|
+
return runShellCommandStreaming(plan.shellCommand, plan.cwd, env, plan.timeoutSeconds, stdoutTailBytes, stderrTailBytes, reporter);
|
|
493
|
+
}
|
|
494
|
+
return runShellCommand(plan.shellCommand, plan.cwd, plan.maxOutputBytes, env, plan.timeoutSeconds);
|
|
495
|
+
});
|
|
496
|
+
const childDurationMs = performance.now() - childStartedAtMs;
|
|
341
497
|
const finishedAt = new Date();
|
|
342
|
-
const writeDrift = finishRunWriteTracking(writeTracker);
|
|
498
|
+
const writeDrift = profiler.measure('write_drift_after', () => finishRunWriteTracking(writeTracker));
|
|
343
499
|
const exitCode = typeof result.status === 'number' ? result.status : null;
|
|
344
500
|
const runStatus = getRunStatus(result.error, exitCode, plan.successExitCodes);
|
|
345
501
|
let killMethod = null;
|
|
@@ -347,7 +503,7 @@ export async function runRun(args, reporter, lang = 'en') {
|
|
|
347
503
|
killMethod = getKillMethod();
|
|
348
504
|
terminateProcessTree(result.pid);
|
|
349
505
|
}
|
|
350
|
-
const receipt = createRunReceipt({
|
|
506
|
+
const receipt = profiler.measure('receipt_create', () => createRunReceipt({
|
|
351
507
|
intent: intentName,
|
|
352
508
|
status: runStatus,
|
|
353
509
|
timedOut: runStatus === 'timed_out',
|
|
@@ -372,16 +528,36 @@ export async function runRun(args, reporter, lang = 'en') {
|
|
|
372
528
|
stdout: result.stdout,
|
|
373
529
|
stderr: result.stderr,
|
|
374
530
|
writeDrift,
|
|
531
|
+
executorOverheadMs: Math.max(0, Math.round((performance.now() - executorStartedAtMs - childDurationMs) * 1000) / 1000),
|
|
532
|
+
phaseTimings: profiler.getReceiptPhases(),
|
|
533
|
+
selectionSummary: {
|
|
534
|
+
strategy: plan.testTargets.length > 0 ? 'project_test_selection' : 'direct_intent',
|
|
535
|
+
changed_file_count: 0,
|
|
536
|
+
changed_surface_counts: {},
|
|
537
|
+
selected_target_count: Math.max(1, plan.testTargets.length),
|
|
538
|
+
fallback_used: false,
|
|
539
|
+
},
|
|
375
540
|
stdoutTailBytes: runReceiptPolicy.stdoutTailBytes,
|
|
376
541
|
stderrTailBytes: runReceiptPolicy.stderrTailBytes,
|
|
542
|
+
}));
|
|
543
|
+
if (options.writeLatestReceipt !== false) {
|
|
544
|
+
profiler.measure('receipt_write', () => writeRunReceipt(projectRoot, receipt));
|
|
545
|
+
}
|
|
546
|
+
profiler.measure('performance_history_write', () => recordRunPerformanceHistory(projectRoot, receipt));
|
|
547
|
+
profiler.writeLatest({
|
|
548
|
+
projectRoot,
|
|
549
|
+
intent: intentName,
|
|
550
|
+
status: runStatus,
|
|
551
|
+
previewMode: null,
|
|
377
552
|
});
|
|
378
|
-
writeRunReceipt(projectRoot, receipt);
|
|
379
553
|
if (json) {
|
|
380
554
|
reporter.stdout(JSON.stringify(receipt, null, 2));
|
|
381
555
|
return runStatus === 'passed' ? 0 : 1;
|
|
382
556
|
}
|
|
383
|
-
|
|
384
|
-
|
|
557
|
+
if (!streamedOutput) {
|
|
558
|
+
emitOutput(reporter, result.stdout, 'stdout');
|
|
559
|
+
emitOutput(reporter, result.stderr, 'stderr');
|
|
560
|
+
}
|
|
385
561
|
if (result.error) {
|
|
386
562
|
const errorWithCode = result.error;
|
|
387
563
|
if (errorWithCode.code === 'ETIMEDOUT') {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { printUsageError, renderCliError, renderHelp } from '../lib/cli-output.js';
|
|
2
|
+
import { t } from '../lib/i18n.js';
|
|
3
|
+
import { checkNpmLatestVersion } from '../lib/npm-version-check.js';
|
|
4
|
+
import { readPackageMetadata } from '../lib/package-info.js';
|
|
5
|
+
import { runUpdate } from './update.js';
|
|
6
|
+
export function getUpgradeHelp(lang = 'en') {
|
|
7
|
+
return renderHelp({
|
|
8
|
+
usage: 'mf upgrade [options]',
|
|
9
|
+
summary: t(lang, 'upgrade.help.summary'),
|
|
10
|
+
options: [
|
|
11
|
+
{ label: '--dry-run', description: t(lang, 'upgrade.help.option.dryRun') },
|
|
12
|
+
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
13
|
+
],
|
|
14
|
+
examples: ['mf upgrade', 'mf upgrade --dry-run'],
|
|
15
|
+
exitCodes: [
|
|
16
|
+
{ label: '0', description: t(lang, 'upgrade.help.exit.ok') },
|
|
17
|
+
{ label: '1', description: t(lang, 'upgrade.help.exit.fail') },
|
|
18
|
+
],
|
|
19
|
+
}, lang);
|
|
20
|
+
}
|
|
21
|
+
function printPackageCheck(check, reporter, lang) {
|
|
22
|
+
reporter.stdout(`${check.packageName} ${check.currentVersion}`);
|
|
23
|
+
reporter.stdout(check.updateAvailable
|
|
24
|
+
? t(lang, 'version.check.latestAvailable', { version: check.latestVersion })
|
|
25
|
+
: t(lang, 'version.check.upToDate', { version: check.latestVersion }));
|
|
26
|
+
if (check.updateAvailable) {
|
|
27
|
+
reporter.stdout('');
|
|
28
|
+
reporter.stdout(t(lang, 'version.check.updateCommand'));
|
|
29
|
+
reporter.stdout(check.updateCommand);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function runUpgrade(args, reporter, lang = 'en') {
|
|
33
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
34
|
+
reporter.stdout(getUpgradeHelp(lang));
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
const supported = new Set(['--dry-run']);
|
|
38
|
+
const unsupported = args.filter((arg) => !supported.has(arg));
|
|
39
|
+
if (unsupported.length > 0) {
|
|
40
|
+
printUsageError(reporter, t(lang, 'cli.error.unknownOption', { option: unsupported[0] }), 'mf upgrade --help', getUpgradeHelp(lang), lang);
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
const dryRun = args.includes('--dry-run');
|
|
44
|
+
reporter.stdout(t(lang, 'upgrade.title'));
|
|
45
|
+
reporter.stdout('');
|
|
46
|
+
reporter.stdout(t(lang, 'upgrade.packageSection'));
|
|
47
|
+
try {
|
|
48
|
+
const packageCheck = await checkNpmLatestVersion(readPackageMetadata());
|
|
49
|
+
printPackageCheck(packageCheck, reporter, lang);
|
|
50
|
+
if (packageCheck.updateAvailable) {
|
|
51
|
+
reporter.stdout('');
|
|
52
|
+
reporter.stdout(t(lang, 'upgrade.packageUpdateRequired'));
|
|
53
|
+
reporter.stdout(t(lang, 'upgrade.noFilesWritten'));
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
+
reporter.stderr(renderCliError(t(lang, 'upgrade.warning.versionCheckFailed', { message }), 'mf version --check', lang));
|
|
60
|
+
reporter.stderr(t(lang, 'upgrade.warning.continueWithBundledTemplate'));
|
|
61
|
+
}
|
|
62
|
+
reporter.stdout('');
|
|
63
|
+
reporter.stdout(t(lang, 'upgrade.projectSection'));
|
|
64
|
+
return runUpdate([dryRun ? '--dry-run' : '--apply'], reporter, lang);
|
|
65
|
+
}
|