mustflow 2.70.0 → 2.74.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +20 -6
  2. package/dist/cli/commands/api.js +17 -0
  3. package/dist/cli/commands/check.js +38 -26
  4. package/dist/cli/commands/doctor.js +17 -5
  5. package/dist/cli/commands/evidence.js +71 -0
  6. package/dist/cli/commands/index.js +24 -9
  7. package/dist/cli/commands/map.js +20 -7
  8. package/dist/cli/commands/run.js +2 -1
  9. package/dist/cli/commands/script-pack.js +124 -0
  10. package/dist/cli/commands/update.js +52 -39
  11. package/dist/cli/commands/verify.js +50 -15
  12. package/dist/cli/commands/workspace.js +2 -0
  13. package/dist/cli/i18n/en.js +38 -0
  14. package/dist/cli/i18n/es.js +38 -0
  15. package/dist/cli/i18n/fr.js +38 -0
  16. package/dist/cli/i18n/hi.js +38 -0
  17. package/dist/cli/i18n/ko.js +38 -0
  18. package/dist/cli/i18n/zh.js +38 -0
  19. package/dist/cli/index.js +1 -0
  20. package/dist/cli/lib/active-command-lock.js +96 -0
  21. package/dist/cli/lib/agent-context.js +179 -10
  22. package/dist/cli/lib/command-registry.js +6 -0
  23. package/dist/cli/lib/dashboard-export.js +1 -0
  24. package/dist/cli/lib/script-pack-registry.js +27 -0
  25. package/dist/cli/script-packs/core-text-budget.js +241 -0
  26. package/dist/core/active-run-locks.js +7 -1
  27. package/dist/core/change-verification.js +10 -0
  28. package/dist/core/completion-verdict.js +14 -1
  29. package/dist/core/complexity-budget.js +206 -0
  30. package/dist/core/conflict-ledger.js +122 -0
  31. package/dist/core/failure-replay-capsule.js +213 -0
  32. package/dist/core/public-json-contracts.js +27 -0
  33. package/dist/core/risk-priced-evidence.js +213 -0
  34. package/dist/core/script-check-result.js +1 -0
  35. package/dist/core/text-budget.js +262 -0
  36. package/dist/core/verification-evidence.js +61 -13
  37. package/package.json +1 -1
  38. package/schemas/README.md +23 -11
  39. package/schemas/change-verification-report.schema.json +29 -0
  40. package/schemas/context-report.schema.json +58 -2
  41. package/schemas/dashboard-export.schema.json +42 -1
  42. package/schemas/diff-risk.schema.json +6 -0
  43. package/schemas/evidence-report.schema.json +45 -0
  44. package/schemas/latest-run-pointer.schema.json +50 -1
  45. package/schemas/script-pack-catalog.schema.json +68 -0
  46. package/schemas/text-budget-report.schema.json +131 -0
  47. package/schemas/verification-plan.schema.json +32 -0
  48. package/schemas/verify-report.schema.json +360 -1
  49. package/schemas/verify-run-manifest.schema.json +50 -1
  50. package/schemas/workspace-verification-plan.schema.json +32 -0
  51. package/templates/default/i18n.toml +2 -2
  52. package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -2
  53. package/templates/default/locales/en/.mustflow/skills/adapter-boundary/SKILL.md +19 -2
  54. package/templates/default/locales/en/.mustflow/skills/routes.toml +1 -1
  55. package/templates/default/manifest.toml +1 -1
package/README.md CHANGED
@@ -123,8 +123,16 @@ mustflow installs and validates an agent workflow for user projects.
123
123
  - Reports host adapter compatibility with `mf adapters status` without generating host-specific files or treating them as command authority.
124
124
  - Classifies changed files, public surfaces, and validation reasons with `mf classify`.
125
125
  - Inspects changed files for quality-gaming patterns such as line stuffing, suppressions, test bypass markers, type escapes, and placeholder implementations with `mf quality check`.
126
- - Prints execution-free verification plans with `mf verify --plan-only --json`, including a machine-readable verification decision graph and read-only local-index lock explanations when available.
126
+ - Prints execution-free verification plans with `mf verify --plan-only --json`, including a risk-priced evidence assessment, machine-readable verification decision graph, and read-only local-index lock explanations when available.
127
+ - Reports a read-only complexity budget in `mf api diff-risk --changed --json`, `mf verify`
128
+ evidence, and dashboard exports so agents justify new dependencies, helper-style surfaces,
129
+ config/schema churn, and broad structural changes before treating added complexity as free.
130
+ - Lists and runs bundled utility scripts through `mf script-pack`, including `core/text-budget` for
131
+ exact file and JSON-field length budgets, so future checks do not sprawl into top-level commands.
132
+ - Prints context trust metadata in `mf context --json` and prompt-cache bundles so agents can distinguish binding instructions, command contracts, contextual hints, generated evidence, and volatile runtime data before using them.
127
133
  - Runs only allowed one-shot commands within a timeout via `mf run <intent>` or `mf verify` when the selected intent is runnable.
134
+ - Records blockers, contradictions, verification gaps, and remaining risks as a structured conflict ledger in verify, evidence, and dashboard reports.
135
+ - Stores bounded failure replay capsules for failed `mf verify` runs so future agents can reproduce the intent, receipt, command fingerprint, and changed-file state without copying raw command output.
128
136
  - Writes command receipts under `.mustflow/state/runs/run-*` and atomically updates `.mustflow/state/runs/latest.json`.
129
137
  - Generates a concise repository navigation map, `REPO_MAP.md`, with `mf map`.
130
138
  - Indexes and searches mustflow docs, skills, skill routes, command rules, command-effect locks, file fingerprints, and opt-in source anchor metadata with SQLite via `mf index` and `mf search`. The local SQLite file is a rebuildable lookup cache, not a memory store, audit log, command transcript store, command-authority source, or source-content database.
@@ -251,7 +259,7 @@ mf run mustflow_update_apply
251
259
  | `mf contract-lint` | Inspect `.mustflow/config/commands.toml` for command-contract errors and warnings without running commands. Add `--suggest` to print non-runnable candidate snippets from existing command files. |
252
260
  | `mf onboard commands` | Suggest review-only command-intent snippets from package.json, Makefile, or justfile without writing files or granting command authority. |
253
261
  | `mf next` | Inspect install state, changed files, verification coverage, and command-contract gaps, then print the next safe mustflow action without running commands. |
254
- | `mf evidence` | Summarize changed-file verification requirements, latest evidence, receipts, remaining risks, and gaps without running commands. |
262
+ | `mf evidence` | Summarize changed-file verification requirements, risk-priced evidence assessment, latest failure replay capsule, conflict ledger, receipts, remaining risks, and gaps without running commands. |
255
263
  | `mf workspace status` | Inspect configured workspace roots and nested repository contract readiness without granting parent-to-child command authority. |
256
264
  | `mf workspace command-catalog` | Aggregate per-repository command intent availability with safe `mf run` entrypoints and no raw command strings. |
257
265
  | `mf workspace verify --changed --plan-only` | Aggregate per-repository changed-file verification plans without running commands or granting parent-to-child command authority. |
@@ -260,7 +268,7 @@ mf run mustflow_update_apply
260
268
  | `mf api command-catalog --json` | Print command intent availability and safe `mf run` entrypoints without exposing raw command strings. |
261
269
  | `mf api verification-plan --changed --json` | Print a stable, read-only verification plan for changed files without executing commands. |
262
270
  | `mf api latest-evidence --json` | Print bounded latest run or verify evidence without raw command output. |
263
- | `mf api diff-risk --changed --json` | Print a compact changed-file risk, verification summary, and read-only residual correction signals. |
271
+ | `mf api diff-risk --changed --json` | Print a compact changed-file risk, verification summary, read-only complexity budget, and residual correction signals. |
264
272
  | `mf api health --json` | Print a compact workspace health report for quick agent gating. |
265
273
  | `mf api locks --json` | Print active `mf run` locks for multi-session coordination. |
266
274
  | `mf api serve --stdio` | Serve the same read-only API reports as newline-delimited JSON responses over stdin/stdout. |
@@ -269,12 +277,15 @@ mf run mustflow_update_apply
269
277
  | `mf docs review comment <path>` | Add multiline review guidance to an existing queue entry. |
270
278
  | `mf docs review approve <path>` | Mark review complete and hide the document from the default queue. |
271
279
  | `mf handoff validate <path>` | Validate a restricted work-item or handoff JSON record without writing files. |
272
- | `mf context --json` | Print read order, command rules, available capabilities, and recent run summary as JSON. |
280
+ | `mf context --json` | Print read order, command rules, context trust metadata, available capabilities, prompt-cache bundles, and recent run summary as JSON. |
273
281
  | `mf skill route` | Resolve compact skill route candidates from task text, paths, and reasons before reading selected skill documents. |
274
282
  | `mf map --stdout` | Print the current mustflow root map to stdout. |
275
283
  | `mf map --write` | Create or update `REPO_MAP.md`. |
276
284
  | `mf quality check` | Inspect changed files for quality-gaming patterns without writing files. |
277
285
  | `mf quality check --all` | Inspect every tracked text file for quality-gaming patterns. |
286
+ | `mf script-pack list` | List bundled script packs and script refs such as `core/text-budget`. |
287
+ | `mf script-pack run core/text-budget check <path...> --max <count>` | Check exact text length budgets for files using grapheme counts by default. |
288
+ | `mf script-pack run core/text-budget check package.json --json-pointer /description --max <count> --json` | Check a JSON string field and print the stable report schema. |
278
289
  | `mf run <intent>` | Run an allowed one-shot command. |
279
290
  | `mf run <intent> --wait` | Wait for conflicting active run locks before executing the command. |
280
291
  | `mf run <intent> --dry-run --json` | Preview whether an intent is runnable and what command metadata would be used, without executing it. |
@@ -300,6 +311,9 @@ mf run mustflow_update_apply
300
311
 
301
312
  Automation and agents should use `--json` output or `mf api serve --stdio` JSONL responses instead of parsing human-facing text. Published JSON Schemas for stable outputs live in `schemas/`.
302
313
 
314
+ `core/text-budget` counts `line` units by splitting text on line breaks; a trailing line break
315
+ therefore contributes an empty final line.
316
+
303
317
  ## Command execution policy
304
318
 
305
319
  Runnable work is declared in `.mustflow/config/commands.toml` so agents do not guess commands.
@@ -318,9 +332,9 @@ Development servers, watch modes, browser UIs, interactive commands, and backgro
318
332
 
319
333
  Command environments remove the project-local `node_modules/.bin` path from `PATH` by default. If an intent needs a project dependency binary such as `eslint`, `tsc`, or `vitest`, declare it through the package manager, for example `npm exec eslint -- ...`, `pnpm exec tsc -- --noEmit`, `bun x eslint ...`, or `yarn exec eslint ...`. `mf check --strict` warns when an agent-runnable intent uses a bare executable name that appears under the project-local `.bin` directory, except for names listed in `defaults.allow_project_local_bin_bare_executables`. `mf run` may resolve those allowed names directly from the local `.bin` directory without exposing every local binary through `PATH`. The installed template allows `mf` and `mustflow` by default. Intent-level `allow_env_inheritance_risks = true` is available when a command intentionally uses `env_policy = "inherit"`.
320
334
 
321
- Use `mf verify --reason <event> --plan-only --json` to inspect matching verification intents, command eligibility, remaining gaps, and missing runnable coverage without executing commands. Use `mf run <intent> --dry-run --json` to inspect one resolved command intent without spawning a process or writing a run receipt. Plan-only verification includes a `decision_graph` that connects changed surfaces, classification reasons, command candidates, eligibility checks, effects, and gaps. When `.mustflow/cache/mustflow.sqlite` is fresh, scheduled entries also include read-only `effectGraph` metadata for write locks and lock conflicts. These graph rows are marked `explanation_only` and never grant command authority; `.mustflow/config/commands.toml` remains the only runnable command source.
335
+ Use `mf verify --reason <event> --plan-only --json` to inspect matching verification intents, command eligibility, risk-priced evidence requirements, remaining gaps, and missing runnable coverage without executing commands. Use `mf run <intent> --dry-run --json` to inspect one resolved command intent without spawning a process or writing a run receipt. Plan-only verification includes a `decision_graph` that connects changed surfaces, classification reasons, command candidates, eligibility checks, effects, and gaps. When `.mustflow/cache/mustflow.sqlite` is fresh, scheduled entries also include read-only `effectGraph` metadata for write locks and lock conflicts. These graph rows are marked `explanation_only` and never grant command authority; `.mustflow/config/commands.toml` remains the only runnable command source.
322
336
 
323
- Each executed command run writes a run record under `.mustflow/state/runs/run-*` and atomically updates `.mustflow/state/runs/latest.json`. The record includes the intent name, working directory, timeout, exit code, timeout status, and the tail of stdout and stderr.
337
+ Each executed command run writes a run record under `.mustflow/state/runs/run-*` and atomically updates `.mustflow/state/runs/latest.json`. The record includes the intent name, working directory, timeout, exit code, timeout status, and the tail of stdout and stderr. `latest.json` is a root-scoped convenience pointer, not session-scoped proof; in multi-agent or multi-terminal workflows, use the per-run `receipt_path` or `mf run <intent> --json` output as the evidence for a specific run.
324
338
 
325
339
  ## Language and profiles
326
340
 
@@ -7,6 +7,7 @@ import { runApiServe } from './api/serve.js';
7
7
  import { createRecommendedNextCommands } from './api/workspace-recommendations.js';
8
8
  import { listActiveRunLocks } from '../../core/active-run-locks.js';
9
9
  import { createChangeVerificationReport, } from '../../core/change-verification.js';
10
+ import { createComplexityBudgetReport, } from '../../core/complexity-budget.js';
10
11
  import { readUtf8FileInsideWithoutSymlinks } from '../../core/safe-filesystem.js';
11
12
  import { createVerificationPlanId } from '../../core/verification-plan-id.js';
12
13
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
@@ -422,6 +423,7 @@ function createUnavailableVerificationPlanOutput(mustflowRoot, classification, i
422
423
  requirements: [],
423
424
  candidates: [],
424
425
  gaps: [],
426
+ risk_assessment: null,
425
427
  schedule: null,
426
428
  test_selection: null,
427
429
  execution_policy: createVerificationPlanExecutionPolicy(),
@@ -479,6 +481,7 @@ function createVerificationPlanOutput() {
479
481
  surfaces: gap.surfaces,
480
482
  detail: gap.detail,
481
483
  })),
484
+ risk_assessment: report.risk_assessment,
482
485
  schedule: toVerificationPlanSchedule(report),
483
486
  test_selection: toVerificationPlanTestSelection(report),
484
487
  execution_policy: createVerificationPlanExecutionPolicy(),
@@ -614,6 +617,14 @@ function getRiskLevel(classification, report) {
614
617
  if (classification.summary.fileCount === 0) {
615
618
  return 'none';
616
619
  }
620
+ if (report.risk_assessment.level === 'critical') {
621
+ return 'high';
622
+ }
623
+ if (report.risk_assessment.level === 'high' ||
624
+ report.risk_assessment.level === 'medium' ||
625
+ report.risk_assessment.level === 'low') {
626
+ return report.risk_assessment.level;
627
+ }
617
628
  const reasons = new Set(classification.summary.validationReasons);
618
629
  if (report.gaps.length > 0 ||
619
630
  classification.summary.publicSurfaceCount > 0 ||
@@ -794,6 +805,7 @@ function createDiffRiskOutput() {
794
805
  update_policies: [],
795
806
  drift_checks: [],
796
807
  required_verification: [],
808
+ complexity_budget: null,
797
809
  residual_corrections: createResidualCorrections(mustflowRoot, null),
798
810
  gap_count: 0,
799
811
  gaps: [],
@@ -820,6 +832,10 @@ function createDiffRiskOutput() {
820
832
  }
821
833
  const requiredVerification = report ? report.schedule.entries.map((entry) => entry.intent) : [];
822
834
  const verificationPlanId = report && contract ? createVerificationPlanId(report, contract) : null;
835
+ const complexityBudget = createComplexityBudgetReport({
836
+ files: classification.files,
837
+ summary: classification.summary,
838
+ });
823
839
  return {
824
840
  schema_version: API_DIFF_RISK_SCHEMA_VERSION,
825
841
  command: 'api diff-risk',
@@ -834,6 +850,7 @@ function createDiffRiskOutput() {
834
850
  update_policies: classification.summary.updatePolicies,
835
851
  drift_checks: classification.summary.driftChecks,
836
852
  required_verification: requiredVerification,
853
+ complexity_budget: complexityBudget,
837
854
  residual_corrections: createResidualCorrections(mustflowRoot, verificationPlanId),
838
855
  gap_count: report?.gaps.length ?? 0,
839
856
  gaps: report?.gaps.map((gap) => ({
@@ -1,4 +1,5 @@
1
1
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
2
+ import { acquireActiveCommandLock, GENERATED_SURFACE_READ_EFFECTS, reportActiveCommandLockConflict, } from '../lib/active-command-lock.js';
2
3
  import { t } from '../lib/i18n.js';
3
4
  import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
4
5
  import { resolveMustflowRoot } from '../lib/project-root.js';
@@ -43,36 +44,47 @@ export function runCheck(args, reporter, lang = 'en') {
43
44
  return 1;
44
45
  }
45
46
  const strict = hasParsedCliOption(options, '--strict');
46
- const report = checkMustflowProjectReport(resolveMustflowRoot(), { strict });
47
- const issues = report.issues;
48
- const warnings = report.warnings;
49
- const ok = issues.length === 0;
50
- if (hasParsedCliOption(options, '--json')) {
51
- reporter.stdout(JSON.stringify({
52
- ok,
53
- strict,
54
- issueCount: issues.length,
55
- issues,
56
- warningCount: warnings.length,
57
- warnings,
58
- issueDetails: report.issueDetails,
59
- }, null, 2));
60
- return ok ? 0 : 1;
47
+ const projectRoot = resolveMustflowRoot();
48
+ const activeLock = acquireActiveCommandLock(projectRoot, 'mf check', GENERATED_SURFACE_READ_EFFECTS);
49
+ if (!activeLock.ok) {
50
+ reportActiveCommandLockConflict(reporter, 'mf check', activeLock.conflicts, 'mf check --help', lang);
51
+ return 1;
61
52
  }
62
- if (ok) {
63
- for (const warning of warnings) {
64
- reporter.stderr(warning);
53
+ try {
54
+ const report = checkMustflowProjectReport(projectRoot, { strict });
55
+ const issues = report.issues;
56
+ const warnings = report.warnings;
57
+ const ok = issues.length === 0;
58
+ if (hasParsedCliOption(options, '--json')) {
59
+ reporter.stdout(JSON.stringify({
60
+ ok,
61
+ strict,
62
+ issueCount: issues.length,
63
+ issues,
64
+ warningCount: warnings.length,
65
+ warnings,
66
+ issueDetails: report.issueDetails,
67
+ }, null, 2));
68
+ return ok ? 0 : 1;
65
69
  }
66
- if (strict) {
67
- reporter.stdout(t(lang, 'check.result.strictPassed'));
70
+ if (ok) {
71
+ for (const warning of warnings) {
72
+ reporter.stderr(warning);
73
+ }
74
+ if (strict) {
75
+ reporter.stdout(t(lang, 'check.result.strictPassed'));
76
+ return 0;
77
+ }
78
+ reporter.stdout(t(lang, 'check.result.passed'));
68
79
  return 0;
69
80
  }
70
- reporter.stdout(t(lang, 'check.result.passed'));
71
- return 0;
81
+ for (const issue of issues) {
82
+ reporter.stderr(issue);
83
+ }
84
+ reporter.stderr(t(lang, 'check.result.failed', { count: issues.length }));
85
+ return 1;
72
86
  }
73
- for (const issue of issues) {
74
- reporter.stderr(issue);
87
+ finally {
88
+ activeLock.handle.release();
75
89
  }
76
- reporter.stderr(t(lang, 'check.result.failed', { count: issues.length }));
77
- return 1;
78
90
  }
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
4
+ import { acquireActiveCommandLock, GENERATED_SURFACE_READ_EFFECTS, reportActiveCommandLockConflict, } from '../lib/active-command-lock.js';
4
5
  import { getAgentContext, } from '../lib/agent-context.js';
5
6
  import { t } from '../lib/i18n.js';
6
7
  import { getLocalIndexDatabasePath } from '../lib/local-index.js';
@@ -278,11 +279,22 @@ export function runDoctor(args, reporter, lang = 'en') {
278
279
  printUsageError(reporter, formatCliOptionParseError(options.error, lang), 'mf doctor --help', getDoctorHelp(lang), lang);
279
280
  return 1;
280
281
  }
281
- const output = createDoctorOutput(hasParsedCliOption(options, '--strict'));
282
- if (hasParsedCliOption(options, '--json')) {
283
- reporter.stdout(JSON.stringify(output, null, 2));
282
+ const projectRoot = resolveMustflowRoot();
283
+ const activeLock = acquireActiveCommandLock(projectRoot, 'mf doctor', GENERATED_SURFACE_READ_EFFECTS);
284
+ if (!activeLock.ok) {
285
+ reportActiveCommandLockConflict(reporter, 'mf doctor', activeLock.conflicts, 'mf doctor --help', lang);
286
+ return 1;
287
+ }
288
+ try {
289
+ const output = createDoctorOutput(hasParsedCliOption(options, '--strict'));
290
+ if (hasParsedCliOption(options, '--json')) {
291
+ reporter.stdout(JSON.stringify(output, null, 2));
292
+ return output.ok ? 0 : 1;
293
+ }
294
+ reporter.stdout(renderDoctorOutput(output, lang));
284
295
  return output.ok ? 0 : 1;
285
296
  }
286
- reporter.stdout(renderDoctorOutput(output, lang));
287
- return output.ok ? 0 : 1;
297
+ finally {
298
+ activeLock.handle.release();
299
+ }
288
300
  }
@@ -234,6 +234,7 @@ function createPlanFromReport(report, contract, classification, latest) {
234
234
  changed_files: classification.files,
235
235
  validation_reasons: classification.summary.validationReasons,
236
236
  selected_intents: report.schedule.entries.map((entry) => entry.intent),
237
+ risk_assessment: report.risk_assessment,
237
238
  requirement_count: requirements.length,
238
239
  gap_count: gaps.length,
239
240
  requirements,
@@ -278,6 +279,7 @@ function unavailablePlan(source, error, classification = null) {
278
279
  changed_files: classification?.files ?? [],
279
280
  validation_reasons: classification?.summary.validationReasons ?? [],
280
281
  selected_intents: [],
282
+ risk_assessment: fallbackRiskAssessment('Plan unavailable; risk assessment could not be computed.'),
281
283
  requirement_count: 0,
282
284
  gap_count: 0,
283
285
  requirements: [],
@@ -304,6 +306,7 @@ function createPlanFromFile(mustflowRoot, planPath, latest) {
304
306
  const schedule = isRecord(parsed.schedule) ? parsed.schedule : null;
305
307
  const testSelection = isRecord(parsed.test_selection) ? parsed.test_selection : null;
306
308
  const classification = isRecord(parsed.classification) ? parsed.classification : null;
309
+ const riskAssessment = readRiskAssessment(parsed.risk_assessment);
307
310
  const selectedIntents = schedule
308
311
  ? uniqueSorted([
309
312
  ...readStringArrayField(schedule, 'selected_intents'),
@@ -324,6 +327,7 @@ function createPlanFromFile(mustflowRoot, planPath, latest) {
324
327
  changed_files: classification ? readStringArrayField(classification, 'files') : readStringArrayField(parsed, 'files'),
325
328
  validation_reasons: classification ? readStringArrayField(classification, 'validation_reasons') : [],
326
329
  selected_intents: selectedIntents,
330
+ risk_assessment: riskAssessment ?? fallbackRiskAssessment('Plan file does not include risk_assessment.'),
327
331
  requirement_count: requirementReports.length,
328
332
  gap_count: gaps.length,
329
333
  requirements: requirementReports,
@@ -371,6 +375,62 @@ function readLatestRequirement(record) {
371
375
  skipped_intents: readStringArrayField(record, 'skipped_intents'),
372
376
  };
373
377
  }
378
+ function isRiskLevel(value) {
379
+ return value === 'low' || value === 'medium' || value === 'high' || value === 'critical';
380
+ }
381
+ function readRiskAssessment(value) {
382
+ if (!isRecord(value)) {
383
+ return null;
384
+ }
385
+ const level = readString(value, 'level');
386
+ if (value.schema_version !== '1' || value.source !== 'change_classification_and_command_contract' || !isRiskLevel(level)) {
387
+ return null;
388
+ }
389
+ return {
390
+ schema_version: '1',
391
+ source: 'change_classification_and_command_contract',
392
+ level,
393
+ reasons: readStringArrayField(value, 'reasons'),
394
+ required_evidence: readStringArrayField(value, 'required_evidence'),
395
+ blocking_gaps: readStringArrayField(value, 'blocking_gaps'),
396
+ rollback_required: readBooleanField(value, 'rollback_required') ?? false,
397
+ human_approval_required: readBooleanField(value, 'human_approval_required') ?? false,
398
+ manual_review_required: readBooleanField(value, 'manual_review_required') ?? false,
399
+ };
400
+ }
401
+ function readFailureReplayCapsule(value) {
402
+ if (!isRecord(value)) {
403
+ return null;
404
+ }
405
+ if (value.schema_version !== '1' ||
406
+ value.source !== 'mf_verify_failure' ||
407
+ value.authority !== 'replay_supporting_evidence') {
408
+ return null;
409
+ }
410
+ return value;
411
+ }
412
+ function readConflictLedger(value) {
413
+ if (!isRecord(value)) {
414
+ return null;
415
+ }
416
+ if (value.schema_version !== '1' || value.source !== 'verification_evidence_model' || !Array.isArray(value.items)) {
417
+ return null;
418
+ }
419
+ return value;
420
+ }
421
+ function fallbackRiskAssessment(reason) {
422
+ return {
423
+ schema_version: '1',
424
+ source: 'change_classification_and_command_contract',
425
+ level: 'low',
426
+ reasons: [reason],
427
+ required_evidence: ['changed_file_review'],
428
+ blocking_gaps: [],
429
+ rollback_required: false,
430
+ human_approval_required: false,
431
+ manual_review_required: false,
432
+ };
433
+ }
374
434
  function createLatestReport(mustflowRoot, expectedPlanId, requested) {
375
435
  if (!requested) {
376
436
  return latestNotRequested();
@@ -392,6 +452,11 @@ function createLatestReport(mustflowRoot, expectedPlanId, requested) {
392
452
  }
393
453
  const evidenceModel = isRecord(parsed.evidence_model) ? parsed.evidence_model : null;
394
454
  const completionVerdict = isRecord(parsed.completion_verdict) ? parsed.completion_verdict : null;
455
+ const latestRiskAssessment = readRiskAssessment(parsed.risk_assessment) ?? (evidenceModel ? readRiskAssessment(evidenceModel.risk_assessment) : null);
456
+ const failureReplayCapsule = readFailureReplayCapsule(parsed.failure_replay_capsule) ??
457
+ (evidenceModel ? readFailureReplayCapsule(evidenceModel.failure_replay_capsule) : null);
458
+ const conflictLedger = readConflictLedger(parsed.conflict_ledger) ??
459
+ (evidenceModel ? readConflictLedger(evidenceModel.conflict_ledger) : null);
395
460
  const requirements = evidenceModel ? recordArray(evidenceModel.requirements).map(readLatestRequirement) : [];
396
461
  const receipts = evidenceModel ? recordArray(evidenceModel.receipts).map(readLatestReceipt) : [];
397
462
  const skippedChecks = evidenceModel ? recordArray(evidenceModel.skipped_checks).map(toGapReport) : [];
@@ -408,6 +473,9 @@ function createLatestReport(mustflowRoot, expectedPlanId, requested) {
408
473
  receipt_count: receipts.length,
409
474
  skipped_check_count: skippedChecks.length,
410
475
  remaining_risk_count: remainingRisks.length,
476
+ risk_assessment: latestRiskAssessment,
477
+ failure_replay_capsule: failureReplayCapsule,
478
+ conflict_ledger: conflictLedger,
411
479
  requirements,
412
480
  receipts,
413
481
  skipped_checks: skippedChecks,
@@ -428,6 +496,9 @@ function latestEmpty(status) {
428
496
  receipt_count: null,
429
497
  skipped_check_count: 0,
430
498
  remaining_risk_count: 0,
499
+ risk_assessment: null,
500
+ failure_replay_capsule: null,
501
+ conflict_ledger: null,
431
502
  requirements: [],
432
503
  receipts: [],
433
504
  skipped_checks: [],
@@ -1,4 +1,5 @@
1
1
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
2
+ import { acquireActiveCommandLock, LOCAL_INDEX_WRITE_EFFECTS, reportActiveCommandLockConflict, } from '../lib/active-command-lock.js';
2
3
  import { t } from '../lib/i18n.js';
3
4
  import { createLocalIndex } from '../lib/local-index.js';
4
5
  import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
@@ -83,15 +84,29 @@ export async function runIndex(args, reporter, lang = 'en') {
83
84
  printUsageError(reporter, formatCliOptionParseError(parsed.error, lang), 'mf index --help', getIndexHelp(lang), lang);
84
85
  return 1;
85
86
  }
86
- const result = await createLocalIndex(resolveMustflowRoot(), {
87
- dryRun: hasParsedCliOption(parsed, '--dry-run'),
88
- includeSource: hasParsedCliOption(parsed, '--source'),
89
- incremental: hasParsedCliOption(parsed, '--incremental'),
90
- });
91
- if (hasParsedCliOption(parsed, '--json')) {
92
- reporter.stdout(JSON.stringify(result, null, 2));
87
+ const dryRun = hasParsedCliOption(parsed, '--dry-run');
88
+ const projectRoot = resolveMustflowRoot();
89
+ const activeLock = dryRun ? null : acquireActiveCommandLock(projectRoot, 'mf index', LOCAL_INDEX_WRITE_EFFECTS);
90
+ if (activeLock && !activeLock.ok) {
91
+ reportActiveCommandLockConflict(reporter, 'mf index', activeLock.conflicts, 'mf index --help', lang);
92
+ return 1;
93
+ }
94
+ try {
95
+ const result = await createLocalIndex(projectRoot, {
96
+ dryRun,
97
+ includeSource: hasParsedCliOption(parsed, '--source'),
98
+ incremental: hasParsedCliOption(parsed, '--incremental'),
99
+ });
100
+ if (hasParsedCliOption(parsed, '--json')) {
101
+ reporter.stdout(JSON.stringify(result, null, 2));
102
+ return 0;
103
+ }
104
+ reporter.stdout(renderIndexSummary(result, lang));
93
105
  return 0;
94
106
  }
95
- reporter.stdout(renderIndexSummary(result, lang));
96
- return 0;
107
+ finally {
108
+ if (activeLock?.ok) {
109
+ activeLock.handle.release();
110
+ }
111
+ }
97
112
  }
@@ -1,5 +1,6 @@
1
1
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
2
2
  import { t } from '../lib/i18n.js';
3
+ import { acquireActiveCommandLock, REPO_MAP_WRITE_EFFECTS, reportActiveCommandLockConflict, } from '../lib/active-command-lock.js';
3
4
  import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
4
5
  import { resolveMustflowRoot } from '../lib/project-root.js';
5
6
  import { generateRepoMap, writeRepoMap } from '../lib/repo-map.js';
@@ -71,13 +72,25 @@ export function runMap(args, reporter, lang = 'en') {
71
72
  shouldPrint = true;
72
73
  }
73
74
  const projectRoot = resolveMustflowRoot();
74
- const content = generateRepoMap(projectRoot, { depth, includeNested });
75
- if (shouldWrite) {
76
- writeRepoMap(projectRoot, content);
77
- reporter.stdout(t(lang, 'map.wrote'));
75
+ const activeLock = shouldWrite ? acquireActiveCommandLock(projectRoot, 'mf map --write', REPO_MAP_WRITE_EFFECTS) : null;
76
+ if (activeLock && !activeLock.ok) {
77
+ reportActiveCommandLockConflict(reporter, 'mf map --write', activeLock.conflicts, 'mf map --help', lang);
78
+ return 1;
79
+ }
80
+ try {
81
+ const content = generateRepoMap(projectRoot, { depth, includeNested });
82
+ if (shouldWrite) {
83
+ writeRepoMap(projectRoot, content);
84
+ reporter.stdout(t(lang, 'map.wrote'));
85
+ }
86
+ if (shouldPrint) {
87
+ reporter.stdout(content);
88
+ }
89
+ return 0;
78
90
  }
79
- if (shouldPrint) {
80
- reporter.stdout(content);
91
+ finally {
92
+ if (activeLock?.ok) {
93
+ activeLock.handle.release();
94
+ }
81
95
  }
82
- return 0;
83
96
  }
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { performance } from 'node:perf_hooks';
3
- import { acquireActiveRunLock } from '../../core/active-run-locks.js';
3
+ import { ACTIVE_RUN_LOCK_ID_ENV, acquireActiveRunLock } from '../../core/active-run-locks.js';
4
4
  import { createCommandEnv } from '../../core/command-env.js';
5
5
  import { createCorrelationId } from '../../core/correlation-id.js';
6
6
  import { printUsageError, renderCliError, renderHelp } from '../lib/cli-output.js';
@@ -360,6 +360,7 @@ export async function runRun(args, reporter, lang = 'en', options = {}) {
360
360
  try {
361
361
  const runReceiptPolicy = profiler.measure('retention_policy', () => resolveRunReceiptRetentionPolicy(readMustflowConfigIfExists(projectRoot)));
362
362
  const env = profiler.measure('environment', () => createCommandEnv(projectRoot, { policy: plan.envPolicy, allowlist: plan.envAllowlist }));
363
+ env[ACTIVE_RUN_LOCK_ID_ENV] = activeRunLock.handle.record.run_id;
363
364
  const writeTracker = profiler.measure('write_drift_before', () => startRunWriteTracking(projectRoot, contract, intentName, {
364
365
  additionalDeclaredPaths: options.additionalDeclaredWritePaths,
365
366
  env,
@@ -0,0 +1,124 @@
1
+ import { printUsageError, renderHelp } from '../lib/cli-output.js';
2
+ import { t } from '../lib/i18n.js';
3
+ import { formatCliOptionParseError, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
4
+ import { resolveMustflowRoot } from '../lib/project-root.js';
5
+ import { findScriptPackScript, SCRIPT_PACKS, } from '../lib/script-pack-registry.js';
6
+ const SCRIPT_PACK_LIST_OPTIONS = [{ name: '--json', kind: 'boolean' }];
7
+ function createCatalogReport(mustflowRoot, packs) {
8
+ return {
9
+ schema_version: '1',
10
+ command: 'script-pack',
11
+ action: 'list',
12
+ ok: true,
13
+ mustflow_root: mustflowRoot,
14
+ packs: packs.map((pack) => ({
15
+ id: pack.id,
16
+ summary_key: pack.summaryKey,
17
+ scripts: pack.scripts.map((script) => ({
18
+ id: script.id,
19
+ ref: script.ref,
20
+ usage: script.usage,
21
+ summary_key: script.summaryKey,
22
+ actions: script.actions,
23
+ report_schema_file: script.reportSchemaFile,
24
+ })),
25
+ })),
26
+ issues: [],
27
+ };
28
+ }
29
+ export function getScriptPackHelp(lang = 'en') {
30
+ return renderHelp({
31
+ usage: 'mf script-pack <list|run> [options]',
32
+ summary: t(lang, 'scriptPack.help.summary'),
33
+ options: [
34
+ { label: '--json', description: t(lang, 'cli.option.json') },
35
+ { label: '-h, --help', description: t(lang, 'cli.option.help') },
36
+ ],
37
+ examples: [
38
+ 'mf script-pack list',
39
+ 'mf script-pack list --json',
40
+ 'mf script-pack run core/text-budget check README.md --max 5000',
41
+ 'mf script-pack run core/text-budget --help',
42
+ ],
43
+ exitCodes: [
44
+ { label: '0', description: t(lang, 'scriptPack.help.exit.ok') },
45
+ { label: '1', description: t(lang, 'scriptPack.help.exit.fail') },
46
+ ],
47
+ }, lang);
48
+ }
49
+ function renderCatalogSummary(report, lang) {
50
+ const lines = [t(lang, 'scriptPack.title')];
51
+ for (const pack of report.packs) {
52
+ lines.push(`- ${pack.id}: ${t(lang, pack.summary_key)}`);
53
+ for (const script of pack.scripts) {
54
+ const schema = script.report_schema_file ?? t(lang, 'value.none');
55
+ lines.push(` - ${script.ref}: ${t(lang, script.summary_key)} ` +
56
+ `(${t(lang, 'scriptPack.label.actions')}: ${script.actions.join(', ')}, ` +
57
+ `${t(lang, 'scriptPack.label.schema')}: ${schema})`);
58
+ }
59
+ }
60
+ return lines.join('\n');
61
+ }
62
+ function parseListOptions(args, lang) {
63
+ const parsed = parseCliOptions(args, SCRIPT_PACK_LIST_OPTIONS, { allowPositionals: false });
64
+ const json = hasParsedCliOption(parsed, '--json');
65
+ if (parsed.error) {
66
+ return { json, error: formatCliOptionParseError(parsed.error, lang) };
67
+ }
68
+ return { json };
69
+ }
70
+ async function runScriptPackList(args, reporter, lang) {
71
+ if (hasCliOptionToken(args, '--help', ['-h'])) {
72
+ reporter.stdout(getScriptPackHelp(lang));
73
+ return 0;
74
+ }
75
+ const options = parseListOptions(args, lang);
76
+ if (options.error) {
77
+ printUsageError(reporter, options.error, 'mf script-pack --help', getScriptPackHelp(lang), lang);
78
+ return 1;
79
+ }
80
+ const report = createCatalogReport(resolveMustflowRoot(), SCRIPT_PACKS);
81
+ if (options.json) {
82
+ reporter.stdout(JSON.stringify(report, null, 2));
83
+ return 0;
84
+ }
85
+ reporter.stdout(renderCatalogSummary(report, lang));
86
+ return 0;
87
+ }
88
+ async function runScriptPackScript(args, reporter, lang) {
89
+ const [scriptRef, ...scriptArgs] = args;
90
+ if (args.length === 1 && hasCliOptionToken(args, '--help', ['-h'])) {
91
+ reporter.stdout(getScriptPackHelp(lang));
92
+ return 0;
93
+ }
94
+ if (!scriptRef) {
95
+ printUsageError(reporter, t(lang, 'scriptPack.error.missingScript'), 'mf script-pack --help', getScriptPackHelp(lang), lang);
96
+ return 1;
97
+ }
98
+ const script = findScriptPackScript(scriptRef);
99
+ if (!script) {
100
+ printUsageError(reporter, t(lang, 'scriptPack.error.unknownScript', { script: scriptRef }), 'mf script-pack list', getScriptPackHelp(lang), lang);
101
+ return 1;
102
+ }
103
+ const runner = await script.loadRunner();
104
+ return runner(scriptArgs, reporter, lang);
105
+ }
106
+ export async function runScriptPack(args, reporter, lang = 'en') {
107
+ const [action, ...rest] = args;
108
+ if ((!action || action !== 'run') && hasCliOptionToken(args, '--help', ['-h'])) {
109
+ reporter.stdout(getScriptPackHelp(lang));
110
+ return 0;
111
+ }
112
+ if (!action) {
113
+ printUsageError(reporter, t(lang, 'scriptPack.error.missingAction'), 'mf script-pack --help', getScriptPackHelp(lang), lang);
114
+ return 1;
115
+ }
116
+ if (action === 'list') {
117
+ return runScriptPackList(rest, reporter, lang);
118
+ }
119
+ if (action === 'run') {
120
+ return runScriptPackScript(rest, reporter, lang);
121
+ }
122
+ printUsageError(reporter, t(lang, 'scriptPack.error.unknownAction', { action }), 'mf script-pack --help', getScriptPackHelp(lang), lang);
123
+ return 1;
124
+ }