agentxchain 2.155.54 → 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
|
@@ -498,14 +498,14 @@ function renderPrompt(role, roleId, turn, state, config, root) {
|
|
|
498
498
|
lines.push('- `files_changed`: **REQUIRED** array of **strings** (file paths only). Do NOT use `files_modified` — the field name is `files_changed`. Do NOT use objects like `{path, change_type}` — just the path string (e.g. `["src/cli.js", "tests/smoke.mjs"]`).');
|
|
499
499
|
lines.push('- `proposed_next_role`: **REQUIRED**. Must be in allowed_next_roles for the current phase, or `"human"`.');
|
|
500
500
|
lines.push('- `decisions[].id`: pattern `DEC-NNN` where NNN is digits only (e.g. `DEC-001`, `DEC-002`). Do NOT use `D1`, `D2`, or freeform IDs.');
|
|
501
|
-
lines.push('- `decisions[].statement`: non-empty string describing the decision. Do NOT use `decision` or `
|
|
501
|
+
lines.push('- `decisions[].statement`: non-empty string describing the decision. Do NOT use `decision`, `description`, or `summary` as the field name — the field is `statement`.');
|
|
502
502
|
lines.push('- `decisions[].rationale`: REQUIRED non-empty string explaining why the decision was made. Do NOT omit this field.');
|
|
503
503
|
lines.push('- `decisions[].category`: one of `implementation`, `architecture`, `scope`, `process`, `quality`, `release`');
|
|
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) {
|
|
@@ -1213,13 +1280,15 @@ export function normalizeTurnResult(tr, config, context = {}) {
|
|
|
1213
1280
|
patched = { ...patched, id: normalizedDecId };
|
|
1214
1281
|
}
|
|
1215
1282
|
|
|
1216
|
-
// Normalize missing statement from decision
|
|
1283
|
+
// Normalize missing statement from unambiguous decision text fields.
|
|
1217
1284
|
const stmt = typeof patched.statement === 'string' ? patched.statement.trim() : '';
|
|
1218
1285
|
if (!stmt) {
|
|
1219
1286
|
const alt = typeof patched.decision === 'string' ? patched.decision.trim()
|
|
1220
|
-
: typeof patched.description === 'string' ? patched.description.trim()
|
|
1287
|
+
: typeof patched.description === 'string' ? patched.description.trim()
|
|
1288
|
+
: typeof patched.summary === 'string' ? patched.summary.trim() : '';
|
|
1221
1289
|
if (alt) {
|
|
1222
|
-
const srcField = typeof patched.decision === 'string' && patched.decision.trim() ? 'decision'
|
|
1290
|
+
const srcField = typeof patched.decision === 'string' && patched.decision.trim() ? 'decision'
|
|
1291
|
+
: typeof patched.description === 'string' && patched.description.trim() ? 'description' : 'summary';
|
|
1223
1292
|
corrections.push(`decisions[${index}].statement: copied from ${srcField}`);
|
|
1224
1293
|
normalizationEvents.push({
|
|
1225
1294
|
field: `decisions[${index}].statement`,
|