mustflow 1.30.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 +35 -11
- 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 +224 -48
- package/dist/cli/commands/upgrade.js +65 -0
- package/dist/cli/commands/verify.js +550 -33
- package/dist/cli/i18n/en.js +73 -10
- package/dist/cli/i18n/es.js +73 -10
- package/dist/cli/i18n/fr.js +73 -10
- package/dist/cli/i18n/hi.js +73 -10
- package/dist/cli/i18n/ko.js +73 -10
- package/dist/cli/i18n/zh.js +73 -10
- package/dist/cli/index.js +27 -46
- package/dist/cli/lib/command-registry.js +5 -0
- 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 -1908
- package/dist/cli/lib/reporter.js +6 -0
- package/dist/cli/lib/run-plan.js +96 -4
- 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 -1661
- package/dist/core/bounded-output.js +38 -0
- package/dist/core/change-classification.js +6 -2
- package/dist/core/change-verification.js +240 -6
- package/dist/core/check-issues.js +12 -0
- package/dist/core/command-contract-validation.js +20 -0
- package/dist/core/command-effects.js +13 -0
- package/dist/core/completion-verdict.js +209 -0
- package/dist/core/contract-lint.js +316 -7
- package/dist/core/dashboard-verification.js +8 -0
- package/dist/core/external-evidence.js +9 -0
- package/dist/core/public-json-contracts.js +28 -0
- package/dist/core/repeated-failure.js +17 -0
- package/dist/core/repro-evidence.js +53 -0
- package/dist/core/run-performance-history.js +307 -0
- package/dist/core/run-profile.js +87 -0
- package/dist/core/run-receipt.js +171 -4
- package/dist/core/run-write-drift.js +18 -2
- package/dist/core/scope-risk.js +64 -0
- package/dist/core/skill-route-alignment.js +110 -0
- package/dist/core/source-anchor-status.js +4 -1
- package/dist/core/test-selection.js +227 -0
- package/dist/core/validation-ratchet.js +52 -0
- package/dist/core/verification-decision-graph.js +67 -0
- package/dist/core/verification-evidence.js +249 -0
- package/dist/core/verification-scheduler.js +96 -2
- package/examples/README.md +12 -4
- package/package.json +1 -1
- package/schemas/README.md +18 -4
- package/schemas/change-verification-report.schema.json +169 -5
- package/schemas/commands.schema.json +51 -1
- package/schemas/contract-lint-report.schema.json +80 -0
- package/schemas/dashboard-export.schema.json +500 -0
- package/schemas/explain-report.schema.json +2 -0
- package/schemas/latest-run-pointer.schema.json +384 -0
- package/schemas/run-receipt.schema.json +113 -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/common/.mustflow/config/commands.toml +1 -1
- 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,14 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
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';
|
|
20
|
+
const VERIFY_RUN_DIR = path.join('.mustflow', 'state', 'runs', 'verify-latest');
|
|
21
|
+
const VERIFY_MANIFEST_PATH = path.join(VERIFY_RUN_DIR, 'manifest.json');
|
|
22
|
+
const LATEST_RUN_RECEIPT_PATH = path.join('.mustflow', 'state', 'runs', 'latest.json');
|
|
12
23
|
function createBufferedOutput() {
|
|
13
24
|
const stdout = [];
|
|
14
25
|
const stderr = [];
|
|
@@ -31,13 +42,16 @@ function createBufferedOutput() {
|
|
|
31
42
|
}
|
|
32
43
|
export function getVerifyHelp(lang = 'en') {
|
|
33
44
|
return renderHelp({
|
|
34
|
-
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]',
|
|
35
46
|
summary: t(lang, 'verify.help.summary'),
|
|
36
47
|
options: [
|
|
37
48
|
{ label: '--reason <event>', description: t(lang, 'verify.help.option.reason') },
|
|
49
|
+
{ label: '--from-classification <path>', description: t(lang, 'verify.help.option.fromClassification') },
|
|
38
50
|
{ label: '--from-plan <path>', description: t(lang, 'verify.help.option.fromPlan') },
|
|
39
51
|
{ label: '--changed', description: t(lang, 'verify.help.option.changed') },
|
|
40
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') },
|
|
41
55
|
{ label: '--plan-only', description: t(lang, 'verify.help.option.planOnly') },
|
|
42
56
|
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
43
57
|
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
@@ -46,9 +60,9 @@ export function getVerifyHelp(lang = 'en') {
|
|
|
46
60
|
'mf verify --reason code_change',
|
|
47
61
|
'mf verify --reason docs_change --json',
|
|
48
62
|
'mf verify --reason docs_change --plan-only --json',
|
|
49
|
-
'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',
|
|
50
65
|
'mf verify --changed --plan-only --json',
|
|
51
|
-
'mf verify --changed --write-plan .mustflow/state/change-plan.json --json',
|
|
52
66
|
'mf verify --reason mustflow_docs_change',
|
|
53
67
|
],
|
|
54
68
|
exitCodes: [
|
|
@@ -59,8 +73,11 @@ export function getVerifyHelp(lang = 'en') {
|
|
|
59
73
|
}
|
|
60
74
|
function parseVerifyArgs(args) {
|
|
61
75
|
let reason;
|
|
76
|
+
let fromClassification;
|
|
62
77
|
let fromPlan;
|
|
63
78
|
let writePlan;
|
|
79
|
+
let reproEvidence;
|
|
80
|
+
let externalEvidence;
|
|
64
81
|
let json = false;
|
|
65
82
|
let planOnly = false;
|
|
66
83
|
let changed = false;
|
|
@@ -90,21 +107,86 @@ function parseVerifyArgs(args) {
|
|
|
90
107
|
if (arg === '--from-plan') {
|
|
91
108
|
const value = args[index + 1];
|
|
92
109
|
if (!value || value.startsWith('-')) {
|
|
93
|
-
return { json, planOnly, changed, reason, fromPlan, error: 'missing_from_plan_value' };
|
|
110
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, error: 'missing_from_plan_value' };
|
|
94
111
|
}
|
|
95
112
|
fromPlan = value;
|
|
96
113
|
index += 1;
|
|
97
114
|
continue;
|
|
98
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
|
+
}
|
|
99
133
|
if (arg === '--write-plan') {
|
|
100
134
|
const value = args[index + 1];
|
|
101
135
|
if (!value || value.startsWith('-')) {
|
|
102
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
json,
|
|
138
|
+
planOnly,
|
|
139
|
+
changed,
|
|
140
|
+
reason,
|
|
141
|
+
fromClassification,
|
|
142
|
+
fromPlan,
|
|
143
|
+
writePlan,
|
|
144
|
+
error: 'missing_write_plan_value',
|
|
145
|
+
};
|
|
103
146
|
}
|
|
104
147
|
writePlan = value;
|
|
105
148
|
index += 1;
|
|
106
149
|
continue;
|
|
107
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
|
+
}
|
|
108
190
|
if (arg.startsWith('--reason=')) {
|
|
109
191
|
const value = arg.slice('--reason='.length);
|
|
110
192
|
if (value.length === 0) {
|
|
@@ -116,29 +198,109 @@ function parseVerifyArgs(args) {
|
|
|
116
198
|
if (arg.startsWith('--from-plan=')) {
|
|
117
199
|
const value = arg.slice('--from-plan='.length);
|
|
118
200
|
if (value.length === 0) {
|
|
119
|
-
return { json, planOnly, changed, reason, fromPlan, error: 'missing_from_plan_value' };
|
|
201
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, error: 'missing_from_plan_value' };
|
|
120
202
|
}
|
|
121
203
|
fromPlan = value;
|
|
122
204
|
continue;
|
|
123
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
|
+
}
|
|
124
222
|
if (arg.startsWith('--write-plan=')) {
|
|
125
223
|
const value = arg.slice('--write-plan='.length);
|
|
126
224
|
if (value.length === 0) {
|
|
127
|
-
return {
|
|
225
|
+
return {
|
|
226
|
+
json,
|
|
227
|
+
planOnly,
|
|
228
|
+
changed,
|
|
229
|
+
reason,
|
|
230
|
+
fromClassification,
|
|
231
|
+
fromPlan,
|
|
232
|
+
writePlan,
|
|
233
|
+
error: 'missing_write_plan_value',
|
|
234
|
+
};
|
|
128
235
|
}
|
|
129
236
|
writePlan = value;
|
|
130
237
|
continue;
|
|
131
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
|
+
}
|
|
132
276
|
if (arg.startsWith('-')) {
|
|
133
|
-
return { json, planOnly, changed, reason, fromPlan, writePlan, error: arg };
|
|
277
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, writePlan, reproEvidence, externalEvidence, error: arg };
|
|
134
278
|
}
|
|
135
|
-
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
|
+
};
|
|
136
291
|
}
|
|
137
|
-
return { json, planOnly, changed, reason, fromPlan, writePlan };
|
|
292
|
+
return { json, planOnly, changed, reason, fromClassification, fromPlan, writePlan, reproEvidence, externalEvidence };
|
|
138
293
|
}
|
|
139
294
|
function uniqueStrings(values) {
|
|
140
295
|
return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))];
|
|
141
296
|
}
|
|
297
|
+
function toPosixPath(value) {
|
|
298
|
+
return value.split(path.sep).join('/');
|
|
299
|
+
}
|
|
300
|
+
function sanitizeIntentFilePart(value) {
|
|
301
|
+
const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
302
|
+
return sanitized.length > 0 ? sanitized.slice(0, 80) : 'intent';
|
|
303
|
+
}
|
|
142
304
|
function readStringArray(value) {
|
|
143
305
|
if (!Array.isArray(value)) {
|
|
144
306
|
return [];
|
|
@@ -298,6 +460,91 @@ export function readInputFromPlan(projectRoot, inputPath) {
|
|
|
298
460
|
classificationReport,
|
|
299
461
|
};
|
|
300
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
|
+
}
|
|
301
548
|
function createInputFromChanged(projectRoot) {
|
|
302
549
|
const plan = createClassifyOutput(projectRoot, 'changed', []);
|
|
303
550
|
return {
|
|
@@ -335,6 +582,9 @@ function skippedResult(candidate) {
|
|
|
335
582
|
reason: candidate.reason,
|
|
336
583
|
detail: candidate.detail,
|
|
337
584
|
exit_code: null,
|
|
585
|
+
verification_plan_id: null,
|
|
586
|
+
receipt_path: null,
|
|
587
|
+
receipt_sha256: null,
|
|
338
588
|
receipt: null,
|
|
339
589
|
};
|
|
340
590
|
}
|
|
@@ -343,13 +593,17 @@ function candidateResultKey(candidate) {
|
|
|
343
593
|
? `intent:${candidate.intent}`
|
|
344
594
|
: `missing:${candidate.reason}:${candidate.skipReason ?? ''}:${candidate.detail ?? ''}`;
|
|
345
595
|
}
|
|
346
|
-
function createSkippedResults(candidates, scheduledIntents) {
|
|
596
|
+
function createSkippedResults(candidates, scheduledIntents, gaps) {
|
|
347
597
|
const seen = new Set();
|
|
348
598
|
const results = [];
|
|
599
|
+
const activeGapReasons = new Set(gaps.map((gap) => gap.reason));
|
|
349
600
|
for (const candidate of candidates) {
|
|
350
601
|
if (candidate.status === 'runnable' || (candidate.intent && scheduledIntents.has(candidate.intent))) {
|
|
351
602
|
continue;
|
|
352
603
|
}
|
|
604
|
+
if (candidate.candidateState === 'gap' && !activeGapReasons.has(candidate.reason)) {
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
353
607
|
const key = candidateResultKey(candidate);
|
|
354
608
|
if (seen.has(key)) {
|
|
355
609
|
continue;
|
|
@@ -363,9 +617,19 @@ function createSkippedResults(candidates, scheduledIntents) {
|
|
|
363
617
|
}
|
|
364
618
|
return results;
|
|
365
619
|
}
|
|
366
|
-
|
|
620
|
+
function testTargetsByScheduledIntent(report) {
|
|
621
|
+
return new Map(report.test_selection.selected
|
|
622
|
+
.filter((candidate) => candidate.status === 'runnable' &&
|
|
623
|
+
candidate.testTargetsApplied &&
|
|
624
|
+
candidate.appliedTestTargets.length > 0)
|
|
625
|
+
.map((candidate) => [candidate.intent, candidate.appliedTestTargets]));
|
|
626
|
+
}
|
|
627
|
+
async function runVerificationIntent(intent, lang, verificationPlanId, testTargets = []) {
|
|
367
628
|
const output = createBufferedOutput();
|
|
368
|
-
const exitCode = await runRun([intent, '--json'], output.reporter, lang
|
|
629
|
+
const exitCode = await runRun([intent, '--json'], output.reporter, lang, {
|
|
630
|
+
writeLatestReceipt: false,
|
|
631
|
+
testTargets,
|
|
632
|
+
});
|
|
369
633
|
const rawStdout = output.stdout().trim();
|
|
370
634
|
let receipt = null;
|
|
371
635
|
let status = exitCode === 0 ? 'passed' : 'failed';
|
|
@@ -390,6 +654,9 @@ async function runVerificationIntent(intent, lang) {
|
|
|
390
654
|
reason: exitCode === 0 ? null : 'run_failed',
|
|
391
655
|
detail: output.stderr().trim() || null,
|
|
392
656
|
exit_code: exitCode,
|
|
657
|
+
verification_plan_id: verificationPlanId,
|
|
658
|
+
receipt_path: null,
|
|
659
|
+
receipt_sha256: null,
|
|
393
660
|
receipt,
|
|
394
661
|
};
|
|
395
662
|
}
|
|
@@ -419,31 +686,243 @@ function getVerificationStatus(summary) {
|
|
|
419
686
|
}
|
|
420
687
|
return 'passed';
|
|
421
688
|
}
|
|
422
|
-
|
|
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) {
|
|
754
|
+
const runDir = path.join(projectRoot, VERIFY_RUN_DIR);
|
|
755
|
+
const intentDir = path.join(runDir, 'intents');
|
|
756
|
+
const receipts = [];
|
|
757
|
+
const results = [];
|
|
758
|
+
rmSync(runDir, { recursive: true, force: true });
|
|
759
|
+
mkdirSync(intentDir, { recursive: true });
|
|
760
|
+
for (const [index, result] of output.results.entries()) {
|
|
761
|
+
let receiptPath = null;
|
|
762
|
+
let receiptSha256 = null;
|
|
763
|
+
let receipt = result.receipt;
|
|
764
|
+
if (result.intent && result.receipt) {
|
|
765
|
+
const fileName = `${String(index + 1).padStart(3, '0')}-${sanitizeIntentFilePart(result.intent)}.json`;
|
|
766
|
+
const absoluteReceiptPath = path.join(intentDir, fileName);
|
|
767
|
+
receiptPath = toPosixPath(path.join(VERIFY_RUN_DIR, 'intents', fileName));
|
|
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');
|
|
776
|
+
}
|
|
777
|
+
receipts.push({
|
|
778
|
+
intent: result.intent,
|
|
779
|
+
status: result.status,
|
|
780
|
+
skipped: result.skipped,
|
|
781
|
+
verification_plan_id: result.skipped ? null : output.verification_plan_id,
|
|
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,
|
|
791
|
+
});
|
|
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
|
+
};
|
|
811
|
+
const manifest = {
|
|
812
|
+
schema_version: '1',
|
|
813
|
+
command: 'verify',
|
|
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 } : {}),
|
|
824
|
+
receipts,
|
|
825
|
+
};
|
|
826
|
+
writeFileSync(path.join(runDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
827
|
+
const latest = {
|
|
828
|
+
schema_version: '1',
|
|
829
|
+
command: 'verify',
|
|
830
|
+
kind: 'verify_run_summary',
|
|
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 } : {}),
|
|
841
|
+
run_dir: toPosixPath(VERIFY_RUN_DIR),
|
|
842
|
+
manifest_path: toPosixPath(VERIFY_MANIFEST_PATH),
|
|
843
|
+
};
|
|
844
|
+
writeFileSync(path.join(projectRoot, LATEST_RUN_RECEIPT_PATH), `${JSON.stringify(latest, null, 2)}\n`, 'utf8');
|
|
845
|
+
return outputWithReceiptPaths;
|
|
846
|
+
}
|
|
847
|
+
async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvidence = null, externalChecks = []) {
|
|
423
848
|
const contract = readCommandContract(projectRoot);
|
|
424
849
|
const report = createChangeVerificationReport(input.classificationReport, contract, projectRoot);
|
|
850
|
+
const verificationPlanId = createVerificationPlanId(report, contract);
|
|
425
851
|
const scheduledIntents = new Set(report.schedule.entries.map((entry) => entry.intent));
|
|
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);
|
|
426
858
|
const results = [];
|
|
427
859
|
for (const entry of report.schedule.entries) {
|
|
428
|
-
results.push(await runVerificationIntent(entry.intent, lang));
|
|
860
|
+
results.push(await runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? []));
|
|
429
861
|
}
|
|
430
|
-
results.push(...createSkippedResults(report.candidates, scheduledIntents));
|
|
862
|
+
results.push(...createSkippedResults(report.candidates, scheduledIntents, report.gaps));
|
|
431
863
|
const summary = summarizeResults(results);
|
|
432
|
-
|
|
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
|
+
});
|
|
902
|
+
const output = {
|
|
433
903
|
schema_version: VERIFY_SCHEMA_VERSION,
|
|
434
904
|
command: 'verify',
|
|
435
905
|
mustflow_root: projectRoot,
|
|
436
906
|
reason: input.reasons.join(', '),
|
|
437
907
|
reasons: input.reasons,
|
|
438
908
|
plan_source: planSource,
|
|
439
|
-
|
|
909
|
+
verification_plan_id: verificationPlanId,
|
|
910
|
+
status,
|
|
911
|
+
completion_verdict: completionVerdict,
|
|
912
|
+
evidence_model: evidenceModel,
|
|
440
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),
|
|
441
918
|
results,
|
|
442
919
|
};
|
|
920
|
+
return writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks, scopeDiffRisks, repeatedFailureRisks, validationRatchetRisks, reproEvidence, externalChecks);
|
|
443
921
|
}
|
|
444
922
|
async function createPlanOnlyOutput(input, projectRoot) {
|
|
445
923
|
const contract = readCommandContract(projectRoot);
|
|
446
924
|
const report = createChangeVerificationReport(input.classificationReport, contract, projectRoot);
|
|
925
|
+
const verificationPlanId = createVerificationPlanId(report, contract);
|
|
447
926
|
const localSurfaceReadModels = await readLocalPathSurfaces(projectRoot, report.files);
|
|
448
927
|
const [firstEntry] = report.schedule.entries;
|
|
449
928
|
const requirements = report.requirements.map((requirement) => {
|
|
@@ -453,7 +932,7 @@ async function createPlanOnlyOutput(input, projectRoot) {
|
|
|
453
932
|
return surfaceReadModels.length > 0 ? { ...requirement, surfaceReadModels } : requirement;
|
|
454
933
|
});
|
|
455
934
|
if (!firstEntry) {
|
|
456
|
-
return { ...report, requirements };
|
|
935
|
+
return { ...report, verification_plan_id: verificationPlanId, requirements };
|
|
457
936
|
}
|
|
458
937
|
const firstGraph = await readLocalCommandEffectGraph(projectRoot, firstEntry.intent);
|
|
459
938
|
const graphsByIntent = new Map([[firstEntry.intent, firstGraph]]);
|
|
@@ -466,6 +945,7 @@ async function createPlanOnlyOutput(input, projectRoot) {
|
|
|
466
945
|
}
|
|
467
946
|
return {
|
|
468
947
|
...report,
|
|
948
|
+
verification_plan_id: verificationPlanId,
|
|
469
949
|
requirements,
|
|
470
950
|
schedule: {
|
|
471
951
|
...report.schedule,
|
|
@@ -483,6 +963,7 @@ function renderVerifyOutput(output, lang) {
|
|
|
483
963
|
`${t(lang, 'verify.label.reason')}: ${output.reason}`,
|
|
484
964
|
`${t(lang, 'verify.label.planSource')}: ${output.plan_source ?? t(lang, 'value.none')}`,
|
|
485
965
|
`${t(lang, 'verify.label.status')}: ${output.status}`,
|
|
966
|
+
`completion verdict: ${output.completion_verdict.status} (${output.completion_verdict.primary_reason})`,
|
|
486
967
|
`matched: ${output.summary.matched}`,
|
|
487
968
|
`ran: ${output.summary.ran}`,
|
|
488
969
|
`passed: ${output.summary.passed}`,
|
|
@@ -507,17 +988,28 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
507
988
|
if (parsed.error) {
|
|
508
989
|
const message = parsed.error === 'missing_reason_value'
|
|
509
990
|
? t(lang, 'cli.error.missingValue', { option: '--reason' })
|
|
510
|
-
: parsed.error === '
|
|
511
|
-
? t(lang, 'cli.error.missingValue', { option: '--from-
|
|
512
|
-
: parsed.error === '
|
|
513
|
-
? t(lang, 'cli.error.missingValue', { option: '--
|
|
514
|
-
: parsed.error
|
|
515
|
-
? t(lang, 'cli.error.
|
|
516
|
-
:
|
|
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 });
|
|
517
1004
|
printUsageError(reporter, message, 'mf verify --help', getVerifyHelp(lang), lang);
|
|
518
1005
|
return 1;
|
|
519
1006
|
}
|
|
520
|
-
const selectedInputCount = [
|
|
1007
|
+
const selectedInputCount = [
|
|
1008
|
+
parsed.reason,
|
|
1009
|
+
parsed.fromClassification,
|
|
1010
|
+
parsed.fromPlan,
|
|
1011
|
+
parsed.changed ? 'changed' : undefined,
|
|
1012
|
+
].filter(Boolean).length;
|
|
521
1013
|
if (selectedInputCount > 1) {
|
|
522
1014
|
printUsageError(reporter, t(lang, 'verify.error.conflictingInputs'), 'mf verify --help', getVerifyHelp(lang), lang);
|
|
523
1015
|
return 1;
|
|
@@ -534,17 +1026,27 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
534
1026
|
printUsageError(reporter, t(lang, 'verify.error.planOnlyJson'), 'mf verify --help', getVerifyHelp(lang), lang);
|
|
535
1027
|
return 1;
|
|
536
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
|
+
}
|
|
537
1037
|
const projectRoot = resolveMustflowRoot();
|
|
538
1038
|
let input;
|
|
539
1039
|
let changedPlan = null;
|
|
1040
|
+
let reproEvidence = null;
|
|
1041
|
+
let externalChecks = [];
|
|
540
1042
|
try {
|
|
541
1043
|
if (parsed.changed) {
|
|
542
1044
|
const changedInput = createInputFromChanged(projectRoot);
|
|
543
1045
|
input = changedInput.input;
|
|
544
1046
|
changedPlan = changedInput.plan;
|
|
545
1047
|
}
|
|
546
|
-
else if (parsed.fromPlan) {
|
|
547
|
-
input = readInputFromPlan(projectRoot, parsed.fromPlan);
|
|
1048
|
+
else if (parsed.fromClassification || parsed.fromPlan) {
|
|
1049
|
+
input = readInputFromPlan(projectRoot, (parsed.fromClassification ?? parsed.fromPlan));
|
|
548
1050
|
}
|
|
549
1051
|
else {
|
|
550
1052
|
input = {
|
|
@@ -555,17 +1057,32 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
555
1057
|
if (parsed.writePlan && changedPlan) {
|
|
556
1058
|
writeChangedPlan(projectRoot, parsed.writePlan, changedPlan);
|
|
557
1059
|
}
|
|
1060
|
+
if (parsed.reproEvidence) {
|
|
1061
|
+
reproEvidence = readReproEvidenceFile(projectRoot, parsed.reproEvidence);
|
|
1062
|
+
}
|
|
1063
|
+
if (parsed.externalEvidence) {
|
|
1064
|
+
externalChecks = readExternalEvidenceFile(projectRoot, parsed.externalEvidence);
|
|
1065
|
+
}
|
|
558
1066
|
}
|
|
559
1067
|
catch (error) {
|
|
560
1068
|
const code = error instanceof Error ? error.message : 'invalid_plan_file';
|
|
561
|
-
|
|
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);
|
|
562
1079
|
return 1;
|
|
563
1080
|
}
|
|
564
1081
|
if (parsed.planOnly) {
|
|
565
1082
|
reporter.stdout(JSON.stringify(await createPlanOnlyOutput(input, projectRoot), null, 2));
|
|
566
1083
|
return 0;
|
|
567
1084
|
}
|
|
568
|
-
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);
|
|
569
1086
|
if (parsed.json) {
|
|
570
1087
|
reporter.stdout(JSON.stringify(output, null, 2));
|
|
571
1088
|
}
|