mustflow 2.103.21 → 2.103.22
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/run/execution.js +1 -1
- package/dist/cli/commands/verify.js +4 -1
- package/dist/core/retention-policy.js +4 -2
- package/dist/core/run-receipt-state.js +247 -0
- package/dist/core/run-receipt.js +5 -1
- package/package.json +1 -1
- package/templates/default/common/.mustflow/config/mustflow.toml +2 -2
- package/templates/default/i18n.toml +1 -1
- package/templates/default/locales/en/.mustflow/skills/clarifying-question-gate/SKILL.md +30 -5
- package/templates/default/manifest.toml +1 -1
package/README.md
CHANGED
|
@@ -142,7 +142,7 @@ mustflow installs and validates an agent workflow for user projects.
|
|
|
142
142
|
- Runs only allowed one-shot commands within a timeout via `mf run <intent>` or `mf verify` when the selected intent is runnable.
|
|
143
143
|
- Records blockers, contradictions, verification gaps, and remaining risks as a structured conflict ledger in verify, evidence, and dashboard reports.
|
|
144
144
|
- Stores bounded failure replay capsules for failed `mf verify` runs so future agents can reproduce the intent, receipt, command fingerprint, and changed-file state without copying raw command output.
|
|
145
|
-
- Writes command receipts under `.mustflow/state/runs/run
|
|
145
|
+
- Writes bounded command receipts under `.mustflow/state/runs/run-*`, atomically updates `.mustflow/state/runs/latest.json`, and rebuilds `.mustflow/state/runs/latest.index.json` for recent retained runs.
|
|
146
146
|
- Generates a concise repository navigation map, `REPO_MAP.md`, with `mf map`.
|
|
147
147
|
- Indexes and searches mustflow docs, skills, skill routes, command rules, command-effect locks, file fingerprints, and opt-in source anchor metadata with SQLite via `mf index` and `mf search`. The local SQLite file is a rebuildable lookup cache, not a memory store, audit log, command transcript store, command-authority source, or source-content database.
|
|
148
148
|
- Tracks agent-created or agent-modified documentation needing prose review with `mf docs review`.
|
|
@@ -373,7 +373,7 @@ Command environments remove the project-local `node_modules/.bin` path from `PAT
|
|
|
373
373
|
|
|
374
374
|
Use `mf verify --reason <event> --plan-only --json` to inspect matching verification intents, command eligibility, risk-priced evidence requirements, remaining gaps, and missing runnable coverage without executing commands. Use `mf run <intent> --dry-run --json` to inspect one resolved command intent without spawning a process or writing a run receipt. Plan-only verification includes a `decision_graph` that connects changed surfaces, classification reasons, command candidates, eligibility checks, effects, and gaps. When `.mustflow/cache/mustflow.sqlite` is fresh, scheduled entries also include read-only `effectGraph` metadata for write locks and lock conflicts. These graph rows are marked `explanation_only` and never grant command authority; `.mustflow/config/commands.toml` remains the only runnable command source.
|
|
375
375
|
|
|
376
|
-
Each executed command run writes a run record under `.mustflow/state/runs/run
|
|
376
|
+
Each executed command run writes a run record under `.mustflow/state/runs/run-*`, atomically updates `.mustflow/state/runs/latest.json`, and rebuilds `.mustflow/state/runs/latest.index.json` from retained `run-*` and `verify-*` directories. The record includes the intent name, working directory, timeout, exit code, timeout status, and the tail of stdout and stderr. `latest.json` is a root-scoped convenience pointer, not session-scoped proof; in multi-agent or multi-terminal workflows, use the per-run `receipt_path`, the retained index, or `mf run <intent> --json` output as the evidence for a specific run.
|
|
377
377
|
|
|
378
378
|
## Language and profiles
|
|
379
379
|
|
|
@@ -290,7 +290,7 @@ export async function executeRunCommand(request, reporter, lang = 'en', options
|
|
|
290
290
|
stderrTailBytes: runReceiptPolicy.stderrTailBytes,
|
|
291
291
|
}));
|
|
292
292
|
if (options.writeLatestReceipt !== false) {
|
|
293
|
-
profiler.measure('receipt_write', () => writeRunReceipt(projectRoot, receipt));
|
|
293
|
+
profiler.measure('receipt_write', () => writeRunReceipt(projectRoot, receipt, runReceiptPolicy));
|
|
294
294
|
}
|
|
295
295
|
if (options.recordPerformanceHistory !== false) {
|
|
296
296
|
profiler.measure('performance_history_write', () => recordRunPerformanceHistory(projectRoot, receipt));
|
|
@@ -15,7 +15,9 @@ import { riskPricedEvidenceRiskCount, } from '../../core/risk-priced-evidence.js
|
|
|
15
15
|
import { countValidationRatchetVerdictEffects, createValidationRatchetRisks, } from '../../core/validation-ratchet.js';
|
|
16
16
|
import { finishRunWriteBatchTracking, startRunWriteBatchTracking, } from '../../core/run-write-drift.js';
|
|
17
17
|
import { createCommandEnv } from '../../core/command-env.js';
|
|
18
|
-
import { readCommandContract } from '../../core/config-loading.js';
|
|
18
|
+
import { readCommandContract, readMustflowConfigIfExists } from '../../core/config-loading.js';
|
|
19
|
+
import { resolveRunReceiptRetentionPolicy } from '../../core/retention-policy.js';
|
|
20
|
+
import { updateRunReceiptState } from '../../core/run-receipt-state.js';
|
|
19
21
|
import { evaluateCommandPreconditions, } from '../../core/command-preconditions.js';
|
|
20
22
|
import { DEFAULT_VERIFY_PARALLELISM, parseVerifyArgs, resolveVerifyParallelism, } from './verify/args.js';
|
|
21
23
|
import { createInputFromChanged, createSyntheticClassificationReport, planErrorMessageKey, readInputFromClassificationReport, resolveVerifyInputPath, writeChangedPlan, } from './verify/input.js';
|
|
@@ -764,6 +766,7 @@ function writeVerifyRunReceipts(projectRoot, output, report, sourceAnchorRisks,
|
|
|
764
766
|
manifest_path: statePaths.manifestPath,
|
|
765
767
|
};
|
|
766
768
|
writeJsonFileInsideWithoutSymlinks(projectRoot, resolveLatestVerifyRunReceiptPath(projectRoot), latest);
|
|
769
|
+
updateRunReceiptState(projectRoot, resolveRunReceiptRetentionPolicy(readMustflowConfigIfExists(projectRoot)));
|
|
767
770
|
return outputWithReceiptPaths;
|
|
768
771
|
}
|
|
769
772
|
async function createVerifyOutput(input, planSource, projectRoot, lang, reproEvidence = null, externalChecks = [], parallelism = DEFAULT_VERIFY_PARALLELISM, parallelismReport = null) {
|
|
@@ -5,6 +5,8 @@ export const DEFAULT_RETENTION_LIMITS = {
|
|
|
5
5
|
repoMapMaxFileKb: 128,
|
|
6
6
|
repoMapFailIfLarger: true,
|
|
7
7
|
runReceiptMaxFileKb: 128,
|
|
8
|
+
runReceiptMaxItems: 50,
|
|
9
|
+
runReceiptMaxTotalMb: 10,
|
|
8
10
|
contextMaxFileKb: 8,
|
|
9
11
|
knowledgeMaxFileKb: 128,
|
|
10
12
|
};
|
|
@@ -49,8 +51,8 @@ export function resolveRunReceiptRetentionPolicy(mustflowToml) {
|
|
|
49
51
|
return {
|
|
50
52
|
store: readString(runReceipts ?? {}, 'store') ?? 'repo_local_ignored',
|
|
51
53
|
maxFileKb: readPositiveIntegerWithDefault(runReceipts, 'max_file_kb', DEFAULT_RETENTION_LIMITS.runReceiptMaxFileKb),
|
|
52
|
-
maxItems: readPositiveIntegerWithDefault(runReceipts, 'max_items',
|
|
53
|
-
maxTotalMb: readPositiveIntegerWithDefault(runReceipts, 'max_total_mb',
|
|
54
|
+
maxItems: readPositiveIntegerWithDefault(runReceipts, 'max_items', DEFAULT_RETENTION_LIMITS.runReceiptMaxItems),
|
|
55
|
+
maxTotalMb: readPositiveIntegerWithDefault(runReceipts, 'max_total_mb', DEFAULT_RETENTION_LIMITS.runReceiptMaxTotalMb),
|
|
54
56
|
stdoutTailBytes: readPositiveIntegerWithDefault(runReceipts, 'keep_stdout_tail_bytes', DEFAULT_RUN_RECEIPT_TAIL_BYTES),
|
|
55
57
|
stderrTailBytes: readPositiveIntegerWithDefault(runReceipts, 'keep_stderr_tail_bytes', DEFAULT_RUN_RECEIPT_TAIL_BYTES),
|
|
56
58
|
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureInside, writeJsonFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
4
|
+
const RUN_RECEIPT_SCHEMA_VERSION = '1';
|
|
5
|
+
const RUN_RECEIPT_DIR = path.join('.mustflow', 'state', 'runs');
|
|
6
|
+
const LATEST_RUN_RECEIPT_INDEX = 'latest.index.json';
|
|
7
|
+
const STATE_DIR_PREFIXES = ['run-', 'verify-'];
|
|
8
|
+
const MIN_RETAINED_RUN_DIRS = 1;
|
|
9
|
+
function toPosixPath(value) {
|
|
10
|
+
return value.split(path.sep).join('/');
|
|
11
|
+
}
|
|
12
|
+
function stateRunsDir(projectRoot) {
|
|
13
|
+
return path.join(projectRoot, RUN_RECEIPT_DIR);
|
|
14
|
+
}
|
|
15
|
+
function latestIndexPath(projectRoot) {
|
|
16
|
+
return path.join(stateRunsDir(projectRoot), LATEST_RUN_RECEIPT_INDEX);
|
|
17
|
+
}
|
|
18
|
+
function runStateKind(name) {
|
|
19
|
+
return STATE_DIR_PREFIXES.find((prefix) => name.startsWith(prefix)) ?? null;
|
|
20
|
+
}
|
|
21
|
+
function isMissingPathError(error) {
|
|
22
|
+
return error instanceof Error && 'code' in error && error.code === 'ENOENT';
|
|
23
|
+
}
|
|
24
|
+
function getStateRunSortKey(name) {
|
|
25
|
+
const match = /^(?:run|verify)-(.+)$/u.exec(name);
|
|
26
|
+
return match?.[1] ?? name;
|
|
27
|
+
}
|
|
28
|
+
function fileSizeBytes(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
const stats = lstatSync(filePath);
|
|
31
|
+
if (stats.isSymbolicLink()) {
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
if (stats.isFile()) {
|
|
35
|
+
return stats.size;
|
|
36
|
+
}
|
|
37
|
+
if (!stats.isDirectory()) {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
return readdirSync(filePath).reduce((total, entry) => total + fileSizeBytes(path.join(filePath, entry)), 0);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (isMissingPathError(error)) {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function readRunStateDirectories(projectRoot) {
|
|
50
|
+
const receiptDir = stateRunsDir(projectRoot);
|
|
51
|
+
if (!existsSync(receiptDir)) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
return readdirSync(receiptDir)
|
|
55
|
+
.map((name) => {
|
|
56
|
+
const kind = runStateKind(name);
|
|
57
|
+
if (!kind) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const absolutePath = path.join(receiptDir, name);
|
|
61
|
+
const relativePath = toPosixPath(path.join(RUN_RECEIPT_DIR, name));
|
|
62
|
+
const stats = lstatSync(absolutePath);
|
|
63
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
absolutePath,
|
|
68
|
+
relativePath,
|
|
69
|
+
name,
|
|
70
|
+
kind,
|
|
71
|
+
sortKey: getStateRunSortKey(name),
|
|
72
|
+
sizeBytes: fileSizeBytes(absolutePath),
|
|
73
|
+
};
|
|
74
|
+
})
|
|
75
|
+
.filter((entry) => Boolean(entry))
|
|
76
|
+
.sort(compareRunStateDirectoriesDescending);
|
|
77
|
+
}
|
|
78
|
+
function compareRunStateDirectoriesDescending(left, right) {
|
|
79
|
+
const bySortKey = right.sortKey.localeCompare(left.sortKey);
|
|
80
|
+
if (bySortKey !== 0) {
|
|
81
|
+
return bySortKey;
|
|
82
|
+
}
|
|
83
|
+
return right.name.localeCompare(left.name);
|
|
84
|
+
}
|
|
85
|
+
function removeRunStateDirectory(projectRoot, directory) {
|
|
86
|
+
const runsDir = stateRunsDir(projectRoot);
|
|
87
|
+
ensureInside(runsDir, directory.absolutePath);
|
|
88
|
+
const relativeToRunsDir = path.relative(runsDir, directory.absolutePath);
|
|
89
|
+
if (relativeToRunsDir.startsWith('..') || path.isAbsolute(relativeToRunsDir) || runStateKind(path.basename(directory.absolutePath)) === null) {
|
|
90
|
+
throw new Error(`Refusing to remove unexpected run receipt path: ${directory.relativePath}`);
|
|
91
|
+
}
|
|
92
|
+
const stats = lstatSync(directory.absolutePath);
|
|
93
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
94
|
+
throw new Error(`Refusing to remove non-directory run receipt path: ${directory.relativePath}`);
|
|
95
|
+
}
|
|
96
|
+
rmSync(directory.absolutePath, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
function applyRunReceiptRetention(projectRoot, policy) {
|
|
99
|
+
const directories = readRunStateDirectories(projectRoot);
|
|
100
|
+
const maxItems = Math.max(MIN_RETAINED_RUN_DIRS, policy.maxItems);
|
|
101
|
+
const maxTotalBytes = Math.max(1, policy.maxTotalMb) * 1024 * 1024;
|
|
102
|
+
const kept = new Set(directories.slice(0, maxItems).map((directory) => directory.name));
|
|
103
|
+
let totalBytes = directories
|
|
104
|
+
.filter((directory) => kept.has(directory.name))
|
|
105
|
+
.reduce((total, directory) => total + directory.sizeBytes, 0);
|
|
106
|
+
for (const directory of directories.slice(maxItems)) {
|
|
107
|
+
removeRunStateDirectory(projectRoot, directory);
|
|
108
|
+
}
|
|
109
|
+
for (const directory of [...directories].reverse()) {
|
|
110
|
+
if (kept.size <= MIN_RETAINED_RUN_DIRS || !kept.has(directory.name) || totalBytes <= maxTotalBytes) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
removeRunStateDirectory(projectRoot, directory);
|
|
114
|
+
kept.delete(directory.name);
|
|
115
|
+
totalBytes -= directory.sizeBytes;
|
|
116
|
+
}
|
|
117
|
+
return readRunStateDirectories(projectRoot);
|
|
118
|
+
}
|
|
119
|
+
function readJsonObject(filePath) {
|
|
120
|
+
try {
|
|
121
|
+
const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
122
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function stringField(value) {
|
|
129
|
+
return typeof value === 'string' ? value : null;
|
|
130
|
+
}
|
|
131
|
+
function stringArrayField(value) {
|
|
132
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === 'string') ? value : undefined;
|
|
133
|
+
}
|
|
134
|
+
function createRunEntry(directory) {
|
|
135
|
+
const receipt = readJsonObject(path.join(directory.absolutePath, 'receipt.json'));
|
|
136
|
+
if (!receipt || receipt.command !== 'run' || typeof receipt.receipt_path !== 'string') {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
command: 'run',
|
|
141
|
+
intent: stringField(receipt.intent),
|
|
142
|
+
status: stringField(receipt.status),
|
|
143
|
+
cwd: stringField(receipt.cwd),
|
|
144
|
+
started_at: stringField(receipt.started_at),
|
|
145
|
+
finished_at: stringField(receipt.finished_at),
|
|
146
|
+
correlation_id: stringField(receipt.correlation_id),
|
|
147
|
+
receipt_path: receipt.receipt_path,
|
|
148
|
+
run_dir: directory.relativePath,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function readVerifyIntentEntry(directory, manifest, manifestPath, manifestReceipt) {
|
|
152
|
+
const receiptPath = stringField(manifestReceipt.receipt_path);
|
|
153
|
+
if (!receiptPath) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const runsDir = path.dirname(directory.absolutePath);
|
|
157
|
+
const receiptAbsolutePath = path.resolve(runsDir, ...receiptPath.split('/').slice(3));
|
|
158
|
+
ensureInside(runsDir, receiptAbsolutePath);
|
|
159
|
+
const receipt = readJsonObject(receiptAbsolutePath);
|
|
160
|
+
return {
|
|
161
|
+
command: 'verify',
|
|
162
|
+
intent: stringField(manifestReceipt.intent),
|
|
163
|
+
status: stringField(manifestReceipt.status),
|
|
164
|
+
cwd: stringField(receipt?.cwd),
|
|
165
|
+
started_at: stringField(receipt?.started_at),
|
|
166
|
+
finished_at: stringField(receipt?.finished_at),
|
|
167
|
+
correlation_id: stringField(manifest.correlation_id),
|
|
168
|
+
verification_plan_id: stringField(manifest.verification_plan_id),
|
|
169
|
+
reason: stringField(manifest.reason),
|
|
170
|
+
reasons: stringArrayField(manifest.reasons),
|
|
171
|
+
receipt_path: receiptPath,
|
|
172
|
+
manifest_path: manifestPath,
|
|
173
|
+
run_dir: directory.relativePath,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function createVerifyEntries(directory) {
|
|
177
|
+
const manifestPath = toPosixPath(path.join(directory.relativePath, 'manifest.json'));
|
|
178
|
+
const manifest = readJsonObject(path.join(directory.absolutePath, 'manifest.json'));
|
|
179
|
+
if (!manifest || manifest.command !== 'verify') {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
const summaryEntry = {
|
|
183
|
+
command: 'verify',
|
|
184
|
+
intent: null,
|
|
185
|
+
status: stringField(manifest.status ?? manifest.execution_status),
|
|
186
|
+
correlation_id: stringField(manifest.correlation_id),
|
|
187
|
+
verification_plan_id: stringField(manifest.verification_plan_id),
|
|
188
|
+
reason: stringField(manifest.reason),
|
|
189
|
+
reasons: stringArrayField(manifest.reasons),
|
|
190
|
+
manifest_path: manifestPath,
|
|
191
|
+
run_dir: directory.relativePath,
|
|
192
|
+
};
|
|
193
|
+
const receiptEntries = Array.isArray(manifest.receipts)
|
|
194
|
+
? manifest.receipts
|
|
195
|
+
.map((receipt) => receipt && typeof receipt === 'object'
|
|
196
|
+
? readVerifyIntentEntry(directory, manifest, manifestPath, receipt)
|
|
197
|
+
: null)
|
|
198
|
+
.filter((entry) => Boolean(entry))
|
|
199
|
+
: [];
|
|
200
|
+
return [summaryEntry, ...receiptEntries];
|
|
201
|
+
}
|
|
202
|
+
function createIndexEntries(directories) {
|
|
203
|
+
return directories.flatMap((directory) => {
|
|
204
|
+
if (directory.kind === 'run-') {
|
|
205
|
+
const entry = createRunEntry(directory);
|
|
206
|
+
return entry ? [entry] : [];
|
|
207
|
+
}
|
|
208
|
+
return createVerifyEntries(directory);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function entryTargetPath(entry) {
|
|
212
|
+
return entry.receipt_path ?? entry.manifest_path ?? null;
|
|
213
|
+
}
|
|
214
|
+
function latestLookupEntries(entries) {
|
|
215
|
+
const latestByIntent = {};
|
|
216
|
+
const latestByCwdIntent = {};
|
|
217
|
+
for (const entry of entries) {
|
|
218
|
+
const targetPath = entryTargetPath(entry);
|
|
219
|
+
if (!entry.intent || !targetPath) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
latestByIntent[entry.intent] ??= targetPath;
|
|
223
|
+
if (entry.cwd) {
|
|
224
|
+
latestByCwdIntent[`${entry.cwd}::${entry.intent}`] ??= targetPath;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return { latestByIntent, latestByCwdIntent };
|
|
228
|
+
}
|
|
229
|
+
export function updateRunReceiptState(projectRoot, policy) {
|
|
230
|
+
const retainedDirectories = applyRunReceiptRetention(projectRoot, policy);
|
|
231
|
+
const entries = createIndexEntries(retainedDirectories);
|
|
232
|
+
const lookups = latestLookupEntries(entries);
|
|
233
|
+
const index = {
|
|
234
|
+
schema_version: RUN_RECEIPT_SCHEMA_VERSION,
|
|
235
|
+
kind: 'run_receipt_index',
|
|
236
|
+
generated_at: new Date().toISOString(),
|
|
237
|
+
retention: {
|
|
238
|
+
max_items: policy.maxItems,
|
|
239
|
+
max_total_mb: policy.maxTotalMb,
|
|
240
|
+
retained_run_dirs: retainedDirectories.length,
|
|
241
|
+
},
|
|
242
|
+
entries,
|
|
243
|
+
latest_by_intent: lookups.latestByIntent,
|
|
244
|
+
latest_by_cwd_intent: lookups.latestByCwdIntent,
|
|
245
|
+
};
|
|
246
|
+
writeJsonFileInsideWithoutSymlinks(projectRoot, latestIndexPath(projectRoot), index);
|
|
247
|
+
}
|
package/dist/core/run-receipt.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createStateRunId } from './atomic-state-write.js';
|
|
|
4
4
|
import { COMMAND_OUTPUT_LIMIT_SCOPE } from './command-output-limits.js';
|
|
5
5
|
import { decodeUtf8Tail } from './bounded-output.js';
|
|
6
6
|
import { DEFAULT_RUN_RECEIPT_TAIL_BYTES } from './retention-policy.js';
|
|
7
|
+
import { updateRunReceiptState } from './run-receipt-state.js';
|
|
7
8
|
import { writeJsonFileInsideWithoutSymlinks } from './safe-filesystem.js';
|
|
8
9
|
import { redactSecretLikeText } from './secret-redaction.js';
|
|
9
10
|
const RUN_RECEIPT_SCHEMA_VERSION = '1';
|
|
@@ -286,7 +287,7 @@ export function createRunReceipt(input) {
|
|
|
286
287
|
receipt_path: getReceiptRelativePath(input.receiptPath),
|
|
287
288
|
};
|
|
288
289
|
}
|
|
289
|
-
export function writeRunReceipt(projectRoot, receipt) {
|
|
290
|
+
export function writeRunReceipt(projectRoot, receipt, policy) {
|
|
290
291
|
const receiptDir = path.join(projectRoot, RUN_RECEIPT_DIR);
|
|
291
292
|
const latestPath = path.join(receiptDir, LATEST_RUN_RECEIPT);
|
|
292
293
|
const receiptPath = path.resolve(projectRoot, receipt.receipt_path);
|
|
@@ -296,4 +297,7 @@ export function writeRunReceipt(projectRoot, receipt) {
|
|
|
296
297
|
}
|
|
297
298
|
writeJsonFileInsideWithoutSymlinks(projectRoot, receiptPath, receipt);
|
|
298
299
|
writeJsonFileInsideWithoutSymlinks(projectRoot, latestPath, receipt);
|
|
300
|
+
if (policy) {
|
|
301
|
+
updateRunReceiptState(projectRoot, policy);
|
|
302
|
+
}
|
|
299
303
|
}
|
package/package.json
CHANGED
|
@@ -350,8 +350,8 @@ on_limit = "report"
|
|
|
350
350
|
[retention.run_receipts]
|
|
351
351
|
store = "repo_local_ignored"
|
|
352
352
|
max_file_kb = 128
|
|
353
|
-
max_items =
|
|
354
|
-
max_total_mb =
|
|
353
|
+
max_items = 50
|
|
354
|
+
max_total_mb = 10
|
|
355
355
|
keep_stdout_tail_bytes = 65536
|
|
356
356
|
keep_stderr_tail_bytes = 65536
|
|
357
357
|
|
|
@@ -565,7 +565,7 @@ translations = {}
|
|
|
565
565
|
[documents."skill.clarifying-question-gate"]
|
|
566
566
|
source = "locales/en/.mustflow/skills/clarifying-question-gate/SKILL.md"
|
|
567
567
|
source_locale = "en"
|
|
568
|
-
revision =
|
|
568
|
+
revision = 4
|
|
569
569
|
translations = {}
|
|
570
570
|
|
|
571
571
|
[documents."skill.heuristic-candidate-selection"]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
mustflow_doc: skill.clarifying-question-gate
|
|
3
3
|
locale: en
|
|
4
4
|
canonical: true
|
|
5
|
-
revision:
|
|
5
|
+
revision: 4
|
|
6
6
|
lifecycle: mustflow-owned
|
|
7
7
|
authority: procedure
|
|
8
8
|
name: clarifying-question-gate
|
|
@@ -96,6 +96,8 @@ verification, and stop-or-ask boundary.
|
|
|
96
96
|
- Reversibility classification for each decision: cheap/reversible, moderate, or expensive/hard to
|
|
97
97
|
roll back.
|
|
98
98
|
- A recommended option for each blocking question, with the tradeoff of at least one alternative.
|
|
99
|
+
- Host input capability when known: whether a structured user-input mechanism such as Codex
|
|
100
|
+
`request_user_input` or MCP elicitation is explicitly available in the current runtime.
|
|
99
101
|
- A request-state decision: `ready`, `ready_with_assumptions`, `needs_confirmation`,
|
|
100
102
|
`blocked_by_conflict`, or `insufficient_evidence`.
|
|
101
103
|
- A normalized task contract when the original request is vague enough to risk drift: goal, current
|
|
@@ -123,6 +125,8 @@ verification, and stop-or-ask boundary.
|
|
|
123
125
|
collection, or broad product discovery.
|
|
124
126
|
- Product decisions are separated from engineering responsibilities. Do not ask whether to preserve
|
|
125
127
|
existing style, avoid swallowed errors, add appropriate tests, or follow command contracts.
|
|
128
|
+
- Structured input tools are optional host capabilities. Do not claim they exist, simulate their UI,
|
|
129
|
+
or depend on them unless the current host explicitly exposes them for this turn or tool call.
|
|
126
130
|
|
|
127
131
|
<!-- mustflow-section: allowed-edits -->
|
|
128
132
|
## Allowed Edits
|
|
@@ -215,22 +219,40 @@ verification, and stop-or-ask boundary.
|
|
|
215
219
|
and one meaningful alternative;
|
|
216
220
|
- avoid open-ended prompts like "how should I implement this?" unless no responsible options can
|
|
217
221
|
be framed from repository evidence.
|
|
218
|
-
15.
|
|
222
|
+
15. Prefer structured user input for real blocking decisions when the host exposes it:
|
|
223
|
+
- use a structured input tool such as `request_user_input` or MCP elicitation only when it is
|
|
224
|
+
explicitly listed as available in the current runtime or tool call;
|
|
225
|
+
- use it for `needs_confirmation` or for a non-blocking `ready_with_assumptions` choice whose
|
|
226
|
+
answer would materially improve the result without stopping all progress;
|
|
227
|
+
- ask at most three short questions, and prefer one question when the answer may change the next
|
|
228
|
+
question;
|
|
229
|
+
- provide two or three mutually exclusive choices, put the recommended choice first, and include
|
|
230
|
+
the concrete consequence or tradeoff for each choice;
|
|
231
|
+
- allow free-form input when the host mechanism supports it, because the listed options are a
|
|
232
|
+
steering aid rather than a closed product decision;
|
|
233
|
+
- use auto-resolution only for non-blocking choices with a narrow reversible default, and never
|
|
234
|
+
for destructive actions, publish or release decisions, credential or secret handling, data
|
|
235
|
+
deletion or migration, auth or billing policy, dependency adoption, or other explicit
|
|
236
|
+
confirmation gates;
|
|
237
|
+
- if no structured input mechanism is available, ask the same blocking decision as a concise
|
|
238
|
+
normal chat question when host policy allows it; do not invent a fake UI, long questionnaire,
|
|
239
|
+
or multiple-choice card in prose when the host explicitly forbids that fallback.
|
|
240
|
+
16. Do not ask bad engineering-delegation questions:
|
|
219
241
|
- "Should I add tests?"
|
|
220
242
|
- "Should I handle errors?"
|
|
221
243
|
- "Should I follow existing style?"
|
|
222
244
|
- "Should I check current files?"
|
|
223
245
|
- "Should I preserve existing behavior?"
|
|
224
|
-
|
|
246
|
+
17. Use prompt rewriting only as an exception:
|
|
225
247
|
- the user explicitly asks for a prompt, issue, PR body, work order, or handoff for another
|
|
226
248
|
agent;
|
|
227
249
|
- the current request is too broken to execute and a normalized contract plus confirmation is the
|
|
228
250
|
smallest safe next step.
|
|
229
251
|
Otherwise, show the normalized contract only when it materially reduces drift, then proceed in
|
|
230
252
|
the same conversation.
|
|
231
|
-
|
|
253
|
+
18. If no blocking question remains, proceed without ceremony. State only the assumptions that matter
|
|
232
254
|
to review or rollback.
|
|
233
|
-
|
|
255
|
+
19. If a blocking question remains unanswered, do not implement around it. Offer the smallest safe
|
|
234
256
|
non-blocked action, such as read-only analysis, a plan, a reproduction, or a narrow preparatory
|
|
235
257
|
refactor when another selected skill supports it.
|
|
236
258
|
|
|
@@ -241,6 +263,8 @@ verification, and stop-or-ask boundary.
|
|
|
241
263
|
- The agent has not asked for facts it could read locally.
|
|
242
264
|
- Expensive, user-owned, security-sensitive, data-affecting, dependency-affecting, and public-contract
|
|
243
265
|
decisions are resolved before implementation.
|
|
266
|
+
- Structured input tools are used only when available and only for bounded decisions that benefit
|
|
267
|
+
from user choice.
|
|
244
268
|
- Safe assumptions are narrow, reversible, and reported.
|
|
245
269
|
- Any normalized contract preserves the user's original request separately from repository-derived
|
|
246
270
|
facts and safe assumptions.
|
|
@@ -288,6 +312,7 @@ run the specific configured verification intents required by the selected implem
|
|
|
288
312
|
- Normalized task contract, only when needed, with `user_confirmed`, `repository_derived`,
|
|
289
313
|
`safe_assumption`, and `unresolved` source tags
|
|
290
314
|
- Blocking questions asked, with recommendation and tradeoff
|
|
315
|
+
- Question delivery mode: structured host input, normal chat fallback, or not needed
|
|
291
316
|
- Safe assumptions made
|
|
292
317
|
- Decisions intentionally deferred
|
|
293
318
|
- Implementation scope selected
|