mustflow 2.18.0 → 2.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli/commands/explain-verify.js +2 -2
- package/dist/cli/commands/run/builtin-dispatch.js +92 -0
- package/dist/cli/commands/run/executor.js +112 -0
- package/dist/cli/commands/run/output.js +59 -0
- package/dist/cli/commands/run/process-tree.js +91 -0
- package/dist/cli/commands/run/receipt.js +42 -0
- package/dist/cli/commands/run.js +22 -414
- package/dist/cli/commands/verify/args.js +262 -0
- package/dist/cli/commands/verify.js +106 -263
- package/dist/cli/i18n/en.js +3 -1
- package/dist/cli/i18n/es.js +3 -1
- package/dist/cli/i18n/fr.js +3 -1
- package/dist/cli/i18n/hi.js +3 -1
- package/dist/cli/i18n/ko.js +3 -1
- package/dist/cli/i18n/zh.js +3 -1
- package/dist/cli/index.js +6 -72
- package/dist/cli/lib/command-registry.js +27 -0
- package/dist/cli/lib/repo-map.js +10 -3
- package/dist/core/atomic-state-write.js +31 -0
- package/dist/core/bounded-output.js +23 -1
- package/dist/core/check-issues.js +1 -0
- package/dist/core/command-contract-validation.js +57 -7
- package/dist/core/completion-verdict.js +2 -1
- package/dist/core/public-json-contracts.js +1 -1
- package/dist/core/run-receipt.js +20 -13
- package/dist/core/source-anchors.js +96 -24
- package/dist/core/verification-evidence.js +4 -1
- package/package.json +1 -1
- package/schemas/README.md +1 -1
- package/schemas/run-receipt.schema.json +26 -3
- package/schemas/verify-report.schema.json +1 -1
- package/schemas/verify-run-manifest.schema.json +1 -1
- package/templates/default/i18n.toml +7 -1
- package/templates/default/locales/en/.mustflow/skills/INDEX.md +2 -1
- package/templates/default/locales/en/.mustflow/skills/routes.toml +6 -0
- package/templates/default/locales/en/.mustflow/skills/source-anchor-authoring/SKILL.md +147 -0
- package/templates/default/manifest.toml +8 -1
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ mustflow installs and validates an agent workflow for user projects.
|
|
|
124
124
|
- Classifies changed files, public surfaces, and validation reasons with `mf classify`.
|
|
125
125
|
- Prints execution-free verification plans with `mf verify --plan-only --json`, including a machine-readable verification decision graph and read-only local-index lock explanations when available.
|
|
126
126
|
- Runs only allowed one-shot commands within a timeout via `mf run <intent>` or `mf verify` when the selected intent is runnable.
|
|
127
|
-
- Writes command receipts
|
|
127
|
+
- Writes command receipts under `.mustflow/state/runs/run-*` and atomically updates `.mustflow/state/runs/latest.json`.
|
|
128
128
|
- Generates a concise repository navigation map, `REPO_MAP.md`, with `mf map`.
|
|
129
129
|
- 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.
|
|
130
130
|
- Tracks agent-created or agent-modified documentation needing prose review with `mf docs review`.
|
|
@@ -358,7 +358,7 @@ Development servers, watch modes, browser UIs, interactive commands, and backgro
|
|
|
358
358
|
|
|
359
359
|
Use `mf verify --reason <event> --plan-only --json` to inspect matching verification intents, command eligibility, 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.
|
|
360
360
|
|
|
361
|
-
Each executed command run writes
|
|
361
|
+
Each executed command run writes a run record under `.mustflow/state/runs/run-*` and atomically updates `.mustflow/state/runs/latest.json`. The record includes the intent name, working directory, timeout, exit code, timeout status, and the tail of stdout and stderr.
|
|
362
362
|
|
|
363
363
|
## Language and profiles
|
|
364
364
|
|
|
@@ -5,7 +5,7 @@ import { createVerificationPlan, } from '../../core/verification-plan.js';
|
|
|
5
5
|
import { createVerificationSchedule } from '../../core/verification-scheduler.js';
|
|
6
6
|
import { t } from '../lib/i18n.js';
|
|
7
7
|
import { readLatestLocalVerificationReadModelQueries, readLocalCommandEffectGraphs, } from '../lib/local-index.js';
|
|
8
|
-
import { planErrorMessageKey,
|
|
8
|
+
import { planErrorMessageKey, readInputFromClassificationReport } from './verify.js';
|
|
9
9
|
export function parseExplainVerifyArgs(args) {
|
|
10
10
|
let reason;
|
|
11
11
|
let fromPlan;
|
|
@@ -69,7 +69,7 @@ export function explainVerifyPlanErrorMessage(error, lang) {
|
|
|
69
69
|
return t(lang, planErrorMessageKey(code));
|
|
70
70
|
}
|
|
71
71
|
export function readExplainVerifyPlanReasons(projectRoot, planPath) {
|
|
72
|
-
return
|
|
72
|
+
return readInputFromClassificationReport(projectRoot, planPath).reasons;
|
|
73
73
|
}
|
|
74
74
|
export async function getVerifyExplainOutput(schemaVersion, projectRoot, reasons, inputReason, planSource) {
|
|
75
75
|
const contract = readCommandContract(projectRoot);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { canRunMustflowBuiltinInProcess, isMustflowBinName } from '../../../core/command-classification.js';
|
|
2
|
+
import { getPackageVersion } from '../../lib/package-info.js';
|
|
3
|
+
import { createBufferedReporter } from './output.js';
|
|
4
|
+
/**
|
|
5
|
+
* mf:anchor cli.run.builtin-inprocess
|
|
6
|
+
* purpose: Dispatch selected mustflow built-in commands without spawning a nested CLI process.
|
|
7
|
+
* search: builtin intent, in-process command, nested mf run, run receipt
|
|
8
|
+
* invariant: Only commands classified by command-classification can use this path.
|
|
9
|
+
* risk: config, state
|
|
10
|
+
*/
|
|
11
|
+
async function runKnownBuiltinCommand(args, reporter, lang) {
|
|
12
|
+
const [command, ...commandArgs] = args;
|
|
13
|
+
if ((command === '--version' || command === '-v' || command === 'version') && commandArgs.length === 0) {
|
|
14
|
+
reporter.stdout(getPackageVersion());
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
if (!canRunMustflowBuiltinInProcess(command)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
if (command === 'check') {
|
|
21
|
+
return (await import('../check.js')).runCheck(commandArgs, reporter, lang);
|
|
22
|
+
}
|
|
23
|
+
if (command === 'classify') {
|
|
24
|
+
return (await import('../classify.js')).runClassify(commandArgs, reporter, lang);
|
|
25
|
+
}
|
|
26
|
+
if (command === 'context') {
|
|
27
|
+
return (await import('../context.js')).runContext(commandArgs, reporter, lang);
|
|
28
|
+
}
|
|
29
|
+
if (command === 'doctor') {
|
|
30
|
+
return (await import('../doctor.js')).runDoctor(commandArgs, reporter, lang);
|
|
31
|
+
}
|
|
32
|
+
if (command === 'help') {
|
|
33
|
+
return (await import('../help.js')).runHelp(commandArgs, reporter, lang);
|
|
34
|
+
}
|
|
35
|
+
if (command === 'impact') {
|
|
36
|
+
return (await import('../impact.js')).runImpact(commandArgs, reporter, lang);
|
|
37
|
+
}
|
|
38
|
+
if (command === 'line-endings') {
|
|
39
|
+
return (await import('../line-endings.js')).runLineEndings(commandArgs, reporter, lang);
|
|
40
|
+
}
|
|
41
|
+
if (command === 'map') {
|
|
42
|
+
return (await import('../map.js')).runMap(commandArgs, reporter, lang);
|
|
43
|
+
}
|
|
44
|
+
if (command === 'status') {
|
|
45
|
+
return (await import('../status.js')).runStatus(commandArgs, reporter, lang);
|
|
46
|
+
}
|
|
47
|
+
if (command === 'update') {
|
|
48
|
+
return (await import('../update.js')).runUpdate(commandArgs, reporter, lang);
|
|
49
|
+
}
|
|
50
|
+
if (command === 'version-sources') {
|
|
51
|
+
return (await import('../version-sources.js')).runVersionSources(commandArgs, reporter, lang);
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
async function withWorkingDirectory(cwd, callback) {
|
|
56
|
+
const previousCwd = process.cwd();
|
|
57
|
+
process.chdir(cwd);
|
|
58
|
+
try {
|
|
59
|
+
return await callback();
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
process.chdir(previousCwd);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export async function runBuiltinArgvInProcess(commandArgv, cwd, lang) {
|
|
66
|
+
const [command = '', ...builtinArgs] = commandArgv;
|
|
67
|
+
if (!isMustflowBinName(command)) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const output = createBufferedReporter();
|
|
71
|
+
try {
|
|
72
|
+
const status = await withWorkingDirectory(cwd, () => runKnownBuiltinCommand(builtinArgs, output.reporter, lang));
|
|
73
|
+
if (status === undefined) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
status,
|
|
78
|
+
signal: null,
|
|
79
|
+
stdout: output.stdout(),
|
|
80
|
+
stderr: output.stderr(),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
return {
|
|
86
|
+
status: 1,
|
|
87
|
+
signal: null,
|
|
88
|
+
stdout: output.stdout(),
|
|
89
|
+
stderr: `${output.stderr()}${message}\n`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { BoundedOutputBuffer } from '../../../core/bounded-output.js';
|
|
3
|
+
import { createPendingTimeoutTermination, forceTerminateProcessTreeNonBlocking, getKillMethod, terminateProcessTreeNonBlocking, } from './process-tree.js';
|
|
4
|
+
import { createOutputLimitError, isOutputLimitExceededError, writeStreamChunk } from './output.js';
|
|
5
|
+
function runSpawnedCommandStreaming(command, cwd, env, timeoutSeconds, maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, streamOutput, enforceOutputLimit) {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const stdout = new BoundedOutputBuffer(stdoutTailBytes);
|
|
8
|
+
const stderr = new BoundedOutputBuffer(stderrTailBytes);
|
|
9
|
+
let settled = false;
|
|
10
|
+
let timedOut = false;
|
|
11
|
+
let childError;
|
|
12
|
+
let childPid;
|
|
13
|
+
let stdoutBytes = 0;
|
|
14
|
+
let stderrBytes = 0;
|
|
15
|
+
let timeout;
|
|
16
|
+
let termination = null;
|
|
17
|
+
const child = spawn(command.executable, command.args ?? [], {
|
|
18
|
+
cwd,
|
|
19
|
+
env,
|
|
20
|
+
shell: command.shell,
|
|
21
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
22
|
+
windowsHide: true,
|
|
23
|
+
detached: process.platform !== 'win32',
|
|
24
|
+
});
|
|
25
|
+
childPid = child.pid;
|
|
26
|
+
const finish = (status, signal) => {
|
|
27
|
+
if (settled) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
settled = true;
|
|
31
|
+
if (timeout) {
|
|
32
|
+
clearTimeout(timeout);
|
|
33
|
+
}
|
|
34
|
+
resolve({
|
|
35
|
+
status: timedOut ? null : status,
|
|
36
|
+
signal: timedOut ? null : signal,
|
|
37
|
+
error: timedOut ? Object.assign(new Error('Command timed out'), { code: 'ETIMEDOUT' }) : childError,
|
|
38
|
+
stdout: stdout.toSnapshot(),
|
|
39
|
+
stderr: stderr.toSnapshot(),
|
|
40
|
+
pid: childPid,
|
|
41
|
+
termination,
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
const stopForOutputLimit = (stream) => {
|
|
45
|
+
if (settled || childError) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
childError = createOutputLimitError(stream, maxOutputBytes);
|
|
49
|
+
child.stdout?.destroy();
|
|
50
|
+
child.stderr?.destroy();
|
|
51
|
+
child.unref();
|
|
52
|
+
terminateProcessTreeNonBlocking(childPid);
|
|
53
|
+
forceTerminateProcessTreeNonBlocking(childPid);
|
|
54
|
+
finish(null, null);
|
|
55
|
+
};
|
|
56
|
+
child.stdout?.on('data', (chunk) => {
|
|
57
|
+
stdout.append(chunk);
|
|
58
|
+
stdoutBytes += chunk.byteLength;
|
|
59
|
+
if (streamOutput) {
|
|
60
|
+
writeStreamChunk(reporter, 'stdout', chunk);
|
|
61
|
+
}
|
|
62
|
+
if (enforceOutputLimit && stdoutBytes > maxOutputBytes) {
|
|
63
|
+
stopForOutputLimit('stdout');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
child.stderr?.on('data', (chunk) => {
|
|
67
|
+
stderr.append(chunk);
|
|
68
|
+
stderrBytes += chunk.byteLength;
|
|
69
|
+
if (streamOutput) {
|
|
70
|
+
writeStreamChunk(reporter, 'stderr', chunk);
|
|
71
|
+
}
|
|
72
|
+
if (enforceOutputLimit && stderrBytes > maxOutputBytes) {
|
|
73
|
+
stopForOutputLimit('stderr');
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
child.once('error', (error) => {
|
|
77
|
+
childError = error;
|
|
78
|
+
});
|
|
79
|
+
child.once('close', (status, signal) => {
|
|
80
|
+
finish(status, signal);
|
|
81
|
+
});
|
|
82
|
+
timeout = setTimeout(() => {
|
|
83
|
+
timedOut = true;
|
|
84
|
+
child.stdout?.destroy();
|
|
85
|
+
child.stderr?.destroy();
|
|
86
|
+
child.unref();
|
|
87
|
+
termination = createPendingTimeoutTermination(getKillMethod());
|
|
88
|
+
terminateProcessTreeNonBlocking(childPid);
|
|
89
|
+
forceTerminateProcessTreeNonBlocking(childPid);
|
|
90
|
+
finish(null, null);
|
|
91
|
+
}, timeoutSeconds * 1000);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
export function runArgvCommandStreaming(command, cwd, env, timeoutSeconds, maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, streamOutput, enforceOutputLimit) {
|
|
95
|
+
return runSpawnedCommandStreaming({ executable: command?.executable ?? '', args: command?.args ?? [], shell: command?.shell ?? false }, cwd, env, timeoutSeconds, maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, streamOutput, enforceOutputLimit);
|
|
96
|
+
}
|
|
97
|
+
export function runShellCommandStreaming(command, cwd, env, timeoutSeconds, maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, streamOutput, enforceOutputLimit) {
|
|
98
|
+
return runSpawnedCommandStreaming({ executable: command ?? '', shell: true }, cwd, env, timeoutSeconds, maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, streamOutput, enforceOutputLimit);
|
|
99
|
+
}
|
|
100
|
+
export function getRunStatus(error, exitCode, successExitCodes) {
|
|
101
|
+
const errorWithCode = error;
|
|
102
|
+
if (errorWithCode?.code === 'ETIMEDOUT') {
|
|
103
|
+
return 'timed_out';
|
|
104
|
+
}
|
|
105
|
+
if (isOutputLimitExceededError(error)) {
|
|
106
|
+
return 'output_limit_exceeded';
|
|
107
|
+
}
|
|
108
|
+
if (error) {
|
|
109
|
+
return 'start_failed';
|
|
110
|
+
}
|
|
111
|
+
return exitCode !== null && successExitCodes.includes(exitCode) ? 'passed' : 'failed';
|
|
112
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const OUTPUT_LIMIT_ERROR_CODE = 'ENOBUFS';
|
|
2
|
+
const OUTPUT_LIMIT_ERROR_MESSAGE = /\bmaxBuffer\b.*\bexceeded\b/i;
|
|
3
|
+
export function emitOutput(reporter, output, stream) {
|
|
4
|
+
if (!output) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
const text = (typeof output === 'object' && 'tail' in output ? output.tail : output.toString()).trimEnd();
|
|
8
|
+
if (text.length === 0) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
reporter[stream](text);
|
|
12
|
+
}
|
|
13
|
+
export function createBufferedReporter() {
|
|
14
|
+
const stdout = [];
|
|
15
|
+
const stderr = [];
|
|
16
|
+
return {
|
|
17
|
+
reporter: {
|
|
18
|
+
stdout(message) {
|
|
19
|
+
stdout.push(`${message}\n`);
|
|
20
|
+
},
|
|
21
|
+
stderr(message) {
|
|
22
|
+
stderr.push(`${message}\n`);
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
stdout() {
|
|
26
|
+
return stdout.join('');
|
|
27
|
+
},
|
|
28
|
+
stderr() {
|
|
29
|
+
return stderr.join('');
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function writeStreamChunk(reporter, stream, chunk) {
|
|
34
|
+
if (stream === 'stdout') {
|
|
35
|
+
if (reporter.writeStdout) {
|
|
36
|
+
reporter.writeStdout(chunk);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
reporter.stdout(chunk.toString());
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (reporter.writeStderr) {
|
|
43
|
+
reporter.writeStderr(chunk);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
reporter.stderr(chunk.toString());
|
|
47
|
+
}
|
|
48
|
+
export function createOutputLimitError(stream, maxOutputBytes) {
|
|
49
|
+
return Object.assign(new Error(`${stream} exceeded max_output_bytes (${maxOutputBytes})`), {
|
|
50
|
+
code: OUTPUT_LIMIT_ERROR_CODE,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function isOutputLimitExceededError(error) {
|
|
54
|
+
if (!error) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const errorWithCode = error;
|
|
58
|
+
return errorWithCode.code === OUTPUT_LIMIT_ERROR_CODE || OUTPUT_LIMIT_ERROR_MESSAGE.test(error.message);
|
|
59
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
function signalProcessTree(pid, signal) {
|
|
3
|
+
if (!pid || pid <= 0) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
if (process.platform === 'win32') {
|
|
7
|
+
spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], {
|
|
8
|
+
stdio: 'ignore',
|
|
9
|
+
windowsHide: true,
|
|
10
|
+
});
|
|
11
|
+
if (signal === 'SIGKILL') {
|
|
12
|
+
try {
|
|
13
|
+
process.kill(pid, signal);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// taskkill may already have terminated the direct child.
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
process.kill(-pid, signal);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
try {
|
|
26
|
+
process.kill(pid, signal);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// The child may already be gone after Node's spawn timeout handling.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function signalProcessTreeNonBlocking(pid, signal) {
|
|
34
|
+
if (!pid || pid <= 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (process.platform === 'win32') {
|
|
38
|
+
const killer = spawn('taskkill', ['/PID', String(pid), '/T', '/F'], {
|
|
39
|
+
stdio: 'ignore',
|
|
40
|
+
windowsHide: true,
|
|
41
|
+
detached: true,
|
|
42
|
+
});
|
|
43
|
+
killer.unref();
|
|
44
|
+
if (signal === 'SIGKILL') {
|
|
45
|
+
try {
|
|
46
|
+
process.kill(pid, signal);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// taskkill may already have terminated the direct child.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
process.kill(-pid, signal);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
try {
|
|
59
|
+
process.kill(pid, signal);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// The child may already be gone after the timeout fired.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function terminateProcessTree(pid) {
|
|
67
|
+
signalProcessTree(pid, 'SIGTERM');
|
|
68
|
+
}
|
|
69
|
+
export function forceTerminateProcessTree(pid) {
|
|
70
|
+
signalProcessTree(pid, 'SIGKILL');
|
|
71
|
+
}
|
|
72
|
+
export function terminateProcessTreeNonBlocking(pid) {
|
|
73
|
+
signalProcessTreeNonBlocking(pid, 'SIGTERM');
|
|
74
|
+
}
|
|
75
|
+
export function forceTerminateProcessTreeNonBlocking(pid) {
|
|
76
|
+
signalProcessTreeNonBlocking(pid, 'SIGKILL');
|
|
77
|
+
}
|
|
78
|
+
export function getKillMethod() {
|
|
79
|
+
return process.platform === 'win32' ? 'taskkill_process_tree' : 'process_group_sigterm';
|
|
80
|
+
}
|
|
81
|
+
export function createPendingTimeoutTermination(method) {
|
|
82
|
+
return {
|
|
83
|
+
reason: 'timeout',
|
|
84
|
+
method,
|
|
85
|
+
graceful_signal: 'SIGTERM',
|
|
86
|
+
forced_signal: 'SIGKILL',
|
|
87
|
+
forced_kill_attempted: true,
|
|
88
|
+
confirmed: false,
|
|
89
|
+
cleanup_pending: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createRunReceipt, createRunReceiptRelativePath, } from '../../../core/run-receipt.js';
|
|
2
|
+
export function assembleRunReceipt(input) {
|
|
3
|
+
return createRunReceipt({
|
|
4
|
+
intent: input.intentName,
|
|
5
|
+
status: input.runStatus,
|
|
6
|
+
timedOut: input.runStatus === 'timed_out',
|
|
7
|
+
startedAt: input.startedAt,
|
|
8
|
+
finishedAt: input.finishedAt,
|
|
9
|
+
projectRoot: input.projectRoot,
|
|
10
|
+
cwd: input.plan.cwd,
|
|
11
|
+
lifecycle: input.plan.lifecycle ?? 'oneshot',
|
|
12
|
+
runPolicy: input.plan.runPolicy ?? 'agent_allowed',
|
|
13
|
+
mode: input.plan.mode,
|
|
14
|
+
argv: input.plan.commandArgv,
|
|
15
|
+
cmd: input.plan.shellCommand,
|
|
16
|
+
envPolicy: input.plan.envPolicy,
|
|
17
|
+
envAllowlist: input.plan.envAllowlist,
|
|
18
|
+
timeoutSeconds: input.plan.timeoutSeconds,
|
|
19
|
+
maxOutputBytes: input.plan.maxOutputBytes,
|
|
20
|
+
successExitCodes: input.plan.successExitCodes,
|
|
21
|
+
exitCode: input.exitCode,
|
|
22
|
+
signal: input.result.signal,
|
|
23
|
+
error: input.result.error?.message ?? null,
|
|
24
|
+
killMethod: input.killMethod,
|
|
25
|
+
termination: input.termination,
|
|
26
|
+
stdout: input.result.stdout,
|
|
27
|
+
stderr: input.result.stderr,
|
|
28
|
+
writeDrift: input.writeDrift,
|
|
29
|
+
executorOverheadMs: input.executorOverheadMs,
|
|
30
|
+
phaseTimings: input.phaseTimings,
|
|
31
|
+
selectionSummary: {
|
|
32
|
+
strategy: input.plan.testTargets.length > 0 ? 'project_test_selection' : 'direct_intent',
|
|
33
|
+
changed_file_count: 0,
|
|
34
|
+
changed_surface_counts: {},
|
|
35
|
+
selected_target_count: Math.max(1, input.plan.testTargets.length),
|
|
36
|
+
fallback_used: false,
|
|
37
|
+
},
|
|
38
|
+
stdoutTailBytes: input.stdoutTailBytes,
|
|
39
|
+
stderrTailBytes: input.stderrTailBytes,
|
|
40
|
+
receiptPath: createRunReceiptRelativePath(),
|
|
41
|
+
});
|
|
42
|
+
}
|