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
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
function uniqueSorted(values) {
|
|
2
|
+
return [...new Set([...values].filter((value) => typeof value === 'string' && value.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
3
|
+
}
|
|
4
|
+
function receiptEntries(results) {
|
|
5
|
+
return results
|
|
6
|
+
.filter((result) => !result.skipped)
|
|
7
|
+
.map((result) => ({
|
|
8
|
+
intent: result.intent,
|
|
9
|
+
status: result.status,
|
|
10
|
+
skipped: false,
|
|
11
|
+
verification_plan_id: result.verification_plan_id,
|
|
12
|
+
receipt_path: result.receipt_path,
|
|
13
|
+
receipt_sha256: result.receipt_sha256,
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
function skippedCheckEntries(results) {
|
|
17
|
+
return results
|
|
18
|
+
.filter((result) => result.skipped)
|
|
19
|
+
.map((result) => ({
|
|
20
|
+
intent: result.intent,
|
|
21
|
+
reason: result.reason,
|
|
22
|
+
detail: result.detail,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
function resultForIntent(results, intent) {
|
|
26
|
+
return results.find((result) => result.intent === intent && !result.skipped) ?? null;
|
|
27
|
+
}
|
|
28
|
+
function requirementOutcome(input) {
|
|
29
|
+
if (input.selectedIntents.some((intent) => {
|
|
30
|
+
const result = resultForIntent(input.results, intent);
|
|
31
|
+
return result?.status === 'failed' || result?.status === 'timed_out' || result?.status === 'start_failed';
|
|
32
|
+
})) {
|
|
33
|
+
return 'contradicted';
|
|
34
|
+
}
|
|
35
|
+
if (input.gapCount > 0 || (input.selectedIntents.length === 0 && input.skippedIntents.length > 0)) {
|
|
36
|
+
return 'blocked';
|
|
37
|
+
}
|
|
38
|
+
if (input.selectedIntents.length === 0) {
|
|
39
|
+
return 'unverified';
|
|
40
|
+
}
|
|
41
|
+
if (input.skippedIntents.length > 0) {
|
|
42
|
+
return 'partially_verified';
|
|
43
|
+
}
|
|
44
|
+
return input.selectedIntents.every((intent) => resultForIntent(input.results, intent)?.status === 'passed')
|
|
45
|
+
? 'verified'
|
|
46
|
+
: 'unverified';
|
|
47
|
+
}
|
|
48
|
+
function explanationFromVerdict(verdict) {
|
|
49
|
+
return {
|
|
50
|
+
verified_by: verdict.status === 'verified' ? [verdict.primary_reason] : [],
|
|
51
|
+
downgraded_by: verdict.status === 'partially_verified' || verdict.status === 'unverified' ? verdict.limitations : [],
|
|
52
|
+
blocked_by: verdict.status === 'blocked' ? verdict.blockers : [],
|
|
53
|
+
contradicted_by: verdict.status === 'contradicted' ? verdict.contradictions : [],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function coverageStatusFromOutcome(outcome) {
|
|
57
|
+
if (outcome === 'verified') {
|
|
58
|
+
return 'covered';
|
|
59
|
+
}
|
|
60
|
+
if (outcome === 'partially_verified') {
|
|
61
|
+
return 'partially_covered';
|
|
62
|
+
}
|
|
63
|
+
if (outcome === 'unverified') {
|
|
64
|
+
return 'uncovered';
|
|
65
|
+
}
|
|
66
|
+
return outcome;
|
|
67
|
+
}
|
|
68
|
+
function receiptPathsForIntents(receipts, intents) {
|
|
69
|
+
const selected = new Set(intents);
|
|
70
|
+
return uniqueSorted(receipts
|
|
71
|
+
.filter((receipt) => receipt.intent !== null && selected.has(receipt.intent))
|
|
72
|
+
.map((receipt) => receipt.receipt_path));
|
|
73
|
+
}
|
|
74
|
+
function coverageMatrixFromRequirements(input) {
|
|
75
|
+
return input.requirements.map((requirement, index) => {
|
|
76
|
+
const matchingGaps = input.source === 'dashboard_snapshot'
|
|
77
|
+
? input.gaps
|
|
78
|
+
: input.gaps.filter((gap) => gap.reason === requirement.reason);
|
|
79
|
+
const matchingSourceAnchorRisks = input.sourceAnchorRisks.filter((risk) => requirement.files.includes(risk.path));
|
|
80
|
+
const status = coverageStatusFromOutcome(requirement.outcome);
|
|
81
|
+
return {
|
|
82
|
+
criterion_id: `${input.source}:${index + 1}:${requirement.reason}`,
|
|
83
|
+
source: input.source,
|
|
84
|
+
statement: `Verification requirement: ${requirement.reason}`,
|
|
85
|
+
status: matchingSourceAnchorRisks.length > 0 && status === 'covered' ? 'partially_covered' : status,
|
|
86
|
+
requirement_reason: requirement.reason,
|
|
87
|
+
evidence: {
|
|
88
|
+
intents: uniqueSorted([...requirement.selected_intents, ...requirement.skipped_intents]),
|
|
89
|
+
receipt_paths: receiptPathsForIntents(input.receipts, requirement.selected_intents),
|
|
90
|
+
gap_reasons: uniqueSorted(matchingGaps.map((gap) => gap.detail)),
|
|
91
|
+
source_anchor_ids: uniqueSorted(matchingSourceAnchorRisks.map((risk) => risk.anchorId)),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function sourceAnchorRemainingRisks(sourceAnchorRisks) {
|
|
97
|
+
return sourceAnchorRisks.map((risk) => ({
|
|
98
|
+
code: 'source_anchor_invariant_review_required',
|
|
99
|
+
severity: 'high',
|
|
100
|
+
detail: `${risk.anchorId} in ${risk.path}:${risk.lineStart} is ${risk.status}; review its invariant before marking the task verified.`,
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
function scopeDiffRemainingRisks(scopeDiffRisks) {
|
|
104
|
+
return scopeDiffRisks.map((risk) => ({
|
|
105
|
+
code: risk.code,
|
|
106
|
+
severity: risk.severity,
|
|
107
|
+
detail: risk.detail,
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
function repeatedFailureRemainingRisks(repeatedFailureRisks) {
|
|
111
|
+
return repeatedFailureRisks.map((risk) => ({
|
|
112
|
+
code: risk.code,
|
|
113
|
+
severity: risk.severity,
|
|
114
|
+
detail: risk.detail,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
function validationRatchetRemainingRisks(validationRatchetRisks) {
|
|
118
|
+
return validationRatchetRisks.map((risk) => ({
|
|
119
|
+
code: risk.code,
|
|
120
|
+
severity: risk.severity,
|
|
121
|
+
detail: risk.detail,
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
function reproEvidenceRemainingRisks(reproEvidenceRisks) {
|
|
125
|
+
return reproEvidenceRisks.map((risk) => ({
|
|
126
|
+
code: risk.code,
|
|
127
|
+
severity: risk.severity,
|
|
128
|
+
detail: risk.detail,
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
function externalEvidenceRemainingRisks(externalEvidenceRisks) {
|
|
132
|
+
return externalEvidenceRisks.map((risk) => ({
|
|
133
|
+
code: risk.code,
|
|
134
|
+
severity: risk.severity,
|
|
135
|
+
detail: risk.detail,
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
export function createVerifyEvidenceModel(input) {
|
|
139
|
+
const requirements = input.report.requirements.map((requirement) => {
|
|
140
|
+
const candidates = input.report.candidates.filter((candidate) => candidate.reason === requirement.reason);
|
|
141
|
+
const selectedIntents = uniqueSorted(candidates
|
|
142
|
+
.filter((candidate) => candidate.selectionState === 'selected')
|
|
143
|
+
.map((candidate) => candidate.intent));
|
|
144
|
+
const skippedIntents = uniqueSorted(candidates
|
|
145
|
+
.filter((candidate) => candidate.status !== 'runnable')
|
|
146
|
+
.map((candidate) => candidate.intent));
|
|
147
|
+
const gapCount = input.report.gaps.filter((gap) => gap.reason === requirement.reason).length;
|
|
148
|
+
return {
|
|
149
|
+
reason: requirement.reason,
|
|
150
|
+
files: [...requirement.files],
|
|
151
|
+
surfaces: [...requirement.surfaces],
|
|
152
|
+
candidate_intents: uniqueSorted(candidates.map((candidate) => candidate.intent)),
|
|
153
|
+
selected_intents: selectedIntents,
|
|
154
|
+
skipped_intents: skippedIntents,
|
|
155
|
+
gap_count: gapCount,
|
|
156
|
+
outcome: requirementOutcome({
|
|
157
|
+
selectedIntents,
|
|
158
|
+
skippedIntents,
|
|
159
|
+
gapCount,
|
|
160
|
+
results: input.results,
|
|
161
|
+
}),
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
const receipts = receiptEntries(input.results);
|
|
165
|
+
const sourceAnchorRisks = input.sourceAnchorRisks ?? [];
|
|
166
|
+
const scopeDiffRisks = input.scopeDiffRisks ?? [];
|
|
167
|
+
const repeatedFailureRisks = input.repeatedFailureRisks ?? [];
|
|
168
|
+
const validationRatchetRisks = input.validationRatchetRisks ?? [];
|
|
169
|
+
const reproEvidence = input.reproEvidence ?? null;
|
|
170
|
+
const reproEvidenceRisks = input.reproEvidenceRisks ?? [];
|
|
171
|
+
const externalChecks = input.externalChecks ?? [];
|
|
172
|
+
const externalEvidenceRisks = input.externalEvidenceRisks ?? [];
|
|
173
|
+
const gaps = input.report.gaps.map((gap) => ({
|
|
174
|
+
reason: gap.reason,
|
|
175
|
+
intent: null,
|
|
176
|
+
status: 'missing',
|
|
177
|
+
detail: gap.detail,
|
|
178
|
+
files: [...gap.files],
|
|
179
|
+
surfaces: [...gap.surfaces],
|
|
180
|
+
}));
|
|
181
|
+
return {
|
|
182
|
+
schema_version: '1',
|
|
183
|
+
source: 'mf_verify',
|
|
184
|
+
verification_plan_id: input.verificationPlanId,
|
|
185
|
+
requirements,
|
|
186
|
+
coverage_matrix: coverageMatrixFromRequirements({
|
|
187
|
+
source: 'verification_requirement',
|
|
188
|
+
requirements,
|
|
189
|
+
receipts,
|
|
190
|
+
gaps,
|
|
191
|
+
sourceAnchorRisks,
|
|
192
|
+
}),
|
|
193
|
+
receipts,
|
|
194
|
+
skipped_checks: skippedCheckEntries(input.results),
|
|
195
|
+
gaps,
|
|
196
|
+
remaining_risks: [
|
|
197
|
+
...sourceAnchorRemainingRisks(sourceAnchorRisks),
|
|
198
|
+
...scopeDiffRemainingRisks(scopeDiffRisks),
|
|
199
|
+
...repeatedFailureRemainingRisks(repeatedFailureRisks),
|
|
200
|
+
...validationRatchetRemainingRisks(validationRatchetRisks),
|
|
201
|
+
...reproEvidenceRemainingRisks(reproEvidenceRisks),
|
|
202
|
+
...externalEvidenceRemainingRisks(externalEvidenceRisks),
|
|
203
|
+
],
|
|
204
|
+
...(reproEvidence ? { repro_evidence: reproEvidence } : {}),
|
|
205
|
+
...(externalChecks.length > 0 ? { external_checks: externalChecks } : {}),
|
|
206
|
+
explanation: explanationFromVerdict(input.verdict),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
export function createDashboardEvidenceModel(input) {
|
|
210
|
+
const requirements = input.changedSurfaces.length > 0 || input.runnableIntents.length > 0
|
|
211
|
+
? [
|
|
212
|
+
{
|
|
213
|
+
reason: 'dashboard_snapshot',
|
|
214
|
+
files: [],
|
|
215
|
+
surfaces: [...input.changedSurfaces],
|
|
216
|
+
candidate_intents: uniqueSorted(input.runnableIntents),
|
|
217
|
+
selected_intents: [],
|
|
218
|
+
skipped_intents: uniqueSorted(input.skippedChecks.map((check) => check.intent)),
|
|
219
|
+
gap_count: input.gaps.length,
|
|
220
|
+
outcome: input.verdict.status,
|
|
221
|
+
},
|
|
222
|
+
]
|
|
223
|
+
: [];
|
|
224
|
+
const receipts = input.latestReceipt ? [input.latestReceipt] : [];
|
|
225
|
+
const sourceAnchorRisks = input.sourceAnchorRisks ?? [];
|
|
226
|
+
const scopeDiffRisks = input.scopeDiffRisks ?? [];
|
|
227
|
+
return {
|
|
228
|
+
schema_version: '1',
|
|
229
|
+
source: 'dashboard_export',
|
|
230
|
+
verification_plan_id: null,
|
|
231
|
+
requirements,
|
|
232
|
+
coverage_matrix: coverageMatrixFromRequirements({
|
|
233
|
+
source: 'dashboard_snapshot',
|
|
234
|
+
requirements,
|
|
235
|
+
receipts,
|
|
236
|
+
gaps: input.gaps,
|
|
237
|
+
sourceAnchorRisks,
|
|
238
|
+
}),
|
|
239
|
+
receipts,
|
|
240
|
+
skipped_checks: input.skippedChecks,
|
|
241
|
+
gaps: input.gaps,
|
|
242
|
+
remaining_risks: [
|
|
243
|
+
...input.remainingRisks,
|
|
244
|
+
...sourceAnchorRemainingRisks(sourceAnchorRisks),
|
|
245
|
+
...scopeDiffRemainingRisks(scopeDiffRisks),
|
|
246
|
+
],
|
|
247
|
+
explanation: explanationFromVerdict(input.verdict),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { commandEffectsConflict, normalizeCommandEffects, } from './command-effects.js';
|
|
2
4
|
function uniqueSorted(values) {
|
|
3
5
|
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
@@ -13,6 +15,79 @@ function toScheduleEffect(effect) {
|
|
|
13
15
|
concurrency: effect.concurrency,
|
|
14
16
|
};
|
|
15
17
|
}
|
|
18
|
+
function isObject(value) {
|
|
19
|
+
return !!value && typeof value === 'object';
|
|
20
|
+
}
|
|
21
|
+
function readJsonFile(filePath) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function getUndeclaredWriteIntent(value) {
|
|
30
|
+
if (!isObject(value) || typeof value.intent !== 'string') {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const writeDrift = value.write_drift;
|
|
34
|
+
if (!isObject(writeDrift) || writeDrift.has_undeclared_changes !== true) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return value.intent;
|
|
38
|
+
}
|
|
39
|
+
function resolveStateRelativePath(projectRoot, relativePath) {
|
|
40
|
+
if (typeof relativePath !== 'string' || relativePath.length === 0 || path.isAbsolute(relativePath)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const normalized = relativePath.replaceAll('\\', '/');
|
|
44
|
+
if (!normalized.startsWith('.mustflow/state/runs/')) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const resolved = path.resolve(projectRoot, normalized);
|
|
48
|
+
const relative = path.relative(projectRoot, resolved);
|
|
49
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return resolved;
|
|
53
|
+
}
|
|
54
|
+
function readVerifyManifestUndeclaredWriteIntents(projectRoot, latest) {
|
|
55
|
+
const manifestPath = resolveStateRelativePath(projectRoot, latest.manifest_path);
|
|
56
|
+
if (!manifestPath) {
|
|
57
|
+
return new Set();
|
|
58
|
+
}
|
|
59
|
+
const manifest = readJsonFile(manifestPath);
|
|
60
|
+
if (!isObject(manifest) || manifest.command !== 'verify' || !Array.isArray(manifest.receipts)) {
|
|
61
|
+
return new Set();
|
|
62
|
+
}
|
|
63
|
+
const intents = new Set();
|
|
64
|
+
for (const entry of manifest.receipts) {
|
|
65
|
+
if (!isObject(entry)) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const receiptPath = resolveStateRelativePath(projectRoot, entry.receipt_path);
|
|
69
|
+
if (!receiptPath) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const intent = getUndeclaredWriteIntent(readJsonFile(receiptPath));
|
|
73
|
+
if (intent) {
|
|
74
|
+
intents.add(intent);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return intents;
|
|
78
|
+
}
|
|
79
|
+
function readLatestUndeclaredWriteIntents(projectRoot) {
|
|
80
|
+
const latestPath = path.join(projectRoot, '.mustflow', 'state', 'runs', 'latest.json');
|
|
81
|
+
const parsed = readJsonFile(latestPath);
|
|
82
|
+
const directIntent = getUndeclaredWriteIntent(parsed);
|
|
83
|
+
if (directIntent) {
|
|
84
|
+
return new Set([directIntent]);
|
|
85
|
+
}
|
|
86
|
+
if (!isObject(parsed) || parsed.command !== 'verify') {
|
|
87
|
+
return new Set();
|
|
88
|
+
}
|
|
89
|
+
return readVerifyManifestUndeclaredWriteIntents(projectRoot, parsed);
|
|
90
|
+
}
|
|
16
91
|
function entriesConflict(left, right) {
|
|
17
92
|
const conflicts = [];
|
|
18
93
|
for (const leftEffect of left.effects) {
|
|
@@ -33,7 +108,8 @@ function entriesConflict(left, right) {
|
|
|
33
108
|
function addEntryToBatches(batches, batchEntries, entry) {
|
|
34
109
|
for (let batchIndex = 0; batchIndex < batchEntries.length; batchIndex += 1) {
|
|
35
110
|
const existingEntries = batchEntries[batchIndex] ?? [];
|
|
36
|
-
const hasConflict =
|
|
111
|
+
const hasConflict = !entry.parallelEligible ||
|
|
112
|
+
existingEntries.some((existing) => !existing.parallelEligible || entriesConflict(entry, existing).length > 0);
|
|
37
113
|
if (hasConflict) {
|
|
38
114
|
continue;
|
|
39
115
|
}
|
|
@@ -55,14 +131,24 @@ function addEntryToBatches(batches, batchEntries, entry) {
|
|
|
55
131
|
});
|
|
56
132
|
}
|
|
57
133
|
export function createVerificationSchedule(projectRoot, commandContract, candidates) {
|
|
134
|
+
const latestUndeclaredWriteIntents = readLatestUndeclaredWriteIntents(projectRoot);
|
|
58
135
|
const runnableIntents = uniqueSorted(candidates
|
|
59
136
|
.filter((candidate) => candidate.status === 'runnable' && candidate.intent.length > 0)
|
|
60
137
|
.map((candidate) => candidate.intent));
|
|
61
138
|
const baseEntries = runnableIntents.map((intent) => {
|
|
62
139
|
const effects = normalizeCommandEffects(projectRoot, commandContract, intent).map(toScheduleEffect);
|
|
140
|
+
const hasExplicitEffects = effects.length > 0 && effects.every((effect) => effect.source === 'effects');
|
|
141
|
+
const hasUndeclaredWriteDrift = latestUndeclaredWriteIntents.has(intent);
|
|
142
|
+
const parallelEligible = hasExplicitEffects && !hasUndeclaredWriteDrift;
|
|
63
143
|
return {
|
|
64
144
|
intent,
|
|
65
145
|
status: 'runnable',
|
|
146
|
+
parallelEligible,
|
|
147
|
+
parallelReason: hasUndeclaredWriteDrift
|
|
148
|
+
? 'undeclared_write_drift'
|
|
149
|
+
: hasExplicitEffects
|
|
150
|
+
? 'explicit_effects'
|
|
151
|
+
: 'missing_explicit_effects',
|
|
66
152
|
effects,
|
|
67
153
|
locks: uniqueSorted(effects.map((effect) => effect.lock)),
|
|
68
154
|
conflicts: [],
|
|
@@ -82,11 +168,19 @@ export function createVerificationSchedule(projectRoot, commandContract, candida
|
|
|
82
168
|
}
|
|
83
169
|
return {
|
|
84
170
|
runner: 'serial_mf_run_receipts',
|
|
171
|
+
failurePolicy: {
|
|
172
|
+
mode: 'batch_boundary',
|
|
173
|
+
startedBatch: 'wait_for_completion',
|
|
174
|
+
nextBatch: 'stop_on_failure',
|
|
175
|
+
},
|
|
85
176
|
batches,
|
|
86
177
|
entries,
|
|
87
178
|
notes: [
|
|
88
179
|
'Batches explain resource compatibility for planning only.',
|
|
89
|
-
'
|
|
180
|
+
'Only entries backed by explicit effects are marked parallel eligible; writes fallback remains serial-only.',
|
|
181
|
+
...uniqueSorted(latestUndeclaredWriteIntents).map((intent) => `Latest receipt for ${intent} reported undeclared writes; it is not parallel eligible.`),
|
|
182
|
+
'If a future parallel batch has already started, let it finish and stop before the next batch on failure.',
|
|
183
|
+
'mf verify still executes copied commands serially and writes the latest run summary after the batch completes.',
|
|
90
184
|
],
|
|
91
185
|
};
|
|
92
186
|
}
|
package/examples/README.md
CHANGED
|
@@ -4,10 +4,18 @@ This directory contains human-readable project examples.
|
|
|
4
4
|
|
|
5
5
|
These examples are not test fixtures and are not copied by `mf init`. They illustrate the structure of a project before and after integrating mustflow, while excluding generated or template-owned files from the example content.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Choose an Example
|
|
8
|
+
|
|
9
|
+
If you are new to mustflow:
|
|
8
10
|
|
|
9
|
-
- `docs-only/`: A documentation-focused repository with explicit checks for missing code.
|
|
10
|
-
- `host-instruction-conflicts/`: A repository where host-specific instructions overlap with mustflow rules.
|
|
11
11
|
- `minimal-js/`: A small JavaScript package before and after adding mustflow.
|
|
12
|
+
|
|
13
|
+
If you are building or integrating an AI coding tool:
|
|
14
|
+
|
|
15
|
+
- `host-instruction-conflicts/`: A repository where host-specific instructions overlap with mustflow rules.
|
|
12
16
|
- `missing-command-contracts/`: A repository with package scripts that are not yet defined as mustflow command intents.
|
|
13
|
-
|
|
17
|
+
|
|
18
|
+
If you are studying repository structure:
|
|
19
|
+
|
|
20
|
+
- `docs-only/`: A documentation-focused repository with explicit checks for missing code.
|
|
21
|
+
- `nested-repos/`: Parent and child repositories with separate local contracts.
|
package/package.json
CHANGED
package/schemas/README.md
CHANGED
|
@@ -9,15 +9,24 @@ Current schemas:
|
|
|
9
9
|
- `adapter-compatibility-report.schema.json`: output of `mf adapters status --json`
|
|
10
10
|
- `context-report.schema.json`: output of `mf context --json`
|
|
11
11
|
- `run-receipt.schema.json`: output of `mf run <intent> --json` and `.mustflow/state/runs/latest.json`,
|
|
12
|
-
including bounded declared-write drift metadata
|
|
12
|
+
including bounded declared-write drift metadata, a safe latest-run performance summary, and optional
|
|
13
|
+
structured phase timings and selection summaries
|
|
13
14
|
- `commands.schema.json`: parsed `.mustflow/config/commands.toml`
|
|
15
|
+
- `test-selection.schema.json`: parsed optional `.mustflow/config/test-selection.toml`
|
|
14
16
|
- `contract-lint-report.schema.json`: output of `mf contract-lint --json`
|
|
17
|
+
- `dashboard-export.schema.json`: bounded static export written by `mf dashboard --export-json <path>`,
|
|
18
|
+
including output policy, redaction and truncation metadata, the dashboard harness report, and
|
|
19
|
+
the evidence-based completion verdict, evidence model, and conservative coverage matrix for the
|
|
20
|
+
exported snapshot
|
|
15
21
|
- `classify-report.schema.json`: output of `mf classify --changed --json` and
|
|
16
22
|
`mf classify <path...> --json`
|
|
17
23
|
- `impact-report.schema.json`: output of `mf impact --changed --json` and
|
|
18
24
|
`mf impact <path...> --json`
|
|
19
25
|
- `line-endings-report.schema.json`: output of `mf line-endings check --json` and
|
|
20
26
|
`mf line-endings normalize --json`
|
|
27
|
+
- `latest-run-pointer.schema.json`: `.mustflow/state/runs/latest.json` when `mf verify` writes a
|
|
28
|
+
pointer to the latest verify run bundle, including the verify completion verdict, evidence model,
|
|
29
|
+
and coverage matrix
|
|
21
30
|
- `handoff-validation-report.schema.json`: output of
|
|
22
31
|
`mf handoff validate <path> --json`
|
|
23
32
|
- `version-sources-report.schema.json`: output of `mf version-sources --json`
|
|
@@ -25,10 +34,15 @@ Current schemas:
|
|
|
25
34
|
- `explain-report.schema.json`: output of `mf explain authority --json`, `mf explain command --json`,
|
|
26
35
|
`mf explain verify --reason <event> --json`, `mf explain retention --json`, `mf explain skills --json`,
|
|
27
36
|
and `mf explain surface --json`. Verify explanations include the shared `decisionGraph` evidence model.
|
|
28
|
-
- `verify-report.schema.json`: output of `mf verify --reason <event> --json
|
|
37
|
+
- `verify-report.schema.json`: output of `mf verify --reason <event> --json`, including an
|
|
38
|
+
evidence-based completion verdict and evidence model with a conservative coverage matrix for the
|
|
39
|
+
selected receipts and skipped checks
|
|
40
|
+
- `verify-run-manifest.schema.json`: `.mustflow/state/runs/verify-latest/manifest.json`, including
|
|
41
|
+
the same completion verdict, evidence model, and coverage matrix as the verify report
|
|
29
42
|
- `change-verification-report.schema.json`: output of `mf verify --reason <event> --plan-only --json` and
|
|
30
|
-
`mf verify --from-
|
|
31
|
-
changed surfaces, classification reasons, command candidates, eligibility,
|
|
43
|
+
`mf verify --from-classification <classify-report.json> --plan-only --json`, including the `decision_graph` that links
|
|
44
|
+
changed surfaces, classification reasons, command candidates, eligibility, selected or not-selected state,
|
|
45
|
+
effects, and gaps.
|
|
32
46
|
Local-index command-effect graphs are explanation-only and cannot grant command authority.
|
|
33
47
|
|
|
34
48
|
These schemas define stable, automation-facing fields. Human-readable command
|