mustflow 1.31.0 → 2.11.0
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 +23 -9
- package/dist/cli/commands/classify.js +61 -6
- package/dist/cli/commands/contract-lint.js +13 -4
- package/dist/cli/commands/dashboard.js +6 -0
- package/dist/cli/commands/index.js +5 -0
- package/dist/cli/commands/run.js +4 -1
- package/dist/cli/commands/verify.js +488 -43
- package/dist/cli/i18n/en.js +61 -10
- package/dist/cli/i18n/es.js +61 -10
- package/dist/cli/i18n/fr.js +61 -10
- package/dist/cli/i18n/hi.js +61 -10
- package/dist/cli/i18n/ko.js +61 -10
- package/dist/cli/i18n/zh.js +61 -10
- package/dist/cli/lib/dashboard-export.js +62 -12
- package/dist/cli/lib/dashboard-html/client-script.js +1936 -0
- package/dist/cli/lib/dashboard-html/locale-bootstrap.js +8 -0
- package/dist/cli/lib/dashboard-html/styles.js +572 -0
- package/dist/cli/lib/dashboard-html/template.js +134 -0
- package/dist/cli/lib/dashboard-html/types.js +1 -0
- package/dist/cli/lib/dashboard-html.js +1 -1907
- package/dist/cli/lib/dashboard-locale.js +37 -0
- package/dist/cli/lib/local-index/constants.js +48 -0
- package/dist/cli/lib/local-index/index.js +2256 -0
- package/dist/cli/lib/local-index/sql.js +15 -0
- package/dist/cli/lib/local-index/types.js +1 -0
- package/dist/cli/lib/local-index.js +1 -1911
- package/dist/cli/lib/run-plan.js +76 -1
- package/dist/cli/lib/templates.js +18 -1
- package/dist/cli/lib/validation/command-intents.js +11 -0
- package/dist/cli/lib/validation/constants.js +238 -0
- package/dist/cli/lib/validation/index.js +1384 -0
- package/dist/cli/lib/validation/primitives.js +198 -0
- package/dist/cli/lib/validation/test-selection.js +95 -0
- package/dist/cli/lib/validation/types.js +1 -0
- package/dist/cli/lib/validation.js +1 -1770
- package/dist/core/check-issues.js +6 -0
- package/dist/core/completion-verdict.js +209 -0
- package/dist/core/contract-lint.js +221 -6
- package/dist/core/external-evidence.js +9 -0
- package/dist/core/public-json-contracts.js +21 -0
- package/dist/core/repeated-failure.js +17 -0
- package/dist/core/repro-evidence.js +53 -0
- package/dist/core/scope-risk.js +64 -0
- package/dist/core/skill-route-alignment.js +20 -0
- package/dist/core/source-anchor-status.js +4 -1
- package/dist/core/test-selection.js +3 -0
- package/dist/core/validation-ratchet.js +52 -0
- package/dist/core/verification-evidence.js +249 -0
- package/examples/README.md +12 -4
- package/package.json +1 -1
- package/schemas/README.md +13 -3
- package/schemas/change-verification-report.schema.json +16 -2
- package/schemas/commands.schema.json +4 -0
- package/schemas/contract-lint-report.schema.json +29 -0
- package/schemas/dashboard-export.schema.json +227 -0
- package/schemas/latest-run-pointer.schema.json +384 -0
- package/schemas/run-receipt.schema.json +4 -0
- package/schemas/test-selection.schema.json +81 -0
- package/schemas/verify-report.schema.json +361 -1
- package/schemas/verify-run-manifest.schema.json +410 -0
- package/templates/default/i18n.toml +1 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +124 -29
- package/templates/default/locales/en/.mustflow/skills/routes.toml +289 -0
- package/templates/default/manifest.toml +29 -2
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { createClassifyOutput } from './classify.js';
|
|
4
5
|
import { runRun } from './run.js';
|
|
5
6
|
import { createChangeVerificationReport, } from '../../core/change-verification.js';
|
|
7
|
+
import { createVerifyCompletionVerdict } from '../../core/completion-verdict.js';
|
|
8
|
+
import { createExternalEvidenceRisks, } from '../../core/external-evidence.js';
|
|
9
|
+
import { createRepeatedFailureRisk } from '../../core/repeated-failure.js';
|
|
10
|
+
import { createReproEvidenceRisks, } from '../../core/repro-evidence.js';
|
|
11
|
+
import { createVerifyEvidenceModel } from '../../core/verification-evidence.js';
|
|
12
|
+
import { createScopeDiffRisks } from '../../core/scope-risk.js';
|
|
13
|
+
import { createValidationRatchetRisks } from '../../core/validation-ratchet.js';
|
|
6
14
|
import { readCommandContract } from '../../core/config-loading.js';
|
|
7
15
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
8
16
|
import { t } from '../lib/i18n.js';
|
|
9
|
-
import { readLocalCommandEffectGraph, readLocalPathSurfaces, } from '../lib/local-index.js';
|
|
17
|
+
import { readLocalCommandEffectGraph, readLocalPathSurfaces, readLocalSourceAnchorVerdictRisks, } from '../lib/local-index.js';
|
|
10
18
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
11
19
|
const VERIFY_SCHEMA_VERSION = '1';
|
|
12
20
|
const VERIFY_RUN_DIR = path.join('.mustflow', 'state', 'runs', 'verify-latest');
|
|
21
|
+
const VERIFY_MANIFEST_PATH = path.join(VERIFY_RUN_DIR, 'manifest.json');
|
|
13
22
|
const LATEST_RUN_RECEIPT_PATH = path.join('.mustflow', 'state', 'runs', 'latest.json');
|
|
14
23
|
function createBufferedOutput() {
|
|
15
24
|
const stdout = [];
|
|
@@ -33,13 +42,16 @@ function createBufferedOutput() {
|
|
|
33
42
|
}
|
|
34
43
|
export function getVerifyHelp(lang = 'en') {
|
|
35
44
|
return renderHelp({
|
|
36
|
-
usage: 'mf verify --reason <event> [options] | mf verify --from-
|
|
45
|
+
usage: 'mf verify --reason <event> [options] | mf verify --from-classification <path> [options] | mf verify --changed [options]',
|
|
37
46
|
summary: t(lang, 'verify.help.summary'),
|
|
38
47
|
options: [
|
|
39
48
|
{ label: '--reason <event>', description: t(lang, 'verify.help.option.reason') },
|
|
49
|
+
{ label: '--from-classification <path>', description: t(lang, 'verify.help.option.fromClassification') },
|
|
40
50
|
{ label: '--from-plan <path>', description: t(lang, 'verify.help.option.fromPlan') },
|
|
41
51
|
{ label: '--changed', description: t(lang, 'verify.help.option.changed') },
|
|
42
52
|
{ label: '--write-plan <path>', description: t(lang, 'verify.help.option.writePlan') },
|
|
53
|
+
{ label: '--repro-evidence <path>', description: t(lang, 'verify.help.option.reproEvidence') },
|
|
54
|
+
{ label: '--external-evidence <path>', description: t(lang, 'verify.help.option.externalEvidence') },
|
|
43
55
|
{ label: '--plan-only', description: t(lang, 'verify.help.option.planOnly') },
|
|
44
56
|
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
45
57
|
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
@@ -48,9 +60,9 @@ export function getVerifyHelp(lang = 'en') {
|
|
|
48
60
|
'mf verify --reason code_change',
|
|
49
61
|
'mf verify --reason docs_change --json',
|
|
50
62
|
'mf verify --reason docs_change --plan-only --json',
|
|
51
|
-
'mf verify --from-
|
|
63
|
+
'mf verify --from-classification .mustflow/state/change-classification.json --json',
|
|
64
|
+
'mf verify --reason bug_fix --repro-evidence repro-evidence.json --json',
|
|
52
65
|
'mf verify --changed --plan-only --json',
|
|
53
|
-
'mf verify --changed --write-plan .mustflow/state/change-plan.json --json',
|
|
54
66
|
'mf verify --reason mustflow_docs_change',
|
|
55
67
|
],
|
|
56
68
|
exitCodes: [
|
|
@@ -61,8 +73,11 @@ export function getVerifyHelp(lang = 'en') {
|
|
|
61
73
|
}
|
|
62
74
|
function parseVerifyArgs(args) {
|
|
63
75
|
let reason;
|
|
76
|
+
let fromClassification;
|
|
64
77
|
let fromPlan;
|
|
65
78
|
let writePlan;
|
|
79
|
+
let reproEvidence;
|
|
80
|
+
let externalEvidence;
|
|
66
81
|
let json = false;
|
|
67
82
|
let planOnly = false;
|
|
68
83
|
let changed = false;
|
|
@@ -92,21 +107,86 @@ function parseVerifyArgs(args) {
|
|
|
92
107
|
if (arg === '--from-plan') {
|
|
93
108
|
const value = args[index + 1];
|
|
94
109
|
if (!value || value.startsWith('-')) {
|
|
95
|
-
return { json, planOnly, changed, reason, fromPlan, error: 'missing_from_plan_value' };
|
|
110
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, error: 'missing_from_plan_value' };
|
|
96
111
|
}
|
|
97
112
|
fromPlan = value;
|
|
98
113
|
index += 1;
|
|
99
114
|
continue;
|
|
100
115
|
}
|
|
116
|
+
if (arg === '--from-classification') {
|
|
117
|
+
const value = args[index + 1];
|
|
118
|
+
if (!value || value.startsWith('-')) {
|
|
119
|
+
return {
|
|
120
|
+
json,
|
|
121
|
+
planOnly,
|
|
122
|
+
changed,
|
|
123
|
+
reason,
|
|
124
|
+
fromClassification,
|
|
125
|
+
fromPlan,
|
|
126
|
+
error: 'missing_from_classification_value',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
fromClassification = value;
|
|
130
|
+
index += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
101
133
|
if (arg === '--write-plan') {
|
|
102
134
|
const value = args[index + 1];
|
|
103
135
|
if (!value || value.startsWith('-')) {
|
|
104
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
json,
|
|
138
|
+
planOnly,
|
|
139
|
+
changed,
|
|
140
|
+
reason,
|
|
141
|
+
fromClassification,
|
|
142
|
+
fromPlan,
|
|
143
|
+
writePlan,
|
|
144
|
+
error: 'missing_write_plan_value',
|
|
145
|
+
};
|
|
105
146
|
}
|
|
106
147
|
writePlan = value;
|
|
107
148
|
index += 1;
|
|
108
149
|
continue;
|
|
109
150
|
}
|
|
151
|
+
if (arg === '--external-evidence') {
|
|
152
|
+
const value = args[index + 1];
|
|
153
|
+
if (!value || value.startsWith('-')) {
|
|
154
|
+
return {
|
|
155
|
+
json,
|
|
156
|
+
planOnly,
|
|
157
|
+
changed,
|
|
158
|
+
reason,
|
|
159
|
+
fromClassification,
|
|
160
|
+
fromPlan,
|
|
161
|
+
writePlan,
|
|
162
|
+
externalEvidence,
|
|
163
|
+
error: 'missing_external_evidence_value',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
externalEvidence = value;
|
|
167
|
+
index += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (arg === '--repro-evidence') {
|
|
171
|
+
const value = args[index + 1];
|
|
172
|
+
if (!value || value.startsWith('-')) {
|
|
173
|
+
return {
|
|
174
|
+
json,
|
|
175
|
+
planOnly,
|
|
176
|
+
changed,
|
|
177
|
+
reason,
|
|
178
|
+
fromClassification,
|
|
179
|
+
fromPlan,
|
|
180
|
+
writePlan,
|
|
181
|
+
reproEvidence,
|
|
182
|
+
externalEvidence,
|
|
183
|
+
error: 'missing_repro_evidence_value',
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
reproEvidence = value;
|
|
187
|
+
index += 1;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
110
190
|
if (arg.startsWith('--reason=')) {
|
|
111
191
|
const value = arg.slice('--reason='.length);
|
|
112
192
|
if (value.length === 0) {
|
|
@@ -118,25 +198,98 @@ function parseVerifyArgs(args) {
|
|
|
118
198
|
if (arg.startsWith('--from-plan=')) {
|
|
119
199
|
const value = arg.slice('--from-plan='.length);
|
|
120
200
|
if (value.length === 0) {
|
|
121
|
-
return { json, planOnly, changed, reason, fromPlan, error: 'missing_from_plan_value' };
|
|
201
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, error: 'missing_from_plan_value' };
|
|
122
202
|
}
|
|
123
203
|
fromPlan = value;
|
|
124
204
|
continue;
|
|
125
205
|
}
|
|
206
|
+
if (arg.startsWith('--from-classification=')) {
|
|
207
|
+
const value = arg.slice('--from-classification='.length);
|
|
208
|
+
if (value.length === 0) {
|
|
209
|
+
return {
|
|
210
|
+
json,
|
|
211
|
+
planOnly,
|
|
212
|
+
changed,
|
|
213
|
+
reason,
|
|
214
|
+
fromClassification,
|
|
215
|
+
fromPlan,
|
|
216
|
+
error: 'missing_from_classification_value',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
fromClassification = value;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
126
222
|
if (arg.startsWith('--write-plan=')) {
|
|
127
223
|
const value = arg.slice('--write-plan='.length);
|
|
128
224
|
if (value.length === 0) {
|
|
129
|
-
return {
|
|
225
|
+
return {
|
|
226
|
+
json,
|
|
227
|
+
planOnly,
|
|
228
|
+
changed,
|
|
229
|
+
reason,
|
|
230
|
+
fromClassification,
|
|
231
|
+
fromPlan,
|
|
232
|
+
writePlan,
|
|
233
|
+
error: 'missing_write_plan_value',
|
|
234
|
+
};
|
|
130
235
|
}
|
|
131
236
|
writePlan = value;
|
|
132
237
|
continue;
|
|
133
238
|
}
|
|
239
|
+
if (arg.startsWith('--external-evidence=')) {
|
|
240
|
+
const value = arg.slice('--external-evidence='.length);
|
|
241
|
+
if (value.length === 0) {
|
|
242
|
+
return {
|
|
243
|
+
json,
|
|
244
|
+
planOnly,
|
|
245
|
+
changed,
|
|
246
|
+
reason,
|
|
247
|
+
fromClassification,
|
|
248
|
+
fromPlan,
|
|
249
|
+
writePlan,
|
|
250
|
+
externalEvidence,
|
|
251
|
+
error: 'missing_external_evidence_value',
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
externalEvidence = value;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (arg.startsWith('--repro-evidence=')) {
|
|
258
|
+
const value = arg.slice('--repro-evidence='.length);
|
|
259
|
+
if (value.length === 0) {
|
|
260
|
+
return {
|
|
261
|
+
json,
|
|
262
|
+
planOnly,
|
|
263
|
+
changed,
|
|
264
|
+
reason,
|
|
265
|
+
fromClassification,
|
|
266
|
+
fromPlan,
|
|
267
|
+
writePlan,
|
|
268
|
+
reproEvidence,
|
|
269
|
+
externalEvidence,
|
|
270
|
+
error: 'missing_repro_evidence_value',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
reproEvidence = value;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
134
276
|
if (arg.startsWith('-')) {
|
|
135
|
-
return { json, planOnly, changed, reason, fromPlan, writePlan, error: arg };
|
|
277
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, writePlan, reproEvidence, externalEvidence, error: arg };
|
|
136
278
|
}
|
|
137
|
-
return {
|
|
279
|
+
return {
|
|
280
|
+
json,
|
|
281
|
+
planOnly,
|
|
282
|
+
changed,
|
|
283
|
+
reason,
|
|
284
|
+
fromClassification,
|
|
285
|
+
fromPlan,
|
|
286
|
+
writePlan,
|
|
287
|
+
reproEvidence,
|
|
288
|
+
externalEvidence,
|
|
289
|
+
error: `unexpected:${arg}`,
|
|
290
|
+
};
|
|
138
291
|
}
|
|
139
|
-
return { json, planOnly, changed, reason, fromPlan, writePlan };
|
|
292
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, writePlan, reproEvidence, externalEvidence };
|
|
140
293
|
}
|
|
141
294
|
function uniqueStrings(values) {
|
|
142
295
|
return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))];
|
|
@@ -307,6 +460,91 @@ export function readInputFromPlan(projectRoot, inputPath) {
|
|
|
307
460
|
classificationReport,
|
|
308
461
|
};
|
|
309
462
|
}
|
|
463
|
+
function isExternalEvidenceStatus(value) {
|
|
464
|
+
return value === 'passed' || value === 'failed' || value === 'cancelled' || value === 'unknown';
|
|
465
|
+
}
|
|
466
|
+
function isReproEvidenceStatus(value) {
|
|
467
|
+
return value === 'present' || value === 'unavailable' || value === 'missing';
|
|
468
|
+
}
|
|
469
|
+
function readOptionalString(value) {
|
|
470
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
471
|
+
}
|
|
472
|
+
function readReproEvidenceItem(value) {
|
|
473
|
+
if (!isPlainRecord(value)) {
|
|
474
|
+
return {
|
|
475
|
+
status: 'missing',
|
|
476
|
+
summary: null,
|
|
477
|
+
reason: null,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (!isReproEvidenceStatus(value.status)) {
|
|
481
|
+
throw new Error('invalid_repro_evidence_file');
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
status: value.status,
|
|
485
|
+
summary: readOptionalString(value.summary),
|
|
486
|
+
reason: readOptionalString(value.reason),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function readReproEvidenceFile(projectRoot, inputPath) {
|
|
490
|
+
let parsed;
|
|
491
|
+
const evidencePath = resolvePlanPath(projectRoot, inputPath);
|
|
492
|
+
try {
|
|
493
|
+
parsed = JSON.parse(readFileSync(evidencePath, 'utf8'));
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
throw new Error('invalid_repro_evidence_file');
|
|
497
|
+
}
|
|
498
|
+
if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'repro-evidence') {
|
|
499
|
+
throw new Error('unsupported_repro_evidence_source');
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
source: 'repro_first_debug',
|
|
503
|
+
authority: 'claim_evidence',
|
|
504
|
+
reported_symptom: readOptionalString(parsed.reported_symptom),
|
|
505
|
+
expected_behavior: readOptionalString(parsed.expected_behavior),
|
|
506
|
+
observed_behavior: readOptionalString(parsed.observed_behavior),
|
|
507
|
+
original_reproduction: readReproEvidenceItem(parsed.original_reproduction),
|
|
508
|
+
evidence_before_fix: readReproEvidenceItem(parsed.evidence_before_fix),
|
|
509
|
+
evidence_after_fix: readReproEvidenceItem(parsed.evidence_after_fix),
|
|
510
|
+
regression_guard: readReproEvidenceItem(parsed.regression_guard),
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
function readExternalEvidenceFile(projectRoot, inputPath) {
|
|
514
|
+
let parsed;
|
|
515
|
+
const evidencePath = resolvePlanPath(projectRoot, inputPath);
|
|
516
|
+
try {
|
|
517
|
+
parsed = JSON.parse(readFileSync(evidencePath, 'utf8'));
|
|
518
|
+
}
|
|
519
|
+
catch {
|
|
520
|
+
throw new Error('invalid_external_evidence_file');
|
|
521
|
+
}
|
|
522
|
+
if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'external-evidence') {
|
|
523
|
+
throw new Error('unsupported_external_evidence_source');
|
|
524
|
+
}
|
|
525
|
+
if (!Array.isArray(parsed.checks)) {
|
|
526
|
+
throw new Error('invalid_external_evidence_file');
|
|
527
|
+
}
|
|
528
|
+
return parsed.checks.map((check) => {
|
|
529
|
+
if (!isPlainRecord(check) ||
|
|
530
|
+
typeof check.provider !== 'string' ||
|
|
531
|
+
check.provider.length === 0 ||
|
|
532
|
+
typeof check.name !== 'string' ||
|
|
533
|
+
check.name.length === 0 ||
|
|
534
|
+
!isExternalEvidenceStatus(check.status)) {
|
|
535
|
+
throw new Error('invalid_external_evidence_file');
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
source: 'external_ci',
|
|
539
|
+
authority: 'supporting_only',
|
|
540
|
+
provider: check.provider,
|
|
541
|
+
name: check.name,
|
|
542
|
+
status: check.status,
|
|
543
|
+
url: readOptionalString(check.url),
|
|
544
|
+
summary: readOptionalString(check.summary),
|
|
545
|
+
};
|
|
546
|
+
});
|
|
547
|
+
}
|
|
310
548
|
function createInputFromChanged(projectRoot) {
|
|
311
549
|
const plan = createClassifyOutput(projectRoot, 'changed', []);
|
|
312
550
|
return {
|
|
@@ -344,6 +582,9 @@ function skippedResult(candidate) {
|
|
|
344
582
|
reason: candidate.reason,
|
|
345
583
|
detail: candidate.detail,
|
|
346
584
|
exit_code: null,
|
|
585
|
+
verification_plan_id: null,
|
|
586
|
+
receipt_path: null,
|
|
587
|
+
receipt_sha256: null,
|
|
347
588
|
receipt: null,
|
|
348
589
|
};
|
|
349
590
|
}
|
|
@@ -383,7 +624,7 @@ function testTargetsByScheduledIntent(report) {
|
|
|
383
624
|
candidate.appliedTestTargets.length > 0)
|
|
384
625
|
.map((candidate) => [candidate.intent, candidate.appliedTestTargets]));
|
|
385
626
|
}
|
|
386
|
-
async function runVerificationIntent(intent, lang, testTargets = []) {
|
|
627
|
+
async function runVerificationIntent(intent, lang, verificationPlanId, testTargets = []) {
|
|
387
628
|
const output = createBufferedOutput();
|
|
388
629
|
const exitCode = await runRun([intent, '--json'], output.reporter, lang, {
|
|
389
630
|
writeLatestReceipt: false,
|
|
@@ -413,6 +654,9 @@ async function runVerificationIntent(intent, lang, testTargets = []) {
|
|
|
413
654
|
reason: exitCode === 0 ? null : 'run_failed',
|
|
414
655
|
detail: output.stderr().trim() || null,
|
|
415
656
|
exit_code: exitCode,
|
|
657
|
+
verification_plan_id: verificationPlanId,
|
|
658
|
+
receipt_path: null,
|
|
659
|
+
receipt_sha256: null,
|
|
416
660
|
receipt,
|
|
417
661
|
};
|
|
418
662
|
}
|
|
@@ -442,35 +686,141 @@ function getVerificationStatus(summary) {
|
|
|
442
686
|
}
|
|
443
687
|
return 'passed';
|
|
444
688
|
}
|
|
445
|
-
function
|
|
689
|
+
function isVerificationStatus(value) {
|
|
690
|
+
return value === 'passed' || value === 'partial' || value === 'failed' || value === 'blocked';
|
|
691
|
+
}
|
|
692
|
+
function readPreviousVerifyLatestSummary(projectRoot) {
|
|
693
|
+
try {
|
|
694
|
+
const parsed = JSON.parse(readFileSync(path.join(projectRoot, LATEST_RUN_RECEIPT_PATH), 'utf8'));
|
|
695
|
+
if (parsed.command !== 'verify' ||
|
|
696
|
+
parsed.kind !== 'verify_run_summary' ||
|
|
697
|
+
typeof parsed.verification_plan_id !== 'string' ||
|
|
698
|
+
!isVerificationStatus(parsed.status)) {
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
verification_plan_id: parsed.verification_plan_id,
|
|
703
|
+
status: parsed.status,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
catch {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function hashTextSha256(content) {
|
|
711
|
+
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
712
|
+
}
|
|
713
|
+
function stableJson(value) {
|
|
714
|
+
if (Array.isArray(value)) {
|
|
715
|
+
return `[${value.map((entry) => stableJson(entry)).join(',')}]`;
|
|
716
|
+
}
|
|
717
|
+
if (value && typeof value === 'object') {
|
|
718
|
+
const record = value;
|
|
719
|
+
return `{${Object.keys(record)
|
|
720
|
+
.sort((left, right) => left.localeCompare(right))
|
|
721
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`)
|
|
722
|
+
.join(',')}}`;
|
|
723
|
+
}
|
|
724
|
+
return JSON.stringify(value) ?? 'null';
|
|
725
|
+
}
|
|
726
|
+
function getCandidateIntentNames(report) {
|
|
727
|
+
return [...new Set(report.candidates.map((candidate) => candidate.intent).filter((intent) => Boolean(intent)))]
|
|
728
|
+
.sort((left, right) => left.localeCompare(right));
|
|
729
|
+
}
|
|
730
|
+
function createVerificationPlanId(report, contract) {
|
|
731
|
+
const relatedIntents = Object.fromEntries(getCandidateIntentNames(report).map((intent) => [intent, contract.intents[intent] ?? null]));
|
|
732
|
+
const fingerprintSource = {
|
|
733
|
+
schema_version: '1',
|
|
734
|
+
algorithm: 'mustflow.verify_plan_id.v1',
|
|
735
|
+
report: {
|
|
736
|
+
source: report.source,
|
|
737
|
+
files: report.files,
|
|
738
|
+
classification_summary: report.classification_summary,
|
|
739
|
+
requirements: report.requirements,
|
|
740
|
+
candidates: report.candidates,
|
|
741
|
+
gaps: report.gaps,
|
|
742
|
+
schedule: report.schedule,
|
|
743
|
+
test_selection: report.test_selection,
|
|
744
|
+
},
|
|
745
|
+
command_contract: {
|
|
746
|
+
defaults: contract.defaults,
|
|
747
|
+
resources: contract.resources,
|
|
748
|
+
intents: relatedIntents,
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
return hashTextSha256(stableJson(fingerprintSource));
|
|
752
|
+
}
|
|
753
|
+
function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks, scopeDiffRisks, repeatedFailureRisks, validationRatchetRisks, reproEvidence, externalChecks) {
|
|
446
754
|
const runDir = path.join(projectRoot, VERIFY_RUN_DIR);
|
|
447
755
|
const intentDir = path.join(runDir, 'intents');
|
|
448
756
|
const receipts = [];
|
|
757
|
+
const results = [];
|
|
449
758
|
rmSync(runDir, { recursive: true, force: true });
|
|
450
759
|
mkdirSync(intentDir, { recursive: true });
|
|
451
760
|
for (const [index, result] of output.results.entries()) {
|
|
452
761
|
let receiptPath = null;
|
|
762
|
+
let receiptSha256 = null;
|
|
763
|
+
let receipt = result.receipt;
|
|
453
764
|
if (result.intent && result.receipt) {
|
|
454
765
|
const fileName = `${String(index + 1).padStart(3, '0')}-${sanitizeIntentFilePart(result.intent)}.json`;
|
|
455
766
|
const absoluteReceiptPath = path.join(intentDir, fileName);
|
|
456
767
|
receiptPath = toPosixPath(path.join(VERIFY_RUN_DIR, 'intents', fileName));
|
|
457
|
-
|
|
768
|
+
receipt = {
|
|
769
|
+
...result.receipt,
|
|
770
|
+
verification_plan_id: output.verification_plan_id,
|
|
771
|
+
receipt_path: receiptPath,
|
|
772
|
+
};
|
|
773
|
+
const receiptContent = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
774
|
+
receiptSha256 = hashTextSha256(receiptContent);
|
|
775
|
+
writeFileSync(absoluteReceiptPath, receiptContent, 'utf8');
|
|
458
776
|
}
|
|
459
777
|
receipts.push({
|
|
460
778
|
intent: result.intent,
|
|
461
779
|
status: result.status,
|
|
462
780
|
skipped: result.skipped,
|
|
781
|
+
verification_plan_id: result.skipped ? null : output.verification_plan_id,
|
|
463
782
|
receipt_path: receiptPath,
|
|
783
|
+
receipt_sha256: receiptSha256,
|
|
784
|
+
});
|
|
785
|
+
results.push({
|
|
786
|
+
...result,
|
|
787
|
+
verification_plan_id: result.skipped ? null : output.verification_plan_id,
|
|
788
|
+
receipt_path: receiptPath,
|
|
789
|
+
receipt_sha256: receiptSha256,
|
|
790
|
+
receipt,
|
|
464
791
|
});
|
|
465
792
|
}
|
|
793
|
+
const outputWithReceiptPaths = {
|
|
794
|
+
...output,
|
|
795
|
+
results,
|
|
796
|
+
evidence_model: createVerifyEvidenceModel({
|
|
797
|
+
report,
|
|
798
|
+
results,
|
|
799
|
+
verificationPlanId: output.verification_plan_id,
|
|
800
|
+
verdict: output.completion_verdict,
|
|
801
|
+
sourceAnchorRisks,
|
|
802
|
+
scopeDiffRisks,
|
|
803
|
+
repeatedFailureRisks,
|
|
804
|
+
validationRatchetRisks,
|
|
805
|
+
reproEvidence,
|
|
806
|
+
reproEvidenceRisks: createReproEvidenceRisks(reproEvidence),
|
|
807
|
+
externalChecks,
|
|
808
|
+
externalEvidenceRisks: createExternalEvidenceRisks(externalChecks),
|
|
809
|
+
}),
|
|
810
|
+
};
|
|
466
811
|
const manifest = {
|
|
467
812
|
schema_version: '1',
|
|
468
813
|
command: 'verify',
|
|
469
|
-
reason:
|
|
470
|
-
reasons:
|
|
471
|
-
plan_source:
|
|
472
|
-
|
|
473
|
-
|
|
814
|
+
reason: outputWithReceiptPaths.reason,
|
|
815
|
+
reasons: outputWithReceiptPaths.reasons,
|
|
816
|
+
plan_source: outputWithReceiptPaths.plan_source,
|
|
817
|
+
verification_plan_id: outputWithReceiptPaths.verification_plan_id,
|
|
818
|
+
status: outputWithReceiptPaths.status,
|
|
819
|
+
completion_verdict: outputWithReceiptPaths.completion_verdict,
|
|
820
|
+
evidence_model: outputWithReceiptPaths.evidence_model,
|
|
821
|
+
summary: outputWithReceiptPaths.summary,
|
|
822
|
+
...(outputWithReceiptPaths.repro_evidence ? { repro_evidence: outputWithReceiptPaths.repro_evidence } : {}),
|
|
823
|
+
...(outputWithReceiptPaths.external_checks ? { external_checks: outputWithReceiptPaths.external_checks } : {}),
|
|
474
824
|
receipts,
|
|
475
825
|
};
|
|
476
826
|
writeFileSync(path.join(runDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
@@ -478,27 +828,77 @@ function writeVerifyRunReceipts(projectRoot, output) {
|
|
|
478
828
|
schema_version: '1',
|
|
479
829
|
command: 'verify',
|
|
480
830
|
kind: 'verify_run_summary',
|
|
481
|
-
reason:
|
|
482
|
-
reasons:
|
|
483
|
-
plan_source:
|
|
484
|
-
|
|
485
|
-
|
|
831
|
+
reason: outputWithReceiptPaths.reason,
|
|
832
|
+
reasons: outputWithReceiptPaths.reasons,
|
|
833
|
+
plan_source: outputWithReceiptPaths.plan_source,
|
|
834
|
+
verification_plan_id: outputWithReceiptPaths.verification_plan_id,
|
|
835
|
+
status: outputWithReceiptPaths.status,
|
|
836
|
+
completion_verdict: outputWithReceiptPaths.completion_verdict,
|
|
837
|
+
evidence_model: outputWithReceiptPaths.evidence_model,
|
|
838
|
+
summary: outputWithReceiptPaths.summary,
|
|
839
|
+
...(outputWithReceiptPaths.repro_evidence ? { repro_evidence: outputWithReceiptPaths.repro_evidence } : {}),
|
|
840
|
+
...(outputWithReceiptPaths.external_checks ? { external_checks: outputWithReceiptPaths.external_checks } : {}),
|
|
486
841
|
run_dir: toPosixPath(VERIFY_RUN_DIR),
|
|
487
|
-
manifest_path: toPosixPath(
|
|
842
|
+
manifest_path: toPosixPath(VERIFY_MANIFEST_PATH),
|
|
488
843
|
};
|
|
489
844
|
writeFileSync(path.join(projectRoot, LATEST_RUN_RECEIPT_PATH), `${JSON.stringify(latest, null, 2)}\n`, 'utf8');
|
|
845
|
+
return outputWithReceiptPaths;
|
|
490
846
|
}
|
|
491
|
-
async function createVerifyOutput(input, planSource, projectRoot, lang) {
|
|
847
|
+
async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvidence = null, externalChecks = []) {
|
|
492
848
|
const contract = readCommandContract(projectRoot);
|
|
493
849
|
const report = createChangeVerificationReport(input.classificationReport, contract, projectRoot);
|
|
850
|
+
const verificationPlanId = createVerificationPlanId(report, contract);
|
|
494
851
|
const scheduledIntents = new Set(report.schedule.entries.map((entry) => entry.intent));
|
|
495
852
|
const scheduledTestTargets = testTargetsByScheduledIntent(report);
|
|
853
|
+
const sourceAnchorRisks = await readLocalSourceAnchorVerdictRisks(projectRoot, report.files);
|
|
854
|
+
const scopeDiffRisks = createScopeDiffRisks(input.classificationReport);
|
|
855
|
+
const validationRatchetRisks = createValidationRatchetRisks(input.classificationReport, projectRoot);
|
|
856
|
+
const reproEvidenceRisks = createReproEvidenceRisks(reproEvidence);
|
|
857
|
+
const externalEvidenceRisks = createExternalEvidenceRisks(externalChecks);
|
|
496
858
|
const results = [];
|
|
497
859
|
for (const entry of report.schedule.entries) {
|
|
498
|
-
results.push(await runVerificationIntent(entry.intent, lang, scheduledTestTargets.get(entry.intent) ?? []));
|
|
860
|
+
results.push(await runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? []));
|
|
499
861
|
}
|
|
500
862
|
results.push(...createSkippedResults(report.candidates, scheduledIntents, report.gaps));
|
|
501
863
|
const summary = summarizeResults(results);
|
|
864
|
+
const status = getVerificationStatus(summary);
|
|
865
|
+
const previousVerifyLatest = readPreviousVerifyLatestSummary(projectRoot);
|
|
866
|
+
const repeatedFailureRisk = createRepeatedFailureRisk({
|
|
867
|
+
previousVerificationPlanId: previousVerifyLatest?.verification_plan_id ?? null,
|
|
868
|
+
previousStatus: previousVerifyLatest?.status ?? null,
|
|
869
|
+
currentVerificationPlanId: verificationPlanId,
|
|
870
|
+
currentStatus: status,
|
|
871
|
+
});
|
|
872
|
+
const repeatedFailureRisks = repeatedFailureRisk ? [repeatedFailureRisk] : [];
|
|
873
|
+
const completionVerdict = createVerifyCompletionVerdict({
|
|
874
|
+
verificationPlanId,
|
|
875
|
+
matchedIntents: summary.matched,
|
|
876
|
+
ranIntents: summary.ran,
|
|
877
|
+
passedIntents: summary.passed,
|
|
878
|
+
failedIntents: summary.failed,
|
|
879
|
+
skippedIntents: summary.skipped,
|
|
880
|
+
receiptCount: results.filter((result) => result.receipt !== null).length,
|
|
881
|
+
sourceAnchorRiskCount: sourceAnchorRisks.length,
|
|
882
|
+
scopeDiffRiskCount: scopeDiffRisks.length,
|
|
883
|
+
repeatedFailureCount: repeatedFailureRisks.length,
|
|
884
|
+
validationRatchetRiskCount: validationRatchetRisks.length,
|
|
885
|
+
reproEvidenceRiskCount: reproEvidenceRisks.length,
|
|
886
|
+
externalEvidenceRiskCount: externalEvidenceRisks.length,
|
|
887
|
+
});
|
|
888
|
+
const evidenceModel = createVerifyEvidenceModel({
|
|
889
|
+
report,
|
|
890
|
+
results,
|
|
891
|
+
verificationPlanId,
|
|
892
|
+
verdict: completionVerdict,
|
|
893
|
+
sourceAnchorRisks,
|
|
894
|
+
scopeDiffRisks,
|
|
895
|
+
repeatedFailureRisks,
|
|
896
|
+
validationRatchetRisks,
|
|
897
|
+
reproEvidence,
|
|
898
|
+
reproEvidenceRisks,
|
|
899
|
+
externalChecks,
|
|
900
|
+
externalEvidenceRisks,
|
|
901
|
+
});
|
|
502
902
|
const output = {
|
|
503
903
|
schema_version: VERIFY_SCHEMA_VERSION,
|
|
504
904
|
command: 'verify',
|
|
@@ -506,16 +906,23 @@ async function createVerifyOutput(input, planSource, projectRoot, lang) {
|
|
|
506
906
|
reason: input.reasons.join(', '),
|
|
507
907
|
reasons: input.reasons,
|
|
508
908
|
plan_source: planSource,
|
|
509
|
-
|
|
909
|
+
verification_plan_id: verificationPlanId,
|
|
910
|
+
status,
|
|
911
|
+
completion_verdict: completionVerdict,
|
|
912
|
+
evidence_model: evidenceModel,
|
|
510
913
|
summary,
|
|
914
|
+
...(reproEvidence ? { repro_evidence: reproEvidence } : {}),
|
|
915
|
+
...(externalChecks.length > 0 ? { external_checks: externalChecks } : {}),
|
|
916
|
+
run_dir: toPosixPath(VERIFY_RUN_DIR),
|
|
917
|
+
manifest_path: toPosixPath(VERIFY_MANIFEST_PATH),
|
|
511
918
|
results,
|
|
512
919
|
};
|
|
513
|
-
writeVerifyRunReceipts(projectRoot, output);
|
|
514
|
-
return output;
|
|
920
|
+
return writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks, scopeDiffRisks, repeatedFailureRisks, validationRatchetRisks, reproEvidence, externalChecks);
|
|
515
921
|
}
|
|
516
922
|
async function createPlanOnlyOutput(input, projectRoot) {
|
|
517
923
|
const contract = readCommandContract(projectRoot);
|
|
518
924
|
const report = createChangeVerificationReport(input.classificationReport, contract, projectRoot);
|
|
925
|
+
const verificationPlanId = createVerificationPlanId(report, contract);
|
|
519
926
|
const localSurfaceReadModels = await readLocalPathSurfaces(projectRoot, report.files);
|
|
520
927
|
const [firstEntry] = report.schedule.entries;
|
|
521
928
|
const requirements = report.requirements.map((requirement) => {
|
|
@@ -525,7 +932,7 @@ async function createPlanOnlyOutput(input, projectRoot) {
|
|
|
525
932
|
return surfaceReadModels.length > 0 ? { ...requirement, surfaceReadModels } : requirement;
|
|
526
933
|
});
|
|
527
934
|
if (!firstEntry) {
|
|
528
|
-
return { ...report, requirements };
|
|
935
|
+
return { ...report, verification_plan_id: verificationPlanId, requirements };
|
|
529
936
|
}
|
|
530
937
|
const firstGraph = await readLocalCommandEffectGraph(projectRoot, firstEntry.intent);
|
|
531
938
|
const graphsByIntent = new Map([[firstEntry.intent, firstGraph]]);
|
|
@@ -538,6 +945,7 @@ async function createPlanOnlyOutput(input, projectRoot) {
|
|
|
538
945
|
}
|
|
539
946
|
return {
|
|
540
947
|
...report,
|
|
948
|
+
verification_plan_id: verificationPlanId,
|
|
541
949
|
requirements,
|
|
542
950
|
schedule: {
|
|
543
951
|
...report.schedule,
|
|
@@ -555,6 +963,7 @@ function renderVerifyOutput(output, lang) {
|
|
|
555
963
|
`${t(lang, 'verify.label.reason')}: ${output.reason}`,
|
|
556
964
|
`${t(lang, 'verify.label.planSource')}: ${output.plan_source ?? t(lang, 'value.none')}`,
|
|
557
965
|
`${t(lang, 'verify.label.status')}: ${output.status}`,
|
|
966
|
+
`completion verdict: ${output.completion_verdict.status} (${output.completion_verdict.primary_reason})`,
|
|
558
967
|
`matched: ${output.summary.matched}`,
|
|
559
968
|
`ran: ${output.summary.ran}`,
|
|
560
969
|
`passed: ${output.summary.passed}`,
|
|
@@ -579,17 +988,28 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
579
988
|
if (parsed.error) {
|
|
580
989
|
const message = parsed.error === 'missing_reason_value'
|
|
581
990
|
? t(lang, 'cli.error.missingValue', { option: '--reason' })
|
|
582
|
-
: parsed.error === '
|
|
583
|
-
? t(lang, 'cli.error.missingValue', { option: '--from-
|
|
584
|
-
: parsed.error === '
|
|
585
|
-
? t(lang, 'cli.error.missingValue', { option: '--
|
|
586
|
-
: parsed.error
|
|
587
|
-
? t(lang, 'cli.error.
|
|
588
|
-
:
|
|
991
|
+
: parsed.error === 'missing_from_classification_value'
|
|
992
|
+
? t(lang, 'cli.error.missingValue', { option: '--from-classification' })
|
|
993
|
+
: parsed.error === 'missing_from_plan_value'
|
|
994
|
+
? t(lang, 'cli.error.missingValue', { option: '--from-plan' })
|
|
995
|
+
: parsed.error === 'missing_write_plan_value'
|
|
996
|
+
? t(lang, 'cli.error.missingValue', { option: '--write-plan' })
|
|
997
|
+
: parsed.error === 'missing_repro_evidence_value'
|
|
998
|
+
? t(lang, 'cli.error.missingValue', { option: '--repro-evidence' })
|
|
999
|
+
: parsed.error === 'missing_external_evidence_value'
|
|
1000
|
+
? t(lang, 'cli.error.missingValue', { option: '--external-evidence' })
|
|
1001
|
+
: parsed.error.startsWith('unexpected:')
|
|
1002
|
+
? t(lang, 'cli.error.unexpectedArgument', { argument: parsed.error.slice('unexpected:'.length) })
|
|
1003
|
+
: t(lang, 'cli.error.unknownOption', { option: parsed.error });
|
|
589
1004
|
printUsageError(reporter, message, 'mf verify --help', getVerifyHelp(lang), lang);
|
|
590
1005
|
return 1;
|
|
591
1006
|
}
|
|
592
|
-
const selectedInputCount = [
|
|
1007
|
+
const selectedInputCount = [
|
|
1008
|
+
parsed.reason,
|
|
1009
|
+
parsed.fromClassification,
|
|
1010
|
+
parsed.fromPlan,
|
|
1011
|
+
parsed.changed ? 'changed' : undefined,
|
|
1012
|
+
].filter(Boolean).length;
|
|
593
1013
|
if (selectedInputCount > 1) {
|
|
594
1014
|
printUsageError(reporter, t(lang, 'verify.error.conflictingInputs'), 'mf verify --help', getVerifyHelp(lang), lang);
|
|
595
1015
|
return 1;
|
|
@@ -606,17 +1026,27 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
606
1026
|
printUsageError(reporter, t(lang, 'verify.error.planOnlyJson'), 'mf verify --help', getVerifyHelp(lang), lang);
|
|
607
1027
|
return 1;
|
|
608
1028
|
}
|
|
1029
|
+
if (parsed.planOnly && parsed.reproEvidence) {
|
|
1030
|
+
printUsageError(reporter, t(lang, 'verify.error.reproEvidenceRequiresRun'), 'mf verify --help', getVerifyHelp(lang), lang);
|
|
1031
|
+
return 1;
|
|
1032
|
+
}
|
|
1033
|
+
if (parsed.planOnly && parsed.externalEvidence) {
|
|
1034
|
+
printUsageError(reporter, t(lang, 'verify.error.externalEvidenceRequiresRun'), 'mf verify --help', getVerifyHelp(lang), lang);
|
|
1035
|
+
return 1;
|
|
1036
|
+
}
|
|
609
1037
|
const projectRoot = resolveMustflowRoot();
|
|
610
1038
|
let input;
|
|
611
1039
|
let changedPlan = null;
|
|
1040
|
+
let reproEvidence = null;
|
|
1041
|
+
let externalChecks = [];
|
|
612
1042
|
try {
|
|
613
1043
|
if (parsed.changed) {
|
|
614
1044
|
const changedInput = createInputFromChanged(projectRoot);
|
|
615
1045
|
input = changedInput.input;
|
|
616
1046
|
changedPlan = changedInput.plan;
|
|
617
1047
|
}
|
|
618
|
-
else if (parsed.fromPlan) {
|
|
619
|
-
input = readInputFromPlan(projectRoot, parsed.fromPlan);
|
|
1048
|
+
else if (parsed.fromClassification || parsed.fromPlan) {
|
|
1049
|
+
input = readInputFromPlan(projectRoot, (parsed.fromClassification ?? parsed.fromPlan));
|
|
620
1050
|
}
|
|
621
1051
|
else {
|
|
622
1052
|
input = {
|
|
@@ -627,17 +1057,32 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
627
1057
|
if (parsed.writePlan && changedPlan) {
|
|
628
1058
|
writeChangedPlan(projectRoot, parsed.writePlan, changedPlan);
|
|
629
1059
|
}
|
|
1060
|
+
if (parsed.reproEvidence) {
|
|
1061
|
+
reproEvidence = readReproEvidenceFile(projectRoot, parsed.reproEvidence);
|
|
1062
|
+
}
|
|
1063
|
+
if (parsed.externalEvidence) {
|
|
1064
|
+
externalChecks = readExternalEvidenceFile(projectRoot, parsed.externalEvidence);
|
|
1065
|
+
}
|
|
630
1066
|
}
|
|
631
1067
|
catch (error) {
|
|
632
1068
|
const code = error instanceof Error ? error.message : 'invalid_plan_file';
|
|
633
|
-
|
|
1069
|
+
const message = code === 'invalid_repro_evidence_file'
|
|
1070
|
+
? t(lang, 'verify.error.invalid_repro_evidence_file')
|
|
1071
|
+
: code === 'unsupported_repro_evidence_source'
|
|
1072
|
+
? t(lang, 'verify.error.unsupported_repro_evidence_source')
|
|
1073
|
+
: code === 'invalid_external_evidence_file'
|
|
1074
|
+
? t(lang, 'verify.error.invalid_external_evidence_file')
|
|
1075
|
+
: code === 'unsupported_external_evidence_source'
|
|
1076
|
+
? t(lang, 'verify.error.unsupported_external_evidence_source')
|
|
1077
|
+
: t(lang, planErrorMessageKey(code));
|
|
1078
|
+
printUsageError(reporter, message, 'mf verify --help', getVerifyHelp(lang), lang);
|
|
634
1079
|
return 1;
|
|
635
1080
|
}
|
|
636
1081
|
if (parsed.planOnly) {
|
|
637
1082
|
reporter.stdout(JSON.stringify(await createPlanOnlyOutput(input, projectRoot), null, 2));
|
|
638
1083
|
return 0;
|
|
639
1084
|
}
|
|
640
|
-
const output = await createVerifyOutput(input, parsed.fromPlan ?? (parsed.changed ? 'changed' : null), projectRoot, lang);
|
|
1085
|
+
const output = await createVerifyOutput(input, parsed.fromClassification ?? parsed.fromPlan ?? (parsed.changed ? 'changed' : null), projectRoot, lang, reproEvidence, externalChecks);
|
|
641
1086
|
if (parsed.json) {
|
|
642
1087
|
reporter.stdout(JSON.stringify(output, null, 2));
|
|
643
1088
|
}
|