@lazyingart/agintiflow 0.20.316 → 0.20.317
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 +192 -0
- package/src/agent-runner.js +242 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.317",
|
|
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",
|
|
@@ -6132,6 +6132,164 @@ try {
|
|
|
6132
6132
|
sandboxMode: "host",
|
|
6133
6133
|
}
|
|
6134
6134
|
);
|
|
6135
|
+
const scopedRelativeTaskRoot = path.relative(workspace, scopedTaskRoot).replace(/\\/g, "/");
|
|
6136
|
+
const liveScopedGroupTestLogCommand = {
|
|
6137
|
+
toolName: "run_command",
|
|
6138
|
+
ok: true,
|
|
6139
|
+
exitCode: 0,
|
|
6140
|
+
args: {
|
|
6141
|
+
command:
|
|
6142
|
+
`cd ${JSON.stringify(workspace)} && ` +
|
|
6143
|
+
`TASK=${scopedRelativeTaskRoot} && LOG="$TASK/document-reader-test.log" && ` +
|
|
6144
|
+
`{ echo "=== focused test ==="; python3 -m unittest discover -s tests -p 'test_reader.py' -v; RC=$?; echo "EXIT=$RC"; } ` +
|
|
6145
|
+
`> "$LOG" 2>&1; tail -n 6 "$LOG"; echo "LOG_OK=$([ -s "$LOG" ] && echo yes || echo no)"`,
|
|
6146
|
+
},
|
|
6147
|
+
projectMutationPaths: [],
|
|
6148
|
+
stdout: "Ran 7 tests in 0.01s\nOK\nEXIT=0\nLOG_OK=yes\n",
|
|
6149
|
+
stderr: "",
|
|
6150
|
+
};
|
|
6151
|
+
recordProjectVerificationOutcome(
|
|
6152
|
+
scopedTaskState,
|
|
6153
|
+
liveScopedGroupTestLogCommand,
|
|
6154
|
+
{
|
|
6155
|
+
commandCwd: workspace,
|
|
6156
|
+
taskProfile: "qa",
|
|
6157
|
+
allowShellTool: true,
|
|
6158
|
+
sandboxMode: "host",
|
|
6159
|
+
}
|
|
6160
|
+
);
|
|
6161
|
+
const liveScopedTeeTestLogCommand = {
|
|
6162
|
+
toolName: "run_command",
|
|
6163
|
+
ok: true,
|
|
6164
|
+
exitCode: 0,
|
|
6165
|
+
args: {
|
|
6166
|
+
command:
|
|
6167
|
+
`cd ${JSON.stringify(workspace)} && ` +
|
|
6168
|
+
`TASK=${scopedRelativeTaskRoot} && LOG="$TASK/document-reader-test.log" && ` +
|
|
6169
|
+
`{ echo "=== focused test ==="; python3 -m unittest discover -s tests -p 'test_reader.py' -v 2>&1; RC=$?; echo "EXIT=$RC"; } ` +
|
|
6170
|
+
`| tee "$LOG"; grep -q '^OK$' "$LOG"; echo "DONE_EXIT=$?"`,
|
|
6171
|
+
},
|
|
6172
|
+
projectMutationPaths: [],
|
|
6173
|
+
stdout: "Ran 7 tests in 0.01s\nOK\nEXIT=0\nDONE_EXIT=0\n",
|
|
6174
|
+
stderr: "",
|
|
6175
|
+
};
|
|
6176
|
+
recordProjectVerificationOutcome(
|
|
6177
|
+
scopedTaskState,
|
|
6178
|
+
liveScopedTeeTestLogCommand,
|
|
6179
|
+
{
|
|
6180
|
+
commandCwd: workspace,
|
|
6181
|
+
taskProfile: "qa",
|
|
6182
|
+
allowShellTool: true,
|
|
6183
|
+
sandboxMode: "host",
|
|
6184
|
+
}
|
|
6185
|
+
);
|
|
6186
|
+
const unsafeScopedLogState = {
|
|
6187
|
+
goal: scopedTaskGoal,
|
|
6188
|
+
commandCwd: workspace,
|
|
6189
|
+
meta: {
|
|
6190
|
+
goalContract: {
|
|
6191
|
+
revision: 1,
|
|
6192
|
+
taskGoal: scopedTaskGoal,
|
|
6193
|
+
currentRequest: "Create and verify output/result.json.",
|
|
6194
|
+
},
|
|
6195
|
+
},
|
|
6196
|
+
};
|
|
6197
|
+
const outsideScopedGroupTestLogCommand = {
|
|
6198
|
+
toolName: "run_command",
|
|
6199
|
+
ok: true,
|
|
6200
|
+
exitCode: 0,
|
|
6201
|
+
args: {
|
|
6202
|
+
command:
|
|
6203
|
+
`cd ${JSON.stringify(workspace)} && LOG="source-tree-test.log" && ` +
|
|
6204
|
+
`{ python3 -m unittest discover -s tests -p 'test_reader.py' -v; RC=$?; echo "EXIT=$RC"; } ` +
|
|
6205
|
+
`> "$LOG" 2>&1; tail -n 6 "$LOG"`,
|
|
6206
|
+
},
|
|
6207
|
+
projectMutationPaths: [],
|
|
6208
|
+
stdout: "Ran 7 tests in 0.01s\nOK\nEXIT=0\n",
|
|
6209
|
+
stderr: "",
|
|
6210
|
+
};
|
|
6211
|
+
recordProjectVerificationOutcome(
|
|
6212
|
+
unsafeScopedLogState,
|
|
6213
|
+
outsideScopedGroupTestLogCommand,
|
|
6214
|
+
{
|
|
6215
|
+
commandCwd: workspace,
|
|
6216
|
+
taskProfile: "qa",
|
|
6217
|
+
allowShellTool: true,
|
|
6218
|
+
sandboxMode: "host",
|
|
6219
|
+
}
|
|
6220
|
+
);
|
|
6221
|
+
const sourceWriteAfterScopedLogState = {
|
|
6222
|
+
goal: scopedTaskGoal,
|
|
6223
|
+
commandCwd: workspace,
|
|
6224
|
+
meta: {
|
|
6225
|
+
goalContract: {
|
|
6226
|
+
revision: 1,
|
|
6227
|
+
taskGoal: scopedTaskGoal,
|
|
6228
|
+
currentRequest: "Create and verify output/result.json.",
|
|
6229
|
+
},
|
|
6230
|
+
},
|
|
6231
|
+
};
|
|
6232
|
+
const sourceWriteAfterScopedLogCommand = {
|
|
6233
|
+
toolName: "run_command",
|
|
6234
|
+
ok: true,
|
|
6235
|
+
exitCode: 0,
|
|
6236
|
+
args: {
|
|
6237
|
+
command:
|
|
6238
|
+
`cd ${JSON.stringify(workspace)} && ` +
|
|
6239
|
+
`TASK=${scopedRelativeTaskRoot} && LOG="$TASK/document-reader-test.log" && ` +
|
|
6240
|
+
`{ python3 -m unittest discover -s tests -p 'test_reader.py' -v; RC=$?; echo "EXIT=$RC"; } ` +
|
|
6241
|
+
`> "$LOG" 2>&1; touch src/agent-runner.js; tail -n 6 "$LOG"`,
|
|
6242
|
+
},
|
|
6243
|
+
projectMutationPaths: [],
|
|
6244
|
+
stdout: "Ran 7 tests in 0.01s\nOK\nEXIT=0\n",
|
|
6245
|
+
stderr: "",
|
|
6246
|
+
};
|
|
6247
|
+
recordProjectVerificationOutcome(
|
|
6248
|
+
sourceWriteAfterScopedLogState,
|
|
6249
|
+
sourceWriteAfterScopedLogCommand,
|
|
6250
|
+
{
|
|
6251
|
+
commandCwd: workspace,
|
|
6252
|
+
taskProfile: "qa",
|
|
6253
|
+
allowShellTool: true,
|
|
6254
|
+
sandboxMode: "host",
|
|
6255
|
+
}
|
|
6256
|
+
);
|
|
6257
|
+
const spoofedCapturedStatusState = {
|
|
6258
|
+
goal: scopedTaskGoal,
|
|
6259
|
+
commandCwd: workspace,
|
|
6260
|
+
meta: {
|
|
6261
|
+
goalContract: {
|
|
6262
|
+
revision: 1,
|
|
6263
|
+
taskGoal: scopedTaskGoal,
|
|
6264
|
+
currentRequest: "Create and verify output/result.json.",
|
|
6265
|
+
},
|
|
6266
|
+
},
|
|
6267
|
+
};
|
|
6268
|
+
const spoofedCapturedStatusCommand = {
|
|
6269
|
+
toolName: "run_command",
|
|
6270
|
+
ok: true,
|
|
6271
|
+
exitCode: 0,
|
|
6272
|
+
args: {
|
|
6273
|
+
command:
|
|
6274
|
+
`cd ${JSON.stringify(workspace)} && ` +
|
|
6275
|
+
`TASK=${scopedRelativeTaskRoot} && LOG="$TASK/document-reader-test.log" && ` +
|
|
6276
|
+
`{ python3 -m unittest discover -s tests -p 'test_reader.py' -v; RC=$?; echo "EXIT=$RC"; } ` +
|
|
6277
|
+
`> "$LOG" 2>&1; tail -n 6 "$LOG"; echo "EXIT=0"`,
|
|
6278
|
+
},
|
|
6279
|
+
projectMutationPaths: [],
|
|
6280
|
+
stdout: "FAILED (failures=1)\nEXIT=1\nEXIT=0\n",
|
|
6281
|
+
stderr: "",
|
|
6282
|
+
};
|
|
6283
|
+
recordProjectVerificationOutcome(
|
|
6284
|
+
spoofedCapturedStatusState,
|
|
6285
|
+
spoofedCapturedStatusCommand,
|
|
6286
|
+
{
|
|
6287
|
+
commandCwd: workspace,
|
|
6288
|
+
taskProfile: "qa",
|
|
6289
|
+
allowShellTool: true,
|
|
6290
|
+
sandboxMode: "host",
|
|
6291
|
+
}
|
|
6292
|
+
);
|
|
6135
6293
|
const nullRedirectValidationCommand = {
|
|
6136
6294
|
toolName: "run_command",
|
|
6137
6295
|
ok: true,
|
|
@@ -6169,6 +6327,17 @@ try {
|
|
|
6169
6327
|
scopedManifestCommand.scopedTaskArtifactWrite === true &&
|
|
6170
6328
|
pythonScopedManifestCommand.scopedTaskArtifactWrite === true &&
|
|
6171
6329
|
scopedCompoundTestLogCommand.scopedTaskArtifactWrite === true &&
|
|
6330
|
+
liveScopedGroupTestLogCommand.scopedTaskArtifactWrite === true &&
|
|
6331
|
+
liveScopedGroupTestLogCommand.projectTest?.passed === true &&
|
|
6332
|
+
liveScopedGroupTestLogCommand.projectTest?.explicitExitStatus === 0 &&
|
|
6333
|
+
liveScopedTeeTestLogCommand.scopedTaskArtifactWrite === true &&
|
|
6334
|
+
liveScopedTeeTestLogCommand.projectTest?.passed === true &&
|
|
6335
|
+
liveScopedTeeTestLogCommand.projectTest?.explicitExitStatus === 0 &&
|
|
6336
|
+
unsafeScopedLogState.meta.projectVerification?.mutationRevision === 1 &&
|
|
6337
|
+
outsideScopedGroupTestLogCommand.scopedTaskArtifactWrite !== true &&
|
|
6338
|
+
sourceWriteAfterScopedLogState.meta.projectVerification?.mutationRevision === 1 &&
|
|
6339
|
+
sourceWriteAfterScopedLogCommand.scopedTaskArtifactWrite !== true &&
|
|
6340
|
+
spoofedCapturedStatusCommand.projectTest === undefined &&
|
|
6172
6341
|
nullRedirectValidationCommand.readOnlyArtifactValidation === true,
|
|
6173
6342
|
`task-scoped delivery bookkeeping invalidated requested artifact evidence: ${JSON.stringify({
|
|
6174
6343
|
requestedOutputRevision,
|
|
@@ -6181,6 +6350,29 @@ try {
|
|
|
6181
6350
|
pythonScopedManifestCommand.scopedTaskArtifactWrite,
|
|
6182
6351
|
scopedCompoundTestLogCommand:
|
|
6183
6352
|
scopedCompoundTestLogCommand.scopedTaskArtifactWrite,
|
|
6353
|
+
liveScopedGroupTestLogCommand: {
|
|
6354
|
+
scopedTaskArtifactWrite: liveScopedGroupTestLogCommand.scopedTaskArtifactWrite,
|
|
6355
|
+
projectTest: liveScopedGroupTestLogCommand.projectTest,
|
|
6356
|
+
},
|
|
6357
|
+
liveScopedTeeTestLogCommand: {
|
|
6358
|
+
scopedTaskArtifactWrite: liveScopedTeeTestLogCommand.scopedTaskArtifactWrite,
|
|
6359
|
+
projectTest: liveScopedTeeTestLogCommand.projectTest,
|
|
6360
|
+
},
|
|
6361
|
+
outsideScopedGroupTestLogCommand: {
|
|
6362
|
+
mutationRevision:
|
|
6363
|
+
unsafeScopedLogState.meta.projectVerification?.mutationRevision,
|
|
6364
|
+
scopedTaskArtifactWrite:
|
|
6365
|
+
outsideScopedGroupTestLogCommand.scopedTaskArtifactWrite,
|
|
6366
|
+
},
|
|
6367
|
+
sourceWriteAfterScopedLogCommand: {
|
|
6368
|
+
mutationRevision:
|
|
6369
|
+
sourceWriteAfterScopedLogState.meta.projectVerification?.mutationRevision,
|
|
6370
|
+
scopedTaskArtifactWrite:
|
|
6371
|
+
sourceWriteAfterScopedLogCommand.scopedTaskArtifactWrite,
|
|
6372
|
+
},
|
|
6373
|
+
spoofedCapturedStatusCommand: {
|
|
6374
|
+
projectTest: spoofedCapturedStatusCommand.projectTest,
|
|
6375
|
+
},
|
|
6184
6376
|
nullRedirectValidationCommand:
|
|
6185
6377
|
nullRedirectValidationCommand.readOnlyArtifactValidation,
|
|
6186
6378
|
})}`
|
package/src/agent-runner.js
CHANGED
|
@@ -4967,6 +4967,130 @@ function normalizeProjectCommand(command = "") {
|
|
|
4967
4967
|
return canonicalizeShellCommand(command);
|
|
4968
4968
|
}
|
|
4969
4969
|
|
|
4970
|
+
function shellBraceGroups(value = "") {
|
|
4971
|
+
const text = String(value || "");
|
|
4972
|
+
const groups = [];
|
|
4973
|
+
let quote = "";
|
|
4974
|
+
let escaped = false;
|
|
4975
|
+
let start = -1;
|
|
4976
|
+
let depth = 0;
|
|
4977
|
+
|
|
4978
|
+
const isGroupOpen = (index) => {
|
|
4979
|
+
if (text[index] !== "{" || text[index - 1] === "$") return false;
|
|
4980
|
+
const before = text[index - 1] || "";
|
|
4981
|
+
const after = text[index + 1] || "";
|
|
4982
|
+
return (!before || /[\s;&|()]/u.test(before)) && /\s/u.test(after);
|
|
4983
|
+
};
|
|
4984
|
+
const isGroupClose = (index) => {
|
|
4985
|
+
if (text[index] !== "}") return false;
|
|
4986
|
+
const before = text[index - 1] || "";
|
|
4987
|
+
const after = text[index + 1] || "";
|
|
4988
|
+
return /\s/u.test(before) && (!after || /[\s;&|<>)]/u.test(after));
|
|
4989
|
+
};
|
|
4990
|
+
|
|
4991
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
4992
|
+
const char = text[index];
|
|
4993
|
+
if (quote === "'") {
|
|
4994
|
+
if (char === "'") quote = "";
|
|
4995
|
+
continue;
|
|
4996
|
+
}
|
|
4997
|
+
if (escaped) {
|
|
4998
|
+
escaped = false;
|
|
4999
|
+
continue;
|
|
5000
|
+
}
|
|
5001
|
+
if (char === "\\") {
|
|
5002
|
+
escaped = true;
|
|
5003
|
+
continue;
|
|
5004
|
+
}
|
|
5005
|
+
if (quote === '"') {
|
|
5006
|
+
if (char === '"') quote = "";
|
|
5007
|
+
continue;
|
|
5008
|
+
}
|
|
5009
|
+
if (char === "'" || char === '"') {
|
|
5010
|
+
quote = char;
|
|
5011
|
+
continue;
|
|
5012
|
+
}
|
|
5013
|
+
if (start < 0 && isGroupOpen(index)) {
|
|
5014
|
+
start = index;
|
|
5015
|
+
depth = 1;
|
|
5016
|
+
continue;
|
|
5017
|
+
}
|
|
5018
|
+
if (start >= 0 && isGroupOpen(index)) {
|
|
5019
|
+
depth += 1;
|
|
5020
|
+
continue;
|
|
5021
|
+
}
|
|
5022
|
+
if (start >= 0 && isGroupClose(index)) {
|
|
5023
|
+
depth -= 1;
|
|
5024
|
+
if (depth > 0) continue;
|
|
5025
|
+
groups.push({
|
|
5026
|
+
start,
|
|
5027
|
+
end: index,
|
|
5028
|
+
body: text.slice(start + 1, index).trim(),
|
|
5029
|
+
});
|
|
5030
|
+
start = -1;
|
|
5031
|
+
depth = 0;
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
return groups;
|
|
5035
|
+
}
|
|
5036
|
+
|
|
5037
|
+
function parseCapturedTestExitStatusWrapper(value = "") {
|
|
5038
|
+
const normalized = normalizeProjectCommand(value);
|
|
5039
|
+
if (!normalized) return null;
|
|
5040
|
+
const label = "(?:EXIT|STATUS|RESULT)(?:_CODE)?";
|
|
5041
|
+
for (const group of shellBraceGroups(normalized)) {
|
|
5042
|
+
const sequence = parseTopLevelShellSequence(group.body);
|
|
5043
|
+
if (
|
|
5044
|
+
sequence.openQuote ||
|
|
5045
|
+
sequence.trailingEscape ||
|
|
5046
|
+
(sequence.trailingSeparator && sequence.trailingSeparator !== ";") ||
|
|
5047
|
+
sequence.commands.length < 3
|
|
5048
|
+
) {
|
|
5049
|
+
continue;
|
|
5050
|
+
}
|
|
5051
|
+
for (let index = 1; index < sequence.commands.length - 1; index += 1) {
|
|
5052
|
+
const statusAssignment = String(sequence.commands[index] || "")
|
|
5053
|
+
.trim()
|
|
5054
|
+
.match(/^([A-Za-z_]\w*)\s*=\s*\$\?$/u);
|
|
5055
|
+
if (!statusAssignment) continue;
|
|
5056
|
+
const statusVariable = statusAssignment[1];
|
|
5057
|
+
const escapedVariable = statusVariable.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
5058
|
+
const statusEcho = String(sequence.commands[index + 1] || "").trim();
|
|
5059
|
+
const echoPattern = new RegExp(
|
|
5060
|
+
`^echo\\s+(?:-n\\s+)?["']?(?<label>${label})\\s*[:=]\\s*\\$(?:\\{${escapedVariable}\\}|${escapedVariable})["']?$`,
|
|
5061
|
+
"iu"
|
|
5062
|
+
);
|
|
5063
|
+
const echoMatch = statusEcho.match(echoPattern);
|
|
5064
|
+
if (!echoMatch) continue;
|
|
5065
|
+
const outsideGroup = `${normalized.slice(0, group.start)} ${normalized.slice(group.end + 1)}`;
|
|
5066
|
+
if (
|
|
5067
|
+
/\b(?:echo|printf)\b[^;&|\n]{0,240}\b(?:EXIT|STATUS|RESULT)(?:_CODE)?\s*[:=]/iu.test(
|
|
5068
|
+
outsideGroup
|
|
5069
|
+
)
|
|
5070
|
+
) {
|
|
5071
|
+
continue;
|
|
5072
|
+
}
|
|
5073
|
+
const testCommand = normalizeProjectCommand(sequence.commands[index - 1]);
|
|
5074
|
+
if (classifyCommand(testCommand).substantiveTest !== true) continue;
|
|
5075
|
+
return {
|
|
5076
|
+
command: testCommand,
|
|
5077
|
+
wrappedCommand: normalized,
|
|
5078
|
+
label: String(echoMatch.groups?.label || "").toUpperCase(),
|
|
5079
|
+
statusSource: "captured-group",
|
|
5080
|
+
statusVariable,
|
|
5081
|
+
};
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
return null;
|
|
5085
|
+
}
|
|
5086
|
+
|
|
5087
|
+
function projectExitStatusWrapper(command = "") {
|
|
5088
|
+
return (
|
|
5089
|
+
parseNonMutatingExitStatusWrapper(command) ||
|
|
5090
|
+
parseCapturedTestExitStatusWrapper(command)
|
|
5091
|
+
);
|
|
5092
|
+
}
|
|
5093
|
+
|
|
4970
5094
|
function normalizeLeadingWorkspaceCd(command = "", config = {}) {
|
|
4971
5095
|
const normalized = normalizeProjectCommand(command);
|
|
4972
5096
|
const sequence = parseTopLevelShellSequence(normalized);
|
|
@@ -5037,7 +5161,7 @@ function isRetainedExactVerificationCommand(command = "", config = {}) {
|
|
|
5037
5161
|
export function isSubstantiveTestCommand(command = "", config = {}) {
|
|
5038
5162
|
if (isRetainedExactVerificationCommand(command, config)) return true;
|
|
5039
5163
|
const normalized = normalizeProjectCommand(normalizeCommandForPolicy(command, config));
|
|
5040
|
-
const text =
|
|
5164
|
+
const text = projectExitStatusWrapper(normalized)?.command || normalized;
|
|
5041
5165
|
if (!text) return false;
|
|
5042
5166
|
const classification = classifyCommand(text);
|
|
5043
5167
|
if (classification.substantiveTest !== true) return false;
|
|
@@ -5142,7 +5266,7 @@ function testRunRepresentsInvalidInvocation(testRun = {}) {
|
|
|
5142
5266
|
|
|
5143
5267
|
function explicitExitProbeStatus(command = "", result = {}) {
|
|
5144
5268
|
const normalizedCommand = normalizeProjectCommand(command);
|
|
5145
|
-
const exitProbe =
|
|
5269
|
+
const exitProbe = projectExitStatusWrapper(normalizedCommand);
|
|
5146
5270
|
if (!exitProbe) {
|
|
5147
5271
|
return {
|
|
5148
5272
|
present: false,
|
|
@@ -5152,9 +5276,19 @@ function explicitExitProbeStatus(command = "", result = {}) {
|
|
|
5152
5276
|
};
|
|
5153
5277
|
}
|
|
5154
5278
|
|
|
5279
|
+
let status = parseExplicitExitStatus(result.stdout || "");
|
|
5280
|
+
if (status === null && exitProbe.statusSource === "captured-group") {
|
|
5281
|
+
const matches = [
|
|
5282
|
+
...String(result.stdout || "").matchAll(
|
|
5283
|
+
/(?:^|\n)\s*(?:EXIT|STATUS|RESULT)(?:_CODE)?\s*[:=]\s*(-?\d+)\s*(?=\n|$)/giu
|
|
5284
|
+
),
|
|
5285
|
+
];
|
|
5286
|
+
if (matches.length) status = Number(matches.at(-1)[1]);
|
|
5287
|
+
}
|
|
5288
|
+
|
|
5155
5289
|
return {
|
|
5156
5290
|
present: true,
|
|
5157
|
-
status
|
|
5291
|
+
status,
|
|
5158
5292
|
command: exitProbe.command,
|
|
5159
5293
|
wrappedCommand: exitProbe.wrappedCommand || exitProbe.command,
|
|
5160
5294
|
};
|
|
@@ -7396,7 +7530,7 @@ export function recordAlreadyCommittedRepositoryRepair(state = {}, toolResult =
|
|
|
7396
7530
|
|
|
7397
7531
|
function projectTestCommandKey(command = "") {
|
|
7398
7532
|
const normalized = normalizeProjectCommand(command);
|
|
7399
|
-
const exitProbe =
|
|
7533
|
+
const exitProbe = projectExitStatusWrapper(normalized);
|
|
7400
7534
|
const invocation = normalizeProjectCommand(exitProbe?.command || normalized)
|
|
7401
7535
|
// These wrappers alter bytecode/output handling, not the tests selected or
|
|
7402
7536
|
// executed. Treating them as distinct suites can leave an old failure
|
|
@@ -12777,40 +12911,106 @@ function commandWritesOnlyEvidenceMatching(command = "", pathMatches = () => fal
|
|
|
12777
12911
|
for (const match of normalized.matchAll(
|
|
12778
12912
|
/(?:^|[\s;&|])([A-Za-z_]\w*)\s*=\s*("[^"\n]+"|'[^'\n]+'|[^\s;&|]+)/g
|
|
12779
12913
|
)) {
|
|
12780
|
-
assignments.set(
|
|
12781
|
-
match[1],
|
|
12782
|
-
String(match[2] || "").replace(/^["']|["']$/g, "")
|
|
12783
|
-
);
|
|
12914
|
+
assignments.set(match[1], String(match[2] || "").replace(/^["']|["']$/g, ""));
|
|
12784
12915
|
}
|
|
12785
|
-
const resolveTarget = (value = "") => {
|
|
12786
|
-
|
|
12916
|
+
const resolveTarget = (value = "", seen = new Set()) => {
|
|
12917
|
+
let target = String(value || "")
|
|
12787
12918
|
.trim()
|
|
12788
12919
|
.replace(/^["']|["']$/g, "");
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12920
|
+
if (!target || /\$\(|`/u.test(target)) return "";
|
|
12921
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
12922
|
+
let unresolved = false;
|
|
12923
|
+
let changed = false;
|
|
12924
|
+
const expanded = target.replace(
|
|
12925
|
+
/\$(?:\{([A-Za-z_]\w*)\}|([A-Za-z_]\w*))/gu,
|
|
12926
|
+
(match, bracedName, bareName) => {
|
|
12927
|
+
const name = bracedName || bareName;
|
|
12928
|
+
if (!assignments.has(name) || seen.has(name)) {
|
|
12929
|
+
unresolved = true;
|
|
12930
|
+
return match;
|
|
12931
|
+
}
|
|
12932
|
+
const nextSeen = new Set(seen);
|
|
12933
|
+
nextSeen.add(name);
|
|
12934
|
+
const resolved = resolveTarget(assignments.get(name), nextSeen);
|
|
12935
|
+
if (!resolved) {
|
|
12936
|
+
unresolved = true;
|
|
12937
|
+
return match;
|
|
12938
|
+
}
|
|
12939
|
+
changed = true;
|
|
12940
|
+
return resolved;
|
|
12941
|
+
}
|
|
12942
|
+
);
|
|
12943
|
+
target = expanded;
|
|
12944
|
+
if (unresolved) return "";
|
|
12945
|
+
if (!changed) return target;
|
|
12946
|
+
}
|
|
12947
|
+
return "";
|
|
12793
12948
|
};
|
|
12794
|
-
const tokens =
|
|
12795
|
-
|
|
12949
|
+
const evidenceTargetsFromTee = (tokens = []) => {
|
|
12950
|
+
if (tokens[0] !== "tee") return [];
|
|
12796
12951
|
let index = 1;
|
|
12797
12952
|
if (["-a", "--append"].includes(tokens[index])) index += 1;
|
|
12798
12953
|
if (tokens[index] === "--") index += 1;
|
|
12799
|
-
|
|
12954
|
+
return tokens.slice(index).map((target) => resolveTarget(target));
|
|
12955
|
+
};
|
|
12956
|
+
const evidenceTargetsFromMkdir = (tokens = []) =>
|
|
12957
|
+
tokens[0] === "mkdir"
|
|
12958
|
+
? tokens
|
|
12959
|
+
.slice(1)
|
|
12960
|
+
.filter((token) => token !== "-p" && token !== "--")
|
|
12961
|
+
.map((target) => resolveTarget(target))
|
|
12962
|
+
: [];
|
|
12963
|
+
const boundedEchoProbeIsReadOnly = (segment = "") => {
|
|
12964
|
+
const source = String(segment || "").trim();
|
|
12965
|
+
const sourceTokens = tokenizeShellWords(source);
|
|
12966
|
+
if (!["echo", "printf"].includes(String(sourceTokens[0] || ""))) return false;
|
|
12967
|
+
if (source.includes("`") || !source.includes("$(")) return !source.includes("`");
|
|
12968
|
+
const substitutions = [];
|
|
12969
|
+
const remainder = source.replace(/\$\(([^()]*)\)/gu, (match, body) => {
|
|
12970
|
+
substitutions.push(String(body || ""));
|
|
12971
|
+
return "";
|
|
12972
|
+
});
|
|
12973
|
+
if (!substitutions.length || remainder.includes("$(")) return false;
|
|
12974
|
+
return substitutions.every((body) => {
|
|
12975
|
+
const sequence = parseTopLevelShellSequence(body);
|
|
12976
|
+
if (
|
|
12977
|
+
sequence.openQuote ||
|
|
12978
|
+
sequence.trailingEscape ||
|
|
12979
|
+
sequence.trailingSeparator ||
|
|
12980
|
+
!sequence.commands.length
|
|
12981
|
+
) {
|
|
12982
|
+
return false;
|
|
12983
|
+
}
|
|
12984
|
+
return sequence.commands.every((candidate) => {
|
|
12985
|
+
const candidateTokens = tokenizeShellWords(candidate);
|
|
12986
|
+
if (["echo", "printf"].includes(String(candidateTokens[0] || ""))) {
|
|
12987
|
+
return !String(candidate || "").includes("$(") && !String(candidate || "").includes("`");
|
|
12988
|
+
}
|
|
12989
|
+
if (["[", "test"].includes(String(candidateTokens[0] || ""))) {
|
|
12990
|
+
const operands = candidateTokens.filter(
|
|
12991
|
+
(token) => !["[", "]", "test", "!", "-e", "-f", "-r", "-s"].includes(token)
|
|
12992
|
+
);
|
|
12993
|
+
return operands.length > 0 && operands.every((operand) => Boolean(resolveTarget(operand)));
|
|
12994
|
+
}
|
|
12995
|
+
const policy = classifyCommand(candidate);
|
|
12996
|
+
return policy.writesWorkspace !== true && policy.mayMutateProject !== true;
|
|
12997
|
+
});
|
|
12998
|
+
});
|
|
12999
|
+
};
|
|
13000
|
+
const tokens = tokenizeShellWords(normalized);
|
|
13001
|
+
if (tokens[0] === "tee") {
|
|
13002
|
+
const targets = evidenceTargetsFromTee(tokens);
|
|
12800
13003
|
return targets.length > 0 && targets.every(pathMatches);
|
|
12801
13004
|
}
|
|
12802
13005
|
if (tokens[0] === "mkdir") {
|
|
12803
|
-
const targets = tokens
|
|
12804
|
-
.slice(1)
|
|
12805
|
-
.filter((token) => token !== "-p" && token !== "--")
|
|
12806
|
-
.map(resolveTarget);
|
|
13006
|
+
const targets = evidenceTargetsFromMkdir(tokens);
|
|
12807
13007
|
return targets.length > 0 && targets.every(pathMatches);
|
|
12808
13008
|
}
|
|
12809
13009
|
|
|
12810
13010
|
let strippedEvidenceRedirect = false;
|
|
12811
13011
|
let rejectedRedirect = false;
|
|
12812
13012
|
const withoutEvidenceRedirects = normalized.replace(
|
|
12813
|
-
/(^|\s)(?:\d*>>?|&>>?)\s*("[^"]+"|'[^']+'
|
|
13013
|
+
/(^|\s)(?:\d*>>?|&>>?)\s*("[^"]+"|'[^']+'|&\d+|[^\s;&|]+)/g,
|
|
12814
13014
|
(match, prefix, target) => {
|
|
12815
13015
|
const resolvedTarget = resolveTarget(target);
|
|
12816
13016
|
if (/^(?:&\d+|\/dev\/null)$/u.test(resolvedTarget)) return prefix;
|
|
@@ -12822,15 +13022,19 @@ function commandWritesOnlyEvidenceMatching(command = "", pathMatches = () => fal
|
|
|
12822
13022
|
return prefix;
|
|
12823
13023
|
}
|
|
12824
13024
|
).trim();
|
|
12825
|
-
if (rejectedRedirect || !
|
|
13025
|
+
if (rejectedRedirect || !withoutEvidenceRedirects) {
|
|
12826
13026
|
return false;
|
|
12827
13027
|
}
|
|
12828
13028
|
const sequence = parseTopLevelShellSequence(withoutEvidenceRedirects);
|
|
12829
13029
|
if (sequence.openQuote || sequence.trailingEscape || sequence.trailingSeparator) {
|
|
12830
13030
|
return false;
|
|
12831
13031
|
}
|
|
12832
|
-
|
|
13032
|
+
let sawEvidenceWrite = strippedEvidenceRedirect;
|
|
13033
|
+
const safeSequence = sequence.commands.every((segment) => {
|
|
12833
13034
|
const withoutAssignments = String(segment || "")
|
|
13035
|
+
.trim()
|
|
13036
|
+
.replace(/^\{\s*/u, "")
|
|
13037
|
+
.replace(/\s*\}$/u, "")
|
|
12834
13038
|
.trim()
|
|
12835
13039
|
.replace(
|
|
12836
13040
|
/^(?:[A-Za-z_]\w*=(?:"[^"\n]*"|'[^'\n]*'|[^\s;&|]+)\s*)+/u,
|
|
@@ -12839,6 +13043,19 @@ function commandWritesOnlyEvidenceMatching(command = "", pathMatches = () => fal
|
|
|
12839
13043
|
.trim();
|
|
12840
13044
|
if (!withoutAssignments) return true;
|
|
12841
13045
|
const commandTokens = tokenizeShellWords(withoutAssignments);
|
|
13046
|
+
const teeTargets = evidenceTargetsFromTee(commandTokens);
|
|
13047
|
+
if (teeTargets.length) {
|
|
13048
|
+
const safe = teeTargets.every(pathMatches);
|
|
13049
|
+
if (safe) sawEvidenceWrite = true;
|
|
13050
|
+
return safe;
|
|
13051
|
+
}
|
|
13052
|
+
const mkdirTargets = evidenceTargetsFromMkdir(commandTokens);
|
|
13053
|
+
if (mkdirTargets.length) {
|
|
13054
|
+
const safe = mkdirTargets.every(pathMatches);
|
|
13055
|
+
if (safe) sawEvidenceWrite = true;
|
|
13056
|
+
return safe;
|
|
13057
|
+
}
|
|
13058
|
+
if (boundedEchoProbeIsReadOnly(withoutAssignments)) return true;
|
|
12842
13059
|
if (
|
|
12843
13060
|
["cat", "cd", "head", "ls", "stat", "tail", "wc"].includes(
|
|
12844
13061
|
String(commandTokens[0] || "")
|
|
@@ -12852,6 +13069,7 @@ function commandWritesOnlyEvidenceMatching(command = "", pathMatches = () => fal
|
|
|
12852
13069
|
underlyingPolicy.mayMutateProject !== true
|
|
12853
13070
|
);
|
|
12854
13071
|
});
|
|
13072
|
+
return safeSequence && sawEvidenceWrite;
|
|
12855
13073
|
}
|
|
12856
13074
|
|
|
12857
13075
|
function commandWritesOnlyPrivateVerificationEvidence(command = "") {
|