mustflow 2.18.2 → 2.18.7
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 -0
- package/dist/cli/commands/run/builtin-dispatch.js +92 -0
- package/dist/cli/commands/run/executor.js +149 -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 +17 -382
- package/dist/cli/commands/verify/args.js +262 -0
- package/dist/cli/commands/verify.js +1 -262
- 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/index.js +6 -72
- package/dist/cli/lib/command-registry.js +27 -0
- package/dist/cli/lib/dashboard-export.js +2 -1
- package/dist/cli/lib/dashboard-html/locale-bootstrap.js +3 -2
- package/dist/cli/lib/dashboard-html/template.js +5 -4
- package/dist/cli/lib/html-json.js +11 -0
- package/dist/cli/lib/local-index/index.js +166 -14
- package/dist/cli/lib/run-plan.js +6 -0
- package/dist/core/check-issues.js +1 -0
- package/dist/core/command-contract-rules.js +0 -3
- package/dist/core/command-contract-validation.js +42 -4
- package/dist/core/command-intent-eligibility.js +4 -4
- package/dist/core/contract-lint.js +3 -3
- package/package.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
|
@@ -356,6 +356,8 @@ Runnable work is declared in `.mustflow/config/commands.toml` so agents do not g
|
|
|
356
356
|
|
|
357
357
|
Development servers, watch modes, browser UIs, interactive commands, and background processes do not run directly. `mf run` also rejects obvious long-running `argv` shapes, such as shell-wrapper background payloads, interpreter loops, package-manager development scripts, watchers, and development servers declared as one-shot commands.
|
|
358
358
|
|
|
359
|
+
Command environments remove the project-local `node_modules/.bin` path from `PATH` by default. If an intent needs a project dependency binary such as `eslint`, `tsc`, or `vitest`, declare it through the package manager, for example `npm exec eslint -- ...`, `pnpm exec tsc -- --noEmit`, `bun x eslint ...`, or `yarn exec eslint ...`. `mf check --strict` warns when an agent-runnable intent uses a bare executable name that appears under the project-local `.bin` directory.
|
|
360
|
+
|
|
359
361
|
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
362
|
|
|
361
363
|
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.
|
|
@@ -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,149 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { BoundedOutputBuffer } from '../../../core/bounded-output.js';
|
|
3
|
+
import { createPendingTimeoutTermination, forceTerminateProcessTree, getKillMethod, terminateProcessTree, } from './process-tree.js';
|
|
4
|
+
import { createOutputLimitError, isOutputLimitExceededError, writeStreamChunk } from './output.js';
|
|
5
|
+
const TERMINATION_CONFIRMATION_FALLBACK_MS = 1000;
|
|
6
|
+
function runSpawnedCommandStreaming(command, cwd, env, timeoutSeconds, killAfterSeconds, maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, streamOutput, enforceOutputLimit) {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
const stdout = new BoundedOutputBuffer(stdoutTailBytes);
|
|
9
|
+
const stderr = new BoundedOutputBuffer(stderrTailBytes);
|
|
10
|
+
let settled = false;
|
|
11
|
+
let timedOut = false;
|
|
12
|
+
let childError;
|
|
13
|
+
let childPid;
|
|
14
|
+
let stdoutBytes = 0;
|
|
15
|
+
let stderrBytes = 0;
|
|
16
|
+
let timeout;
|
|
17
|
+
let forceKillTimeout;
|
|
18
|
+
let terminationFallbackTimeout;
|
|
19
|
+
let terminationStarted = false;
|
|
20
|
+
let termination = null;
|
|
21
|
+
const child = spawn(command.executable, command.args ?? [], {
|
|
22
|
+
cwd,
|
|
23
|
+
env,
|
|
24
|
+
shell: command.shell,
|
|
25
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
26
|
+
windowsHide: true,
|
|
27
|
+
detached: process.platform !== 'win32',
|
|
28
|
+
});
|
|
29
|
+
childPid = child.pid;
|
|
30
|
+
const finish = (status, signal, terminationConfirmed = true) => {
|
|
31
|
+
if (settled) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
settled = true;
|
|
35
|
+
if (timeout) {
|
|
36
|
+
clearTimeout(timeout);
|
|
37
|
+
}
|
|
38
|
+
if (forceKillTimeout) {
|
|
39
|
+
clearTimeout(forceKillTimeout);
|
|
40
|
+
}
|
|
41
|
+
if (terminationFallbackTimeout) {
|
|
42
|
+
clearTimeout(terminationFallbackTimeout);
|
|
43
|
+
}
|
|
44
|
+
const confirmedTermination = termination ?
|
|
45
|
+
{
|
|
46
|
+
...termination,
|
|
47
|
+
confirmed: terminationConfirmed,
|
|
48
|
+
cleanup_pending: !terminationConfirmed,
|
|
49
|
+
} :
|
|
50
|
+
null;
|
|
51
|
+
resolve({
|
|
52
|
+
status: timedOut ? null : status,
|
|
53
|
+
signal: timedOut ? null : signal,
|
|
54
|
+
error: timedOut ? Object.assign(new Error('Command timed out'), { code: 'ETIMEDOUT' }) : childError,
|
|
55
|
+
stdout: stdout.toSnapshot(),
|
|
56
|
+
stderr: stderr.toSnapshot(),
|
|
57
|
+
pid: childPid,
|
|
58
|
+
termination: confirmedTermination,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const beginTermination = () => {
|
|
62
|
+
if (terminationStarted) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
terminationStarted = true;
|
|
66
|
+
child.stdout?.destroy();
|
|
67
|
+
child.stderr?.destroy();
|
|
68
|
+
terminateProcessTree(childPid);
|
|
69
|
+
const forceAfterMs = killAfterSeconds * 1000;
|
|
70
|
+
forceKillTimeout = setTimeout(() => {
|
|
71
|
+
if (termination) {
|
|
72
|
+
termination = {
|
|
73
|
+
...termination,
|
|
74
|
+
forced_kill_attempted: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
forceTerminateProcessTree(childPid);
|
|
78
|
+
}, forceAfterMs);
|
|
79
|
+
terminationFallbackTimeout = setTimeout(() => {
|
|
80
|
+
child.unref();
|
|
81
|
+
finish(null, null, false);
|
|
82
|
+
}, forceAfterMs + TERMINATION_CONFIRMATION_FALLBACK_MS);
|
|
83
|
+
};
|
|
84
|
+
const stopForOutputLimit = (stream) => {
|
|
85
|
+
if (settled || childError) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
childError = createOutputLimitError(stream, maxOutputBytes);
|
|
89
|
+
if (timeout) {
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
timeout = undefined;
|
|
92
|
+
}
|
|
93
|
+
beginTermination();
|
|
94
|
+
};
|
|
95
|
+
child.stdout?.on('data', (chunk) => {
|
|
96
|
+
stdout.append(chunk);
|
|
97
|
+
stdoutBytes += chunk.byteLength;
|
|
98
|
+
if (streamOutput) {
|
|
99
|
+
writeStreamChunk(reporter, 'stdout', chunk);
|
|
100
|
+
}
|
|
101
|
+
if (enforceOutputLimit && stdoutBytes > maxOutputBytes) {
|
|
102
|
+
stopForOutputLimit('stdout');
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
child.stderr?.on('data', (chunk) => {
|
|
106
|
+
stderr.append(chunk);
|
|
107
|
+
stderrBytes += chunk.byteLength;
|
|
108
|
+
if (streamOutput) {
|
|
109
|
+
writeStreamChunk(reporter, 'stderr', chunk);
|
|
110
|
+
}
|
|
111
|
+
if (enforceOutputLimit && stderrBytes > maxOutputBytes) {
|
|
112
|
+
stopForOutputLimit('stderr');
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
child.once('error', (error) => {
|
|
116
|
+
childError = error;
|
|
117
|
+
});
|
|
118
|
+
child.once('close', (status, signal) => {
|
|
119
|
+
finish(status, signal);
|
|
120
|
+
});
|
|
121
|
+
timeout = setTimeout(() => {
|
|
122
|
+
if (settled || childError) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
timedOut = true;
|
|
126
|
+
termination = createPendingTimeoutTermination(getKillMethod());
|
|
127
|
+
beginTermination();
|
|
128
|
+
}, timeoutSeconds * 1000);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
export function runArgvCommandStreaming(command, cwd, env, timeoutSeconds, killAfterSeconds, maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, streamOutput, enforceOutputLimit) {
|
|
132
|
+
return runSpawnedCommandStreaming({ executable: command?.executable ?? '', args: command?.args ?? [], shell: command?.shell ?? false }, cwd, env, timeoutSeconds, killAfterSeconds, maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, streamOutput, enforceOutputLimit);
|
|
133
|
+
}
|
|
134
|
+
export function runShellCommandStreaming(command, cwd, env, timeoutSeconds, killAfterSeconds, maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, streamOutput, enforceOutputLimit) {
|
|
135
|
+
return runSpawnedCommandStreaming({ executable: command ?? '', shell: true }, cwd, env, timeoutSeconds, killAfterSeconds, maxOutputBytes, stdoutTailBytes, stderrTailBytes, reporter, streamOutput, enforceOutputLimit);
|
|
136
|
+
}
|
|
137
|
+
export function getRunStatus(error, exitCode, successExitCodes) {
|
|
138
|
+
const errorWithCode = error;
|
|
139
|
+
if (errorWithCode?.code === 'ETIMEDOUT') {
|
|
140
|
+
return 'timed_out';
|
|
141
|
+
}
|
|
142
|
+
if (isOutputLimitExceededError(error)) {
|
|
143
|
+
return 'output_limit_exceeded';
|
|
144
|
+
}
|
|
145
|
+
if (error) {
|
|
146
|
+
return 'start_failed';
|
|
147
|
+
}
|
|
148
|
+
return exitCode !== null && successExitCodes.includes(exitCode) ? 'passed' : 'failed';
|
|
149
|
+
}
|
|
@@ -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, forcedKillAttempted = false) {
|
|
82
|
+
return {
|
|
83
|
+
reason: 'timeout',
|
|
84
|
+
method,
|
|
85
|
+
graceful_signal: 'SIGTERM',
|
|
86
|
+
forced_signal: 'SIGKILL',
|
|
87
|
+
forced_kill_attempted: forcedKillAttempted,
|
|
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
|
+
}
|