mustflow 2.18.21 → 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 +7 -8
- 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/local-index/index.js +3 -3
- package/dist/cli/lib/repo-map.js +3 -2
- package/dist/cli/lib/run-plan.js +8 -4
- package/dist/core/check-issues.js +1 -1
- 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 +60 -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';
|
|
@@ -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) {
|
|
@@ -1059,7 +1059,6 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1059
1059
|
const statePaths = createVerifyRunStatePaths(projectRoot);
|
|
1060
1060
|
const receipts = [];
|
|
1061
1061
|
const results = [];
|
|
1062
|
-
mkdirSync(statePaths.absoluteIntentDir, { recursive: true });
|
|
1063
1062
|
for (const [index, result] of output.results.entries()) {
|
|
1064
1063
|
let receiptPath = null;
|
|
1065
1064
|
let receiptSha256 = null;
|
|
@@ -1075,7 +1074,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1075
1074
|
};
|
|
1076
1075
|
const receiptContent = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
1077
1076
|
receiptSha256 = hashTextSha256(receiptContent);
|
|
1078
|
-
|
|
1077
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, absoluteReceiptPath, receipt);
|
|
1079
1078
|
}
|
|
1080
1079
|
receipts.push({
|
|
1081
1080
|
intent: result.intent,
|
|
@@ -1181,7 +1180,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1181
1180
|
...(outputWithReceiptPaths.external_checks ? { external_checks: outputWithReceiptPaths.external_checks } : {}),
|
|
1182
1181
|
receipts,
|
|
1183
1182
|
};
|
|
1184
|
-
|
|
1183
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, statePaths.absoluteManifestPath, manifest);
|
|
1185
1184
|
const latest = {
|
|
1186
1185
|
schema_version: '1',
|
|
1187
1186
|
command: 'verify',
|
|
@@ -1202,7 +1201,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
1202
1201
|
run_dir: statePaths.runDir,
|
|
1203
1202
|
manifest_path: statePaths.manifestPath,
|
|
1204
1203
|
};
|
|
1205
|
-
|
|
1204
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, path.join(projectRoot, LATEST_RUN_RECEIPT_PATH), latest);
|
|
1206
1205
|
return outputWithReceiptPaths;
|
|
1207
1206
|
}
|
|
1208
1207
|
async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvidence = null, externalChecks = [], parallelism = DEFAULT_VERIFY_PARALLELISM) {
|
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,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) {
|
|
@@ -2202,8 +2203,7 @@ export async function createLocalIndex(projectRoot, options = {}) {
|
|
|
2202
2203
|
const database = new SQL.Database();
|
|
2203
2204
|
createSchema(database, capabilities);
|
|
2204
2205
|
populateDatabase(database, capabilities, documents, skills, skillRoutes, commandIntents, sourceAnchors, indexedFiles, verificationEvidence, indexMode, sourceScopeHash, includeSource, new Date().toISOString());
|
|
2205
|
-
|
|
2206
|
-
writeFileSync(databasePath, database.export());
|
|
2206
|
+
writeFileInsideWithoutSymlinks(projectRoot, databasePath, database.export());
|
|
2207
2207
|
database.close();
|
|
2208
2208
|
}
|
|
2209
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,
|
|
@@ -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],
|
|
@@ -161,6 +161,7 @@ function validateCommandIntent(intentName, intent, issues) {
|
|
|
161
161
|
validateAllowedStringField(intent, 'env_policy', `[commands.intents.${intentName}].env_policy`, COMMAND_ENV_POLICIES, issues);
|
|
162
162
|
validateStringArrayField(intent, 'env_allowlist', `[commands.intents.${intentName}].env_allowlist`, issues);
|
|
163
163
|
validateMaxOutputBytesField(intent, 'max_output_bytes', `[commands.intents.${intentName}].max_output_bytes`, issues);
|
|
164
|
+
validatePositiveIntegerField(intent, 'kill_after_seconds', `[commands.intents.${intentName}].kill_after_seconds`, issues);
|
|
164
165
|
validateCommandIntentSelection(intentName, intent, issues);
|
|
165
166
|
if (intent.status !== 'configured') {
|
|
166
167
|
return;
|
|
@@ -223,10 +224,10 @@ function getEffectiveCommandEnvPolicy(defaults, intent) {
|
|
|
223
224
|
}
|
|
224
225
|
return { policy: DEFAULT_COMMAND_ENV_POLICY, source: 'implicit' };
|
|
225
226
|
}
|
|
226
|
-
function
|
|
227
|
-
const
|
|
227
|
+
export function findCommandEnvInheritanceWarnings(commandsToml) {
|
|
228
|
+
const warnings = [];
|
|
228
229
|
if (!commandsToml || !isRecord(commandsToml.intents)) {
|
|
229
|
-
return
|
|
230
|
+
return warnings;
|
|
230
231
|
}
|
|
231
232
|
const defaults = isRecord(commandsToml.defaults) ? commandsToml.defaults : undefined;
|
|
232
233
|
for (const [intentName, intent] of Object.entries(commandsToml.intents)) {
|
|
@@ -237,13 +238,26 @@ function validateCommandEnvInheritanceWarnings(commandsToml) {
|
|
|
237
238
|
if (envPolicy.policy !== 'inherit') {
|
|
238
239
|
continue;
|
|
239
240
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
241
|
+
warnings.push({
|
|
242
|
+
intentName,
|
|
243
|
+
source: envPolicy.source,
|
|
244
|
+
network: intent.network === true,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return warnings;
|
|
248
|
+
}
|
|
249
|
+
function formatCommandEnvInheritanceWarning(warning) {
|
|
250
|
+
const networkScope = warning.network ? ' with network = true' : '';
|
|
251
|
+
const migration = 'set env_policy = "minimal" or "allowlist" unless broad host state is required';
|
|
252
|
+
if (warning.source === 'implicit') {
|
|
253
|
+
return `configured agent-runnable intent ${warning.intentName} implicitly inherits the host environment${networkScope}; ${migration}`;
|
|
254
|
+
}
|
|
255
|
+
return `configured agent-runnable intent ${warning.intentName} uses env_policy = "inherit"${networkScope}; ${migration}`;
|
|
256
|
+
}
|
|
257
|
+
function validateCommandEnvInheritanceWarnings(commandsToml) {
|
|
258
|
+
const issues = [];
|
|
259
|
+
for (const warning of findCommandEnvInheritanceWarnings(commandsToml)) {
|
|
260
|
+
issues.push(commandContractWarning(formatCommandEnvInheritanceWarning(warning)));
|
|
247
261
|
}
|
|
248
262
|
return issues;
|
|
249
263
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const DEFAULT_COMMAND_MAX_OUTPUT_BYTES = 1_048_576;
|
|
2
2
|
export const MAX_COMMAND_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
3
|
+
export const COMMAND_OUTPUT_LIMIT_SCOPE = 'per_stream';
|
|
3
4
|
export function commandMaxOutputBytesLimitMessage(label) {
|
|
4
|
-
return `${label} must be less than or equal to ${MAX_COMMAND_OUTPUT_BYTES}`;
|
|
5
|
+
return `${label} must be less than or equal to ${MAX_COMMAND_OUTPUT_BYTES} per output stream`;
|
|
5
6
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { readFileInsideWithoutSymlinks, writeFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
4
5
|
const GITATTRIBUTES_PATH = '.gitattributes';
|
|
5
6
|
function toPosixPath(value) {
|
|
6
7
|
return value.split(path.sep).join('/');
|
|
@@ -10,7 +11,7 @@ function hasLfPolicy(projectRoot) {
|
|
|
10
11
|
if (!existsSync(attributesPath)) {
|
|
11
12
|
return false;
|
|
12
13
|
}
|
|
13
|
-
const content =
|
|
14
|
+
const content = readFileInsideWithoutSymlinks(projectRoot, attributesPath).toString('utf8');
|
|
14
15
|
return /^\*\s+.*(?:^|\s)eol=lf(?:\s|$)/imu.test(content);
|
|
15
16
|
}
|
|
16
17
|
function gitList(projectRoot, args) {
|
|
@@ -107,14 +108,21 @@ export function inspectLineEndings(projectRoot, mode, options = {}) {
|
|
|
107
108
|
if (!existsSync(absolutePath)) {
|
|
108
109
|
continue;
|
|
109
110
|
}
|
|
110
|
-
|
|
111
|
+
let buffer;
|
|
112
|
+
try {
|
|
113
|
+
buffer = readFileInsideWithoutSymlinks(root, absolutePath);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
issues.push(error instanceof Error ? error.message : String(error));
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
111
119
|
const lineEnding = detectLineEnding(buffer);
|
|
112
120
|
const wouldChange = policy === 'lf' && (lineEnding === 'crlf' || lineEnding === 'mixed');
|
|
113
121
|
if (!wouldChange) {
|
|
114
122
|
continue;
|
|
115
123
|
}
|
|
116
124
|
if (canApply) {
|
|
117
|
-
|
|
125
|
+
writeFileInsideWithoutSymlinks(root, absolutePath, normalizeLf(buffer));
|
|
118
126
|
changedFiles.push(toPosixPath(relativePath));
|
|
119
127
|
}
|
|
120
128
|
nonCompliantFiles.push({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { existsSync,
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { writeJsonFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
4
5
|
export const REPEATED_FAILURE_STATE_PATH = '.mustflow/state/repeated-failures.json';
|
|
5
6
|
export const REPEATED_FAILURE_STATE_LIMIT = 50;
|
|
6
7
|
const UNRESOLVED_VERIFY_STATUSES = new Set(['failed', 'blocked', 'partial']);
|
|
@@ -59,8 +60,7 @@ function readRepeatedFailureState(projectRoot) {
|
|
|
59
60
|
}
|
|
60
61
|
function writeRepeatedFailureState(projectRoot, state) {
|
|
61
62
|
const statePath = repeatedFailureStatePath(projectRoot);
|
|
62
|
-
|
|
63
|
-
writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
63
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, statePath, state);
|
|
64
64
|
}
|
|
65
65
|
export function createVerificationFailureFingerprint(input) {
|
|
66
66
|
const failedIntents = normalizeStrings(input.failedIntents);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { writeJsonFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
3
4
|
const PERFORMANCE_HISTORY_SCHEMA_VERSION = '1';
|
|
4
5
|
const PERFORMANCE_HISTORY_DIR = path.join('.mustflow', 'state', 'perf');
|
|
5
6
|
const PERFORMANCE_SAMPLES_FILE = 'samples.json';
|
|
@@ -297,9 +298,8 @@ export function recordRunPerformanceHistory(projectRoot, receipt) {
|
|
|
297
298
|
const samples = enforceSizeLimit(pruneSamples([...readSamples(samplesPath), sample], sample.observed_day), sample.observed_day);
|
|
298
299
|
const samplesFile = createSamplesFile(samples);
|
|
299
300
|
const summaryFile = createSummary(samples, sample.observed_day);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
writeFileSync(summaryPath, serialize(summaryFile));
|
|
301
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, samplesPath, samplesFile);
|
|
302
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, summaryPath, summaryFile);
|
|
303
303
|
}
|
|
304
304
|
catch {
|
|
305
305
|
// Performance history is a local optimization hint. A write failure must not affect command execution.
|
package/dist/core/run-profile.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
1
|
import { performance } from 'node:perf_hooks';
|
|
3
2
|
import path from 'node:path';
|
|
3
|
+
import { writeJsonFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
4
4
|
const RUN_PROFILE_SCHEMA_VERSION = '1';
|
|
5
5
|
const RUN_PROFILE_ENV = 'MUSTFLOW_RUN_PROFILE';
|
|
6
6
|
const RUN_PROFILE_DIR = path.join('.mustflow', 'state', 'runs');
|
|
@@ -75,8 +75,7 @@ export class RunProfiler {
|
|
|
75
75
|
profile_path: getProfileRelativePath(),
|
|
76
76
|
};
|
|
77
77
|
const profilePath = path.join(input.projectRoot, RUN_PROFILE_DIR, LATEST_RUN_PROFILE);
|
|
78
|
-
|
|
79
|
-
writeFileSync(profilePath, `${JSON.stringify(profile, null, 2)}\n`);
|
|
78
|
+
writeJsonFileInsideWithoutSymlinks(input.projectRoot, profilePath, profile);
|
|
80
79
|
}
|
|
81
80
|
recordPhase(name, startedAtMs) {
|
|
82
81
|
this.phases.push({
|
package/dist/core/run-receipt.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { createStateRunId } from './atomic-state-write.js';
|
|
4
|
+
import { COMMAND_OUTPUT_LIMIT_SCOPE } from './command-output-limits.js';
|
|
4
5
|
import { decodeUtf8Tail } from './bounded-output.js';
|
|
5
6
|
import { DEFAULT_RUN_RECEIPT_TAIL_BYTES } from './retention-policy.js';
|
|
7
|
+
import { writeJsonFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
6
8
|
import { redactSecretLikeText } from './secret-redaction.js';
|
|
7
9
|
const RUN_RECEIPT_SCHEMA_VERSION = '1';
|
|
8
10
|
const RUN_RECEIPT_DIR = path.join('.mustflow', 'state', 'runs');
|
|
@@ -111,6 +113,7 @@ function createPerformanceSummary(input) {
|
|
|
111
113
|
cwd: input.cwd,
|
|
112
114
|
env_allowlist: input.envAllowlist,
|
|
113
115
|
env_policy: input.envPolicy,
|
|
116
|
+
kill_after_seconds: input.killAfterSeconds,
|
|
114
117
|
lifecycle: input.lifecycle,
|
|
115
118
|
max_output_bytes: input.maxOutputBytes,
|
|
116
119
|
mode: input.mode,
|
|
@@ -148,8 +151,10 @@ function createPerformanceSummary(input) {
|
|
|
148
151
|
output_summary: {
|
|
149
152
|
stdout_bytes: input.stdout.bytes,
|
|
150
153
|
stderr_bytes: input.stderr.bytes,
|
|
154
|
+
total_bytes: input.stdout.bytes + input.stderr.bytes,
|
|
151
155
|
stdout_truncated: input.stdout.truncated,
|
|
152
156
|
stderr_truncated: input.stderr.truncated,
|
|
157
|
+
max_output_bytes_scope: COMMAND_OUTPUT_LIMIT_SCOPE,
|
|
153
158
|
},
|
|
154
159
|
result_summary: {
|
|
155
160
|
status: input.status,
|
|
@@ -235,7 +240,9 @@ export function createRunReceipt(input) {
|
|
|
235
240
|
env_policy: input.envPolicy,
|
|
236
241
|
env_allowlist: input.envAllowlist,
|
|
237
242
|
timeout_seconds: input.timeoutSeconds,
|
|
243
|
+
kill_after_seconds: input.killAfterSeconds,
|
|
238
244
|
max_output_bytes: input.maxOutputBytes,
|
|
245
|
+
max_output_bytes_scope: COMMAND_OUTPUT_LIMIT_SCOPE,
|
|
239
246
|
success_exit_codes: input.successExitCodes,
|
|
240
247
|
exit_code: input.exitCode,
|
|
241
248
|
signal: input.signal,
|
|
@@ -260,6 +267,7 @@ export function createRunReceipt(input) {
|
|
|
260
267
|
envPolicy: input.envPolicy,
|
|
261
268
|
envAllowlist: input.envAllowlist,
|
|
262
269
|
timeoutSeconds: input.timeoutSeconds,
|
|
270
|
+
killAfterSeconds: input.killAfterSeconds,
|
|
263
271
|
maxOutputBytes: input.maxOutputBytes,
|
|
264
272
|
successExitCodes: input.successExitCodes,
|
|
265
273
|
exitCode: input.exitCode,
|
|
@@ -285,6 +293,6 @@ export function writeRunReceipt(projectRoot, receipt) {
|
|
|
285
293
|
if (relativeToRunDir.startsWith('..') || path.isAbsolute(relativeToRunDir)) {
|
|
286
294
|
throw new Error(`Run receipt path must stay inside ${RUN_RECEIPT_DIR}`);
|
|
287
295
|
}
|
|
288
|
-
|
|
289
|
-
|
|
296
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, receiptPath, receipt);
|
|
297
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, latestPath, receipt);
|
|
290
298
|
}
|