mustflow 2.18.20 → 2.21.1
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/dist/cli/commands/classify.js +2 -3
- package/dist/cli/commands/doctor.js +46 -6
- package/dist/cli/commands/run/output.js +1 -1
- package/dist/cli/commands/run/receipt.js +1 -0
- package/dist/cli/commands/verify.js +52 -23
- package/dist/cli/i18n/en.js +1 -0
- package/dist/cli/i18n/es.js +1 -0
- package/dist/cli/i18n/fr.js +1 -0
- package/dist/cli/i18n/hi.js +1 -0
- package/dist/cli/i18n/ko.js +1 -0
- package/dist/cli/i18n/zh.js +1 -0
- package/dist/cli/lib/git-changes.js +7 -1
- package/dist/cli/lib/local-index/index.js +9 -30
- package/dist/cli/lib/repo-map.js +3 -2
- package/dist/cli/lib/run-plan.js +8 -4
- package/dist/core/change-classification.js +24 -2
- package/dist/core/check-issues.js +1 -1
- package/dist/core/command-contract-rules.js +6 -0
- package/dist/core/command-contract-validation.js +24 -10
- package/dist/core/command-output-limits.js +2 -1
- package/dist/core/line-endings.js +12 -4
- package/dist/core/repeated-failure.js +3 -3
- package/dist/core/run-performance-history.js +4 -4
- package/dist/core/run-profile.js +2 -3
- package/dist/core/run-receipt.js +11 -3
- package/dist/core/run-write-drift.js +64 -12
- package/dist/core/safe-filesystem.js +155 -0
- package/package.json +1 -1
- package/schemas/commands.schema.json +1 -0
- package/schemas/doctor-report.schema.json +23 -1
- package/schemas/run-receipt.schema.json +6 -2
- package/templates/default/i18n.toml +13 -13
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +13 -13
- package/templates/default/locales/en/.mustflow/skills/adapter-boundary/SKILL.md +72 -4
- package/templates/default/locales/en/.mustflow/skills/command-contract-authoring/SKILL.md +16 -10
- package/templates/default/locales/en/.mustflow/skills/command-pattern/SKILL.md +64 -7
- package/templates/default/locales/en/.mustflow/skills/database-change-safety/SKILL.md +249 -16
- package/templates/default/locales/en/.mustflow/skills/dependency-reality-check/SKILL.md +37 -7
- package/templates/default/locales/en/.mustflow/skills/migration-safety-check/SKILL.md +74 -10
- package/templates/default/locales/en/.mustflow/skills/performance-budget-check/SKILL.md +132 -5
- package/templates/default/locales/en/.mustflow/skills/pure-core-imperative-shell/SKILL.md +12 -5
- package/templates/default/locales/en/.mustflow/skills/result-option/SKILL.md +4 -2
- package/templates/default/locales/en/.mustflow/skills/security-privacy-review/SKILL.md +112 -29
- package/templates/default/locales/en/.mustflow/skills/state-machine-pattern/SKILL.md +17 -4
- package/templates/default/locales/en/.mustflow/skills/structure-discovery-gate/SKILL.md +193 -2
- package/templates/default/manifest.toml +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import { createChangeClassificationReport, } from '../../core/change-classification.js';
|
|
3
|
+
import { writeJsonFileInsideWithoutSymlinks } from '../../core/safe-filesystem.js';
|
|
4
4
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
5
5
|
import { requireGitChangedFiles } from '../lib/git-changes.js';
|
|
6
6
|
import { t } from '../lib/i18n.js';
|
|
@@ -111,8 +111,7 @@ function resolveWritePath(projectRoot, inputPath) {
|
|
|
111
111
|
}
|
|
112
112
|
function writeClassifyOutput(projectRoot, inputPath, output) {
|
|
113
113
|
const outputPath = resolveWritePath(projectRoot, inputPath);
|
|
114
|
-
|
|
115
|
-
writeFileSync(outputPath, `${JSON.stringify(output, null, 2)}\n`, 'utf8');
|
|
114
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, outputPath, output);
|
|
116
115
|
}
|
|
117
116
|
export function runClassify(args, reporter, lang = 'en') {
|
|
118
117
|
if (args.includes('--help') || args.includes('-h')) {
|
|
@@ -5,7 +5,9 @@ import { getAgentContext, } from '../lib/agent-context.js';
|
|
|
5
5
|
import { t } from '../lib/i18n.js';
|
|
6
6
|
import { getLocalIndexDatabasePath } from '../lib/local-index.js';
|
|
7
7
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
8
|
-
import {
|
|
8
|
+
import { checkMustflowProjectReport } from '../lib/validation.js';
|
|
9
|
+
import { findCommandEnvInheritanceWarnings } from '../../core/command-contract-validation.js';
|
|
10
|
+
import { readCommandContract } from '../../core/config-loading.js';
|
|
9
11
|
import { summarizeSkillRouteAlignment } from '../../core/skill-route-alignment.js';
|
|
10
12
|
const DOCTOR_SCHEMA_VERSION = '1';
|
|
11
13
|
export function getDoctorHelp(lang = 'en') {
|
|
@@ -46,6 +48,7 @@ function createDiagnostics(output) {
|
|
|
46
48
|
const localIndexExists = existsSync(getLocalIndexDatabasePath(output.mustflow_root));
|
|
47
49
|
const runnableIntentCount = output.context.runnable_intents.length;
|
|
48
50
|
const skillRouteAlignment = summarizeSkillRouteAlignment(output.check.issues);
|
|
51
|
+
const inheritedEnvIntentCount = output.command_environment.inherited_intents.length;
|
|
49
52
|
diagnostics.push({
|
|
50
53
|
id: 'install',
|
|
51
54
|
status: output.installed ? 'ok' : 'fail',
|
|
@@ -55,7 +58,9 @@ function createDiagnostics(output) {
|
|
|
55
58
|
diagnostics.push({
|
|
56
59
|
id: 'validation',
|
|
57
60
|
status: output.check.ok ? 'ok' : 'fail',
|
|
58
|
-
summary:
|
|
61
|
+
summary: output.check.warning_count === 0
|
|
62
|
+
? `${output.check.issue_count} ${pluralize(output.check.issue_count, 'issue', 'issues')}`
|
|
63
|
+
: `${output.check.issue_count} ${pluralize(output.check.issue_count, 'issue', 'issues')}, ${output.check.warning_count} ${pluralize(output.check.warning_count, 'warning', 'warnings')}`,
|
|
59
64
|
action: output.check.ok ? null : checkCommand,
|
|
60
65
|
});
|
|
61
66
|
diagnostics.push({
|
|
@@ -72,6 +77,16 @@ function createDiagnostics(output) {
|
|
|
72
77
|
: 'missing',
|
|
73
78
|
action: output.context.command_contract_exists ? 'mf help commands' : 'mf init --dry-run',
|
|
74
79
|
});
|
|
80
|
+
diagnostics.push({
|
|
81
|
+
id: 'environment',
|
|
82
|
+
status: output.context.command_contract_exists ? (inheritedEnvIntentCount > 0 ? 'warn' : 'ok') : 'info',
|
|
83
|
+
summary: output.context.command_contract_exists
|
|
84
|
+
? inheritedEnvIntentCount === 0
|
|
85
|
+
? 'no inherited host-environment intents'
|
|
86
|
+
: `${inheritedEnvIntentCount} inherited host-environment ${pluralize(inheritedEnvIntentCount, 'intent', 'intents')}: ${output.command_environment.inherited_intents.join(', ')}`
|
|
87
|
+
: 'command contract missing',
|
|
88
|
+
action: inheritedEnvIntentCount > 0 ? 'mf check --strict --json' : null,
|
|
89
|
+
});
|
|
75
90
|
diagnostics.push({
|
|
76
91
|
id: 'read_order',
|
|
77
92
|
status: output.context.missing_read_order.length === 0 ? 'ok' : 'fail',
|
|
@@ -124,15 +139,37 @@ function getNextSteps(output) {
|
|
|
124
139
|
}
|
|
125
140
|
return nextSteps;
|
|
126
141
|
}
|
|
142
|
+
function readCommandEnvironmentSummary(projectRoot) {
|
|
143
|
+
try {
|
|
144
|
+
const contract = readCommandContract(projectRoot);
|
|
145
|
+
const warnings = findCommandEnvInheritanceWarnings(contract);
|
|
146
|
+
return {
|
|
147
|
+
inherited_intents: warnings.map((warning) => warning.intentName).sort((left, right) => left.localeCompare(right)),
|
|
148
|
+
inherited_network_intents: warnings
|
|
149
|
+
.filter((warning) => warning.network)
|
|
150
|
+
.map((warning) => warning.intentName)
|
|
151
|
+
.sort((left, right) => left.localeCompare(right)),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return {
|
|
156
|
+
inherited_intents: [],
|
|
157
|
+
inherited_network_intents: [],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
127
161
|
function createDoctorOutput(strict) {
|
|
128
162
|
const mustflowRoot = resolveMustflowRoot();
|
|
129
163
|
const context = getAgentContext(mustflowRoot);
|
|
130
|
-
const
|
|
164
|
+
const checkReport = checkMustflowProjectReport(mustflowRoot, { strict });
|
|
131
165
|
const check = {
|
|
132
|
-
ok: issues.length === 0,
|
|
133
|
-
issue_count: issues.length,
|
|
134
|
-
issues,
|
|
166
|
+
ok: checkReport.issues.length === 0,
|
|
167
|
+
issue_count: checkReport.issues.length,
|
|
168
|
+
issues: checkReport.issues,
|
|
169
|
+
warning_count: checkReport.warnings.length,
|
|
170
|
+
warnings: checkReport.warnings,
|
|
135
171
|
};
|
|
172
|
+
const commandEnvironment = readCommandEnvironmentSummary(mustflowRoot);
|
|
136
173
|
const baseOutput = {
|
|
137
174
|
schema_version: DOCTOR_SCHEMA_VERSION,
|
|
138
175
|
command: 'doctor',
|
|
@@ -150,6 +187,7 @@ function createDoctorOutput(strict) {
|
|
|
150
187
|
missing_optional_read_order: context.optional_read_order.filter((entry) => !entry.exists).map((entry) => entry.path),
|
|
151
188
|
latest_run_exists: context.latest_run.exists,
|
|
152
189
|
},
|
|
190
|
+
command_environment: commandEnvironment,
|
|
153
191
|
effective_policy: context.effective_policy,
|
|
154
192
|
state_policy: context.state_policy,
|
|
155
193
|
blocked_actions: context.blocked_actions,
|
|
@@ -171,6 +209,8 @@ function getDiagnosticLabel(id, lang) {
|
|
|
171
209
|
return t(lang, 'doctor.diagnostic.skillRoutes');
|
|
172
210
|
case 'commands':
|
|
173
211
|
return t(lang, 'doctor.diagnostic.commands');
|
|
212
|
+
case 'environment':
|
|
213
|
+
return t(lang, 'doctor.diagnostic.environment');
|
|
174
214
|
case 'read_order':
|
|
175
215
|
return t(lang, 'doctor.diagnostic.readOrder');
|
|
176
216
|
case 'optional_read_order':
|
|
@@ -46,7 +46,7 @@ export function writeStreamChunk(reporter, stream, chunk) {
|
|
|
46
46
|
reporter.stderr(chunk.toString());
|
|
47
47
|
}
|
|
48
48
|
export function createOutputLimitError(stream, maxOutputBytes) {
|
|
49
|
-
return Object.assign(new Error(`${stream} exceeded max_output_bytes (${maxOutputBytes})`), {
|
|
49
|
+
return Object.assign(new Error(`${stream} exceeded per-stream max_output_bytes (${maxOutputBytes})`), {
|
|
50
50
|
code: OUTPUT_LIMIT_ERROR_CODE,
|
|
51
51
|
});
|
|
52
52
|
}
|
|
@@ -16,6 +16,7 @@ export function assembleRunReceipt(input) {
|
|
|
16
16
|
envPolicy: input.plan.envPolicy,
|
|
17
17
|
envAllowlist: input.plan.envAllowlist,
|
|
18
18
|
timeoutSeconds: input.plan.timeoutSeconds,
|
|
19
|
+
killAfterSeconds: input.plan.killAfterSeconds,
|
|
19
20
|
maxOutputBytes: input.plan.maxOutputBytes,
|
|
20
21
|
successExitCodes: input.plan.successExitCodes,
|
|
21
22
|
exitCode: input.exitCode,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { readFileSync } 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
|
+
import { writeJsonFileInsideWithoutSymlinks } from '../../core/safe-filesystem.js';
|
|
7
8
|
import { createVerifyCompletionVerdict, } from '../../core/completion-verdict.js';
|
|
8
|
-
import {
|
|
9
|
+
import { createStateRunId } from '../../core/atomic-state-write.js';
|
|
9
10
|
import { createExternalEvidenceRisks, } from '../../core/external-evidence.js';
|
|
10
11
|
import { createRepeatedFailureRisks, createVerificationFailureFingerprint, updateRepeatedFailureState, } from '../../core/repeated-failure.js';
|
|
11
12
|
import { countReproEvidenceVerdictEffects, createReproEvidenceRisks, } from '../../core/repro-evidence.js';
|
|
@@ -16,7 +17,7 @@ import { readCommandContract } from '../../core/config-loading.js';
|
|
|
16
17
|
import { DEFAULT_VERIFY_PARALLELISM, parseVerifyArgs } from './verify/args.js';
|
|
17
18
|
import { printUsageError, renderHelp } from '../lib/cli-output.js';
|
|
18
19
|
import { t } from '../lib/i18n.js';
|
|
19
|
-
import {
|
|
20
|
+
import { readLocalCommandEffectGraphs, readLocalPathSurfaces, readLocalSourceAnchorVerdictRisks, } from '../lib/local-index.js';
|
|
20
21
|
import { resolveMustflowRoot } from '../lib/project-root.js';
|
|
21
22
|
const VERIFY_SCHEMA_VERSION = '1';
|
|
22
23
|
const RUN_STATE_DIR = path.join('.mustflow', 'state', 'runs');
|
|
@@ -545,8 +546,7 @@ function createInputFromChanged(projectRoot) {
|
|
|
545
546
|
}
|
|
546
547
|
function writeChangedPlan(projectRoot, inputPath, plan) {
|
|
547
548
|
const planPath = resolvePlanPath(projectRoot, inputPath);
|
|
548
|
-
|
|
549
|
-
writeFileSync(planPath, `${JSON.stringify(plan, null, 2)}\n`, 'utf8');
|
|
549
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, planPath, plan);
|
|
550
550
|
}
|
|
551
551
|
export function planErrorMessageKey(code) {
|
|
552
552
|
switch (code) {
|
|
@@ -578,6 +578,20 @@ function skippedResult(candidate) {
|
|
|
578
578
|
receipt: null,
|
|
579
579
|
};
|
|
580
580
|
}
|
|
581
|
+
function stoppedAfterFailedBatchResult(entry, verificationPlanId) {
|
|
582
|
+
return {
|
|
583
|
+
intent: entry.intent,
|
|
584
|
+
status: 'skipped',
|
|
585
|
+
skipped: true,
|
|
586
|
+
reason: 'stopped_after_failed_batch',
|
|
587
|
+
detail: 'Skipped because an earlier verification batch failed and the schedule failure policy stops before the next batch.',
|
|
588
|
+
exit_code: null,
|
|
589
|
+
verification_plan_id: verificationPlanId,
|
|
590
|
+
receipt_path: null,
|
|
591
|
+
receipt_sha256: null,
|
|
592
|
+
receipt: null,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
581
595
|
function candidateResultKey(candidate) {
|
|
582
596
|
return candidate.intent
|
|
583
597
|
? `intent:${candidate.intent}`
|
|
@@ -670,21 +684,40 @@ async function runVerificationEntriesInParallelChunks(entries, parallelism, lang
|
|
|
670
684
|
}
|
|
671
685
|
return results;
|
|
672
686
|
}
|
|
687
|
+
function verificationResultFailed(result) {
|
|
688
|
+
return (!result.skipped &&
|
|
689
|
+
(result.status === 'failed' ||
|
|
690
|
+
result.status === 'timed_out' ||
|
|
691
|
+
result.status === 'start_failed' ||
|
|
692
|
+
result.status === 'output_limit_exceeded'));
|
|
693
|
+
}
|
|
673
694
|
async function runScheduledVerificationIntents(report, lang, verificationPlanId, scheduledTestTargets, parallelism) {
|
|
674
|
-
if (parallelism <= DEFAULT_VERIFY_PARALLELISM) {
|
|
675
|
-
return runVerificationEntriesSequentially(report.schedule.entries, lang, verificationPlanId, scheduledTestTargets);
|
|
676
|
-
}
|
|
677
695
|
const results = [];
|
|
678
|
-
for (
|
|
696
|
+
for (let batchIndex = 0; batchIndex < report.schedule.batches.length; batchIndex += 1) {
|
|
697
|
+
const batch = report.schedule.batches[batchIndex];
|
|
679
698
|
const entries = entriesForScheduleBatch(report.schedule.entries, batch);
|
|
680
699
|
if (entries.length === 0) {
|
|
681
700
|
continue;
|
|
682
701
|
}
|
|
702
|
+
let batchResults;
|
|
683
703
|
if (entries.length > 1 && entries.every((entry) => entry.parallelEligible)) {
|
|
684
|
-
|
|
704
|
+
batchResults =
|
|
705
|
+
parallelism > DEFAULT_VERIFY_PARALLELISM
|
|
706
|
+
? await runVerificationEntriesInParallelChunks(entries, parallelism, lang, verificationPlanId, scheduledTestTargets)
|
|
707
|
+
: await runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets);
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
batchResults = await runVerificationEntriesSequentially(entries, lang, verificationPlanId, scheduledTestTargets);
|
|
711
|
+
}
|
|
712
|
+
results.push(...batchResults);
|
|
713
|
+
if (!batchResults.some(verificationResultFailed)) {
|
|
685
714
|
continue;
|
|
686
715
|
}
|
|
687
|
-
|
|
716
|
+
const remainingEntries = report.schedule.batches
|
|
717
|
+
.slice(batchIndex + 1)
|
|
718
|
+
.flatMap((remainingBatch) => entriesForScheduleBatch(report.schedule.entries, remainingBatch));
|
|
719
|
+
results.push(...remainingEntries.map((entry) => stoppedAfterFailedBatchResult(entry, verificationPlanId)));
|
|
720
|
+
break;
|
|
688
721
|
}
|
|
689
722
|
return results;
|
|
690
723
|
}
|
|
@@ -1026,7 +1059,6 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1026
1059
|
const statePaths = createVerifyRunStatePaths(projectRoot);
|
|
1027
1060
|
const receipts = [];
|
|
1028
1061
|
const results = [];
|
|
1029
|
-
mkdirSync(statePaths.absoluteIntentDir, { recursive: true });
|
|
1030
1062
|
for (const [index, result] of output.results.entries()) {
|
|
1031
1063
|
let receiptPath = null;
|
|
1032
1064
|
let receiptSha256 = null;
|
|
@@ -1042,7 +1074,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1042
1074
|
};
|
|
1043
1075
|
const receiptContent = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
1044
1076
|
receiptSha256 = hashTextSha256(receiptContent);
|
|
1045
|
-
|
|
1077
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, absoluteReceiptPath, receipt);
|
|
1046
1078
|
}
|
|
1047
1079
|
receipts.push({
|
|
1048
1080
|
intent: result.intent,
|
|
@@ -1148,7 +1180,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1148
1180
|
...(outputWithReceiptPaths.external_checks ? { external_checks: outputWithReceiptPaths.external_checks } : {}),
|
|
1149
1181
|
receipts,
|
|
1150
1182
|
};
|
|
1151
|
-
|
|
1183
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, statePaths.absoluteManifestPath, manifest);
|
|
1152
1184
|
const latest = {
|
|
1153
1185
|
schema_version: '1',
|
|
1154
1186
|
command: 'verify',
|
|
@@ -1169,7 +1201,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1169
1201
|
run_dir: statePaths.runDir,
|
|
1170
1202
|
manifest_path: statePaths.manifestPath,
|
|
1171
1203
|
};
|
|
1172
|
-
|
|
1204
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, path.join(projectRoot, LATEST_RUN_RECEIPT_PATH), latest);
|
|
1173
1205
|
return outputWithReceiptPaths;
|
|
1174
1206
|
}
|
|
1175
1207
|
async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvidence = null, externalChecks = [], parallelism = DEFAULT_VERIFY_PARALLELISM) {
|
|
@@ -1276,14 +1308,11 @@ async function createPlanOnlyOutput(input, projectRoot) {
|
|
|
1276
1308
|
if (!firstEntry) {
|
|
1277
1309
|
return { ...report, verification_plan_id: verificationPlanId, requirements };
|
|
1278
1310
|
}
|
|
1279
|
-
const
|
|
1280
|
-
const graphsByIntent =
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
graphsByIntent.set(entry.intent, await readLocalCommandEffectGraph(projectRoot, entry.intent));
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1311
|
+
const scheduledIntents = Array.from(new Set(report.schedule.entries.map((entry) => entry.intent)));
|
|
1312
|
+
const graphsByIntent = await readLocalCommandEffectGraphs(projectRoot, scheduledIntents);
|
|
1313
|
+
const firstGraph = graphsByIntent.get(firstEntry.intent);
|
|
1314
|
+
if (!firstGraph) {
|
|
1315
|
+
return { ...report, verification_plan_id: verificationPlanId, requirements };
|
|
1287
1316
|
}
|
|
1288
1317
|
return {
|
|
1289
1318
|
...report,
|
package/dist/cli/i18n/en.js
CHANGED
|
@@ -488,6 +488,7 @@ export const enMessages = {
|
|
|
488
488
|
"doctor.diagnostic.validation": "Validation",
|
|
489
489
|
"doctor.diagnostic.skillRoutes": "Skill routes",
|
|
490
490
|
"doctor.diagnostic.commands": "Command specification",
|
|
491
|
+
"doctor.diagnostic.environment": "Environment",
|
|
491
492
|
"doctor.diagnostic.readOrder": "Read order",
|
|
492
493
|
"doctor.diagnostic.optionalReadOrder": "Optional read order",
|
|
493
494
|
"doctor.diagnostic.repoMap": "REPO_MAP.md",
|
package/dist/cli/i18n/es.js
CHANGED
|
@@ -488,6 +488,7 @@ export const esMessages = {
|
|
|
488
488
|
"doctor.diagnostic.validation": "Validación",
|
|
489
489
|
"doctor.diagnostic.skillRoutes": "Rutas de skills",
|
|
490
490
|
"doctor.diagnostic.commands": "Especificación de comandos",
|
|
491
|
+
"doctor.diagnostic.environment": "Entorno",
|
|
491
492
|
"doctor.diagnostic.readOrder": "Orden de lectura",
|
|
492
493
|
"doctor.diagnostic.optionalReadOrder": "Orden de lectura opcional",
|
|
493
494
|
"doctor.diagnostic.repoMap": "REPO_MAP.md",
|
package/dist/cli/i18n/fr.js
CHANGED
|
@@ -488,6 +488,7 @@ export const frMessages = {
|
|
|
488
488
|
"doctor.diagnostic.validation": "Validation",
|
|
489
489
|
"doctor.diagnostic.skillRoutes": "Routage des skills",
|
|
490
490
|
"doctor.diagnostic.commands": "Spécification des commandes",
|
|
491
|
+
"doctor.diagnostic.environment": "Environnement",
|
|
491
492
|
"doctor.diagnostic.readOrder": "Ordre de lecture",
|
|
492
493
|
"doctor.diagnostic.optionalReadOrder": "Ordre de lecture optionnel",
|
|
493
494
|
"doctor.diagnostic.repoMap": "REPO_MAP.md",
|
package/dist/cli/i18n/hi.js
CHANGED
|
@@ -488,6 +488,7 @@ export const hiMessages = {
|
|
|
488
488
|
"doctor.diagnostic.validation": "सत्यापन",
|
|
489
489
|
"doctor.diagnostic.skillRoutes": "स्किल रूट",
|
|
490
490
|
"doctor.diagnostic.commands": "कमांड विनिर्देश",
|
|
491
|
+
"doctor.diagnostic.environment": "एनवायरनमेंट",
|
|
491
492
|
"doctor.diagnostic.readOrder": "पढ़ने का क्रम",
|
|
492
493
|
"doctor.diagnostic.optionalReadOrder": "वैकल्पिक पढ़ने का क्रम",
|
|
493
494
|
"doctor.diagnostic.repoMap": "REPO_MAP.md",
|
package/dist/cli/i18n/ko.js
CHANGED
|
@@ -488,6 +488,7 @@ export const koMessages = {
|
|
|
488
488
|
"doctor.diagnostic.validation": "검증",
|
|
489
489
|
"doctor.diagnostic.skillRoutes": "스킬 라우팅",
|
|
490
490
|
"doctor.diagnostic.commands": "명령",
|
|
491
|
+
"doctor.diagnostic.environment": "실행 환경",
|
|
491
492
|
"doctor.diagnostic.readOrder": "읽기 순서",
|
|
492
493
|
"doctor.diagnostic.optionalReadOrder": "선택적 읽기 순서",
|
|
493
494
|
"doctor.diagnostic.repoMap": "REPO_MAP.md",
|
package/dist/cli/i18n/zh.js
CHANGED
|
@@ -488,6 +488,7 @@ export const zhMessages = {
|
|
|
488
488
|
"doctor.diagnostic.validation": "验证",
|
|
489
489
|
"doctor.diagnostic.skillRoutes": "技能路由",
|
|
490
490
|
"doctor.diagnostic.commands": "命令规范",
|
|
491
|
+
"doctor.diagnostic.environment": "环境",
|
|
491
492
|
"doctor.diagnostic.readOrder": "读取顺序",
|
|
492
493
|
"doctor.diagnostic.optionalReadOrder": "可选读取顺序",
|
|
493
494
|
"doctor.diagnostic.repoMap": "REPO_MAP.md",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { parseGitStatusOutput } from '../../core/change-classification.js';
|
|
3
|
+
const GIT_STATUS_TIMEOUT_MS = 10_000;
|
|
4
|
+
const GIT_STATUS_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
3
5
|
export class GitChangedFilesError extends Error {
|
|
4
6
|
result;
|
|
5
7
|
constructor(result) {
|
|
@@ -9,9 +11,13 @@ export class GitChangedFilesError extends Error {
|
|
|
9
11
|
}
|
|
10
12
|
}
|
|
11
13
|
export function readGitChangedFiles(projectRoot) {
|
|
12
|
-
const result = spawnSync('git', ['status', '--
|
|
14
|
+
const result = spawnSync('git', ['status', '--porcelain=v1', '-z', '--untracked-files=all'], {
|
|
13
15
|
cwd: projectRoot,
|
|
14
16
|
encoding: 'utf8',
|
|
17
|
+
input: '',
|
|
18
|
+
maxBuffer: GIT_STATUS_MAX_BUFFER_BYTES,
|
|
19
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
20
|
+
timeout: GIT_STATUS_TIMEOUT_MS,
|
|
15
21
|
windowsHide: true,
|
|
16
22
|
});
|
|
17
23
|
if (result.status !== 0 || typeof result.stdout !== 'string') {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { isRecord, readCommandContract, readString, readStringArray } from '../command-contract.js';
|
|
@@ -8,6 +8,7 @@ import { collectSourceAnchorIndexRecords, hasHighRiskSourceAnchorRiskTags, } fro
|
|
|
8
8
|
import { listSourceAnchorFiles } from '../../../core/source-anchors.js';
|
|
9
9
|
import { normalizeCommandEffects } from '../../../core/command-effects.js';
|
|
10
10
|
import { listChangeClassificationRuleDescriptors } from '../../../core/change-classification.js';
|
|
11
|
+
import { writeFileInsideWithoutSymlinks } from '../../../core/safe-filesystem.js';
|
|
11
12
|
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';
|
|
12
13
|
import { loadSqlJs } from './sql.js';
|
|
13
14
|
export function getLocalIndexDatabasePath(projectRoot) {
|
|
@@ -331,16 +332,16 @@ function collectSourceAnchorCandidatePaths(projectRoot, sourceConfig) {
|
|
|
331
332
|
excludeGeneratedOrVendor: true,
|
|
332
333
|
});
|
|
333
334
|
}
|
|
334
|
-
function
|
|
335
|
+
function collectFastPreflightIndexedFileRecords(projectRoot, includeSource, sourceConfig) {
|
|
335
336
|
const records = new Map();
|
|
336
337
|
for (const relativePath of getExistingIndexablePaths(projectRoot)) {
|
|
337
|
-
records.set(relativePath,
|
|
338
|
+
records.set(relativePath, readIndexedFileRecord(projectRoot, relativePath, 'workflow'));
|
|
338
339
|
}
|
|
339
340
|
if (includeSource) {
|
|
340
341
|
try {
|
|
341
342
|
for (const sourcePath of collectSourceAnchorCandidatePaths(projectRoot, sourceConfig)) {
|
|
342
343
|
if (!records.has(sourcePath)) {
|
|
343
|
-
records.set(sourcePath,
|
|
344
|
+
records.set(sourcePath, readIndexedFileRecord(projectRoot, sourcePath, 'source_anchor'));
|
|
344
345
|
}
|
|
345
346
|
}
|
|
346
347
|
}
|
|
@@ -349,7 +350,7 @@ function collectFastPreflightIndexedFileMetadataRecords(projectRoot, includeSour
|
|
|
349
350
|
}
|
|
350
351
|
}
|
|
351
352
|
if (existsSync(path.join(projectRoot, ...LATEST_RUN_STATE_RELATIVE_PATH.split('/')))) {
|
|
352
|
-
records.set(LATEST_RUN_STATE_RELATIVE_PATH,
|
|
353
|
+
records.set(LATEST_RUN_STATE_RELATIVE_PATH, readIndexedFileRecord(projectRoot, LATEST_RUN_STATE_RELATIVE_PATH, 'state'));
|
|
353
354
|
}
|
|
354
355
|
return [...records.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
355
356
|
}
|
|
@@ -2053,27 +2054,6 @@ function createStoredLocalIndexResult(projectRoot, databasePath, dryRun, indexMo
|
|
|
2053
2054
|
indexed_paths: readStoredIndexedPaths(database),
|
|
2054
2055
|
};
|
|
2055
2056
|
}
|
|
2056
|
-
function indexedFileMetadataMatch(database, currentFiles) {
|
|
2057
|
-
const rows = queryRows(database, 'SELECT path, source_scope, size_bytes, mtime_ms, parser_version FROM indexed_files ORDER BY path');
|
|
2058
|
-
if (rows.length !== currentFiles.length) {
|
|
2059
|
-
return false;
|
|
2060
|
-
}
|
|
2061
|
-
const currentByPath = new Map(currentFiles.map((file) => [file.path, file]));
|
|
2062
|
-
for (const row of rows) {
|
|
2063
|
-
const storedPath = toSearchString(row.path);
|
|
2064
|
-
const current = currentByPath.get(storedPath);
|
|
2065
|
-
if (!current) {
|
|
2066
|
-
return false;
|
|
2067
|
-
}
|
|
2068
|
-
if (normalizeIndexedFileSourceScope(row.source_scope) !== current.sourceScope ||
|
|
2069
|
-
row.size_bytes !== current.sizeBytes ||
|
|
2070
|
-
row.mtime_ms !== current.mtimeMs ||
|
|
2071
|
-
toSearchString(row.parser_version) !== LOCAL_INDEX_PARSER_VERSION) {
|
|
2072
|
-
return false;
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
return true;
|
|
2076
|
-
}
|
|
2077
2057
|
function indexedFilesMatch(database, currentFiles) {
|
|
2078
2058
|
const rows = queryRows(database, 'SELECT path, source_scope, content_hash, parser_version FROM indexed_files ORDER BY path');
|
|
2079
2059
|
if (rows.length !== currentFiles.length) {
|
|
@@ -2116,7 +2096,7 @@ async function readIncrementalPreflightReuse(SQL, databasePath, projectRoot, cur
|
|
|
2116
2096
|
if (!hasTable(database, 'indexed_files')) {
|
|
2117
2097
|
return { result: null, rebuildReason: 'indexed_files_missing' };
|
|
2118
2098
|
}
|
|
2119
|
-
if (!
|
|
2099
|
+
if (!indexedFilesMatch(database, currentFiles)) {
|
|
2120
2100
|
return { result: null, rebuildReason: 'file_fingerprint_mismatch' };
|
|
2121
2101
|
}
|
|
2122
2102
|
const capabilities = readStoredSearchCapabilities(database);
|
|
@@ -2190,7 +2170,7 @@ export async function createLocalIndex(projectRoot, options = {}) {
|
|
|
2190
2170
|
capabilities = detectLocalSearchCapabilities(capabilityDatabase);
|
|
2191
2171
|
capabilityDatabase.close();
|
|
2192
2172
|
if (incremental) {
|
|
2193
|
-
const preflightFiles =
|
|
2173
|
+
const preflightFiles = collectFastPreflightIndexedFileRecords(projectRoot, includeSource, sourceConfig);
|
|
2194
2174
|
const preflightReuse = await readIncrementalPreflightReuse(SQL, databasePath, projectRoot, preflightFiles, sourceScopeHash, dryRun, indexMode);
|
|
2195
2175
|
if (preflightReuse.result) {
|
|
2196
2176
|
return preflightReuse.result;
|
|
@@ -2223,8 +2203,7 @@ export async function createLocalIndex(projectRoot, options = {}) {
|
|
|
2223
2203
|
const database = new SQL.Database();
|
|
2224
2204
|
createSchema(database, capabilities);
|
|
2225
2205
|
populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, verificationEvidence, indexMode, sourceScopeHash, includeSource, new Date().toISOString());
|
|
2226
|
-
|
|
2227
|
-
writeFileSync(databasePath, database.export());
|
|
2206
|
+
writeFileInsideWithoutSymlinks(projectRoot, databasePath, database.export());
|
|
2228
2207
|
database.close();
|
|
2229
2208
|
}
|
|
2230
2209
|
return {
|
package/dist/cli/lib/repo-map.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { existsSync, lstatSync, readdirSync, realpathSync, statSync
|
|
3
|
+
import { existsSync, lstatSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { toPosixPath } from './filesystem.js';
|
|
6
|
+
import { writeUtf8FileInsideWithoutSymlinks } from '../../core/safe-filesystem.js';
|
|
6
7
|
import { readTomlFile } from './toml.js';
|
|
7
8
|
const DEFAULT_DEPTH = 3;
|
|
8
9
|
const REPO_MAP_DOC_ID = 'repo-map';
|
|
@@ -691,5 +692,5 @@ export function generateRepoMap(projectRoot, options = {}) {
|
|
|
691
692
|
].join('\n');
|
|
692
693
|
}
|
|
693
694
|
export function writeRepoMap(projectRoot, content) {
|
|
694
|
-
|
|
695
|
+
writeUtf8FileInsideWithoutSymlinks(projectRoot, path.join(projectRoot, 'REPO_MAP.md'), content);
|
|
695
696
|
}
|
package/dist/cli/lib/run-plan.js
CHANGED
|
@@ -4,7 +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
|
+
import { DEFAULT_COMMAND_MAX_OUTPUT_BYTES, COMMAND_OUTPUT_LIMIT_SCOPE, MAX_COMMAND_OUTPUT_BYTES, commandMaxOutputBytesLimitMessage, } from '../../core/command-output-limits.js';
|
|
8
8
|
import { t } from './i18n.js';
|
|
9
9
|
function getSuccessExitCodes(intent) {
|
|
10
10
|
const value = intent.success_exit_codes;
|
|
@@ -78,8 +78,10 @@ function readEffectiveMaxOutputBytes(contract, intent) {
|
|
|
78
78
|
readPositiveInteger(contract.defaults, 'max_output_bytes') ??
|
|
79
79
|
DEFAULT_COMMAND_MAX_OUTPUT_BYTES;
|
|
80
80
|
}
|
|
81
|
-
function readEffectiveKillAfterSeconds(contract) {
|
|
82
|
-
return readPositiveInteger(
|
|
81
|
+
function readEffectiveKillAfterSeconds(contract, intent) {
|
|
82
|
+
return readPositiveInteger(intent, 'kill_after_seconds') ??
|
|
83
|
+
readPositiveInteger(contract.defaults, 'kill_after_seconds') ??
|
|
84
|
+
5;
|
|
83
85
|
}
|
|
84
86
|
function getMaxOutputBytesLimitDetail(contract, intent) {
|
|
85
87
|
const intentValue = readPositiveInteger(intent, 'max_output_bytes');
|
|
@@ -106,7 +108,7 @@ function readRunIntentMetadata(contract, intent) {
|
|
|
106
108
|
kind: readString(intent, 'kind') ?? null,
|
|
107
109
|
configuredCwd,
|
|
108
110
|
timeoutSeconds: readPositiveInteger(intent, 'timeout_seconds') ?? null,
|
|
109
|
-
killAfterSeconds: readEffectiveKillAfterSeconds(contract),
|
|
111
|
+
killAfterSeconds: readEffectiveKillAfterSeconds(contract, intent),
|
|
110
112
|
maxOutputBytes: readEffectiveMaxOutputBytes(contract, intent),
|
|
111
113
|
successExitCodes: getSuccessExitCodes(intent),
|
|
112
114
|
commandArgv,
|
|
@@ -284,7 +286,9 @@ export function createRunPreview(plan, previewMode) {
|
|
|
284
286
|
cwd: plan.relativeCwd,
|
|
285
287
|
resolved_cwd: plan.cwd,
|
|
286
288
|
timeout_seconds: plan.timeoutSeconds,
|
|
289
|
+
kill_after_seconds: plan.killAfterSeconds,
|
|
287
290
|
max_output_bytes: plan.maxOutputBytes,
|
|
291
|
+
max_output_bytes_scope: plan.maxOutputBytes === null ? null : COMMAND_OUTPUT_LIMIT_SCOPE,
|
|
288
292
|
mode: plan.mode,
|
|
289
293
|
argv: plan.commandArgv,
|
|
290
294
|
resolved_argv: plan.argvCommand,
|
|
@@ -46,14 +46,36 @@ function uniqueSorted(values) {
|
|
|
46
46
|
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
47
47
|
}
|
|
48
48
|
function toPosixPath(value) {
|
|
49
|
-
return value.
|
|
49
|
+
return value.replaceAll('\\', '/');
|
|
50
50
|
}
|
|
51
51
|
export function normalizeStatusPath(value) {
|
|
52
|
-
const pathText = toPosixPath(value);
|
|
52
|
+
const pathText = toPosixPath(value.trim());
|
|
53
53
|
const renameTarget = pathText.includes(' -> ') ? (pathText.split(' -> ').pop() ?? pathText) : pathText;
|
|
54
54
|
return renameTarget.replace(/^"|"$/gu, '');
|
|
55
55
|
}
|
|
56
|
+
function normalizePorcelainStatusPath(value) {
|
|
57
|
+
return toPosixPath(value);
|
|
58
|
+
}
|
|
59
|
+
function parseGitPorcelainStatusOutput(output) {
|
|
60
|
+
const paths = [];
|
|
61
|
+
const parts = output.split('\0').filter((part) => part.length > 0);
|
|
62
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
63
|
+
const entry = parts[index] ?? '';
|
|
64
|
+
const status = entry.slice(0, 2);
|
|
65
|
+
const filePath = normalizePorcelainStatusPath(entry.slice(3));
|
|
66
|
+
if (filePath.length > 0) {
|
|
67
|
+
paths.push(filePath);
|
|
68
|
+
}
|
|
69
|
+
if (status.includes('R') || status.includes('C')) {
|
|
70
|
+
index += 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return uniqueSorted(paths);
|
|
74
|
+
}
|
|
56
75
|
export function parseGitStatusOutput(output) {
|
|
76
|
+
if (output.includes('\0')) {
|
|
77
|
+
return parseGitPorcelainStatusOutput(output);
|
|
78
|
+
}
|
|
57
79
|
const paths = output
|
|
58
80
|
.split(/\r?\n/u)
|
|
59
81
|
.map((line) => line.slice(3))
|
|
@@ -4,7 +4,7 @@ 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
|
|
7
|
+
['mustflow.command_contract.max_output_bytes_exceeds_limit', /^\[commands\.(?:defaults|intents\.[^\]]+)\]\.max_output_bytes must be less than or equal to \d+ per output stream$/u],
|
|
8
8
|
['mustflow.command_contract.oneshot_stdin_not_closed', /^Oneshot intent [^\s]+ must set stdin = "closed"$/u],
|
|
9
9
|
['mustflow.command_contract.long_running_agent_allowed', /^Long-running intent [^\s]+ must not use run_policy = "agent_allowed"$/u],
|
|
10
10
|
['mustflow.command_contract.executable_source_missing', /^Configured intent [^\s]+ must define argv or mode = "shell" with cmd$/u],
|
|
@@ -125,6 +125,12 @@ export function commandIntentBlockedCommandPattern(intent) {
|
|
|
125
125
|
detail: 'Shell command contains a blocked long-running or background pattern.',
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
+
if (intent.mode === 'shell' && typeof intent.cmd === 'string' && commandTextHasLongRunningPattern(intent.cmd)) {
|
|
129
|
+
return {
|
|
130
|
+
code: 'long_running_command_pattern',
|
|
131
|
+
detail: `Shell command contains a blocked long-running pattern: ${intent.cmd}.`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
128
134
|
const argv = readStringArray(intent, 'argv');
|
|
129
135
|
if (!argv) {
|
|
130
136
|
return null;
|