mustflow 2.17.0 → 2.18.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 +1 -1
- package/dist/cli/commands/classify.js +13 -3
- package/dist/cli/commands/dashboard.js +2 -1
- package/dist/cli/commands/impact.js +13 -3
- package/dist/cli/commands/run.js +86 -11
- package/dist/cli/commands/verify.js +9 -1
- package/dist/cli/i18n/en.js +7 -0
- package/dist/cli/i18n/es.js +7 -0
- package/dist/cli/i18n/fr.js +7 -0
- package/dist/cli/i18n/hi.js +7 -0
- package/dist/cli/i18n/ko.js +7 -0
- package/dist/cli/i18n/zh.js +7 -0
- package/dist/cli/lib/git-changes.js +25 -2
- package/dist/cli/lib/local-index/constants.js +4 -1
- package/dist/cli/lib/local-index/index.js +22 -5
- package/dist/cli/lib/repo-map.js +81 -28
- package/dist/cli/lib/run-plan.js +25 -2
- package/dist/cli/lib/validation/index.js +2 -1
- package/dist/core/check-issues.js +2 -0
- package/dist/core/command-contract-rules.js +104 -2
- package/dist/core/command-contract-validation.js +14 -2
- package/dist/core/command-intent-eligibility.js +9 -1
- package/dist/core/command-output-limits.js +5 -0
- package/dist/core/contract-lint.js +10 -1
- package/package.json +1 -1
- package/schemas/README.md +3 -3
- package/schemas/change-verification-report.schema.json +2 -1
- package/schemas/contract-lint-report.schema.json +2 -1
- package/schemas/explain-report.schema.json +1 -0
- package/schemas/latest-run-pointer.schema.json +1 -0
- package/schemas/verify-report.schema.json +1 -0
- package/schemas/verify-run-manifest.schema.json +1 -0
- package/templates/default/manifest.toml +1 -1
|
@@ -7,7 +7,7 @@ import { readTomlFile } from '../toml.js';
|
|
|
7
7
|
import { collectSourceAnchorIndexRecords, hasHighRiskSourceAnchorRiskTags, } from '../../../core/source-anchor-status.js';
|
|
8
8
|
import { normalizeCommandEffects } from '../../../core/command-effects.js';
|
|
9
9
|
import { listChangeClassificationRuleDescriptors } from '../../../core/change-classification.js';
|
|
10
|
-
import { DEFAULT_DATABASE_RELATIVE_PATH, DEFAULT_PROMPT_CACHE_STABLE_READ, DEFAULT_PROMPT_CACHE_TASK_SOURCES, DEFAULT_PROMPT_CACHE_VOLATILE_SOURCES, INDEX_CONFIG_RELATIVE_PATH, LOCAL_INDEX_CONTENT_MODE, LOCAL_INDEX_EXCLUDED_RAW_DATA_KINDS, LOCAL_INDEX_PARSER_VERSION, LOCAL_INDEX_SCHEMA_VERSION, LOCAL_INDEX_STORE_FULL_CONTENT, LATEST_RUN_STATE_RELATIVE_PATH, MAX_SEARCH_MATCH_SNIPPET_CHARS, MAX_SNIPPET_BYTES_PER_DOCUMENT, MUSTFLOW_RELATIVE_PATH, SEARCH_BACKEND_FTS5, SEARCH_BACKEND_TABLE_SCAN, SEARCH_MATCH_CONTEXT_AFTER_CHARS, SEARCH_MATCH_CONTEXT_BEFORE_CHARS, SEARCH_MATCH_TRUNCATION_MARKER, SEARCH_NGRAM_MAX_LENGTH, SEARCH_NGRAM_MIN_LENGTH, TEST_DISABLE_FTS5_ENV, } from './constants.js';
|
|
10
|
+
import { DEFAULT_DATABASE_RELATIVE_PATH, DEFAULT_PROMPT_CACHE_STABLE_READ, DEFAULT_PROMPT_CACHE_TASK_SOURCES, DEFAULT_PROMPT_CACHE_VOLATILE_SOURCES, INDEX_CONFIG_RELATIVE_PATH, LOCAL_INDEX_CONTENT_MODE, LOCAL_INDEX_EXCLUDED_RAW_DATA_KINDS, LOCAL_INDEX_PARSER_VERSION, LOCAL_INDEX_SCHEMA_VERSION, LOCAL_INDEX_STORE_FULL_CONTENT, LATEST_RUN_STATE_RELATIVE_PATH, MAX_SEARCH_MATCH_SNIPPET_CHARS, MAX_SNIPPET_BYTES_PER_DOCUMENT, MUSTFLOW_RELATIVE_PATH, SEARCH_BACKEND_FTS5, SEARCH_BACKEND_TABLE_SCAN, SEARCH_MATCH_CONTEXT_AFTER_CHARS, SEARCH_MATCH_CONTEXT_BEFORE_CHARS, SEARCH_MATCH_TRUNCATION_MARKER, SEARCH_NGRAM_MAX_GRAMS_PER_TARGET, SEARCH_NGRAM_MAX_LENGTH, SEARCH_NGRAM_MAX_TOKEN_CHARS, SEARCH_NGRAM_MIN_LENGTH, SOURCE_INDEX_MAX_FILE_BYTES, TEST_DISABLE_FTS5_ENV, } from './constants.js';
|
|
11
11
|
import { loadSqlJs } from './sql.js';
|
|
12
12
|
export function getLocalIndexDatabasePath(projectRoot) {
|
|
13
13
|
return path.join(projectRoot, ...DEFAULT_DATABASE_RELATIVE_PATH.split('/'));
|
|
@@ -83,11 +83,12 @@ function readPositiveInteger(table, key) {
|
|
|
83
83
|
}
|
|
84
84
|
function readLocalIndexSourceConfig(projectRoot) {
|
|
85
85
|
const sourceIndexTable = readNestedTable(readIndexToml(projectRoot), 'source_index');
|
|
86
|
+
const configuredMaxFileBytes = readPositiveInteger(sourceIndexTable, 'max_file_bytes');
|
|
86
87
|
return {
|
|
87
88
|
enabledByDefault: readBoolean(sourceIndexTable, 'enabled_by_default') === true,
|
|
88
89
|
include: readOptionalStringArray(sourceIndexTable, 'include') ?? [],
|
|
89
90
|
exclude: readOptionalStringArray(sourceIndexTable, 'exclude') ?? [],
|
|
90
|
-
maxFileBytes:
|
|
91
|
+
maxFileBytes: Math.min(configuredMaxFileBytes ?? SOURCE_INDEX_MAX_FILE_BYTES, SOURCE_INDEX_MAX_FILE_BYTES),
|
|
91
92
|
allowedExtensions: readOptionalStringArray(sourceIndexTable, 'allowed_extensions') ?? [],
|
|
92
93
|
};
|
|
93
94
|
}
|
|
@@ -321,10 +322,14 @@ function buildSearchNgrams(values) {
|
|
|
321
322
|
const grams = new Set();
|
|
322
323
|
for (const value of values) {
|
|
323
324
|
for (const token of extractSearchTokens(value)) {
|
|
324
|
-
const
|
|
325
|
+
const boundedToken = token.slice(0, SEARCH_NGRAM_MAX_TOKEN_CHARS);
|
|
326
|
+
const maxLength = Math.min(SEARCH_NGRAM_MAX_LENGTH, boundedToken.length);
|
|
325
327
|
for (let length = SEARCH_NGRAM_MIN_LENGTH; length <= maxLength; length += 1) {
|
|
326
|
-
for (let index = 0; index <=
|
|
327
|
-
grams.add(
|
|
328
|
+
for (let index = 0; index <= boundedToken.length - length; index += 1) {
|
|
329
|
+
grams.add(boundedToken.slice(index, index + length));
|
|
330
|
+
if (grams.size >= SEARCH_NGRAM_MAX_GRAMS_PER_TARGET) {
|
|
331
|
+
return [...grams].sort((left, right) => left.localeCompare(right));
|
|
332
|
+
}
|
|
328
333
|
}
|
|
329
334
|
}
|
|
330
335
|
}
|
|
@@ -1650,6 +1655,18 @@ function populateDatabase(database, capabilities, documents, skills, skillRoutes
|
|
|
1650
1655
|
'max_snippet_bytes_per_document',
|
|
1651
1656
|
String(MAX_SNIPPET_BYTES_PER_DOCUMENT),
|
|
1652
1657
|
]);
|
|
1658
|
+
database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', [
|
|
1659
|
+
'search_ngram_max_token_chars',
|
|
1660
|
+
String(SEARCH_NGRAM_MAX_TOKEN_CHARS),
|
|
1661
|
+
]);
|
|
1662
|
+
database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', [
|
|
1663
|
+
'search_ngram_max_grams_per_target',
|
|
1664
|
+
String(SEARCH_NGRAM_MAX_GRAMS_PER_TARGET),
|
|
1665
|
+
]);
|
|
1666
|
+
database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', [
|
|
1667
|
+
'source_index_max_file_bytes',
|
|
1668
|
+
String(SOURCE_INDEX_MAX_FILE_BYTES),
|
|
1669
|
+
]);
|
|
1653
1670
|
database.run('INSERT INTO metadata (key, value) VALUES (?, ?)', [
|
|
1654
1671
|
'excluded_raw_data_kinds',
|
|
1655
1672
|
LOCAL_INDEX_EXCLUDED_RAW_DATA_KINDS.join(','),
|
package/dist/cli/lib/repo-map.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { existsSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { existsSync, lstatSync, readdirSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
5
|
+
import { toPosixPath } from './filesystem.js';
|
|
6
6
|
import { readTomlFile } from './toml.js';
|
|
7
7
|
const DEFAULT_DEPTH = 3;
|
|
8
8
|
const REPO_MAP_DOC_ID = 'repo-map';
|
|
@@ -241,7 +241,7 @@ function getRepoMapConfig(projectRoot) {
|
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
243
|
function getGitFiles(projectRoot) {
|
|
244
|
-
const result = spawnSync('git', ['ls-files'], {
|
|
244
|
+
const result = spawnSync('git', ['ls-files', '-z'], {
|
|
245
245
|
cwd: projectRoot,
|
|
246
246
|
encoding: 'utf8',
|
|
247
247
|
});
|
|
@@ -249,16 +249,43 @@ function getGitFiles(projectRoot) {
|
|
|
249
249
|
return [];
|
|
250
250
|
}
|
|
251
251
|
return result.stdout
|
|
252
|
-
.split(
|
|
253
|
-
.map((line) => line
|
|
252
|
+
.split('\0')
|
|
253
|
+
.map((line) => toPosixPath(line))
|
|
254
254
|
.filter(Boolean);
|
|
255
255
|
}
|
|
256
|
-
function
|
|
256
|
+
function isAnchorCandidatePath(relativePath, priorityPaths) {
|
|
257
|
+
return priorityPaths.has(relativePath) || Boolean(getAnchorDescription(relativePath));
|
|
258
|
+
}
|
|
259
|
+
function listAnchorCandidateFilesRecursive(rootPath, depth, priorityPaths) {
|
|
260
|
+
const results = [];
|
|
261
|
+
function visit(currentPath, directoryDepth) {
|
|
262
|
+
for (const entry of readdirSync(currentPath, { withFileTypes: true })) {
|
|
263
|
+
const entryPath = path.join(currentPath, entry.name);
|
|
264
|
+
const relativePath = toPosixPath(path.relative(rootPath, entryPath));
|
|
265
|
+
if (entry.isDirectory()) {
|
|
266
|
+
if (EXCLUDED_SEGMENTS.has(entry.name) || directoryDepth >= depth) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
visit(entryPath, directoryDepth + 1);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (entry.isFile() && isAnchorCandidatePath(relativePath, priorityPaths)) {
|
|
273
|
+
results.push(relativePath);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (!existsSync(rootPath) || !statSync(rootPath).isDirectory()) {
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
visit(rootPath, 0);
|
|
281
|
+
return results.sort();
|
|
282
|
+
}
|
|
283
|
+
function getRepositoryFiles(projectRoot, depth, priorityPaths) {
|
|
257
284
|
const files = new Set();
|
|
258
285
|
for (const relativePath of getGitFiles(projectRoot)) {
|
|
259
286
|
files.add(relativePath);
|
|
260
287
|
}
|
|
261
|
-
for (const relativePath of
|
|
288
|
+
for (const relativePath of listAnchorCandidateFilesRecursive(projectRoot, depth, priorityPaths)) {
|
|
262
289
|
files.add(relativePath);
|
|
263
290
|
}
|
|
264
291
|
return Array.from(files);
|
|
@@ -306,7 +333,7 @@ function isUnderExcludedPrefix(relativePath, excludedPrefixes) {
|
|
|
306
333
|
return excludedPrefixes.some((prefix) => relativePath === prefix.slice(0, -1) || relativePath.startsWith(prefix));
|
|
307
334
|
}
|
|
308
335
|
function discoverAnchors(projectRoot, depth, priorityPaths, nestedRepositories, excludedPrefixes) {
|
|
309
|
-
return getRepositoryFiles(projectRoot)
|
|
336
|
+
return getRepositoryFiles(projectRoot, depth, priorityPaths)
|
|
310
337
|
.filter(shouldIncludePath)
|
|
311
338
|
.filter((relativePath) => !isUnderNestedRepository(relativePath, nestedRepositories))
|
|
312
339
|
.filter((relativePath) => !isUnderExcludedPrefix(relativePath, excludedPrefixes))
|
|
@@ -350,13 +377,9 @@ function renderDirectoryAnchors(anchors) {
|
|
|
350
377
|
function hasGitMarker(directoryPath) {
|
|
351
378
|
return existsSync(path.join(directoryPath, '.git'));
|
|
352
379
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
catch {
|
|
358
|
-
return false;
|
|
359
|
-
}
|
|
380
|
+
function isRealPathInside(parentRealPath, childRealPath) {
|
|
381
|
+
const relative = path.relative(parentRealPath, childRealPath);
|
|
382
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
360
383
|
}
|
|
361
384
|
function isSafeWorkspaceRoot(projectRoot, workspaceRoot) {
|
|
362
385
|
const absoluteRoot = path.resolve(projectRoot, workspaceRoot);
|
|
@@ -371,6 +394,32 @@ function getWorkspaceRootPrefixes(projectRoot, workspaceConfig) {
|
|
|
371
394
|
.filter((workspaceRoot) => isSafeWorkspaceRoot(projectRoot, workspaceRoot))
|
|
372
395
|
.map((workspaceRoot) => `${toPosixPath(workspaceRoot).replace(/\/+$/, '')}/`);
|
|
373
396
|
}
|
|
397
|
+
function resolveSafeDirectoryTarget(projectRootRealPath, logicalPath, followSymlinks) {
|
|
398
|
+
try {
|
|
399
|
+
const stats = lstatSync(logicalPath);
|
|
400
|
+
if (stats.isSymbolicLink()) {
|
|
401
|
+
if (!followSymlinks) {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
const realPath = realpathSync(logicalPath);
|
|
405
|
+
if (!isRealPathInside(projectRootRealPath, realPath) || !statSync(realPath).isDirectory()) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
return { logicalPath, realPath };
|
|
409
|
+
}
|
|
410
|
+
if (!stats.isDirectory()) {
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
const realPath = realpathSync(logicalPath);
|
|
414
|
+
if (!isRealPathInside(projectRootRealPath, realPath)) {
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
return { logicalPath, realPath };
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
374
423
|
function collectNestedRepository(projectRoot, repositoryPath, anchorFiles) {
|
|
375
424
|
const relativeRoot = `${toPosixPath(path.relative(projectRoot, repositoryPath))}/`;
|
|
376
425
|
const existingAnchors = new Set();
|
|
@@ -422,31 +471,34 @@ function discoverNestedRepositories(projectRoot, mapConfig, workspaceConfig) {
|
|
|
422
471
|
}
|
|
423
472
|
const repositories = [];
|
|
424
473
|
const seenRepositoryPaths = new Set();
|
|
425
|
-
|
|
474
|
+
const seenDirectoryPaths = new Set();
|
|
475
|
+
const projectRootRealPath = realpathSync(projectRoot);
|
|
476
|
+
function visit(directoryTarget, depth) {
|
|
426
477
|
if (repositories.length >= workspaceConfig.maxRepositories || depth > workspaceConfig.maxDepth) {
|
|
427
478
|
return;
|
|
428
479
|
}
|
|
429
|
-
if (
|
|
430
|
-
|
|
480
|
+
if (seenDirectoryPaths.has(directoryTarget.realPath)) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
seenDirectoryPaths.add(directoryTarget.realPath);
|
|
484
|
+
if (hasGitMarker(directoryTarget.logicalPath)) {
|
|
485
|
+
const resolvedRepositoryPath = directoryTarget.realPath;
|
|
431
486
|
if (!seenRepositoryPaths.has(resolvedRepositoryPath)) {
|
|
432
487
|
seenRepositoryPaths.add(resolvedRepositoryPath);
|
|
433
|
-
repositories.push(collectNestedRepository(projectRoot,
|
|
488
|
+
repositories.push(collectNestedRepository(projectRoot, directoryTarget.logicalPath, mapConfig.anchorFiles));
|
|
434
489
|
}
|
|
435
490
|
if (workspaceConfig.stopAtRepositoryRoot) {
|
|
436
491
|
return;
|
|
437
492
|
}
|
|
438
493
|
}
|
|
439
|
-
for (const entry of readdirSync(
|
|
440
|
-
if (!entry.isDirectory()) {
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
494
|
+
for (const entry of readdirSync(directoryTarget.logicalPath, { withFileTypes: true })) {
|
|
443
495
|
if (EXCLUDED_SEGMENTS.has(entry.name)) {
|
|
444
496
|
continue;
|
|
445
497
|
}
|
|
446
|
-
|
|
447
|
-
|
|
498
|
+
const childDirectoryTarget = resolveSafeDirectoryTarget(projectRootRealPath, path.join(directoryTarget.logicalPath, entry.name), workspaceConfig.followSymlinks);
|
|
499
|
+
if (childDirectoryTarget) {
|
|
500
|
+
visit(childDirectoryTarget, depth + 1);
|
|
448
501
|
}
|
|
449
|
-
visit(path.join(directoryPath, entry.name), depth + 1);
|
|
450
502
|
}
|
|
451
503
|
}
|
|
452
504
|
for (const workspaceRoot of workspaceConfig.roots) {
|
|
@@ -454,10 +506,11 @@ function discoverNestedRepositories(projectRoot, mapConfig, workspaceConfig) {
|
|
|
454
506
|
continue;
|
|
455
507
|
}
|
|
456
508
|
const absoluteWorkspaceRoot = path.resolve(projectRoot, workspaceRoot);
|
|
457
|
-
|
|
509
|
+
const workspaceTarget = resolveSafeDirectoryTarget(projectRootRealPath, absoluteWorkspaceRoot, workspaceConfig.followSymlinks);
|
|
510
|
+
if (!workspaceTarget) {
|
|
458
511
|
continue;
|
|
459
512
|
}
|
|
460
|
-
visit(
|
|
513
|
+
visit(workspaceTarget, 0);
|
|
461
514
|
}
|
|
462
515
|
return repositories.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
463
516
|
}
|
package/dist/cli/lib/run-plan.js
CHANGED
|
@@ -4,6 +4,7 @@ import { resolveSafeProjectCwd } from '../../core/command-cwd.js';
|
|
|
4
4
|
import { resolveCommandEnv } from '../../core/command-env.js';
|
|
5
5
|
import { evaluateCommandIntentEligibility, } from '../../core/command-intent-eligibility.js';
|
|
6
6
|
import { isRecord, readPositiveInteger, readString, readStringArray, } from '../../core/config-loading.js';
|
|
7
|
+
import { DEFAULT_COMMAND_MAX_OUTPUT_BYTES, MAX_COMMAND_OUTPUT_BYTES, commandMaxOutputBytesLimitMessage, } from '../../core/command-output-limits.js';
|
|
7
8
|
import { t } from './i18n.js';
|
|
8
9
|
function getSuccessExitCodes(intent) {
|
|
9
10
|
const value = intent.success_exit_codes;
|
|
@@ -72,6 +73,24 @@ function getRunPlanMode(commandArgv, intent) {
|
|
|
72
73
|
}
|
|
73
74
|
return intent.mode === 'shell' ? 'shell' : null;
|
|
74
75
|
}
|
|
76
|
+
function readEffectiveMaxOutputBytes(contract, intent) {
|
|
77
|
+
return readPositiveInteger(intent, 'max_output_bytes') ??
|
|
78
|
+
readPositiveInteger(contract.defaults, 'max_output_bytes') ??
|
|
79
|
+
DEFAULT_COMMAND_MAX_OUTPUT_BYTES;
|
|
80
|
+
}
|
|
81
|
+
function getMaxOutputBytesLimitDetail(contract, intent) {
|
|
82
|
+
const intentValue = readPositiveInteger(intent, 'max_output_bytes');
|
|
83
|
+
if (intentValue !== undefined) {
|
|
84
|
+
return intentValue > MAX_COMMAND_OUTPUT_BYTES ?
|
|
85
|
+
commandMaxOutputBytesLimitMessage('[commands.intents.<intent>].max_output_bytes') :
|
|
86
|
+
null;
|
|
87
|
+
}
|
|
88
|
+
const defaultValue = readPositiveInteger(contract.defaults, 'max_output_bytes');
|
|
89
|
+
if (defaultValue !== undefined && defaultValue > MAX_COMMAND_OUTPUT_BYTES) {
|
|
90
|
+
return commandMaxOutputBytesLimitMessage('[commands.defaults].max_output_bytes');
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
75
94
|
function readRunIntentMetadata(contract, intent) {
|
|
76
95
|
const configuredCwd = readString(intent, 'cwd') ?? readString(contract.defaults, 'default_cwd') ?? '.';
|
|
77
96
|
const commandArgv = readStringArray(intent, 'argv');
|
|
@@ -84,7 +103,7 @@ function readRunIntentMetadata(contract, intent) {
|
|
|
84
103
|
kind: readString(intent, 'kind') ?? null,
|
|
85
104
|
configuredCwd,
|
|
86
105
|
timeoutSeconds: readPositiveInteger(intent, 'timeout_seconds') ?? null,
|
|
87
|
-
maxOutputBytes:
|
|
106
|
+
maxOutputBytes: readEffectiveMaxOutputBytes(contract, intent),
|
|
88
107
|
successExitCodes: getSuccessExitCodes(intent),
|
|
89
108
|
commandArgv,
|
|
90
109
|
shellCommand,
|
|
@@ -149,6 +168,10 @@ export function createRunPlan(projectRoot, contract, intentName, options = {}) {
|
|
|
149
168
|
return createBlockedRunPlan(contract, intentName, rawIntent, eligibility, eligibility.code, eligibility.detail);
|
|
150
169
|
}
|
|
151
170
|
const metadata = readRunIntentMetadata(contract, rawIntent);
|
|
171
|
+
const maxOutputBytesLimitDetail = getMaxOutputBytesLimitDetail(contract, rawIntent);
|
|
172
|
+
if (maxOutputBytesLimitDetail) {
|
|
173
|
+
return createBlockedRunPlan(contract, intentName, rawIntent, eligibility, 'max_output_bytes_exceeds_limit', maxOutputBytesLimitDetail);
|
|
174
|
+
}
|
|
152
175
|
let cwd;
|
|
153
176
|
try {
|
|
154
177
|
cwd = resolveSafeProjectCwd(projectRoot, metadata.configuredCwd);
|
|
@@ -207,7 +230,7 @@ function createSuggestedIntentSnippet(intentName, metadata, reasonCode) {
|
|
|
207
230
|
return null;
|
|
208
231
|
}
|
|
209
232
|
let commandLines;
|
|
210
|
-
if (reasonCode === 'blocked_shell_background_pattern') {
|
|
233
|
+
if (reasonCode === 'blocked_shell_background_pattern' || reasonCode === 'blocked_long_running_command_pattern') {
|
|
211
234
|
commandLines = [`argv = ${formatTomlStringArray(['TODO_REPLACE_WITH_FINITE_COMMAND'])}`];
|
|
212
235
|
}
|
|
213
236
|
else if (metadata?.shellCommand) {
|
|
@@ -605,7 +605,8 @@ function validateStrictVersionSources(projectRoot, preferencesToml, versioningTo
|
|
|
605
605
|
pushStrictIssue(issues, '[release.versioning] is enabled but no version source was detected; add .mustflow/config/versioning.toml or a package/template version source');
|
|
606
606
|
}
|
|
607
607
|
function validateStrictTemplateVersionSync(projectRoot, preferencesToml, issues) {
|
|
608
|
-
const
|
|
608
|
+
const changedPathResult = existsSync(path.join(projectRoot, '.git')) ? readGitChangedFiles(projectRoot) : undefined;
|
|
609
|
+
const changedPaths = changedPathResult?.ok ? changedPathResult.files : undefined;
|
|
609
610
|
for (const issue of validateTemplateVersionSync(projectRoot, preferencesToml, changedPaths)) {
|
|
610
611
|
if (issue.severity === 'warning') {
|
|
611
612
|
pushStrictWarning(issues, issue.message);
|
|
@@ -4,10 +4,12 @@ const CHECK_ISSUE_ID_RULES = [
|
|
|
4
4
|
['mustflow.command_contract.configured_missing_lifecycle', /^Configured intent [^\s]+ must define lifecycle$/u],
|
|
5
5
|
['mustflow.command_contract.configured_missing_run_policy', /^Configured intent [^\s]+ must define run_policy$/u],
|
|
6
6
|
['mustflow.command_contract.oneshot_missing_timeout', /^Oneshot intent [^\s]+ must define timeout_seconds$/u],
|
|
7
|
+
['mustflow.command_contract.max_output_bytes_exceeds_limit', /^\[commands\.(?:defaults|intents\.[^\]]+)\]\.max_output_bytes must be less than or equal to \d+$/u],
|
|
7
8
|
['mustflow.command_contract.oneshot_stdin_not_closed', /^Oneshot intent [^\s]+ must set stdin = "closed"$/u],
|
|
8
9
|
['mustflow.command_contract.long_running_agent_allowed', /^Long-running intent [^\s]+ must not use run_policy = "agent_allowed"$/u],
|
|
9
10
|
['mustflow.command_contract.executable_source_missing', /^Configured intent [^\s]+ must define argv or mode = "shell" with cmd$/u],
|
|
10
11
|
['mustflow.command_contract.shell_background_pattern', /^Shell intent [^\s]+ contains a blocked long-running or background pattern$/u],
|
|
12
|
+
['mustflow.command_contract.long_running_command_pattern', /^Intent [^\s]+ contains a blocked long-running or background command pattern$/u],
|
|
11
13
|
['mustflow.command_contract.success_exit_codes_invalid', /^\[commands\.intents\.[^\]]+\]\.success_exit_codes must be an integer array$/u],
|
|
12
14
|
['mustflow.command_contract.effects_invalid', /^(?:Strict: )?(?:\[commands\.(?:resources|intents\.[^\]]+\.effects)[^\]]*\]|Command effect for intent [^\s]+ must define path, paths, or lock)/u],
|
|
13
15
|
['mustflow.command_contract.effect_path_escape', /^Strict: Command effect path must stay inside the current root:/u],
|
|
@@ -1,16 +1,39 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import { readString, readStringArray } from './config-loading.js';
|
|
2
3
|
const SAFE_COMMAND_INTENT_NAME_PATTERN = /^[A-Za-z0-9_-]+$/u;
|
|
4
|
+
const SHELL_WRAPPER_COMMANDS = new Set(['sh', 'bash', 'zsh', 'dash', 'ksh', 'cmd', 'powershell', 'pwsh']);
|
|
5
|
+
const SHELL_EVALUATION_FLAGS = new Set(['-c', '/c', '-command', '-commandwithargs']);
|
|
6
|
+
const INTERPRETER_EVALUATION_FLAGS = new Map([
|
|
7
|
+
['node', new Set(['-e', '--eval'])],
|
|
8
|
+
['python', new Set(['-c'])],
|
|
9
|
+
['python3', new Set(['-c'])],
|
|
10
|
+
['py', new Set(['-c'])],
|
|
11
|
+
['ruby', new Set(['-e'])],
|
|
12
|
+
['perl', new Set(['-e'])],
|
|
13
|
+
]);
|
|
14
|
+
const PACKAGE_SCRIPT_RUNNERS = new Set(['bun', 'npm', 'pnpm', 'yarn']);
|
|
15
|
+
const LONG_RUNNING_PACKAGE_SCRIPTS = new Set(['dev', 'start', 'serve', 'watch', 'preview']);
|
|
16
|
+
const LONG_RUNNING_EXECUTABLES = new Set(['nodemon', 'pm2', 'serve', 'http-server', 'live-server', 'webpack-dev-server']);
|
|
3
17
|
export const BACKGROUND_SHELL_PATTERNS = [
|
|
4
|
-
|
|
18
|
+
/(?:^|[^&])&(?!&)\s*$/u,
|
|
5
19
|
/\bnohup\b/iu,
|
|
6
20
|
/\bdisown\b/iu,
|
|
7
21
|
/\bStart-Process\b/iu,
|
|
8
|
-
|
|
22
|
+
/(?:^|[;&|]\s*)start\s+/iu,
|
|
9
23
|
/\bxdg-open\b/iu,
|
|
10
24
|
/\bopen\s+/iu,
|
|
11
25
|
/\bchrome(?:\.exe)?\b/iu,
|
|
12
26
|
/\bchromium(?:\.exe)?\b/iu,
|
|
13
27
|
];
|
|
28
|
+
export const LONG_RUNNING_COMMAND_TEXT_PATTERNS = [
|
|
29
|
+
/\b(?:npm|pnpm|bun|yarn)\s+(?:run\s+)?(?:dev|start|serve|watch|preview)\b/iu,
|
|
30
|
+
/\b(?:nohup|disown)\b/iu,
|
|
31
|
+
/(?:^|[^&])&(?!&)\s*$/u,
|
|
32
|
+
/\bsetInterval\s*\(/u,
|
|
33
|
+
/\bwhile\s*(?:\(\s*true\s*\)|true)\b/iu,
|
|
34
|
+
/\bserve_forever\s*\(/iu,
|
|
35
|
+
/\bcreateServer\s*\([^)]*\)\s*\.listen\s*\(/u,
|
|
36
|
+
];
|
|
14
37
|
export function commandIntentNameIsSafe(intentName) {
|
|
15
38
|
return SAFE_COMMAND_INTENT_NAME_PATTERN.test(intentName);
|
|
16
39
|
}
|
|
@@ -25,3 +48,82 @@ export function shellCommandHasBlockedBackgroundPattern(command) {
|
|
|
25
48
|
export function commandIntentHasBlockedShellBackgroundPattern(intent) {
|
|
26
49
|
return intent.mode === 'shell' && typeof intent.cmd === 'string' && shellCommandHasBlockedBackgroundPattern(intent.cmd);
|
|
27
50
|
}
|
|
51
|
+
function normalizeExecutableName(value) {
|
|
52
|
+
return path.basename(value).replace(/\.(?:cmd|exe|ps1)$/iu, '').toLowerCase();
|
|
53
|
+
}
|
|
54
|
+
function findFlagPayload(argv, flags) {
|
|
55
|
+
for (let index = 1; index < argv.length - 1; index += 1) {
|
|
56
|
+
if (flags.has(argv[index].toLowerCase())) {
|
|
57
|
+
return argv[index + 1];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function commandTextHasLongRunningPattern(command) {
|
|
63
|
+
return LONG_RUNNING_COMMAND_TEXT_PATTERNS.some((pattern) => pattern.test(command));
|
|
64
|
+
}
|
|
65
|
+
function readPackageScriptName(command, args) {
|
|
66
|
+
if (!PACKAGE_SCRIPT_RUNNERS.has(command)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
if (args[0] === 'run' && args[1] && !args[1].startsWith('-')) {
|
|
70
|
+
return args[1];
|
|
71
|
+
}
|
|
72
|
+
if (args[0] && LONG_RUNNING_PACKAGE_SCRIPTS.has(args[0])) {
|
|
73
|
+
return args[0];
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function argvHasBlockedLongRunningPattern(argv) {
|
|
78
|
+
const [rawCommand = '', ...args] = argv;
|
|
79
|
+
const command = normalizeExecutableName(rawCommand);
|
|
80
|
+
const shellPayload = SHELL_WRAPPER_COMMANDS.has(command) ? findFlagPayload(argv, SHELL_EVALUATION_FLAGS) : null;
|
|
81
|
+
if (shellPayload && (shellCommandHasBlockedBackgroundPattern(shellPayload) || commandTextHasLongRunningPattern(shellPayload))) {
|
|
82
|
+
return `shell wrapper payload contains a blocked long-running or background pattern: ${shellPayload}`;
|
|
83
|
+
}
|
|
84
|
+
const interpreterFlags = INTERPRETER_EVALUATION_FLAGS.get(command);
|
|
85
|
+
const interpreterPayload = interpreterFlags ? findFlagPayload(argv, interpreterFlags) : null;
|
|
86
|
+
if (interpreterPayload && commandTextHasLongRunningPattern(interpreterPayload)) {
|
|
87
|
+
return `interpreter evaluation payload contains a blocked long-running pattern: ${interpreterPayload}`;
|
|
88
|
+
}
|
|
89
|
+
const packageScriptName = readPackageScriptName(command, args);
|
|
90
|
+
if (packageScriptName && LONG_RUNNING_PACKAGE_SCRIPTS.has(packageScriptName)) {
|
|
91
|
+
return `package-manager script "${packageScriptName}" is commonly long-running`;
|
|
92
|
+
}
|
|
93
|
+
if (LONG_RUNNING_EXECUTABLES.has(command)) {
|
|
94
|
+
return `executable "${command}" is commonly long-running`;
|
|
95
|
+
}
|
|
96
|
+
if (command === 'vite' && !args.includes('build')) {
|
|
97
|
+
return 'vite without build is commonly a development server';
|
|
98
|
+
}
|
|
99
|
+
if (command === 'next' && ['dev', 'start'].includes(args[0] ?? '')) {
|
|
100
|
+
return `next ${args[0]} is commonly long-running`;
|
|
101
|
+
}
|
|
102
|
+
if (command === 'webpack' && (args.includes('--watch') || args.includes('-w') || args.includes('serve'))) {
|
|
103
|
+
return 'webpack watch or serve mode is commonly long-running';
|
|
104
|
+
}
|
|
105
|
+
if (command === 'tsc' && (args.includes('--watch') || args.includes('-w'))) {
|
|
106
|
+
return 'tsc watch mode is long-running';
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
export function commandIntentBlockedCommandPattern(intent) {
|
|
111
|
+
if (intent.mode === 'shell' && typeof intent.cmd === 'string' && shellCommandHasBlockedBackgroundPattern(intent.cmd)) {
|
|
112
|
+
return {
|
|
113
|
+
code: 'shell_background_pattern',
|
|
114
|
+
detail: 'Shell command contains a blocked long-running or background pattern.',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const argv = readStringArray(intent, 'argv');
|
|
118
|
+
if (!argv) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const detail = argvHasBlockedLongRunningPattern(argv);
|
|
122
|
+
if (!detail) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
code: 'long_running_command_pattern',
|
|
127
|
+
detail: `Argv command contains a blocked long-running or background pattern: ${detail}.`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { COMMAND_LIFECYCLES, COMMAND_RUN_POLICIES, LONG_RUNNING_LIFECYCLES, isRecord, } from './config-loading.js';
|
|
2
2
|
import { COMMAND_ENV_POLICIES } from './command-env.js';
|
|
3
3
|
import { COMMAND_EFFECT_CONCURRENCY, COMMAND_EFFECT_MODES, COMMAND_EFFECT_TYPES, validateCommandEffectLockWarnings, validateCommandEffects, } from './command-effects.js';
|
|
4
|
-
import { commandIntentHasBlockedShellBackgroundPattern, commandIntentHasCommandSource, commandIntentNameIsSafe, } from './command-contract-rules.js';
|
|
4
|
+
import { commandIntentBlockedCommandPattern, commandIntentHasBlockedShellBackgroundPattern, commandIntentHasCommandSource, commandIntentNameIsSafe, } from './command-contract-rules.js';
|
|
5
|
+
import { MAX_COMMAND_OUTPUT_BYTES, commandMaxOutputBytesLimitMessage } from './command-output-limits.js';
|
|
5
6
|
function commandContractIssue(message) {
|
|
6
7
|
return { message };
|
|
7
8
|
}
|
|
@@ -46,6 +47,12 @@ function validatePositiveIntegerField(table, key, label, issues) {
|
|
|
46
47
|
issues.push(commandContractIssue(`${label} must be a positive integer`));
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
function validateMaxOutputBytesField(table, key, label, issues) {
|
|
51
|
+
validatePositiveIntegerField(table, key, label, issues);
|
|
52
|
+
if (isPositiveInteger(table[key]) && Number(table[key]) > MAX_COMMAND_OUTPUT_BYTES) {
|
|
53
|
+
issues.push(commandContractIssue(commandMaxOutputBytesLimitMessage(label)));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
49
56
|
function validateAllowedStringField(table, key, label, allowedValues, issues) {
|
|
50
57
|
if (!hasOwn(table, key)) {
|
|
51
58
|
return;
|
|
@@ -70,7 +77,7 @@ function validateCommandDefaults(commandsToml, issues) {
|
|
|
70
77
|
validateAllowedStringField(defaults, 'env_policy', '[commands.defaults].env_policy', COMMAND_ENV_POLICIES, issues);
|
|
71
78
|
validateStringArrayField(defaults, 'env_allowlist', '[commands.defaults].env_allowlist', issues);
|
|
72
79
|
validatePositiveIntegerField(defaults, 'default_timeout_seconds', '[commands.defaults].default_timeout_seconds', issues);
|
|
73
|
-
|
|
80
|
+
validateMaxOutputBytesField(defaults, 'max_output_bytes', '[commands.defaults].max_output_bytes', issues);
|
|
74
81
|
validatePositiveIntegerField(defaults, 'kill_after_seconds', '[commands.defaults].kill_after_seconds', issues);
|
|
75
82
|
}
|
|
76
83
|
function validateCommandResources(commandsToml, issues) {
|
|
@@ -148,6 +155,7 @@ function validateCommandIntent(intentName, intent, issues) {
|
|
|
148
155
|
validateAllowedStringField(intent, 'run_policy', `[commands.intents.${intentName}].run_policy`, COMMAND_RUN_POLICIES, issues);
|
|
149
156
|
validateAllowedStringField(intent, 'env_policy', `[commands.intents.${intentName}].env_policy`, COMMAND_ENV_POLICIES, issues);
|
|
150
157
|
validateStringArrayField(intent, 'env_allowlist', `[commands.intents.${intentName}].env_allowlist`, issues);
|
|
158
|
+
validateMaxOutputBytesField(intent, 'max_output_bytes', `[commands.intents.${intentName}].max_output_bytes`, issues);
|
|
151
159
|
validateCommandIntentSelection(intentName, intent, issues);
|
|
152
160
|
if (intent.status !== 'configured') {
|
|
153
161
|
return;
|
|
@@ -178,6 +186,10 @@ function validateCommandIntent(intentName, intent, issues) {
|
|
|
178
186
|
if (commandIntentHasBlockedShellBackgroundPattern(intent)) {
|
|
179
187
|
issues.push(commandContractIssue(`Shell intent ${intentName} contains a blocked long-running or background pattern`));
|
|
180
188
|
}
|
|
189
|
+
const blockedCommandPattern = commandIntentBlockedCommandPattern(intent);
|
|
190
|
+
if (blockedCommandPattern?.code === 'long_running_command_pattern') {
|
|
191
|
+
issues.push(commandContractIssue(`Intent ${intentName} contains a blocked long-running or background command pattern`));
|
|
192
|
+
}
|
|
181
193
|
if (hasOwn(intent, 'success_exit_codes')) {
|
|
182
194
|
const value = intent.success_exit_codes;
|
|
183
195
|
if (!Array.isArray(value) || value.length === 0 || value.some((entry) => !Number.isInteger(entry))) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isRecord, readString } from './config-loading.js';
|
|
2
|
-
import { commandIntentHasBlockedShellBackgroundPattern, commandIntentHasCommandSource, commandIntentNameIsSafe, } from './command-contract-rules.js';
|
|
2
|
+
import { commandIntentBlockedCommandPattern, commandIntentHasBlockedShellBackgroundPattern, commandIntentHasCommandSource, commandIntentNameIsSafe, } from './command-contract-rules.js';
|
|
3
3
|
export function evaluateCommandIntentEligibility(intentName, rawIntent) {
|
|
4
4
|
if (!commandIntentNameIsSafe(intentName)) {
|
|
5
5
|
return {
|
|
@@ -68,6 +68,14 @@ export function evaluateCommandIntentEligibility(intentName, rawIntent) {
|
|
|
68
68
|
detail: 'Shell command contains a blocked long-running or background pattern.',
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
|
+
const blockedPattern = commandIntentBlockedCommandPattern(rawIntent);
|
|
72
|
+
if (blockedPattern?.code === 'long_running_command_pattern') {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
code: 'blocked_long_running_command_pattern',
|
|
76
|
+
detail: blockedPattern.detail,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
71
79
|
return {
|
|
72
80
|
ok: true,
|
|
73
81
|
code: 'ok',
|
|
@@ -2,7 +2,8 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { COMMAND_LIFECYCLES, COMMAND_RUN_POLICIES, LONG_RUNNING_LIFECYCLES, isRecord, readPositiveInteger, readString, readStringArray, } from './config-loading.js';
|
|
4
4
|
import { evaluateCommandIntentEligibility, } from './command-intent-eligibility.js';
|
|
5
|
-
import { commandIntentHasBlockedShellBackgroundPattern, commandIntentHasCommandSource, commandIntentNameIsSafe, } from './command-contract-rules.js';
|
|
5
|
+
import { commandIntentBlockedCommandPattern, commandIntentHasBlockedShellBackgroundPattern, commandIntentHasCommandSource, commandIntentNameIsSafe, } from './command-contract-rules.js';
|
|
6
|
+
import { MAX_COMMAND_OUTPUT_BYTES } from './command-output-limits.js';
|
|
6
7
|
import { commandEffectsConflict, normalizeCommandEffects } from './command-effects.js';
|
|
7
8
|
import { listChangeClassificationValidationReasons } from './change-classification.js';
|
|
8
9
|
import { parseSkillIndexRoutes } from './skill-route-alignment.js';
|
|
@@ -309,6 +310,10 @@ function lintIntent(name, value, issues) {
|
|
|
309
310
|
if (lifecycle === 'oneshot' && readPositiveInteger(value, 'timeout_seconds') === undefined) {
|
|
310
311
|
pushIssue(issues, 'error', 'oneshot_missing_timeout', name, `Oneshot intent ${name} must define timeout_seconds.`);
|
|
311
312
|
}
|
|
313
|
+
const maxOutputBytes = readPositiveInteger(value, 'max_output_bytes');
|
|
314
|
+
if (maxOutputBytes !== undefined && maxOutputBytes > MAX_COMMAND_OUTPUT_BYTES) {
|
|
315
|
+
pushIssue(issues, 'error', 'max_output_bytes_exceeds_limit', name, `Intent ${name} max_output_bytes must be less than or equal to ${MAX_COMMAND_OUTPUT_BYTES}.`);
|
|
316
|
+
}
|
|
312
317
|
if (lifecycle === 'oneshot' && readString(value, 'stdin') !== 'closed') {
|
|
313
318
|
pushIssue(issues, 'error', 'oneshot_stdin_not_closed', name, `Oneshot intent ${name} must set stdin to closed.`);
|
|
314
319
|
}
|
|
@@ -321,6 +326,10 @@ function lintIntent(name, value, issues) {
|
|
|
321
326
|
if (commandIntentHasBlockedShellBackgroundPattern(value)) {
|
|
322
327
|
pushIssue(issues, 'error', 'shell_background_pattern', name, `Shell intent ${name} contains a blocked long-running or background pattern.`);
|
|
323
328
|
}
|
|
329
|
+
const blockedCommandPattern = commandIntentBlockedCommandPattern(value);
|
|
330
|
+
if (blockedCommandPattern?.code === 'long_running_command_pattern') {
|
|
331
|
+
pushIssue(issues, 'error', 'long_running_command_pattern', name, `Intent ${name} contains a blocked long-running or background command pattern.`);
|
|
332
|
+
}
|
|
324
333
|
if (!successExitCodesAreValid(value)) {
|
|
325
334
|
pushIssue(issues, 'error', 'invalid_success_exit_codes', name, `Intent ${name} success_exit_codes must be an integer array.`);
|
|
326
335
|
}
|
package/package.json
CHANGED
package/schemas/README.md
CHANGED
|
@@ -35,10 +35,10 @@ Current schemas:
|
|
|
35
35
|
`mf explain verify --reason <event> --json`, `mf explain retention --json`, `mf explain skills --json`,
|
|
36
36
|
and `mf explain surface --json`. Verify explanations include the shared `decisionGraph` evidence model.
|
|
37
37
|
- `verify-report.schema.json`: output of `mf verify --reason <event> --json`, including an
|
|
38
|
-
evidence-based completion verdict and evidence model with a
|
|
39
|
-
selected receipts and skipped checks
|
|
38
|
+
explicit execution aggregate, evidence-based completion verdict, and evidence model with a
|
|
39
|
+
conservative coverage matrix for the selected receipts and skipped checks
|
|
40
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
|
|
41
|
+
the same execution aggregate, completion verdict, evidence model, and coverage matrix as the verify report
|
|
42
42
|
- `change-verification-report.schema.json`: output of `mf verify --reason <event> --plan-only --json` and
|
|
43
43
|
`mf verify --from-classification <classify-report.json> --plan-only --json`, including the `decision_graph` that links
|
|
44
44
|
changed surfaces, classification reasons, command candidates, eligibility, selected or not-selected state,
|
|
@@ -139,7 +139,8 @@
|
|
|
139
139
|
"missing_timeout",
|
|
140
140
|
"missing_command_source",
|
|
141
141
|
"unsafe_intent_name",
|
|
142
|
-
"blocked_shell_background_pattern"
|
|
142
|
+
"blocked_shell_background_pattern",
|
|
143
|
+
"blocked_long_running_command_pattern"
|
|
143
144
|
]
|
|
144
145
|
},
|
|
145
146
|
"runnable": { "type": "boolean" },
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"type": "string",
|
|
34
34
|
"pattern": "^sha256:[0-9a-f]{64}$"
|
|
35
35
|
},
|
|
36
|
+
"execution_status": { "enum": ["passed", "partial", "failed", "blocked"] },
|
|
36
37
|
"status": { "enum": ["passed", "partial", "failed", "blocked"] },
|
|
37
38
|
"completion_verdict": { "$ref": "#/$defs/completionVerdict" },
|
|
38
39
|
"evidence_model": { "$ref": "#/$defs/evidenceModel" },
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"type": "string",
|
|
35
35
|
"pattern": "^sha256:[0-9a-f]{64}$"
|
|
36
36
|
},
|
|
37
|
+
"execution_status": { "enum": ["passed", "partial", "failed", "blocked"] },
|
|
37
38
|
"status": { "enum": ["passed", "partial", "failed", "blocked"] },
|
|
38
39
|
"completion_verdict": { "$ref": "#/$defs/completionVerdict" },
|
|
39
40
|
"evidence_model": { "$ref": "#/$defs/evidenceModel" },
|