@lazyingart/agintiflow 0.20.315 → 0.20.316

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.315",
3
+ "version": "0.20.316",
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",
@@ -1431,7 +1431,7 @@ try {
1431
1431
  "host workspace chmod sequence should remain categorized as permission-change"
1432
1432
  );
1433
1433
  const androidReadonlyProbePolicy = evaluateCommandPolicy(
1434
- 'test -x /usr/bin/gradle && echo "GRADLE_OK" ; test -x /usr/bin/java && /usr/bin/java -version 2>&1 | head -1; adb devices; emulator -list-avds; find android-app -type f | sort',
1434
+ 'test -x /usr/bin/gradle && echo "GRADLE_OK" ; test -x /usr/bin/java && /usr/bin/java -version 2>&1 | head -1; adb devices; emulator -list-avds; find android-app -maxdepth 16 -type f | sort',
1435
1435
  hostWorkspacePolicy
1436
1436
  );
1437
1437
  assert(androidReadonlyProbePolicy.allowed, "Android host read-only probes should not require full-host destructive access");
@@ -4556,6 +4556,36 @@ 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 pipedUnittestCommand =
4560
+ "python3 -m unittest discover -s tests -p 'test_wechat_document_reader.py' -v 2>&1 | tail -14; echo \"EXIT=${PIPESTATUS[0]}\"";
4561
+ assert(
4562
+ parseNonMutatingExitStatusWrapper(pipedUnittestCommand)?.command ===
4563
+ "python3 -m unittest discover -s tests -p 'test_wechat_document_reader.py' -v 2>&1" &&
4564
+ isSubstantiveTestCommand(pipedUnittestCommand),
4565
+ "the exact bounded PIPESTATUS unittest wrapper lost its test identity"
4566
+ );
4567
+ for (const [status, expectedPassed] of [[0, true], [1, false]]) {
4568
+ const state = { meta: { goalContract: { revision: 1 } } };
4569
+ const result = {
4570
+ toolName: "run_command",
4571
+ ok: true,
4572
+ exitCode: 0,
4573
+ args: { command: pipedUnittestCommand },
4574
+ stdout: `Ran 7 tests in 0.01s\n${status === 0 ? "OK" : "FAILED (failures=1)"}\nEXIT=${status}\n`,
4575
+ stderr: "",
4576
+ commandPolicy: classifyCommand(pipedUnittestCommand),
4577
+ };
4578
+ recordProjectVerificationOutcome(state, result, {
4579
+ commandCwd: workspace,
4580
+ taskProfile: "qa",
4581
+ });
4582
+ assert(
4583
+ result.projectTest?.passed === expectedPassed &&
4584
+ result.projectTest?.explicitExitStatus === status &&
4585
+ state.meta.projectVerification?.mutationRevision === 0,
4586
+ `a PIPESTATUS unittest wrapper was not recorded at the correct status/revision: ${status}`
4587
+ );
4588
+ }
4559
4589
  for (const [command, output, expectedRevision] of [
4560
4590
  ['npm test; printf "EXIT_CODE=%d\\n" "$?"', "EXIT_CODE=0\n", 0],
4561
4591
  ['git push; echo "EXIT=$?"', "EXIT=0\n", 0],
@@ -4579,6 +4609,50 @@ try {
4579
4609
  `a status wrapper changed the inner command's mutation identity: ${command}`
4580
4610
  );
4581
4611
  }
4612
+ const boundedFindCommand =
4613
+ `cd ${workspace} && echo "=== CANDIDATES ===" && ` +
4614
+ "find . -maxdepth 4 \\( -name '*.py' -o -name '*.md' \\) -not -path './output/*' 2>/dev/null | sort; echo DONE";
4615
+ const boundedFindPolicy = evaluateCommandPolicy(boundedFindCommand, {
4616
+ commandCwd: workspace,
4617
+ allowShellTool: true,
4618
+ allowDestructive: false,
4619
+ sandboxMode: "host",
4620
+ });
4621
+ assert(
4622
+ boundedFindPolicy.allowed === true &&
4623
+ boundedFindPolicy.category === "read-only" &&
4624
+ boundedFindPolicy.writesWorkspace === false,
4625
+ "the exact bounded read-only find sequence was classified as a project mutation"
4626
+ );
4627
+ const boundedFindState = { meta: { goalContract: { revision: 1 } } };
4628
+ const boundedFindResult = {
4629
+ toolName: "run_command",
4630
+ ok: true,
4631
+ exitCode: 0,
4632
+ args: { command: boundedFindCommand },
4633
+ stdout: "./tests/test_wechat_document_reader.py\nDONE\n",
4634
+ stderr: "",
4635
+ commandPolicy: boundedFindPolicy,
4636
+ };
4637
+ recordProjectVerificationOutcome(boundedFindState, boundedFindResult, {
4638
+ commandCwd: workspace,
4639
+ taskProfile: "qa",
4640
+ });
4641
+ assert(
4642
+ boundedFindState.meta.projectVerification?.mutationRevision === 0,
4643
+ "a successful bounded read-only find fabricated a source revision"
4644
+ );
4645
+ for (const command of [
4646
+ "find . -maxdepth 4 -name '*.py' -delete",
4647
+ "find . -maxdepth 4 -name '*.py' -exec touch {} \\;",
4648
+ ]) {
4649
+ const classification = classifyCommand(command);
4650
+ assert(
4651
+ classification.writesWorkspace === true &&
4652
+ ["destructive", "general-shell"].includes(classification.category),
4653
+ `a write-capable find form was accepted as read-only: ${command}`
4654
+ );
4655
+ }
4582
4656
  const readOnlyValidatorState = { meta: { goalContract: { revision: 1 } } };
4583
4657
  const readOnlyValidatorResult = {
4584
4658
  toolName: "run_command",
@@ -5143,12 +5143,20 @@ function testRunRepresentsInvalidInvocation(testRun = {}) {
5143
5143
  function explicitExitProbeStatus(command = "", result = {}) {
5144
5144
  const normalizedCommand = normalizeProjectCommand(command);
5145
5145
  const exitProbe = parseNonMutatingExitStatusWrapper(normalizedCommand);
5146
- if (!exitProbe) return { present: false, status: null, command: normalizedCommand };
5146
+ if (!exitProbe) {
5147
+ return {
5148
+ present: false,
5149
+ status: null,
5150
+ command: normalizedCommand,
5151
+ wrappedCommand: normalizedCommand,
5152
+ };
5153
+ }
5147
5154
 
5148
5155
  return {
5149
5156
  present: true,
5150
5157
  status: parseExplicitExitStatus(result.stdout || ""),
5151
5158
  command: exitProbe.command,
5159
+ wrappedCommand: exitProbe.wrappedCommand || exitProbe.command,
5152
5160
  };
5153
5161
  }
5154
5162
 
@@ -6717,7 +6725,10 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
6717
6725
  );
6718
6726
  const exitProbe = explicitExitProbeStatus(command, toolResult);
6719
6727
  const mutationCommand = normalizeProjectCommand(
6720
- normalizeCommandForPolicy(exitProbe.command || command, config)
6728
+ normalizeCommandForPolicy(
6729
+ exitProbe.wrappedCommand || exitProbe.command || command,
6730
+ config
6731
+ )
6721
6732
  );
6722
6733
  // The result proves the command already executed. Classify its semantic
6723
6734
  // mutation capability independently of whether the current policy would
@@ -17,7 +17,6 @@ const READ_ONLY_PATTERNS = [
17
17
  /^command\s+-v\s+[-\w.]+$/,
18
18
  /^uname(?:\s+-a)?$/,
19
19
  /^ls(?:\s+(?:"[^"\n]*"|'[^'\n]*'|[-\w./~*]+))*$/,
20
- /^find(?:\s+[./~\w-]+)*(?:\s+-maxdepth\s+\d+)?(?:\s+-type\s+[fd])?$/,
21
20
  /^rg(?:\s+.+)?$/,
22
21
  /^grep(?:\s+.+)?$/,
23
22
  /^pgrep(?:\s+.+)?$/,
@@ -143,8 +142,71 @@ function isReadOnlyFindCommand(command = "") {
143
142
  if (!/^find\s+/.test(normalized)) return false;
144
143
  if (/(^|\s)(-delete|-exec|-execdir|-ok|-okdir|-fprint|-fprintf|-fls)\b/.test(normalized)) return false;
145
144
  const unquoted = stripQuotedSegments(normalized);
146
- if (/[|<>;&`$]/.test(unquoted)) return false;
147
- return /^find\s+(?:[-./~\w]+|\/workspace)(?:\s+[-\w]+(?:\s+(?:"[^"\n]*"|'[^'\n]*'|[^\s|<>;&`$]+))?)*$/.test(normalized);
145
+ if (/[|<>;&`$]/.test(unquoted) || hasActiveShellExpansion(normalized)) return false;
146
+
147
+ const tokens = tokenizeShellWords(normalized);
148
+ if (tokens[0] !== "find" || tokens.length < 4) return false;
149
+ let index = 1;
150
+ let roots = 0;
151
+ while (index < tokens.length && !tokens[index].startsWith("-") && !["(", ")", "!"].includes(tokens[index])) {
152
+ const root = tokens[index];
153
+ if (!/^(?:\.{1,2}|\/workspace(?:\/[-\w./~*]+)?|~?(?:\/)?[-\w./*]+)$/.test(root)) return false;
154
+ roots += 1;
155
+ index += 1;
156
+ }
157
+ if (!roots) return false;
158
+
159
+ let maxDepth = null;
160
+ let parenthesisDepth = 0;
161
+ const patternPredicates = new Set([
162
+ "-name",
163
+ "-iname",
164
+ "-path",
165
+ "-ipath",
166
+ "-wholename",
167
+ "-iwholename",
168
+ ]);
169
+ const operators = new Set(["!", "-not", "-o", "-or", "-a", "-and", "-true", "-false", "-empty", "-print", "-print0", "-quit"]);
170
+ while (index < tokens.length) {
171
+ const token = tokens[index];
172
+ if (token === "(") {
173
+ parenthesisDepth += 1;
174
+ index += 1;
175
+ continue;
176
+ }
177
+ if (token === ")") {
178
+ parenthesisDepth -= 1;
179
+ if (parenthesisDepth < 0) return false;
180
+ index += 1;
181
+ continue;
182
+ }
183
+ if (operators.has(token)) {
184
+ index += 1;
185
+ continue;
186
+ }
187
+ const argument = tokens[index + 1];
188
+ if (!argument || /[\u0000-\u001f\u007f]/u.test(argument)) return false;
189
+ if (token === "-maxdepth" || token === "-mindepth") {
190
+ if (!/^\d+$/.test(argument)) return false;
191
+ const depth = Number(argument);
192
+ if (!Number.isSafeInteger(depth) || depth > 64) return false;
193
+ if (token === "-maxdepth") maxDepth = depth;
194
+ index += 2;
195
+ continue;
196
+ }
197
+ if (token === "-type") {
198
+ if (!/^[bcdpflsD]$/.test(argument)) return false;
199
+ index += 2;
200
+ continue;
201
+ }
202
+ if (patternPredicates.has(token)) {
203
+ if (argument.length > 1024) return false;
204
+ index += 2;
205
+ continue;
206
+ }
207
+ return false;
208
+ }
209
+ return parenthesisDepth === 0 && maxDepth !== null;
148
210
  }
149
211
 
150
212
  function isUnboundedRecursiveGrep(command = "") {
@@ -2227,10 +2227,51 @@ function normalizeProjectCommand(value = "") {
2227
2227
  return canonicalizeShellCommand(value);
2228
2228
  }
2229
2229
 
2230
+ function pipelineStatusCommand(value = "", pipelineIndex = 0) {
2231
+ const sequence = parseTopLevelShellSequence(value);
2232
+ if (
2233
+ sequence.openQuote ||
2234
+ sequence.trailingEscape ||
2235
+ sequence.trailingSeparator ||
2236
+ sequence.commands.length < 2 ||
2237
+ sequence.separators.at(-1) !== "|"
2238
+ ) {
2239
+ return "";
2240
+ }
2241
+
2242
+ let pipelineStart = sequence.commands.length - 1;
2243
+ while (pipelineStart > 0 && sequence.separators[pipelineStart - 1] === "|") {
2244
+ pipelineStart -= 1;
2245
+ }
2246
+ const commandIndex = pipelineStart + Number(pipelineIndex || 0);
2247
+ if (commandIndex < pipelineStart || commandIndex >= sequence.commands.length) return "";
2248
+ return normalizeProjectCommand(sequence.commands[commandIndex]);
2249
+ }
2250
+
2230
2251
  export function parseNonMutatingExitStatusWrapper(value = "") {
2231
2252
  const normalized = normalizeProjectCommand(value);
2232
2253
  if (!normalized) return null;
2233
2254
  const label = "(?:EXIT|STATUS|RESULT)(?:_CODE)?";
2255
+ const pipelinePattern = new RegExp(
2256
+ `^(?<pipelineCommand>[\\s\\S]+);\\s*echo\\s+(?:"(?<doubleLabel>${label})\\s*[:=]\\s*\\$\\{PIPESTATUS\\[0\\]\\}"|(?<bareLabel>${label})\\s*[:=]\\s*\\$\\{PIPESTATUS\\[0\\]\\})$`,
2257
+ "i"
2258
+ );
2259
+ const pipelineMatch = normalized.match(pipelinePattern);
2260
+ if (pipelineMatch?.groups?.pipelineCommand) {
2261
+ const wrappedCommand = normalizeProjectCommand(pipelineMatch.groups.pipelineCommand);
2262
+ const command = pipelineStatusCommand(wrappedCommand, 0);
2263
+ if (command) {
2264
+ return {
2265
+ command,
2266
+ wrappedCommand,
2267
+ label: String(
2268
+ pipelineMatch.groups.doubleLabel || pipelineMatch.groups.bareLabel || ""
2269
+ ).toUpperCase(),
2270
+ statusSource: "pipeline",
2271
+ pipelineIndex: 0,
2272
+ };
2273
+ }
2274
+ }
2234
2275
  const patterns = [
2235
2276
  new RegExp(
2236
2277
  `^(?<command>[\\s\\S]+);\\s*echo\\s+(?:\"(?<doubleLabel>${label})\\s*[:=]\\s*\\$\\?\"|'(?<singleLabel>${label})\\s*[:=]\\s*\\$\\?'|(?<bareLabel>${label})\\s*[:=]\\s*\\$\\?)$`,