@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.317",
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,
@@ -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 < 3
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 (classifyCommand(testCommand).substantiveTest !== true) continue;
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
- /(?:^|\n)\s*(?:EXIT|STATUS|RESULT)(?:_CODE)?\s*[:=]\s*(-?\d+)\s*(?=\n|$)/giu
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
  : "",