mustflow 2.22.16 → 2.22.46
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/dashboard.js +51 -4
- package/dist/cli/commands/explain.js +3 -2
- package/dist/cli/commands/help.js +0 -1
- package/dist/cli/commands/run.js +51 -4
- package/dist/cli/commands/verify.js +2 -1
- package/dist/cli/i18n/en.js +5 -0
- package/dist/cli/i18n/es.js +5 -0
- package/dist/cli/i18n/fr.js +5 -0
- package/dist/cli/i18n/hi.js +5 -0
- package/dist/cli/i18n/ko.js +5 -0
- package/dist/cli/i18n/zh.js +5 -0
- package/dist/cli/lib/cli-output.js +1 -1
- package/dist/cli/lib/dashboard-html/client-script.js +9 -0
- package/dist/cli/lib/dashboard-html/styles.js +48 -1
- package/dist/cli/lib/doc-review-ledger.js +1 -1
- package/dist/cli/lib/git-changes.js +2 -0
- package/dist/cli/lib/local-index/index.js +324 -298
- package/dist/cli/lib/repo-map.js +19 -5
- package/dist/cli/lib/run-plan.js +20 -7
- package/dist/cli/lib/run-root-trust.js +33 -2
- package/dist/cli/lib/validation/index.js +6 -2
- package/dist/cli/lib/validation/test-selection.js +11 -1
- package/dist/core/active-run-locks.js +36 -8
- package/dist/core/atomic-state-write.js +5 -20
- package/dist/core/change-verification.js +18 -2
- package/dist/core/command-intent-eligibility.js +7 -0
- package/dist/core/contract-lint.js +3 -3
- package/dist/core/line-endings.js +2 -0
- package/dist/core/repeated-failure.js +1 -1
- package/dist/core/run-write-drift.js +42 -26
- package/dist/core/safe-filesystem.js +54 -5
- package/dist/core/skill-route-explanation.js +2 -1
- package/dist/core/source-anchors.js +7 -3
- package/dist/core/test-selection.js +13 -2
- package/dist/core/test-target-paths.js +17 -0
- package/dist/core/validation-ratchet.js +62 -17
- package/dist/core/verification-decision-graph.js +8 -1
- package/package.json +1 -1
- package/templates/default/i18n.toml +141 -3
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +24 -1
- package/templates/default/locales/en/.mustflow/skills/api-contract-change/SKILL.md +212 -0
- package/templates/default/locales/en/.mustflow/skills/astro-code-change/SKILL.md +184 -0
- package/templates/default/locales/en/.mustflow/skills/auth-permission-change/SKILL.md +194 -0
- package/templates/default/locales/en/.mustflow/skills/config-env-change/SKILL.md +189 -0
- package/templates/default/locales/en/.mustflow/skills/css-code-change/SKILL.md +199 -0
- package/templates/default/locales/en/.mustflow/skills/dart-code-change/SKILL.md +179 -0
- package/templates/default/locales/en/.mustflow/skills/database-migration-change/SKILL.md +178 -0
- package/templates/default/locales/en/.mustflow/skills/dependency-upgrade-review/SKILL.md +151 -0
- package/templates/default/locales/en/.mustflow/skills/elysia-code-change/SKILL.md +115 -0
- package/templates/default/locales/en/.mustflow/skills/file-path-cross-platform-change/SKILL.md +147 -0
- package/templates/default/locales/en/.mustflow/skills/flutter-code-change/SKILL.md +116 -0
- package/templates/default/locales/en/.mustflow/skills/go-code-change/SKILL.md +156 -0
- package/templates/default/locales/en/.mustflow/skills/hono-code-change/SKILL.md +117 -0
- package/templates/default/locales/en/.mustflow/skills/html-code-change/SKILL.md +173 -0
- package/templates/default/locales/en/.mustflow/skills/javascript-code-change/SKILL.md +149 -0
- package/templates/default/locales/en/.mustflow/skills/python-code-change/SKILL.md +154 -0
- package/templates/default/locales/en/.mustflow/skills/release-publish-change/SKILL.md +172 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +138 -0
- package/templates/default/locales/en/.mustflow/skills/rust-code-change/SKILL.md +154 -0
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +22 -7
- package/templates/default/locales/en/.mustflow/skills/security-regression-tests/SKILL.md +31 -20
- package/templates/default/locales/en/.mustflow/skills/svelte-code-change/SKILL.md +186 -0
- package/templates/default/locales/en/.mustflow/skills/tailwind-code-change/SKILL.md +164 -0
- package/templates/default/locales/en/.mustflow/skills/tauri-code-change/SKILL.md +185 -0
- package/templates/default/locales/en/.mustflow/skills/typescript-code-change/SKILL.md +184 -0
- package/templates/default/locales/en/.mustflow/skills/unocss-code-change/SKILL.md +186 -0
- package/templates/default/manifest.toml +158 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash, randomBytes } from 'node:crypto';
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
2
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
3
3
|
import http from 'node:http';
|
|
4
4
|
import path from 'node:path';
|
|
@@ -306,8 +306,36 @@ function sendText(response, statusCode, value) {
|
|
|
306
306
|
function sendBadRequest(response) {
|
|
307
307
|
sendText(response, 400, 'Bad request');
|
|
308
308
|
}
|
|
309
|
+
function isDashboardBadRequestError(error, message) {
|
|
310
|
+
return (error instanceof SyntaxError ||
|
|
311
|
+
message === 'Request body is too large.' ||
|
|
312
|
+
message === 'Invalid review status.' ||
|
|
313
|
+
message === 'Request body must be a JSON object.' ||
|
|
314
|
+
message === 'Request body must include an updates array.' ||
|
|
315
|
+
message === 'Each update must be a JSON object.' ||
|
|
316
|
+
message === 'Each update must include an id.' ||
|
|
317
|
+
message === 'Bulk documentation review updates require a separate confirmed flow.' ||
|
|
318
|
+
message.startsWith('Unknown dashboard preference: ') ||
|
|
319
|
+
message.endsWith(' is locked in the dashboard.') ||
|
|
320
|
+
message.endsWith(' is required.') ||
|
|
321
|
+
message.endsWith(' must be a boolean.') ||
|
|
322
|
+
message.endsWith(' must be an integer.') ||
|
|
323
|
+
message.endsWith(' must be a string.') ||
|
|
324
|
+
message.endsWith(' must not be empty.') ||
|
|
325
|
+
/^.+ must be at (?:least|most) \d+\.$/u.test(message) ||
|
|
326
|
+
/^.+ must be one of: .+\.$/u.test(message) ||
|
|
327
|
+
message.startsWith('status must be ') ||
|
|
328
|
+
message.startsWith('reviewerKind must be '));
|
|
329
|
+
}
|
|
309
330
|
function isAuthorized(request, token) {
|
|
310
|
-
|
|
331
|
+
const rawToken = request.headers['x-mustflow-dashboard-token'];
|
|
332
|
+
const candidate = Array.isArray(rawToken) ? rawToken[0] : rawToken;
|
|
333
|
+
if (typeof candidate !== 'string') {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
const expected = Buffer.from(token);
|
|
337
|
+
const actual = Buffer.from(candidate);
|
|
338
|
+
return expected.byteLength === actual.byteLength && timingSafeEqual(actual, expected);
|
|
311
339
|
}
|
|
312
340
|
async function readRequestJson(request) {
|
|
313
341
|
const chunks = [];
|
|
@@ -841,19 +869,38 @@ export async function runDashboard(args, reporter, lang = 'en') {
|
|
|
841
869
|
}
|
|
842
870
|
sendText(response, 404, 'Not found');
|
|
843
871
|
}
|
|
844
|
-
catch {
|
|
845
|
-
|
|
872
|
+
catch (error) {
|
|
873
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
874
|
+
if (isDashboardBadRequestError(error, message)) {
|
|
875
|
+
sendBadRequest(response);
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
reporter.stderr(message);
|
|
879
|
+
sendText(response, 500, 'Internal server error');
|
|
846
880
|
}
|
|
847
881
|
});
|
|
882
|
+
server.headersTimeout = 10_000;
|
|
883
|
+
server.requestTimeout = 30_000;
|
|
884
|
+
server.keepAliveTimeout = 1_000;
|
|
848
885
|
return new Promise((resolve) => {
|
|
849
886
|
let resolved = false;
|
|
887
|
+
const sockets = new Set();
|
|
850
888
|
const close = () => {
|
|
851
889
|
if (resolved) {
|
|
852
890
|
return;
|
|
853
891
|
}
|
|
854
892
|
resolved = true;
|
|
855
893
|
server.close(() => resolve(0));
|
|
894
|
+
for (const socket of sockets) {
|
|
895
|
+
socket.destroy();
|
|
896
|
+
}
|
|
856
897
|
};
|
|
898
|
+
server.on('connection', (socket) => {
|
|
899
|
+
sockets.add(socket);
|
|
900
|
+
socket.on('close', () => {
|
|
901
|
+
sockets.delete(socket);
|
|
902
|
+
});
|
|
903
|
+
});
|
|
857
904
|
server.on('error', (error) => {
|
|
858
905
|
if (!resolved) {
|
|
859
906
|
resolved = true;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
4
4
|
import { t } from '../lib/i18n.js';
|
|
5
|
+
import { MUSTFLOW_JSON_MAX_BYTES, readMustflowTextFile } from '../lib/mustflow-read.js';
|
|
5
6
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
6
7
|
import { explainAssetOptimization, explainCommandIntent, } from '../../core/command-explanation.js';
|
|
7
8
|
import { readCommandContract, readMustflowConfigIfExists } from '../../core/config-loading.js';
|
|
@@ -225,7 +226,7 @@ function getLatestFailureExplainOutput(projectRoot) {
|
|
|
225
226
|
}
|
|
226
227
|
let parsed;
|
|
227
228
|
try {
|
|
228
|
-
parsed = JSON.parse(
|
|
229
|
+
parsed = JSON.parse(readMustflowTextFile(projectRoot, LATEST_RUN_RECEIPT_RELATIVE_PATH, { maxBytes: MUSTFLOW_JSON_MAX_BYTES }));
|
|
229
230
|
}
|
|
230
231
|
catch {
|
|
231
232
|
return {
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -36,6 +36,9 @@ function reportRunPlanFailure(plan, reporter, lang) {
|
|
|
36
36
|
case 'stdin_not_closed':
|
|
37
37
|
message = t(lang, 'run.error.stdin', { intent: plan.intentName });
|
|
38
38
|
break;
|
|
39
|
+
case 'agent_shell_requires_allow':
|
|
40
|
+
message = t(lang, 'run.error.agentShellRequiresAllow', { intent: plan.intentName });
|
|
41
|
+
break;
|
|
39
42
|
case 'missing_timeout':
|
|
40
43
|
message = t(lang, 'run.error.timeout', { intent: plan.intentName });
|
|
41
44
|
break;
|
|
@@ -66,6 +69,12 @@ function reportRunPlanFailure(plan, reporter, lang) {
|
|
|
66
69
|
detail: getRunPlanDetail(plan, lang, 'run.error.cwdOutsideProjectDetail'),
|
|
67
70
|
});
|
|
68
71
|
break;
|
|
72
|
+
case 'invalid_test_target':
|
|
73
|
+
message = t(lang, 'run.error.invalidTestTarget', {
|
|
74
|
+
intent: plan.intentName,
|
|
75
|
+
detail: getRunPlanDetail(plan, lang, 'run.error.invalidTestTargetDetail'),
|
|
76
|
+
});
|
|
77
|
+
break;
|
|
69
78
|
case 'max_output_bytes_exceeds_limit':
|
|
70
79
|
message = t(lang, 'run.error.maxOutputBytes', {
|
|
71
80
|
intent: plan.intentName,
|
|
@@ -108,6 +117,31 @@ function renderActiveLockConflictMessage(intentName, conflicts, lang) {
|
|
|
108
117
|
: t(lang, 'run.error.activeLockConflictUnknown');
|
|
109
118
|
return t(lang, 'run.error.activeLockConflict', { intent: intentName, detail });
|
|
110
119
|
}
|
|
120
|
+
function createRunProgressReporter(input) {
|
|
121
|
+
if (!input.enabled) {
|
|
122
|
+
return () => undefined;
|
|
123
|
+
}
|
|
124
|
+
input.reporter.stderr(t(input.lang, 'run.progress.started', { intent: input.intentName, seconds: input.timeoutSeconds }));
|
|
125
|
+
const timers = [];
|
|
126
|
+
for (const ratio of [0.5, 0.8]) {
|
|
127
|
+
const delayMs = Math.max(1, Math.floor(input.timeoutSeconds * 1000 * ratio));
|
|
128
|
+
const elapsedSeconds = Math.max(1, Math.round(input.timeoutSeconds * ratio));
|
|
129
|
+
const timer = setTimeout(() => {
|
|
130
|
+
input.reporter.stderr(t(input.lang, 'run.progress.timeoutWarning', {
|
|
131
|
+
intent: input.intentName,
|
|
132
|
+
seconds: elapsedSeconds,
|
|
133
|
+
percent: Math.round(ratio * 100),
|
|
134
|
+
}));
|
|
135
|
+
}, delayMs);
|
|
136
|
+
timer.unref?.();
|
|
137
|
+
timers.push(timer);
|
|
138
|
+
}
|
|
139
|
+
return () => {
|
|
140
|
+
for (const timer of timers) {
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
111
145
|
export function getRunHelp(lang = 'en') {
|
|
112
146
|
return renderHelp({
|
|
113
147
|
usage: 'mf run <intent> [options]',
|
|
@@ -225,19 +259,32 @@ export async function runRun(args, reporter, lang = 'en', options = {}) {
|
|
|
225
259
|
const env = profiler.measure('environment', () => createCommandEnv(projectRoot, { policy: plan.envPolicy, allowlist: plan.envAllowlist }));
|
|
226
260
|
const writeTracker = profiler.measure('write_drift_before', () => startRunWriteTracking(projectRoot, contract, intentName, {
|
|
227
261
|
additionalDeclaredPaths: options.additionalDeclaredWritePaths,
|
|
262
|
+
env,
|
|
228
263
|
}));
|
|
229
264
|
const stdoutTailBytes = Math.min(runReceiptPolicy.stdoutTailBytes, plan.maxOutputBytes);
|
|
230
265
|
const stderrTailBytes = Math.min(runReceiptPolicy.stderrTailBytes, plan.maxOutputBytes);
|
|
231
266
|
let streamedOutput = false;
|
|
232
267
|
const childStartedAtMs = performance.now();
|
|
233
268
|
const startedAt = new Date();
|
|
269
|
+
const stopRunProgress = createRunProgressReporter({
|
|
270
|
+
enabled: !json && Boolean(reporter.writeStderr),
|
|
271
|
+
intentName,
|
|
272
|
+
timeoutSeconds: plan.timeoutSeconds,
|
|
273
|
+
reporter,
|
|
274
|
+
lang,
|
|
275
|
+
});
|
|
234
276
|
const result = await profiler.measureAsync('child_command', async () => {
|
|
235
|
-
|
|
277
|
+
try {
|
|
278
|
+
if (plan.commandArgv) {
|
|
279
|
+
streamedOutput = !json;
|
|
280
|
+
return runArgvCommandStreaming(plan.argvCommand, plan.cwd, env, plan.timeoutSeconds, plan.killAfterSeconds, plan.maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, !json, true);
|
|
281
|
+
}
|
|
236
282
|
streamedOutput = !json;
|
|
237
|
-
return
|
|
283
|
+
return runShellCommandStreaming(plan.shellCommand, plan.cwd, env, plan.timeoutSeconds, plan.killAfterSeconds, plan.maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, !json, true);
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
stopRunProgress();
|
|
238
287
|
}
|
|
239
|
-
streamedOutput = !json;
|
|
240
|
-
return runShellCommandStreaming(plan.shellCommand, plan.cwd, env, plan.timeoutSeconds, plan.killAfterSeconds, plan.maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, !json, true);
|
|
241
288
|
});
|
|
242
289
|
const childDurationMs = performance.now() - childStartedAtMs;
|
|
243
290
|
const finishedAt = new Date();
|
|
@@ -11,6 +11,7 @@ import { createVerifyEvidenceModel } from '../../core/verification-evidence.js';
|
|
|
11
11
|
import { createScopeDiffRisks } from '../../core/scope-risk.js';
|
|
12
12
|
import { countValidationRatchetVerdictEffects, createValidationRatchetRisks, } from '../../core/validation-ratchet.js';
|
|
13
13
|
import { finishRunWriteBatchTracking, startRunWriteBatchTracking, } from '../../core/run-write-drift.js';
|
|
14
|
+
import { createCommandEnv } from '../../core/command-env.js';
|
|
14
15
|
import { readCommandContract } from '../../core/config-loading.js';
|
|
15
16
|
import { evaluateCommandPreconditions, } from '../../core/command-preconditions.js';
|
|
16
17
|
import { DEFAULT_VERIFY_PARALLELISM, parseVerifyArgs, resolveVerifyParallelism, } from './verify/args.js';
|
|
@@ -198,7 +199,7 @@ async function runVerificationEntriesInParallelChunks(projectRoot, entries, para
|
|
|
198
199
|
const results = [];
|
|
199
200
|
for (let index = 0; index < entries.length; index += parallelism) {
|
|
200
201
|
const chunk = entries.slice(index, index + parallelism);
|
|
201
|
-
const batchTracker = startRunWriteBatchTracking(projectRoot);
|
|
202
|
+
const batchTracker = startRunWriteBatchTracking(projectRoot, createCommandEnv(projectRoot, { policy: 'minimal', allowlist: [] }));
|
|
202
203
|
const chunkResults = await Promise.all(chunk.map((entry) => runVerificationIntent(entry.intent, lang, verificationPlanId, correlationId, scheduledTestTargets.get(entry.intent) ?? [])));
|
|
203
204
|
const writeDriftByIntent = finishRunWriteBatchTracking(batchTracker, chunk.map((entry) => ({
|
|
204
205
|
intentName: entry.intent,
|
package/dist/cli/i18n/en.js
CHANGED
|
@@ -665,12 +665,15 @@ Read these files before working:
|
|
|
665
665
|
"run.help.exit.ok": "The command completed with an allowed exit code",
|
|
666
666
|
"run.help.exit.fail": "The command was invalid, refused, timed out, or failed",
|
|
667
667
|
"run.label.suggestedIntentSnippet": "Suggested command contract snippet",
|
|
668
|
+
"run.progress.started": "Running {intent} (timeout: {seconds}s)...",
|
|
669
|
+
"run.progress.timeoutWarning": "Still running {intent}... ({seconds}s elapsed, {percent}% of timeout)",
|
|
668
670
|
"run.error.missingIntent": "Missing command name",
|
|
669
671
|
"run.error.unknownIntent": "Unknown command: {intent}",
|
|
670
672
|
"run.error.statusNotConfigured": 'Command "{intent}" is {status}; only configured commands can be run',
|
|
671
673
|
"run.error.lifecycleNotOneshot": 'Refused: command "{intent}" has lifecycle = "{lifecycle}"; mf run only executes oneshot commands',
|
|
672
674
|
"run.error.runPolicy": 'Command "{intent}" requires run_policy = "agent_allowed" for mf run',
|
|
673
675
|
"run.error.stdin": 'Command "{intent}" must set stdin = "closed"',
|
|
676
|
+
"run.error.agentShellRequiresAllow": 'Command "{intent}" must set allow_shell = true when mode = "shell"',
|
|
674
677
|
"run.error.timeout": 'Command "{intent}" must define timeout_seconds',
|
|
675
678
|
"run.error.commandSource": 'Command "{intent}" must define argv or mode = "shell" with cmd',
|
|
676
679
|
"run.error.unsafeIntent": 'Intent "{intent}" has an unsafe name. {detail}',
|
|
@@ -681,6 +684,8 @@ Read these files before working:
|
|
|
681
684
|
"run.error.blockedLongRunningCommandDetail": "Command argv must describe a finite one-shot command, not a development server, watcher, shell wrapper, interpreter loop, or background process.",
|
|
682
685
|
"run.error.cwdOutsideProject": 'Command "{intent}" has an invalid cwd: {detail}',
|
|
683
686
|
"run.error.cwdOutsideProjectDetail": "Intent cwd must stay inside the current root.",
|
|
687
|
+
"run.error.invalidTestTarget": 'Command "{intent}" received an invalid test target. {detail}',
|
|
688
|
+
"run.error.invalidTestTargetDetail": "Test targets must be relative file paths and cannot start with '-'.",
|
|
684
689
|
"run.error.maxOutputBytes": 'Command "{intent}" has invalid max_output_bytes. {detail}',
|
|
685
690
|
"run.error.maxOutputBytesDetail": "The output limit must stay within the allowed maximum.",
|
|
686
691
|
"run.error.conflictingPreviewModes": "Use either --dry-run or --plan-only, not both",
|
package/dist/cli/i18n/es.js
CHANGED
|
@@ -665,12 +665,15 @@ Lee estos archivos antes de trabajar:
|
|
|
665
665
|
"run.help.exit.ok": "El comando se completo con un codigo de salida permitido",
|
|
666
666
|
"run.help.exit.fail": "El comando no era válido, fue rechazado, agotó el tiempo o falló",
|
|
667
667
|
"run.label.suggestedIntentSnippet": "Snippet sugerido para el contrato de comandos",
|
|
668
|
+
"run.progress.started": "Ejecutando {intent} (timeout: {seconds}s)...",
|
|
669
|
+
"run.progress.timeoutWarning": "{intent} sigue ejecutándose... ({seconds}s transcurridos, {percent}% del timeout)",
|
|
668
670
|
"run.error.missingIntent": "Falta el nombre del comando",
|
|
669
671
|
"run.error.unknownIntent": "Comando desconocido: {intent}",
|
|
670
672
|
"run.error.statusNotConfigured": 'El comando "{intent}" está en estado {status}; sólo se pueden ejecutar comandos configurados',
|
|
671
673
|
"run.error.lifecycleNotOneshot": 'Rechazado: el comando "{intent}" tiene lifecycle = "{lifecycle}"; mf run sólo ejecuta comandos oneshot',
|
|
672
674
|
"run.error.runPolicy": 'El comando "{intent}" requiere run_policy = "agent_allowed" para mf run',
|
|
673
675
|
"run.error.stdin": 'El comando "{intent}" debe establecer stdin = "closed"',
|
|
676
|
+
"run.error.agentShellRequiresAllow": 'El comando "{intent}" debe establecer allow_shell = true cuando mode = "shell"',
|
|
674
677
|
"run.error.timeout": 'El comando "{intent}" debe definir timeout_seconds',
|
|
675
678
|
"run.error.commandSource": 'El comando "{intent}" debe definir argv o mode = "shell" con cmd',
|
|
676
679
|
"run.error.unsafeIntent": 'La intención "{intent}" tiene un nombre no seguro. {detail}',
|
|
@@ -681,6 +684,8 @@ Lee estos archivos antes de trabajar:
|
|
|
681
684
|
"run.error.blockedLongRunningCommandDetail": "argv debe describir un comando finito de una sola ejecución, no un servidor de desarrollo, watcher, envoltorio de shell, bucle de intérprete o proceso en segundo plano.",
|
|
682
685
|
"run.error.cwdOutsideProject": 'El comando "{intent}" tiene un cwd no válido: {detail}',
|
|
683
686
|
"run.error.cwdOutsideProjectDetail": "El cwd de la intención debe permanecer dentro de la raíz actual.",
|
|
687
|
+
"run.error.invalidTestTarget": 'El comando "{intent}" recibió un objetivo de prueba no válido. {detail}',
|
|
688
|
+
"run.error.invalidTestTargetDetail": "Los objetivos de prueba deben ser rutas relativas y no pueden empezar con '-'.",
|
|
684
689
|
"run.error.maxOutputBytes": 'El comando "{intent}" tiene max_output_bytes no válido. {detail}',
|
|
685
690
|
"run.error.maxOutputBytesDetail": "El límite de salida debe permanecer dentro del máximo permitido.",
|
|
686
691
|
"run.error.conflictingPreviewModes": "Usa --dry-run o --plan-only, no ambos",
|
package/dist/cli/i18n/fr.js
CHANGED
|
@@ -665,12 +665,15 @@ Lisez ces fichiers avant de travailler :
|
|
|
665
665
|
"run.help.exit.ok": "La commande s'est terminée avec un code de sortie autorisé",
|
|
666
666
|
"run.help.exit.fail": "La commande était non valide, refusée, expirée ou a échoué",
|
|
667
667
|
"run.label.suggestedIntentSnippet": "Extrait suggéré de contrat de commande",
|
|
668
|
+
"run.progress.started": "Exécution de {intent} (timeout : {seconds}s)...",
|
|
669
|
+
"run.progress.timeoutWarning": "{intent} est toujours en cours... ({seconds}s écoulées, {percent}% du timeout)",
|
|
668
670
|
"run.error.missingIntent": "Nom de commande manquant",
|
|
669
671
|
"run.error.unknownIntent": "Commande inconnue : {intent}",
|
|
670
672
|
"run.error.statusNotConfigured": 'La commande "{intent}" est {status} ; seules les commandes configurées peuvent être exécutées',
|
|
671
673
|
"run.error.lifecycleNotOneshot": 'Refusé : la commande "{intent}" a lifecycle = "{lifecycle}" ; mf run exécute uniquement les commandes oneshot',
|
|
672
674
|
"run.error.runPolicy": 'La commande "{intent}" nécessite run_policy = "agent_allowed" pour mf run',
|
|
673
675
|
"run.error.stdin": 'La commande "{intent}" doit définir stdin = "closed"',
|
|
676
|
+
"run.error.agentShellRequiresAllow": 'La commande "{intent}" doit définir allow_shell = true lorsque mode = "shell"',
|
|
674
677
|
"run.error.timeout": 'La commande "{intent}" doit définir timeout_seconds',
|
|
675
678
|
"run.error.commandSource": 'La commande "{intent}" doit définir argv ou mode = "shell" avec cmd',
|
|
676
679
|
"run.error.unsafeIntent": 'L’intention "{intent}" a un nom non sûr. {detail}',
|
|
@@ -681,6 +684,8 @@ Lisez ces fichiers avant de travailler :
|
|
|
681
684
|
"run.error.blockedLongRunningCommandDetail": "argv doit décrire une commande ponctuelle finie, pas un serveur de développement, un watcher, un wrapper shell, une boucle d'interpréteur ou un processus en arrière-plan.",
|
|
682
685
|
"run.error.cwdOutsideProject": 'La commande "{intent}" a un cwd non valide : {detail}',
|
|
683
686
|
"run.error.cwdOutsideProjectDetail": "Le cwd de l’intention doit rester dans la racine actuelle.",
|
|
687
|
+
"run.error.invalidTestTarget": 'La commande "{intent}" a reçu une cible de test invalide. {detail}',
|
|
688
|
+
"run.error.invalidTestTargetDetail": "Les cibles de test doivent être des chemins relatifs et ne peuvent pas commencer par '-'.",
|
|
684
689
|
"run.error.maxOutputBytes": 'La commande "{intent}" a une valeur max_output_bytes non valide. {detail}',
|
|
685
690
|
"run.error.maxOutputBytesDetail": "La limite de sortie doit rester dans le maximum autorisé.",
|
|
686
691
|
"run.error.conflictingPreviewModes": "Utilisez --dry-run ou --plan-only, pas les deux",
|
package/dist/cli/i18n/hi.js
CHANGED
|
@@ -665,12 +665,15 @@ export const hiMessages = {
|
|
|
665
665
|
"run.help.exit.ok": "कमांड अनुमत exit code के साथ पूरी हुई",
|
|
666
666
|
"run.help.exit.fail": "कमांड अमान्य थी, अस्वीकार हुई, timed out हुई या विफल हुई",
|
|
667
667
|
"run.label.suggestedIntentSnippet": "Suggested command contract snippet",
|
|
668
|
+
"run.progress.started": "{intent} चल रहा है (timeout: {seconds}s)...",
|
|
669
|
+
"run.progress.timeoutWarning": "{intent} अभी भी चल रहा है... ({seconds}s बीते, timeout का {percent}%)",
|
|
668
670
|
"run.error.missingIntent": "कमांड नाम नहीं दिया गया",
|
|
669
671
|
"run.error.unknownIntent": "अज्ञात कमांड: {intent}",
|
|
670
672
|
"run.error.statusNotConfigured": 'कमांड "{intent}" {status} है; केवल configured कमांड चलाई जा सकती हैं',
|
|
671
673
|
"run.error.lifecycleNotOneshot": 'अस्वीकृत: कमांड "{intent}" का lifecycle = "{lifecycle}" है; mf run केवल oneshot कमांड चलाता है',
|
|
672
674
|
"run.error.runPolicy": 'mf run के लिए कमांड "{intent}" में run_policy = "agent_allowed" चाहिए',
|
|
673
675
|
"run.error.stdin": 'कमांड "{intent}" को stdin = "closed" सेट करना होगा',
|
|
676
|
+
"run.error.agentShellRequiresAllow": 'कमांड "{intent}" में mode = "shell" होने पर allow_shell = true सेट होना चाहिए',
|
|
674
677
|
"run.error.timeout": 'कमांड "{intent}" को timeout_seconds परिभाषित करना होगा',
|
|
675
678
|
"run.error.commandSource": 'कमांड "{intent}" को argv या mode = "shell" के साथ cmd परिभाषित करना होगा',
|
|
676
679
|
"run.error.unsafeIntent": 'इंटेंट "{intent}" का नाम सुरक्षित नहीं है। {detail}',
|
|
@@ -681,6 +684,8 @@ export const hiMessages = {
|
|
|
681
684
|
"run.error.blockedLongRunningCommandDetail": "argv में finite one-shot command होना चाहिए, development server, watcher, shell wrapper, interpreter loop, या background process नहीं।",
|
|
682
685
|
"run.error.cwdOutsideProject": 'कमांड "{intent}" का cwd अमान्य है: {detail}',
|
|
683
686
|
"run.error.cwdOutsideProjectDetail": "Intent cwd current root के अंदर रहना चाहिए।",
|
|
687
|
+
"run.error.invalidTestTarget": 'कमांड "{intent}" को अमान्य test target मिला। {detail}',
|
|
688
|
+
"run.error.invalidTestTargetDetail": "Test targets relative file paths होने चाहिए और '-' से शुरू नहीं हो सकते।",
|
|
684
689
|
"run.error.maxOutputBytes": 'कमांड "{intent}" में max_output_bytes अमान्य है। {detail}',
|
|
685
690
|
"run.error.maxOutputBytesDetail": "Output limit अनुमत maximum के अंदर रहनी चाहिए।",
|
|
686
691
|
"run.error.conflictingPreviewModes": "--dry-run या --plan-only में से एक इस्तेमाल करें, दोनों नहीं",
|
package/dist/cli/i18n/ko.js
CHANGED
|
@@ -665,12 +665,15 @@ export const koMessages = {
|
|
|
665
665
|
"run.help.exit.ok": "명령이 허용된 종료 코드로 완료되었습니다",
|
|
666
666
|
"run.help.exit.fail": "명령이 잘못되었거나, 거부되었거나, 시간 초과되었거나, 실패했습니다",
|
|
667
667
|
"run.label.suggestedIntentSnippet": "제안 명령 계약 조각",
|
|
668
|
+
"run.progress.started": "{intent} 실행 중(timeout: {seconds}초)...",
|
|
669
|
+
"run.progress.timeoutWarning": "{intent} 계속 실행 중... ({seconds}초 경과, timeout의 {percent}%)",
|
|
668
670
|
"run.error.missingIntent": "명령 이름이 없습니다",
|
|
669
671
|
"run.error.unknownIntent": "알 수 없는 명령: {intent}",
|
|
670
672
|
"run.error.statusNotConfigured": '명령 "{intent}"의 상태는 {status}입니다. 설정된 상태(configured)인 명령만 실행할 수 있습니다',
|
|
671
673
|
"run.error.lifecycleNotOneshot": '거부됨: 명령 "{intent}"의 수명 주기(lifecycle)는 "{lifecycle}"입니다. mf run은 일회성(oneshot) 명령만 실행합니다',
|
|
672
674
|
"run.error.runPolicy": '명령 "{intent}"는 mf run에서 실행하려면 run_policy = "agent_allowed"가 필요합니다',
|
|
673
675
|
"run.error.stdin": '명령 "{intent}"는 stdin = "closed"를 설정해야 합니다',
|
|
676
|
+
"run.error.agentShellRequiresAllow": '명령 "{intent}"는 mode = "shell"일 때 allow_shell = true를 설정해야 합니다',
|
|
674
677
|
"run.error.timeout": '명령 "{intent}"는 timeout_seconds를 정의해야 합니다',
|
|
675
678
|
"run.error.commandSource": '명령 "{intent}"는 argv를 정의하거나 mode = "shell"과 cmd를 함께 정의해야 합니다',
|
|
676
679
|
"run.error.unsafeIntent": '명령 의도 "{intent}"의 이름이 안전하지 않습니다. {detail}',
|
|
@@ -681,6 +684,8 @@ export const koMessages = {
|
|
|
681
684
|
"run.error.blockedLongRunningCommandDetail": "argv는 개발 서버, 감시 명령, 셸 래퍼, 인터프리터 반복 작업, 백그라운드 프로세스가 아니라 끝나는 단발성 명령이어야 합니다.",
|
|
682
685
|
"run.error.cwdOutsideProject": '명령 "{intent}"의 실행 위치(cwd)가 올바르지 않습니다: {detail}',
|
|
683
686
|
"run.error.cwdOutsideProjectDetail": "명령 실행 위치(cwd)는 현재 루트 안에 있어야 합니다.",
|
|
687
|
+
"run.error.invalidTestTarget": '명령 "{intent}"에 올바르지 않은 테스트 대상이 전달되었습니다. {detail}',
|
|
688
|
+
"run.error.invalidTestTargetDetail": "테스트 대상은 상대 파일 경로여야 하며 '-'로 시작할 수 없습니다.",
|
|
684
689
|
"run.error.maxOutputBytes": '명령 "{intent}"의 max_output_bytes 값이 올바르지 않습니다. {detail}',
|
|
685
690
|
"run.error.maxOutputBytesDetail": "출력 상한은 허용된 최댓값 안에 있어야 합니다.",
|
|
686
691
|
"run.error.conflictingPreviewModes": "--dry-run과 --plan-only 중 하나만 사용하세요",
|
package/dist/cli/i18n/zh.js
CHANGED
|
@@ -665,12 +665,15 @@ export const zhMessages = {
|
|
|
665
665
|
"run.help.exit.ok": "命令已以允许的退出码完成",
|
|
666
666
|
"run.help.exit.fail": "命令无效、被拒绝、超时或失败",
|
|
667
667
|
"run.label.suggestedIntentSnippet": "建议的命令契约片段",
|
|
668
|
+
"run.progress.started": "正在运行 {intent}(超时:{seconds} 秒)...",
|
|
669
|
+
"run.progress.timeoutWarning": "{intent} 仍在运行...(已用 {seconds} 秒,达到超时的 {percent}%)",
|
|
668
670
|
"run.error.missingIntent": "缺少命令名称",
|
|
669
671
|
"run.error.unknownIntent": "未知命令:{intent}",
|
|
670
672
|
"run.error.statusNotConfigured": '命令 "{intent}" 的状态为 {status};只能运行已配置的命令',
|
|
671
673
|
"run.error.lifecycleNotOneshot": '已拒绝:命令 "{intent}" 的 lifecycle = "{lifecycle}";mf run 只执行 oneshot 命令',
|
|
672
674
|
"run.error.runPolicy": '命令 "{intent}" 需要 run_policy = "agent_allowed" 才能通过 mf run 执行',
|
|
673
675
|
"run.error.stdin": '命令 "{intent}" 必须设置 stdin = "closed"',
|
|
676
|
+
"run.error.agentShellRequiresAllow": '当 mode = "shell" 时,命令 "{intent}" 必须设置 allow_shell = true',
|
|
674
677
|
"run.error.timeout": '命令 "{intent}" 必须定义 timeout_seconds',
|
|
675
678
|
"run.error.commandSource": '命令 "{intent}" 必须定义 argv,或定义 mode = "shell" 并提供 cmd',
|
|
676
679
|
"run.error.unsafeIntent": '意图 "{intent}" 的名称不安全。{detail}',
|
|
@@ -681,6 +684,8 @@ export const zhMessages = {
|
|
|
681
684
|
"run.error.blockedLongRunningCommandDetail": "argv 必须描述会结束的单次命令,而不是开发服务器、监听命令、shell 包装器、解释器循环或后台进程。",
|
|
682
685
|
"run.error.cwdOutsideProject": '命令 "{intent}" 的 cwd 无效:{detail}',
|
|
683
686
|
"run.error.cwdOutsideProjectDetail": "意图 cwd 必须位于当前根目录内。",
|
|
687
|
+
"run.error.invalidTestTarget": '命令 "{intent}" 收到无效测试目标。{detail}',
|
|
688
|
+
"run.error.invalidTestTargetDetail": "测试目标必须是相对文件路径,且不能以 '-' 开头。",
|
|
684
689
|
"run.error.maxOutputBytes": '命令 "{intent}" 的 max_output_bytes 无效。{detail}',
|
|
685
690
|
"run.error.maxOutputBytesDetail": "输出限制必须保持在允许的最大值内。",
|
|
686
691
|
"run.error.conflictingPreviewModes": "只能使用 --dry-run 或 --plan-only,不能同时使用",
|
|
@@ -32,5 +32,5 @@ export function renderCliError(message, helpCommand, lang = 'en') {
|
|
|
32
32
|
}
|
|
33
33
|
export function printUsageError(reporter, message, helpCommand, helpText, lang = 'en') {
|
|
34
34
|
reporter.stderr(renderCliError(message, helpCommand, lang));
|
|
35
|
-
reporter.
|
|
35
|
+
reporter.stderr(helpText);
|
|
36
36
|
}
|
|
@@ -146,6 +146,10 @@ function updateSaveState() {
|
|
|
146
146
|
document.getElementById("save").disabled = pending.size === 0;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
function hasUnsavedChanges() {
|
|
150
|
+
return pending.size > 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
149
153
|
function setPending(id, value) {
|
|
150
154
|
const original = snapshot.settings.find((setting) => setting.id === id)?.value;
|
|
151
155
|
if (Object.is(original, value)) {
|
|
@@ -1899,6 +1903,11 @@ document.getElementById("reload").addEventListener("click", () => {
|
|
|
1899
1903
|
document.getElementById("save").addEventListener("click", () => {
|
|
1900
1904
|
save().catch((error) => statusText(error.message, "error"));
|
|
1901
1905
|
});
|
|
1906
|
+
window.addEventListener("beforeunload", (event) => {
|
|
1907
|
+
if (!hasUnsavedChanges()) return;
|
|
1908
|
+
event.preventDefault();
|
|
1909
|
+
event.returnValue = "";
|
|
1910
|
+
});
|
|
1902
1911
|
document.getElementById("open-mustflow").addEventListener("click", () => {
|
|
1903
1912
|
openMustflowFolder().catch((error) => statusText(error.message, "error"));
|
|
1904
1913
|
});
|
|
@@ -9,6 +9,9 @@ export function renderDashboardStyles() {
|
|
|
9
9
|
--accent: #8fb4ff;
|
|
10
10
|
--danger: #ff9a9a;
|
|
11
11
|
--ok: #9be7ba;
|
|
12
|
+
--control-bg: #11141a;
|
|
13
|
+
--control-hover-bg: #171b23;
|
|
14
|
+
--control-active-bg: #0d1015;
|
|
12
15
|
--row-bg: rgba(255, 255, 255, 0.018);
|
|
13
16
|
--row-bg-alt: rgba(255, 255, 255, 0.035);
|
|
14
17
|
--status-neutral-bg: rgba(174, 182, 197, 0.1);
|
|
@@ -16,6 +19,27 @@ export function renderDashboardStyles() {
|
|
|
16
19
|
--status-warn-bg: rgba(255, 154, 154, 0.1);
|
|
17
20
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
18
21
|
}
|
|
22
|
+
@media (prefers-color-scheme: light) {
|
|
23
|
+
:root {
|
|
24
|
+
color-scheme: light;
|
|
25
|
+
--bg: #f6f8fb;
|
|
26
|
+
--panel: #ffffff;
|
|
27
|
+
--line: #d9e0ea;
|
|
28
|
+
--text: #162033;
|
|
29
|
+
--muted: #5d6b82;
|
|
30
|
+
--accent: #285fc2;
|
|
31
|
+
--danger: #b4232d;
|
|
32
|
+
--ok: #197a47;
|
|
33
|
+
--control-bg: #ffffff;
|
|
34
|
+
--control-hover-bg: #eef3f9;
|
|
35
|
+
--control-active-bg: #e4ebf5;
|
|
36
|
+
--row-bg: rgba(40, 95, 194, 0.035);
|
|
37
|
+
--row-bg-alt: rgba(40, 95, 194, 0.065);
|
|
38
|
+
--status-neutral-bg: rgba(93, 107, 130, 0.11);
|
|
39
|
+
--status-ok-bg: rgba(25, 122, 71, 0.11);
|
|
40
|
+
--status-warn-bg: rgba(180, 35, 45, 0.1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
19
43
|
* { box-sizing: border-box; }
|
|
20
44
|
body {
|
|
21
45
|
margin: 0;
|
|
@@ -75,6 +99,11 @@ main {
|
|
|
75
99
|
gap: 8px;
|
|
76
100
|
margin-bottom: 14px;
|
|
77
101
|
overflow-x: auto;
|
|
102
|
+
-webkit-overflow-scrolling: touch;
|
|
103
|
+
scrollbar-width: none;
|
|
104
|
+
}
|
|
105
|
+
.tabs::-webkit-scrollbar {
|
|
106
|
+
display: none;
|
|
78
107
|
}
|
|
79
108
|
.tab {
|
|
80
109
|
border-color: transparent;
|
|
@@ -120,17 +149,35 @@ input:focus-visible {
|
|
|
120
149
|
white-space: nowrap;
|
|
121
150
|
}
|
|
122
151
|
button, select, input {
|
|
123
|
-
background:
|
|
152
|
+
background: var(--control-bg);
|
|
124
153
|
border: 1px solid var(--line);
|
|
125
154
|
border-radius: 6px;
|
|
126
155
|
color: var(--text);
|
|
127
156
|
font: inherit;
|
|
128
157
|
min-height: 38px;
|
|
158
|
+
transition: background-color 160ms ease, border-color 160ms ease;
|
|
129
159
|
}
|
|
130
160
|
button {
|
|
131
161
|
cursor: pointer;
|
|
132
162
|
padding: 0 14px;
|
|
133
163
|
}
|
|
164
|
+
button:not(:disabled):hover,
|
|
165
|
+
select:hover,
|
|
166
|
+
input:hover {
|
|
167
|
+
background: var(--control-hover-bg);
|
|
168
|
+
border-color: var(--accent);
|
|
169
|
+
}
|
|
170
|
+
button:not(:disabled):active {
|
|
171
|
+
background: var(--control-active-bg);
|
|
172
|
+
}
|
|
173
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
174
|
+
button {
|
|
175
|
+
transition: background-color 160ms ease, border-color 160ms ease, transform 120ms ease;
|
|
176
|
+
}
|
|
177
|
+
button:not(:disabled):active {
|
|
178
|
+
transform: translateY(1px);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
134
181
|
button:disabled {
|
|
135
182
|
cursor: not-allowed;
|
|
136
183
|
opacity: 0.6;
|
|
@@ -75,7 +75,7 @@ function readLedgerFile(projectRoot) {
|
|
|
75
75
|
const ledgerPath = path.join(projectRoot, DOC_REVIEW_LEDGER_RELATIVE_PATH);
|
|
76
76
|
const ledgerDirectoryPath = path.dirname(ledgerPath);
|
|
77
77
|
ensureInside(projectRoot, ledgerPath);
|
|
78
|
-
ensureInsideWithoutSymlinks(projectRoot, ledgerDirectoryPath, {
|
|
78
|
+
ensureInsideWithoutSymlinks(projectRoot, ledgerDirectoryPath, { allowMissingDescendant: true });
|
|
79
79
|
if (!existsSync(ledgerDirectoryPath)) {
|
|
80
80
|
return { schema_version: '1', documents: [] };
|
|
81
81
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { parseGitStatusOutput } from '../../core/change-classification.js';
|
|
3
|
+
import { createCommandEnv } from '../../core/command-env.js';
|
|
3
4
|
const GIT_STATUS_TIMEOUT_MS = 10_000;
|
|
4
5
|
const GIT_STATUS_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
5
6
|
export class GitChangedFilesError extends Error {
|
|
@@ -14,6 +15,7 @@ export function readGitChangedFiles(projectRoot) {
|
|
|
14
15
|
const result = spawnSync('git', ['status', '--porcelain=v1', '-z', '--untracked-files=all'], {
|
|
15
16
|
cwd: projectRoot,
|
|
16
17
|
encoding: 'utf8',
|
|
18
|
+
env: createCommandEnv(projectRoot, { policy: 'minimal', allowlist: [] }),
|
|
17
19
|
input: '',
|
|
18
20
|
maxBuffer: GIT_STATUS_MAX_BUFFER_BYTES,
|
|
19
21
|
stdio: ['ignore', 'pipe', 'pipe'],
|