mustflow 2.22.4 → 2.22.9

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 (72) hide show
  1. package/README.md +17 -75
  2. package/dist/cli/commands/classify.js +2 -0
  3. package/dist/cli/commands/contract-lint.js +2 -2
  4. package/dist/cli/commands/dashboard.js +23 -75
  5. package/dist/cli/commands/help.js +8 -9
  6. package/dist/cli/commands/impact.js +2 -3
  7. package/dist/cli/commands/init.js +61 -5
  8. package/dist/cli/commands/run/receipt.js +1 -0
  9. package/dist/cli/commands/run.js +14 -1
  10. package/dist/cli/commands/update.js +2 -2
  11. package/dist/cli/commands/verify/evidence-input.js +269 -0
  12. package/dist/cli/commands/verify/input.js +212 -0
  13. package/dist/cli/commands/verify.js +23 -482
  14. package/dist/cli/commands/version-sources.js +2 -3
  15. package/dist/cli/i18n/en.js +5 -0
  16. package/dist/cli/i18n/es.js +5 -0
  17. package/dist/cli/i18n/fr.js +5 -0
  18. package/dist/cli/i18n/hi.js +5 -0
  19. package/dist/cli/i18n/ko.js +5 -0
  20. package/dist/cli/i18n/zh.js +5 -0
  21. package/dist/cli/lib/agent-context.js +6 -11
  22. package/dist/cli/lib/dashboard-export.js +2 -0
  23. package/dist/cli/lib/dashboard-mutations.js +79 -0
  24. package/dist/cli/lib/local-index/command-effect-index.js +25 -0
  25. package/dist/cli/lib/local-index/hashing.js +7 -0
  26. package/dist/cli/lib/local-index/index.js +127 -823
  27. package/dist/cli/lib/local-index/source-index.js +137 -0
  28. package/dist/cli/lib/local-index/verification-evidence.js +451 -0
  29. package/dist/cli/lib/local-index/workflow-documents.js +204 -0
  30. package/dist/cli/lib/mustflow-read.js +41 -0
  31. package/dist/cli/lib/project-root.js +1 -2
  32. package/dist/cli/lib/repo-map.js +65 -16
  33. package/dist/cli/lib/run-root-trust.js +27 -0
  34. package/dist/cli/lib/templates.js +124 -8
  35. package/dist/cli/lib/toml.js +6 -1
  36. package/dist/cli/lib/validation/constants.js +2 -0
  37. package/dist/cli/lib/validation/index.js +291 -22
  38. package/dist/cli/lib/validation/primitives.js +2 -2
  39. package/dist/cli/lib/validation/test-selection.js +2 -2
  40. package/dist/core/bounded-output.js +32 -7
  41. package/dist/core/change-classification-policy.js +47 -0
  42. package/dist/core/change-classification.js +10 -43
  43. package/dist/core/check-issues.js +7 -1
  44. package/dist/core/command-contract-validation.js +28 -4
  45. package/dist/core/command-env.js +1 -1
  46. package/dist/core/config-loading.js +9 -3
  47. package/dist/core/contract-lint.js +8 -3
  48. package/dist/core/correlation-id.js +16 -0
  49. package/dist/core/run-receipt.js +1 -0
  50. package/dist/core/safe-filesystem.js +11 -4
  51. package/dist/core/skill-route-alignment.js +1 -0
  52. package/dist/core/skill-route-explanation.js +9 -3
  53. package/dist/core/test-selection.js +2 -3
  54. package/dist/core/verification-scheduler.js +7 -6
  55. package/dist/core/version-sources.js +2 -3
  56. package/package.json +4 -1
  57. package/schemas/README.md +4 -0
  58. package/schemas/change-verification-report.schema.json +4 -0
  59. package/schemas/classify-report.schema.json +4 -0
  60. package/schemas/commands.schema.json +1 -0
  61. package/schemas/dashboard-export.schema.json +4 -0
  62. package/schemas/latest-run-pointer.schema.json +4 -0
  63. package/schemas/run-receipt.schema.json +4 -0
  64. package/schemas/verify-report.schema.json +4 -0
  65. package/schemas/verify-run-manifest.schema.json +4 -0
  66. package/templates/default/i18n.toml +3 -3
  67. package/templates/default/locales/en/.mustflow/skills/INDEX.md +10 -6
  68. package/templates/default/locales/en/.mustflow/skills/architecture-deepening-review/SKILL.md +25 -2
  69. package/templates/default/locales/en/.mustflow/skills/routes.toml +2 -2
  70. package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +9 -1
  71. package/templates/default/locales/en/.mustflow/skills/test-design-guard/SKILL.md +9 -1
  72. package/templates/default/manifest.toml +1 -1
@@ -1,8 +1,8 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import path from 'node:path';
3
- import { createClassifyOutput } from './classify.js';
4
3
  import { runRun } from './run.js';
5
4
  import { createChangeVerificationReport, } from '../../core/change-verification.js';
5
+ import { createCorrelationId } from '../../core/correlation-id.js';
6
6
  import { readUtf8FileInsideWithoutSymlinks, writeJsonFileInsideWithoutSymlinks } from '../../core/safe-filesystem.js';
7
7
  import { createVerifyCompletionVerdict, } from '../../core/completion-verdict.js';
8
8
  import { createStateRunId } from '../../core/atomic-state-write.js';
@@ -15,10 +15,13 @@ import { countValidationRatchetVerdictEffects, createValidationRatchetRisks, } f
15
15
  import { finishRunWriteBatchTracking, startRunWriteBatchTracking, } from '../../core/run-write-drift.js';
16
16
  import { readCommandContract } from '../../core/config-loading.js';
17
17
  import { DEFAULT_VERIFY_PARALLELISM, parseVerifyArgs, resolveVerifyParallelism, } from './verify/args.js';
18
+ import { createInputFromChanged, createSyntheticClassificationReport, planErrorMessageKey, readInputFromClassificationReport, resolveVerifyInputPath, writeChangedPlan, } from './verify/input.js';
19
+ import { readExternalEvidenceFile, readReproEvidenceFile } from './verify/evidence-input.js';
18
20
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
19
21
  import { t } from '../lib/i18n.js';
20
22
  import { readLocalCommandEffectGraphs, readLocalPathSurfaces, readLocalSourceAnchorVerdictRisks, } from '../lib/local-index.js';
21
23
  import { resolveMustflowRoot } from '../lib/project-root.js';
24
+ export { planErrorMessageKey, readInputFromClassificationReport } from './verify/input.js';
22
25
  const VERIFY_SCHEMA_VERSION = '1';
23
26
  const RUN_STATE_DIR = path.join('.mustflow', 'state', 'runs');
24
27
  const LATEST_RUN_RECEIPT_PATH = path.join(RUN_STATE_DIR, 'latest.json');
@@ -96,474 +99,6 @@ function sanitizeIntentFilePart(value) {
96
99
  const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
97
100
  return sanitized.length > 0 ? sanitized.slice(0, 80) : 'intent';
98
101
  }
99
- function readStringArray(value) {
100
- if (!Array.isArray(value)) {
101
- return [];
102
- }
103
- return value.filter((item) => typeof item === 'string');
104
- }
105
- function isStringArray(value) {
106
- return Array.isArray(value) && value.every((item) => typeof item === 'string');
107
- }
108
- function isPlainRecord(value) {
109
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
110
- }
111
- function isUpdatePolicy(value) {
112
- return value === 'update' || value === 'update_or_mark_stale' || value === 'not_applicable';
113
- }
114
- function isUpdatePolicyArray(value) {
115
- return Array.isArray(value) && value.every((item) => isUpdatePolicy(item));
116
- }
117
- function readPlanRoot(value) {
118
- if (!isPlainRecord(value)) {
119
- return null;
120
- }
121
- const root = value.mustflow_root;
122
- return typeof root === 'string' && root.length > 0 ? root : null;
123
- }
124
- function readStrictClassificationSummary(value) {
125
- if (!isPlainRecord(value)) {
126
- return null;
127
- }
128
- if (!Number.isInteger(value.fileCount) ||
129
- Number(value.fileCount) < 0 ||
130
- !Number.isInteger(value.publicSurfaceCount) ||
131
- Number(value.publicSurfaceCount) < 0 ||
132
- !isStringArray(value.changeKinds) ||
133
- !isStringArray(value.validationReasons) ||
134
- !isUpdatePolicyArray(value.updatePolicies) ||
135
- !isStringArray(value.driftChecks) ||
136
- !isStringArray(value.affectedContracts)) {
137
- return null;
138
- }
139
- return {
140
- fileCount: Number(value.fileCount),
141
- publicSurfaceCount: Number(value.publicSurfaceCount),
142
- changeKinds: [...value.changeKinds],
143
- validationReasons: uniqueStrings(value.validationReasons),
144
- updatePolicies: [...value.updatePolicies],
145
- driftChecks: [...value.driftChecks],
146
- affectedContracts: [...value.affectedContracts],
147
- };
148
- }
149
- function readStrictClassification(value) {
150
- if (!isPlainRecord(value) || typeof value.path !== 'string' || !isStringArray(value.changeKinds)) {
151
- return null;
152
- }
153
- const surface = value.surface;
154
- if (!isPlainRecord(surface)) {
155
- return null;
156
- }
157
- if (typeof surface.kind !== 'string' ||
158
- typeof surface.category !== 'string' ||
159
- typeof surface.isPublicSurface !== 'boolean' ||
160
- !isStringArray(surface.validationReasons) ||
161
- !isStringArray(surface.affectedContracts) ||
162
- !isUpdatePolicy(surface.updatePolicy) ||
163
- !isStringArray(surface.driftChecks)) {
164
- return null;
165
- }
166
- return {
167
- path: value.path,
168
- changeKinds: [...value.changeKinds],
169
- surface: {
170
- kind: surface.kind,
171
- category: surface.category,
172
- isPublicSurface: surface.isPublicSurface,
173
- validationReasons: [...surface.validationReasons],
174
- affectedContracts: [...surface.affectedContracts],
175
- updatePolicy: surface.updatePolicy,
176
- driftChecks: [...surface.driftChecks],
177
- },
178
- };
179
- }
180
- function readStrictClassifications(value) {
181
- if (!Array.isArray(value)) {
182
- return null;
183
- }
184
- const classifications = value.map(readStrictClassification);
185
- return classifications.every((classification) => classification !== null)
186
- ? classifications
187
- : null;
188
- }
189
- function readStrictClassifyPlan(projectRoot, plan) {
190
- if (!isPlainRecord(plan)) {
191
- throw new Error('unsupported_plan_source');
192
- }
193
- if (plan.schema_version !== '1' || plan.command !== 'classify') {
194
- throw new Error('unsupported_plan_source');
195
- }
196
- if (readPlanRoot(plan) !== projectRoot) {
197
- throw new Error('plan_root_mismatch');
198
- }
199
- const source = plan.source;
200
- const files = plan.files;
201
- const classifications = readStrictClassifications(plan.classifications);
202
- const summary = readStrictClassificationSummary(plan.summary);
203
- if ((source !== 'changed' && source !== 'paths') || !isStringArray(files) || !classifications || !summary) {
204
- throw new Error('invalid_plan_file');
205
- }
206
- if (summary.validationReasons.length === 0) {
207
- throw new Error('missing_plan_reasons');
208
- }
209
- return {
210
- source,
211
- files: [...files],
212
- classifications,
213
- summary,
214
- };
215
- }
216
- function emptyClassificationSummary(validationReasons) {
217
- return {
218
- fileCount: 0,
219
- publicSurfaceCount: 0,
220
- changeKinds: [],
221
- validationReasons,
222
- updatePolicies: [],
223
- driftChecks: [],
224
- affectedContracts: [],
225
- };
226
- }
227
- function createSyntheticClassificationReport(reasons, source = 'paths', files = []) {
228
- return {
229
- source,
230
- files,
231
- classifications: [],
232
- summary: emptyClassificationSummary(reasons),
233
- };
234
- }
235
- function resolvePlanPath(projectRoot, inputPath) {
236
- const resolved = path.resolve(projectRoot, inputPath);
237
- const relative = path.relative(projectRoot, resolved);
238
- if (relative.startsWith('..') || path.isAbsolute(relative)) {
239
- throw new Error('plan_path_outside_root');
240
- }
241
- return resolved;
242
- }
243
- function readJsonInputFile(projectRoot, inputPath, invalidCode) {
244
- const inputFilePath = resolvePlanPath(projectRoot, inputPath);
245
- let content;
246
- try {
247
- content = readUtf8FileInsideWithoutSymlinks(projectRoot, inputFilePath);
248
- }
249
- catch (error) {
250
- if (error instanceof Error && error.message.startsWith('Path must not contain symlinks:')) {
251
- throw new Error('input_path_contains_symlink');
252
- }
253
- throw new Error(invalidCode);
254
- }
255
- try {
256
- return JSON.parse(content);
257
- }
258
- catch {
259
- throw new Error(invalidCode);
260
- }
261
- }
262
- export function readInputFromClassificationReport(projectRoot, inputPath) {
263
- const parsed = readJsonInputFile(projectRoot, inputPath, 'invalid_plan_file');
264
- const classificationReport = readStrictClassifyPlan(projectRoot, parsed);
265
- return {
266
- reasons: classificationReport.summary.validationReasons,
267
- classificationReport,
268
- };
269
- }
270
- function isExternalEvidenceStatus(value) {
271
- return value === 'passed' || value === 'failed' || value === 'cancelled' || value === 'unknown';
272
- }
273
- function isLegacyReproEvidenceStatus(value) {
274
- return value === 'present' || value === 'unavailable' || value === 'missing';
275
- }
276
- function isReproBeforeFixStatus(value) {
277
- return value === 'reproduced' || value === 'unavailable' || value === 'missing';
278
- }
279
- function isReproBeforeFixOutcome(value) {
280
- return value === 'failed_as_expected' || value === 'failed_differently' || value === 'passed_unexpectedly' || value === null;
281
- }
282
- function isReproAfterFixStatus(value) {
283
- return value === 'passed' || value === 'failed' || value === 'unavailable' || value === 'missing';
284
- }
285
- function isReproAfterFixOutcome(value) {
286
- return value === 'passed_expected_behavior' || value === 'failed_same_route' || value === 'failed_differently' || value === null;
287
- }
288
- function isReproRegressionGuardStatus(value) {
289
- return value === 'passed' || value === 'failed' || value === 'unavailable' || value === 'missing';
290
- }
291
- function isReproRouteKind(value) {
292
- return (value === 'test' ||
293
- value === 'cli' ||
294
- value === 'browser' ||
295
- value === 'api' ||
296
- value === 'manual' ||
297
- value === 'unknown' ||
298
- value === null);
299
- }
300
- function readOptionalString(value) {
301
- return typeof value === 'string' && value.length > 0 ? value : null;
302
- }
303
- function readRouteStep(value, index) {
304
- if (!isPlainRecord(value)) {
305
- return {
306
- ordinal: index + 1,
307
- action: null,
308
- target: null,
309
- input_digest: null,
310
- observation_digest: null,
311
- summary: null,
312
- };
313
- }
314
- const ordinal = typeof value.ordinal === 'number' && Number.isInteger(value.ordinal) && value.ordinal > 0 ? value.ordinal : index + 1;
315
- return {
316
- ordinal,
317
- action: readOptionalString(value.action),
318
- target: readOptionalString(value.target),
319
- input_digest: readOptionalString(value.input_digest),
320
- observation_digest: readOptionalString(value.observation_digest),
321
- summary: readOptionalString(value.summary),
322
- };
323
- }
324
- function readReproductionRoute(value) {
325
- if (!isPlainRecord(value)) {
326
- return {
327
- route_id: null,
328
- route_kind: null,
329
- route_digest: null,
330
- failure_oracle_hash: null,
331
- steps: [],
332
- };
333
- }
334
- const routeKind = value.route_kind ?? null;
335
- if (!isReproRouteKind(routeKind)) {
336
- throw new Error('invalid_repro_evidence_file');
337
- }
338
- const rawSteps = Array.isArray(value.steps) ? value.steps : [];
339
- return {
340
- route_id: readOptionalString(value.route_id),
341
- route_kind: routeKind,
342
- route_digest: readOptionalString(value.route_digest),
343
- failure_oracle_hash: readOptionalString(value.failure_oracle_hash),
344
- steps: rawSteps.map((step, index) => readRouteStep(step, index)),
345
- };
346
- }
347
- function readLegacyReproEvidenceItem(value) {
348
- if (!isPlainRecord(value)) {
349
- return {
350
- status: 'missing',
351
- summary: null,
352
- reason: null,
353
- };
354
- }
355
- if (!isLegacyReproEvidenceStatus(value.status)) {
356
- throw new Error('invalid_repro_evidence_file');
357
- }
358
- return {
359
- status: value.status,
360
- summary: readOptionalString(value.summary),
361
- reason: readOptionalString(value.reason),
362
- };
363
- }
364
- function legacyBeforeFixEvidence(value) {
365
- const item = readLegacyReproEvidenceItem(value);
366
- return {
367
- status: item.status === 'present' ? 'reproduced' : item.status,
368
- outcome: item.status === 'present' ? 'failed_as_expected' : null,
369
- receipt_path: null,
370
- receipt_sha256: null,
371
- verification_plan_id: null,
372
- summary: item.summary,
373
- reason: item.reason,
374
- };
375
- }
376
- function legacyAfterFixEvidence(value) {
377
- const item = readLegacyReproEvidenceItem(value);
378
- return {
379
- status: item.status === 'present' ? 'passed' : item.status,
380
- outcome: item.status === 'present' ? 'passed_expected_behavior' : null,
381
- same_route_as: null,
382
- receipt_path: null,
383
- receipt_sha256: null,
384
- verification_plan_id: null,
385
- summary: item.summary,
386
- reason: item.reason,
387
- };
388
- }
389
- function legacyRegressionGuardEvidence(value) {
390
- const item = readLegacyReproEvidenceItem(value);
391
- return {
392
- status: item.status === 'present' ? 'passed' : item.status,
393
- intent: null,
394
- test_path: null,
395
- receipt_path: null,
396
- receipt_sha256: null,
397
- verification_plan_id: null,
398
- summary: item.summary,
399
- reason: item.reason,
400
- };
401
- }
402
- function readBeforeFixEvidence(value) {
403
- if (!isPlainRecord(value)) {
404
- return {
405
- status: 'missing',
406
- outcome: null,
407
- receipt_path: null,
408
- receipt_sha256: null,
409
- verification_plan_id: null,
410
- summary: null,
411
- reason: null,
412
- };
413
- }
414
- const outcome = value.outcome ?? null;
415
- if (!isReproBeforeFixStatus(value.status) || !isReproBeforeFixOutcome(outcome)) {
416
- throw new Error('invalid_repro_evidence_file');
417
- }
418
- return {
419
- status: value.status,
420
- outcome,
421
- receipt_path: readOptionalString(value.receipt_path),
422
- receipt_sha256: readOptionalString(value.receipt_sha256),
423
- verification_plan_id: readOptionalString(value.verification_plan_id),
424
- summary: readOptionalString(value.summary),
425
- reason: readOptionalString(value.reason),
426
- };
427
- }
428
- function readAfterFixEvidence(value) {
429
- if (!isPlainRecord(value)) {
430
- return {
431
- status: 'missing',
432
- outcome: null,
433
- same_route_as: null,
434
- receipt_path: null,
435
- receipt_sha256: null,
436
- verification_plan_id: null,
437
- summary: null,
438
- reason: null,
439
- };
440
- }
441
- const outcome = value.outcome ?? null;
442
- if (!isReproAfterFixStatus(value.status) || !isReproAfterFixOutcome(outcome)) {
443
- throw new Error('invalid_repro_evidence_file');
444
- }
445
- return {
446
- status: value.status,
447
- outcome,
448
- same_route_as: readOptionalString(value.same_route_as),
449
- receipt_path: readOptionalString(value.receipt_path),
450
- receipt_sha256: readOptionalString(value.receipt_sha256),
451
- verification_plan_id: readOptionalString(value.verification_plan_id),
452
- summary: readOptionalString(value.summary),
453
- reason: readOptionalString(value.reason),
454
- };
455
- }
456
- function readRegressionGuardEvidence(value) {
457
- if (!isPlainRecord(value)) {
458
- return {
459
- status: 'missing',
460
- intent: null,
461
- test_path: null,
462
- receipt_path: null,
463
- receipt_sha256: null,
464
- verification_plan_id: null,
465
- summary: null,
466
- reason: null,
467
- };
468
- }
469
- if (!isReproRegressionGuardStatus(value.status)) {
470
- throw new Error('invalid_repro_evidence_file');
471
- }
472
- return {
473
- status: value.status,
474
- intent: readOptionalString(value.intent),
475
- test_path: readOptionalString(value.test_path),
476
- receipt_path: readOptionalString(value.receipt_path),
477
- receipt_sha256: readOptionalString(value.receipt_sha256),
478
- verification_plan_id: readOptionalString(value.verification_plan_id),
479
- summary: readOptionalString(value.summary),
480
- reason: readOptionalString(value.reason),
481
- };
482
- }
483
- function readReproEvidenceFile(projectRoot, inputPath) {
484
- const parsed = readJsonInputFile(projectRoot, inputPath, 'invalid_repro_evidence_file');
485
- if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'repro-evidence') {
486
- throw new Error('unsupported_repro_evidence_source');
487
- }
488
- const regressionGuard = isPlainRecord(parsed.regression_guard) && isReproRegressionGuardStatus(parsed.regression_guard.status)
489
- ? readRegressionGuardEvidence(parsed.regression_guard)
490
- : legacyRegressionGuardEvidence(parsed.regression_guard);
491
- return {
492
- source: 'repro_first_debug',
493
- authority: 'claim_evidence',
494
- reported_symptom: readOptionalString(parsed.reported_symptom),
495
- expected_behavior: readOptionalString(parsed.expected_behavior),
496
- observed_behavior: readOptionalString(parsed.observed_behavior),
497
- reproduction_route: readReproductionRoute(parsed.reproduction_route),
498
- before_fix: isPlainRecord(parsed.before_fix)
499
- ? readBeforeFixEvidence(parsed.before_fix)
500
- : legacyBeforeFixEvidence(parsed.evidence_before_fix),
501
- after_fix: isPlainRecord(parsed.after_fix)
502
- ? readAfterFixEvidence(parsed.after_fix)
503
- : legacyAfterFixEvidence(parsed.evidence_after_fix),
504
- regression_guard: regressionGuard,
505
- };
506
- }
507
- function readExternalEvidenceFile(projectRoot, inputPath) {
508
- const parsed = readJsonInputFile(projectRoot, inputPath, 'invalid_external_evidence_file');
509
- if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'external-evidence') {
510
- throw new Error('unsupported_external_evidence_source');
511
- }
512
- if (!Array.isArray(parsed.checks)) {
513
- throw new Error('invalid_external_evidence_file');
514
- }
515
- return parsed.checks.map((check) => {
516
- if (!isPlainRecord(check) ||
517
- typeof check.provider !== 'string' ||
518
- check.provider.length === 0 ||
519
- typeof check.name !== 'string' ||
520
- check.name.length === 0 ||
521
- !isExternalEvidenceStatus(check.status)) {
522
- throw new Error('invalid_external_evidence_file');
523
- }
524
- return {
525
- source: 'external_ci',
526
- authority: 'supporting_only',
527
- provider: check.provider,
528
- name: check.name,
529
- status: check.status,
530
- url: readOptionalString(check.url),
531
- summary: readOptionalString(check.summary),
532
- };
533
- });
534
- }
535
- function createInputFromChanged(projectRoot) {
536
- const plan = createClassifyOutput(projectRoot, 'changed', []);
537
- return {
538
- plan,
539
- input: {
540
- reasons: plan.summary.validationReasons,
541
- classificationReport: plan,
542
- },
543
- };
544
- }
545
- function writeChangedPlan(projectRoot, inputPath, plan) {
546
- const planPath = resolvePlanPath(projectRoot, inputPath);
547
- writeJsonFileInsideWithoutSymlinks(projectRoot, planPath, plan);
548
- }
549
- export function planErrorMessageKey(code) {
550
- switch (code) {
551
- case 'plan_path_outside_root':
552
- return 'verify.error.plan_path_outside_root';
553
- case 'input_path_contains_symlink':
554
- return 'verify.error.input_path_contains_symlink';
555
- case 'missing_plan_reasons':
556
- return 'verify.error.missing_plan_reasons';
557
- case 'unsupported_plan_source':
558
- return 'verify.error.unsupported_plan_source';
559
- case 'plan_root_mismatch':
560
- return 'verify.error.plan_root_mismatch';
561
- case 'git_changed_files_unavailable':
562
- return 'verify.error.changed_files_unavailable';
563
- default:
564
- return 'verify.error.invalid_plan_file';
565
- }
566
- }
567
102
  function skippedResult(candidate) {
568
103
  return {
569
104
  intent: candidate.intent || null,
@@ -628,9 +163,10 @@ function testTargetsByScheduledIntent(report) {
628
163
  candidate.appliedTestTargets.length > 0)
629
164
  .map((candidate) => [candidate.intent, candidate.appliedTestTargets]));
630
165
  }
631
- async function runVerificationIntent(intent, lang, verificationPlanId, testTargets = [], additionalDeclaredWritePaths = []) {
166
+ async function runVerificationIntent(intent, lang, verificationPlanId, correlationId, testTargets = [], additionalDeclaredWritePaths = []) {
632
167
  const output = createBufferedOutput();
633
168
  const exitCode = await runRun([intent, '--json'], output.reporter, lang, {
169
+ correlationId,
634
170
  writeLatestReceipt: false,
635
171
  writeLatestProfile: false,
636
172
  recordPerformanceHistory: false,
@@ -672,19 +208,19 @@ function entriesForScheduleBatch(entries, batch) {
672
208
  const batchIntents = new Set(batch.intents);
673
209
  return entries.filter((entry) => batchIntents.has(entry.intent));
674
210
  }
675
- async function runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets) {
211
+ async function runVerificationEntriesSequentially(entries, lang, verificationPlanId, correlationId, scheduledTestTargets) {
676
212
  const results = [];
677
213
  for (const entry of entries) {
678
- results.push(await runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? []));
214
+ results.push(await runVerificationIntent(entry.intent, lang, verificationPlanId, correlationId, scheduledTestTargets.get(entry.intent) ?? []));
679
215
  }
680
216
  return results;
681
217
  }
682
- async function runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, scheduledTestTargets) {
218
+ async function runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, correlationId, scheduledTestTargets) {
683
219
  const results = [];
684
220
  for (let index = 0; index < entries.length; index += parallelism) {
685
221
  const chunk = entries.slice(index, index + parallelism);
686
222
  const batchTracker = startRunWriteBatchTracking(projectRoot);
687
- const chunkResults = await Promise.all(chunk.map((entry) => runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? [])));
223
+ const chunkResults = await Promise.all(chunk.map((entry) => runVerificationIntent(entry.intent, lang, verificationPlanId, correlationId, scheduledTestTargets.get(entry.intent) ?? [])));
688
224
  const writeDriftByIntent = finishRunWriteBatchTracking(batchTracker, chunk.map((entry) => ({
689
225
  intentName: entry.intent,
690
226
  declaredPaths: declaredWritePathsForScheduleEntry(entry),
@@ -732,7 +268,7 @@ function verificationResultFailed(result) {
732
268
  result.status === 'start_failed' ||
733
269
  result.status === 'output_limit_exceeded'));
734
270
  }
735
- async function runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, scheduledTestTargets, parallelism) {
271
+ async function runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, correlationId, scheduledTestTargets, parallelism) {
736
272
  const results = [];
737
273
  for (let batchIndex = 0; batchIndex < report.schedule.batches.length; batchIndex += 1) {
738
274
  const batch = report.schedule.batches[batchIndex];
@@ -744,11 +280,11 @@ async function runScheduledVerificationIntents(report, projectRoot, lang, verifi
744
280
  if (entries.length > 1 && entries.every((entry) => entry.parallelEligible)) {
745
281
  batchResults =
746
282
  parallelism > DEFAULT_VERIFY_PARALLELISM
747
- ? await runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, scheduledTestTargets)
748
- : await runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets);
283
+ ? await runVerificationEntriesInParallelChunks(projectRoot, entries, parallelism, lang, verificationPlanId, correlationId, scheduledTestTargets)
284
+ : await runVerificationEntriesSequentially(entries, lang, verificationPlanId, correlationId, scheduledTestTargets);
749
285
  }
750
286
  else {
751
- batchResults = await runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets);
287
+ batchResults = await runVerificationEntriesSequentially(entries, lang, verificationPlanId, correlationId, scheduledTestTargets);
752
288
  }
753
289
  results.push(...batchResults);
754
290
  if (!batchResults.some(verificationResultFailed)) {
@@ -1217,6 +753,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
1217
753
  const manifest = {
1218
754
  schema_version: '1',
1219
755
  command: 'verify',
756
+ correlation_id: outputWithReceiptPaths.correlation_id,
1220
757
  reason: outputWithReceiptPaths.reason,
1221
758
  reasons: outputWithReceiptPaths.reasons,
1222
759
  plan_source: outputWithReceiptPaths.plan_source,
@@ -1237,6 +774,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
1237
774
  schema_version: '1',
1238
775
  command: 'verify',
1239
776
  kind: 'verify_run_summary',
777
+ correlation_id: outputWithReceiptPaths.correlation_id,
1240
778
  reason: outputWithReceiptPaths.reason,
1241
779
  reasons: outputWithReceiptPaths.reasons,
1242
780
  plan_source: outputWithReceiptPaths.plan_source,
@@ -1269,7 +807,7 @@ async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvi
1269
807
  const reproEvidenceRisks = createReproEvidenceRisks(reproEvidence, { verificationPlanId });
1270
808
  const reproEvidenceVerdictEffects = countReproEvidenceVerdictEffects(reproEvidenceRisks);
1271
809
  const externalEvidenceRisks = createExternalEvidenceRisks(externalChecks);
1272
- const results = await runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, scheduledTestTargets, parallelism);
810
+ const results = await runScheduledVerificationIntents(report, projectRoot, lang, verificationPlanId, input.correlationId, scheduledTestTargets, parallelism);
1273
811
  results.push(...createSkippedResults(report.candidates, scheduledIntents, report.gaps));
1274
812
  const summary = summarizeResults(results);
1275
813
  const status = getVerificationStatus(summary);
@@ -1325,6 +863,7 @@ async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvi
1325
863
  const output = {
1326
864
  schema_version: VERIFY_SCHEMA_VERSION,
1327
865
  command: 'verify',
866
+ correlation_id: input.correlationId,
1328
867
  mustflow_root: projectRoot,
1329
868
  reason: input.reasons.join(', '),
1330
869
  reasons: input.reasons,
@@ -1359,16 +898,17 @@ async function createPlanOnlyOutput(input, projectRoot) {
1359
898
  return surfaceReadModels.length > 0 ? { ...requirement, surfaceReadModels } : requirement;
1360
899
  });
1361
900
  if (!firstEntry) {
1362
- return { ...report, verification_plan_id: verificationPlanId, requirements };
901
+ return { ...report, correlation_id: input.correlationId, verification_plan_id: verificationPlanId, requirements };
1363
902
  }
1364
903
  const scheduledIntents = Array.from(new Set(report.schedule.entries.map((entry) => entry.intent)));
1365
904
  const graphsByIntent = await readLocalCommandEffectGraphs(projectRoot, scheduledIntents);
1366
905
  const firstGraph = graphsByIntent.get(firstEntry.intent);
1367
906
  if (!firstGraph) {
1368
- return { ...report, verification_plan_id: verificationPlanId, requirements };
907
+ return { ...report, correlation_id: input.correlationId, verification_plan_id: verificationPlanId, requirements };
1369
908
  }
1370
909
  return {
1371
910
  ...report,
911
+ correlation_id: input.correlationId,
1372
912
  verification_plan_id: verificationPlanId,
1373
913
  requirements,
1374
914
  schedule: {
@@ -1475,7 +1015,7 @@ export async function runVerify(args, reporter, lang = 'en') {
1475
1015
  let externalChecks = [];
1476
1016
  try {
1477
1017
  if (parsed.writePlan) {
1478
- resolvePlanPath(projectRoot, parsed.writePlan);
1018
+ resolveVerifyInputPath(projectRoot, parsed.writePlan);
1479
1019
  }
1480
1020
  if (parsed.changed) {
1481
1021
  const changedInput = createInputFromChanged(projectRoot);
@@ -1487,6 +1027,7 @@ export async function runVerify(args, reporter, lang = 'en') {
1487
1027
  }
1488
1028
  else {
1489
1029
  input = {
1030
+ correlationId: createCorrelationId('verify'),
1490
1031
  reasons: [parsed.reason],
1491
1032
  classificationReport: createSyntheticClassificationReport([parsed.reason]),
1492
1033
  };
@@ -1,9 +1,8 @@
1
- import path from 'node:path';
2
1
  import { printUsageError, renderHelp } from '../lib/cli-output.js';
3
2
  import { isRecord } from '../lib/command-contract.js';
4
3
  import { t } from '../lib/i18n.js';
5
4
  import { resolveMustflowRoot } from '../lib/project-root.js';
6
- import { readTomlFile } from '../lib/toml.js';
5
+ import { readMustflowTomlFile } from '../lib/toml.js';
7
6
  import { detectVersionSources, releaseVersioningIsEnabled, } from '../../core/version-sources.js';
8
7
  const VERSION_SOURCES_SCHEMA_VERSION = '1';
9
8
  export function getVersionSourcesHelp(lang = 'en') {
@@ -23,7 +22,7 @@ export function getVersionSourcesHelp(lang = 'en') {
23
22
  }
24
23
  function readPreferences(projectRoot) {
25
24
  try {
26
- const preferences = readTomlFile(path.join(projectRoot, '.mustflow', 'config', 'preferences.toml'));
25
+ const preferences = readMustflowTomlFile(projectRoot, '.mustflow/config/preferences.toml');
27
26
  return isRecord(preferences) ? preferences : undefined;
28
27
  }
29
28
  catch {
@@ -583,6 +583,8 @@ Read these files before working:
583
583
  "init.error.invalidPreference": "Invalid init preference override: {value}",
584
584
  "init.error.invalidPreferenceValue": "Invalid value for {key}: {value}",
585
585
  "init.error.unsupportedPreference": "Unsupported init preference setting: {key}",
586
+ "init.error.promptInputTooLarge": "Interactive init stdin input is too large; expected at most {maxBytes} bytes.",
587
+ "init.error.promptInputTooManyResponses": "Interactive init stdin input has too many responses; expected at most {maxResponses} lines.",
586
588
  "init.prompt.locale": "Which language should mustflow documents use?",
587
589
  "init.prompt.profile": "Which project profile should mustflow use?",
588
590
  "init.prompt.agentLang": "Which language should agents use for final reports?",
@@ -650,6 +652,7 @@ Read these files before working:
650
652
  "run.help.option.dryRun": "Print a non-executing command plan",
651
653
  "run.help.option.planOnly": "Alias for --dry-run",
652
654
  "run.help.option.json": "Print the run record or command plan as JSON",
655
+ "run.help.option.allowUntrustedRoot": "Allow one execution from a root with a missing or invalid manifest lock after manual review",
653
656
  "run.help.exit.ok": "The command completed with an allowed exit code",
654
657
  "run.help.exit.fail": "The command was invalid, refused, timed out, or failed",
655
658
  "run.label.suggestedIntentSnippet": "Suggested command contract snippet",
@@ -672,6 +675,8 @@ Read these files before working:
672
675
  "run.error.maxOutputBytes": 'Command "{intent}" has invalid max_output_bytes. {detail}',
673
676
  "run.error.maxOutputBytesDetail": "The output limit must stay within the allowed maximum.",
674
677
  "run.error.conflictingPreviewModes": "Use either --dry-run or --plan-only, not both",
678
+ "run.error.untrustedRootMissing": "Refused to execute commands because {path} is missing. Run mf init/update to install the workflow, or pass --allow-untrusted-root after reviewing AGENTS.md and .mustflow/config/commands.toml.",
679
+ "run.error.untrustedRootInvalid": "Refused to execute commands because the manifest lock is invalid: {detail}. Restore or regenerate it, or pass --allow-untrusted-root after reviewing AGENTS.md and .mustflow/config/commands.toml.",
675
680
  "run.error.timedOut": 'Command "{intent}" timed out after {seconds} seconds',
676
681
  "run.error.outputLimitExceeded": 'Command "{intent}" exceeded max_output_bytes: {message}',
677
682
  "run.error.startFailed": 'Command "{intent}" failed to start: {message}',