@lazyingart/agintiflow 0.20.317 → 0.20.318
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/package.json +1 -1
- package/scripts/smoke-dynamic-step-budget.js +103 -0
- package/src/agent-runner.js +294 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.318",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -6183,6 +6183,84 @@ try {
|
|
|
6183
6183
|
sandboxMode: "host",
|
|
6184
6184
|
}
|
|
6185
6185
|
);
|
|
6186
|
+
const liveScopedOutsideStatusTestLogCommand = {
|
|
6187
|
+
toolName: "run_command",
|
|
6188
|
+
ok: true,
|
|
6189
|
+
exitCode: 0,
|
|
6190
|
+
args: {
|
|
6191
|
+
command:
|
|
6192
|
+
`cd ${JSON.stringify(workspace)} && ` +
|
|
6193
|
+
`TASK=${scopedRelativeTaskRoot} && ` +
|
|
6194
|
+
`{ date -u +"RUN_AT=%FT%TZ"; PYTHONPATH=src python -m pytest tests/test_wechat_document_reader.py -q; } ` +
|
|
6195
|
+
`> "$TASK/document-reader-test.log" 2>&1; rc=$?; echo "PYTEST_EXIT=$rc"; ` +
|
|
6196
|
+
`cat "$TASK/document-reader-test.log"`,
|
|
6197
|
+
},
|
|
6198
|
+
projectMutationPaths: [],
|
|
6199
|
+
stdout: "PYTEST_EXIT=0\n....... [100%]\n7 passed in 0.04s\n",
|
|
6200
|
+
stderr: "",
|
|
6201
|
+
};
|
|
6202
|
+
recordProjectVerificationOutcome(
|
|
6203
|
+
scopedTaskState,
|
|
6204
|
+
liveScopedOutsideStatusTestLogCommand,
|
|
6205
|
+
{
|
|
6206
|
+
commandCwd: workspace,
|
|
6207
|
+
taskProfile: "qa",
|
|
6208
|
+
allowShellTool: true,
|
|
6209
|
+
sandboxMode: "host",
|
|
6210
|
+
}
|
|
6211
|
+
);
|
|
6212
|
+
const conditionalTestCommand =
|
|
6213
|
+
`cd ${JSON.stringify(workspace)} && ` +
|
|
6214
|
+
`if PYTHONPATH=src python -c "import pytest" 2>/dev/null; then ` +
|
|
6215
|
+
`PYTHONPATH=src python -m pytest tests/test_wechat_document_reader.py -q; else ` +
|
|
6216
|
+
`PYTHONPATH=src python -m unittest tests.test_wechat_document_reader -v; fi`;
|
|
6217
|
+
const conditionalTestState = { meta: { goalContract: { revision: 1 } } };
|
|
6218
|
+
const conditionalTestResult = {
|
|
6219
|
+
toolName: "run_command",
|
|
6220
|
+
ok: true,
|
|
6221
|
+
exitCode: 0,
|
|
6222
|
+
args: { command: conditionalTestCommand },
|
|
6223
|
+
projectMutationPaths: [],
|
|
6224
|
+
stdout: "....... [100%]\n7 passed in 0.04s\n",
|
|
6225
|
+
stderr: "",
|
|
6226
|
+
commandPolicy: classifyCommand(conditionalTestCommand),
|
|
6227
|
+
};
|
|
6228
|
+
recordProjectVerificationOutcome(
|
|
6229
|
+
conditionalTestState,
|
|
6230
|
+
conditionalTestResult,
|
|
6231
|
+
{
|
|
6232
|
+
commandCwd: workspace,
|
|
6233
|
+
taskProfile: "qa",
|
|
6234
|
+
allowShellTool: true,
|
|
6235
|
+
sandboxMode: "host",
|
|
6236
|
+
}
|
|
6237
|
+
);
|
|
6238
|
+
const mutatingConditionalTestCommand = conditionalTestCommand.replace(
|
|
6239
|
+
"tests.test_wechat_document_reader -v; fi",
|
|
6240
|
+
"tests.test_wechat_document_reader -v; touch src/agent-runner.js; fi"
|
|
6241
|
+
);
|
|
6242
|
+
const mutatingConditionalTestState = { meta: { goalContract: { revision: 1 } } };
|
|
6243
|
+
const mutatingConditionalTestResult = {
|
|
6244
|
+
toolName: "run_command",
|
|
6245
|
+
ok: true,
|
|
6246
|
+
exitCode: 0,
|
|
6247
|
+
args: { command: mutatingConditionalTestCommand },
|
|
6248
|
+
projectMutationPaths: [],
|
|
6249
|
+
stdout: "7 passed in 0.04s\n",
|
|
6250
|
+
stderr: "",
|
|
6251
|
+
commandPolicy: classifyCommand(mutatingConditionalTestCommand),
|
|
6252
|
+
};
|
|
6253
|
+
recordProjectVerificationOutcome(
|
|
6254
|
+
mutatingConditionalTestState,
|
|
6255
|
+
mutatingConditionalTestResult,
|
|
6256
|
+
{
|
|
6257
|
+
commandCwd: workspace,
|
|
6258
|
+
taskProfile: "qa",
|
|
6259
|
+
allowShellTool: true,
|
|
6260
|
+
allowDestructive: true,
|
|
6261
|
+
sandboxMode: "host",
|
|
6262
|
+
}
|
|
6263
|
+
);
|
|
6186
6264
|
const unsafeScopedLogState = {
|
|
6187
6265
|
goal: scopedTaskGoal,
|
|
6188
6266
|
commandCwd: workspace,
|
|
@@ -6333,6 +6411,14 @@ try {
|
|
|
6333
6411
|
liveScopedTeeTestLogCommand.scopedTaskArtifactWrite === true &&
|
|
6334
6412
|
liveScopedTeeTestLogCommand.projectTest?.passed === true &&
|
|
6335
6413
|
liveScopedTeeTestLogCommand.projectTest?.explicitExitStatus === 0 &&
|
|
6414
|
+
liveScopedOutsideStatusTestLogCommand.scopedTaskArtifactWrite === true &&
|
|
6415
|
+
liveScopedOutsideStatusTestLogCommand.projectTest?.passed === true &&
|
|
6416
|
+
liveScopedOutsideStatusTestLogCommand.projectTest?.explicitExitStatus === 0 &&
|
|
6417
|
+
conditionalTestState.meta.projectVerification?.mutationRevision === 0 &&
|
|
6418
|
+
conditionalTestResult.boundedReadOnlyTestWrapper === true &&
|
|
6419
|
+
conditionalTestResult.projectTest?.passed === true &&
|
|
6420
|
+
mutatingConditionalTestState.meta.projectVerification?.mutationRevision === 1 &&
|
|
6421
|
+
mutatingConditionalTestResult.projectTest === undefined &&
|
|
6336
6422
|
unsafeScopedLogState.meta.projectVerification?.mutationRevision === 1 &&
|
|
6337
6423
|
outsideScopedGroupTestLogCommand.scopedTaskArtifactWrite !== true &&
|
|
6338
6424
|
sourceWriteAfterScopedLogState.meta.projectVerification?.mutationRevision === 1 &&
|
|
@@ -6358,6 +6444,23 @@ try {
|
|
|
6358
6444
|
scopedTaskArtifactWrite: liveScopedTeeTestLogCommand.scopedTaskArtifactWrite,
|
|
6359
6445
|
projectTest: liveScopedTeeTestLogCommand.projectTest,
|
|
6360
6446
|
},
|
|
6447
|
+
liveScopedOutsideStatusTestLogCommand: {
|
|
6448
|
+
scopedTaskArtifactWrite:
|
|
6449
|
+
liveScopedOutsideStatusTestLogCommand.scopedTaskArtifactWrite,
|
|
6450
|
+
projectTest: liveScopedOutsideStatusTestLogCommand.projectTest,
|
|
6451
|
+
},
|
|
6452
|
+
conditionalTestResult: {
|
|
6453
|
+
mutationRevision:
|
|
6454
|
+
conditionalTestState.meta.projectVerification?.mutationRevision,
|
|
6455
|
+
boundedReadOnlyTestWrapper:
|
|
6456
|
+
conditionalTestResult.boundedReadOnlyTestWrapper,
|
|
6457
|
+
projectTest: conditionalTestResult.projectTest,
|
|
6458
|
+
},
|
|
6459
|
+
mutatingConditionalTestResult: {
|
|
6460
|
+
mutationRevision:
|
|
6461
|
+
mutatingConditionalTestState.meta.projectVerification?.mutationRevision,
|
|
6462
|
+
projectTest: mutatingConditionalTestResult.projectTest,
|
|
6463
|
+
},
|
|
6361
6464
|
outsideScopedGroupTestLogCommand: {
|
|
6362
6465
|
mutationRevision:
|
|
6363
6466
|
unsafeScopedLogState.meta.projectVerification?.mutationRevision,
|
package/src/agent-runner.js
CHANGED
|
@@ -5034,6 +5034,183 @@ function shellBraceGroups(value = "") {
|
|
|
5034
5034
|
return groups;
|
|
5035
5035
|
}
|
|
5036
5036
|
|
|
5037
|
+
function unquotedShellControlDelimiter(value = "", keyword = "", startIndex = 0) {
|
|
5038
|
+
const text = String(value || "");
|
|
5039
|
+
const expected = String(keyword || "").toLowerCase();
|
|
5040
|
+
let quote = "";
|
|
5041
|
+
let escaped = false;
|
|
5042
|
+
for (let index = Math.max(0, Number(startIndex || 0)); index < text.length; index += 1) {
|
|
5043
|
+
const char = text[index];
|
|
5044
|
+
if (quote === "'") {
|
|
5045
|
+
if (char === "'") quote = "";
|
|
5046
|
+
continue;
|
|
5047
|
+
}
|
|
5048
|
+
if (escaped) {
|
|
5049
|
+
escaped = false;
|
|
5050
|
+
continue;
|
|
5051
|
+
}
|
|
5052
|
+
if (char === "\\") {
|
|
5053
|
+
escaped = true;
|
|
5054
|
+
continue;
|
|
5055
|
+
}
|
|
5056
|
+
if (quote === '"') {
|
|
5057
|
+
if (char === '"') quote = "";
|
|
5058
|
+
continue;
|
|
5059
|
+
}
|
|
5060
|
+
if (char === "'" || char === '"') {
|
|
5061
|
+
quote = char;
|
|
5062
|
+
continue;
|
|
5063
|
+
}
|
|
5064
|
+
if (char !== ";") continue;
|
|
5065
|
+
const suffix = text.slice(index + 1).match(/^\s*([A-Za-z]+)\b/u);
|
|
5066
|
+
if (String(suffix?.[1] || "").toLowerCase() === expected) return index;
|
|
5067
|
+
}
|
|
5068
|
+
return -1;
|
|
5069
|
+
}
|
|
5070
|
+
|
|
5071
|
+
function boundedTestAvailabilityProbe(value = "") {
|
|
5072
|
+
let command = normalizeProjectCommand(value);
|
|
5073
|
+
if (!command || /(?:`|\$\()/u.test(command)) return false;
|
|
5074
|
+
command = command
|
|
5075
|
+
.replace(/(?:^|\s)(?:\d*>>?|&>>?)\s*(?:"\/dev\/null"|'\/dev\/null'|\/dev\/null)/gu, " ")
|
|
5076
|
+
.trim()
|
|
5077
|
+
.replace(/^(?:[A-Za-z_]\w*=(?:"[^"\n]*"|'[^'\n]*'|[^\s;&|]+)\s*)+/u, "")
|
|
5078
|
+
.trim();
|
|
5079
|
+
const tokens = tokenizeShellWords(command);
|
|
5080
|
+
if (
|
|
5081
|
+
["command", "which"].includes(String(tokens[0] || "")) &&
|
|
5082
|
+
(tokens[0] !== "command" || tokens[1] === "-v")
|
|
5083
|
+
) {
|
|
5084
|
+
const target = tokens[0] === "command" ? tokens[2] : tokens[1];
|
|
5085
|
+
return Boolean(target && /^[A-Za-z0-9_.+-]+$/u.test(target) && tokens.length <= 3);
|
|
5086
|
+
}
|
|
5087
|
+
const executable = String(tokens[0] || "").split(/[\\/]/u).at(-1) || "";
|
|
5088
|
+
const codeIndex = tokens.indexOf("-c");
|
|
5089
|
+
if (!/^python(?:\d+(?:\.\d+)*)?$/iu.test(executable) || codeIndex < 1) return false;
|
|
5090
|
+
const code = String(tokens[codeIndex + 1] || "").trim();
|
|
5091
|
+
if (!code || codeIndex + 2 !== tokens.length) return false;
|
|
5092
|
+
return /^(?:import\s+[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*|from\s+[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*\s+import\s+[A-Za-z_]\w*)$/u.test(
|
|
5093
|
+
code
|
|
5094
|
+
);
|
|
5095
|
+
}
|
|
5096
|
+
|
|
5097
|
+
function boundedTestCommandClassification(value = "") {
|
|
5098
|
+
const command = normalizeProjectCommand(value);
|
|
5099
|
+
const executableCommand = command
|
|
5100
|
+
.replace(/^(?:[A-Za-z_]\w*=(?:"[^"\n]*"|'[^'\n]*'|[^\s;&|]+)\s*)+/u, "")
|
|
5101
|
+
.trim();
|
|
5102
|
+
return {
|
|
5103
|
+
command,
|
|
5104
|
+
executableCommand,
|
|
5105
|
+
policy: classifyCommand(executableCommand),
|
|
5106
|
+
};
|
|
5107
|
+
}
|
|
5108
|
+
|
|
5109
|
+
function parseBoundedConditionalTestWrapper(value = "") {
|
|
5110
|
+
let normalized = normalizeProjectCommand(value);
|
|
5111
|
+
if (!normalized) return null;
|
|
5112
|
+
normalized = normalized.replace(
|
|
5113
|
+
/^cd\s+(?:"[^"\n]+"|'[^'\n]+'|[^\s;&|]+)\s*&&\s*/u,
|
|
5114
|
+
""
|
|
5115
|
+
);
|
|
5116
|
+
if (!/^if\s+/u.test(normalized)) return null;
|
|
5117
|
+
const thenAt = unquotedShellControlDelimiter(normalized, "then", 3);
|
|
5118
|
+
if (thenAt < 0) return null;
|
|
5119
|
+
const thenMatch = normalized.slice(thenAt + 1).match(/^\s*then\b/u);
|
|
5120
|
+
if (!thenMatch) return null;
|
|
5121
|
+
const thenBodyStart = thenAt + 1 + thenMatch[0].length;
|
|
5122
|
+
const elseAt = unquotedShellControlDelimiter(normalized, "else", thenBodyStart);
|
|
5123
|
+
if (elseAt < 0) return null;
|
|
5124
|
+
const elseMatch = normalized.slice(elseAt + 1).match(/^\s*else\b/u);
|
|
5125
|
+
if (!elseMatch) return null;
|
|
5126
|
+
const elseBodyStart = elseAt + 1 + elseMatch[0].length;
|
|
5127
|
+
const fiAt = unquotedShellControlDelimiter(normalized, "fi", elseBodyStart);
|
|
5128
|
+
if (fiAt < 0 || !/^\s*fi\s*$/u.test(normalized.slice(fiAt + 1))) return null;
|
|
5129
|
+
|
|
5130
|
+
const condition = normalized.slice(3, thenAt).trim();
|
|
5131
|
+
const thenCommand = normalizeProjectCommand(
|
|
5132
|
+
normalized.slice(thenBodyStart, elseAt).trim()
|
|
5133
|
+
);
|
|
5134
|
+
const elseCommand = normalizeProjectCommand(
|
|
5135
|
+
normalized.slice(elseBodyStart, fiAt).trim()
|
|
5136
|
+
);
|
|
5137
|
+
if (!boundedTestAvailabilityProbe(condition)) return null;
|
|
5138
|
+
const branches = [thenCommand, elseCommand];
|
|
5139
|
+
const classifications = branches.map((candidate) =>
|
|
5140
|
+
boundedTestCommandClassification(candidate)
|
|
5141
|
+
);
|
|
5142
|
+
const policies = classifications.map((candidate) => candidate.policy);
|
|
5143
|
+
if (
|
|
5144
|
+
branches.some((candidate) => {
|
|
5145
|
+
const sequence = parseTopLevelShellSequence(candidate);
|
|
5146
|
+
return (
|
|
5147
|
+
!candidate ||
|
|
5148
|
+
sequence.openQuote ||
|
|
5149
|
+
sequence.trailingEscape ||
|
|
5150
|
+
sequence.trailingSeparator ||
|
|
5151
|
+
sequence.commands.length !== 1
|
|
5152
|
+
);
|
|
5153
|
+
}) ||
|
|
5154
|
+
policies.some((policy) => policy.substantiveTest !== true)
|
|
5155
|
+
) {
|
|
5156
|
+
return null;
|
|
5157
|
+
}
|
|
5158
|
+
return {
|
|
5159
|
+
command: thenCommand,
|
|
5160
|
+
alternateCommand: elseCommand,
|
|
5161
|
+
wrappedCommand: normalizeProjectCommand(value),
|
|
5162
|
+
statusSource: "process",
|
|
5163
|
+
semanticallyReadOnly: policies.every(
|
|
5164
|
+
(policy, index) =>
|
|
5165
|
+
!commandCanMutateProjectContent(
|
|
5166
|
+
classifications[index].executableCommand,
|
|
5167
|
+
policy
|
|
5168
|
+
)
|
|
5169
|
+
),
|
|
5170
|
+
};
|
|
5171
|
+
}
|
|
5172
|
+
|
|
5173
|
+
function capturedStatusEcho(value = "", statusVariable = "") {
|
|
5174
|
+
const label =
|
|
5175
|
+
"(?:(?:[A-Z][A-Z0-9_]{0,31}_)?(?:EXIT|STATUS|RESULT)(?:_CODE)?)";
|
|
5176
|
+
const variable = String(statusVariable || "");
|
|
5177
|
+
const statusOperand = variable
|
|
5178
|
+
? `\\$(?:\\{${variable.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}\\}|${variable.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")})`
|
|
5179
|
+
: "\\$\\?";
|
|
5180
|
+
return String(value || "").trim().match(
|
|
5181
|
+
new RegExp(
|
|
5182
|
+
`^echo\\s+(?:-n\\s+)?["']?(?<label>${label})\\s*[:=]\\s*${statusOperand}["']?$`,
|
|
5183
|
+
"iu"
|
|
5184
|
+
)
|
|
5185
|
+
);
|
|
5186
|
+
}
|
|
5187
|
+
|
|
5188
|
+
function boundedReadOnlyTestPrefixCommand(value = "") {
|
|
5189
|
+
const command = normalizeProjectCommand(value);
|
|
5190
|
+
if (!command) return false;
|
|
5191
|
+
const tokens = tokenizeShellWords(command);
|
|
5192
|
+
if (
|
|
5193
|
+
["date", "echo", "printf"].includes(String(tokens[0] || "")) &&
|
|
5194
|
+
!/(?:`|\$\()/u.test(command)
|
|
5195
|
+
) {
|
|
5196
|
+
return true;
|
|
5197
|
+
}
|
|
5198
|
+
if (
|
|
5199
|
+
["cat", "grep", "head", "stat", "tail", "wc"].includes(
|
|
5200
|
+
String(tokens[0] || "")
|
|
5201
|
+
) &&
|
|
5202
|
+
!/(?:`|\$\(|[|<>])/u.test(command)
|
|
5203
|
+
) {
|
|
5204
|
+
return true;
|
|
5205
|
+
}
|
|
5206
|
+
const policy = classifyCommand(command);
|
|
5207
|
+
return (
|
|
5208
|
+
policy.writesWorkspace !== true &&
|
|
5209
|
+
policy.mayMutateProject !== true &&
|
|
5210
|
+
policy.substantiveTest !== true
|
|
5211
|
+
);
|
|
5212
|
+
}
|
|
5213
|
+
|
|
5037
5214
|
function parseCapturedTestExitStatusWrapper(value = "") {
|
|
5038
5215
|
const normalized = normalizeProjectCommand(value);
|
|
5039
5216
|
if (!normalized) return null;
|
|
@@ -5044,7 +5221,7 @@ function parseCapturedTestExitStatusWrapper(value = "") {
|
|
|
5044
5221
|
sequence.openQuote ||
|
|
5045
5222
|
sequence.trailingEscape ||
|
|
5046
5223
|
(sequence.trailingSeparator && sequence.trailingSeparator !== ";") ||
|
|
5047
|
-
sequence.commands.length <
|
|
5224
|
+
sequence.commands.length < 1
|
|
5048
5225
|
) {
|
|
5049
5226
|
continue;
|
|
5050
5227
|
}
|
|
@@ -5071,7 +5248,7 @@ function parseCapturedTestExitStatusWrapper(value = "") {
|
|
|
5071
5248
|
continue;
|
|
5072
5249
|
}
|
|
5073
5250
|
const testCommand = normalizeProjectCommand(sequence.commands[index - 1]);
|
|
5074
|
-
if (
|
|
5251
|
+
if (boundedTestCommandClassification(testCommand).policy.substantiveTest !== true) continue;
|
|
5075
5252
|
return {
|
|
5076
5253
|
command: testCommand,
|
|
5077
5254
|
wrappedCommand: normalized,
|
|
@@ -5080,10 +5257,110 @@ function parseCapturedTestExitStatusWrapper(value = "") {
|
|
|
5080
5257
|
statusVariable,
|
|
5081
5258
|
};
|
|
5082
5259
|
}
|
|
5260
|
+
|
|
5261
|
+
for (let index = 0; index < sequence.commands.length - 1; index += 1) {
|
|
5262
|
+
const testCommand = normalizeProjectCommand(sequence.commands[index]);
|
|
5263
|
+
const directEcho = capturedStatusEcho(sequence.commands[index + 1]);
|
|
5264
|
+
if (
|
|
5265
|
+
!directEcho ||
|
|
5266
|
+
boundedTestCommandClassification(testCommand).policy.substantiveTest !== true
|
|
5267
|
+
) {
|
|
5268
|
+
continue;
|
|
5269
|
+
}
|
|
5270
|
+
if (
|
|
5271
|
+
sequence.commands.slice(0, index).some(
|
|
5272
|
+
(candidate) => !boundedReadOnlyTestPrefixCommand(candidate)
|
|
5273
|
+
) ||
|
|
5274
|
+
sequence.commands.length !== index + 2
|
|
5275
|
+
) {
|
|
5276
|
+
continue;
|
|
5277
|
+
}
|
|
5278
|
+
const outsideGroup = `${normalized.slice(0, group.start)} ${normalized.slice(group.end + 1)}`;
|
|
5279
|
+
if (
|
|
5280
|
+
/\b(?:echo|printf)\b[^;&|\n]{0,240}\b(?:EXIT|STATUS|RESULT)(?:_CODE)?\s*[:=]/iu.test(
|
|
5281
|
+
outsideGroup
|
|
5282
|
+
)
|
|
5283
|
+
) {
|
|
5284
|
+
continue;
|
|
5285
|
+
}
|
|
5286
|
+
return {
|
|
5287
|
+
command: testCommand,
|
|
5288
|
+
wrappedCommand: normalized,
|
|
5289
|
+
label: String(directEcho.groups?.label || "").toUpperCase(),
|
|
5290
|
+
statusSource: "captured-group",
|
|
5291
|
+
};
|
|
5292
|
+
}
|
|
5293
|
+
|
|
5294
|
+
const bodyCommands = sequence.commands.map((candidate) =>
|
|
5295
|
+
normalizeProjectCommand(candidate)
|
|
5296
|
+
);
|
|
5297
|
+
const testCommand = bodyCommands.at(-1) || "";
|
|
5298
|
+
if (
|
|
5299
|
+
boundedTestCommandClassification(testCommand).policy.substantiveTest !== true ||
|
|
5300
|
+
bodyCommands.slice(0, -1).some(
|
|
5301
|
+
(candidate) => !boundedReadOnlyTestPrefixCommand(candidate)
|
|
5302
|
+
)
|
|
5303
|
+
) {
|
|
5304
|
+
continue;
|
|
5305
|
+
}
|
|
5306
|
+
const suffix = normalized
|
|
5307
|
+
.slice(group.end + 1)
|
|
5308
|
+
.replace(
|
|
5309
|
+
/^\s*(?:(?:\d*>>?|&>>?)\s*(?:"[^"\n]+"|'[^'\n]+'|&\d+|[^\s;&|]+)\s*)+/u,
|
|
5310
|
+
""
|
|
5311
|
+
)
|
|
5312
|
+
.replace(/^\s*;\s*/u, "");
|
|
5313
|
+
const suffixSequence = parseTopLevelShellSequence(suffix);
|
|
5314
|
+
if (
|
|
5315
|
+
!suffix ||
|
|
5316
|
+
suffixSequence.openQuote ||
|
|
5317
|
+
suffixSequence.trailingEscape ||
|
|
5318
|
+
suffixSequence.trailingSeparator ||
|
|
5319
|
+
suffixSequence.commands.length < 2
|
|
5320
|
+
) {
|
|
5321
|
+
continue;
|
|
5322
|
+
}
|
|
5323
|
+
const statusAssignment = String(suffixSequence.commands[0] || "")
|
|
5324
|
+
.trim()
|
|
5325
|
+
.match(/^([A-Za-z_]\w*)\s*=\s*\$\?$/u);
|
|
5326
|
+
if (!statusAssignment) continue;
|
|
5327
|
+
const statusEcho = capturedStatusEcho(
|
|
5328
|
+
suffixSequence.commands[1],
|
|
5329
|
+
statusAssignment[1]
|
|
5330
|
+
);
|
|
5331
|
+
if (!statusEcho) continue;
|
|
5332
|
+
if (
|
|
5333
|
+
suffixSequence.commands.slice(2).some((candidate) => {
|
|
5334
|
+
if (
|
|
5335
|
+
/\b(?:echo|printf)\b[^;&|\n]{0,240}\b(?:[A-Z][A-Z0-9_]{0,31}_)?(?:EXIT|STATUS|RESULT)(?:_CODE)?\s*[:=]/iu.test(
|
|
5336
|
+
candidate
|
|
5337
|
+
)
|
|
5338
|
+
) {
|
|
5339
|
+
return true;
|
|
5340
|
+
}
|
|
5341
|
+
return !boundedReadOnlyTestPrefixCommand(candidate);
|
|
5342
|
+
})
|
|
5343
|
+
) {
|
|
5344
|
+
continue;
|
|
5345
|
+
}
|
|
5346
|
+
return {
|
|
5347
|
+
command: testCommand,
|
|
5348
|
+
wrappedCommand: normalized,
|
|
5349
|
+
label: String(statusEcho.groups?.label || "").toUpperCase(),
|
|
5350
|
+
statusSource: "captured-group",
|
|
5351
|
+
statusVariable: statusAssignment[1],
|
|
5352
|
+
};
|
|
5083
5353
|
}
|
|
5084
5354
|
return null;
|
|
5085
5355
|
}
|
|
5086
5356
|
|
|
5357
|
+
function projectTestCommandWrapper(command = "") {
|
|
5358
|
+
return (
|
|
5359
|
+
parseCapturedTestExitStatusWrapper(command) ||
|
|
5360
|
+
parseBoundedConditionalTestWrapper(command)
|
|
5361
|
+
);
|
|
5362
|
+
}
|
|
5363
|
+
|
|
5087
5364
|
function projectExitStatusWrapper(command = "") {
|
|
5088
5365
|
return (
|
|
5089
5366
|
parseNonMutatingExitStatusWrapper(command) ||
|
|
@@ -5161,6 +5438,7 @@ function isRetainedExactVerificationCommand(command = "", config = {}) {
|
|
|
5161
5438
|
export function isSubstantiveTestCommand(command = "", config = {}) {
|
|
5162
5439
|
if (isRetainedExactVerificationCommand(command, config)) return true;
|
|
5163
5440
|
const normalized = normalizeProjectCommand(normalizeCommandForPolicy(command, config));
|
|
5441
|
+
if (projectTestCommandWrapper(normalized)) return true;
|
|
5164
5442
|
const text = projectExitStatusWrapper(normalized)?.command || normalized;
|
|
5165
5443
|
if (!text) return false;
|
|
5166
5444
|
const classification = classifyCommand(text);
|
|
@@ -5278,9 +5556,16 @@ function explicitExitProbeStatus(command = "", result = {}) {
|
|
|
5278
5556
|
|
|
5279
5557
|
let status = parseExplicitExitStatus(result.stdout || "");
|
|
5280
5558
|
if (status === null && exitProbe.statusSource === "captured-group") {
|
|
5559
|
+
const escapedLabel = String(exitProbe.label || "")
|
|
5560
|
+
.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
5281
5561
|
const matches = [
|
|
5282
5562
|
...String(result.stdout || "").matchAll(
|
|
5283
|
-
|
|
5563
|
+
escapedLabel
|
|
5564
|
+
? new RegExp(
|
|
5565
|
+
`(?:^|\\n)\\s*${escapedLabel}\\s*[:=]\\s*(-?\\d+)\\s*(?=\\n|$)`,
|
|
5566
|
+
"giu"
|
|
5567
|
+
)
|
|
5568
|
+
: /(?:^|\n)\s*(?:EXIT|STATUS|RESULT)(?:_CODE)?\s*[:=]\s*(-?\d+)\s*(?=\n|$)/giu
|
|
5284
5569
|
),
|
|
5285
5570
|
];
|
|
5286
5571
|
if (matches.length) status = Number(matches.at(-1)[1]);
|
|
@@ -6853,6 +7138,7 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
|
|
|
6853
7138
|
|
|
6854
7139
|
if (toolName === "run_command") {
|
|
6855
7140
|
const command = normalizeProjectCommand(toolResult.args?.command || "");
|
|
7141
|
+
const testCommandWrapper = projectTestCommandWrapper(command);
|
|
6856
7142
|
const retainedExactVerification = isRetainedExactVerificationCommand(
|
|
6857
7143
|
command,
|
|
6858
7144
|
config
|
|
@@ -6966,6 +7252,7 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
|
|
|
6966
7252
|
command &&
|
|
6967
7253
|
toolResult.blocked !== true &&
|
|
6968
7254
|
!retainedExactVerification &&
|
|
7255
|
+
testCommandWrapper?.semanticallyReadOnly !== true &&
|
|
6969
7256
|
!disposableGeneratedCleanup &&
|
|
6970
7257
|
!disposableGeneratedVerificationSideEffects &&
|
|
6971
7258
|
!scopedTaskArtifactWrite &&
|
|
@@ -6990,6 +7277,9 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
|
|
|
6990
7277
|
if (readOnlyArtifactValidation) {
|
|
6991
7278
|
toolResult.readOnlyArtifactValidation = true;
|
|
6992
7279
|
}
|
|
7280
|
+
if (testCommandWrapper?.semanticallyReadOnly === true) {
|
|
7281
|
+
toolResult.boundedReadOnlyTestWrapper = true;
|
|
7282
|
+
}
|
|
6993
7283
|
let requiredBatch = currentRequiredCommandBatch(verification, requiredCommands);
|
|
6994
7284
|
const activeExecutionContract = state.meta?.activeExecutionContract;
|
|
6995
7285
|
const activeTurnCommands =
|
|
@@ -13043,6 +13333,7 @@ function commandWritesOnlyEvidenceMatching(command = "", pathMatches = () => fal
|
|
|
13043
13333
|
.trim();
|
|
13044
13334
|
if (!withoutAssignments) return true;
|
|
13045
13335
|
const commandTokens = tokenizeShellWords(withoutAssignments);
|
|
13336
|
+
if (boundedReadOnlyTestPrefixCommand(withoutAssignments)) return true;
|
|
13046
13337
|
const teeTargets = evidenceTargetsFromTee(commandTokens);
|
|
13047
13338
|
if (teeTargets.length) {
|
|
13048
13339
|
const safe = teeTargets.every(pathMatches);
|