mustflow 2.18.0 → 2.18.2
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 +2 -2
- package/dist/cli/commands/explain-verify.js +2 -2
- package/dist/cli/commands/run.js +74 -97
- package/dist/cli/commands/verify.js +148 -44
- package/dist/cli/i18n/en.js +3 -1
- package/dist/cli/i18n/es.js +3 -1
- package/dist/cli/i18n/fr.js +3 -1
- package/dist/cli/i18n/hi.js +3 -1
- package/dist/cli/i18n/ko.js +3 -1
- package/dist/cli/i18n/zh.js +3 -1
- package/dist/cli/lib/repo-map.js +10 -3
- package/dist/core/atomic-state-write.js +31 -0
- package/dist/core/bounded-output.js +23 -1
- package/dist/core/check-issues.js +1 -0
- package/dist/core/command-contract-validation.js +57 -7
- package/dist/core/completion-verdict.js +2 -1
- package/dist/core/public-json-contracts.js +1 -1
- package/dist/core/run-receipt.js +20 -13
- package/dist/core/source-anchors.js +96 -24
- package/dist/core/verification-evidence.js +4 -1
- package/package.json +1 -1
- package/schemas/README.md +1 -1
- package/schemas/run-receipt.schema.json +26 -3
- package/schemas/verify-report.schema.json +1 -1
- package/schemas/verify-run-manifest.schema.json +1 -1
- package/templates/default/manifest.toml +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { mkdirSync, readFileSync,
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { createClassifyOutput } from './classify.js';
|
|
5
5
|
import { runRun } from './run.js';
|
|
6
6
|
import { createChangeVerificationReport, } from '../../core/change-verification.js';
|
|
7
7
|
import { createVerifyCompletionVerdict, } from '../../core/completion-verdict.js';
|
|
8
|
+
import { atomicWriteJsonFile, createStateRunId } from '../../core/atomic-state-write.js';
|
|
8
9
|
import { createExternalEvidenceRisks, } from '../../core/external-evidence.js';
|
|
9
10
|
import { createRepeatedFailureRisks, createVerificationFailureFingerprint, updateRepeatedFailureState, } from '../../core/repeated-failure.js';
|
|
10
11
|
import { countReproEvidenceVerdictEffects, createReproEvidenceRisks, } from '../../core/repro-evidence.js';
|
|
@@ -17,9 +18,9 @@ import { t } from '../lib/i18n.js';
|
|
|
17
18
|
import { readLocalCommandEffectGraph, readLocalPathSurfaces, readLocalSourceAnchorVerdictRisks, } from '../lib/local-index.js';
|
|
18
19
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
19
20
|
const VERIFY_SCHEMA_VERSION = '1';
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
21
|
+
const RUN_STATE_DIR = path.join('.mustflow', 'state', 'runs');
|
|
22
|
+
const LATEST_RUN_RECEIPT_PATH = path.join(RUN_STATE_DIR, 'latest.json');
|
|
23
|
+
const DEFAULT_VERIFY_PARALLELISM = 1;
|
|
23
24
|
function createBufferedOutput() {
|
|
24
25
|
const stdout = [];
|
|
25
26
|
const stderr = [];
|
|
@@ -52,6 +53,7 @@ export function getVerifyHelp(lang = 'en') {
|
|
|
52
53
|
{ label: '--write-plan <path>', description: t(lang, 'verify.help.option.writePlan') },
|
|
53
54
|
{ label: '--repro-evidence <path>', description: t(lang, 'verify.help.option.reproEvidence') },
|
|
54
55
|
{ label: '--external-evidence <path>', description: t(lang, 'verify.help.option.externalEvidence') },
|
|
56
|
+
{ label: '--parallel <count>', description: t(lang, 'verify.help.option.parallel') },
|
|
55
57
|
{ label: '--plan-only', description: t(lang, 'verify.help.option.planOnly') },
|
|
56
58
|
{ label: '--json', description: t(lang, 'cli.option.json') },
|
|
57
59
|
{ label: '-h, --help', description: t(lang, 'cli.option.help') },
|
|
@@ -81,6 +83,7 @@ function parseVerifyArgs(args) {
|
|
|
81
83
|
let json = false;
|
|
82
84
|
let planOnly = false;
|
|
83
85
|
let changed = false;
|
|
86
|
+
let parallelism = DEFAULT_VERIFY_PARALLELISM;
|
|
84
87
|
for (let index = 0; index < args.length; index += 1) {
|
|
85
88
|
const arg = args[index];
|
|
86
89
|
if (arg === '--json') {
|
|
@@ -95,6 +98,19 @@ function parseVerifyArgs(args) {
|
|
|
95
98
|
changed = true;
|
|
96
99
|
continue;
|
|
97
100
|
}
|
|
101
|
+
if (arg === '--parallel') {
|
|
102
|
+
const value = args[index + 1];
|
|
103
|
+
if (!value || value.startsWith('-')) {
|
|
104
|
+
return { json, planOnly, changed, reason, parallelism, error: 'missing_parallel_value' };
|
|
105
|
+
}
|
|
106
|
+
const parsedParallelism = parseVerifyParallelism(value);
|
|
107
|
+
if (parsedParallelism === null) {
|
|
108
|
+
return { json, planOnly, changed, reason, parallelism, error: 'invalid_parallel_value' };
|
|
109
|
+
}
|
|
110
|
+
parallelism = parsedParallelism;
|
|
111
|
+
index += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
98
114
|
if (arg === '--reason') {
|
|
99
115
|
const value = args[index + 1];
|
|
100
116
|
if (!value || value.startsWith('-')) {
|
|
@@ -195,6 +211,15 @@ function parseVerifyArgs(args) {
|
|
|
195
211
|
reason = value;
|
|
196
212
|
continue;
|
|
197
213
|
}
|
|
214
|
+
if (arg.startsWith('--parallel=')) {
|
|
215
|
+
const value = arg.slice('--parallel='.length);
|
|
216
|
+
const parsedParallelism = parseVerifyParallelism(value);
|
|
217
|
+
if (parsedParallelism === null) {
|
|
218
|
+
return { json, planOnly, changed, reason, parallelism, error: 'invalid_parallel_value' };
|
|
219
|
+
}
|
|
220
|
+
parallelism = parsedParallelism;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
198
223
|
if (arg.startsWith('--from-plan=')) {
|
|
199
224
|
const value = arg.slice('--from-plan='.length);
|
|
200
225
|
if (value.length === 0) {
|
|
@@ -289,7 +314,25 @@ function parseVerifyArgs(args) {
|
|
|
289
314
|
error: `unexpected:${arg}`,
|
|
290
315
|
};
|
|
291
316
|
}
|
|
292
|
-
return {
|
|
317
|
+
return {
|
|
318
|
+
json,
|
|
319
|
+
planOnly,
|
|
320
|
+
changed,
|
|
321
|
+
reason,
|
|
322
|
+
fromClassification,
|
|
323
|
+
fromPlan,
|
|
324
|
+
writePlan,
|
|
325
|
+
reproEvidence,
|
|
326
|
+
externalEvidence,
|
|
327
|
+
parallelism,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function parseVerifyParallelism(value) {
|
|
331
|
+
if (!/^[1-9][0-9]*$/u.test(value)) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const parsed = Number(value);
|
|
335
|
+
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
293
336
|
}
|
|
294
337
|
function uniqueStrings(values) {
|
|
295
338
|
return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))];
|
|
@@ -297,6 +340,18 @@ function uniqueStrings(values) {
|
|
|
297
340
|
function toPosixPath(value) {
|
|
298
341
|
return value.split(path.sep).join('/');
|
|
299
342
|
}
|
|
343
|
+
function createVerifyRunStatePaths(projectRoot) {
|
|
344
|
+
const runDir = toPosixPath(path.join(RUN_STATE_DIR, createStateRunId('verify')));
|
|
345
|
+
const manifestPath = toPosixPath(path.join(runDir, 'manifest.json'));
|
|
346
|
+
const absoluteRunDir = path.join(projectRoot, runDir);
|
|
347
|
+
return {
|
|
348
|
+
runDir,
|
|
349
|
+
manifestPath,
|
|
350
|
+
absoluteRunDir,
|
|
351
|
+
absoluteIntentDir: path.join(absoluteRunDir, 'intents'),
|
|
352
|
+
absoluteManifestPath: path.join(projectRoot, manifestPath),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
300
355
|
function sanitizeIntentFilePart(value) {
|
|
301
356
|
const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
302
357
|
return sanitized.length > 0 ? sanitized.slice(0, 80) : 'intent';
|
|
@@ -445,7 +500,7 @@ function resolvePlanPath(projectRoot, inputPath) {
|
|
|
445
500
|
}
|
|
446
501
|
return resolved;
|
|
447
502
|
}
|
|
448
|
-
export function
|
|
503
|
+
export function readInputFromClassificationReport(projectRoot, inputPath) {
|
|
449
504
|
let parsed;
|
|
450
505
|
const planPath = resolvePlanPath(projectRoot, inputPath);
|
|
451
506
|
try {
|
|
@@ -836,7 +891,8 @@ async function runVerificationIntent(intent, lang, verificationPlanId, testTarge
|
|
|
836
891
|
if (receiptStatus === 'passed' ||
|
|
837
892
|
receiptStatus === 'failed' ||
|
|
838
893
|
receiptStatus === 'timed_out' ||
|
|
839
|
-
receiptStatus === 'start_failed'
|
|
894
|
+
receiptStatus === 'start_failed' ||
|
|
895
|
+
receiptStatus === 'output_limit_exceeded') {
|
|
840
896
|
status = receiptStatus;
|
|
841
897
|
}
|
|
842
898
|
}
|
|
@@ -856,12 +912,52 @@ async function runVerificationIntent(intent, lang, verificationPlanId, testTarge
|
|
|
856
912
|
receipt,
|
|
857
913
|
};
|
|
858
914
|
}
|
|
915
|
+
function entriesForScheduleBatch(entries, batch) {
|
|
916
|
+
const batchIntents = new Set(batch.intents);
|
|
917
|
+
return entries.filter((entry) => batchIntents.has(entry.intent));
|
|
918
|
+
}
|
|
919
|
+
async function runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets) {
|
|
920
|
+
const results = [];
|
|
921
|
+
for (const entry of entries) {
|
|
922
|
+
results.push(await runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? []));
|
|
923
|
+
}
|
|
924
|
+
return results;
|
|
925
|
+
}
|
|
926
|
+
async function runVerificationEntriesInParallelChunks(entries, parallelism, lang, verificationPlanId, scheduledTestTargets) {
|
|
927
|
+
const results = [];
|
|
928
|
+
for (let index = 0; index < entries.length; index += parallelism) {
|
|
929
|
+
const chunk = entries.slice(index, index + parallelism);
|
|
930
|
+
results.push(...(await Promise.all(chunk.map((entry) => runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? [])))));
|
|
931
|
+
}
|
|
932
|
+
return results;
|
|
933
|
+
}
|
|
934
|
+
async function runScheduledVerificationIntents(report, lang, verificationPlanId, scheduledTestTargets, parallelism) {
|
|
935
|
+
if (parallelism <= DEFAULT_VERIFY_PARALLELISM) {
|
|
936
|
+
return runVerificationEntriesSequentially(report.schedule.entries, lang, verificationPlanId, scheduledTestTargets);
|
|
937
|
+
}
|
|
938
|
+
const results = [];
|
|
939
|
+
for (const batch of report.schedule.batches) {
|
|
940
|
+
const entries = entriesForScheduleBatch(report.schedule.entries, batch);
|
|
941
|
+
if (entries.length === 0) {
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
if (entries.length > 1 && entries.every((entry) => entry.parallelEligible)) {
|
|
945
|
+
results.push(...(await runVerificationEntriesInParallelChunks(entries, parallelism, lang, verificationPlanId, scheduledTestTargets)));
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
results.push(...(await runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets)));
|
|
949
|
+
}
|
|
950
|
+
return results;
|
|
951
|
+
}
|
|
859
952
|
function summarizeResults(results) {
|
|
860
953
|
const ran = results.filter((result) => !result.skipped).length;
|
|
861
954
|
const passed = results.filter((result) => result.status === 'passed').length;
|
|
862
955
|
const skipped = results.filter((result) => result.skipped).length;
|
|
863
956
|
const failed = results.filter((result) => !result.skipped &&
|
|
864
|
-
(result.status === 'failed' ||
|
|
957
|
+
(result.status === 'failed' ||
|
|
958
|
+
result.status === 'timed_out' ||
|
|
959
|
+
result.status === 'start_failed' ||
|
|
960
|
+
result.status === 'output_limit_exceeded')).length;
|
|
865
961
|
return {
|
|
866
962
|
matched: results.filter((result) => result.intent !== null).length,
|
|
867
963
|
ran,
|
|
@@ -910,11 +1006,15 @@ function timedOutForResult(result) {
|
|
|
910
1006
|
return result.status === 'timed_out' || resultSummary?.timed_out === true;
|
|
911
1007
|
}
|
|
912
1008
|
function errorKindForResult(result) {
|
|
913
|
-
return stringField(resultSummaryForResult(result)?.error_kind) ??
|
|
1009
|
+
return (stringField(resultSummaryForResult(result)?.error_kind) ??
|
|
1010
|
+
(result.status === 'start_failed' || result.status === 'output_limit_exceeded' ? result.status : null));
|
|
914
1011
|
}
|
|
915
1012
|
function failedResults(results) {
|
|
916
1013
|
return results.filter((result) => !result.skipped &&
|
|
917
|
-
(result.status === 'failed' ||
|
|
1014
|
+
(result.status === 'failed' ||
|
|
1015
|
+
result.status === 'timed_out' ||
|
|
1016
|
+
result.status === 'start_failed' ||
|
|
1017
|
+
result.status === 'output_limit_exceeded'));
|
|
918
1018
|
}
|
|
919
1019
|
function createFailureFingerprintForVerify(input) {
|
|
920
1020
|
const failures = failedResults(input.results);
|
|
@@ -1026,7 +1126,10 @@ function createCriteriaEvidence(report, results) {
|
|
|
1026
1126
|
.filter((intent) => intent !== null);
|
|
1027
1127
|
const gapCount = report.gaps.filter((gap) => gap.reason === requirement.reason).length;
|
|
1028
1128
|
const selectedResults = selectedIntents.map((intent) => resultForSelectedIntent(results, intent));
|
|
1029
|
-
if (selectedResults.some((result) => result?.status === 'failed' ||
|
|
1129
|
+
if (selectedResults.some((result) => result?.status === 'failed' ||
|
|
1130
|
+
result?.status === 'timed_out' ||
|
|
1131
|
+
result?.status === 'start_failed' ||
|
|
1132
|
+
result?.status === 'output_limit_exceeded')) {
|
|
1030
1133
|
return { ...current, contradicted: current.contradicted + 1 };
|
|
1031
1134
|
}
|
|
1032
1135
|
if (gapCount > 0 || (selectedIntents.length === 0 && skippedIntents.length > 0)) {
|
|
@@ -1181,20 +1284,18 @@ function createVerificationPlanId(report, contract) {
|
|
|
1181
1284
|
return hashTextSha256(stableJson(fingerprintSource));
|
|
1182
1285
|
}
|
|
1183
1286
|
function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks, scopeDiffRisks, validationRatchetRisks, reproEvidence, externalChecks) {
|
|
1184
|
-
const
|
|
1185
|
-
const intentDir = path.join(runDir, 'intents');
|
|
1287
|
+
const statePaths = createVerifyRunStatePaths(projectRoot);
|
|
1186
1288
|
const receipts = [];
|
|
1187
1289
|
const results = [];
|
|
1188
|
-
|
|
1189
|
-
mkdirSync(intentDir, { recursive: true });
|
|
1290
|
+
mkdirSync(statePaths.absoluteIntentDir, { recursive: true });
|
|
1190
1291
|
for (const [index, result] of output.results.entries()) {
|
|
1191
1292
|
let receiptPath = null;
|
|
1192
1293
|
let receiptSha256 = null;
|
|
1193
1294
|
let receipt = result.receipt;
|
|
1194
1295
|
if (result.intent && result.receipt) {
|
|
1195
1296
|
const fileName = `${String(index + 1).padStart(3, '0')}-${sanitizeIntentFilePart(result.intent)}.json`;
|
|
1196
|
-
const absoluteReceiptPath = path.join(
|
|
1197
|
-
receiptPath = toPosixPath(path.join(
|
|
1297
|
+
const absoluteReceiptPath = path.join(statePaths.absoluteIntentDir, fileName);
|
|
1298
|
+
receiptPath = toPosixPath(path.join(statePaths.runDir, 'intents', fileName));
|
|
1198
1299
|
receipt = {
|
|
1199
1300
|
...result.receipt,
|
|
1200
1301
|
verification_plan_id: output.verification_plan_id,
|
|
@@ -1202,7 +1303,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1202
1303
|
};
|
|
1203
1304
|
const receiptContent = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
1204
1305
|
receiptSha256 = hashTextSha256(receiptContent);
|
|
1205
|
-
|
|
1306
|
+
atomicWriteJsonFile(absoluteReceiptPath, receipt);
|
|
1206
1307
|
}
|
|
1207
1308
|
receipts.push({
|
|
1208
1309
|
intent: result.intent,
|
|
@@ -1272,6 +1373,8 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1272
1373
|
completion_verdict: completionVerdict,
|
|
1273
1374
|
failure_fingerprint: failureFingerprint,
|
|
1274
1375
|
repeated_failure_summary: repeatedFailureSummary,
|
|
1376
|
+
run_dir: statePaths.runDir,
|
|
1377
|
+
manifest_path: statePaths.manifestPath,
|
|
1275
1378
|
results,
|
|
1276
1379
|
evidence_model: createVerifyEvidenceModel({
|
|
1277
1380
|
report,
|
|
@@ -1306,7 +1409,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1306
1409
|
...(outputWithReceiptPaths.external_checks ? { external_checks: outputWithReceiptPaths.external_checks } : {}),
|
|
1307
1410
|
receipts,
|
|
1308
1411
|
};
|
|
1309
|
-
|
|
1412
|
+
atomicWriteJsonFile(statePaths.absoluteManifestPath, manifest);
|
|
1310
1413
|
const latest = {
|
|
1311
1414
|
schema_version: '1',
|
|
1312
1415
|
command: 'verify',
|
|
@@ -1324,13 +1427,13 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1324
1427
|
summary: outputWithReceiptPaths.summary,
|
|
1325
1428
|
...(outputWithReceiptPaths.repro_evidence ? { repro_evidence: outputWithReceiptPaths.repro_evidence } : {}),
|
|
1326
1429
|
...(outputWithReceiptPaths.external_checks ? { external_checks: outputWithReceiptPaths.external_checks } : {}),
|
|
1327
|
-
run_dir:
|
|
1328
|
-
manifest_path:
|
|
1430
|
+
run_dir: statePaths.runDir,
|
|
1431
|
+
manifest_path: statePaths.manifestPath,
|
|
1329
1432
|
};
|
|
1330
|
-
|
|
1433
|
+
atomicWriteJsonFile(path.join(projectRoot, LATEST_RUN_RECEIPT_PATH), latest);
|
|
1331
1434
|
return outputWithReceiptPaths;
|
|
1332
1435
|
}
|
|
1333
|
-
async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvidence = null, externalChecks = []) {
|
|
1436
|
+
async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvidence = null, externalChecks = [], parallelism = DEFAULT_VERIFY_PARALLELISM) {
|
|
1334
1437
|
const contract = readCommandContract(projectRoot);
|
|
1335
1438
|
const report = createChangeVerificationReport(input.classificationReport, contract, projectRoot);
|
|
1336
1439
|
const verificationPlanId = createVerificationPlanId(report, contract);
|
|
@@ -1343,10 +1446,7 @@ async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvi
|
|
|
1343
1446
|
const reproEvidenceRisks = createReproEvidenceRisks(reproEvidence, { verificationPlanId });
|
|
1344
1447
|
const reproEvidenceVerdictEffects = countReproEvidenceVerdictEffects(reproEvidenceRisks);
|
|
1345
1448
|
const externalEvidenceRisks = createExternalEvidenceRisks(externalChecks);
|
|
1346
|
-
const results =
|
|
1347
|
-
for (const entry of report.schedule.entries) {
|
|
1348
|
-
results.push(await runVerificationIntent(entry.intent, lang, verificationPlanId, scheduledTestTargets.get(entry.intent) ?? []));
|
|
1349
|
-
}
|
|
1449
|
+
const results = await runScheduledVerificationIntents(report, lang, verificationPlanId, scheduledTestTargets, parallelism);
|
|
1350
1450
|
results.push(...createSkippedResults(report.candidates, scheduledIntents, report.gaps));
|
|
1351
1451
|
const summary = summarizeResults(results);
|
|
1352
1452
|
const status = getVerificationStatus(summary);
|
|
@@ -1416,8 +1516,8 @@ async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvi
|
|
|
1416
1516
|
summary,
|
|
1417
1517
|
...(reproEvidence ? { repro_evidence: reproEvidence } : {}),
|
|
1418
1518
|
...(externalChecks.length > 0 ? { external_checks: externalChecks } : {}),
|
|
1419
|
-
run_dir:
|
|
1420
|
-
manifest_path:
|
|
1519
|
+
run_dir: '',
|
|
1520
|
+
manifest_path: '',
|
|
1421
1521
|
results,
|
|
1422
1522
|
};
|
|
1423
1523
|
return writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks, scopeDiffRisks, validationRatchetRisks, reproEvidence, externalChecks);
|
|
@@ -1491,19 +1591,23 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
1491
1591
|
if (parsed.error) {
|
|
1492
1592
|
const message = parsed.error === 'missing_reason_value'
|
|
1493
1593
|
? t(lang, 'cli.error.missingValue', { option: '--reason' })
|
|
1494
|
-
: parsed.error === '
|
|
1495
|
-
? t(lang, 'cli.error.missingValue', { option: '--
|
|
1496
|
-
: parsed.error === '
|
|
1497
|
-
? t(lang, '
|
|
1498
|
-
: parsed.error === '
|
|
1499
|
-
? t(lang, 'cli.error.missingValue', { option: '--
|
|
1500
|
-
: parsed.error === '
|
|
1501
|
-
? t(lang, 'cli.error.missingValue', { option: '--
|
|
1502
|
-
: parsed.error === '
|
|
1503
|
-
? t(lang, 'cli.error.missingValue', { option: '--
|
|
1504
|
-
: parsed.error
|
|
1505
|
-
? t(lang, 'cli.error.
|
|
1506
|
-
:
|
|
1594
|
+
: parsed.error === 'missing_parallel_value'
|
|
1595
|
+
? t(lang, 'cli.error.missingValue', { option: '--parallel' })
|
|
1596
|
+
: parsed.error === 'invalid_parallel_value'
|
|
1597
|
+
? t(lang, 'verify.error.invalidParallel')
|
|
1598
|
+
: parsed.error === 'missing_from_classification_value'
|
|
1599
|
+
? t(lang, 'cli.error.missingValue', { option: '--from-classification' })
|
|
1600
|
+
: parsed.error === 'missing_from_plan_value'
|
|
1601
|
+
? t(lang, 'cli.error.missingValue', { option: '--from-plan' })
|
|
1602
|
+
: parsed.error === 'missing_write_plan_value'
|
|
1603
|
+
? t(lang, 'cli.error.missingValue', { option: '--write-plan' })
|
|
1604
|
+
: parsed.error === 'missing_repro_evidence_value'
|
|
1605
|
+
? t(lang, 'cli.error.missingValue', { option: '--repro-evidence' })
|
|
1606
|
+
: parsed.error === 'missing_external_evidence_value'
|
|
1607
|
+
? t(lang, 'cli.error.missingValue', { option: '--external-evidence' })
|
|
1608
|
+
: parsed.error.startsWith('unexpected:')
|
|
1609
|
+
? t(lang, 'cli.error.unexpectedArgument', { argument: parsed.error.slice('unexpected:'.length) })
|
|
1610
|
+
: t(lang, 'cli.error.unknownOption', { option: parsed.error });
|
|
1507
1611
|
printUsageError(reporter, message, 'mf verify --help', getVerifyHelp(lang), lang);
|
|
1508
1612
|
return 1;
|
|
1509
1613
|
}
|
|
@@ -1552,7 +1656,7 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
1552
1656
|
changedPlan = changedInput.plan;
|
|
1553
1657
|
}
|
|
1554
1658
|
else if (parsed.fromClassification || parsed.fromPlan) {
|
|
1555
|
-
input =
|
|
1659
|
+
input = readInputFromClassificationReport(projectRoot, (parsed.fromClassification ?? parsed.fromPlan));
|
|
1556
1660
|
}
|
|
1557
1661
|
else {
|
|
1558
1662
|
input = {
|
|
@@ -1588,7 +1692,7 @@ export async function runVerify(args, reporter, lang = 'en') {
|
|
|
1588
1692
|
reporter.stdout(JSON.stringify(await createPlanOnlyOutput(input, projectRoot), null, 2));
|
|
1589
1693
|
return 0;
|
|
1590
1694
|
}
|
|
1591
|
-
const output = await createVerifyOutput(input, parsed.fromClassification ?? parsed.fromPlan ?? (parsed.changed ? 'changed' : null), projectRoot, lang, reproEvidence, externalChecks);
|
|
1695
|
+
const output = await createVerifyOutput(input, parsed.fromClassification ?? parsed.fromPlan ?? (parsed.changed ? 'changed' : null), projectRoot, lang, reproEvidence, externalChecks, parsed.parallelism ?? DEFAULT_VERIFY_PARALLELISM);
|
|
1592
1696
|
if (parsed.json) {
|
|
1593
1697
|
reporter.stdout(JSON.stringify(output, null, 2));
|
|
1594
1698
|
}
|
package/dist/cli/i18n/en.js
CHANGED
|
@@ -750,11 +750,12 @@ Read these files before working:
|
|
|
750
750
|
"verify.help.summary": "Run configured verification intents selected by required_after metadata.",
|
|
751
751
|
"verify.help.option.reason": "Select the required_after reason to verify",
|
|
752
752
|
"verify.help.option.fromClassification": "Read verification reasons from an mf classify report inside this repository",
|
|
753
|
-
"verify.help.option.fromPlan": "
|
|
753
|
+
"verify.help.option.fromPlan": "Deprecated compatibility alias for --from-classification; it still expects an mf classify report",
|
|
754
754
|
"verify.help.option.changed": "Classify current Git changes and verify the matching reasons",
|
|
755
755
|
"verify.help.option.writePlan": "Compatibility option that writes the changed-file classification report",
|
|
756
756
|
"verify.help.option.reproEvidence": "Read structured bug-fix reproduction evidence from a repository-local JSON summary",
|
|
757
757
|
"verify.help.option.externalEvidence": "Read lower-authority external CI evidence from a repository-local JSON summary",
|
|
758
|
+
"verify.help.option.parallel": "Run safe non-conflicting schedule batches with up to this many commands; default is 1",
|
|
758
759
|
"verify.help.option.planOnly": "Print the verification plan without running commands; requires --json",
|
|
759
760
|
"verify.help.exit.ok": "All selected verification intents passed",
|
|
760
761
|
"verify.help.exit.fail": "Verification failed, was partial, was blocked, or input was invalid",
|
|
@@ -769,6 +770,7 @@ Read these files before working:
|
|
|
769
770
|
"verify.error.planOnlyJson": "--plan-only requires --json",
|
|
770
771
|
"verify.error.reproEvidenceRequiresRun": "--repro-evidence cannot be used with --plan-only",
|
|
771
772
|
"verify.error.externalEvidenceRequiresRun": "--external-evidence cannot be used with --plan-only",
|
|
773
|
+
"verify.error.invalidParallel": "--parallel must be a positive integer",
|
|
772
774
|
"verify.error.invalid_plan_file": "Classification report must be a readable JSON file",
|
|
773
775
|
"verify.error.unsupported_plan_source": "Verification input must be an mf classify report",
|
|
774
776
|
"verify.error.plan_root_mismatch": "Classification report must come from this mustflow root",
|
package/dist/cli/i18n/es.js
CHANGED
|
@@ -750,11 +750,12 @@ Lee estos archivos antes de trabajar:
|
|
|
750
750
|
"verify.help.summary": "Ejecuta intenciones de verificación configuradas seleccionadas por metadatos required_after.",
|
|
751
751
|
"verify.help.option.reason": "Selecciona la razón required_after que se debe verificar",
|
|
752
752
|
"verify.help.option.fromClassification": "Lee razones de verificación desde un informe de mf classify dentro de este repositorio",
|
|
753
|
-
"verify.help.option.fromPlan": "Alias de compatibilidad para --from-classification",
|
|
753
|
+
"verify.help.option.fromPlan": "Alias de compatibilidad obsoleto para --from-classification; todavía espera un informe de mf classify",
|
|
754
754
|
"verify.help.option.changed": "Clasifica los cambios actuales de Git y verifica las razones correspondientes",
|
|
755
755
|
"verify.help.option.writePlan": "Opción de compatibilidad que escribe el informe de clasificación de cambios",
|
|
756
756
|
"verify.help.option.reproEvidence": "Lee evidencia estructurada de reproducción de errores desde un resumen JSON local del repositorio",
|
|
757
757
|
"verify.help.option.externalEvidence": "Lee evidencia de CI externa de menor autoridad desde un resumen JSON local del repositorio",
|
|
758
|
+
"verify.help.option.parallel": "Ejecuta lotes programados seguros y sin conflictos con hasta esta cantidad de comandos; el valor predeterminado es 1",
|
|
758
759
|
"verify.help.option.planOnly": "Imprime el plan de verificación sin ejecutar comandos; requiere --json",
|
|
759
760
|
"verify.help.exit.ok": "Todas las intenciones de verificación seleccionadas pasaron",
|
|
760
761
|
"verify.help.exit.fail": "La verificación falló, fue parcial, quedó bloqueada o la entrada no fue válida",
|
|
@@ -769,6 +770,7 @@ Lee estos archivos antes de trabajar:
|
|
|
769
770
|
"verify.error.planOnlyJson": "--plan-only requiere --json",
|
|
770
771
|
"verify.error.reproEvidenceRequiresRun": "--repro-evidence no se puede usar con --plan-only",
|
|
771
772
|
"verify.error.externalEvidenceRequiresRun": "--external-evidence no se puede usar con --plan-only",
|
|
773
|
+
"verify.error.invalidParallel": "--parallel debe ser un entero positivo",
|
|
772
774
|
"verify.error.invalid_plan_file": "El informe de clasificación debe ser un archivo JSON legible",
|
|
773
775
|
"verify.error.unsupported_plan_source": "La entrada de verificación debe ser un informe de mf classify",
|
|
774
776
|
"verify.error.plan_root_mismatch": "El informe de clasificación debe provenir de esta raíz mustflow",
|
package/dist/cli/i18n/fr.js
CHANGED
|
@@ -750,11 +750,12 @@ Lisez ces fichiers avant de travailler :
|
|
|
750
750
|
"verify.help.summary": "Exécute les intentions de vérification configurées sélectionnées par les métadonnées required_after.",
|
|
751
751
|
"verify.help.option.reason": "Sélectionne la raison required_after à vérifier",
|
|
752
752
|
"verify.help.option.fromClassification": "Lit les raisons de vérification depuis un rapport mf classify dans ce dépôt",
|
|
753
|
-
"verify.help.option.fromPlan": "Alias de compatibilité pour --from-classification",
|
|
753
|
+
"verify.help.option.fromPlan": "Alias de compatibilité obsolète pour --from-classification; il attend toujours un rapport mf classify",
|
|
754
754
|
"verify.help.option.changed": "Classe les changements Git actuels et vérifie les raisons correspondantes",
|
|
755
755
|
"verify.help.option.writePlan": "Option de compatibilité qui écrit le rapport de classification des changements",
|
|
756
756
|
"verify.help.option.reproEvidence": "Lit une preuve structurée de reproduction de bogue depuis un résumé JSON local au dépôt",
|
|
757
757
|
"verify.help.option.externalEvidence": "Lit une preuve CI externe de moindre autorité depuis un résumé JSON local au dépôt",
|
|
758
|
+
"verify.help.option.parallel": "Exécute les lots planifiés sûrs et sans conflit avec au plus ce nombre de commandes ; valeur par défaut : 1",
|
|
758
759
|
"verify.help.option.planOnly": "Affiche le plan de vérification sans exécuter de commandes; nécessite --json",
|
|
759
760
|
"verify.help.exit.ok": "Toutes les intentions de vérification sélectionnées ont réussi",
|
|
760
761
|
"verify.help.exit.fail": "La vérification a échoué, est partielle, est bloquée ou l'entrée est invalide",
|
|
@@ -769,6 +770,7 @@ Lisez ces fichiers avant de travailler :
|
|
|
769
770
|
"verify.error.planOnlyJson": "--plan-only nécessite --json",
|
|
770
771
|
"verify.error.reproEvidenceRequiresRun": "--repro-evidence ne peut pas être utilisé avec --plan-only",
|
|
771
772
|
"verify.error.externalEvidenceRequiresRun": "--external-evidence ne peut pas être utilisé avec --plan-only",
|
|
773
|
+
"verify.error.invalidParallel": "--parallel doit être un entier positif",
|
|
772
774
|
"verify.error.invalid_plan_file": "Le rapport de classification doit être un fichier JSON lisible",
|
|
773
775
|
"verify.error.unsupported_plan_source": "L'entrée de vérification doit être un rapport mf classify",
|
|
774
776
|
"verify.error.plan_root_mismatch": "Le rapport de classification doit venir de cette racine mustflow",
|
package/dist/cli/i18n/hi.js
CHANGED
|
@@ -750,11 +750,12 @@ export const hiMessages = {
|
|
|
750
750
|
"verify.help.summary": "required_after metadata से चुने गए configured verification intents चलाएँ।",
|
|
751
751
|
"verify.help.option.reason": "Verify करने के लिए required_after reason चुनें",
|
|
752
752
|
"verify.help.option.fromClassification": "इस repository के अंदर mf classify report से verification reasons पढ़ें",
|
|
753
|
-
"verify.help.option.fromPlan": "--from-classification का compatibility alias",
|
|
753
|
+
"verify.help.option.fromPlan": "--from-classification का deprecated compatibility alias; input अब भी mf classify report होना चाहिए",
|
|
754
754
|
"verify.help.option.changed": "Current Git changes classify करके matching reasons verify करें",
|
|
755
755
|
"verify.help.option.writePlan": "Changed-file classification report लिखने वाला compatibility option",
|
|
756
756
|
"verify.help.option.reproEvidence": "Repository-local JSON summary से structured bug reproduction evidence पढ़ें",
|
|
757
757
|
"verify.help.option.externalEvidence": "Repository-local JSON summary से lower-authority external CI evidence पढ़ें",
|
|
758
|
+
"verify.help.option.parallel": "Safe और non-conflicting schedule batches को इतने commands तक साथ चलाएं; default 1 है",
|
|
758
759
|
"verify.help.option.planOnly": "Commands चलाए बिना verification plan print करें; --json चाहिए",
|
|
759
760
|
"verify.help.exit.ok": "सभी selected verification intents pass हुए",
|
|
760
761
|
"verify.help.exit.fail": "Verification fail हुआ, partial रहा, blocked रहा, या input invalid था",
|
|
@@ -769,6 +770,7 @@ export const hiMessages = {
|
|
|
769
770
|
"verify.error.planOnlyJson": "--plan-only के लिए --json चाहिए",
|
|
770
771
|
"verify.error.reproEvidenceRequiresRun": "--repro-evidence को --plan-only के साथ इस्तेमाल नहीं किया जा सकता",
|
|
771
772
|
"verify.error.externalEvidenceRequiresRun": "--external-evidence को --plan-only के साथ इस्तेमाल नहीं किया जा सकता",
|
|
773
|
+
"verify.error.invalidParallel": "--parallel positive integer होना चाहिए",
|
|
772
774
|
"verify.error.invalid_plan_file": "Classification report readable JSON file होना चाहिए",
|
|
773
775
|
"verify.error.unsupported_plan_source": "Verification input mf classify report होना चाहिए",
|
|
774
776
|
"verify.error.plan_root_mismatch": "Classification report इसी mustflow root से आना चाहिए",
|
package/dist/cli/i18n/ko.js
CHANGED
|
@@ -750,11 +750,12 @@ export const koMessages = {
|
|
|
750
750
|
"verify.help.summary": "required_after 메타데이터로 선택된 설정된 검증 의도를 실행합니다.",
|
|
751
751
|
"verify.help.option.reason": "검증할 required_after 이유를 지정합니다",
|
|
752
752
|
"verify.help.option.fromClassification": "이 저장소 안의 mf classify 보고서에서 검증 이유를 읽습니다",
|
|
753
|
-
"verify.help.option.fromPlan": "--from-classification과 같은 호환
|
|
753
|
+
"verify.help.option.fromPlan": "--from-classification과 같은 폐기 예정 호환 옵션입니다. 입력은 여전히 mf classify 보고서여야 합니다",
|
|
754
754
|
"verify.help.option.changed": "현재 Git 변경을 분류하고 맞는 검증 이유를 실행합니다",
|
|
755
755
|
"verify.help.option.writePlan": "변경 파일 분류 보고서를 쓰는 호환 옵션입니다",
|
|
756
756
|
"verify.help.option.reproEvidence": "저장소 안의 JSON 요약에서 구조화된 버그 재현 증거를 읽습니다",
|
|
757
757
|
"verify.help.option.externalEvidence": "저장소 안의 JSON 요약에서 낮은 권한의 외부 CI 증거를 읽습니다",
|
|
758
|
+
"verify.help.option.parallel": "안전하고 서로 충돌하지 않는 예정 실행 묶음을 이 개수까지 함께 실행합니다. 기본값은 1입니다",
|
|
758
759
|
"verify.help.option.planOnly": "명령을 실행하지 않고 검증 계획만 출력합니다. --json이 필요합니다",
|
|
759
760
|
"verify.help.exit.ok": "선택된 모든 검증 의도가 통과했습니다",
|
|
760
761
|
"verify.help.exit.fail": "검증이 실패했거나, 일부만 실행됐거나, 막혔거나, 입력이 올바르지 않습니다",
|
|
@@ -769,6 +770,7 @@ export const koMessages = {
|
|
|
769
770
|
"verify.error.planOnlyJson": "--plan-only에는 --json이 필요합니다",
|
|
770
771
|
"verify.error.reproEvidenceRequiresRun": "--repro-evidence는 --plan-only와 함께 사용할 수 없습니다",
|
|
771
772
|
"verify.error.externalEvidenceRequiresRun": "--external-evidence는 --plan-only와 함께 사용할 수 없습니다",
|
|
773
|
+
"verify.error.invalidParallel": "--parallel 값은 양의 정수여야 합니다",
|
|
772
774
|
"verify.error.invalid_plan_file": "분류 보고서는 읽을 수 있는 JSON 파일이어야 합니다",
|
|
773
775
|
"verify.error.unsupported_plan_source": "검증 입력은 mf classify 보고서여야 합니다",
|
|
774
776
|
"verify.error.plan_root_mismatch": "분류 보고서는 현재 mustflow 루트에서 나온 것이어야 합니다",
|
package/dist/cli/i18n/zh.js
CHANGED
|
@@ -750,11 +750,12 @@ export const zhMessages = {
|
|
|
750
750
|
"verify.help.summary": "运行由 required_after 元数据选出的已配置验证意图。",
|
|
751
751
|
"verify.help.option.reason": "选择要验证的 required_after 原因",
|
|
752
752
|
"verify.help.option.fromClassification": "从此仓库内的 mf classify 报告读取验证原因",
|
|
753
|
-
"verify.help.option.fromPlan": "--from-classification
|
|
753
|
+
"verify.help.option.fromPlan": "--from-classification 的已弃用兼容别名;输入仍必须是 mf classify 报告",
|
|
754
754
|
"verify.help.option.changed": "分类当前 Git 变更并验证匹配的原因",
|
|
755
755
|
"verify.help.option.writePlan": "写入变更文件分类报告的兼容选项",
|
|
756
756
|
"verify.help.option.reproEvidence": "从仓库本地 JSON 摘要读取结构化的 bug 复现证据",
|
|
757
757
|
"verify.help.option.externalEvidence": "从仓库本地 JSON 摘要读取低权限外部 CI 证据",
|
|
758
|
+
"verify.help.option.parallel": "最多并行执行这个数量的安全、无冲突计划批次命令;默认值为 1",
|
|
758
759
|
"verify.help.option.planOnly": "仅输出验证计划,不执行命令;需要 --json",
|
|
759
760
|
"verify.help.exit.ok": "选中的所有验证意图均已通过",
|
|
760
761
|
"verify.help.exit.fail": "验证失败、部分完成、被阻止,或输入无效",
|
|
@@ -769,6 +770,7 @@ export const zhMessages = {
|
|
|
769
770
|
"verify.error.planOnlyJson": "--plan-only 需要 --json",
|
|
770
771
|
"verify.error.reproEvidenceRequiresRun": "--repro-evidence 不能与 --plan-only 一起使用",
|
|
771
772
|
"verify.error.externalEvidenceRequiresRun": "--external-evidence 不能与 --plan-only 一起使用",
|
|
773
|
+
"verify.error.invalidParallel": "--parallel 必须是正整数",
|
|
772
774
|
"verify.error.invalid_plan_file": "分类报告必须是可读取的 JSON 文件",
|
|
773
775
|
"verify.error.unsupported_plan_source": "验证输入必须是 mf classify 报告",
|
|
774
776
|
"verify.error.plan_root_mismatch": "分类报告必须来自当前 mustflow 根目录",
|
package/dist/cli/lib/repo-map.js
CHANGED
|
@@ -11,6 +11,8 @@ const REPO_MAP_GENERATOR = 'mustflow';
|
|
|
11
11
|
const REPO_MAP_RELATIVE_ROOT = '.';
|
|
12
12
|
const REPO_MAP_SOURCE_POLICY = 'anchors_only';
|
|
13
13
|
const REPO_MAP_PRIVACY_MODE = 'minimal';
|
|
14
|
+
const GIT_LS_FILES_TIMEOUT_MS = 5_000;
|
|
15
|
+
const GIT_LS_FILES_MAX_BUFFER_BYTES = 1_048_576;
|
|
14
16
|
const EXCLUDED_SEGMENTS = new Set([
|
|
15
17
|
'.astro',
|
|
16
18
|
'.cache',
|
|
@@ -240,10 +242,15 @@ function getRepoMapConfig(projectRoot) {
|
|
|
240
242
|
},
|
|
241
243
|
};
|
|
242
244
|
}
|
|
243
|
-
function
|
|
244
|
-
const
|
|
245
|
+
export function listGitFilesForRepoMap(projectRoot, options = {}) {
|
|
246
|
+
const spawnGit = options.spawnGit ??
|
|
247
|
+
((command, args, spawnOptions) => spawnSync(command, [...args], spawnOptions));
|
|
248
|
+
const result = spawnGit('git', ['ls-files', '-z'], {
|
|
245
249
|
cwd: projectRoot,
|
|
246
250
|
encoding: 'utf8',
|
|
251
|
+
maxBuffer: options.maxBuffer ?? GIT_LS_FILES_MAX_BUFFER_BYTES,
|
|
252
|
+
timeout: options.timeout ?? GIT_LS_FILES_TIMEOUT_MS,
|
|
253
|
+
windowsHide: true,
|
|
247
254
|
});
|
|
248
255
|
if (result.status !== 0 || result.error) {
|
|
249
256
|
return [];
|
|
@@ -282,7 +289,7 @@ function listAnchorCandidateFilesRecursive(rootPath, depth, priorityPaths) {
|
|
|
282
289
|
}
|
|
283
290
|
function getRepositoryFiles(projectRoot, depth, priorityPaths) {
|
|
284
291
|
const files = new Set();
|
|
285
|
-
for (const relativePath of
|
|
292
|
+
for (const relativePath of listGitFilesForRepoMap(projectRoot)) {
|
|
286
293
|
files.add(relativePath);
|
|
287
294
|
}
|
|
288
295
|
for (const relativePath of listAnchorCandidateFilesRecursive(projectRoot, depth, priorityPaths)) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
function tempFilePath(targetPath) {
|
|
5
|
+
const suffix = `${process.pid}-${Date.now()}-${randomBytes(6).toString('hex')}`;
|
|
6
|
+
return path.join(path.dirname(targetPath), `.${path.basename(targetPath)}.${suffix}.tmp`);
|
|
7
|
+
}
|
|
8
|
+
export function createStateRunId(prefix) {
|
|
9
|
+
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-');
|
|
10
|
+
return `${prefix}-${timestamp}-${process.pid}-${randomBytes(6).toString('hex')}`;
|
|
11
|
+
}
|
|
12
|
+
export function atomicWriteTextFile(targetPath, content) {
|
|
13
|
+
mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
14
|
+
const temporaryPath = tempFilePath(targetPath);
|
|
15
|
+
try {
|
|
16
|
+
writeFileSync(temporaryPath, content, { encoding: 'utf8', flag: 'wx' });
|
|
17
|
+
renameSync(temporaryPath, targetPath);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
try {
|
|
21
|
+
unlinkSync(temporaryPath);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Best-effort cleanup for a temporary file that may not have been created.
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function atomicWriteJsonFile(targetPath, value) {
|
|
30
|
+
atomicWriteTextFile(targetPath, `${JSON.stringify(value, null, 2)}\n`);
|
|
31
|
+
}
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
function isUtf8ContinuationByte(value) {
|
|
2
|
+
return value !== undefined && (value & 0xc0) === 0x80;
|
|
3
|
+
}
|
|
4
|
+
function findUtf8TailStart(buffer, startOffset) {
|
|
5
|
+
let start = Math.min(buffer.byteLength, Math.max(0, Math.trunc(startOffset)));
|
|
6
|
+
while (start < buffer.byteLength && isUtf8ContinuationByte(buffer[start])) {
|
|
7
|
+
start += 1;
|
|
8
|
+
}
|
|
9
|
+
return start;
|
|
10
|
+
}
|
|
11
|
+
export function decodeUtf8Tail(buffer, maxTailBytes) {
|
|
12
|
+
if (maxTailBytes <= 0) {
|
|
13
|
+
return { text: '', truncated: buffer.byteLength > 0 };
|
|
14
|
+
}
|
|
15
|
+
const rawStart = buffer.byteLength > maxTailBytes ? buffer.byteLength - maxTailBytes : 0;
|
|
16
|
+
const start = findUtf8TailStart(buffer, rawStart);
|
|
17
|
+
return {
|
|
18
|
+
text: buffer.subarray(start).toString('utf8'),
|
|
19
|
+
truncated: buffer.byteLength > maxTailBytes || start > 0,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
1
22
|
export class BoundedOutputBuffer {
|
|
2
23
|
#maxTailBytes;
|
|
3
24
|
#chunks = [];
|
|
@@ -30,9 +51,10 @@ export class BoundedOutputBuffer {
|
|
|
30
51
|
}
|
|
31
52
|
}
|
|
32
53
|
toSnapshot() {
|
|
54
|
+
const tail = decodeUtf8Tail(Buffer.concat(this.#chunks, this.#tailBytes), this.#maxTailBytes);
|
|
33
55
|
return {
|
|
34
56
|
bytes: this.#bytes,
|
|
35
|
-
tail:
|
|
57
|
+
tail: tail.text,
|
|
36
58
|
};
|
|
37
59
|
}
|
|
38
60
|
}
|
|
@@ -14,6 +14,7 @@ const CHECK_ISSUE_ID_RULES = [
|
|
|
14
14
|
['mustflow.command_contract.effects_invalid', /^(?:Strict: )?(?:\[commands\.(?:resources|intents\.[^\]]+\.effects)[^\]]*\]|Command effect for intent [^\s]+ must define path, paths, or lock)/u],
|
|
15
15
|
['mustflow.command_contract.effect_path_escape', /^Strict: Command effect path must stay inside the current root:/u],
|
|
16
16
|
['mustflow.command_contract.shared_writes_without_effects', /^Strict warning: configured agent-runnable intents .+ share path:.+ through writes without explicit effects or resource locks$/u],
|
|
17
|
+
['mustflow.command_contract.broad_env_inheritance', /^Strict warning: configured agent-runnable intent [^\s]+ (?:implicitly inherits the host environment|uses env_policy = "inherit")/u],
|
|
17
18
|
['mustflow.prompt_cache.required', /^Strict: \[prompt_cache\] table is required$/u],
|
|
18
19
|
['mustflow.prompt_cache.volatile_in_stable', /^Strict: \[prompt_cache\.layers\.stable\]\.read must not include volatile path /u],
|
|
19
20
|
['mustflow.refresh.hash_method_required', /^Strict: \[refresh\]\.default_method should be "hash_check" for cache-friendly refresh$/u],
|