@lazyingart/agintiflow 0.20.318 → 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.318",
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(
@@ -5189,6 +5189,13 @@ function boundedReadOnlyTestPrefixCommand(value = "") {
5189
5189
  const command = normalizeProjectCommand(value);
5190
5190
  if (!command) return false;
5191
5191
  const tokens = tokenizeShellWords(command);
5192
+ if (
5193
+ tokens[0] === "cd" &&
5194
+ tokens.length === 2 &&
5195
+ !/(?:`|\$\(|[|<>])/u.test(command)
5196
+ ) {
5197
+ return true;
5198
+ }
5192
5199
  if (
5193
5200
  ["date", "echo", "printf"].includes(String(tokens[0] || "")) &&
5194
5201
  !/(?:`|\$\()/u.test(command)
@@ -5361,6 +5368,55 @@ function projectTestCommandWrapper(command = "") {
5361
5368
  );
5362
5369
  }
5363
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
+
5364
5420
  function projectExitStatusWrapper(command = "") {
5365
5421
  return (
5366
5422
  parseNonMutatingExitStatusWrapper(command) ||
@@ -7248,6 +7304,10 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
7248
7304
  commandMutationPaths.length === 0 &&
7249
7305
  commandIsBoundedReadOnlyArtifactValidation(mutationCommand)
7250
7306
  );
7307
+ const nonMutatingTestOutputPipeline = Boolean(
7308
+ commandMutationPaths.length === 0 &&
7309
+ commandIsNonMutatingTestOutputPipeline(mutationCommand)
7310
+ );
7251
7311
  const projectContentMutation = Boolean(
7252
7312
  command &&
7253
7313
  toolResult.blocked !== true &&
@@ -7257,6 +7317,7 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
7257
7317
  !disposableGeneratedVerificationSideEffects &&
7258
7318
  !scopedTaskArtifactWrite &&
7259
7319
  !readOnlyArtifactValidation &&
7320
+ !nonMutatingTestOutputPipeline &&
7260
7321
  (commandSucceeded || commandMutationPaths.length > 0) &&
7261
7322
  (
7262
7323
  commandCanMutateProjectContent(mutationCommand, commandPolicy) ||
@@ -7277,6 +7338,9 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
7277
7338
  if (readOnlyArtifactValidation) {
7278
7339
  toolResult.readOnlyArtifactValidation = true;
7279
7340
  }
7341
+ if (nonMutatingTestOutputPipeline) {
7342
+ toolResult.nonAuthoritativeTestOutputPipeline = true;
7343
+ }
7280
7344
  if (testCommandWrapper?.semanticallyReadOnly === true) {
7281
7345
  toolResult.boundedReadOnlyTestWrapper = true;
7282
7346
  }
@@ -22632,6 +22696,9 @@ async function completionEvidenceDecision({ config, state, store, observers, ste
22632
22696
  detail.suggestedTestCommands.length
22633
22697
  ? `Use the established test command now: ${detail.suggestedTestCommands.join("; ")}.`
22634
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
+ : "",
22635
22702
  detail.failedProjectTestSummary
22636
22703
  ? `The latest current test run failed: ${detail.failedProjectTestSummary}`
22637
22704
  : "",