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
@@ -0,0 +1,269 @@
1
+ import { readVerifyJsonInputFile } from './input.js';
2
+ function isPlainRecord(value) {
3
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
4
+ }
5
+ function isExternalEvidenceStatus(value) {
6
+ return value === 'passed' || value === 'failed' || value === 'cancelled' || value === 'unknown';
7
+ }
8
+ function isLegacyReproEvidenceStatus(value) {
9
+ return value === 'present' || value === 'unavailable' || value === 'missing';
10
+ }
11
+ function isReproBeforeFixStatus(value) {
12
+ return value === 'reproduced' || value === 'unavailable' || value === 'missing';
13
+ }
14
+ function isReproBeforeFixOutcome(value) {
15
+ return value === 'failed_as_expected' || value === 'failed_differently' || value === 'passed_unexpectedly' || value === null;
16
+ }
17
+ function isReproAfterFixStatus(value) {
18
+ return value === 'passed' || value === 'failed' || value === 'unavailable' || value === 'missing';
19
+ }
20
+ function isReproAfterFixOutcome(value) {
21
+ return value === 'passed_expected_behavior' || value === 'failed_same_route' || value === 'failed_differently' || value === null;
22
+ }
23
+ function isReproRegressionGuardStatus(value) {
24
+ return value === 'passed' || value === 'failed' || value === 'unavailable' || value === 'missing';
25
+ }
26
+ function isReproRouteKind(value) {
27
+ return (value === 'test' ||
28
+ value === 'cli' ||
29
+ value === 'browser' ||
30
+ value === 'api' ||
31
+ value === 'manual' ||
32
+ value === 'unknown' ||
33
+ value === null);
34
+ }
35
+ function readOptionalString(value) {
36
+ return typeof value === 'string' && value.length > 0 ? value : null;
37
+ }
38
+ function readRouteStep(value, index) {
39
+ if (!isPlainRecord(value)) {
40
+ return {
41
+ ordinal: index + 1,
42
+ action: null,
43
+ target: null,
44
+ input_digest: null,
45
+ observation_digest: null,
46
+ summary: null,
47
+ };
48
+ }
49
+ const ordinal = typeof value.ordinal === 'number' && Number.isInteger(value.ordinal) && value.ordinal > 0 ? value.ordinal : index + 1;
50
+ return {
51
+ ordinal,
52
+ action: readOptionalString(value.action),
53
+ target: readOptionalString(value.target),
54
+ input_digest: readOptionalString(value.input_digest),
55
+ observation_digest: readOptionalString(value.observation_digest),
56
+ summary: readOptionalString(value.summary),
57
+ };
58
+ }
59
+ function readReproductionRoute(value) {
60
+ if (!isPlainRecord(value)) {
61
+ return {
62
+ route_id: null,
63
+ route_kind: null,
64
+ route_digest: null,
65
+ failure_oracle_hash: null,
66
+ steps: [],
67
+ };
68
+ }
69
+ const routeKind = value.route_kind ?? null;
70
+ if (!isReproRouteKind(routeKind)) {
71
+ throw new Error('invalid_repro_evidence_file');
72
+ }
73
+ const rawSteps = Array.isArray(value.steps) ? value.steps : [];
74
+ return {
75
+ route_id: readOptionalString(value.route_id),
76
+ route_kind: routeKind,
77
+ route_digest: readOptionalString(value.route_digest),
78
+ failure_oracle_hash: readOptionalString(value.failure_oracle_hash),
79
+ steps: rawSteps.map((step, index) => readRouteStep(step, index)),
80
+ };
81
+ }
82
+ function readLegacyReproEvidenceItem(value) {
83
+ if (!isPlainRecord(value)) {
84
+ return {
85
+ status: 'missing',
86
+ summary: null,
87
+ reason: null,
88
+ };
89
+ }
90
+ if (!isLegacyReproEvidenceStatus(value.status)) {
91
+ throw new Error('invalid_repro_evidence_file');
92
+ }
93
+ return {
94
+ status: value.status,
95
+ summary: readOptionalString(value.summary),
96
+ reason: readOptionalString(value.reason),
97
+ };
98
+ }
99
+ function legacyBeforeFixEvidence(value) {
100
+ const item = readLegacyReproEvidenceItem(value);
101
+ return {
102
+ status: item.status === 'present' ? 'reproduced' : item.status,
103
+ outcome: item.status === 'present' ? 'failed_as_expected' : null,
104
+ receipt_path: null,
105
+ receipt_sha256: null,
106
+ verification_plan_id: null,
107
+ summary: item.summary,
108
+ reason: item.reason,
109
+ };
110
+ }
111
+ function legacyAfterFixEvidence(value) {
112
+ const item = readLegacyReproEvidenceItem(value);
113
+ return {
114
+ status: item.status === 'present' ? 'passed' : item.status,
115
+ outcome: item.status === 'present' ? 'passed_expected_behavior' : null,
116
+ same_route_as: null,
117
+ receipt_path: null,
118
+ receipt_sha256: null,
119
+ verification_plan_id: null,
120
+ summary: item.summary,
121
+ reason: item.reason,
122
+ };
123
+ }
124
+ function legacyRegressionGuardEvidence(value) {
125
+ const item = readLegacyReproEvidenceItem(value);
126
+ return {
127
+ status: item.status === 'present' ? 'passed' : item.status,
128
+ intent: null,
129
+ test_path: null,
130
+ receipt_path: null,
131
+ receipt_sha256: null,
132
+ verification_plan_id: null,
133
+ summary: item.summary,
134
+ reason: item.reason,
135
+ };
136
+ }
137
+ function readBeforeFixEvidence(value) {
138
+ if (!isPlainRecord(value)) {
139
+ return {
140
+ status: 'missing',
141
+ outcome: null,
142
+ receipt_path: null,
143
+ receipt_sha256: null,
144
+ verification_plan_id: null,
145
+ summary: null,
146
+ reason: null,
147
+ };
148
+ }
149
+ const outcome = value.outcome ?? null;
150
+ if (!isReproBeforeFixStatus(value.status) || !isReproBeforeFixOutcome(outcome)) {
151
+ throw new Error('invalid_repro_evidence_file');
152
+ }
153
+ return {
154
+ status: value.status,
155
+ outcome,
156
+ receipt_path: readOptionalString(value.receipt_path),
157
+ receipt_sha256: readOptionalString(value.receipt_sha256),
158
+ verification_plan_id: readOptionalString(value.verification_plan_id),
159
+ summary: readOptionalString(value.summary),
160
+ reason: readOptionalString(value.reason),
161
+ };
162
+ }
163
+ function readAfterFixEvidence(value) {
164
+ if (!isPlainRecord(value)) {
165
+ return {
166
+ status: 'missing',
167
+ outcome: null,
168
+ same_route_as: null,
169
+ receipt_path: null,
170
+ receipt_sha256: null,
171
+ verification_plan_id: null,
172
+ summary: null,
173
+ reason: null,
174
+ };
175
+ }
176
+ const outcome = value.outcome ?? null;
177
+ if (!isReproAfterFixStatus(value.status) || !isReproAfterFixOutcome(outcome)) {
178
+ throw new Error('invalid_repro_evidence_file');
179
+ }
180
+ return {
181
+ status: value.status,
182
+ outcome,
183
+ same_route_as: readOptionalString(value.same_route_as),
184
+ receipt_path: readOptionalString(value.receipt_path),
185
+ receipt_sha256: readOptionalString(value.receipt_sha256),
186
+ verification_plan_id: readOptionalString(value.verification_plan_id),
187
+ summary: readOptionalString(value.summary),
188
+ reason: readOptionalString(value.reason),
189
+ };
190
+ }
191
+ function readRegressionGuardEvidence(value) {
192
+ if (!isPlainRecord(value)) {
193
+ return {
194
+ status: 'missing',
195
+ intent: null,
196
+ test_path: null,
197
+ receipt_path: null,
198
+ receipt_sha256: null,
199
+ verification_plan_id: null,
200
+ summary: null,
201
+ reason: null,
202
+ };
203
+ }
204
+ if (!isReproRegressionGuardStatus(value.status)) {
205
+ throw new Error('invalid_repro_evidence_file');
206
+ }
207
+ return {
208
+ status: value.status,
209
+ intent: readOptionalString(value.intent),
210
+ test_path: readOptionalString(value.test_path),
211
+ receipt_path: readOptionalString(value.receipt_path),
212
+ receipt_sha256: readOptionalString(value.receipt_sha256),
213
+ verification_plan_id: readOptionalString(value.verification_plan_id),
214
+ summary: readOptionalString(value.summary),
215
+ reason: readOptionalString(value.reason),
216
+ };
217
+ }
218
+ export function readReproEvidenceFile(projectRoot, inputPath) {
219
+ const parsed = readVerifyJsonInputFile(projectRoot, inputPath, 'invalid_repro_evidence_file');
220
+ if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'repro-evidence') {
221
+ throw new Error('unsupported_repro_evidence_source');
222
+ }
223
+ const regressionGuard = isPlainRecord(parsed.regression_guard) && isReproRegressionGuardStatus(parsed.regression_guard.status)
224
+ ? readRegressionGuardEvidence(parsed.regression_guard)
225
+ : legacyRegressionGuardEvidence(parsed.regression_guard);
226
+ return {
227
+ source: 'repro_first_debug',
228
+ authority: 'claim_evidence',
229
+ reported_symptom: readOptionalString(parsed.reported_symptom),
230
+ expected_behavior: readOptionalString(parsed.expected_behavior),
231
+ observed_behavior: readOptionalString(parsed.observed_behavior),
232
+ reproduction_route: readReproductionRoute(parsed.reproduction_route),
233
+ before_fix: isPlainRecord(parsed.before_fix)
234
+ ? readBeforeFixEvidence(parsed.before_fix)
235
+ : legacyBeforeFixEvidence(parsed.evidence_before_fix),
236
+ after_fix: isPlainRecord(parsed.after_fix)
237
+ ? readAfterFixEvidence(parsed.after_fix)
238
+ : legacyAfterFixEvidence(parsed.evidence_after_fix),
239
+ regression_guard: regressionGuard,
240
+ };
241
+ }
242
+ export function readExternalEvidenceFile(projectRoot, inputPath) {
243
+ const parsed = readVerifyJsonInputFile(projectRoot, inputPath, 'invalid_external_evidence_file');
244
+ if (!isPlainRecord(parsed) || parsed.schema_version !== '1' || parsed.command !== 'external-evidence') {
245
+ throw new Error('unsupported_external_evidence_source');
246
+ }
247
+ if (!Array.isArray(parsed.checks)) {
248
+ throw new Error('invalid_external_evidence_file');
249
+ }
250
+ return parsed.checks.map((check) => {
251
+ if (!isPlainRecord(check) ||
252
+ typeof check.provider !== 'string' ||
253
+ check.provider.length === 0 ||
254
+ typeof check.name !== 'string' ||
255
+ check.name.length === 0 ||
256
+ !isExternalEvidenceStatus(check.status)) {
257
+ throw new Error('invalid_external_evidence_file');
258
+ }
259
+ return {
260
+ source: 'external_ci',
261
+ authority: 'supporting_only',
262
+ provider: check.provider,
263
+ name: check.name,
264
+ status: check.status,
265
+ url: readOptionalString(check.url),
266
+ summary: readOptionalString(check.summary),
267
+ };
268
+ });
269
+ }
@@ -0,0 +1,212 @@
1
+ import path from 'node:path';
2
+ import { createClassifyOutput } from '../classify.js';
3
+ import { createCorrelationId, isCorrelationId } from '../../../core/correlation-id.js';
4
+ import { readUtf8FileInsideWithoutSymlinks, writeJsonFileInsideWithoutSymlinks } from '../../../core/safe-filesystem.js';
5
+ function uniqueStrings(values) {
6
+ return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))];
7
+ }
8
+ function isStringArray(value) {
9
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
10
+ }
11
+ function isPlainRecord(value) {
12
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
13
+ }
14
+ function isUpdatePolicy(value) {
15
+ return value === 'update' || value === 'update_or_mark_stale' || value === 'not_applicable';
16
+ }
17
+ function isUpdatePolicyArray(value) {
18
+ return Array.isArray(value) && value.every((item) => isUpdatePolicy(item));
19
+ }
20
+ function readPlanRoot(value) {
21
+ if (!isPlainRecord(value)) {
22
+ return null;
23
+ }
24
+ const root = value.mustflow_root;
25
+ return typeof root === 'string' && root.length > 0 ? root : null;
26
+ }
27
+ function readPlanCorrelationId(value) {
28
+ if (!isPlainRecord(value)) {
29
+ return null;
30
+ }
31
+ return isCorrelationId(value.correlation_id) ? value.correlation_id : null;
32
+ }
33
+ function readStrictClassificationSummary(value) {
34
+ if (!isPlainRecord(value)) {
35
+ return null;
36
+ }
37
+ if (!Number.isInteger(value.fileCount) ||
38
+ Number(value.fileCount) < 0 ||
39
+ !Number.isInteger(value.publicSurfaceCount) ||
40
+ Number(value.publicSurfaceCount) < 0 ||
41
+ !isStringArray(value.changeKinds) ||
42
+ !isStringArray(value.validationReasons) ||
43
+ !isUpdatePolicyArray(value.updatePolicies) ||
44
+ !isStringArray(value.driftChecks) ||
45
+ !isStringArray(value.affectedContracts)) {
46
+ return null;
47
+ }
48
+ return {
49
+ fileCount: Number(value.fileCount),
50
+ publicSurfaceCount: Number(value.publicSurfaceCount),
51
+ changeKinds: [...value.changeKinds],
52
+ validationReasons: uniqueStrings(value.validationReasons),
53
+ updatePolicies: [...value.updatePolicies],
54
+ driftChecks: [...value.driftChecks],
55
+ affectedContracts: [...value.affectedContracts],
56
+ };
57
+ }
58
+ function readStrictClassification(value) {
59
+ if (!isPlainRecord(value) || typeof value.path !== 'string' || !isStringArray(value.changeKinds)) {
60
+ return null;
61
+ }
62
+ const surface = value.surface;
63
+ if (!isPlainRecord(surface)) {
64
+ return null;
65
+ }
66
+ if (typeof surface.kind !== 'string' ||
67
+ typeof surface.category !== 'string' ||
68
+ typeof surface.isPublicSurface !== 'boolean' ||
69
+ !isStringArray(surface.validationReasons) ||
70
+ !isStringArray(surface.affectedContracts) ||
71
+ !isUpdatePolicy(surface.updatePolicy) ||
72
+ !isStringArray(surface.driftChecks)) {
73
+ return null;
74
+ }
75
+ return {
76
+ path: value.path,
77
+ changeKinds: [...value.changeKinds],
78
+ surface: {
79
+ kind: surface.kind,
80
+ category: surface.category,
81
+ isPublicSurface: surface.isPublicSurface,
82
+ validationReasons: [...surface.validationReasons],
83
+ affectedContracts: [...surface.affectedContracts],
84
+ updatePolicy: surface.updatePolicy,
85
+ driftChecks: [...surface.driftChecks],
86
+ },
87
+ };
88
+ }
89
+ function readStrictClassifications(value) {
90
+ if (!Array.isArray(value)) {
91
+ return null;
92
+ }
93
+ const classifications = value.map(readStrictClassification);
94
+ return classifications.every((classification) => classification !== null)
95
+ ? classifications
96
+ : null;
97
+ }
98
+ function readStrictClassifyPlan(projectRoot, plan) {
99
+ if (!isPlainRecord(plan)) {
100
+ throw new Error('unsupported_plan_source');
101
+ }
102
+ if (plan.schema_version !== '1' || plan.command !== 'classify') {
103
+ throw new Error('unsupported_plan_source');
104
+ }
105
+ if (readPlanRoot(plan) !== projectRoot) {
106
+ throw new Error('plan_root_mismatch');
107
+ }
108
+ const source = plan.source;
109
+ const files = plan.files;
110
+ const classifications = readStrictClassifications(plan.classifications);
111
+ const summary = readStrictClassificationSummary(plan.summary);
112
+ if ((source !== 'changed' && source !== 'paths') || !isStringArray(files) || !classifications || !summary) {
113
+ throw new Error('invalid_plan_file');
114
+ }
115
+ if (summary.validationReasons.length === 0) {
116
+ throw new Error('missing_plan_reasons');
117
+ }
118
+ return {
119
+ source,
120
+ files: [...files],
121
+ classifications,
122
+ summary,
123
+ };
124
+ }
125
+ function emptyClassificationSummary(validationReasons) {
126
+ return {
127
+ fileCount: 0,
128
+ publicSurfaceCount: 0,
129
+ changeKinds: [],
130
+ validationReasons,
131
+ updatePolicies: [],
132
+ driftChecks: [],
133
+ affectedContracts: [],
134
+ };
135
+ }
136
+ export function createSyntheticClassificationReport(reasons, source = 'paths', files = []) {
137
+ return {
138
+ source,
139
+ files,
140
+ classifications: [],
141
+ summary: emptyClassificationSummary(reasons),
142
+ };
143
+ }
144
+ export function resolveVerifyInputPath(projectRoot, inputPath) {
145
+ const resolved = path.resolve(projectRoot, inputPath);
146
+ const relative = path.relative(projectRoot, resolved);
147
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
148
+ throw new Error('plan_path_outside_root');
149
+ }
150
+ return resolved;
151
+ }
152
+ export function readVerifyJsonInputFile(projectRoot, inputPath, invalidCode) {
153
+ const inputFilePath = resolveVerifyInputPath(projectRoot, inputPath);
154
+ let content;
155
+ try {
156
+ content = readUtf8FileInsideWithoutSymlinks(projectRoot, inputFilePath);
157
+ }
158
+ catch (error) {
159
+ if (error instanceof Error && error.message.startsWith('Path must not contain symlinks:')) {
160
+ throw new Error('input_path_contains_symlink');
161
+ }
162
+ throw new Error(invalidCode);
163
+ }
164
+ try {
165
+ return JSON.parse(content);
166
+ }
167
+ catch {
168
+ throw new Error(invalidCode);
169
+ }
170
+ }
171
+ export function readInputFromClassificationReport(projectRoot, inputPath) {
172
+ const parsed = readVerifyJsonInputFile(projectRoot, inputPath, 'invalid_plan_file');
173
+ const classificationReport = readStrictClassifyPlan(projectRoot, parsed);
174
+ return {
175
+ correlationId: readPlanCorrelationId(parsed) ?? createCorrelationId('verify'),
176
+ reasons: classificationReport.summary.validationReasons,
177
+ classificationReport,
178
+ };
179
+ }
180
+ export function createInputFromChanged(projectRoot) {
181
+ const plan = createClassifyOutput(projectRoot, 'changed', []);
182
+ return {
183
+ plan,
184
+ input: {
185
+ correlationId: plan.correlation_id,
186
+ reasons: plan.summary.validationReasons,
187
+ classificationReport: plan,
188
+ },
189
+ };
190
+ }
191
+ export function writeChangedPlan(projectRoot, inputPath, plan) {
192
+ const planPath = resolveVerifyInputPath(projectRoot, inputPath);
193
+ writeJsonFileInsideWithoutSymlinks(projectRoot, planPath, plan);
194
+ }
195
+ export function planErrorMessageKey(code) {
196
+ switch (code) {
197
+ case 'plan_path_outside_root':
198
+ return 'verify.error.plan_path_outside_root';
199
+ case 'input_path_contains_symlink':
200
+ return 'verify.error.input_path_contains_symlink';
201
+ case 'missing_plan_reasons':
202
+ return 'verify.error.missing_plan_reasons';
203
+ case 'unsupported_plan_source':
204
+ return 'verify.error.unsupported_plan_source';
205
+ case 'plan_root_mismatch':
206
+ return 'verify.error.plan_root_mismatch';
207
+ case 'git_changed_files_unavailable':
208
+ return 'verify.error.changed_files_unavailable';
209
+ default:
210
+ return 'verify.error.invalid_plan_file';
211
+ }
212
+ }