mustflow 2.23.0 → 2.25.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 +12 -2
- package/dist/cli/commands/adapters.js +11 -9
- package/dist/cli/commands/api.js +263 -113
- package/dist/cli/commands/check.js +11 -7
- package/dist/cli/commands/classify.js +16 -42
- package/dist/cli/commands/context.js +18 -31
- package/dist/cli/commands/contract-lint.js +12 -7
- package/dist/cli/commands/dashboard.js +65 -114
- package/dist/cli/commands/docs.js +43 -26
- package/dist/cli/commands/doctor.js +11 -7
- package/dist/cli/commands/evidence.js +642 -0
- package/dist/cli/commands/explain-verify.js +1 -59
- package/dist/cli/commands/explain.js +84 -36
- package/dist/cli/commands/handoff.js +13 -17
- package/dist/cli/commands/impact.js +14 -20
- package/dist/cli/commands/index.js +15 -9
- package/dist/cli/commands/init.js +56 -70
- package/dist/cli/commands/line-endings.js +15 -9
- package/dist/cli/commands/map.js +30 -42
- package/dist/cli/commands/next.js +300 -0
- package/dist/cli/commands/onboard.js +136 -0
- package/dist/cli/commands/run.js +47 -42
- package/dist/cli/commands/search.js +43 -69
- package/dist/cli/commands/status.js +9 -6
- package/dist/cli/commands/update.js +16 -10
- package/dist/cli/commands/upgrade.js +9 -6
- package/dist/cli/commands/verify/args.js +55 -249
- package/dist/cli/commands/verify.js +2 -1
- package/dist/cli/commands/version-sources.js +9 -6
- package/dist/cli/commands/version.js +9 -6
- package/dist/cli/commands/workspace.js +564 -0
- package/dist/cli/i18n/en.js +60 -1
- package/dist/cli/i18n/es.js +60 -1
- package/dist/cli/i18n/fr.js +60 -1
- package/dist/cli/i18n/hi.js +60 -1
- package/dist/cli/i18n/ko.js +60 -1
- package/dist/cli/i18n/zh.js +60 -1
- package/dist/cli/index.js +28 -25
- package/dist/cli/lib/agent-context.js +8 -9
- package/dist/cli/lib/command-registry.js +24 -0
- package/dist/cli/lib/dashboard-html/client-script.js +1 -1
- package/dist/cli/lib/local-index/database-path.js +5 -0
- package/dist/cli/lib/local-index/database-read.js +88 -0
- package/dist/cli/lib/local-index/effect-graph-read-model.js +112 -0
- package/dist/cli/lib/local-index/freshness.js +60 -0
- package/dist/cli/lib/local-index/index.js +12 -1866
- package/dist/cli/lib/local-index/path-surface-read-model.js +134 -0
- package/dist/cli/lib/local-index/populate.js +474 -0
- package/dist/cli/lib/local-index/schema.js +413 -0
- package/dist/cli/lib/local-index/search-read-model.js +533 -0
- package/dist/cli/lib/local-index/search-text.js +79 -0
- package/dist/cli/lib/option-parser.js +93 -0
- package/dist/cli/lib/repo-map.js +2 -2
- package/dist/cli/lib/run-plan.js +5 -22
- package/dist/core/change-verification.js +11 -5
- package/dist/core/command-effects.js +1 -3
- package/dist/core/command-intent-eligibility.js +14 -0
- package/dist/core/command-preconditions.js +8 -4
- package/dist/core/command-run-constraints.js +43 -0
- package/dist/core/public-json-contracts.js +57 -0
- package/dist/core/test-selection.js +8 -2
- package/dist/core/verification-plan.js +32 -4
- package/package.json +1 -1
- package/schemas/README.md +16 -0
- package/schemas/api-serve-response.schema.json +89 -0
- package/schemas/change-verification-report.schema.json +4 -1
- package/schemas/contract-lint-report.schema.json +1 -0
- package/schemas/evidence-report.schema.json +287 -0
- package/schemas/explain-report.schema.json +4 -0
- package/schemas/next-report.schema.json +121 -0
- package/schemas/onboard-commands-report.schema.json +100 -0
- package/schemas/workspace-command-catalog.schema.json +172 -0
- package/schemas/workspace-status.schema.json +141 -0
- package/schemas/workspace-verification-plan.schema.json +195 -0
- package/templates/default/i18n.toml +1 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -1
- package/templates/default/locales/en/.mustflow/skills/clarifying-question-gate/SKILL.md +183 -0
- package/templates/default/locales/en/.mustflow/skills/routes.toml +7 -1
- package/templates/default/locales/en/.mustflow/skills/structure-discovery-gate/SKILL.md +63 -20
- package/templates/default/manifest.toml +8 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { t } from './i18n.js';
|
|
2
|
+
function createSpecMap(specs) {
|
|
3
|
+
const map = new Map();
|
|
4
|
+
for (const spec of specs) {
|
|
5
|
+
map.set(spec.name, spec);
|
|
6
|
+
for (const alias of spec.aliases ?? []) {
|
|
7
|
+
map.set(alias, spec);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return map;
|
|
11
|
+
}
|
|
12
|
+
function splitOptionToken(token) {
|
|
13
|
+
const separatorIndex = token.indexOf('=');
|
|
14
|
+
if (separatorIndex === -1) {
|
|
15
|
+
return { name: token, inlineValue: null };
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
name: token.slice(0, separatorIndex),
|
|
19
|
+
inlineValue: token.slice(separatorIndex + 1),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function hasCliOptionToken(args, name, aliases = []) {
|
|
23
|
+
const tokens = new Set([name, ...aliases]);
|
|
24
|
+
return args.some((arg) => tokens.has(splitOptionToken(arg).name));
|
|
25
|
+
}
|
|
26
|
+
export function parseCliOptions(args, specs, config = {}) {
|
|
27
|
+
const specByToken = createSpecMap(specs);
|
|
28
|
+
const values = new Map();
|
|
29
|
+
const occurrences = [];
|
|
30
|
+
const positionals = [];
|
|
31
|
+
const allowPositionals = config.allowPositionals === true;
|
|
32
|
+
const allowUnknownOptions = config.allowUnknownOptions === true;
|
|
33
|
+
const allowEmptyStringValues = config.allowEmptyStringValues === true;
|
|
34
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
35
|
+
const arg = args[index];
|
|
36
|
+
if (!arg) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!arg.startsWith('-')) {
|
|
40
|
+
if (allowPositionals) {
|
|
41
|
+
positionals.push(arg);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
return { values, occurrences, positionals, error: { kind: 'unknown_option', option: arg } };
|
|
45
|
+
}
|
|
46
|
+
const { name, inlineValue } = splitOptionToken(arg);
|
|
47
|
+
const spec = specByToken.get(name);
|
|
48
|
+
if (!spec) {
|
|
49
|
+
if (allowUnknownOptions && allowPositionals) {
|
|
50
|
+
positionals.push(arg);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
return { values, occurrences, positionals, error: { kind: 'unknown_option', option: arg } };
|
|
54
|
+
}
|
|
55
|
+
if (spec.kind === 'boolean') {
|
|
56
|
+
if (inlineValue !== null) {
|
|
57
|
+
return { values, occurrences, positionals, error: { kind: 'unknown_option', option: arg } };
|
|
58
|
+
}
|
|
59
|
+
values.set(spec.name, true);
|
|
60
|
+
occurrences.push({ name: spec.name, value: true, token: arg });
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (inlineValue !== null) {
|
|
64
|
+
if (inlineValue.length === 0 && !allowEmptyStringValues) {
|
|
65
|
+
return { values, occurrences, positionals, error: { kind: 'missing_value', option: name } };
|
|
66
|
+
}
|
|
67
|
+
values.set(spec.name, inlineValue);
|
|
68
|
+
occurrences.push({ name: spec.name, value: inlineValue, token: arg });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const nextArg = args[index + 1];
|
|
72
|
+
if (!nextArg || nextArg.startsWith('-')) {
|
|
73
|
+
return { values, occurrences, positionals, error: { kind: 'missing_value', option: name } };
|
|
74
|
+
}
|
|
75
|
+
values.set(spec.name, nextArg);
|
|
76
|
+
occurrences.push({ name: spec.name, value: nextArg, token: arg });
|
|
77
|
+
index += 1;
|
|
78
|
+
}
|
|
79
|
+
return { values, occurrences, positionals, error: null };
|
|
80
|
+
}
|
|
81
|
+
export function hasParsedCliOption(parsed, name) {
|
|
82
|
+
return parsed.values.get(name) === true;
|
|
83
|
+
}
|
|
84
|
+
export function getParsedCliStringOption(parsed, name) {
|
|
85
|
+
const value = parsed.values.get(name);
|
|
86
|
+
return typeof value === 'string' ? value : null;
|
|
87
|
+
}
|
|
88
|
+
export function formatCliOptionParseError(error, lang) {
|
|
89
|
+
if (error.kind === 'missing_value') {
|
|
90
|
+
return t(lang, 'cli.error.missingValue', { option: error.option });
|
|
91
|
+
}
|
|
92
|
+
return t(lang, 'cli.error.unknownOption', { option: error.option });
|
|
93
|
+
}
|
package/dist/cli/lib/repo-map.js
CHANGED
|
@@ -215,7 +215,7 @@ function readMustflowConfig(projectRoot) {
|
|
|
215
215
|
return {};
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
|
-
function getRepoMapConfig(projectRoot) {
|
|
218
|
+
export function getRepoMapConfig(projectRoot) {
|
|
219
219
|
const parsed = readMustflowConfig(projectRoot);
|
|
220
220
|
const configuredPriorityPaths = [...getStringArray(parsed.read_order), ...getStringArray(parsed.optional_read_order)];
|
|
221
221
|
const map = isRecord(parsed.map) ? parsed.map : {};
|
|
@@ -503,7 +503,7 @@ function collectNestedRepository(projectRoot, repositoryPath, anchorFiles) {
|
|
|
503
503
|
editingPolicies,
|
|
504
504
|
};
|
|
505
505
|
}
|
|
506
|
-
function discoverNestedRepositories(projectRoot, mapConfig, workspaceConfig) {
|
|
506
|
+
export function discoverNestedRepositories(projectRoot, mapConfig, workspaceConfig) {
|
|
507
507
|
if (!mapConfig.includeNested || !workspaceConfig.enabled || workspaceConfig.roots.length === 0) {
|
|
508
508
|
return [];
|
|
509
509
|
}
|
package/dist/cli/lib/run-plan.js
CHANGED
|
@@ -2,10 +2,11 @@ import path from 'node:path';
|
|
|
2
2
|
import { isMustflowBinName } from '../../core/command-classification.js';
|
|
3
3
|
import { resolveSafeProjectCwd } from '../../core/command-cwd.js';
|
|
4
4
|
import { resolveCommandEnv } from '../../core/command-env.js';
|
|
5
|
+
import { getCommandMaxOutputBytesLimitDetail, readEffectiveCommandCwd, readEffectiveCommandMaxOutputBytes, } from '../../core/command-run-constraints.js';
|
|
5
6
|
import { evaluateCommandIntentEligibility, } from '../../core/command-intent-eligibility.js';
|
|
6
7
|
import { inspectActiveRunLocks, } from '../../core/active-run-locks.js';
|
|
7
8
|
import { isRecord, readPositiveInteger, readString, readStringArray, } from '../../core/config-loading.js';
|
|
8
|
-
import { DEFAULT_COMMAND_MAX_OUTPUT_BYTES, COMMAND_OUTPUT_LIMIT_SCOPE,
|
|
9
|
+
import { DEFAULT_COMMAND_MAX_OUTPUT_BYTES, COMMAND_OUTPUT_LIMIT_SCOPE, } from '../../core/command-output-limits.js';
|
|
9
10
|
import { normalizeSuccessExitCodes } from '../../core/success-exit-codes.js';
|
|
10
11
|
import { normalizeSafeTestTargetPath, TEST_TARGET_PATH_ERROR } from '../../core/test-target-paths.js';
|
|
11
12
|
import { evaluateCommandPreconditions, } from '../../core/command-preconditions.js';
|
|
@@ -79,31 +80,13 @@ function getRunPlanMode(commandArgv, intent) {
|
|
|
79
80
|
}
|
|
80
81
|
return intent.mode === 'shell' ? 'shell' : null;
|
|
81
82
|
}
|
|
82
|
-
function readEffectiveMaxOutputBytes(contract, intent) {
|
|
83
|
-
return readPositiveInteger(intent, 'max_output_bytes') ??
|
|
84
|
-
readPositiveInteger(contract.defaults, 'max_output_bytes') ??
|
|
85
|
-
DEFAULT_COMMAND_MAX_OUTPUT_BYTES;
|
|
86
|
-
}
|
|
87
83
|
function readEffectiveKillAfterSeconds(contract, intent) {
|
|
88
84
|
return readPositiveInteger(intent, 'kill_after_seconds') ??
|
|
89
85
|
readPositiveInteger(contract.defaults, 'kill_after_seconds') ??
|
|
90
86
|
5;
|
|
91
87
|
}
|
|
92
|
-
function getMaxOutputBytesLimitDetail(contract, intent) {
|
|
93
|
-
const intentValue = readPositiveInteger(intent, 'max_output_bytes');
|
|
94
|
-
if (intentValue !== undefined) {
|
|
95
|
-
return intentValue > MAX_COMMAND_OUTPUT_BYTES ?
|
|
96
|
-
commandMaxOutputBytesLimitMessage('[commands.intents.<intent>].max_output_bytes') :
|
|
97
|
-
null;
|
|
98
|
-
}
|
|
99
|
-
const defaultValue = readPositiveInteger(contract.defaults, 'max_output_bytes');
|
|
100
|
-
if (defaultValue !== undefined && defaultValue > MAX_COMMAND_OUTPUT_BYTES) {
|
|
101
|
-
return commandMaxOutputBytesLimitMessage('[commands.defaults].max_output_bytes');
|
|
102
|
-
}
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
88
|
function readRunIntentMetadata(contract, intent) {
|
|
106
|
-
const configuredCwd =
|
|
89
|
+
const configuredCwd = readEffectiveCommandCwd(contract, intent);
|
|
107
90
|
const commandArgv = readStringArray(intent, 'argv');
|
|
108
91
|
const shellCommand = intent.mode === 'shell' ? readString(intent, 'cmd') : undefined;
|
|
109
92
|
const env = resolveCommandEnv(contract, intent);
|
|
@@ -115,7 +98,7 @@ function readRunIntentMetadata(contract, intent) {
|
|
|
115
98
|
configuredCwd,
|
|
116
99
|
timeoutSeconds: readPositiveInteger(intent, 'timeout_seconds') ?? null,
|
|
117
100
|
killAfterSeconds: readEffectiveKillAfterSeconds(contract, intent),
|
|
118
|
-
maxOutputBytes:
|
|
101
|
+
maxOutputBytes: readEffectiveCommandMaxOutputBytes(contract, intent, DEFAULT_COMMAND_MAX_OUTPUT_BYTES),
|
|
119
102
|
successExitCodes: getSuccessExitCodes(intent),
|
|
120
103
|
commandArgv,
|
|
121
104
|
shellCommand,
|
|
@@ -186,7 +169,7 @@ export function createRunPlan(projectRoot, contract, intentName, options = {}) {
|
|
|
186
169
|
return createBlockedRunPlan(contract, intentName, rawIntent, eligibility, eligibility.code, eligibility.detail, preconditions);
|
|
187
170
|
}
|
|
188
171
|
const metadata = readRunIntentMetadata(contract, rawIntent);
|
|
189
|
-
const maxOutputBytesLimitDetail =
|
|
172
|
+
const maxOutputBytesLimitDetail = getCommandMaxOutputBytesLimitDetail(contract, rawIntent);
|
|
190
173
|
if (maxOutputBytesLimitDetail) {
|
|
191
174
|
return createBlockedRunPlan(contract, intentName, rawIntent, eligibility, 'max_output_bytes_exceeds_limit', maxOutputBytesLimitDetail, preconditions);
|
|
192
175
|
}
|
|
@@ -120,7 +120,7 @@ function escalationIntentsForCandidate(commandContract, candidate) {
|
|
|
120
120
|
...readIntentRelationList(commandContract, candidate.intent, 'escalate_to'),
|
|
121
121
|
]);
|
|
122
122
|
}
|
|
123
|
-
function expandCandidatesWithDeclaredFallbacks(commandContract, candidates) {
|
|
123
|
+
function expandCandidatesWithDeclaredFallbacks(commandContract, projectRoot, candidates) {
|
|
124
124
|
const hasRunnableCandidate = candidates.some((candidate) => candidate.status === 'runnable' && candidate.intent.length > 0);
|
|
125
125
|
if (hasRunnableCandidate) {
|
|
126
126
|
return candidates;
|
|
@@ -133,7 +133,10 @@ function expandCandidatesWithDeclaredFallbacks(commandContract, candidates) {
|
|
|
133
133
|
return [
|
|
134
134
|
...candidates,
|
|
135
135
|
...fallbackIntents.map((intent) => {
|
|
136
|
-
const fallback = classifyVerificationCandidate(intent, commandContract.intents[intent]
|
|
136
|
+
const fallback = classifyVerificationCandidate(intent, commandContract.intents[intent], {
|
|
137
|
+
commandContract,
|
|
138
|
+
projectRoot,
|
|
139
|
+
});
|
|
137
140
|
return {
|
|
138
141
|
...fallback,
|
|
139
142
|
detail: fallback.status === 'runnable' ? 'Declared fallback for unavailable verification intent.' : fallback.detail,
|
|
@@ -153,7 +156,7 @@ function requirementNeedsDeclaredEscalation(requirement) {
|
|
|
153
156
|
].join('\n').toLowerCase();
|
|
154
157
|
return DECLARED_ESCALATION_SIGNALS.some((signal) => signalText.includes(signal.toLowerCase()));
|
|
155
158
|
}
|
|
156
|
-
function expandCandidatesWithDeclaredEscalations(commandContract, requirement, candidates) {
|
|
159
|
+
function expandCandidatesWithDeclaredEscalations(commandContract, projectRoot, requirement, candidates) {
|
|
157
160
|
if (!requirementNeedsDeclaredEscalation(requirement)) {
|
|
158
161
|
return candidates;
|
|
159
162
|
}
|
|
@@ -165,7 +168,10 @@ function expandCandidatesWithDeclaredEscalations(commandContract, requirement, c
|
|
|
165
168
|
return [
|
|
166
169
|
...candidates,
|
|
167
170
|
...escalationIntents.map((intent) => {
|
|
168
|
-
const escalation = classifyVerificationCandidate(intent, commandContract.intents[intent]
|
|
171
|
+
const escalation = classifyVerificationCandidate(intent, commandContract.intents[intent], {
|
|
172
|
+
commandContract,
|
|
173
|
+
projectRoot,
|
|
174
|
+
});
|
|
169
175
|
return {
|
|
170
176
|
...escalation,
|
|
171
177
|
detail: escalation.status === 'runnable'
|
|
@@ -295,7 +301,7 @@ export function createChangeVerificationReport(classificationReport, commandCont
|
|
|
295
301
|
const requirements = classificationReport.summary.validationReasons.map((reason) => createVerificationRequirement(classificationReport, reason));
|
|
296
302
|
const plans = requirements.map((requirement) => ({
|
|
297
303
|
requirement,
|
|
298
|
-
candidates: expandCandidatesWithDeclaredEscalations(commandContract, requirement, expandCandidatesWithDeclaredFallbacks(commandContract, createVerificationPlan(commandContract, requirement.reason).candidates)),
|
|
304
|
+
candidates: expandCandidatesWithDeclaredEscalations(commandContract, projectRoot, requirement, expandCandidatesWithDeclaredFallbacks(commandContract, projectRoot, createVerificationPlan(commandContract, requirement.reason, projectRoot).candidates)),
|
|
299
305
|
}));
|
|
300
306
|
const plansWithProjectTestSelection = plans.map((plan) => {
|
|
301
307
|
const additionalCandidates = testSelectionPlan.candidates
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { isRecord, readString, readStringArray, } from './config-loading.js';
|
|
3
3
|
import { resolveSafeProjectCwd } from './command-cwd.js';
|
|
4
|
+
import { readEffectiveCommandCwd } from './command-run-constraints.js';
|
|
4
5
|
export const COMMAND_EFFECT_MODES = new Set(['read', 'write', 'append', 'replace', 'delete_recreate']);
|
|
5
6
|
export const COMMAND_EFFECT_TYPES = new Set(['read', 'write']);
|
|
6
7
|
export const COMMAND_EFFECT_CONCURRENCY = new Set(['shared', 'exclusive']);
|
|
@@ -13,9 +14,6 @@ function normalizeRelativePath(rawPath) {
|
|
|
13
14
|
function pathLockKey(relativePath) {
|
|
14
15
|
return `path:${normalizeRelativePath(relativePath)}`;
|
|
15
16
|
}
|
|
16
|
-
function readEffectiveCommandCwd(commandContract, intent) {
|
|
17
|
-
return readString(intent, 'cwd') ?? readString(commandContract.defaults, 'default_cwd') ?? '.';
|
|
18
|
-
}
|
|
19
17
|
function validateEffectPath(projectRoot, commandContract, intent, rawPath) {
|
|
20
18
|
const cwd = resolveSafeProjectCwd(projectRoot, readEffectiveCommandCwd(commandContract, intent));
|
|
21
19
|
const resolved = path.resolve(cwd, rawPath);
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { isRecord, readString } from './config-loading.js';
|
|
2
2
|
import { commandIntentBlockedCommandPattern, commandIntentHasCommandSource, commandIntentNameIsSafe, } from './command-contract-rules.js';
|
|
3
|
+
export const COMMAND_INTENT_INELIGIBILITY_CODES = [
|
|
4
|
+
'intent_not_table',
|
|
5
|
+
'status_not_configured',
|
|
6
|
+
'lifecycle_not_oneshot',
|
|
7
|
+
'run_policy_not_agent_allowed',
|
|
8
|
+
'stdin_not_closed',
|
|
9
|
+
'agent_shell_requires_allow',
|
|
10
|
+
'missing_timeout',
|
|
11
|
+
'missing_command_source',
|
|
12
|
+
'unsafe_intent_name',
|
|
13
|
+
'blocked_shell_background_pattern',
|
|
14
|
+
'blocked_long_running_command_pattern',
|
|
15
|
+
];
|
|
16
|
+
export const commandIntentIneligibilityCodeContractComplete = true;
|
|
3
17
|
export function evaluateCommandIntentEligibility(intentName, rawIntent) {
|
|
4
18
|
if (!commandIntentNameIsSafe(intentName)) {
|
|
5
19
|
return {
|
|
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isRecord, readString, readStringArray, } from './config-loading.js';
|
|
4
4
|
import { evaluateCommandIntentEligibility } from './command-intent-eligibility.js';
|
|
5
|
+
import { getCommandRunStaticBlocker } from './command-run-constraints.js';
|
|
5
6
|
export const COMMAND_PRECONDITION_KINDS = new Set(['path_exists', 'artifact_freshness']);
|
|
6
7
|
const IGNORED_WALK_DIRECTORIES = new Set([
|
|
7
8
|
'.git',
|
|
@@ -47,20 +48,23 @@ function readPreconditionDeclarations(intent) {
|
|
|
47
48
|
satisfyIntent: readString(precondition, 'satisfy_intent') ?? null,
|
|
48
49
|
}));
|
|
49
50
|
}
|
|
50
|
-
function createSatisfyIntentSummary(contract, intentName) {
|
|
51
|
+
function createSatisfyIntentSummary(projectRoot, contract, intentName) {
|
|
51
52
|
if (!intentName) {
|
|
52
53
|
return null;
|
|
53
54
|
}
|
|
54
55
|
const rawIntent = contract.intents[intentName];
|
|
55
56
|
const eligibility = evaluateCommandIntentEligibility(intentName, rawIntent);
|
|
57
|
+
const runBlocker = eligibility.ok && isRecord(rawIntent)
|
|
58
|
+
? getCommandRunStaticBlocker(projectRoot, contract, rawIntent)
|
|
59
|
+
: null;
|
|
56
60
|
return {
|
|
57
61
|
intent: intentName,
|
|
58
62
|
declared: isRecord(rawIntent),
|
|
59
|
-
runnable: eligibility.ok,
|
|
63
|
+
runnable: eligibility.ok && !runBlocker,
|
|
60
64
|
status: isRecord(rawIntent) ? readString(rawIntent, 'status') ?? null : null,
|
|
61
65
|
lifecycle: isRecord(rawIntent) ? readString(rawIntent, 'lifecycle') ?? null : null,
|
|
62
66
|
runPolicy: isRecord(rawIntent) ? readString(rawIntent, 'run_policy') ?? null : null,
|
|
63
|
-
detail: eligibility.detail,
|
|
67
|
+
detail: eligibility.detail ?? runBlocker?.detail ?? null,
|
|
64
68
|
};
|
|
65
69
|
}
|
|
66
70
|
function escapeRegExp(value) {
|
|
@@ -258,7 +262,7 @@ export function evaluateCommandPreconditions(projectRoot, contract, intentName)
|
|
|
258
262
|
}
|
|
259
263
|
const context = { projectFiles: null };
|
|
260
264
|
return readPreconditionDeclarations(intent).map((declaration) => {
|
|
261
|
-
const satisfyIntent = createSatisfyIntentSummary(contract, declaration.satisfyIntent);
|
|
265
|
+
const satisfyIntent = createSatisfyIntentSummary(projectRoot, contract, declaration.satisfyIntent);
|
|
262
266
|
if (declaration.kind === 'path_exists') {
|
|
263
267
|
return evaluatePathExists(projectRoot, declaration, satisfyIntent);
|
|
264
268
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { resolveSafeProjectCwd } from './command-cwd.js';
|
|
2
|
+
import { MAX_COMMAND_OUTPUT_BYTES, commandMaxOutputBytesLimitMessage, } from './command-output-limits.js';
|
|
3
|
+
import { readPositiveInteger, readString, } from './config-loading.js';
|
|
4
|
+
export function readEffectiveCommandCwd(contract, intent) {
|
|
5
|
+
return readString(intent, 'cwd') ?? readString(contract.defaults, 'default_cwd') ?? '.';
|
|
6
|
+
}
|
|
7
|
+
export function readEffectiveCommandMaxOutputBytes(contract, intent, fallback) {
|
|
8
|
+
return readPositiveInteger(intent, 'max_output_bytes') ??
|
|
9
|
+
readPositiveInteger(contract.defaults, 'max_output_bytes') ??
|
|
10
|
+
fallback;
|
|
11
|
+
}
|
|
12
|
+
export function getCommandMaxOutputBytesLimitDetail(contract, intent) {
|
|
13
|
+
const intentValue = readPositiveInteger(intent, 'max_output_bytes');
|
|
14
|
+
if (intentValue !== undefined) {
|
|
15
|
+
return intentValue > MAX_COMMAND_OUTPUT_BYTES ?
|
|
16
|
+
commandMaxOutputBytesLimitMessage('[commands.intents.<intent>].max_output_bytes') :
|
|
17
|
+
null;
|
|
18
|
+
}
|
|
19
|
+
const defaultValue = readPositiveInteger(contract.defaults, 'max_output_bytes');
|
|
20
|
+
if (defaultValue !== undefined && defaultValue > MAX_COMMAND_OUTPUT_BYTES) {
|
|
21
|
+
return commandMaxOutputBytesLimitMessage('[commands.defaults].max_output_bytes');
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
export function getCommandRunStaticBlocker(projectRoot, contract, intent) {
|
|
26
|
+
const maxOutputBytesLimitDetail = getCommandMaxOutputBytesLimitDetail(contract, intent);
|
|
27
|
+
if (maxOutputBytesLimitDetail) {
|
|
28
|
+
return {
|
|
29
|
+
reason: 'max_output_bytes_exceeds_limit',
|
|
30
|
+
detail: maxOutputBytesLimitDetail,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
resolveSafeProjectCwd(projectRoot, readEffectiveCommandCwd(contract, intent));
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return {
|
|
38
|
+
reason: 'cwd_outside_project',
|
|
39
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
@@ -79,6 +79,13 @@ const PUBLIC_JSON_SCHEMA_CONTRACTS = [
|
|
|
79
79
|
documented: true,
|
|
80
80
|
installedCommand: ['mf', 'api', 'locks', '--json'],
|
|
81
81
|
},
|
|
82
|
+
{
|
|
83
|
+
id: 'api-serve-response',
|
|
84
|
+
schemaFile: 'api-serve-response.schema.json',
|
|
85
|
+
producer: 'mf api serve --stdio',
|
|
86
|
+
packaged: true,
|
|
87
|
+
documented: true,
|
|
88
|
+
},
|
|
82
89
|
{
|
|
83
90
|
id: 'run-receipt',
|
|
84
91
|
schemaFile: 'run-receipt.schema.json',
|
|
@@ -109,6 +116,56 @@ const PUBLIC_JSON_SCHEMA_CONTRACTS = [
|
|
|
109
116
|
documented: true,
|
|
110
117
|
installedCommand: ['mf', 'contract-lint', '--json'],
|
|
111
118
|
},
|
|
119
|
+
{
|
|
120
|
+
id: 'onboard-commands-report',
|
|
121
|
+
schemaFile: 'onboard-commands-report.schema.json',
|
|
122
|
+
producer: 'mf onboard commands --json',
|
|
123
|
+
packaged: true,
|
|
124
|
+
documented: true,
|
|
125
|
+
installedCommand: ['mf', 'onboard', 'commands', '--json'],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 'next-report',
|
|
129
|
+
schemaFile: 'next-report.schema.json',
|
|
130
|
+
producer: 'mf next --json',
|
|
131
|
+
packaged: true,
|
|
132
|
+
documented: true,
|
|
133
|
+
installedCommand: ['mf', 'next', '--json'],
|
|
134
|
+
expectedExitCodes: [0, 1],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'evidence-report',
|
|
138
|
+
schemaFile: 'evidence-report.schema.json',
|
|
139
|
+
producer: 'mf evidence --changed --json',
|
|
140
|
+
packaged: true,
|
|
141
|
+
documented: true,
|
|
142
|
+
installedCommand: ['mf', 'evidence', '--changed', '--json'],
|
|
143
|
+
expectedExitCodes: [0, 1],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'workspace-status',
|
|
147
|
+
schemaFile: 'workspace-status.schema.json',
|
|
148
|
+
producer: 'mf workspace status --json',
|
|
149
|
+
packaged: true,
|
|
150
|
+
documented: true,
|
|
151
|
+
installedCommand: ['mf', 'workspace', 'status', '--json'],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: 'workspace-command-catalog',
|
|
155
|
+
schemaFile: 'workspace-command-catalog.schema.json',
|
|
156
|
+
producer: 'mf workspace command-catalog --json',
|
|
157
|
+
packaged: true,
|
|
158
|
+
documented: true,
|
|
159
|
+
installedCommand: ['mf', 'workspace', 'command-catalog', '--json'],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: 'workspace-verification-plan',
|
|
163
|
+
schemaFile: 'workspace-verification-plan.schema.json',
|
|
164
|
+
producer: 'mf workspace verify --changed --plan-only --json',
|
|
165
|
+
packaged: true,
|
|
166
|
+
documented: true,
|
|
167
|
+
installedCommand: ['mf', 'workspace', 'verify', '--changed', '--plan-only', '--json'],
|
|
168
|
+
},
|
|
112
169
|
{
|
|
113
170
|
id: 'dashboard-export',
|
|
114
171
|
schemaFile: 'dashboard-export.schema.json',
|
|
@@ -190,7 +190,10 @@ export function createProjectTestSelectionPlan(projectRoot, classificationReport
|
|
|
190
190
|
fallbackIntent: rule.fallbackIntent,
|
|
191
191
|
testTargets: rule.testTargets,
|
|
192
192
|
});
|
|
193
|
-
const primary = withDetail(classifyVerificationCandidate(rule.intent, commandContract.intents[rule.intent]
|
|
193
|
+
const primary = withDetail(classifyVerificationCandidate(rule.intent, commandContract.intents[rule.intent], {
|
|
194
|
+
commandContract,
|
|
195
|
+
projectRoot,
|
|
196
|
+
}), `Project-declared test selection rule "${rule.id}".`);
|
|
194
197
|
const primaryTestTargets = appliedTestTargetsForCandidate(rule, commandContract, primary);
|
|
195
198
|
const primaryCandidate = { reason, candidate: primary, testTargets: primaryTestTargets };
|
|
196
199
|
candidates.push(primaryCandidate);
|
|
@@ -200,7 +203,10 @@ export function createProjectTestSelectionPlan(projectRoot, classificationReport
|
|
|
200
203
|
continue;
|
|
201
204
|
}
|
|
202
205
|
selectedReportCandidates.push(toReportCandidate(rule, reason, 'primary', primary, primaryTestTargets));
|
|
203
|
-
const fallback = withDetail(classifyVerificationCandidate(rule.fallbackIntent, commandContract.intents[rule.fallbackIntent]
|
|
206
|
+
const fallback = withDetail(classifyVerificationCandidate(rule.fallbackIntent, commandContract.intents[rule.fallbackIntent], {
|
|
207
|
+
commandContract,
|
|
208
|
+
projectRoot,
|
|
209
|
+
}), `Fallback for project-declared test selection rule "${rule.id}".`);
|
|
204
210
|
const fallbackTestTargets = appliedTestTargetsForCandidate(rule, commandContract, fallback);
|
|
205
211
|
const fallbackCandidate = { reason, candidate: fallback, testTargets: fallbackTestTargets };
|
|
206
212
|
candidates.push(fallbackCandidate);
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import { isRecord, readStringArray, } from './config-loading.js';
|
|
2
|
-
import {
|
|
2
|
+
import { getCommandRunStaticBlocker, } from './command-run-constraints.js';
|
|
3
|
+
import { COMMAND_INTENT_INELIGIBILITY_CODES, evaluateCommandIntentEligibility, } from './command-intent-eligibility.js';
|
|
4
|
+
export const VERIFICATION_SKIP_REASONS = [
|
|
5
|
+
'no_matching_intents',
|
|
6
|
+
...COMMAND_INTENT_INELIGIBILITY_CODES,
|
|
7
|
+
'cwd_outside_project',
|
|
8
|
+
'max_output_bytes_exceeds_limit',
|
|
9
|
+
];
|
|
10
|
+
export const verificationSkipReasonContractComplete = true;
|
|
3
11
|
function hasRequiredAfter(intent, reason) {
|
|
4
12
|
return (readStringArray(intent, 'required_after') ?? []).includes(reason);
|
|
5
13
|
}
|
|
6
|
-
|
|
14
|
+
function getRunPlanBlocker(rawIntent, context) {
|
|
15
|
+
if (!context.projectRoot || !context.commandContract) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return getCommandRunStaticBlocker(context.projectRoot, context.commandContract, rawIntent);
|
|
19
|
+
}
|
|
20
|
+
export function classifyVerificationCandidate(intentName, rawIntent, context = {}) {
|
|
7
21
|
const eligibility = evaluateCommandIntentEligibility(intentName, rawIntent);
|
|
8
22
|
if (!eligibility.ok) {
|
|
9
23
|
return {
|
|
@@ -13,6 +27,17 @@ export function classifyVerificationCandidate(intentName, rawIntent) {
|
|
|
13
27
|
detail: eligibility.detail,
|
|
14
28
|
};
|
|
15
29
|
}
|
|
30
|
+
if (isRecord(rawIntent)) {
|
|
31
|
+
const blocker = getRunPlanBlocker(rawIntent, context);
|
|
32
|
+
if (blocker) {
|
|
33
|
+
return {
|
|
34
|
+
intent: intentName,
|
|
35
|
+
status: 'skipped',
|
|
36
|
+
reason: blocker.reason,
|
|
37
|
+
detail: blocker.detail,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
16
41
|
return {
|
|
17
42
|
intent: intentName,
|
|
18
43
|
status: 'runnable',
|
|
@@ -20,10 +45,13 @@ export function classifyVerificationCandidate(intentName, rawIntent) {
|
|
|
20
45
|
detail: null,
|
|
21
46
|
};
|
|
22
47
|
}
|
|
23
|
-
export function createVerificationPlan(contract, reason) {
|
|
48
|
+
export function createVerificationPlan(contract, reason, projectRoot) {
|
|
24
49
|
const candidates = Object.entries(contract.intents)
|
|
25
50
|
.filter(([, intent]) => isRecord(intent) && hasRequiredAfter(intent, reason))
|
|
26
|
-
.map(([intentName, intent]) => classifyVerificationCandidate(intentName, intent
|
|
51
|
+
.map(([intentName, intent]) => classifyVerificationCandidate(intentName, intent, {
|
|
52
|
+
commandContract: contract,
|
|
53
|
+
projectRoot,
|
|
54
|
+
}))
|
|
27
55
|
.sort((left, right) => left.intent.localeCompare(right.intent));
|
|
28
56
|
return {
|
|
29
57
|
reason,
|
package/package.json
CHANGED
package/schemas/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Current schemas:
|
|
|
15
15
|
- `diff-risk.schema.json`: output of `mf api diff-risk --changed --json`
|
|
16
16
|
- `health.schema.json`: output of `mf api health --json`
|
|
17
17
|
- `locks.schema.json`: output of `mf api locks --json`
|
|
18
|
+
- `api-serve-response.schema.json`: each newline-delimited response from `mf api serve --stdio`
|
|
18
19
|
- `run-receipt.schema.json`: output of `mf run <intent> --json` and `.mustflow/state/runs/latest.json`,
|
|
19
20
|
including bounded declared-write drift metadata, a safe latest-run performance summary, and optional
|
|
20
21
|
structured phase timings and selection summaries
|
|
@@ -23,6 +24,21 @@ Current schemas:
|
|
|
23
24
|
command execution or automatic dependency execution
|
|
24
25
|
- `test-selection.schema.json`: parsed optional `.mustflow/config/test-selection.toml`
|
|
25
26
|
- `contract-lint-report.schema.json`: output of `mf contract-lint --json`
|
|
27
|
+
- `onboard-commands-report.schema.json`: output of `mf onboard commands --json`, containing
|
|
28
|
+
review-only command-intent suggestions that do not grant command authority or write files
|
|
29
|
+
- `next-report.schema.json`: output of `mf next --json`, containing the next safe action,
|
|
30
|
+
changed-file verification gaps, and read-only command recommendations
|
|
31
|
+
- `evidence-report.schema.json`: output of `mf evidence --changed --json`, containing verification
|
|
32
|
+
requirements, latest bounded evidence, receipts, remaining risks, and gaps without running commands
|
|
33
|
+
- `workspace-status.schema.json`: output of `mf workspace status --json`, containing configured
|
|
34
|
+
workspace roots, discovered nested repositories, and per-root command-contract readiness without
|
|
35
|
+
granting command authority
|
|
36
|
+
- `workspace-command-catalog.schema.json`: output of `mf workspace command-catalog --json`,
|
|
37
|
+
containing per-root command intent availability, safe `mf run` entrypoints, and no raw command
|
|
38
|
+
strings
|
|
39
|
+
- `workspace-verification-plan.schema.json`: output of
|
|
40
|
+
`mf workspace verify --changed --plan-only --json`, containing per-root changed-file
|
|
41
|
+
verification plans without running commands or granting parent-to-child command authority
|
|
26
42
|
- `dashboard-export.schema.json`: bounded static export written by `mf dashboard --export-json <path>`,
|
|
27
43
|
including output policy, redaction and truncation metadata, the dashboard harness report, and
|
|
28
44
|
the evidence-based completion verdict, evidence model, and conservative coverage matrix for the
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://mustflow.github.io/schemas/api-serve-response.schema.json",
|
|
4
|
+
"title": "mustflow api serve response",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"command",
|
|
10
|
+
"transport",
|
|
11
|
+
"id",
|
|
12
|
+
"ok",
|
|
13
|
+
"policy"
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"schema_version": { "const": "1" },
|
|
17
|
+
"command": { "const": "api serve" },
|
|
18
|
+
"transport": { "const": "stdio" },
|
|
19
|
+
"id": { "type": ["string", "number", "null"] },
|
|
20
|
+
"ok": { "type": "boolean" },
|
|
21
|
+
"policy": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"additionalProperties": false,
|
|
24
|
+
"required": [
|
|
25
|
+
"mode",
|
|
26
|
+
"executes_commands",
|
|
27
|
+
"direct_commands_allowed",
|
|
28
|
+
"raw_output_included",
|
|
29
|
+
"hidden_reasoning_included"
|
|
30
|
+
],
|
|
31
|
+
"properties": {
|
|
32
|
+
"mode": { "const": "read_only" },
|
|
33
|
+
"executes_commands": { "const": false },
|
|
34
|
+
"direct_commands_allowed": { "const": false },
|
|
35
|
+
"raw_output_included": { "const": false },
|
|
36
|
+
"hidden_reasoning_included": { "const": false }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"result": {
|
|
40
|
+
"anyOf": [
|
|
41
|
+
{ "$ref": "workspace-summary.schema.json" },
|
|
42
|
+
{ "$ref": "command-catalog.schema.json" },
|
|
43
|
+
{ "$ref": "verification-plan.schema.json" },
|
|
44
|
+
{ "$ref": "latest-evidence.schema.json" },
|
|
45
|
+
{ "$ref": "diff-risk.schema.json" },
|
|
46
|
+
{ "$ref": "health.schema.json" },
|
|
47
|
+
{ "$ref": "locks.schema.json" }
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"error": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"additionalProperties": false,
|
|
53
|
+
"required": ["code", "message"],
|
|
54
|
+
"properties": {
|
|
55
|
+
"code": {
|
|
56
|
+
"enum": [
|
|
57
|
+
"invalid_json",
|
|
58
|
+
"invalid_request",
|
|
59
|
+
"unknown_action",
|
|
60
|
+
"action_requires_changed",
|
|
61
|
+
"action_does_not_accept_changed",
|
|
62
|
+
"report_unavailable"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"message": { "type": "string" }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"oneOf": [
|
|
70
|
+
{
|
|
71
|
+
"properties": {
|
|
72
|
+
"ok": { "const": true }
|
|
73
|
+
},
|
|
74
|
+
"required": ["result"],
|
|
75
|
+
"not": {
|
|
76
|
+
"required": ["error"]
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"properties": {
|
|
81
|
+
"ok": { "const": false }
|
|
82
|
+
},
|
|
83
|
+
"required": ["error"],
|
|
84
|
+
"not": {
|
|
85
|
+
"required": ["result"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
@@ -255,8 +255,11 @@
|
|
|
255
255
|
"missing_timeout",
|
|
256
256
|
"missing_command_source",
|
|
257
257
|
"unsafe_intent_name",
|
|
258
|
+
"agent_shell_requires_allow",
|
|
258
259
|
"blocked_shell_background_pattern",
|
|
259
|
-
"blocked_long_running_command_pattern"
|
|
260
|
+
"blocked_long_running_command_pattern",
|
|
261
|
+
"cwd_outside_project",
|
|
262
|
+
"max_output_bytes_exceeds_limit"
|
|
260
263
|
]
|
|
261
264
|
},
|
|
262
265
|
"verificationCandidate": {
|