@lazyingart/agintiflow 0.20.264 → 0.20.265

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.264",
3
+ "version": "0.20.265",
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",
@@ -8,6 +8,7 @@ import {
8
8
  genericArtifactFilenameBlock,
9
9
  modelTimeoutExhaustionRoute,
10
10
  modelTimeoutRetryRoute,
11
+ normalizeNoMatchQueryResult,
11
12
  applyModelTimeoutRetryRoute,
12
13
  recoverFocusedTextRewriteWithWritingSpecialist,
13
14
  repairModelMessageHistory,
@@ -956,6 +957,33 @@ try {
956
957
  );
957
958
  assert(readonlyVersionPipelinePolicy.allowed, "read-only version probe pipelines should not require package-install-policy=allow");
958
959
  assert(readonlyVersionPipelinePolicy.category === "read-only", "read-only version probe pipelines should be classified as read-only");
960
+ const pgrepNoMatchPolicy = evaluateCommandPolicy(
961
+ "pgrep -af gateway_service",
962
+ dockerWorkspaceNoInstallsPolicy
963
+ );
964
+ assert(
965
+ pgrepNoMatchPolicy.allowed &&
966
+ pgrepNoMatchPolicy.category === "read-only" &&
967
+ pgrepNoMatchPolicy.noMatchExitIsSuccess === true,
968
+ "pgrep should be a read-only query whose no-match exit is meaningful evidence"
969
+ );
970
+ const pgrepNoMatchResult = normalizeNoMatchQueryResult(
971
+ { ok: false, exitCode: 1, stdout: "", stderr: "" },
972
+ pgrepNoMatchPolicy
973
+ );
974
+ assert(
975
+ pgrepNoMatchResult.ok === true &&
976
+ pgrepNoMatchResult.noMatch === true &&
977
+ pgrepNoMatchResult.semanticOutcome === "no-match",
978
+ "pgrep exit 1 with no diagnostics should normalize to successful no-match evidence"
979
+ );
980
+ assert(
981
+ normalizeNoMatchQueryResult(
982
+ { ok: false, exitCode: 2, stdout: "", stderr: "invalid option" },
983
+ pgrepNoMatchPolicy
984
+ ).ok === false,
985
+ "an actual pgrep error was incorrectly normalized as no-match evidence"
986
+ );
959
987
  const readonlyDiffSlicePolicy = evaluateCommandPolicy(
960
988
  "git diff -- src/agent-runner.js | sed -n '1,240p'",
961
989
  dockerWorkspaceNoInstallsPolicy
@@ -1000,6 +1028,16 @@ try {
1000
1028
  );
1001
1029
  assert(pythonUnittestPolicy.allowed, "stdlib python unittest should be allowed without package installs");
1002
1030
  assert(pythonUnittestPolicy.category === "test", "stdlib python unittest should be classified as test");
1031
+ const pythonNoBytecodeAcceptancePolicy = evaluateCommandPolicy(
1032
+ "PYTHONDONTWRITEBYTECODE=1 python3 /tmp/devops_sensor_gateway_contract.py",
1033
+ dockerWorkspaceNoInstallsPolicy
1034
+ );
1035
+ assert(
1036
+ pythonNoBytecodeAcceptancePolicy.allowed &&
1037
+ pythonNoBytecodeAcceptancePolicy.category === "test" &&
1038
+ pythonNoBytecodeAcceptancePolicy.substantiveTest === true,
1039
+ "a no-bytecode Python acceptance command should retain substantive test classification"
1040
+ );
1003
1041
  const pythonDemoPolicy = evaluateCommandPolicy("python3 demo.py 2>&1", dockerWorkspaceNoInstallsPolicy);
1004
1042
  assert(pythonDemoPolicy.allowed, "workspace-local python demo script should be allowed without package installs");
1005
1043
  assert(pythonDemoPolicy.category === "toolchain", "workspace-local python demo script should be classified as toolchain");
@@ -60,6 +60,7 @@ import {
60
60
  completionExternalBlockerCanClose,
61
61
  pythonTopLevelDefinitionDuplicates,
62
62
  recordCanonicalGeneratedOutputProgress,
63
+ recordAlreadyCommittedRepositoryRepair,
63
64
  recordProjectVerificationOutcome,
64
65
  recordExactOutputProgress,
65
66
  recordStaticDiscoveryProgress,
@@ -2095,6 +2096,36 @@ try {
2095
2096
  }
2096
2097
  );
2097
2098
  assert(coveredTestQuality.ok === true, coveredTestQuality.reason);
2099
+ const coveredBeforeAcceptanceQuality = await validateMutatedPythonSourceQuality(
2100
+ { commandCwd: pythonQualityWorkspace },
2101
+ {
2102
+ meta: {
2103
+ projectVerification: {
2104
+ mutationRevision: 3,
2105
+ privateMutationRevision: 0,
2106
+ mutationHistory: [{ revision: 3, paths: ["tests/test_service_launch.py"] }],
2107
+ testRuns: [
2108
+ {
2109
+ command: "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -v",
2110
+ mutationRevision: 3,
2111
+ privateMutationRevision: 0,
2112
+ passed: true,
2113
+ },
2114
+ {
2115
+ command: "python3 /tmp/project_acceptance.py",
2116
+ mutationRevision: 3,
2117
+ privateMutationRevision: 0,
2118
+ passed: true,
2119
+ },
2120
+ ],
2121
+ },
2122
+ },
2123
+ }
2124
+ );
2125
+ assert(
2126
+ coveredBeforeAcceptanceQuality.ok === true,
2127
+ "a later passing acceptance verifier erased current unit-suite coverage for a changed test"
2128
+ );
2098
2129
  assert(normalizeDynamicStepsMode("off") === "off", "dynamic mode off did not normalize");
2099
2130
  for (const command of [
2100
2131
  "npm run build",
@@ -6136,6 +6167,124 @@ try {
6136
6167
  ) === null,
6137
6168
  "a clean-worktree verification gate could not rerun after a Git-state repair"
6138
6169
  );
6170
+ const successfulRepositoryRepairState = structuredClone(repositoryCleanGateFailure);
6171
+ successfulRepositoryRepairState.goal = "Repair and verify the service lifecycle.";
6172
+ successfulRepositoryRepairState.meta.goalContract = { revision: 4 };
6173
+ successfulRepositoryRepairState.meta.activeExecutionContract = {
6174
+ revision: 4,
6175
+ materialMutationRevision: 6,
6176
+ };
6177
+ const successfulRepositoryCommit = {
6178
+ toolName: "run_command",
6179
+ ok: true,
6180
+ exitCode: 0,
6181
+ args: {
6182
+ command:
6183
+ "git add -- 'gateway_service.py' && git commit -m 'Repair service lifecycle'",
6184
+ },
6185
+ stdout:
6186
+ "[main abcdef1] Repair service lifecycle\n 1 file changed, 1 insertion(+), 1 deletion(-)\n",
6187
+ stderr: "",
6188
+ };
6189
+ recordProjectVerificationOutcome(
6190
+ successfulRepositoryRepairState,
6191
+ successfulRepositoryCommit,
6192
+ { commandCwd: workspace, taskProfile: "devops" }
6193
+ );
6194
+ const successfulRepositoryRepairMarker = recordAlreadyCommittedRepositoryRepair(
6195
+ successfulRepositoryRepairState,
6196
+ successfulRepositoryCommit
6197
+ );
6198
+ const postCommitRepositoryRuntime = nextStepRuntimeConfig(
6199
+ { provider: "deepseek", taskProfile: "devops", commandCwd: workspace },
6200
+ successfulRepositoryRepairState
6201
+ );
6202
+ assert(
6203
+ successfulRepositoryRepairMarker?.source === "successful-commit" &&
6204
+ postCommitRepositoryRuntime.testFailureRepositoryStateRepair !== true &&
6205
+ postCommitRepositoryRuntime.testVerificationPending === true &&
6206
+ postCommitRepositoryRuntime.testVerificationCommand === failedValidationCommand,
6207
+ "a successful bounded repair commit did not advance directly to the exact failed verifier"
6208
+ );
6209
+ const cleanStatusRepositoryRepairState = structuredClone(repositoryCleanGateFailure);
6210
+ cleanStatusRepositoryRepairState.goal = "Resume the committed service repair.";
6211
+ cleanStatusRepositoryRepairState.meta.goalContract = { revision: 5 };
6212
+ cleanStatusRepositoryRepairState.meta.activeExecutionContract = {
6213
+ revision: 5,
6214
+ startedMutationRevision: 6,
6215
+ materialMutationRevision: 6,
6216
+ requiresFileMutation: true,
6217
+ requiresSourceGrounding: true,
6218
+ };
6219
+ cleanStatusRepositoryRepairState.meta.durableGitEvidence = [{
6220
+ action: "commit",
6221
+ goalRevision: 5,
6222
+ mutationRevision: 5,
6223
+ }];
6224
+ const cleanStatusAfterCommit = {
6225
+ toolName: "run_command",
6226
+ ok: true,
6227
+ exitCode: 0,
6228
+ args: { command: "git status --short" },
6229
+ stdout: "",
6230
+ stderr: "",
6231
+ };
6232
+ recordProjectVerificationOutcome(
6233
+ cleanStatusRepositoryRepairState,
6234
+ cleanStatusAfterCommit,
6235
+ { commandCwd: workspace, taskProfile: "devops" }
6236
+ );
6237
+ const cleanStatusRepositoryRepairMarker = recordAlreadyCommittedRepositoryRepair(
6238
+ cleanStatusRepositoryRepairState,
6239
+ cleanStatusAfterCommit
6240
+ );
6241
+ const cleanStatusRepositoryRuntime = nextStepRuntimeConfig(
6242
+ { provider: "deepseek", taskProfile: "devops", commandCwd: workspace },
6243
+ cleanStatusRepositoryRepairState
6244
+ );
6245
+ assert(
6246
+ cleanStatusRepositoryRepairMarker?.source === "clean-status-after-commit" &&
6247
+ cleanStatusRepositoryRuntime.completionFreshMutationRequired !== true &&
6248
+ cleanStatusRepositoryRuntime.testVerificationPending === true &&
6249
+ cleanStatusRepositoryRuntime.testVerificationCommand === failedValidationCommand &&
6250
+ buildKnownConstrainedPhasePlan(
6251
+ { provider: "deepseek", taskProfile: "devops", commandCwd: workspace },
6252
+ cleanStatusRepositoryRepairState,
6253
+ cleanStatusRepositoryRuntime
6254
+ )?.mode === "exact-verification",
6255
+ "current commit plus an exact clean Git status did not recover an interrupted repository repair"
6256
+ );
6257
+ const repeatedDirtyRepositoryResult = {
6258
+ toolName: "run_command",
6259
+ ok: false,
6260
+ exitCode: 1,
6261
+ args: { command: failedValidationCommand },
6262
+ stdout: "",
6263
+ stderr:
6264
+ "AssertionError: repository worktree is not clean after a new unrelated mutation",
6265
+ };
6266
+ recordProjectVerificationOutcome(
6267
+ successfulRepositoryRepairState,
6268
+ repeatedDirtyRepositoryResult,
6269
+ {
6270
+ provider: "deepseek",
6271
+ taskProfile: "devops",
6272
+ commandCwd: workspace,
6273
+ testVerificationPending: true,
6274
+ testVerificationCommand: failedValidationCommand,
6275
+ }
6276
+ );
6277
+ const repeatedDirtyRepositoryRuntime = nextStepRuntimeConfig(
6278
+ { provider: "deepseek", taskProfile: "devops", commandCwd: workspace },
6279
+ successfulRepositoryRepairState
6280
+ );
6281
+ assert(
6282
+ repeatedDirtyRepositoryResult.repositoryStateRepairConsumed === true &&
6283
+ !successfulRepositoryRepairState.meta.repositoryStateRepair &&
6284
+ repeatedDirtyRepositoryRuntime.testFailureRepositoryStateRepair === true &&
6285
+ repeatedDirtyRepositoryRuntime.testVerificationPending !== true,
6286
+ "a consumed repository-repair marker caused an unchanged clean-worktree verifier loop"
6287
+ );
6139
6288
  const oversizedRepositoryState = {
6140
6289
  ...repositoryCleanGateFailure,
6141
6290
  goal: "Complete the current repository task and preserve its established acceptance contract.",
@@ -6599,6 +6748,32 @@ try {
6599
6748
  projectTestVerificationFinishBlock(repairedValidationState) === null,
6600
6749
  "a passing rerun at the current real mutation revision did not reopen completion"
6601
6750
  );
6751
+ const presentationWrappedValidationState = {
6752
+ meta: {
6753
+ projectVerification: {
6754
+ mutationRevision: 7,
6755
+ privateMutationRevision: 0,
6756
+ testRuns: [
6757
+ {
6758
+ command: "python3 -m unittest discover -s tests -v 2>&1",
6759
+ mutationRevision: 2,
6760
+ privateMutationRevision: 0,
6761
+ passed: false,
6762
+ },
6763
+ {
6764
+ command: "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -v",
6765
+ mutationRevision: 7,
6766
+ privateMutationRevision: 0,
6767
+ passed: true,
6768
+ },
6769
+ ],
6770
+ },
6771
+ },
6772
+ };
6773
+ assert(
6774
+ projectTestVerificationFinishBlock(presentationWrappedValidationState) === null,
6775
+ "presentation-only test wrappers left a superseded failure unresolved"
6776
+ );
6602
6777
  const tracebackEvidence = compactFailedTestEvidence(
6603
6778
  {
6604
6779
  stderr: [
@@ -68,6 +68,14 @@ const recoveryInstructionContract = deriveScsTaskContract({
68
68
  taskProfile: "devops",
69
69
  });
70
70
 
71
+ const readOnlyVerificationContinuationContract = deriveScsTaskContract({
72
+ goal: [
73
+ "Continue the exact active DevOps task from its clean committed state. Do not mutate source, tests, or documentation and do not create another commit.",
74
+ "Run exactly `git status --short` once, then follow the runtime's exact required verification commands serially. These include `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -v` and `PYTHONDONTWRITEBYTECODE=1 python3 /tmp/devops_sensor_gateway_contract.py`.",
75
+ ].join("\n"),
76
+ taskProfile: "devops",
77
+ });
78
+
71
79
  const readOnlyReviewContract = deriveScsTaskContract({
72
80
  goal: [
73
81
  "Review focus: changed files only.",
@@ -171,6 +179,25 @@ assert.deepEqual(
171
179
  ],
172
180
  "a verification command introduced by 'followed by' was not retained"
173
181
  );
182
+ assert.deepEqual(
183
+ readOnlyVerificationContinuationContract.exactOutputPaths,
184
+ [],
185
+ "a verifier command following a negated create clause became an exact output path"
186
+ );
187
+ assert.deepEqual(
188
+ readOnlyVerificationContinuationContract.exactInputPaths,
189
+ [],
190
+ "a quoted verifier command became an exact input path"
191
+ );
192
+ assert.deepEqual(
193
+ readOnlyVerificationContinuationContract.requiredProjectCommands,
194
+ [
195
+ "git status --short",
196
+ "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -v",
197
+ "PYTHONDONTWRITEBYTECODE=1 python3 /tmp/devops_sensor_gateway_contract.py",
198
+ ],
199
+ "an exact read-only continuation lost one or more required verification commands"
200
+ );
174
201
  assert.equal(
175
202
  readOnlyReviewContract.requiresWorkspaceMutation,
176
203
  false,
@@ -84,6 +84,7 @@ import {
84
84
  buildPermissionAdvice,
85
85
  goalRevisionCoversActiveTask,
86
86
  isAlreadyCommittedCleanGitNoop,
87
+ isCleanGitStatusAfterCurrentCommit,
87
88
  } from "./permission-advice.js";
88
89
  import { formatBehaviorContractForPrompt } from "./behavior-contract.js";
89
90
  import { browserStateReconciliationGuidance } from "./browser-automation-guidance.js";
@@ -4295,6 +4296,23 @@ export function shellDiagnosticHint(command = "", result = {}) {
4295
4296
  return "";
4296
4297
  }
4297
4298
 
4299
+ export function normalizeNoMatchQueryResult(result = {}, policy = {}) {
4300
+ if (
4301
+ policy?.noMatchExitIsSuccess !== true ||
4302
+ Number(result?.exitCode) !== 1 ||
4303
+ String(result?.stdout || "").trim() ||
4304
+ String(result?.stderr || "").trim()
4305
+ ) {
4306
+ return result;
4307
+ }
4308
+ return {
4309
+ ...result,
4310
+ ok: true,
4311
+ noMatch: true,
4312
+ semanticOutcome: "no-match",
4313
+ };
4314
+ }
4315
+
4298
4316
  function hashForLog(value) {
4299
4317
  return crypto.createHash("sha256").update(String(value ?? "")).digest("hex");
4300
4318
  }
@@ -6142,6 +6160,21 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
6142
6160
  requiredCommandFailure: failedRequiredCommand,
6143
6161
  ...failedEvidence,
6144
6162
  };
6163
+ const repositoryStateRepairMarker = state.meta?.repositoryStateRepair;
6164
+ if (
6165
+ repositoryStateRepairMarker &&
6166
+ Number(repositoryStateRepairMarker.version || 0) === 1 &&
6167
+ Number(repositoryStateRepairMarker.mutationRevision || 0) ===
6168
+ verification.mutationRevision &&
6169
+ projectTestCommandKey(repositoryStateRepairMarker.command || "") ===
6170
+ projectTestCommandKey(command)
6171
+ ) {
6172
+ // A repository repair authorizes one exact verifier rerun. Consume the
6173
+ // marker so a still-dirty worktree returns to repair instead of
6174
+ // replaying the same verifier indefinitely.
6175
+ delete state.meta.repositoryStateRepair;
6176
+ toolResult.repositoryStateRepairConsumed = true;
6177
+ }
6145
6178
  verification.testRuns = [...verification.testRuns, testRun].slice(-24);
6146
6179
  toolResult.projectTest = testRun;
6147
6180
  if (
@@ -6243,13 +6276,19 @@ export function failedTestRequiresCleanRepositoryState(testRun = {}) {
6243
6276
  }
6244
6277
 
6245
6278
  export function recordAlreadyCommittedRepositoryRepair(state = {}, toolResult = {}) {
6246
- if (
6247
- !isAlreadyCommittedCleanGitNoop(
6248
- toolResult.args || {},
6249
- toolResult,
6250
- state
6251
- )
6252
- ) {
6279
+ const successfulCommit = inferSuccessfulGitActionsFromCommandResult(toolResult)
6280
+ .includes("commit");
6281
+ const cleanCommitNoop = isAlreadyCommittedCleanGitNoop(
6282
+ toolResult.args || {},
6283
+ toolResult,
6284
+ state
6285
+ );
6286
+ const cleanStatusAfterCommit = isCleanGitStatusAfterCurrentCommit(
6287
+ toolResult.args || {},
6288
+ toolResult,
6289
+ state
6290
+ );
6291
+ if (!successfulCommit && !cleanCommitNoop && !cleanStatusAfterCommit) {
6253
6292
  return null;
6254
6293
  }
6255
6294
  const failed = currentFailedProjectTest(state);
@@ -6266,7 +6305,11 @@ export function recordAlreadyCommittedRepositoryRepair(state = {}, toolResult =
6266
6305
  ),
6267
6306
  failureSignature: String(failed.test.failureSignature || ""),
6268
6307
  command: String(failed.test.command || ""),
6269
- source: "clean-commit-noop",
6308
+ source: successfulCommit
6309
+ ? "successful-commit"
6310
+ : cleanCommitNoop
6311
+ ? "clean-commit-noop"
6312
+ : "clean-status-after-commit",
6270
6313
  at: new Date().toISOString(),
6271
6314
  };
6272
6315
  state.meta.repositoryStateRepair = marker;
@@ -6277,7 +6320,13 @@ export function recordAlreadyCommittedRepositoryRepair(state = {}, toolResult =
6277
6320
  function projectTestCommandKey(command = "") {
6278
6321
  const normalized = normalizeProjectCommand(command);
6279
6322
  const exitProbe = parseNonMutatingExitStatusWrapper(normalized);
6280
- return normalizeProjectCommand(exitProbe?.command || normalized);
6323
+ const invocation = normalizeProjectCommand(exitProbe?.command || normalized)
6324
+ // These wrappers alter bytecode/output handling, not the tests selected or
6325
+ // executed. Treating them as distinct suites can leave an old failure
6326
+ // unresolved after the same verifier passes without the wrapper.
6327
+ .replace(/^PYTHONDONTWRITEBYTECODE=(?:0|1|true|false)\s+/i, "")
6328
+ .replace(/\s+(?:2>&1|1>&2)\s*$/u, "");
6329
+ return normalizeProjectCommand(invocation);
6281
6330
  }
6282
6331
 
6283
6332
  function commandIncludesGitCommit(command = "") {
@@ -11509,6 +11558,11 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
11509
11558
  runtimeConfig.repositoryStateRepairCommitPaths =
11510
11559
  taskOwnedMutationPathsSinceLatestCommit(verification);
11511
11560
  } else if (retainedRepositoryStateRepair) {
11561
+ delete runtimeConfig.completionFreshMutationRequired;
11562
+ delete runtimeConfig.completionFreshMutationRevision;
11563
+ delete runtimeConfig.completionFreshMutationPaths;
11564
+ delete runtimeConfig.completionFreshMutationNeedsSourceRead;
11565
+ delete runtimeConfig.repositoryGroundingRequired;
11512
11566
  runtimeConfig.testFailureRepairActive = false;
11513
11567
  runtimeConfig.testFailureRepairMutationRequired = false;
11514
11568
  runtimeConfig.testFailureRepairNeedsPatchContext = false;
@@ -13560,23 +13614,26 @@ async function runShellCommand(command, config, policy = evaluateCommandPolicy(c
13560
13614
  const result = config.useDockerSandbox
13561
13615
  ? await runDockerSandboxCommand(command, config, policy, { signal: config.abortSignal })
13562
13616
  : await runHostShellCommand(command, config);
13563
- const diagnosticHint = shellDiagnosticHint(command, result);
13617
+ const normalizedResult = normalizeNoMatchQueryResult(result, policy);
13618
+ const diagnosticHint = shellDiagnosticHint(command, normalizedResult);
13564
13619
 
13565
13620
  return {
13566
- ok: result.ok !== false,
13567
- exitCode: Number.isInteger(result.exitCode) ? result.exitCode : 0,
13568
- stdout: trimCommandOutput(result.stdout, 8000),
13569
- stderr: trimCommandOutput(result.stderr, 4000),
13621
+ ok: normalizedResult.ok !== false,
13622
+ exitCode: Number.isInteger(normalizedResult.exitCode) ? normalizedResult.exitCode : 0,
13623
+ stdout: trimCommandOutput(normalizedResult.stdout, 8000),
13624
+ stderr: trimCommandOutput(normalizedResult.stderr, 4000),
13625
+ ...(normalizedResult.noMatch === true ? { noMatch: true, semanticOutcome: "no-match" } : {}),
13570
13626
  ...(diagnosticHint ? { diagnosticHint } : {}),
13571
13627
  };
13572
13628
  } catch (error) {
13573
13629
  if (isAbortError(error, config)) throw error;
13574
- const failedResult = {
13630
+ const rawFailedResult = {
13575
13631
  ok: false,
13576
13632
  exitCode: Number.isInteger(error?.code) ? error.code : 1,
13577
13633
  stdout: trimCommandOutput(error?.stdout || "", 8000),
13578
13634
  stderr: trimCommandOutput(error?.stderr || error?.message || "", 4000),
13579
13635
  };
13636
+ const failedResult = normalizeNoMatchQueryResult(rawFailedResult, policy);
13580
13637
  const diagnosticHint = shellDiagnosticHint(command, failedResult);
13581
13638
  return diagnosticHint ? { ...failedResult, diagnosticHint } : failedResult;
13582
13639
  }
@@ -14656,6 +14713,7 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
14656
14713
  mayMutateProject: Boolean(policy.mayMutateProject),
14657
14714
  substantiveTest: Boolean(policy.substantiveTest),
14658
14715
  gitOnly: Boolean(policy.gitOnly),
14716
+ noMatchExitIsSuccess: Boolean(policy.noMatchExitIsSuccess),
14659
14717
  normalizedCommand: normalizeCommandForPolicy(String(args.command), config),
14660
14718
  },
14661
14719
  ...(generatedOutputPaths.length
@@ -15551,13 +15609,13 @@ export async function validateMutatedPythonSourceQuality(config = {}, state = {}
15551
15609
 
15552
15610
  const defects = [];
15553
15611
  const checkedPaths = [];
15554
- const latestPassingTest = [...(Array.isArray(verification.testRuns) ? verification.testRuns : [])]
15612
+ const currentPassingTests = [...(Array.isArray(verification.testRuns) ? verification.testRuns : [])]
15555
15613
  .reverse()
15556
- .find(
15614
+ .filter(
15557
15615
  (run) =>
15558
15616
  run?.passed === true &&
15559
- Number(run?.mutationRevision || 0) === Math.max(0, Number(verification.mutationRevision || 0)) &&
15560
- Number(run?.privateMutationRevision || 0) === verificationPrivateMutationRevision(verification)
15617
+ !testRunRepresentsInvalidInvocation(run) &&
15618
+ testRunMatchesVerificationRevision(run, verification)
15561
15619
  );
15562
15620
  for (const sourcePath of paths) {
15563
15621
  let target;
@@ -15577,14 +15635,14 @@ export async function validateMutatedPythonSourceQuality(config = {}, state = {}
15577
15635
  message: syntax.reason,
15578
15636
  });
15579
15637
  }
15580
- if (
15581
- pathLooksLikeTestSource(sourcePath) &&
15582
- (!latestPassingTest || !testCommandCoversMutatedPath(latestPassingTest.command, sourcePath))
15583
- ) {
15638
+ const coveringTest = pathLooksLikeTestSource(sourcePath)
15639
+ ? currentPassingTests.find((run) => testCommandCoversMutatedPath(run.command, sourcePath))
15640
+ : null;
15641
+ if (pathLooksLikeTestSource(sourcePath) && !coveringTest) {
15584
15642
  defects.push({
15585
15643
  code: "mutated-test-not-covered-by-validation",
15586
15644
  path: sourcePath,
15587
- command: String(latestPassingTest?.command || ""),
15645
+ command: String(currentPassingTests[0]?.command || ""),
15588
15646
  });
15589
15647
  }
15590
15648
  for (const duplicate of pythonTopLevelDefinitionDuplicates(content)) {
@@ -15610,7 +15668,7 @@ export async function validateMutatedPythonSourceQuality(config = {}, state = {}
15610
15668
  item.code === "python-syntax-error"
15611
15669
  ? `${item.path}: ${item.message || "Python syntax validation failed"}`
15612
15670
  : item.code === "mutated-test-not-covered-by-validation"
15613
- ? `${item.path}: the latest successful test command did not include this changed test (${item.command || "no current test command"})`
15671
+ ? `${item.path}: no current successful test command included this changed test (${item.command || "no current test command"})`
15614
15672
  : item.code === "python-main-guard-before-required-definition"
15615
15673
  ? `${item.path}: __main__ guard at line ${item.guardLine} executes before ` +
15616
15674
  `${(item.calledLater || []).map((candidate) => `${candidate.name} at line ${candidate.line}`).join(", ")}`
@@ -20,6 +20,7 @@ const READ_ONLY_PATTERNS = [
20
20
  /^find(?:\s+[./~\w-]+)*(?:\s+-maxdepth\s+\d+)?(?:\s+-type\s+[fd])?$/,
21
21
  /^rg(?:\s+.+)?$/,
22
22
  /^grep(?:\s+.+)?$/,
23
+ /^pgrep(?:\s+.+)?$/,
23
24
  /^cat(?:\s+[-\w./~*]+)+$/,
24
25
  /^head(?:\s+.+)?$/,
25
26
  /^tail(?:\s+.+)?$/,
@@ -751,7 +752,14 @@ function classifyBackgroundShell(normalized = "") {
751
752
  const SAFE_WORKSPACE_WRITE_PATTERNS = [/^mkdir\s+-p\s+[-\w./]+$/];
752
753
  const SAFE_CHMOD_MODE_PATTERN = /^[-+=,rwxugoXst0-7]+$/;
753
754
  const SAFE_WORKSPACE_TARGET_LIMIT = 64;
754
- const SAFE_ENV_ASSIGNMENT_NAMES = new Set(["ANDROID_HOME", "ANDROID_SDK_ROOT", "JAVA_HOME", "GRADLE_USER_HOME", "PATH"]);
755
+ const SAFE_ENV_ASSIGNMENT_NAMES = new Set([
756
+ "ANDROID_HOME",
757
+ "ANDROID_SDK_ROOT",
758
+ "GRADLE_USER_HOME",
759
+ "JAVA_HOME",
760
+ "PATH",
761
+ "PYTHONDONTWRITEBYTECODE",
762
+ ]);
755
763
  const SAFE_ENV_VALUE_PATTERN = /^[-\w./:@+,%]+$/;
756
764
 
757
765
  const NETWORK_FETCH_PATTERNS = [
@@ -1681,6 +1689,7 @@ function classifySimpleCommand(normalized) {
1681
1689
  needsNetwork: false,
1682
1690
  writesWorkspace: false,
1683
1691
  gitOnly: /^git\s+/.test(commandForPatternMatching),
1692
+ noMatchExitIsSuccess: /^pgrep\b/.test(commandForPatternMatching),
1684
1693
  };
1685
1694
  }
1686
1695
  const packageScript = packageManagerScriptName(validationTokens);
@@ -615,6 +615,44 @@ export function goalRevisionCoversActiveTask(state = {}, evidenceRevision = 0) {
615
615
  return candidateRevision >= activeTaskStartRevision;
616
616
  }
617
617
 
618
+ function hasDurableCommitEvidence(state = {}, { requireCurrentMutation = true } = {}) {
619
+ const mutationRevision = Math.max(
620
+ 0,
621
+ Number(state.meta?.projectVerification?.mutationRevision || 0)
622
+ );
623
+ return (Array.isArray(state.meta?.durableGitEvidence) ? state.meta.durableGitEvidence : []).some(
624
+ (item) =>
625
+ String(item?.action || "").toLowerCase() === "commit" &&
626
+ goalRevisionCoversActiveTask(state, item?.goalRevision) &&
627
+ (
628
+ !requireCurrentMutation ||
629
+ Number(item?.mutationRevision || 0) >= mutationRevision
630
+ )
631
+ );
632
+ }
633
+
634
+ export function isCleanGitStatusAfterCurrentCommit(args = {}, result = {}, state = {}) {
635
+ const command = String(args.command || args.text || "").trim();
636
+ const cleanStatusCommand =
637
+ /^git(?:\s+-C\s+(?:"[^"]*"|'[^']*'|\S+))?\s+status\s+(?:--short|--porcelain(?:=[^\s]+)?)(?:\s+--untracked-files=(?:all|normal))?$/i.test(
638
+ command
639
+ );
640
+ if (
641
+ !cleanStatusCommand ||
642
+ result.ok === false ||
643
+ Number(result.exitCode ?? 0) !== 0 ||
644
+ String(result.stdout || "").trim() ||
645
+ String(result.stderr || "").trim()
646
+ ) {
647
+ return false;
648
+ }
649
+ // An exact clean status proves that the current files match HEAD. Permit a
650
+ // same-task commit from an earlier mutation revision so interrupted work
651
+ // that was externally restored to HEAD can return to its verifier. The
652
+ // verifier, not this marker, still decides task completion.
653
+ return hasDurableCommitEvidence(state, { requireCurrentMutation: false });
654
+ }
655
+
618
656
  export function isAlreadyCommittedCleanGitNoop(args = {}, result = {}, state = {}) {
619
657
  const command = String(args.command || args.text || "");
620
658
  const output = `${result.stdout || ""}\n${result.stderr || ""}`;
@@ -625,16 +663,7 @@ export function isAlreadyCommittedCleanGitNoop(args = {}, result = {}, state = {
625
663
  ) {
626
664
  return false;
627
665
  }
628
- const mutationRevision = Math.max(
629
- 0,
630
- Number(state.meta?.projectVerification?.mutationRevision || 0)
631
- );
632
- return (Array.isArray(state.meta?.durableGitEvidence) ? state.meta.durableGitEvidence : []).some(
633
- (item) =>
634
- String(item?.action || "").toLowerCase() === "commit" &&
635
- goalRevisionCoversActiveTask(state, item?.goalRevision) &&
636
- Number(item?.mutationRevision || 0) >= mutationRevision
637
- );
666
+ return hasDurableCommitEvidence(state);
638
667
  }
639
668
 
640
669
  export function buildFailedCommandAdvice({ args = {}, commandPolicy = {}, commandResult = {}, config = {}, state = {} } = {}) {
@@ -649,6 +649,7 @@ function inferExactInputPaths(goal = "") {
649
649
  }
650
650
  quotedPathPattern.lastIndex = 0;
651
651
  for (const match of line.matchAll(quotedPathPattern)) {
652
+ if (looksLikeShellCommandLiteral(match[1])) continue;
652
653
  pushPath(match[1]);
653
654
  }
654
655
  const unquotedLine = line.replace(quotedPathPattern, (match) => " ".repeat(match.length));
@@ -1379,12 +1380,36 @@ function prefixRequestsInlineCommandExecution(prefix = "") {
1379
1380
  while (preamble.test(clause)) clause = clause.replace(preamble, "").trim();
1380
1381
  clause = clause.replace(/(?:[::]|--?)\s*$/, "").trim();
1381
1382
  return (
1382
- /^(?:followed\s+by|run|rerun|re-run|execute|invoke|launch|verify|validate|check|confirm)(?:\s+(?:(?:the|this|that)\s+)?(?:following\s+)?command(?:\s+named)?)?\s*$/i.test(
1383
+ /^(?:followed\s+by|run|rerun|re-run|execute|invoke|launch|verify|validate|check|confirm)(?:\s+(?:exactly|again|once))?(?:\s+(?:(?:the|this|that)\s+)?(?:exact|required|following\s+)?command(?:\s+named)?)?\s*$/i.test(
1383
1384
  clause
1384
1385
  ) || /^(?:随后运行|隨後運行|接着运行|接著運行|运行|運行|执行|執行|调用|調用|验证|驗證|检查|檢查|确认|確認)\s*$/.test(clause)
1385
1386
  );
1386
1387
  }
1387
1388
 
1389
+ function sentenceContinuesRequestedCommandList(source = "", sentenceStart = -1, commandIndex = -1) {
1390
+ const prefix = String(source || "").slice(sentenceStart + 1, commandIndex).trim();
1391
+ if (!/^(?:these|those|the\s+following)\s+(?:commands?\s+)?(?:include|are)\b/i.test(prefix)) {
1392
+ return false;
1393
+ }
1394
+
1395
+ const preceding = String(source || "").slice(0, Math.max(0, sentenceStart)).trimEnd();
1396
+ const previousStart = Math.max(
1397
+ preceding.lastIndexOf("\n"),
1398
+ preceding.lastIndexOf("."),
1399
+ preceding.lastIndexOf("!"),
1400
+ preceding.lastIndexOf("?"),
1401
+ preceding.lastIndexOf("。"),
1402
+ preceding.lastIndexOf("!"),
1403
+ preceding.lastIndexOf("?")
1404
+ );
1405
+ const previousSentence = preceding.slice(previousStart + 1).trim();
1406
+ return (
1407
+ /\b(?:run|rerun|re-run|execute|invoke|launch|verify|validate|check|confirm|follow)\b/i.test(previousSentence) &&
1408
+ /\b(?:exact|required|following|verification|validation|test|check)\b/i.test(previousSentence) &&
1409
+ /\bcommands?\b/i.test(previousSentence)
1410
+ );
1411
+ }
1412
+
1388
1413
  function canonicalBareVerifierCommand(rawPath = "") {
1389
1414
  const candidate = String(rawPath || "").trim();
1390
1415
  if (
@@ -1439,7 +1464,12 @@ function inferExplicitRequestedCommands(goal = "") {
1439
1464
  source.lastIndexOf("?", index - 1)
1440
1465
  );
1441
1466
  const prefix = source.slice(sentenceStart + 1, index).trimEnd();
1442
- if (!prefixRequestsInlineCommandExecution(prefix)) continue;
1467
+ if (
1468
+ !prefixRequestsInlineCommandExecution(prefix) &&
1469
+ !sentenceContinuesRequestedCommandList(source, sentenceStart, index)
1470
+ ) {
1471
+ continue;
1472
+ }
1443
1473
 
1444
1474
  const command = normalizeProjectCommand(match[1]);
1445
1475
  if (
@@ -1487,7 +1517,7 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
1487
1517
  }));
1488
1518
  const excludedOutputPaths = inferExplicitlyExcludedOutputPaths(evidenceGoal);
1489
1519
  const inferredOutputPaths = filterExplicitlyExcludedOutputPaths(
1490
- inferExactOutputPaths(evidenceGoal),
1520
+ inferExactOutputPaths(positiveEvidenceGoal),
1491
1521
  excludedOutputPaths
1492
1522
  );
1493
1523
  const exactOutputPaths = filterExplicitlyExcludedOutputPaths(
@@ -1515,7 +1545,7 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
1515
1545
  requiresPerSourceChecks: requiresPerSourceChecks(evidenceGoal),
1516
1546
  requiredToolCalls,
1517
1547
  requiredGitActions,
1518
- requiredProjectCommands: inferExplicitRequestedCommands(evidenceGoal),
1548
+ requiredProjectCommands: inferExplicitRequestedCommands(positiveEvidenceGoal),
1519
1549
  requiresWorkspaceMutation: goalRequestsWorkspaceMutation(evidenceGoal, taskProfile),
1520
1550
  requiresFileMutation: goalRequestsFileMutation(evidenceGoal, taskProfile),
1521
1551
  requiresSourceGrounding: requiresSourceGrounding(evidenceGoal),