agentxchain 2.155.55 → 2.155.56
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
|
@@ -504,8 +504,8 @@ function renderPrompt(role, roleId, turn, state, config, root) {
|
|
|
504
504
|
lines.push('- `objections[].id`: pattern `OBJ-NNN` where NNN is digits only (e.g. `OBJ-001`, `OBJ-002`). Do NOT append extra suffixes like `-M31` or use non-numeric characters after `OBJ-`.');
|
|
505
505
|
lines.push('- `objections[].severity`: one of `low`, `medium`, `high`, `blocking`');
|
|
506
506
|
lines.push('- `verification.status`: **REQUIRED**. One of `pass`, `fail`, `skipped`. Always include this field in the `verification` object.');
|
|
507
|
-
lines.push('- `verification.status: "pass"` is valid only when every `verification.machine_evidence[].exit_code` is `0`');
|
|
508
|
-
lines.push('- Expected-failure checks
|
|
507
|
+
lines.push('- `verification.status: "pass"` is valid only when every `verification.machine_evidence[].exit_code` is `0`, unless a negative-case command explicitly sets `expected_exit_code` to the same non-zero value.');
|
|
508
|
+
lines.push('- Expected-failure checks should be wrapped in a verifier that exits `0` when the failure occurs as expected. If you must record a raw non-zero negative-case command on a passing turn, set `verification.machine_evidence[].expected_exit_code` to the expected non-zero code and explain it in `evidence_summary`.');
|
|
509
509
|
lines.push('- If verification commands produce side-effect files (e.g., `.tusq/plan.json`, `coverage/`, `.cache/`), declare each in `verification.produced_files` with `disposition: "ignore"` (temporary output to clean up) or `disposition: "artifact"` (output to checkpoint as a turn deliverable). Undeclared dirty files with declared verification will be auto-cleaned but declaring them is preferred.');
|
|
510
510
|
lines.push('- `artifact.type`: **REQUIRED**. One of `workspace`, `patch`, `commit`, `review`.');
|
|
511
511
|
lines.push('- If you make zero repo file edits, set `artifact.type` to `"review"` and `files_changed` to `[]`.');
|
|
@@ -168,7 +168,11 @@
|
|
|
168
168
|
"type": "string",
|
|
169
169
|
"pattern": "\\S"
|
|
170
170
|
},
|
|
171
|
-
"exit_code": { "type": "integer" }
|
|
171
|
+
"exit_code": { "type": "integer" },
|
|
172
|
+
"expected_exit_code": {
|
|
173
|
+
"type": "integer",
|
|
174
|
+
"description": "Optional expected exit code for deliberate negative-case verification commands."
|
|
175
|
+
}
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
},
|
|
@@ -815,16 +815,32 @@ function validateVerification(tr) {
|
|
|
815
815
|
if (typeof entry.exit_code !== 'number' || !Number.isInteger(entry.exit_code)) {
|
|
816
816
|
errors.push(`verification.machine_evidence[${i}].exit_code must be an integer.`);
|
|
817
817
|
}
|
|
818
|
+
if ('expected_exit_code' in entry && (typeof entry.expected_exit_code !== 'number' || !Number.isInteger(entry.expected_exit_code))) {
|
|
819
|
+
errors.push(`verification.machine_evidence[${i}].expected_exit_code must be an integer when provided.`);
|
|
820
|
+
}
|
|
818
821
|
}
|
|
819
822
|
|
|
820
|
-
// If status is pass but any command has non-zero exit code, that
|
|
823
|
+
// If status is pass but any command has non-zero exit code, that is only
|
|
824
|
+
// valid for explicitly declared expected negative checks.
|
|
821
825
|
if (v.status === 'pass') {
|
|
822
|
-
const failedCommands = v.machine_evidence.filter(
|
|
826
|
+
const failedCommands = v.machine_evidence.filter((entry) => (
|
|
827
|
+
typeof entry?.exit_code === 'number'
|
|
828
|
+
&& entry.exit_code !== 0
|
|
829
|
+
&& !isExpectedNonZeroMachineEvidence(entry, v.evidence_summary)
|
|
830
|
+
));
|
|
823
831
|
if (failedCommands.length > 0) {
|
|
824
832
|
errors.push(
|
|
825
|
-
`verification.status is "pass" but ${failedCommands.length} command(s) have non-zero exit codes.
|
|
833
|
+
`verification.status is "pass" but ${failedCommands.length} command(s) have non-zero exit codes that are not explicitly declared as expected failures. Set verification.machine_evidence[].expected_exit_code to the expected non-zero code, wrap expected-failure checks in a verifier that exits 0 only when the failure occurs as expected, or do not report "pass".`
|
|
826
834
|
);
|
|
827
835
|
}
|
|
836
|
+
const expectedNonZeroCommands = v.machine_evidence.filter((entry) => (
|
|
837
|
+
typeof entry?.exit_code === 'number'
|
|
838
|
+
&& entry.exit_code !== 0
|
|
839
|
+
&& isExpectedNonZeroMachineEvidence(entry, v.evidence_summary)
|
|
840
|
+
));
|
|
841
|
+
if (expectedNonZeroCommands.length > 0) {
|
|
842
|
+
warnings.push(`verification.status is "pass" with ${expectedNonZeroCommands.length} expected non-zero command(s).`);
|
|
843
|
+
}
|
|
828
844
|
}
|
|
829
845
|
}
|
|
830
846
|
|
|
@@ -865,6 +881,57 @@ function validateVerification(tr) {
|
|
|
865
881
|
return { errors, warnings };
|
|
866
882
|
}
|
|
867
883
|
|
|
884
|
+
function isExpectedNonZeroMachineEvidence(entry, evidenceSummary) {
|
|
885
|
+
if (!entry || typeof entry !== 'object') {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
if (!Number.isInteger(entry.exit_code) || entry.exit_code === 0) {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
if (Number.isInteger(entry.expected_exit_code)) {
|
|
892
|
+
return entry.expected_exit_code === entry.exit_code;
|
|
893
|
+
}
|
|
894
|
+
return evidenceSummaryDeclaresExpectedExit(evidenceSummary, entry.command, entry.exit_code);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function evidenceSummaryDeclaresExpectedExit(evidenceSummary, command, exitCode) {
|
|
898
|
+
if (typeof evidenceSummary !== 'string' || typeof command !== 'string') {
|
|
899
|
+
return false;
|
|
900
|
+
}
|
|
901
|
+
const summary = normalizeEvidenceText(evidenceSummary);
|
|
902
|
+
const snippets = expectedExitCommandSnippets(command).map(normalizeEvidenceText).filter(Boolean);
|
|
903
|
+
for (const snippet of snippets) {
|
|
904
|
+
const index = summary.indexOf(snippet);
|
|
905
|
+
if (index === -1) {
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
const window = summary.slice(index, index + snippet.length + 160);
|
|
909
|
+
if (mentionsExitCode(window, exitCode)) {
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function expectedExitCommandSnippets(command) {
|
|
917
|
+
const normalized = normalizeEvidenceText(command);
|
|
918
|
+
const snippets = [normalized];
|
|
919
|
+
const firstTypeMatch = normalized.match(/(--first-type)\s+([^&|;`\n\r ]+)/i);
|
|
920
|
+
if (firstTypeMatch) {
|
|
921
|
+
snippets.push(`${firstTypeMatch[1]} ${firstTypeMatch[2]}`);
|
|
922
|
+
}
|
|
923
|
+
return [...new Set(snippets)];
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function mentionsExitCode(text, exitCode) {
|
|
927
|
+
const code = String(exitCode).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
928
|
+
return new RegExp(`\\b(?:exit(?:s|ed)?(?:\\s+(?:code|with\\s+code))?|returns?|returned)\\s*${code}\\b`, 'i').test(text);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function normalizeEvidenceText(value) {
|
|
932
|
+
return String(value).replace(/\s+/g, ' ').trim();
|
|
933
|
+
}
|
|
934
|
+
|
|
868
935
|
// ── Stage E: Protocol Compliance ─────────────────────────────────────────────
|
|
869
936
|
|
|
870
937
|
function validateProtocol(tr, state, config) {
|