mustflow 2.70.0 → 2.74.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 +19 -5
- package/dist/cli/commands/api.js +17 -0
- package/dist/cli/commands/evidence.js +71 -0
- package/dist/cli/commands/script-pack.js +124 -0
- package/dist/cli/commands/verify.js +50 -15
- package/dist/cli/commands/workspace.js +2 -0
- package/dist/cli/i18n/en.js +38 -0
- package/dist/cli/i18n/es.js +38 -0
- package/dist/cli/i18n/fr.js +38 -0
- package/dist/cli/i18n/hi.js +38 -0
- package/dist/cli/i18n/ko.js +38 -0
- package/dist/cli/i18n/zh.js +38 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/lib/agent-context.js +179 -10
- package/dist/cli/lib/command-registry.js +6 -0
- package/dist/cli/lib/dashboard-export.js +1 -0
- package/dist/cli/lib/script-pack-registry.js +27 -0
- package/dist/cli/script-packs/core-text-budget.js +241 -0
- package/dist/core/change-verification.js +10 -0
- package/dist/core/completion-verdict.js +14 -1
- package/dist/core/complexity-budget.js +206 -0
- package/dist/core/conflict-ledger.js +122 -0
- package/dist/core/failure-replay-capsule.js +213 -0
- package/dist/core/public-json-contracts.js +27 -0
- package/dist/core/risk-priced-evidence.js +213 -0
- package/dist/core/script-check-result.js +1 -0
- package/dist/core/text-budget.js +262 -0
- package/dist/core/verification-evidence.js +61 -13
- package/package.json +1 -1
- package/schemas/README.md +23 -11
- package/schemas/change-verification-report.schema.json +29 -0
- package/schemas/context-report.schema.json +58 -2
- package/schemas/dashboard-export.schema.json +42 -1
- package/schemas/diff-risk.schema.json +6 -0
- package/schemas/evidence-report.schema.json +45 -0
- package/schemas/latest-run-pointer.schema.json +50 -1
- package/schemas/script-pack-catalog.schema.json +68 -0
- package/schemas/text-budget-report.schema.json +131 -0
- package/schemas/verification-plan.schema.json +32 -0
- package/schemas/verify-report.schema.json +360 -1
- package/schemas/verify-run-manifest.schema.json +50 -1
- package/schemas/workspace-verification-plan.schema.json +32 -0
- package/templates/default/i18n.toml +2 -2
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -2
- package/templates/default/locales/en/.mustflow/skills/adapter-boundary/SKILL.md +19 -2
- package/templates/default/locales/en/.mustflow/skills/routes.toml +1 -1
- package/templates/default/manifest.toml +1 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
2
|
+
import { t } from '../lib/i18n.js';
|
|
3
|
+
import { formatCliOptionParseError, getParsedCliStringOption, hasCliOptionToken, hasParsedCliOption, parseCliOptions, } from '../lib/option-parser.js';
|
|
4
|
+
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
5
|
+
import { inspectTextBudget, TEXT_BUDGET_SCRIPT_REF, TEXT_BUDGET_UNITS, } from '../../core/text-budget.js';
|
|
6
|
+
const TEXT_BUDGET_OPTIONS = [
|
|
7
|
+
{ name: '--json', kind: 'boolean' },
|
|
8
|
+
{ name: '--min', kind: 'string' },
|
|
9
|
+
{ name: '--max', kind: 'string' },
|
|
10
|
+
{ name: '--exact', kind: 'string' },
|
|
11
|
+
{ name: '--unit', kind: 'string' },
|
|
12
|
+
{ name: '--json-pointer', kind: 'string' },
|
|
13
|
+
];
|
|
14
|
+
export function getCoreTextBudgetHelp(lang = 'en') {
|
|
15
|
+
return renderHelp({
|
|
16
|
+
usage: 'mf script-pack run core/text-budget check <path...> [options]',
|
|
17
|
+
summary: t(lang, 'textBudget.help.summary'),
|
|
18
|
+
options: [
|
|
19
|
+
{ label: '--min <count>', description: t(lang, 'textBudget.help.option.min') },
|
|
20
|
+
{ label: '--max <count>', description: t(lang, 'textBudget.help.option.max') },
|
|
21
|
+
{ label: '--exact <count>', description: t(lang, 'textBudget.help.option.exact') },
|
|
22
|
+
{ label: '--unit <unit>', description: t(lang, 'textBudget.help.option.unit') },
|
|
23
|
+
{ label: '--json-pointer <pointer>', description: t(lang, 'textBudget.help.option.jsonPointer') },
|
|
24
|
+
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
25
|
+
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
26
|
+
],
|
|
27
|
+
examples: [
|
|
28
|
+
'mf script-pack run core/text-budget check README.md --max 5000',
|
|
29
|
+
'mf script-pack run core/text-budget check package.json --json-pointer /description --max 80 --json',
|
|
30
|
+
'mf script-pack run core/text-budget check docs/intro.md --min 20 --max 120 --unit word',
|
|
31
|
+
],
|
|
32
|
+
exitCodes: [
|
|
33
|
+
{ label: '0', description: t(lang, 'textBudget.help.exit.ok') },
|
|
34
|
+
{ label: '1', description: t(lang, 'textBudget.help.exit.fail') },
|
|
35
|
+
],
|
|
36
|
+
}, lang);
|
|
37
|
+
}
|
|
38
|
+
function parseNonNegativeInteger(value, option, lang) {
|
|
39
|
+
if (value === null) {
|
|
40
|
+
return { value: null };
|
|
41
|
+
}
|
|
42
|
+
if (!/^(?:0|[1-9]\d*)$/u.test(value)) {
|
|
43
|
+
return { value: null, error: t(lang, 'textBudget.error.invalidNumber', { option, value }) };
|
|
44
|
+
}
|
|
45
|
+
const parsed = Number(value);
|
|
46
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
47
|
+
return { value: null, error: t(lang, 'textBudget.error.invalidNumber', { option, value }) };
|
|
48
|
+
}
|
|
49
|
+
return { value: parsed };
|
|
50
|
+
}
|
|
51
|
+
function parseTextBudgetUnit(value, lang) {
|
|
52
|
+
if (value === null) {
|
|
53
|
+
return { value: 'grapheme' };
|
|
54
|
+
}
|
|
55
|
+
if (TEXT_BUDGET_UNITS.includes(value)) {
|
|
56
|
+
return { value: value };
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
value: 'grapheme',
|
|
60
|
+
error: t(lang, 'textBudget.error.invalidUnit', { unit: value, allowed: TEXT_BUDGET_UNITS.join(', ') }),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function parseTextBudgetOptions(args, lang) {
|
|
64
|
+
const [action, ...rest] = args;
|
|
65
|
+
const parsed = parseCliOptions(rest, TEXT_BUDGET_OPTIONS, {
|
|
66
|
+
allowPositionals: true,
|
|
67
|
+
allowEmptyStringValues: true,
|
|
68
|
+
});
|
|
69
|
+
const json = hasParsedCliOption(parsed, '--json');
|
|
70
|
+
const min = parseNonNegativeInteger(getParsedCliStringOption(parsed, '--min'), '--min', lang);
|
|
71
|
+
const max = parseNonNegativeInteger(getParsedCliStringOption(parsed, '--max'), '--max', lang);
|
|
72
|
+
const exact = parseNonNegativeInteger(getParsedCliStringOption(parsed, '--exact'), '--exact', lang);
|
|
73
|
+
const unit = parseTextBudgetUnit(getParsedCliStringOption(parsed, '--unit'), lang);
|
|
74
|
+
const jsonPointer = getParsedCliStringOption(parsed, '--json-pointer');
|
|
75
|
+
if (action !== 'check') {
|
|
76
|
+
return {
|
|
77
|
+
action: 'check',
|
|
78
|
+
json,
|
|
79
|
+
paths: parsed.positionals,
|
|
80
|
+
min: min.value,
|
|
81
|
+
max: max.value,
|
|
82
|
+
exact: exact.value,
|
|
83
|
+
unit: unit.value,
|
|
84
|
+
jsonPointer,
|
|
85
|
+
error: action ? t(lang, 'textBudget.error.unknownAction', { action }) : t(lang, 'textBudget.error.missingAction'),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (parsed.error) {
|
|
89
|
+
return {
|
|
90
|
+
action,
|
|
91
|
+
json,
|
|
92
|
+
paths: parsed.positionals,
|
|
93
|
+
min: min.value,
|
|
94
|
+
max: max.value,
|
|
95
|
+
exact: exact.value,
|
|
96
|
+
unit: unit.value,
|
|
97
|
+
jsonPointer,
|
|
98
|
+
error: formatCliOptionParseError(parsed.error, lang),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
for (const candidate of [min, max, exact, unit]) {
|
|
102
|
+
if (candidate.error) {
|
|
103
|
+
return {
|
|
104
|
+
action,
|
|
105
|
+
json,
|
|
106
|
+
paths: parsed.positionals,
|
|
107
|
+
min: min.value,
|
|
108
|
+
max: max.value,
|
|
109
|
+
exact: exact.value,
|
|
110
|
+
unit: unit.value,
|
|
111
|
+
jsonPointer,
|
|
112
|
+
error: candidate.error,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (parsed.positionals.length === 0) {
|
|
117
|
+
return {
|
|
118
|
+
action,
|
|
119
|
+
json,
|
|
120
|
+
paths: parsed.positionals,
|
|
121
|
+
min: min.value,
|
|
122
|
+
max: max.value,
|
|
123
|
+
exact: exact.value,
|
|
124
|
+
unit: unit.value,
|
|
125
|
+
jsonPointer,
|
|
126
|
+
error: t(lang, 'textBudget.error.missingPath'),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (exact.value !== null && (min.value !== null || max.value !== null)) {
|
|
130
|
+
return {
|
|
131
|
+
action,
|
|
132
|
+
json,
|
|
133
|
+
paths: parsed.positionals,
|
|
134
|
+
min: min.value,
|
|
135
|
+
max: max.value,
|
|
136
|
+
exact: exact.value,
|
|
137
|
+
unit: unit.value,
|
|
138
|
+
jsonPointer,
|
|
139
|
+
error: t(lang, 'textBudget.error.exactConflict'),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (exact.value === null && min.value === null && max.value === null) {
|
|
143
|
+
return {
|
|
144
|
+
action,
|
|
145
|
+
json,
|
|
146
|
+
paths: parsed.positionals,
|
|
147
|
+
min: min.value,
|
|
148
|
+
max: max.value,
|
|
149
|
+
exact: exact.value,
|
|
150
|
+
unit: unit.value,
|
|
151
|
+
jsonPointer,
|
|
152
|
+
error: t(lang, 'textBudget.error.missingBudget'),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (min.value !== null && max.value !== null && min.value > max.value) {
|
|
156
|
+
return {
|
|
157
|
+
action,
|
|
158
|
+
json,
|
|
159
|
+
paths: parsed.positionals,
|
|
160
|
+
min: min.value,
|
|
161
|
+
max: max.value,
|
|
162
|
+
exact: exact.value,
|
|
163
|
+
unit: unit.value,
|
|
164
|
+
jsonPointer,
|
|
165
|
+
error: t(lang, 'textBudget.error.minGreaterThanMax'),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
action,
|
|
170
|
+
json,
|
|
171
|
+
paths: parsed.positionals,
|
|
172
|
+
min: min.value,
|
|
173
|
+
max: max.value,
|
|
174
|
+
exact: exact.value,
|
|
175
|
+
unit: unit.value,
|
|
176
|
+
jsonPointer,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function renderBudget(report) {
|
|
180
|
+
if (report.policy.exact !== null) {
|
|
181
|
+
return `exact ${report.policy.exact} ${report.policy.unit}`;
|
|
182
|
+
}
|
|
183
|
+
const lower = report.policy.min === null ? null : `min ${report.policy.min}`;
|
|
184
|
+
const upper = report.policy.max === null ? null : `max ${report.policy.max}`;
|
|
185
|
+
return [lower, upper].filter((part) => part !== null).join(', ') + ` ${report.policy.unit}`;
|
|
186
|
+
}
|
|
187
|
+
function renderTextBudgetSummary(report, lang) {
|
|
188
|
+
const lines = [
|
|
189
|
+
t(lang, 'textBudget.title'),
|
|
190
|
+
`${t(lang, 'scriptPack.label.script')}: ${TEXT_BUDGET_SCRIPT_REF}`,
|
|
191
|
+
`${t(lang, 'label.status')}: ${report.status}`,
|
|
192
|
+
`${t(lang, 'textBudget.label.budget')}: ${renderBudget(report)}`,
|
|
193
|
+
`${t(lang, 'textBudget.label.checkedTargets')}: ${report.metrics.length}`,
|
|
194
|
+
`${t(lang, 'textBudget.label.findings')}: ${report.findings.length}`,
|
|
195
|
+
];
|
|
196
|
+
if (report.metrics.length > 0) {
|
|
197
|
+
lines.push(t(lang, 'textBudget.label.metrics'));
|
|
198
|
+
for (const metric of report.metrics) {
|
|
199
|
+
const pointer = metric.json_pointer === null ? '' : ` ${metric.json_pointer}`;
|
|
200
|
+
lines.push(`- ${metric.path}${pointer}: ${metric.value} ${metric.unit}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (report.findings.length > 0) {
|
|
204
|
+
lines.push(t(lang, 'textBudget.label.findings'));
|
|
205
|
+
for (const finding of report.findings) {
|
|
206
|
+
lines.push(`- ${finding.path}: ${finding.code} (${finding.message})`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (report.issues.length > 0) {
|
|
210
|
+
lines.push(t(lang, 'textBudget.label.issues'), ...report.issues.map((issue) => `- ${issue}`));
|
|
211
|
+
}
|
|
212
|
+
if (report.findings.length === 0 && report.issues.length === 0) {
|
|
213
|
+
lines.push(t(lang, 'textBudget.clean'));
|
|
214
|
+
}
|
|
215
|
+
return lines.join('\n');
|
|
216
|
+
}
|
|
217
|
+
export function runCoreTextBudgetScript(args, reporter, lang = 'en') {
|
|
218
|
+
if (hasCliOptionToken(args, '--help', ['-h'])) {
|
|
219
|
+
reporter.stdout(getCoreTextBudgetHelp(lang));
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
const options = parseTextBudgetOptions(args, lang);
|
|
223
|
+
if (options.error) {
|
|
224
|
+
printUsageError(reporter, options.error, 'mf script-pack run core/text-budget --help', getCoreTextBudgetHelp(lang), lang);
|
|
225
|
+
return 1;
|
|
226
|
+
}
|
|
227
|
+
const report = inspectTextBudget(resolveMustflowRoot(), {
|
|
228
|
+
paths: options.paths,
|
|
229
|
+
min: options.min,
|
|
230
|
+
max: options.max,
|
|
231
|
+
exact: options.exact,
|
|
232
|
+
unit: options.unit,
|
|
233
|
+
jsonPointer: options.jsonPointer,
|
|
234
|
+
});
|
|
235
|
+
if (options.json) {
|
|
236
|
+
reporter.stdout(JSON.stringify(report, null, 2));
|
|
237
|
+
return report.ok ? 0 : 1;
|
|
238
|
+
}
|
|
239
|
+
reporter.stdout(renderTextBudgetSummary(report, lang));
|
|
240
|
+
return report.ok ? 0 : 1;
|
|
241
|
+
}
|
|
@@ -4,6 +4,7 @@ import { classifyVerificationCandidate, createVerificationPlan, } from './verifi
|
|
|
4
4
|
import { createVerificationDecisionGraph, } from './verification-decision-graph.js';
|
|
5
5
|
import { createVerificationSchedule, } from './verification-scheduler.js';
|
|
6
6
|
import { createProjectTestSelectionPlan, } from './test-selection.js';
|
|
7
|
+
import { createVerificationRiskAssessment, } from './risk-priced-evidence.js';
|
|
7
8
|
export const CHANGE_VERIFICATION_SCHEMA_VERSION = '1';
|
|
8
9
|
function uniqueSorted(values) {
|
|
9
10
|
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
@@ -328,6 +329,14 @@ export function createChangeVerificationReport(classificationReport, commandCont
|
|
|
328
329
|
const gaps = requirements
|
|
329
330
|
.map((requirement) => gapForRequirement(requirement, candidates))
|
|
330
331
|
.filter((gap) => gap !== null);
|
|
332
|
+
const riskAssessment = createVerificationRiskAssessment({
|
|
333
|
+
classificationSummary: classificationReport.summary,
|
|
334
|
+
requirements,
|
|
335
|
+
candidates,
|
|
336
|
+
gaps,
|
|
337
|
+
commandContract,
|
|
338
|
+
selectedIntents: schedule.entries.map((entry) => entry.intent),
|
|
339
|
+
});
|
|
331
340
|
return {
|
|
332
341
|
schema_version: CHANGE_VERIFICATION_SCHEMA_VERSION,
|
|
333
342
|
source: classificationReport.source,
|
|
@@ -336,6 +345,7 @@ export function createChangeVerificationReport(classificationReport, commandCont
|
|
|
336
345
|
requirements,
|
|
337
346
|
candidates,
|
|
338
347
|
gaps,
|
|
348
|
+
risk_assessment: riskAssessment,
|
|
339
349
|
schedule,
|
|
340
350
|
decision_graph: createVerificationDecisionGraph(commandContract, requirements, candidates, gaps, schedule),
|
|
341
351
|
test_selection: testSelectionPlan.report,
|
|
@@ -10,6 +10,7 @@ function createRiskEvidence(input) {
|
|
|
10
10
|
receipt_binding: input.receiptBindingRiskCount ?? 0,
|
|
11
11
|
stale_receipt: input.staleReceiptCount ?? 0,
|
|
12
12
|
plan_mismatch: input.planMismatchCount ?? 0,
|
|
13
|
+
risk_priced_evidence: input.riskPricedEvidenceRiskCount ?? 0,
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
function emptyReceiptBindingEvidence() {
|
|
@@ -158,6 +159,9 @@ function verifyStatus(input) {
|
|
|
158
159
|
if ((input.externalEvidenceRiskCount ?? 0) > 0) {
|
|
159
160
|
downgradeLimitations.push('external_evidence_requires_review');
|
|
160
161
|
}
|
|
162
|
+
if ((input.riskPricedEvidenceRiskCount ?? 0) > 0) {
|
|
163
|
+
downgradeLimitations.push('risk_priced_evidence_requires_review');
|
|
164
|
+
}
|
|
161
165
|
if (downgradeLimitations.length > 0) {
|
|
162
166
|
return {
|
|
163
167
|
status: 'partially_verified',
|
|
@@ -175,7 +179,9 @@ function verifyStatus(input) {
|
|
|
175
179
|
? 'stale_receipt_review_required'
|
|
176
180
|
: (input.reproEvidenceRiskCount ?? 0) > 0
|
|
177
181
|
? 'repro_evidence_missing'
|
|
178
|
-
:
|
|
182
|
+
: (input.externalEvidenceRiskCount ?? 0) > 0
|
|
183
|
+
? 'external_evidence_review_required'
|
|
184
|
+
: 'risk_priced_evidence_review_required',
|
|
179
185
|
blockers: [],
|
|
180
186
|
contradictions: [],
|
|
181
187
|
limitations: downgradeLimitations,
|
|
@@ -230,6 +236,7 @@ export function createVerifyCompletionVerdict(input) {
|
|
|
230
236
|
receipt_binding_risk_count: normalizedInput.receiptBindingRiskCount ?? 0,
|
|
231
237
|
stale_receipt_count: normalizedInput.staleReceiptCount ?? 0,
|
|
232
238
|
plan_mismatch_count: normalizedInput.planMismatchCount ?? 0,
|
|
239
|
+
risk_priced_evidence_risk_count: normalizedInput.riskPricedEvidenceRiskCount ?? 0,
|
|
233
240
|
risks,
|
|
234
241
|
receipt_binding: receiptBinding,
|
|
235
242
|
latest_run_status: null,
|
|
@@ -269,6 +276,11 @@ export function createDashboardCompletionVerdict(input) {
|
|
|
269
276
|
primaryReason = 'scope_diff_review_required';
|
|
270
277
|
limitations.push('scope_diff_risk_requires_review');
|
|
271
278
|
}
|
|
279
|
+
else if ((input.riskPricedEvidenceRiskCount ?? 0) > 0) {
|
|
280
|
+
status = 'partially_verified';
|
|
281
|
+
primaryReason = 'risk_priced_evidence_review_required';
|
|
282
|
+
limitations.push('risk_priced_evidence_requires_review');
|
|
283
|
+
}
|
|
272
284
|
else if (input.gapCount > 0) {
|
|
273
285
|
status = 'blocked';
|
|
274
286
|
primaryReason = 'verification_gaps_present';
|
|
@@ -331,6 +343,7 @@ export function createDashboardCompletionVerdict(input) {
|
|
|
331
343
|
receipt_binding_risk_count: input.receiptBindingRiskCount ?? 0,
|
|
332
344
|
stale_receipt_count: input.staleReceiptCount ?? 0,
|
|
333
345
|
plan_mismatch_count: input.planMismatchCount ?? 0,
|
|
346
|
+
risk_priced_evidence_risk_count: input.riskPricedEvidenceRiskCount ?? 0,
|
|
334
347
|
risks,
|
|
335
348
|
receipt_binding: receiptBinding,
|
|
336
349
|
latest_run_status: input.latestRunStatus,
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
export const COMPLEXITY_BUDGET_DEFAULTS = {
|
|
2
|
+
scoreBudget: 12,
|
|
3
|
+
maxConfigSurfaces: 2,
|
|
4
|
+
maxSchemaSurfaces: 2,
|
|
5
|
+
maxPathsPerRisk: 8,
|
|
6
|
+
};
|
|
7
|
+
const CHANGE_KIND_FAMILY_BY_KIND = {
|
|
8
|
+
documentation: 'documentation',
|
|
9
|
+
example: 'documentation',
|
|
10
|
+
translation: 'documentation',
|
|
11
|
+
workflow: 'workflow',
|
|
12
|
+
host_instruction: 'workflow',
|
|
13
|
+
installed_template: 'template',
|
|
14
|
+
package_metadata: 'release',
|
|
15
|
+
schema: 'contract',
|
|
16
|
+
test: 'test',
|
|
17
|
+
test_fixture: 'test',
|
|
18
|
+
implementation: 'implementation',
|
|
19
|
+
unknown: 'unknown',
|
|
20
|
+
};
|
|
21
|
+
const DEPENDENCY_SURFACE_FILENAMES = new Set([
|
|
22
|
+
'package.json',
|
|
23
|
+
'package-lock.json',
|
|
24
|
+
'pnpm-lock.yaml',
|
|
25
|
+
'bun.lock',
|
|
26
|
+
'bun.lockb',
|
|
27
|
+
'yarn.lock',
|
|
28
|
+
'Cargo.toml',
|
|
29
|
+
'Cargo.lock',
|
|
30
|
+
'go.mod',
|
|
31
|
+
'go.sum',
|
|
32
|
+
'pyproject.toml',
|
|
33
|
+
'pom.xml',
|
|
34
|
+
'build.gradle',
|
|
35
|
+
'deno.json',
|
|
36
|
+
'deno.jsonc',
|
|
37
|
+
]);
|
|
38
|
+
const CONFIG_SURFACE_FILENAMES = new Set(['Makefile', 'justfile', 'Dockerfile']);
|
|
39
|
+
const CONFIG_FILE_PATTERN = /(?:config|rc)\.(?:cjs|cts|js|json|mjs|mts|toml|ts|yaml|yml)$/u;
|
|
40
|
+
const HELPER_FILE_PATTERN = /.*(?:helper|helpers|util|utils|manager|common|misc).*\.(?:cjs|cts|go|java|js|jsx|kt|mjs|mts|php|ps1|py|rb|rs|swift|ts|tsx)$/u;
|
|
41
|
+
const REQUIREMENTS_FILE_PATTERN = /^requirements(?:-[^/]*)?\.txt$/u;
|
|
42
|
+
const TSCONFIG_FILE_PATTERN = /^tsconfig[^/]*\.json$/u;
|
|
43
|
+
function uniqueSorted(values) {
|
|
44
|
+
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
45
|
+
}
|
|
46
|
+
function changeKindFamily(kind) {
|
|
47
|
+
return CHANGE_KIND_FAMILY_BY_KIND[kind] ?? kind;
|
|
48
|
+
}
|
|
49
|
+
function baseName(file) {
|
|
50
|
+
const slashIndex = file.lastIndexOf('/');
|
|
51
|
+
return slashIndex === -1 ? file : file.slice(slashIndex + 1);
|
|
52
|
+
}
|
|
53
|
+
function isDependencySurface(file) {
|
|
54
|
+
const name = baseName(file);
|
|
55
|
+
return DEPENDENCY_SURFACE_FILENAMES.has(name) || REQUIREMENTS_FILE_PATTERN.test(name);
|
|
56
|
+
}
|
|
57
|
+
function isConfigurationSurface(file) {
|
|
58
|
+
const name = baseName(file);
|
|
59
|
+
return (file.startsWith('.github/') ||
|
|
60
|
+
file.startsWith('.mustflow/config/') ||
|
|
61
|
+
CONFIG_SURFACE_FILENAMES.has(name) ||
|
|
62
|
+
CONFIG_FILE_PATTERN.test(name) ||
|
|
63
|
+
TSCONFIG_FILE_PATTERN.test(name));
|
|
64
|
+
}
|
|
65
|
+
function isHelperContainer(file) {
|
|
66
|
+
return HELPER_FILE_PATTERN.test(baseName(file));
|
|
67
|
+
}
|
|
68
|
+
function isSchemaSurface(file) {
|
|
69
|
+
return file.startsWith('schemas/') && file.endsWith('.schema.json');
|
|
70
|
+
}
|
|
71
|
+
function matchingPaths(files, predicate) {
|
|
72
|
+
return files.filter(predicate);
|
|
73
|
+
}
|
|
74
|
+
function firstPaths(paths) {
|
|
75
|
+
return paths.slice(0, COMPLEXITY_BUDGET_DEFAULTS.maxPathsPerRisk);
|
|
76
|
+
}
|
|
77
|
+
function metric(name, value, weight) {
|
|
78
|
+
return {
|
|
79
|
+
name,
|
|
80
|
+
value,
|
|
81
|
+
weight,
|
|
82
|
+
weighted_score: value * weight,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function scoreMetrics(input) {
|
|
86
|
+
const families = uniqueSorted(input.summary.changeKinds.map(changeKindFamily));
|
|
87
|
+
const dependencySurfaces = matchingPaths(input.files, isDependencySurface);
|
|
88
|
+
const configSurfaces = matchingPaths(input.files, isConfigurationSurface);
|
|
89
|
+
const helperContainers = matchingPaths(input.files, isHelperContainer);
|
|
90
|
+
const schemaSurfaces = matchingPaths(input.files, isSchemaSurface);
|
|
91
|
+
return [
|
|
92
|
+
metric('changed_files', input.summary.fileCount, 1),
|
|
93
|
+
metric('public_surfaces', input.summary.publicSurfaceCount, 2),
|
|
94
|
+
metric('change_kind_families_beyond_one', Math.max(0, families.length - 1), 2),
|
|
95
|
+
metric('dependency_surfaces', dependencySurfaces.length, 5),
|
|
96
|
+
metric('configuration_surfaces', configSurfaces.length, 3),
|
|
97
|
+
metric('helper_containers', helperContainers.length, 3),
|
|
98
|
+
metric('schema_surfaces', schemaSurfaces.length, 2),
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
function risk(input) {
|
|
102
|
+
return {
|
|
103
|
+
code: input.code,
|
|
104
|
+
severity: 'warning',
|
|
105
|
+
detail: input.detail,
|
|
106
|
+
count: input.count,
|
|
107
|
+
limit: input.limit,
|
|
108
|
+
paths: firstPaths(input.paths),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function createComplexityBudgetRisks(input, score) {
|
|
112
|
+
const dependencySurfaces = matchingPaths(input.files, isDependencySurface);
|
|
113
|
+
const configSurfaces = matchingPaths(input.files, isConfigurationSurface);
|
|
114
|
+
const helperContainers = matchingPaths(input.files, isHelperContainer);
|
|
115
|
+
const schemaSurfaces = matchingPaths(input.files, isSchemaSurface);
|
|
116
|
+
const risks = [];
|
|
117
|
+
if (score > COMPLEXITY_BUDGET_DEFAULTS.scoreBudget) {
|
|
118
|
+
risks.push(risk({
|
|
119
|
+
code: 'complexity_score_budget_exceeded',
|
|
120
|
+
detail: `Complexity score ${score} exceeds the review budget of ${COMPLEXITY_BUDGET_DEFAULTS.scoreBudget}.`,
|
|
121
|
+
count: score,
|
|
122
|
+
limit: COMPLEXITY_BUDGET_DEFAULTS.scoreBudget,
|
|
123
|
+
paths: input.files,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
if (dependencySurfaces.length > 0) {
|
|
127
|
+
risks.push(risk({
|
|
128
|
+
code: 'dependency_surface_requires_justification',
|
|
129
|
+
detail: [
|
|
130
|
+
'Dependency or package manifest changes add operational cost;',
|
|
131
|
+
'justify the dependency boundary and rollback path.',
|
|
132
|
+
].join(' '),
|
|
133
|
+
count: dependencySurfaces.length,
|
|
134
|
+
limit: 0,
|
|
135
|
+
paths: dependencySurfaces,
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
if (helperContainers.length > 0) {
|
|
139
|
+
risks.push(risk({
|
|
140
|
+
code: 'helper_container_requires_justification',
|
|
141
|
+
detail: [
|
|
142
|
+
'Helper, util, common, manager, or misc files tend to hide broad abstractions;',
|
|
143
|
+
'justify why the new surface reduces net complexity.',
|
|
144
|
+
].join(' '),
|
|
145
|
+
count: helperContainers.length,
|
|
146
|
+
limit: 0,
|
|
147
|
+
paths: helperContainers,
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
if (configSurfaces.length > COMPLEXITY_BUDGET_DEFAULTS.maxConfigSurfaces) {
|
|
151
|
+
risks.push(risk({
|
|
152
|
+
code: 'configuration_surface_budget_exceeded',
|
|
153
|
+
detail: [
|
|
154
|
+
`Configuration surface count ${configSurfaces.length}`,
|
|
155
|
+
`exceeds the review budget of ${COMPLEXITY_BUDGET_DEFAULTS.maxConfigSurfaces}.`,
|
|
156
|
+
].join(' '),
|
|
157
|
+
count: configSurfaces.length,
|
|
158
|
+
limit: COMPLEXITY_BUDGET_DEFAULTS.maxConfigSurfaces,
|
|
159
|
+
paths: configSurfaces,
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
if (schemaSurfaces.length > COMPLEXITY_BUDGET_DEFAULTS.maxSchemaSurfaces) {
|
|
163
|
+
risks.push(risk({
|
|
164
|
+
code: 'schema_surface_budget_exceeded',
|
|
165
|
+
detail: [
|
|
166
|
+
`Schema surface count ${schemaSurfaces.length}`,
|
|
167
|
+
`exceeds the review budget of ${COMPLEXITY_BUDGET_DEFAULTS.maxSchemaSurfaces}.`,
|
|
168
|
+
].join(' '),
|
|
169
|
+
count: schemaSurfaces.length,
|
|
170
|
+
limit: COMPLEXITY_BUDGET_DEFAULTS.maxSchemaSurfaces,
|
|
171
|
+
paths: schemaSurfaces,
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
return risks;
|
|
175
|
+
}
|
|
176
|
+
function reviewPrompts(risks) {
|
|
177
|
+
if (risks.length === 0) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
return [
|
|
181
|
+
'Explain why this added complexity is necessary for the requested outcome.',
|
|
182
|
+
'Name the simpler alternative that was rejected and why it is not enough.',
|
|
183
|
+
'Identify the rollback or removal path if the added surface proves unnecessary.',
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
export function createComplexityBudgetReport(input) {
|
|
187
|
+
const metrics = scoreMetrics(input);
|
|
188
|
+
const score = metrics.reduce((total, entry) => total + entry.weighted_score, 0);
|
|
189
|
+
const risks = createComplexityBudgetRisks(input, score);
|
|
190
|
+
return {
|
|
191
|
+
schema_version: '1',
|
|
192
|
+
source: 'change_classification',
|
|
193
|
+
status: risks.length > 0 ? 'review_required' : 'within_budget',
|
|
194
|
+
score,
|
|
195
|
+
budget: COMPLEXITY_BUDGET_DEFAULTS.scoreBudget,
|
|
196
|
+
metrics,
|
|
197
|
+
risks,
|
|
198
|
+
review_prompts: reviewPrompts(risks),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
export function createComplexityBudgetReportFromVerification(report) {
|
|
202
|
+
return createComplexityBudgetReport({
|
|
203
|
+
files: report.files,
|
|
204
|
+
summary: report.classification_summary,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
function hashConflictIdentity(value) {
|
|
3
|
+
return `conflict:${createHash('sha256').update(JSON.stringify(value)).digest('hex').slice(0, 16)}`;
|
|
4
|
+
}
|
|
5
|
+
function uniqueSorted(values) {
|
|
6
|
+
return [...new Set([...values].filter((value) => typeof value === 'string' && value.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
7
|
+
}
|
|
8
|
+
function remainingRiskSeverity(severity) {
|
|
9
|
+
if (severity === 'critical') {
|
|
10
|
+
return 'critical';
|
|
11
|
+
}
|
|
12
|
+
if (severity === 'high' || severity === 'error') {
|
|
13
|
+
return 'error';
|
|
14
|
+
}
|
|
15
|
+
if (severity === 'medium' || severity === 'warning') {
|
|
16
|
+
return 'warning';
|
|
17
|
+
}
|
|
18
|
+
return 'info';
|
|
19
|
+
}
|
|
20
|
+
function createConflictItem(input) {
|
|
21
|
+
return {
|
|
22
|
+
id: hashConflictIdentity({
|
|
23
|
+
kind: input.kind,
|
|
24
|
+
summary: input.summary,
|
|
25
|
+
detail: input.detail,
|
|
26
|
+
sources: input.sources,
|
|
27
|
+
}),
|
|
28
|
+
status: 'open',
|
|
29
|
+
owner: 'user_or_maintainer',
|
|
30
|
+
expires_at: null,
|
|
31
|
+
...input,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function verdictItem(kind, key) {
|
|
35
|
+
const contradiction = kind === 'contradiction';
|
|
36
|
+
return createConflictItem({
|
|
37
|
+
kind,
|
|
38
|
+
severity: contradiction ? 'critical' : 'error',
|
|
39
|
+
summary: key,
|
|
40
|
+
detail: contradiction
|
|
41
|
+
? `Completion evidence is contradicted by ${key}.`
|
|
42
|
+
: `Completion is blocked by ${key}.`,
|
|
43
|
+
sources: [
|
|
44
|
+
{
|
|
45
|
+
kind: 'completion_verdict',
|
|
46
|
+
authority: 'verdict',
|
|
47
|
+
key,
|
|
48
|
+
paths: [],
|
|
49
|
+
surfaces: [],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
affected_paths: [],
|
|
53
|
+
affected_surfaces: [],
|
|
54
|
+
blocks_completion: true,
|
|
55
|
+
resolution: contradiction
|
|
56
|
+
? 'Resolve the contradictory evidence and rerun verification before claiming completion.'
|
|
57
|
+
: 'Resolve the blocker and provide fresh verification evidence before claiming completion.',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function gapItem(gap) {
|
|
61
|
+
const key = gap.reason ?? gap.intent ?? gap.status ?? 'verification_gap';
|
|
62
|
+
return createConflictItem({
|
|
63
|
+
kind: 'verification_gap',
|
|
64
|
+
severity: 'error',
|
|
65
|
+
summary: key,
|
|
66
|
+
detail: gap.detail ?? 'Verification coverage is missing for this requirement.',
|
|
67
|
+
sources: [
|
|
68
|
+
{
|
|
69
|
+
kind: 'verification_gap',
|
|
70
|
+
authority: 'verification_plan',
|
|
71
|
+
key,
|
|
72
|
+
paths: uniqueSorted(gap.files),
|
|
73
|
+
surfaces: uniqueSorted(gap.surfaces),
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
affected_paths: uniqueSorted(gap.files),
|
|
77
|
+
affected_surfaces: uniqueSorted(gap.surfaces),
|
|
78
|
+
blocks_completion: true,
|
|
79
|
+
resolution: 'Add or run an allowed verification intent that covers this gap, then refresh the evidence model.',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function riskItem(risk) {
|
|
83
|
+
const severity = remainingRiskSeverity(risk.severity);
|
|
84
|
+
return createConflictItem({
|
|
85
|
+
kind: 'remaining_risk',
|
|
86
|
+
severity,
|
|
87
|
+
summary: risk.code,
|
|
88
|
+
detail: risk.detail,
|
|
89
|
+
sources: [
|
|
90
|
+
{
|
|
91
|
+
kind: 'remaining_risk',
|
|
92
|
+
authority: 'evidence_model',
|
|
93
|
+
key: risk.code,
|
|
94
|
+
paths: [],
|
|
95
|
+
surfaces: [],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
affected_paths: [],
|
|
99
|
+
affected_surfaces: [],
|
|
100
|
+
blocks_completion: severity === 'error' || severity === 'critical',
|
|
101
|
+
resolution: 'Review the remaining risk and attach stronger evidence or an explicit acceptance decision.',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export function createConflictLedger(input) {
|
|
105
|
+
const items = [
|
|
106
|
+
...input.verdict.blockers.map((blocker) => verdictItem('blocker', blocker)),
|
|
107
|
+
...input.verdict.contradictions.map((contradiction) => verdictItem('contradiction', contradiction)),
|
|
108
|
+
...input.gaps.map(gapItem),
|
|
109
|
+
...input.remainingRisks.map(riskItem),
|
|
110
|
+
];
|
|
111
|
+
const blockingCount = items.filter((item) => item.blocks_completion).length;
|
|
112
|
+
const contradictionCount = items.filter((item) => item.kind === 'contradiction').length;
|
|
113
|
+
return {
|
|
114
|
+
schema_version: '1',
|
|
115
|
+
source: 'verification_evidence_model',
|
|
116
|
+
status: items.length > 0 ? 'open' : 'clear',
|
|
117
|
+
item_count: items.length,
|
|
118
|
+
blocking_count: blockingCount,
|
|
119
|
+
contradiction_count: contradictionCount,
|
|
120
|
+
items,
|
|
121
|
+
};
|
|
122
|
+
}
|