@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.316",
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
  })}`
@@ -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 = parseNonMutatingExitStatusWrapper(normalized)?.command || normalized;
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 = parseNonMutatingExitStatusWrapper(normalizedCommand);
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: parseExplicitExitStatus(result.stdout || ""),
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 = parseNonMutatingExitStatusWrapper(normalized);
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
- const target = String(value || "")
12916
+ const resolveTarget = (value = "", seen = new Set()) => {
12917
+ let target = String(value || "")
12787
12918
  .trim()
12788
12919
  .replace(/^["']|["']$/g, "");
12789
- const variable = target.match(/^\$(?:\{([A-Za-z_]\w*)\}|([A-Za-z_]\w*))$/);
12790
- return variable
12791
- ? String(assignments.get(variable[1] || variable[2]) || "")
12792
- : target;
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 = tokenizeShellWords(normalized);
12795
- if (tokens[0] === "tee") {
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
- const targets = tokens.slice(index).map(resolveTarget);
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*("[^"]+"|'[^']+'|[^\s;&|]+)/g,
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 || !strippedEvidenceRedirect || !withoutEvidenceRedirects) {
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
- return sequence.commands.every((segment) => {
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 = "") {