@lazyingart/agintiflow 0.20.317 → 0.20.319
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 +162 -0
- package/src/agent-runner.js +361 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.319",
|
|
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",
|
|
@@ -4556,6 +4556,65 @@ try {
|
|
|
4556
4556
|
failedSpacedExitProbeResult.projectTest?.explicitExitStatus === 1,
|
|
4557
4557
|
"a spaced nonzero test-status wrapper was recorded as successful generic evidence"
|
|
4558
4558
|
);
|
|
4559
|
+
const unsafePipedTestCommand =
|
|
4560
|
+
"cd . && PYTHONPATH=src python -m pytest tests/test_wechat_document_reader.py -q 2>&1 | tail -5 && echo '---artifacts---' && ls -la output/task";
|
|
4561
|
+
const unsafePipedTestState = { meta: { goalContract: { revision: 1 } } };
|
|
4562
|
+
const unsafePipedTestResult = {
|
|
4563
|
+
toolName: "run_command",
|
|
4564
|
+
ok: true,
|
|
4565
|
+
exitCode: 0,
|
|
4566
|
+
args: { command: unsafePipedTestCommand },
|
|
4567
|
+
stdout: "....... [100%]\n7 passed in 0.04s\n---artifacts---\n",
|
|
4568
|
+
stderr: "",
|
|
4569
|
+
projectMutationPaths: [],
|
|
4570
|
+
commandPolicy: classifyCommand(unsafePipedTestCommand),
|
|
4571
|
+
};
|
|
4572
|
+
recordProjectVerificationOutcome(
|
|
4573
|
+
unsafePipedTestState,
|
|
4574
|
+
unsafePipedTestResult,
|
|
4575
|
+
{ commandCwd: workspace, taskProfile: "qa", allowShellTool: true, sandboxMode: "host" }
|
|
4576
|
+
);
|
|
4577
|
+
assert(
|
|
4578
|
+
unsafePipedTestState.meta.projectVerification?.mutationRevision === 0 &&
|
|
4579
|
+
unsafePipedTestResult.nonAuthoritativeTestOutputPipeline === true &&
|
|
4580
|
+
unsafePipedTestResult.projectTest === undefined,
|
|
4581
|
+
`an exit-status-hiding test pipeline fabricated source mutation or passing test evidence: ${JSON.stringify({
|
|
4582
|
+
mutationRevision:
|
|
4583
|
+
unsafePipedTestState.meta.projectVerification?.mutationRevision,
|
|
4584
|
+
nonAuthoritativeTestOutputPipeline:
|
|
4585
|
+
unsafePipedTestResult.nonAuthoritativeTestOutputPipeline,
|
|
4586
|
+
projectTest: unsafePipedTestResult.projectTest,
|
|
4587
|
+
policy: unsafePipedTestResult.commandPolicy,
|
|
4588
|
+
})}`
|
|
4589
|
+
);
|
|
4590
|
+
const mutatingPipedTestCommand = `${unsafePipedTestCommand} && touch src/agent-runner.js`;
|
|
4591
|
+
const mutatingPipedTestState = { meta: { goalContract: { revision: 1 } } };
|
|
4592
|
+
const mutatingPipedTestResult = {
|
|
4593
|
+
toolName: "run_command",
|
|
4594
|
+
ok: true,
|
|
4595
|
+
exitCode: 0,
|
|
4596
|
+
args: { command: mutatingPipedTestCommand },
|
|
4597
|
+
stdout: "7 passed in 0.04s\n",
|
|
4598
|
+
stderr: "",
|
|
4599
|
+
projectMutationPaths: [],
|
|
4600
|
+
commandPolicy: classifyCommand(mutatingPipedTestCommand),
|
|
4601
|
+
};
|
|
4602
|
+
recordProjectVerificationOutcome(
|
|
4603
|
+
mutatingPipedTestState,
|
|
4604
|
+
mutatingPipedTestResult,
|
|
4605
|
+
{
|
|
4606
|
+
commandCwd: workspace,
|
|
4607
|
+
taskProfile: "qa",
|
|
4608
|
+
allowShellTool: true,
|
|
4609
|
+
allowDestructive: true,
|
|
4610
|
+
sandboxMode: "host",
|
|
4611
|
+
}
|
|
4612
|
+
);
|
|
4613
|
+
assert(
|
|
4614
|
+
mutatingPipedTestState.meta.projectVerification?.mutationRevision === 1 &&
|
|
4615
|
+
mutatingPipedTestResult.nonAuthoritativeTestOutputPipeline !== true,
|
|
4616
|
+
"a source mutation appended to an unsafe test pipeline escaped revision tracking"
|
|
4617
|
+
);
|
|
4559
4618
|
const pipedUnittestCommand =
|
|
4560
4619
|
"python3 -m unittest discover -s tests -p 'test_wechat_document_reader.py' -v 2>&1 | tail -14; echo \"EXIT=${PIPESTATUS[0]}\"";
|
|
4561
4620
|
assert(
|
|
@@ -6183,6 +6242,84 @@ try {
|
|
|
6183
6242
|
sandboxMode: "host",
|
|
6184
6243
|
}
|
|
6185
6244
|
);
|
|
6245
|
+
const liveScopedOutsideStatusTestLogCommand = {
|
|
6246
|
+
toolName: "run_command",
|
|
6247
|
+
ok: true,
|
|
6248
|
+
exitCode: 0,
|
|
6249
|
+
args: {
|
|
6250
|
+
command:
|
|
6251
|
+
`cd ${JSON.stringify(workspace)} && ` +
|
|
6252
|
+
`TASK=${scopedRelativeTaskRoot} && ` +
|
|
6253
|
+
`{ date -u +"RUN_AT=%FT%TZ"; PYTHONPATH=src python -m pytest tests/test_wechat_document_reader.py -q; } ` +
|
|
6254
|
+
`> "$TASK/document-reader-test.log" 2>&1; rc=$?; echo "PYTEST_EXIT=$rc"; ` +
|
|
6255
|
+
`cat "$TASK/document-reader-test.log"`,
|
|
6256
|
+
},
|
|
6257
|
+
projectMutationPaths: [],
|
|
6258
|
+
stdout: "PYTEST_EXIT=0\n....... [100%]\n7 passed in 0.04s\n",
|
|
6259
|
+
stderr: "",
|
|
6260
|
+
};
|
|
6261
|
+
recordProjectVerificationOutcome(
|
|
6262
|
+
scopedTaskState,
|
|
6263
|
+
liveScopedOutsideStatusTestLogCommand,
|
|
6264
|
+
{
|
|
6265
|
+
commandCwd: workspace,
|
|
6266
|
+
taskProfile: "qa",
|
|
6267
|
+
allowShellTool: true,
|
|
6268
|
+
sandboxMode: "host",
|
|
6269
|
+
}
|
|
6270
|
+
);
|
|
6271
|
+
const conditionalTestCommand =
|
|
6272
|
+
`cd ${JSON.stringify(workspace)} && ` +
|
|
6273
|
+
`if PYTHONPATH=src python -c "import pytest" 2>/dev/null; then ` +
|
|
6274
|
+
`PYTHONPATH=src python -m pytest tests/test_wechat_document_reader.py -q; else ` +
|
|
6275
|
+
`PYTHONPATH=src python -m unittest tests.test_wechat_document_reader -v; fi`;
|
|
6276
|
+
const conditionalTestState = { meta: { goalContract: { revision: 1 } } };
|
|
6277
|
+
const conditionalTestResult = {
|
|
6278
|
+
toolName: "run_command",
|
|
6279
|
+
ok: true,
|
|
6280
|
+
exitCode: 0,
|
|
6281
|
+
args: { command: conditionalTestCommand },
|
|
6282
|
+
projectMutationPaths: [],
|
|
6283
|
+
stdout: "....... [100%]\n7 passed in 0.04s\n",
|
|
6284
|
+
stderr: "",
|
|
6285
|
+
commandPolicy: classifyCommand(conditionalTestCommand),
|
|
6286
|
+
};
|
|
6287
|
+
recordProjectVerificationOutcome(
|
|
6288
|
+
conditionalTestState,
|
|
6289
|
+
conditionalTestResult,
|
|
6290
|
+
{
|
|
6291
|
+
commandCwd: workspace,
|
|
6292
|
+
taskProfile: "qa",
|
|
6293
|
+
allowShellTool: true,
|
|
6294
|
+
sandboxMode: "host",
|
|
6295
|
+
}
|
|
6296
|
+
);
|
|
6297
|
+
const mutatingConditionalTestCommand = conditionalTestCommand.replace(
|
|
6298
|
+
"tests.test_wechat_document_reader -v; fi",
|
|
6299
|
+
"tests.test_wechat_document_reader -v; touch src/agent-runner.js; fi"
|
|
6300
|
+
);
|
|
6301
|
+
const mutatingConditionalTestState = { meta: { goalContract: { revision: 1 } } };
|
|
6302
|
+
const mutatingConditionalTestResult = {
|
|
6303
|
+
toolName: "run_command",
|
|
6304
|
+
ok: true,
|
|
6305
|
+
exitCode: 0,
|
|
6306
|
+
args: { command: mutatingConditionalTestCommand },
|
|
6307
|
+
projectMutationPaths: [],
|
|
6308
|
+
stdout: "7 passed in 0.04s\n",
|
|
6309
|
+
stderr: "",
|
|
6310
|
+
commandPolicy: classifyCommand(mutatingConditionalTestCommand),
|
|
6311
|
+
};
|
|
6312
|
+
recordProjectVerificationOutcome(
|
|
6313
|
+
mutatingConditionalTestState,
|
|
6314
|
+
mutatingConditionalTestResult,
|
|
6315
|
+
{
|
|
6316
|
+
commandCwd: workspace,
|
|
6317
|
+
taskProfile: "qa",
|
|
6318
|
+
allowShellTool: true,
|
|
6319
|
+
allowDestructive: true,
|
|
6320
|
+
sandboxMode: "host",
|
|
6321
|
+
}
|
|
6322
|
+
);
|
|
6186
6323
|
const unsafeScopedLogState = {
|
|
6187
6324
|
goal: scopedTaskGoal,
|
|
6188
6325
|
commandCwd: workspace,
|
|
@@ -6333,6 +6470,14 @@ try {
|
|
|
6333
6470
|
liveScopedTeeTestLogCommand.scopedTaskArtifactWrite === true &&
|
|
6334
6471
|
liveScopedTeeTestLogCommand.projectTest?.passed === true &&
|
|
6335
6472
|
liveScopedTeeTestLogCommand.projectTest?.explicitExitStatus === 0 &&
|
|
6473
|
+
liveScopedOutsideStatusTestLogCommand.scopedTaskArtifactWrite === true &&
|
|
6474
|
+
liveScopedOutsideStatusTestLogCommand.projectTest?.passed === true &&
|
|
6475
|
+
liveScopedOutsideStatusTestLogCommand.projectTest?.explicitExitStatus === 0 &&
|
|
6476
|
+
conditionalTestState.meta.projectVerification?.mutationRevision === 0 &&
|
|
6477
|
+
conditionalTestResult.boundedReadOnlyTestWrapper === true &&
|
|
6478
|
+
conditionalTestResult.projectTest?.passed === true &&
|
|
6479
|
+
mutatingConditionalTestState.meta.projectVerification?.mutationRevision === 1 &&
|
|
6480
|
+
mutatingConditionalTestResult.projectTest === undefined &&
|
|
6336
6481
|
unsafeScopedLogState.meta.projectVerification?.mutationRevision === 1 &&
|
|
6337
6482
|
outsideScopedGroupTestLogCommand.scopedTaskArtifactWrite !== true &&
|
|
6338
6483
|
sourceWriteAfterScopedLogState.meta.projectVerification?.mutationRevision === 1 &&
|
|
@@ -6358,6 +6503,23 @@ try {
|
|
|
6358
6503
|
scopedTaskArtifactWrite: liveScopedTeeTestLogCommand.scopedTaskArtifactWrite,
|
|
6359
6504
|
projectTest: liveScopedTeeTestLogCommand.projectTest,
|
|
6360
6505
|
},
|
|
6506
|
+
liveScopedOutsideStatusTestLogCommand: {
|
|
6507
|
+
scopedTaskArtifactWrite:
|
|
6508
|
+
liveScopedOutsideStatusTestLogCommand.scopedTaskArtifactWrite,
|
|
6509
|
+
projectTest: liveScopedOutsideStatusTestLogCommand.projectTest,
|
|
6510
|
+
},
|
|
6511
|
+
conditionalTestResult: {
|
|
6512
|
+
mutationRevision:
|
|
6513
|
+
conditionalTestState.meta.projectVerification?.mutationRevision,
|
|
6514
|
+
boundedReadOnlyTestWrapper:
|
|
6515
|
+
conditionalTestResult.boundedReadOnlyTestWrapper,
|
|
6516
|
+
projectTest: conditionalTestResult.projectTest,
|
|
6517
|
+
},
|
|
6518
|
+
mutatingConditionalTestResult: {
|
|
6519
|
+
mutationRevision:
|
|
6520
|
+
mutatingConditionalTestState.meta.projectVerification?.mutationRevision,
|
|
6521
|
+
projectTest: mutatingConditionalTestResult.projectTest,
|
|
6522
|
+
},
|
|
6361
6523
|
outsideScopedGroupTestLogCommand: {
|
|
6362
6524
|
mutationRevision:
|
|
6363
6525
|
unsafeScopedLogState.meta.projectVerification?.mutationRevision,
|
package/src/agent-runner.js
CHANGED
|
@@ -5034,6 +5034,190 @@ 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
|
+
tokens[0] === "cd" &&
|
|
5194
|
+
tokens.length === 2 &&
|
|
5195
|
+
!/(?:`|\$\(|[|<>])/u.test(command)
|
|
5196
|
+
) {
|
|
5197
|
+
return true;
|
|
5198
|
+
}
|
|
5199
|
+
if (
|
|
5200
|
+
["date", "echo", "printf"].includes(String(tokens[0] || "")) &&
|
|
5201
|
+
!/(?:`|\$\()/u.test(command)
|
|
5202
|
+
) {
|
|
5203
|
+
return true;
|
|
5204
|
+
}
|
|
5205
|
+
if (
|
|
5206
|
+
["cat", "grep", "head", "stat", "tail", "wc"].includes(
|
|
5207
|
+
String(tokens[0] || "")
|
|
5208
|
+
) &&
|
|
5209
|
+
!/(?:`|\$\(|[|<>])/u.test(command)
|
|
5210
|
+
) {
|
|
5211
|
+
return true;
|
|
5212
|
+
}
|
|
5213
|
+
const policy = classifyCommand(command);
|
|
5214
|
+
return (
|
|
5215
|
+
policy.writesWorkspace !== true &&
|
|
5216
|
+
policy.mayMutateProject !== true &&
|
|
5217
|
+
policy.substantiveTest !== true
|
|
5218
|
+
);
|
|
5219
|
+
}
|
|
5220
|
+
|
|
5037
5221
|
function parseCapturedTestExitStatusWrapper(value = "") {
|
|
5038
5222
|
const normalized = normalizeProjectCommand(value);
|
|
5039
5223
|
if (!normalized) return null;
|
|
@@ -5044,7 +5228,7 @@ function parseCapturedTestExitStatusWrapper(value = "") {
|
|
|
5044
5228
|
sequence.openQuote ||
|
|
5045
5229
|
sequence.trailingEscape ||
|
|
5046
5230
|
(sequence.trailingSeparator && sequence.trailingSeparator !== ";") ||
|
|
5047
|
-
sequence.commands.length <
|
|
5231
|
+
sequence.commands.length < 1
|
|
5048
5232
|
) {
|
|
5049
5233
|
continue;
|
|
5050
5234
|
}
|
|
@@ -5071,7 +5255,7 @@ function parseCapturedTestExitStatusWrapper(value = "") {
|
|
|
5071
5255
|
continue;
|
|
5072
5256
|
}
|
|
5073
5257
|
const testCommand = normalizeProjectCommand(sequence.commands[index - 1]);
|
|
5074
|
-
if (
|
|
5258
|
+
if (boundedTestCommandClassification(testCommand).policy.substantiveTest !== true) continue;
|
|
5075
5259
|
return {
|
|
5076
5260
|
command: testCommand,
|
|
5077
5261
|
wrappedCommand: normalized,
|
|
@@ -5080,10 +5264,159 @@ function parseCapturedTestExitStatusWrapper(value = "") {
|
|
|
5080
5264
|
statusVariable,
|
|
5081
5265
|
};
|
|
5082
5266
|
}
|
|
5267
|
+
|
|
5268
|
+
for (let index = 0; index < sequence.commands.length - 1; index += 1) {
|
|
5269
|
+
const testCommand = normalizeProjectCommand(sequence.commands[index]);
|
|
5270
|
+
const directEcho = capturedStatusEcho(sequence.commands[index + 1]);
|
|
5271
|
+
if (
|
|
5272
|
+
!directEcho ||
|
|
5273
|
+
boundedTestCommandClassification(testCommand).policy.substantiveTest !== true
|
|
5274
|
+
) {
|
|
5275
|
+
continue;
|
|
5276
|
+
}
|
|
5277
|
+
if (
|
|
5278
|
+
sequence.commands.slice(0, index).some(
|
|
5279
|
+
(candidate) => !boundedReadOnlyTestPrefixCommand(candidate)
|
|
5280
|
+
) ||
|
|
5281
|
+
sequence.commands.length !== index + 2
|
|
5282
|
+
) {
|
|
5283
|
+
continue;
|
|
5284
|
+
}
|
|
5285
|
+
const outsideGroup = `${normalized.slice(0, group.start)} ${normalized.slice(group.end + 1)}`;
|
|
5286
|
+
if (
|
|
5287
|
+
/\b(?:echo|printf)\b[^;&|\n]{0,240}\b(?:EXIT|STATUS|RESULT)(?:_CODE)?\s*[:=]/iu.test(
|
|
5288
|
+
outsideGroup
|
|
5289
|
+
)
|
|
5290
|
+
) {
|
|
5291
|
+
continue;
|
|
5292
|
+
}
|
|
5293
|
+
return {
|
|
5294
|
+
command: testCommand,
|
|
5295
|
+
wrappedCommand: normalized,
|
|
5296
|
+
label: String(directEcho.groups?.label || "").toUpperCase(),
|
|
5297
|
+
statusSource: "captured-group",
|
|
5298
|
+
};
|
|
5299
|
+
}
|
|
5300
|
+
|
|
5301
|
+
const bodyCommands = sequence.commands.map((candidate) =>
|
|
5302
|
+
normalizeProjectCommand(candidate)
|
|
5303
|
+
);
|
|
5304
|
+
const testCommand = bodyCommands.at(-1) || "";
|
|
5305
|
+
if (
|
|
5306
|
+
boundedTestCommandClassification(testCommand).policy.substantiveTest !== true ||
|
|
5307
|
+
bodyCommands.slice(0, -1).some(
|
|
5308
|
+
(candidate) => !boundedReadOnlyTestPrefixCommand(candidate)
|
|
5309
|
+
)
|
|
5310
|
+
) {
|
|
5311
|
+
continue;
|
|
5312
|
+
}
|
|
5313
|
+
const suffix = normalized
|
|
5314
|
+
.slice(group.end + 1)
|
|
5315
|
+
.replace(
|
|
5316
|
+
/^\s*(?:(?:\d*>>?|&>>?)\s*(?:"[^"\n]+"|'[^'\n]+'|&\d+|[^\s;&|]+)\s*)+/u,
|
|
5317
|
+
""
|
|
5318
|
+
)
|
|
5319
|
+
.replace(/^\s*;\s*/u, "");
|
|
5320
|
+
const suffixSequence = parseTopLevelShellSequence(suffix);
|
|
5321
|
+
if (
|
|
5322
|
+
!suffix ||
|
|
5323
|
+
suffixSequence.openQuote ||
|
|
5324
|
+
suffixSequence.trailingEscape ||
|
|
5325
|
+
suffixSequence.trailingSeparator ||
|
|
5326
|
+
suffixSequence.commands.length < 2
|
|
5327
|
+
) {
|
|
5328
|
+
continue;
|
|
5329
|
+
}
|
|
5330
|
+
const statusAssignment = String(suffixSequence.commands[0] || "")
|
|
5331
|
+
.trim()
|
|
5332
|
+
.match(/^([A-Za-z_]\w*)\s*=\s*\$\?$/u);
|
|
5333
|
+
if (!statusAssignment) continue;
|
|
5334
|
+
const statusEcho = capturedStatusEcho(
|
|
5335
|
+
suffixSequence.commands[1],
|
|
5336
|
+
statusAssignment[1]
|
|
5337
|
+
);
|
|
5338
|
+
if (!statusEcho) continue;
|
|
5339
|
+
if (
|
|
5340
|
+
suffixSequence.commands.slice(2).some((candidate) => {
|
|
5341
|
+
if (
|
|
5342
|
+
/\b(?:echo|printf)\b[^;&|\n]{0,240}\b(?:[A-Z][A-Z0-9_]{0,31}_)?(?:EXIT|STATUS|RESULT)(?:_CODE)?\s*[:=]/iu.test(
|
|
5343
|
+
candidate
|
|
5344
|
+
)
|
|
5345
|
+
) {
|
|
5346
|
+
return true;
|
|
5347
|
+
}
|
|
5348
|
+
return !boundedReadOnlyTestPrefixCommand(candidate);
|
|
5349
|
+
})
|
|
5350
|
+
) {
|
|
5351
|
+
continue;
|
|
5352
|
+
}
|
|
5353
|
+
return {
|
|
5354
|
+
command: testCommand,
|
|
5355
|
+
wrappedCommand: normalized,
|
|
5356
|
+
label: String(statusEcho.groups?.label || "").toUpperCase(),
|
|
5357
|
+
statusSource: "captured-group",
|
|
5358
|
+
statusVariable: statusAssignment[1],
|
|
5359
|
+
};
|
|
5083
5360
|
}
|
|
5084
5361
|
return null;
|
|
5085
5362
|
}
|
|
5086
5363
|
|
|
5364
|
+
function projectTestCommandWrapper(command = "") {
|
|
5365
|
+
return (
|
|
5366
|
+
parseCapturedTestExitStatusWrapper(command) ||
|
|
5367
|
+
parseBoundedConditionalTestWrapper(command)
|
|
5368
|
+
);
|
|
5369
|
+
}
|
|
5370
|
+
|
|
5371
|
+
function commandIsNonMutatingTestOutputPipeline(command = "") {
|
|
5372
|
+
const normalized = normalizeProjectCommand(command);
|
|
5373
|
+
if (!normalized || /\b(?:PIPESTATUS|pipefail)\b/u.test(normalized)) return false;
|
|
5374
|
+
const sequence = parseTopLevelShellSequence(normalized);
|
|
5375
|
+
if (
|
|
5376
|
+
sequence.openQuote ||
|
|
5377
|
+
sequence.trailingEscape ||
|
|
5378
|
+
sequence.trailingSeparator ||
|
|
5379
|
+
!sequence.commands.length
|
|
5380
|
+
) {
|
|
5381
|
+
return false;
|
|
5382
|
+
}
|
|
5383
|
+
let sawTestPipeline = false;
|
|
5384
|
+
for (let index = 0; index < sequence.commands.length;) {
|
|
5385
|
+
if (sequence.separators[index] === "|") {
|
|
5386
|
+
let pipelineEnd = index + 1;
|
|
5387
|
+
while (sequence.separators[pipelineEnd] === "|") pipelineEnd += 1;
|
|
5388
|
+
const test = boundedTestCommandClassification(sequence.commands[index]);
|
|
5389
|
+
if (test.policy.substantiveTest !== true) return false;
|
|
5390
|
+
const filtersAreReadOnly = sequence.commands
|
|
5391
|
+
.slice(index + 1, pipelineEnd + 1)
|
|
5392
|
+
.every((candidate) => {
|
|
5393
|
+
const tokens = tokenizeShellWords(candidate);
|
|
5394
|
+
if (
|
|
5395
|
+
!["cat", "grep", "head", "sed", "sort", "tail", "tr", "uniq", "wc"].includes(
|
|
5396
|
+
String(tokens[0] || "")
|
|
5397
|
+
) ||
|
|
5398
|
+
/(?:`|\$\(|[<>])/u.test(candidate)
|
|
5399
|
+
) {
|
|
5400
|
+
return false;
|
|
5401
|
+
}
|
|
5402
|
+
const policy = classifyCommand(candidate);
|
|
5403
|
+
return policy.writesWorkspace !== true && policy.mayMutateProject !== true;
|
|
5404
|
+
});
|
|
5405
|
+
if (!filtersAreReadOnly) return false;
|
|
5406
|
+
sawTestPipeline = true;
|
|
5407
|
+
index = pipelineEnd + 1;
|
|
5408
|
+
continue;
|
|
5409
|
+
}
|
|
5410
|
+
const candidate = String(sequence.commands[index] || "")
|
|
5411
|
+
.trim()
|
|
5412
|
+
.replace(/^(?:[A-Za-z_]\w*=(?:"[^"\n]*"|'[^'\n]*'|[^\s;&|]+)\s*)+/u, "")
|
|
5413
|
+
.trim();
|
|
5414
|
+
if (candidate && !boundedReadOnlyTestPrefixCommand(candidate)) return false;
|
|
5415
|
+
index += 1;
|
|
5416
|
+
}
|
|
5417
|
+
return sawTestPipeline;
|
|
5418
|
+
}
|
|
5419
|
+
|
|
5087
5420
|
function projectExitStatusWrapper(command = "") {
|
|
5088
5421
|
return (
|
|
5089
5422
|
parseNonMutatingExitStatusWrapper(command) ||
|
|
@@ -5161,6 +5494,7 @@ function isRetainedExactVerificationCommand(command = "", config = {}) {
|
|
|
5161
5494
|
export function isSubstantiveTestCommand(command = "", config = {}) {
|
|
5162
5495
|
if (isRetainedExactVerificationCommand(command, config)) return true;
|
|
5163
5496
|
const normalized = normalizeProjectCommand(normalizeCommandForPolicy(command, config));
|
|
5497
|
+
if (projectTestCommandWrapper(normalized)) return true;
|
|
5164
5498
|
const text = projectExitStatusWrapper(normalized)?.command || normalized;
|
|
5165
5499
|
if (!text) return false;
|
|
5166
5500
|
const classification = classifyCommand(text);
|
|
@@ -5278,9 +5612,16 @@ function explicitExitProbeStatus(command = "", result = {}) {
|
|
|
5278
5612
|
|
|
5279
5613
|
let status = parseExplicitExitStatus(result.stdout || "");
|
|
5280
5614
|
if (status === null && exitProbe.statusSource === "captured-group") {
|
|
5615
|
+
const escapedLabel = String(exitProbe.label || "")
|
|
5616
|
+
.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
5281
5617
|
const matches = [
|
|
5282
5618
|
...String(result.stdout || "").matchAll(
|
|
5283
|
-
|
|
5619
|
+
escapedLabel
|
|
5620
|
+
? new RegExp(
|
|
5621
|
+
`(?:^|\\n)\\s*${escapedLabel}\\s*[:=]\\s*(-?\\d+)\\s*(?=\\n|$)`,
|
|
5622
|
+
"giu"
|
|
5623
|
+
)
|
|
5624
|
+
: /(?:^|\n)\s*(?:EXIT|STATUS|RESULT)(?:_CODE)?\s*[:=]\s*(-?\d+)\s*(?=\n|$)/giu
|
|
5284
5625
|
),
|
|
5285
5626
|
];
|
|
5286
5627
|
if (matches.length) status = Number(matches.at(-1)[1]);
|
|
@@ -6853,6 +7194,7 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
|
|
|
6853
7194
|
|
|
6854
7195
|
if (toolName === "run_command") {
|
|
6855
7196
|
const command = normalizeProjectCommand(toolResult.args?.command || "");
|
|
7197
|
+
const testCommandWrapper = projectTestCommandWrapper(command);
|
|
6856
7198
|
const retainedExactVerification = isRetainedExactVerificationCommand(
|
|
6857
7199
|
command,
|
|
6858
7200
|
config
|
|
@@ -6962,14 +7304,20 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
|
|
|
6962
7304
|
commandMutationPaths.length === 0 &&
|
|
6963
7305
|
commandIsBoundedReadOnlyArtifactValidation(mutationCommand)
|
|
6964
7306
|
);
|
|
7307
|
+
const nonMutatingTestOutputPipeline = Boolean(
|
|
7308
|
+
commandMutationPaths.length === 0 &&
|
|
7309
|
+
commandIsNonMutatingTestOutputPipeline(mutationCommand)
|
|
7310
|
+
);
|
|
6965
7311
|
const projectContentMutation = Boolean(
|
|
6966
7312
|
command &&
|
|
6967
7313
|
toolResult.blocked !== true &&
|
|
6968
7314
|
!retainedExactVerification &&
|
|
7315
|
+
testCommandWrapper?.semanticallyReadOnly !== true &&
|
|
6969
7316
|
!disposableGeneratedCleanup &&
|
|
6970
7317
|
!disposableGeneratedVerificationSideEffects &&
|
|
6971
7318
|
!scopedTaskArtifactWrite &&
|
|
6972
7319
|
!readOnlyArtifactValidation &&
|
|
7320
|
+
!nonMutatingTestOutputPipeline &&
|
|
6973
7321
|
(commandSucceeded || commandMutationPaths.length > 0) &&
|
|
6974
7322
|
(
|
|
6975
7323
|
commandCanMutateProjectContent(mutationCommand, commandPolicy) ||
|
|
@@ -6990,6 +7338,12 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
|
|
|
6990
7338
|
if (readOnlyArtifactValidation) {
|
|
6991
7339
|
toolResult.readOnlyArtifactValidation = true;
|
|
6992
7340
|
}
|
|
7341
|
+
if (nonMutatingTestOutputPipeline) {
|
|
7342
|
+
toolResult.nonAuthoritativeTestOutputPipeline = true;
|
|
7343
|
+
}
|
|
7344
|
+
if (testCommandWrapper?.semanticallyReadOnly === true) {
|
|
7345
|
+
toolResult.boundedReadOnlyTestWrapper = true;
|
|
7346
|
+
}
|
|
6993
7347
|
let requiredBatch = currentRequiredCommandBatch(verification, requiredCommands);
|
|
6994
7348
|
const activeExecutionContract = state.meta?.activeExecutionContract;
|
|
6995
7349
|
const activeTurnCommands =
|
|
@@ -13043,6 +13397,7 @@ function commandWritesOnlyEvidenceMatching(command = "", pathMatches = () => fal
|
|
|
13043
13397
|
.trim();
|
|
13044
13398
|
if (!withoutAssignments) return true;
|
|
13045
13399
|
const commandTokens = tokenizeShellWords(withoutAssignments);
|
|
13400
|
+
if (boundedReadOnlyTestPrefixCommand(withoutAssignments)) return true;
|
|
13046
13401
|
const teeTargets = evidenceTargetsFromTee(commandTokens);
|
|
13047
13402
|
if (teeTargets.length) {
|
|
13048
13403
|
const safe = teeTargets.every(pathMatches);
|
|
@@ -22341,6 +22696,9 @@ async function completionEvidenceDecision({ config, state, store, observers, ste
|
|
|
22341
22696
|
detail.suggestedTestCommands.length
|
|
22342
22697
|
? `Use the established test command now: ${detail.suggestedTestCommands.join("; ")}.`
|
|
22343
22698
|
: "",
|
|
22699
|
+
detail.missingEvidence.includes("test")
|
|
22700
|
+
? "Run the focused test directly without piping it through tail, head, tee, grep, or another output filter that can replace the test exit status. If bounded output is essential, enable shell pipefail or capture the test's PIPESTATUS immediately and emit one EXIT=<status> marker. Keep artifact inspection in a separate later command."
|
|
22701
|
+
: "",
|
|
22344
22702
|
detail.failedProjectTestSummary
|
|
22345
22703
|
? `The latest current test run failed: ${detail.failedProjectTestSummary}`
|
|
22346
22704
|
: "",
|