@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 +1 -1
- package/scripts/smoke-coding-tools.js +38 -0
- package/scripts/smoke-dynamic-step-budget.js +175 -0
- package/scripts/smoke-scs-evidence-visibility.js +27 -0
- package/src/agent-runner.js +83 -25
- package/src/command-policy.js +10 -1
- package/src/permission-advice.js +39 -10
- package/src/scs-evidence.js +34 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
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,
|
package/src/agent-runner.js
CHANGED
|
@@ -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
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
13617
|
+
const normalizedResult = normalizeNoMatchQueryResult(result, policy);
|
|
13618
|
+
const diagnosticHint = shellDiagnosticHint(command, normalizedResult);
|
|
13564
13619
|
|
|
13565
13620
|
return {
|
|
13566
|
-
ok:
|
|
13567
|
-
exitCode: Number.isInteger(
|
|
13568
|
-
stdout: trimCommandOutput(
|
|
13569
|
-
stderr: trimCommandOutput(
|
|
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
|
|
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
|
|
15612
|
+
const currentPassingTests = [...(Array.isArray(verification.testRuns) ? verification.testRuns : [])]
|
|
15555
15613
|
.reverse()
|
|
15556
|
-
.
|
|
15614
|
+
.filter(
|
|
15557
15615
|
(run) =>
|
|
15558
15616
|
run?.passed === true &&
|
|
15559
|
-
|
|
15560
|
-
|
|
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
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
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(
|
|
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}:
|
|
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(", ")}`
|
package/src/command-policy.js
CHANGED
|
@@ -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([
|
|
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);
|
package/src/permission-advice.js
CHANGED
|
@@ -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
|
-
|
|
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 = {} } = {}) {
|
package/src/scs-evidence.js
CHANGED
|
@@ -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 (
|
|
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(
|
|
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(
|
|
1548
|
+
requiredProjectCommands: inferExplicitRequestedCommands(positiveEvidenceGoal),
|
|
1519
1549
|
requiresWorkspaceMutation: goalRequestsWorkspaceMutation(evidenceGoal, taskProfile),
|
|
1520
1550
|
requiresFileMutation: goalRequestsFileMutation(evidenceGoal, taskProfile),
|
|
1521
1551
|
requiresSourceGrounding: requiresSourceGrounding(evidenceGoal),
|