mustflow 2.22.15 → 2.22.16

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.
@@ -69,5 +69,6 @@ export function runCheck(args, reporter, lang = 'en') {
69
69
  for (const issue of issues) {
70
70
  reporter.stderr(issue);
71
71
  }
72
+ reporter.stderr(t(lang, 'check.result.failed', { count: issues.length }));
72
73
  return 1;
73
74
  }
@@ -86,10 +86,10 @@ export async function runContext(args, reporter, lang = 'en') {
86
86
  }
87
87
  const context = getAgentContext(mustflowRoot);
88
88
  reporter.stdout(t(lang, 'context.title'));
89
- reporter.stdout(`${t(lang, 'label.installed')}: ${context.installed ? 'yes' : 'no'}`);
89
+ reporter.stdout(`${t(lang, 'label.installed')}: ${context.installed ? t(lang, 'value.yes') : t(lang, 'value.no')}`);
90
90
  reporter.stdout(`${t(lang, 'label.mustflowRoot')}: ${context.mustflow_root}`);
91
- reporter.stdout(`${t(lang, 'label.commandContract')}: ${context.command_contract.exists ? 'present' : 'missing'}`);
91
+ reporter.stdout(`${t(lang, 'label.commandContract')}: ${context.command_contract.exists ? t(lang, 'value.present') : t(lang, 'value.missing')}`);
92
92
  reporter.stdout(`${t(lang, 'label.runnableIntents')}: ${context.command_contract.runnable_intents.length}`);
93
- reporter.stdout(`${t(lang, 'label.latestRun')}: ${context.latest_run.exists ? 'present' : 'missing'}`);
93
+ reporter.stdout(`${t(lang, 'label.latestRun')}: ${context.latest_run.exists ? t(lang, 'value.present') : t(lang, 'value.missing')}`);
94
94
  return 0;
95
95
  }
@@ -223,20 +223,32 @@ function getDiagnosticLabel(id, lang) {
223
223
  return t(lang, 'doctor.diagnostic.latestRun');
224
224
  }
225
225
  }
226
+ function getDiagnosticStatusLabel(status, lang) {
227
+ switch (status) {
228
+ case 'ok':
229
+ return t(lang, 'doctor.status.ok');
230
+ case 'warn':
231
+ return t(lang, 'doctor.status.warn');
232
+ case 'fail':
233
+ return t(lang, 'doctor.status.fail');
234
+ case 'info':
235
+ return t(lang, 'doctor.status.info');
236
+ }
237
+ }
226
238
  function renderDoctorOutput(output, lang) {
227
239
  const lines = [];
228
240
  lines.push(t(lang, 'doctor.title'));
229
241
  lines.push(`${t(lang, 'label.mustflowRoot')}: ${output.mustflow_root}`);
230
- lines.push(`${t(lang, 'label.installed')}: ${output.installed ? 'yes' : 'no'}`);
231
- lines.push(`${t(lang, 'doctor.label.strict')}: ${output.strict ? 'yes' : 'no'}`);
232
- lines.push(`${t(lang, 'doctor.label.check')}: ${output.check.ok ? 'passed' : 'failed'}`);
242
+ lines.push(`${t(lang, 'label.installed')}: ${output.installed ? t(lang, 'value.yes') : t(lang, 'value.no')}`);
243
+ lines.push(`${t(lang, 'doctor.label.strict')}: ${output.strict ? t(lang, 'value.yes') : t(lang, 'value.no')}`);
244
+ lines.push(`${t(lang, 'doctor.label.check')}: ${output.check.ok ? t(lang, 'value.passed') : t(lang, 'value.failed')}`);
233
245
  lines.push(`${t(lang, 'doctor.label.issues')}: ${output.check.issue_count}`);
234
- lines.push(`${t(lang, 'label.commandContract')}: ${output.context.command_contract_exists ? 'present' : 'missing'}`);
246
+ lines.push(`${t(lang, 'label.commandContract')}: ${output.context.command_contract_exists ? t(lang, 'value.present') : t(lang, 'value.missing')}`);
235
247
  lines.push(`${t(lang, 'label.runnableIntents')}: ${output.context.runnable_intents.length}`);
236
248
  lines.push('', t(lang, 'doctor.section.health'));
237
249
  for (const diagnostic of output.diagnostics) {
238
250
  const action = diagnostic.action && diagnostic.status !== 'ok' ? ` (${t(lang, 'doctor.actionLabel')}: ${diagnostic.action})` : '';
239
- lines.push(`- [${diagnostic.status}] ${getDiagnosticLabel(diagnostic.id, lang)}: ${diagnostic.summary}${action}`);
251
+ lines.push(`- [${getDiagnosticStatusLabel(diagnostic.status, lang)}] ${getDiagnosticLabel(diagnostic.id, lang)}: ${diagnostic.summary}${action}`);
240
252
  }
241
253
  if (output.check.issues.length > 0) {
242
254
  lines.push('', t(lang, 'doctor.section.issueList'));
@@ -14,11 +14,12 @@ import { explainSourceAnchor } from '../../core/source-anchor-explanation.js';
14
14
  import { checkMustflowProject } from '../lib/validation.js';
15
15
  import { readLocalCommandEffectGraph, readLocalPathSurface, } from '../lib/local-index.js';
16
16
  import { explainVerifyArgErrorMessage, explainVerifyPlanErrorMessage, getVerifyExplainOutput, parseExplainVerifyArgs, readExplainVerifyPlanReasons, renderVerifyExplainDecision, } from './explain-verify.js';
17
+ import { createRunPlan, } from '../lib/run-plan.js';
17
18
  const EXPLAIN_SCHEMA_VERSION = '1';
18
19
  const LATEST_RUN_RECEIPT_RELATIVE_PATH = '.mustflow/state/runs/latest.json';
19
20
  export function getExplainHelp(lang = 'en') {
20
21
  return renderHelp({
21
- usage: 'mf explain <topic> [target] [options] | mf explain verify --reason <event> [options] | mf explain why <target> [options]',
22
+ usage: 'mf explain <topic> [target] [options] | mf explain verify --reason <event> [options] | mf explain why <target> [options] | mf explain --why-blocked <intent> [options]',
22
23
  summary: t(lang, 'explain.help.summary'),
23
24
  options: [
24
25
  { label: '--json', description: t(lang, 'cli.option.json') },
@@ -33,6 +34,8 @@ export function getExplainHelp(lang = 'en') {
33
34
  'mf explain asset-optimization --json',
34
35
  'mf explain command test',
35
36
  'mf explain command lint --json',
37
+ 'mf explain --why-blocked deploy',
38
+ 'mf explain --why-blocked lint --json',
36
39
  'mf explain retention',
37
40
  'mf explain retention --json',
38
41
  'mf explain verify --reason code_change',
@@ -92,6 +95,43 @@ async function getCommandExplainOutput(projectRoot, commandName) {
92
95
  decision: effectGraph ? { ...decision, effectGraph } : decision,
93
96
  };
94
97
  }
98
+ function getWhyBlockedExplainOutput(projectRoot, commandName) {
99
+ const contract = readCommandContract(projectRoot);
100
+ const plan = createRunPlan(projectRoot, contract, commandName);
101
+ const commandDecision = explainCommandIntent(contract, commandName, { projectRoot });
102
+ const blockedRunPlan = {
103
+ runnable: plan.ok,
104
+ reasonCode: plan.reasonCode,
105
+ detail: plan.detail,
106
+ status: plan.intentStatus,
107
+ lifecycle: plan.lifecycle,
108
+ runPolicy: plan.runPolicy,
109
+ configuredCwd: plan.configuredCwd,
110
+ timeoutSeconds: plan.timeoutSeconds,
111
+ mode: plan.mode,
112
+ writes: plan.writes ?? [],
113
+ suggestedIntentSnippet: plan.suggestedIntentSnippet,
114
+ };
115
+ const decision = {
116
+ ...commandDecision,
117
+ decision: plan.ok
118
+ ? `command intent "${commandName}" is not blocked for mf run`
119
+ : `command intent "${commandName}" is blocked for mf run`,
120
+ effectiveAction: plan.ok
121
+ ? `Run mf run ${commandName} when this intent is required for the changed behavior.`
122
+ : plan.suggestedIntentSnippet
123
+ ? 'Review the suggested command contract snippet before enabling agent execution.'
124
+ : commandDecision.effectiveAction,
125
+ blockedRunPlan,
126
+ };
127
+ return {
128
+ schema_version: EXPLAIN_SCHEMA_VERSION,
129
+ command: 'explain',
130
+ topic: 'why',
131
+ mustflow_root: projectRoot,
132
+ decision,
133
+ };
134
+ }
95
135
  function getRetentionExplainOutput(projectRoot) {
96
136
  return {
97
137
  schema_version: EXPLAIN_SCHEMA_VERSION,
@@ -393,6 +433,13 @@ function renderExplainDecision(output, lang) {
393
433
  const latest = output.decision.latestFailure;
394
434
  lines.push('', 'Latest run failure', `- path: ${latest.path}`, `- present: ${latest.present ? t(lang, 'value.yes') : t(lang, 'value.no')}`, `- valid: ${latest.valid ? t(lang, 'value.yes') : t(lang, 'value.no')}`, `- failed: ${latest.failed ? t(lang, 'value.yes') : t(lang, 'value.no')}`, `- status: ${latest.status ?? t(lang, 'value.none')}`, `- intent: ${latest.intent ?? t(lang, 'value.none')}`, `- exit_code: ${latest.exitCode ?? t(lang, 'value.none')}`, `- error_kind: ${latest.errorKind ?? t(lang, 'value.none')}`, `- duration_ms: ${latest.durationMs ?? t(lang, 'value.none')}`, `- summary: ${latest.summary}`);
395
435
  }
436
+ if ('blockedRunPlan' in output.decision && output.decision.blockedRunPlan) {
437
+ const plan = output.decision.blockedRunPlan;
438
+ lines.push('', t(lang, 'explain.label.blockedRunPlan'), `- runnable: ${plan.runnable ? t(lang, 'value.yes') : t(lang, 'value.no')}`, `- reason_code: ${plan.reasonCode ?? t(lang, 'value.none')}`, `- detail: ${plan.detail ?? t(lang, 'value.none')}`, `- status: ${plan.status ?? t(lang, 'value.none')}`, `- lifecycle: ${plan.lifecycle ?? t(lang, 'value.none')}`, `- run_policy: ${plan.runPolicy ?? t(lang, 'value.none')}`, `- configured_cwd: ${plan.configuredCwd ?? t(lang, 'value.none')}`, `- timeout_seconds: ${plan.timeoutSeconds ?? t(lang, 'value.none')}`, `- mode: ${plan.mode ?? t(lang, 'value.none')}`, `- writes: ${plan.writes.join(', ') || t(lang, 'value.none')}`);
439
+ if (plan.suggestedIntentSnippet) {
440
+ lines.push('', `${t(lang, 'run.label.suggestedIntentSnippet')}:`, plan.suggestedIntentSnippet);
441
+ }
442
+ }
396
443
  if ('boundary' in output.decision) {
397
444
  lines.push('', t(lang, 'explain.label.authorityBoundary'), `- role: ${output.decision.boundary.role}`);
398
445
  if (output.decision.boundary.canDefine.length > 0) {
@@ -508,6 +555,23 @@ export async function runExplain(args, reporter, lang = 'en') {
508
555
  const json = args.includes('--json');
509
556
  const positional = args.filter((arg) => arg !== '--json');
510
557
  const [topic, targetArg, ...rest] = positional;
558
+ if (topic === '--why-blocked') {
559
+ if (!targetArg) {
560
+ printUsageError(reporter, t(lang, 'explain.error.missingCommand'), 'mf explain --help', getExplainHelp(lang), lang);
561
+ return 1;
562
+ }
563
+ if (rest.length > 0) {
564
+ printUsageError(reporter, t(lang, 'cli.error.unexpectedArgument', { argument: rest[0] }), 'mf explain --help', getExplainHelp(lang), lang);
565
+ return 1;
566
+ }
567
+ const output = getWhyBlockedExplainOutput(resolveMustflowRoot(), targetArg);
568
+ if (json) {
569
+ reporter.stdout(JSON.stringify(output, null, 2));
570
+ return 0;
571
+ }
572
+ reporter.stdout(renderExplainDecision(output, lang));
573
+ return 0;
574
+ }
511
575
  if (topic === 'why') {
512
576
  const projectRoot = resolveMustflowRoot();
513
577
  const output = await getWhyExplainOutput(projectRoot, targetArg, rest, lang, reporter);
@@ -58,8 +58,8 @@ function renderIndexSummary(result, lang) {
58
58
  `source_anchors: ${result.source_anchor_count}`,
59
59
  `source_anchor_risk_signals: ${result.source_anchor_risk_signal_count}`,
60
60
  `index_mode: ${result.index_mode}`,
61
- `reused_existing: ${result.reused_existing ? 'yes' : 'no'}`,
62
- `${t(lang, 'label.wroteFiles')}: ${result.wrote_files ? 'yes' : 'no'}`,
61
+ `reused_existing: ${result.reused_existing ? t(lang, 'value.yes') : t(lang, 'value.no')}`,
62
+ `${t(lang, 'label.wroteFiles')}: ${result.wrote_files ? t(lang, 'value.yes') : t(lang, 'value.no')}`,
63
63
  ];
64
64
  if (result.dry_run) {
65
65
  lines.push(t(lang, 'index.dryRunNoFiles'));
@@ -97,10 +97,16 @@ function createPlanCommandHash(plan) {
97
97
  };
98
98
  return `sha256:${createHash('sha256').update(JSON.stringify(payload)).digest('hex')}`;
99
99
  }
100
- function renderActiveLockConflictMessage(intentName, conflicts) {
100
+ function renderActiveLockConflictMessage(intentName, conflicts, lang) {
101
101
  const [first] = conflicts;
102
- const detail = first ? `${first.lock} conflicts with active intent ${first.conflictsWithIntent} (pid ${first.conflictsWithPid})` : 'unknown active lock conflict';
103
- return `mf run ${intentName} is blocked by an active run lock: ${detail}`;
102
+ const detail = first
103
+ ? t(lang, 'run.error.activeLockConflictDetail', {
104
+ lock: first.lock,
105
+ intent: first.conflictsWithIntent,
106
+ pid: first.conflictsWithPid,
107
+ })
108
+ : t(lang, 'run.error.activeLockConflictUnknown');
109
+ return t(lang, 'run.error.activeLockConflict', { intent: intentName, detail });
104
110
  }
105
111
  export function getRunHelp(lang = 'en') {
106
112
  return renderHelp({
@@ -205,7 +211,7 @@ export async function runRun(args, reporter, lang = 'en', options = {}) {
205
211
  }
206
212
  const activeRunLock = profiler.measure('active_lock_acquire', () => acquireActiveRunLock(projectRoot, contract, intentName, { commandHash: createPlanCommandHash(plan) }));
207
213
  if (!activeRunLock.ok) {
208
- reporter.stderr(renderCliError(renderActiveLockConflictMessage(intentName, activeRunLock.conflicts), 'mf run --dry-run --json', lang));
214
+ reporter.stderr(renderCliError(renderActiveLockConflictMessage(intentName, activeRunLock.conflicts, lang), 'mf run --dry-run --json', lang));
209
215
  writeLatestProfile(profiler, options, {
210
216
  projectRoot,
211
217
  intent: intentName,
@@ -53,7 +53,7 @@ export function runStatus(args, reporter, lang = 'en') {
53
53
  return 0;
54
54
  }
55
55
  reporter.stdout(t(lang, 'status.title'));
56
- reporter.stdout(`${t(lang, 'label.installed')}: ${status.installed ? 'yes' : 'no'}`);
56
+ reporter.stdout(`${t(lang, 'label.installed')}: ${status.installed ? t(lang, 'value.yes') : t(lang, 'value.no')}`);
57
57
  reporter.stdout(`${t(lang, 'label.manifestLock')}: ${status.manifestLock}`);
58
58
  reporter.stdout(`${t(lang, 'label.trackedFiles')}: ${status.trackedFiles}`);
59
59
  reporter.stdout(`${t(lang, 'label.changedFiles')}: ${status.changedFiles.length}`);
@@ -919,18 +919,24 @@ function renderVerifyOutput(output, lang) {
919
919
  `${t(lang, 'verify.label.reason')}: ${output.reason}`,
920
920
  `${t(lang, 'verify.label.planSource')}: ${output.plan_source ?? t(lang, 'value.none')}`,
921
921
  `${t(lang, 'verify.label.status')}: ${output.status}`,
922
- `completion verdict: ${output.completion_verdict.status} (${output.completion_verdict.primary_reason})`,
923
- `matched: ${output.summary.matched}`,
924
- `ran: ${output.summary.ran}`,
925
- `passed: ${output.summary.passed}`,
926
- `failed: ${output.summary.failed}`,
927
- `skipped: ${output.summary.skipped}`,
922
+ `${t(lang, 'verify.label.completionVerdict')}: ${output.completion_verdict.status} (${output.completion_verdict.primary_reason})`,
923
+ `${t(lang, 'verify.label.matched')}: ${output.summary.matched}`,
924
+ `${t(lang, 'verify.label.ran')}: ${output.summary.ran}`,
925
+ `${t(lang, 'verify.label.passed')}: ${output.summary.passed}`,
926
+ `${t(lang, 'verify.label.failed')}: ${output.summary.failed}`,
927
+ `${t(lang, 'verify.label.skipped')}: ${output.summary.skipped}`,
928
928
  ];
929
929
  if (output.parallelism) {
930
930
  const cpuAvailable = output.parallelism.cpu_available ?? t(lang, 'value.none');
931
- lines.push(`parallelism: requested ${output.parallelism.requested}, effective ${output.parallelism.effective}, repository max ${output.parallelism.repository_max}, cpu available ${cpuAvailable}, mode ${output.parallelism.mode}`);
931
+ lines.push(`${t(lang, 'verify.label.parallelism')}: ${t(lang, 'verify.parallelism.summary', {
932
+ requested: output.parallelism.requested,
933
+ effective: output.parallelism.effective,
934
+ repositoryMax: output.parallelism.repository_max,
935
+ cpuAvailable,
936
+ mode: output.parallelism.mode,
937
+ })}`);
932
938
  if (output.parallelism.capped) {
933
- lines.push(`parallelism note: ${output.parallelism.note}`);
939
+ lines.push(`${t(lang, 'verify.label.parallelismNote')}: ${output.parallelism.note}`);
934
940
  }
935
941
  }
936
942
  lines.push('', t(lang, 'verify.label.results'));
@@ -20,6 +20,10 @@ export const enMessages = {
20
20
  "value.yes": "yes",
21
21
  "value.no": "no",
22
22
  "value.none": "none",
23
+ "value.present": "present",
24
+ "value.missing": "missing",
25
+ "value.passed": "passed",
26
+ "value.failed": "failed",
23
27
  "command.adapters.summary": "Inspect host adapter compatibility without generating adapter files",
24
28
  "command.init.summary": "Copy the default mustflow agent workflow",
25
29
  "command.check.summary": "Validate mustflow files",
@@ -54,6 +58,7 @@ export const enMessages = {
54
58
  "check.help.exit.fail": "Validation failed or the command received invalid input",
55
59
  "check.result.passed": "mustflow check passed",
56
60
  "check.result.strictPassed": "mustflow strict check passed",
61
+ "check.result.failed": "mustflow check failed: {count} issue(s) found.",
57
62
  "contractLint.help.summary": "Inspect .mustflow/config/commands.toml for command-contract errors and warnings.",
58
63
  "contractLint.help.option.coverage": "Also report required_after coverage for change-classification reasons",
59
64
  "contractLint.help.option.suggest": "Suggest non-runnable intent snippets from package.json, Makefile, or justfile",
@@ -484,6 +489,10 @@ export const enMessages = {
484
489
  "doctor.section.issueList": "Issue list:",
485
490
  "doctor.section.suggestedCommands": "Suggested commands:",
486
491
  "doctor.actionLabel": "run",
492
+ "doctor.status.ok": "ok",
493
+ "doctor.status.warn": "warn",
494
+ "doctor.status.fail": "fail",
495
+ "doctor.status.info": "info",
487
496
  "doctor.diagnostic.install": "Install",
488
497
  "doctor.diagnostic.validation": "Validation",
489
498
  "doctor.diagnostic.skillRoutes": "Skill routes",
@@ -680,6 +689,9 @@ Read these files before working:
680
689
  "run.error.timedOut": 'Command "{intent}" timed out after {seconds} seconds',
681
690
  "run.error.outputLimitExceeded": 'Command "{intent}" exceeded max_output_bytes: {message}',
682
691
  "run.error.startFailed": 'Command "{intent}" failed to start: {message}',
692
+ "run.error.activeLockConflict": "mf run {intent} is blocked by an active run lock: {detail}",
693
+ "run.error.activeLockConflictDetail": "{lock} conflicts with active intent {intent} (pid {pid})",
694
+ "run.error.activeLockConflictUnknown": "unknown active lock conflict",
683
695
  "search.help.summary": "Search the local SQLite index for the mustflow workflow.",
684
696
  "search.help.option.limit": "Set the number of results to print. Default: 10, max: 50",
685
697
  "search.help.option.scope": "Select indexed workflow data, source anchors, or both. Default: workflow",
@@ -771,6 +783,15 @@ Read these files before working:
771
783
  "verify.label.planSource": "Plan source",
772
784
  "verify.label.status": "Status",
773
785
  "verify.label.results": "Results",
786
+ "verify.label.completionVerdict": "Completion verdict",
787
+ "verify.label.matched": "Matched",
788
+ "verify.label.ran": "Ran",
789
+ "verify.label.passed": "Passed",
790
+ "verify.label.failed": "Failed",
791
+ "verify.label.skipped": "Skipped",
792
+ "verify.label.parallelism": "Parallelism",
793
+ "verify.label.parallelismNote": "Parallelism note",
794
+ "verify.parallelism.summary": "requested {requested}, effective {effective}, repository max {repositoryMax}, CPU available {cpuAvailable}, mode {mode}",
774
795
  "verify.error.missingReason": "Missing verification reason",
775
796
  "verify.error.conflictingInputs": "Use only one of --reason, --from-classification, --from-plan, or --changed",
776
797
  "verify.error.writePlanRequiresChanged": "--write-plan requires --changed",
@@ -810,6 +831,7 @@ Read these files before working:
810
831
  "explain.label.publicSurface": "Public surface",
811
832
  "explain.label.validationReasons": "Validation reasons",
812
833
  "explain.label.affectedContracts": "Affected contracts",
834
+ "explain.label.blockedRunPlan": "Blocked run plan",
813
835
  "explain.error.missingTopic": "Missing explain topic",
814
836
  "explain.error.missingCommand": "Missing command intent",
815
837
  "explain.error.missingSkill": "Missing skill id",
@@ -20,6 +20,10 @@ export const esMessages = {
20
20
  "value.yes": "sí",
21
21
  "value.no": "no",
22
22
  "value.none": "ninguno",
23
+ "value.present": "presente",
24
+ "value.missing": "faltante",
25
+ "value.passed": "superado",
26
+ "value.failed": "fallido",
23
27
  "command.adapters.summary": "Inspecciona compatibilidad de adaptadores sin generar archivos",
24
28
  "command.init.summary": "Copia el flujo de trabajo de agente mustflow predeterminado",
25
29
  "command.check.summary": "Valida los archivos mustflow",
@@ -54,6 +58,7 @@ export const esMessages = {
54
58
  "check.help.exit.fail": "La validación falló o el comando recibió una entrada no válida",
55
59
  "check.result.passed": "comprobacion mustflow superada",
56
60
  "check.result.strictPassed": "comprobacion estricta de mustflow superada",
61
+ "check.result.failed": "comprobacion mustflow fallida: se encontraron {count} problema(s).",
57
62
  "contractLint.help.summary": "Inspecciona .mustflow/config/commands.toml para encontrar errores y advertencias del contrato de comandos.",
58
63
  "contractLint.help.option.coverage": "También informa cobertura required_after para razones de clasificación de cambios",
59
64
  "contractLint.help.option.suggest": "Sugiere fragmentos de intent no ejecutables desde package.json, Makefile o justfile",
@@ -484,6 +489,10 @@ export const esMessages = {
484
489
  "doctor.section.issueList": "Lista de problemas:",
485
490
  "doctor.section.suggestedCommands": "Comandos sugeridos:",
486
491
  "doctor.actionLabel": "ejecutar",
492
+ "doctor.status.ok": "ok",
493
+ "doctor.status.warn": "advertencia",
494
+ "doctor.status.fail": "fallo",
495
+ "doctor.status.info": "info",
487
496
  "doctor.diagnostic.install": "Instalación",
488
497
  "doctor.diagnostic.validation": "Validación",
489
498
  "doctor.diagnostic.skillRoutes": "Rutas de skills",
@@ -680,6 +689,9 @@ Lee estos archivos antes de trabajar:
680
689
  "run.error.timedOut": 'El comando "{intent}" agotó el tiempo después de {seconds} segundos',
681
690
  "run.error.outputLimitExceeded": 'El comando "{intent}" superó max_output_bytes: {message}',
682
691
  "run.error.startFailed": 'No se pudo iniciar el comando "{intent}": {message}',
692
+ "run.error.activeLockConflict": "mf run {intent} está bloqueado por un bloqueo de ejecución activo: {detail}",
693
+ "run.error.activeLockConflictDetail": "{lock} entra en conflicto con la intención activa {intent} (pid {pid})",
694
+ "run.error.activeLockConflictUnknown": "conflicto de bloqueo de ejecución activo desconocido",
683
695
  "search.help.summary": "Busca en el índice SQLite local del flujo de trabajo mustflow.",
684
696
  "search.help.option.limit": "Define la cantidad de resultados que se imprimen. Predeterminado: 10, máximo: 50",
685
697
  "search.help.option.scope": "Selecciona datos del flujo de trabajo indexados, anchors de código fuente o ambos. Predeterminado: workflow",
@@ -771,6 +783,15 @@ Lee estos archivos antes de trabajar:
771
783
  "verify.label.planSource": "Fuente del plan",
772
784
  "verify.label.status": "Estado",
773
785
  "verify.label.results": "Resultados",
786
+ "verify.label.completionVerdict": "Veredicto de finalización",
787
+ "verify.label.matched": "Coincidencias",
788
+ "verify.label.ran": "Ejecutadas",
789
+ "verify.label.passed": "Superadas",
790
+ "verify.label.failed": "Fallidas",
791
+ "verify.label.skipped": "Omitidas",
792
+ "verify.label.parallelism": "Paralelismo",
793
+ "verify.label.parallelismNote": "Nota de paralelismo",
794
+ "verify.parallelism.summary": "solicitado {requested}, efectivo {effective}, máximo del repositorio {repositoryMax}, CPU disponible {cpuAvailable}, modo {mode}",
774
795
  "verify.error.missingReason": "Falta la razón de verificación",
775
796
  "verify.error.conflictingInputs": "Usa solo uno de --reason, --from-classification, --from-plan o --changed",
776
797
  "verify.error.writePlanRequiresChanged": "--write-plan requiere --changed",
@@ -810,6 +831,7 @@ Lee estos archivos antes de trabajar:
810
831
  "explain.label.publicSurface": "Superficie publica",
811
832
  "explain.label.validationReasons": "Razones de validacion",
812
833
  "explain.label.affectedContracts": "Contratos afectados",
834
+ "explain.label.blockedRunPlan": "Plan de ejecución bloqueado",
813
835
  "explain.error.missingTopic": "Falta el tema de explicación",
814
836
  "explain.error.missingCommand": "Falta la intención de comando",
815
837
  "explain.error.missingSkill": "Falta el id de skill",
@@ -20,6 +20,10 @@ export const frMessages = {
20
20
  "value.yes": "oui",
21
21
  "value.no": "non",
22
22
  "value.none": "aucun",
23
+ "value.present": "présent",
24
+ "value.missing": "manquant",
25
+ "value.passed": "réussi",
26
+ "value.failed": "échoué",
23
27
  "command.adapters.summary": "Inspecte la compatibilité des adaptateurs sans générer de fichiers",
24
28
  "command.init.summary": "Copie le flux de travail d'agent mustflow par défaut",
25
29
  "command.check.summary": "Valide les fichiers mustflow",
@@ -54,6 +58,7 @@ export const frMessages = {
54
58
  "check.help.exit.fail": "La validation a échoué ou la commande a reçu une entrée non valide",
55
59
  "check.result.passed": "vérification mustflow réussie",
56
60
  "check.result.strictPassed": "vérification stricte mustflow réussie",
61
+ "check.result.failed": "vérification mustflow échouée : {count} problème(s) trouvé(s).",
57
62
  "contractLint.help.summary": "Inspecte .mustflow/config/commands.toml pour trouver les erreurs et avertissements du contrat de commandes.",
58
63
  "contractLint.help.option.coverage": "Signale aussi la couverture required_after des raisons de classification",
59
64
  "contractLint.help.option.suggest": "Suggère des fragments d'intent non exécutables depuis package.json, Makefile ou justfile",
@@ -484,6 +489,10 @@ export const frMessages = {
484
489
  "doctor.section.issueList": "Liste des problèmes :",
485
490
  "doctor.section.suggestedCommands": "Commandes suggérées :",
486
491
  "doctor.actionLabel": "exécuter",
492
+ "doctor.status.ok": "ok",
493
+ "doctor.status.warn": "alerte",
494
+ "doctor.status.fail": "échec",
495
+ "doctor.status.info": "info",
487
496
  "doctor.diagnostic.install": "Installation",
488
497
  "doctor.diagnostic.validation": "Validation",
489
498
  "doctor.diagnostic.skillRoutes": "Routage des skills",
@@ -680,6 +689,9 @@ Lisez ces fichiers avant de travailler :
680
689
  "run.error.timedOut": 'La commande "{intent}" a expiré après {seconds} secondes',
681
690
  "run.error.outputLimitExceeded": 'La commande "{intent}" a dépassé max_output_bytes : {message}',
682
691
  "run.error.startFailed": 'Impossible de démarrer la commande "{intent}" : {message}',
692
+ "run.error.activeLockConflict": "mf run {intent} est bloqué par un verrou d'exécution actif : {detail}",
693
+ "run.error.activeLockConflictDetail": "{lock} entre en conflit avec l'intention active {intent} (pid {pid})",
694
+ "run.error.activeLockConflictUnknown": "conflit de verrou d'exécution actif inconnu",
683
695
  "search.help.summary": "Recherche dans l'index SQLite local du flux de travail mustflow.",
684
696
  "search.help.option.limit": "Définit le nombre de résultats à imprimer. Par défaut : 10, max : 50",
685
697
  "search.help.option.scope": "Sélectionne les données de workflow indexées, les anchors de source, ou les deux. Par défaut : workflow",
@@ -771,6 +783,15 @@ Lisez ces fichiers avant de travailler :
771
783
  "verify.label.planSource": "Source du plan",
772
784
  "verify.label.status": "État",
773
785
  "verify.label.results": "Résultats",
786
+ "verify.label.completionVerdict": "Verdict de complétion",
787
+ "verify.label.matched": "Correspondants",
788
+ "verify.label.ran": "Exécutés",
789
+ "verify.label.passed": "Réussis",
790
+ "verify.label.failed": "Échoués",
791
+ "verify.label.skipped": "Ignorés",
792
+ "verify.label.parallelism": "Parallélisme",
793
+ "verify.label.parallelismNote": "Note de parallélisme",
794
+ "verify.parallelism.summary": "demandé {requested}, effectif {effective}, maximum du dépôt {repositoryMax}, CPU disponible {cpuAvailable}, mode {mode}",
774
795
  "verify.error.missingReason": "Raison de vérification manquante",
775
796
  "verify.error.conflictingInputs": "Utilisez un seul de --reason, --from-classification, --from-plan ou --changed",
776
797
  "verify.error.writePlanRequiresChanged": "--write-plan nécessite --changed",
@@ -810,6 +831,7 @@ Lisez ces fichiers avant de travailler :
810
831
  "explain.label.publicSurface": "Surface publique",
811
832
  "explain.label.validationReasons": "Raisons de validation",
812
833
  "explain.label.affectedContracts": "Contrats affectes",
834
+ "explain.label.blockedRunPlan": "Plan d'exécution bloqué",
813
835
  "explain.error.missingTopic": "Sujet d'explication manquant",
814
836
  "explain.error.missingCommand": "Intention de commande manquante",
815
837
  "explain.error.missingSkill": "Identifiant de skill manquant",
@@ -20,6 +20,10 @@ export const hiMessages = {
20
20
  "value.yes": "हाँ",
21
21
  "value.no": "नहीं",
22
22
  "value.none": "none",
23
+ "value.present": "मौजूद",
24
+ "value.missing": "गुम",
25
+ "value.passed": "पास",
26
+ "value.failed": "फेल",
23
27
  "command.adapters.summary": "एडाप्टर फ़ाइलें बनाए बिना होस्ट संगतता जाँचें",
24
28
  "command.init.summary": "डिफ़ॉल्ट mustflow एजेंट वर्कफ़्लो कॉपी करें",
25
29
  "command.check.summary": "mustflow फ़ाइलों की जाँच करें",
@@ -54,6 +58,7 @@ export const hiMessages = {
54
58
  "check.help.exit.fail": "सत्यापन विफल हुआ या कमांड को अमान्य इनपुट मिला",
55
59
  "check.result.passed": "mustflow check पास हुआ",
56
60
  "check.result.strictPassed": "mustflow strict check पास हुआ",
61
+ "check.result.failed": "mustflow check फेल हुआ: {count} समस्या मिली।",
57
62
  "contractLint.help.summary": ".mustflow/config/commands.toml में command-contract errors और warnings जाँचें.",
58
63
  "contractLint.help.option.coverage": "change-classification reasons के लिए required_after coverage भी report करें",
59
64
  "contractLint.help.option.suggest": "package.json, Makefile, या justfile से non-runnable intent snippets सुझाएँ",
@@ -484,6 +489,10 @@ export const hiMessages = {
484
489
  "doctor.section.issueList": "समस्या सूची:",
485
490
  "doctor.section.suggestedCommands": "सुझाई गई कमांड:",
486
491
  "doctor.actionLabel": "चलाएँ",
492
+ "doctor.status.ok": "ठीक",
493
+ "doctor.status.warn": "चेतावनी",
494
+ "doctor.status.fail": "फेल",
495
+ "doctor.status.info": "जानकारी",
487
496
  "doctor.diagnostic.install": "इंस्टॉल",
488
497
  "doctor.diagnostic.validation": "सत्यापन",
489
498
  "doctor.diagnostic.skillRoutes": "स्किल रूट",
@@ -680,6 +689,9 @@ export const hiMessages = {
680
689
  "run.error.timedOut": 'कमांड "{intent}" {seconds} सेकंड बाद time out हुई',
681
690
  "run.error.outputLimitExceeded": 'कमांड "{intent}" ने max_output_bytes सीमा पार की: {message}',
682
691
  "run.error.startFailed": 'कमांड "{intent}" शुरू नहीं हो सकी: {message}',
692
+ "run.error.activeLockConflict": "mf run {intent} सक्रिय रन लॉक के कारण रुका है: {detail}",
693
+ "run.error.activeLockConflictDetail": "{lock} सक्रिय intent {intent} से टकराता है (pid {pid})",
694
+ "run.error.activeLockConflictUnknown": "अज्ञात सक्रिय रन लॉक टकराव",
683
695
  "search.help.summary": "mustflow वर्कफ़्लो के लिए स्थानीय SQLite इंडेक्स में खोजें।",
684
696
  "search.help.option.limit": "प्रिंट किए जाने वाले परिणामों की संख्या सेट करें। डिफ़ॉल्ट: 10, अधिकतम: 50",
685
697
  "search.help.option.scope": "इंडेक्स किए गए वर्कफ़्लो डेटा, source anchors, या दोनों चुनें। डिफ़ॉल्ट: workflow",
@@ -771,6 +783,15 @@ export const hiMessages = {
771
783
  "verify.label.planSource": "Plan source",
772
784
  "verify.label.status": "Status",
773
785
  "verify.label.results": "Results",
786
+ "verify.label.completionVerdict": "Completion verdict",
787
+ "verify.label.matched": "Matched",
788
+ "verify.label.ran": "Ran",
789
+ "verify.label.passed": "Passed",
790
+ "verify.label.failed": "Failed",
791
+ "verify.label.skipped": "Skipped",
792
+ "verify.label.parallelism": "Parallelism",
793
+ "verify.label.parallelismNote": "Parallelism note",
794
+ "verify.parallelism.summary": "requested {requested}, effective {effective}, repository max {repositoryMax}, CPU available {cpuAvailable}, mode {mode}",
774
795
  "verify.error.missingReason": "Verification reason missing है",
775
796
  "verify.error.conflictingInputs": "--reason, --from-classification, --from-plan, या --changed में से केवल एक इस्तेमाल करें",
776
797
  "verify.error.writePlanRequiresChanged": "--write-plan के लिए --changed चाहिए",
@@ -810,6 +831,7 @@ export const hiMessages = {
810
831
  "explain.label.publicSurface": "Public surface",
811
832
  "explain.label.validationReasons": "Validation reasons",
812
833
  "explain.label.affectedContracts": "Affected contracts",
834
+ "explain.label.blockedRunPlan": "Blocked run plan",
813
835
  "explain.error.missingTopic": "Explain topic गुम है",
814
836
  "explain.error.missingCommand": "Command intent गुम है",
815
837
  "explain.error.missingSkill": "Skill id गुम है",
@@ -20,6 +20,10 @@ export const koMessages = {
20
20
  "value.yes": "예",
21
21
  "value.no": "아니요",
22
22
  "value.none": "없음",
23
+ "value.present": "있음",
24
+ "value.missing": "없음",
25
+ "value.passed": "통과",
26
+ "value.failed": "실패",
23
27
  "command.adapters.summary": "어댑터 파일을 만들지 않고 호스트 호환성을 확인합니다",
24
28
  "command.init.summary": "기본 mustflow 에이전트 워크플로우를 복사합니다",
25
29
  "command.check.summary": "mustflow 파일을 검사합니다",
@@ -54,6 +58,7 @@ export const koMessages = {
54
58
  "check.help.exit.fail": "검증 실패 또는 잘못된 입력이 제공되었습니다",
55
59
  "check.result.passed": "mustflow 검사 통과",
56
60
  "check.result.strictPassed": "mustflow 엄격 검사 통과",
61
+ "check.result.failed": "mustflow 검사 실패: 문제 {count}개를 찾았습니다.",
57
62
  "contractLint.help.summary": ".mustflow/config/commands.toml의 명령 계약 오류와 경고를 확인합니다.",
58
63
  "contractLint.help.option.coverage": "변경 분류 이유에 대한 required_after 연결 상태도 보고합니다",
59
64
  "contractLint.help.option.suggest": "package.json, Makefile, justfile에서 실행 불가 후보 조각을 제안합니다",
@@ -484,6 +489,10 @@ export const koMessages = {
484
489
  "doctor.section.issueList": "문제 목록:",
485
490
  "doctor.section.suggestedCommands": "추천 명령:",
486
491
  "doctor.actionLabel": "명령",
492
+ "doctor.status.ok": "정상",
493
+ "doctor.status.warn": "주의",
494
+ "doctor.status.fail": "실패",
495
+ "doctor.status.info": "정보",
487
496
  "doctor.diagnostic.install": "설치",
488
497
  "doctor.diagnostic.validation": "검증",
489
498
  "doctor.diagnostic.skillRoutes": "스킬 라우팅",
@@ -680,6 +689,9 @@ export const koMessages = {
680
689
  "run.error.timedOut": '명령 "{intent}"가 {seconds}초 뒤 시간 초과되었습니다',
681
690
  "run.error.outputLimitExceeded": '명령 "{intent}"가 max_output_bytes 제한을 넘었습니다: {message}',
682
691
  "run.error.startFailed": '명령 "{intent}"를 시작하지 못했습니다: {message}',
692
+ "run.error.activeLockConflict": "mf run {intent} 실행이 활성 실행 잠금 때문에 차단되었습니다: {detail}",
693
+ "run.error.activeLockConflictDetail": "{lock} 잠금이 실행 중인 {intent} 의도와 충돌합니다(pid {pid})",
694
+ "run.error.activeLockConflictUnknown": "알 수 없는 활성 실행 잠금 충돌",
683
695
  "search.help.summary": "로컬 SQLite 색인에서 mustflow 워크플로우를 검색합니다.",
684
696
  "search.help.option.limit": "출력할 검색 결과 수를 설정합니다. 기본값: 10, 최대: 50",
685
697
  "search.help.option.scope": "색인된 워크플로 데이터, 소스 앵커, 또는 둘 다를 선택합니다. 기본값: workflow",
@@ -771,6 +783,15 @@ export const koMessages = {
771
783
  "verify.label.planSource": "계획 원본",
772
784
  "verify.label.status": "상태",
773
785
  "verify.label.results": "결과",
786
+ "verify.label.completionVerdict": "완료 판정",
787
+ "verify.label.matched": "일치",
788
+ "verify.label.ran": "실행",
789
+ "verify.label.passed": "통과",
790
+ "verify.label.failed": "실패",
791
+ "verify.label.skipped": "생략",
792
+ "verify.label.parallelism": "병렬 실행",
793
+ "verify.label.parallelismNote": "병렬 실행 참고",
794
+ "verify.parallelism.summary": "요청 {requested}, 실제 {effective}, 저장소 상한 {repositoryMax}, 사용 가능 CPU {cpuAvailable}, 모드 {mode}",
774
795
  "verify.error.missingReason": "검증 이유가 없습니다",
775
796
  "verify.error.conflictingInputs": "--reason, --from-classification, --from-plan, --changed 중 하나만 사용하세요",
776
797
  "verify.error.writePlanRequiresChanged": "--write-plan에는 --changed가 필요합니다",
@@ -810,6 +831,7 @@ export const koMessages = {
810
831
  "explain.label.publicSurface": "공개 표면",
811
832
  "explain.label.validationReasons": "검증 이유",
812
833
  "explain.label.affectedContracts": "영향받는 계약",
834
+ "explain.label.blockedRunPlan": "차단된 실행 계획",
813
835
  "explain.error.missingTopic": "설명할 주제가 없습니다",
814
836
  "explain.error.missingCommand": "설명할 명령 의도가 없습니다",
815
837
  "explain.error.missingSkill": "설명할 스킬 식별자가 없습니다",
@@ -20,6 +20,10 @@ export const zhMessages = {
20
20
  "value.yes": "是",
21
21
  "value.no": "否",
22
22
  "value.none": "无",
23
+ "value.present": "存在",
24
+ "value.missing": "缺失",
25
+ "value.passed": "通过",
26
+ "value.failed": "失败",
23
27
  "command.adapters.summary": "不生成适配器文件,检查宿主兼容性",
24
28
  "command.init.summary": "复制默认的 mustflow 代理工作流",
25
29
  "command.check.summary": "验证 mustflow 文件",
@@ -54,6 +58,7 @@ export const zhMessages = {
54
58
  "check.help.exit.fail": "验证失败,或命令收到了无效输入",
55
59
  "check.result.passed": "mustflow 检查已通过",
56
60
  "check.result.strictPassed": "mustflow 严格检查已通过",
61
+ "check.result.failed": "mustflow 检查失败:发现 {count} 个问题。",
57
62
  "contractLint.help.summary": "检查 .mustflow/config/commands.toml 中的命令契约错误和警告。",
58
63
  "contractLint.help.option.coverage": "同时报告变更分类原因的 required_after 覆盖情况",
59
64
  "contractLint.help.option.suggest": "从 package.json、Makefile 或 justfile 建议不可运行的 intent 片段",
@@ -484,6 +489,10 @@ export const zhMessages = {
484
489
  "doctor.section.issueList": "问题列表:",
485
490
  "doctor.section.suggestedCommands": "建议命令:",
486
491
  "doctor.actionLabel": "运行",
492
+ "doctor.status.ok": "正常",
493
+ "doctor.status.warn": "警告",
494
+ "doctor.status.fail": "失败",
495
+ "doctor.status.info": "信息",
487
496
  "doctor.diagnostic.install": "安装",
488
497
  "doctor.diagnostic.validation": "验证",
489
498
  "doctor.diagnostic.skillRoutes": "技能路由",
@@ -680,6 +689,9 @@ export const zhMessages = {
680
689
  "run.error.timedOut": '命令 "{intent}" 在 {seconds} 秒后超时',
681
690
  "run.error.outputLimitExceeded": '命令 "{intent}" 超过 max_output_bytes:{message}',
682
691
  "run.error.startFailed": '命令 "{intent}" 启动失败:{message}',
692
+ "run.error.activeLockConflict": "mf run {intent} 被活动运行锁阻止:{detail}",
693
+ "run.error.activeLockConflictDetail": "{lock} 与活动 intent {intent} 冲突(pid {pid})",
694
+ "run.error.activeLockConflictUnknown": "未知的活动运行锁冲突",
683
695
  "search.help.summary": "搜索本地 SQLite 索引中的 mustflow 工作流。",
684
696
  "search.help.option.limit": "设置要输出的结果数量。默认值:10,最大:50",
685
697
  "search.help.option.scope": "选择已索引的工作流数据、源码 anchors,或两者。默认值:workflow",
@@ -771,6 +783,15 @@ export const zhMessages = {
771
783
  "verify.label.planSource": "计划来源",
772
784
  "verify.label.status": "状态",
773
785
  "verify.label.results": "结果",
786
+ "verify.label.completionVerdict": "完成判定",
787
+ "verify.label.matched": "匹配",
788
+ "verify.label.ran": "已运行",
789
+ "verify.label.passed": "已通过",
790
+ "verify.label.failed": "已失败",
791
+ "verify.label.skipped": "已跳过",
792
+ "verify.label.parallelism": "并行度",
793
+ "verify.label.parallelismNote": "并行度说明",
794
+ "verify.parallelism.summary": "请求 {requested},实际 {effective},仓库上限 {repositoryMax},可用 CPU {cpuAvailable},模式 {mode}",
774
795
  "verify.error.missingReason": "缺少验证原因",
775
796
  "verify.error.conflictingInputs": "只能使用 --reason、--from-classification、--from-plan 或 --changed 其中之一",
776
797
  "verify.error.writePlanRequiresChanged": "--write-plan 需要 --changed",
@@ -810,6 +831,7 @@ export const zhMessages = {
810
831
  "explain.label.publicSurface": "公共表面",
811
832
  "explain.label.validationReasons": "验证原因",
812
833
  "explain.label.affectedContracts": "受影响契约",
834
+ "explain.label.blockedRunPlan": "被阻止的运行计划",
813
835
  "explain.error.missingTopic": "缺少 explain 主题",
814
836
  "explain.error.missingCommand": "缺少命令意图",
815
837
  "explain.error.missingSkill": "缺少技能标识",
package/dist/cli/index.js CHANGED
@@ -35,6 +35,7 @@ function getTopLevelHelp(lang) {
35
35
  'mf map --write',
36
36
  'mf search mustflow_check',
37
37
  'mf explain authority AGENTS.md',
38
+ 'mf explain --why-blocked test',
38
39
  'mf impact --changed',
39
40
  'mf upgrade --dry-run',
40
41
  'mf verify --changed --plan-only --json',
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  export { ensureFileTargetInsideWithoutSymlinks, ensureInside, ensureInsideWithoutSymlinks, readFileInsideWithoutSymlinks, readUtf8FileInsideWithoutSymlinks, writeFileInsideWithoutSymlinks, writeUtf8FileInsideWithoutSymlinks, } from '../../core/safe-filesystem.js';
4
4
  import { readFileInsideWithoutSymlinks, writeFileInsideWithoutSymlinks, } from '../../core/safe-filesystem.js';
5
5
  export function toPosixPath(value) {
6
- return value.split(path.sep).join('/');
6
+ return value.replace(/\\/gu, '/');
7
7
  }
8
8
  export function copyFileInsideWithoutSymlinks(sourceParentPath, sourcePath, targetParentPath, targetPath) {
9
9
  const content = readFileInsideWithoutSymlinks(sourceParentPath, sourcePath);
@@ -2311,10 +2311,10 @@ export async function searchLocalIndex(projectRoot, query, options = {}) {
2311
2311
  catch {
2312
2312
  return searchLocalWorkflowFilesDirectly(projectRoot, databasePath, normalizedQuery, limit, scope);
2313
2313
  }
2314
- const cacheLayers = readCacheLayerSets(projectRoot);
2315
2314
  let capabilities = searchCapabilities(false);
2316
2315
  const results = [];
2317
2316
  try {
2317
+ const cacheLayers = readCacheLayerSets(projectRoot);
2318
2318
  const stalePaths = getStalePaths(projectRoot, database);
2319
2319
  capabilities = readStoredSearchCapabilities(database);
2320
2320
  const indexedMatches = getIndexedSearchMatches(database, normalizedQuery);
@@ -1,3 +1,4 @@
1
+ import path from 'node:path';
1
2
  const DEFAULT_NPM_REGISTRY_URL = 'https://registry.npmjs.org';
2
3
  const DEFAULT_VERSION_CHECK_TIMEOUT_MS = 3_000;
3
4
  const PACKAGE_MANAGER_COMMANDS = [
@@ -37,6 +38,7 @@ const PACKAGE_MANAGER_COMMANDS = [
37
38
  },
38
39
  },
39
40
  ];
41
+ const PACKAGE_MANAGER_IDS = PACKAGE_MANAGER_COMMANDS.map((entry) => entry.id);
40
42
  function isRecord(value) {
41
43
  return typeof value === 'object' && value !== null && !Array.isArray(value);
42
44
  }
@@ -102,23 +104,31 @@ function getTimeoutMs() {
102
104
  const parsed = rawValue ? Number(rawValue) : DEFAULT_VERSION_CHECK_TIMEOUT_MS;
103
105
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : DEFAULT_VERSION_CHECK_TIMEOUT_MS;
104
106
  }
105
- function detectPackageManagerId() {
106
- const signals = [
107
- process.env.npm_config_user_agent,
108
- process.env.npm_execpath,
109
- process.execPath,
110
- process.argv[1],
111
- import.meta.url,
112
- ]
113
- .filter((signal) => typeof signal === 'string' && signal.length > 0)
114
- .map((signal) => signal.toLowerCase());
115
- for (const id of ['bun', 'pnpm', 'yarn', 'deno', 'npm']) {
116
- if (signals.some((signal) => signal.includes(id))) {
107
+ function packageManagerFromUserAgent(userAgent) {
108
+ const firstToken = userAgent?.trim().toLowerCase().split(/\s+/u)[0] ?? '';
109
+ for (const id of PACKAGE_MANAGER_IDS) {
110
+ if (firstToken === id || firstToken.startsWith(`${id}/`)) {
117
111
  return id;
118
112
  }
119
113
  }
120
114
  return null;
121
115
  }
116
+ function packageManagerFromExecutablePath(executablePath) {
117
+ if (!executablePath) {
118
+ return null;
119
+ }
120
+ const normalized = path
121
+ .basename(executablePath)
122
+ .toLowerCase()
123
+ .replace(/\.(?:cmd|ps1|exe|cjs|mjs|js)$/u, '')
124
+ .replace(/-cli$/u, '');
125
+ return PACKAGE_MANAGER_IDS.find((id) => normalized === id) ?? null;
126
+ }
127
+ function detectPackageManagerId() {
128
+ return (packageManagerFromUserAgent(process.env.npm_config_user_agent) ??
129
+ packageManagerFromExecutablePath(process.env.npm_execpath) ??
130
+ packageManagerFromExecutablePath(process.execPath));
131
+ }
122
132
  function getPackageInstallCommands(packageName) {
123
133
  const detectedId = detectPackageManagerId();
124
134
  const commands = [...PACKAGE_MANAGER_COMMANDS];
@@ -195,7 +195,7 @@ function acquireMutex(projectRoot) {
195
195
  const root = activeLockRoot(projectRoot);
196
196
  const mutex = activeLockMutexDirectory(projectRoot);
197
197
  mkdirSync(root, { recursive: true });
198
- const startedAt = Date.now();
198
+ let startedAt = Date.now();
199
199
  while (true) {
200
200
  try {
201
201
  mkdirSync(mutex);
@@ -215,11 +215,13 @@ function acquireMutex(projectRoot) {
215
215
  const staleByAge = Number.isFinite(ownerStartedAt) && Date.now() - ownerStartedAt > LOCK_MUTEX_STALE_MS;
216
216
  if (!isProcessLive(ownerPid) || staleByAge) {
217
217
  rmSync(mutex, { recursive: true, force: true });
218
+ startedAt = Date.now();
218
219
  continue;
219
220
  }
220
221
  }
221
222
  catch {
222
223
  rmSync(mutex, { recursive: true, force: true });
224
+ startedAt = Date.now();
223
225
  continue;
224
226
  }
225
227
  throw new Error('active_run_lock_mutex_busy');
@@ -108,13 +108,14 @@ function listProjectFiles(projectRoot) {
108
108
  walk(projectRoot);
109
109
  return files.sort((left, right) => left.localeCompare(right));
110
110
  }
111
- function matchingSourceFiles(projectRoot, patterns) {
111
+ function matchingSourceFiles(projectRoot, patterns, context) {
112
112
  const safePatterns = patterns.filter((pattern) => !relativePathIsUnsafe(pattern));
113
113
  const matchers = safePatterns.map(globToRegExp);
114
114
  if (matchers.length === 0) {
115
115
  return [];
116
116
  }
117
- return listProjectFiles(projectRoot).filter((filePath) => matchers.some((matcher) => matcher.test(filePath)));
117
+ context.projectFiles ??= listProjectFiles(projectRoot);
118
+ return context.projectFiles.filter((filePath) => matchers.some((matcher) => matcher.test(filePath)));
118
119
  }
119
120
  function evaluatePathExists(projectRoot, declaration, satisfyIntent) {
120
121
  const pathValue = declaration.path;
@@ -158,7 +159,7 @@ function evaluatePathExists(projectRoot, declaration, satisfyIntent) {
158
159
  satisfyIntent,
159
160
  };
160
161
  }
161
- function evaluateArtifactFreshness(projectRoot, declaration, satisfyIntent) {
162
+ function evaluateArtifactFreshness(projectRoot, declaration, satisfyIntent, context) {
162
163
  const artifact = declaration.artifact;
163
164
  if (!artifact || declaration.sources.length === 0) {
164
165
  return {
@@ -200,7 +201,7 @@ function evaluateArtifactFreshness(projectRoot, declaration, satisfyIntent) {
200
201
  satisfyIntent,
201
202
  };
202
203
  }
203
- const sourceFiles = matchingSourceFiles(projectRoot, declaration.sources);
204
+ const sourceFiles = matchingSourceFiles(projectRoot, declaration.sources, context);
204
205
  if (sourceFiles.length === 0) {
205
206
  return {
206
207
  kind: declaration.kind,
@@ -215,9 +216,26 @@ function evaluateArtifactFreshness(projectRoot, declaration, satisfyIntent) {
215
216
  };
216
217
  }
217
218
  const artifactMtime = statSync(artifactPath).mtimeMs;
218
- const newest = sourceFiles
219
- .map((source) => ({ source, mtime: statSync(path.join(projectRoot, ...source.split('/'))).mtimeMs }))
220
- .sort((left, right) => right.mtime - left.mtime)[0];
219
+ let newest = null;
220
+ for (const source of sourceFiles) {
221
+ const mtime = statSync(path.join(projectRoot, ...source.split('/'))).mtimeMs;
222
+ if (!newest || mtime > newest.mtime) {
223
+ newest = { source, mtime };
224
+ }
225
+ }
226
+ if (!newest) {
227
+ return {
228
+ kind: declaration.kind,
229
+ label: declaration.label,
230
+ status: 'unknown',
231
+ detail: 'no readable source files matched the freshness precondition.',
232
+ path: null,
233
+ artifact,
234
+ sources: declaration.sources,
235
+ newestSource: null,
236
+ satisfyIntent,
237
+ };
238
+ }
221
239
  const stale = newest.mtime > artifactMtime;
222
240
  return {
223
241
  kind: declaration.kind,
@@ -238,13 +256,14 @@ export function evaluateCommandPreconditions(projectRoot, contract, intentName)
238
256
  if (!isRecord(intent)) {
239
257
  return [];
240
258
  }
259
+ const context = { projectFiles: null };
241
260
  return readPreconditionDeclarations(intent).map((declaration) => {
242
261
  const satisfyIntent = createSatisfyIntentSummary(contract, declaration.satisfyIntent);
243
262
  if (declaration.kind === 'path_exists') {
244
263
  return evaluatePathExists(projectRoot, declaration, satisfyIntent);
245
264
  }
246
265
  if (declaration.kind === 'artifact_freshness') {
247
- return evaluateArtifactFreshness(projectRoot, declaration, satisfyIntent);
266
+ return evaluateArtifactFreshness(projectRoot, declaration, satisfyIntent, context);
248
267
  }
249
268
  return {
250
269
  kind: declaration.kind,
@@ -276,15 +276,28 @@ function createSamplesFile(samples) {
276
276
  function serialize(value) {
277
277
  return `${JSON.stringify(value, null, 2)}\n`;
278
278
  }
279
+ function serializedHistorySize(samples, today) {
280
+ return (Buffer.byteLength(serialize(createSamplesFile(samples)), 'utf8') +
281
+ Buffer.byteLength(serialize(createSummary(samples, today)), 'utf8'));
282
+ }
279
283
  function enforceSizeLimit(samples, today) {
280
- let pruned = [...samples];
281
- let summary = createSummary(pruned, today);
282
- while (pruned.length > 0 &&
283
- Buffer.byteLength(serialize(createSamplesFile(pruned)), 'utf8') + Buffer.byteLength(serialize(summary), 'utf8') > MAX_TOTAL_BYTES) {
284
- pruned = pruned.slice(1);
285
- summary = createSummary(pruned, today);
284
+ if (serializedHistorySize(samples, today) <= MAX_TOTAL_BYTES) {
285
+ return samples;
286
+ }
287
+ let low = 1;
288
+ let high = samples.length;
289
+ let firstFittingIndex = samples.length;
290
+ while (low <= high) {
291
+ const middle = Math.floor((low + high) / 2);
292
+ const candidate = samples.slice(middle);
293
+ if (serializedHistorySize(candidate, today) <= MAX_TOTAL_BYTES) {
294
+ firstFittingIndex = middle;
295
+ high = middle - 1;
296
+ continue;
297
+ }
298
+ low = middle + 1;
286
299
  }
287
- return pruned;
300
+ return samples.slice(firstFittingIndex);
288
301
  }
289
302
  export function recordRunPerformanceHistory(projectRoot, receipt) {
290
303
  const sample = createSample(receipt);
@@ -146,7 +146,7 @@ export function createValidationRatchetRisks(report, projectRoot) {
146
146
  if (SNAPSHOT_PATH.test(classification.path) && diff.added.length + diff.removed.length >= 20) {
147
147
  addRisk('snapshot_mass_updated', 'medium', classification.path, riskDetail(classification.path, 'changes a large snapshot region; review that the update does not hide a regression.'));
148
148
  }
149
- if (GOLDEN_PATH.test(`${classification.path} `) && diff.added.length + diff.removed.length >= 20) {
149
+ if (GOLDEN_PATH.test(classification.path) && diff.added.length + diff.removed.length >= 20) {
150
150
  addRisk('golden_output_replaced', 'medium', classification.path, riskDetail(classification.path, 'replaces a broad golden or expected-output region; review the behavioral reason.'));
151
151
  }
152
152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustflow",
3
- "version": "2.22.15",
3
+ "version": "2.22.16",
4
4
  "description": "Agent workflow documents and CLI for mustflow repository roots.",
5
5
  "type": "module",
6
6
  "license": "MIT-0",
@@ -360,9 +360,62 @@
360
360
  },
361
361
  "effectGraph": {
362
362
  "$ref": "#/$defs/commandEffectGraph"
363
+ },
364
+ "blockedRunPlan": {
365
+ "$ref": "#/$defs/blockedRunPlan"
363
366
  }
364
367
  }
365
368
  },
369
+ "blockedRunPlan": {
370
+ "type": "object",
371
+ "additionalProperties": false,
372
+ "required": [
373
+ "runnable",
374
+ "reasonCode",
375
+ "detail",
376
+ "status",
377
+ "lifecycle",
378
+ "runPolicy",
379
+ "configuredCwd",
380
+ "timeoutSeconds",
381
+ "mode",
382
+ "writes",
383
+ "suggestedIntentSnippet"
384
+ ],
385
+ "properties": {
386
+ "runnable": { "type": "boolean" },
387
+ "reasonCode": {
388
+ "type": ["string", "null"],
389
+ "enum": [
390
+ "intent_not_table",
391
+ "status_not_configured",
392
+ "lifecycle_not_oneshot",
393
+ "run_policy_not_agent_allowed",
394
+ "stdin_not_closed",
395
+ "missing_timeout",
396
+ "missing_command_source",
397
+ "unsafe_intent_name",
398
+ "blocked_shell_background_pattern",
399
+ "blocked_long_running_command_pattern",
400
+ "cwd_outside_project",
401
+ "max_output_bytes_exceeds_limit",
402
+ null
403
+ ]
404
+ },
405
+ "detail": { "type": ["string", "null"] },
406
+ "status": { "type": ["string", "null"] },
407
+ "lifecycle": { "type": ["string", "null"] },
408
+ "runPolicy": { "type": ["string", "null"] },
409
+ "configuredCwd": { "type": ["string", "null"] },
410
+ "timeoutSeconds": { "type": ["integer", "null"] },
411
+ "mode": { "type": ["string", "null"] },
412
+ "writes": {
413
+ "type": "array",
414
+ "items": { "type": "string" }
415
+ },
416
+ "suggestedIntentSnippet": { "type": ["string", "null"] }
417
+ }
418
+ },
366
419
  "commandEffectGraph": {
367
420
  "type": "object",
368
421
  "additionalProperties": false,
@@ -1,6 +1,6 @@
1
1
  id = "default"
2
2
  name = "default"
3
- version = "2.22.15"
3
+ version = "2.22.16"
4
4
  description = "Minimal workflow for LLM agents to read, edit, and verify their work in a repository."
5
5
  common_root = "common"
6
6
  locales_root = "locales"