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,5 +1,6 @@
|
|
|
1
1
|
const SKILL_ROUTE_SOURCE_FILES = [
|
|
2
2
|
'.mustflow/skills/INDEX.md',
|
|
3
|
+
'.mustflow/skills/routes.toml',
|
|
3
4
|
'.mustflow/skills/*/SKILL.md',
|
|
4
5
|
'.mustflow/config/commands.toml',
|
|
5
6
|
'.mustflow/docs/agent-workflow.md',
|
|
@@ -20,6 +21,18 @@ export const SKILL_INDEX_ROUTE_COLUMN_COUNT = 7;
|
|
|
20
21
|
export const SKILL_INDEX_SKILL_PATH_COLUMN_INDEX = 1;
|
|
21
22
|
export const SKILL_INDEX_VERIFICATION_INTENTS_COLUMN_INDEX = 5;
|
|
22
23
|
export const SKILL_INDEX_ROUTE_COLUMNS = 'Trigger, Skill Document, Required Input, Edit Scope, Risk, Verification Intents, Expected Output';
|
|
24
|
+
export const SKILL_ROUTE_CATEGORY_LABELS = {
|
|
25
|
+
bug_failure: 'Bug and Failure',
|
|
26
|
+
general_code: 'General Code Change',
|
|
27
|
+
tests: 'Tests and Regression',
|
|
28
|
+
docs_release: 'Documentation and Release',
|
|
29
|
+
security_privacy: 'Security and Privacy',
|
|
30
|
+
data_external: 'Data and External Systems',
|
|
31
|
+
ui_assets: 'UI and Assets',
|
|
32
|
+
architecture_patterns: 'Architecture Patterns',
|
|
33
|
+
workflow_contracts: 'Workflow and Contract Maintenance',
|
|
34
|
+
};
|
|
35
|
+
const SKILL_ROUTE_CATEGORY_BY_HEADING = new Map(Object.entries(SKILL_ROUTE_CATEGORY_LABELS).map(([category, label]) => [label, category]));
|
|
23
36
|
function splitMarkdownTableRow(line) {
|
|
24
37
|
return line
|
|
25
38
|
.trim()
|
|
@@ -39,7 +52,13 @@ export function findSkillIndexRoutePathColumn(cells) {
|
|
|
39
52
|
}
|
|
40
53
|
export function parseSkillIndexRoutes(content) {
|
|
41
54
|
const routes = [];
|
|
55
|
+
let currentCategory;
|
|
42
56
|
for (const line of content.split(/\r?\n/u)) {
|
|
57
|
+
const categoryHeading = /^###\s+(.+?)\s*$/u.exec(line.trim())?.[1];
|
|
58
|
+
if (categoryHeading) {
|
|
59
|
+
currentCategory = SKILL_ROUTE_CATEGORY_BY_HEADING.get(categoryHeading);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
43
62
|
if (!line.trim().startsWith('|')) {
|
|
44
63
|
continue;
|
|
45
64
|
}
|
|
@@ -63,6 +82,7 @@ export function parseSkillIndexRoutes(content) {
|
|
|
63
82
|
risk: cells[4] ?? '',
|
|
64
83
|
commandIntents: readBacktickValues(cells[SKILL_INDEX_VERIFICATION_INTENTS_COLUMN_INDEX] ?? ''),
|
|
65
84
|
expectedOutput: cells[6] ?? '',
|
|
85
|
+
category: currentCategory,
|
|
66
86
|
});
|
|
67
87
|
}
|
|
68
88
|
return routes;
|
|
@@ -19,6 +19,9 @@ const HIGH_RISK_SOURCE_ANCHOR_TAGS = new Set([
|
|
|
19
19
|
'ssrf',
|
|
20
20
|
'xss',
|
|
21
21
|
]);
|
|
22
|
+
export function hasHighRiskSourceAnchorRiskTags(risk) {
|
|
23
|
+
return risk.some((tag) => HIGH_RISK_SOURCE_ANCHOR_TAGS.has(tag));
|
|
24
|
+
}
|
|
22
25
|
function sha256(value) {
|
|
23
26
|
return `sha256:${createHash('sha256').update(value).digest('hex')}`;
|
|
24
27
|
}
|
|
@@ -62,7 +65,7 @@ function currentAnchorSignals(risk) {
|
|
|
62
65
|
};
|
|
63
66
|
}
|
|
64
67
|
function hasHighRisk(risk) {
|
|
65
|
-
return risk
|
|
68
|
+
return hasHighRiskSourceAnchorRiskTags(risk);
|
|
66
69
|
}
|
|
67
70
|
function sameSymbolIdentity(left, right) {
|
|
68
71
|
return left.kind === right.kind && left.name !== null && left.name === right.name;
|
|
@@ -3,6 +3,7 @@ import { isRecord, readStringArray, resolveMustflowConfigPath, } from './config-
|
|
|
3
3
|
import { readTomlFile } from './toml.js';
|
|
4
4
|
import { classifyVerificationCandidate, } from './verification-plan.js';
|
|
5
5
|
export const TEST_SELECTION_CONFIG_RELATIVE_PATH = '.mustflow/config/test-selection.toml';
|
|
6
|
+
const STALE_OR_MISSING_RULES_NOTE = 'Project-declared test selection rules did not cover the current changed files; review .mustflow/config/test-selection.toml for stale or missing rules.';
|
|
6
7
|
function uniqueSorted(values) {
|
|
7
8
|
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
8
9
|
}
|
|
@@ -203,7 +204,9 @@ export function createProjectTestSelectionPlan(projectRoot, classificationReport
|
|
|
203
204
|
matches.length > 0
|
|
204
205
|
? 'Matched project-declared test selection rules are treated as a minimum selected set.'
|
|
205
206
|
: 'No project-declared test selection rules matched the current changed files.',
|
|
207
|
+
...(matches.length > 0 ? [] : [STALE_OR_MISSING_RULES_NOTE]),
|
|
206
208
|
'Local index data and performance history may add suggestions later, but must not remove manifest-selected tests.',
|
|
209
|
+
'Absence of historical failures is not evidence that a test can be omitted.',
|
|
207
210
|
'Test targets are passed only when the selected command intent declares selection.accepts_test_targets = true.',
|
|
208
211
|
];
|
|
209
212
|
return {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const TEST_CHANGE_KINDS = new Set(['test', 'test_fixture']);
|
|
4
|
+
const SKIP_OR_ONLY_MARKER = /\b(?:describe|it|test)\s*\.\s*(?:skip|only)\s*\(/u;
|
|
5
|
+
function isTestClassification(classification) {
|
|
6
|
+
return classification.surface.category === 'test' || classification.changeKinds.some((kind) => TEST_CHANGE_KINDS.has(kind));
|
|
7
|
+
}
|
|
8
|
+
function resolveInsideRoot(projectRoot, relativePath) {
|
|
9
|
+
const resolvedPath = path.resolve(projectRoot, relativePath);
|
|
10
|
+
const relative = path.relative(projectRoot, resolvedPath);
|
|
11
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return resolvedPath;
|
|
15
|
+
}
|
|
16
|
+
function fileTextIfReadable(projectRoot, relativePath) {
|
|
17
|
+
const resolvedPath = resolveInsideRoot(projectRoot, relativePath);
|
|
18
|
+
if (resolvedPath === null || !existsSync(resolvedPath)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return readFileSync(resolvedPath, 'utf8');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function createValidationRatchetRisks(report, projectRoot) {
|
|
29
|
+
const risks = [];
|
|
30
|
+
for (const classification of report.classifications.filter(isTestClassification)) {
|
|
31
|
+
const resolvedPath = resolveInsideRoot(projectRoot, classification.path);
|
|
32
|
+
if (report.source === 'changed' && (resolvedPath === null || !existsSync(resolvedPath))) {
|
|
33
|
+
risks.push({
|
|
34
|
+
code: 'related_test_deleted',
|
|
35
|
+
severity: 'high',
|
|
36
|
+
path: classification.path,
|
|
37
|
+
detail: `Changed test path ${classification.path} is absent; deleted related tests require review before marking the task verified.`,
|
|
38
|
+
});
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const fileText = fileTextIfReadable(projectRoot, classification.path);
|
|
42
|
+
if (fileText !== null && SKIP_OR_ONLY_MARKER.test(fileText)) {
|
|
43
|
+
risks.push({
|
|
44
|
+
code: 'skip_or_only_marker_present',
|
|
45
|
+
severity: 'medium',
|
|
46
|
+
path: classification.path,
|
|
47
|
+
detail: `Changed test path ${classification.path} contains a .skip or .only marker; review whether validation was weakened before marking the task verified.`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return risks;
|
|
52
|
+
}
|
|
@@ -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
|
+
}
|
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
|
@@ -12,15 +12,21 @@ Current schemas:
|
|
|
12
12
|
including bounded declared-write drift metadata, a safe latest-run performance summary, and optional
|
|
13
13
|
structured phase timings and selection summaries
|
|
14
14
|
- `commands.schema.json`: parsed `.mustflow/config/commands.toml`
|
|
15
|
+
- `test-selection.schema.json`: parsed optional `.mustflow/config/test-selection.toml`
|
|
15
16
|
- `contract-lint-report.schema.json`: output of `mf contract-lint --json`
|
|
16
17
|
- `dashboard-export.schema.json`: bounded static export written by `mf dashboard --export-json <path>`,
|
|
17
|
-
including output policy, redaction and truncation metadata,
|
|
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
|
|
18
21
|
- `classify-report.schema.json`: output of `mf classify --changed --json` and
|
|
19
22
|
`mf classify <path...> --json`
|
|
20
23
|
- `impact-report.schema.json`: output of `mf impact --changed --json` and
|
|
21
24
|
`mf impact <path...> --json`
|
|
22
25
|
- `line-endings-report.schema.json`: output of `mf line-endings check --json` and
|
|
23
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
|
|
24
30
|
- `handoff-validation-report.schema.json`: output of
|
|
25
31
|
`mf handoff validate <path> --json`
|
|
26
32
|
- `version-sources-report.schema.json`: output of `mf version-sources --json`
|
|
@@ -28,9 +34,13 @@ Current schemas:
|
|
|
28
34
|
- `explain-report.schema.json`: output of `mf explain authority --json`, `mf explain command --json`,
|
|
29
35
|
`mf explain verify --reason <event> --json`, `mf explain retention --json`, `mf explain skills --json`,
|
|
30
36
|
and `mf explain surface --json`. Verify explanations include the shared `decisionGraph` evidence model.
|
|
31
|
-
- `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
|
|
32
42
|
- `change-verification-report.schema.json`: output of `mf verify --reason <event> --plan-only --json` and
|
|
33
|
-
`mf verify --from-
|
|
43
|
+
`mf verify --from-classification <classify-report.json> --plan-only --json`, including the `decision_graph` that links
|
|
34
44
|
changed surfaces, classification reasons, command candidates, eligibility, selected or not-selected state,
|
|
35
45
|
effects, and gaps.
|
|
36
46
|
Local-index command-effect graphs are explanation-only and cannot grant command authority.
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"candidates",
|
|
14
14
|
"gaps",
|
|
15
15
|
"schedule",
|
|
16
|
-
"decision_graph"
|
|
16
|
+
"decision_graph",
|
|
17
|
+
"test_selection"
|
|
17
18
|
],
|
|
18
19
|
"properties": {
|
|
19
20
|
"schema_version": {
|
|
@@ -28,6 +29,10 @@
|
|
|
28
29
|
"classification_summary": {
|
|
29
30
|
"$ref": "#/$defs/classificationSummary"
|
|
30
31
|
},
|
|
32
|
+
"verification_plan_id": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"pattern": "^sha256:[0-9a-f]{64}$"
|
|
35
|
+
},
|
|
31
36
|
"requirements": {
|
|
32
37
|
"type": "array",
|
|
33
38
|
"items": {
|
|
@@ -252,7 +257,16 @@
|
|
|
252
257
|
"verificationCandidate": {
|
|
253
258
|
"type": "object",
|
|
254
259
|
"additionalProperties": false,
|
|
255
|
-
"required": [
|
|
260
|
+
"required": [
|
|
261
|
+
"reason",
|
|
262
|
+
"intent",
|
|
263
|
+
"status",
|
|
264
|
+
"skipReason",
|
|
265
|
+
"detail",
|
|
266
|
+
"candidateState",
|
|
267
|
+
"eligibilityState",
|
|
268
|
+
"selectionState"
|
|
269
|
+
],
|
|
256
270
|
"properties": {
|
|
257
271
|
"reason": {
|
|
258
272
|
"type": "string"
|
|
@@ -147,6 +147,10 @@
|
|
|
147
147
|
},
|
|
148
148
|
"env_policy": { "$ref": "#/$defs/envPolicy" },
|
|
149
149
|
"env_allowlist": { "$ref": "#/$defs/stringArray" },
|
|
150
|
+
"manual_start_hint": { "type": "string" },
|
|
151
|
+
"health_check_url": { "type": "string" },
|
|
152
|
+
"stop_instruction": { "type": "string" },
|
|
153
|
+
"related_oneshot_checks": { "$ref": "#/$defs/stringArray" },
|
|
150
154
|
"effects": {
|
|
151
155
|
"type": "array",
|
|
152
156
|
"items": { "$ref": "#/$defs/effect" }
|
|
@@ -55,6 +55,35 @@
|
|
|
55
55
|
"type": "array",
|
|
56
56
|
"items": { "type": "string" }
|
|
57
57
|
},
|
|
58
|
+
"suggestions": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"items": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"additionalProperties": false,
|
|
63
|
+
"required": [
|
|
64
|
+
"sourceFile",
|
|
65
|
+
"sourceKind",
|
|
66
|
+
"sourceName",
|
|
67
|
+
"commandHint",
|
|
68
|
+
"suggestedIntent",
|
|
69
|
+
"status",
|
|
70
|
+
"reason",
|
|
71
|
+
"snippet"
|
|
72
|
+
],
|
|
73
|
+
"properties": {
|
|
74
|
+
"sourceFile": { "type": "string" },
|
|
75
|
+
"sourceKind": {
|
|
76
|
+
"enum": ["package_script", "make_target", "just_recipe"]
|
|
77
|
+
},
|
|
78
|
+
"sourceName": { "type": "string" },
|
|
79
|
+
"commandHint": { "type": "string" },
|
|
80
|
+
"suggestedIntent": { "type": "string" },
|
|
81
|
+
"status": { "const": "unknown" },
|
|
82
|
+
"reason": { "type": "string" },
|
|
83
|
+
"snippet": { "type": "string" }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
58
87
|
"coverage": {
|
|
59
88
|
"type": "object",
|
|
60
89
|
"additionalProperties": false,
|