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.
- package/README.md +20 -6
- package/dist/cli/commands/api.js +17 -0
- package/dist/cli/commands/check.js +38 -26
- package/dist/cli/commands/doctor.js +17 -5
- package/dist/cli/commands/evidence.js +71 -0
- package/dist/cli/commands/index.js +24 -9
- package/dist/cli/commands/map.js +20 -7
- package/dist/cli/commands/run.js +2 -1
- package/dist/cli/commands/script-pack.js +124 -0
- package/dist/cli/commands/update.js +52 -39
- package/dist/cli/commands/verify.js +50 -15
- package/dist/cli/commands/workspace.js +2 -0
- package/dist/cli/i18n/en.js +38 -0
- package/dist/cli/i18n/es.js +38 -0
- package/dist/cli/i18n/fr.js +38 -0
- package/dist/cli/i18n/hi.js +38 -0
- package/dist/cli/i18n/ko.js +38 -0
- package/dist/cli/i18n/zh.js +38 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/lib/active-command-lock.js +96 -0
- package/dist/cli/lib/agent-context.js +179 -10
- package/dist/cli/lib/command-registry.js +6 -0
- package/dist/cli/lib/dashboard-export.js +1 -0
- package/dist/cli/lib/script-pack-registry.js +27 -0
- package/dist/cli/script-packs/core-text-budget.js +241 -0
- package/dist/core/active-run-locks.js +7 -1
- package/dist/core/change-verification.js +10 -0
- package/dist/core/completion-verdict.js +14 -1
- package/dist/core/complexity-budget.js +206 -0
- package/dist/core/conflict-ledger.js +122 -0
- package/dist/core/failure-replay-capsule.js +213 -0
- package/dist/core/public-json-contracts.js +27 -0
- package/dist/core/risk-priced-evidence.js +213 -0
- package/dist/core/script-check-result.js +1 -0
- package/dist/core/text-budget.js +262 -0
- package/dist/core/verification-evidence.js +61 -13
- package/package.json +1 -1
- package/schemas/README.md +23 -11
- package/schemas/change-verification-report.schema.json +29 -0
- package/schemas/context-report.schema.json +58 -2
- package/schemas/dashboard-export.schema.json +42 -1
- package/schemas/diff-risk.schema.json +6 -0
- package/schemas/evidence-report.schema.json +45 -0
- package/schemas/latest-run-pointer.schema.json +50 -1
- package/schemas/script-pack-catalog.schema.json +68 -0
- package/schemas/text-budget-report.schema.json +131 -0
- package/schemas/verification-plan.schema.json +32 -0
- package/schemas/verify-report.schema.json +360 -1
- package/schemas/verify-run-manifest.schema.json +50 -1
- package/schemas/workspace-verification-plan.schema.json +32 -0
- package/templates/default/i18n.toml +2 -2
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -2
- package/templates/default/locales/en/.mustflow/skills/adapter-boundary/SKILL.md +19 -2
- package/templates/default/locales/en/.mustflow/skills/routes.toml +1 -1
- 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,
|
|
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,
|
|
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
|
|
package/dist/cli/commands/api.js
CHANGED
|
@@ -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
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 (
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
96
|
-
|
|
107
|
+
finally {
|
|
108
|
+
if (activeLock?.ok) {
|
|
109
|
+
activeLock.handle.release();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
97
112
|
}
|
package/dist/cli/commands/map.js
CHANGED
|
@@ -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
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
91
|
+
finally {
|
|
92
|
+
if (activeLock?.ok) {
|
|
93
|
+
activeLock.handle.release();
|
|
94
|
+
}
|
|
81
95
|
}
|
|
82
|
-
return 0;
|
|
83
96
|
}
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -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
|
+
}
|