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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.155.55",
3
+ "version": "2.155.56",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 must be wrapped in a verifier that exits `0` when the failure occurs as expected; do not list raw non-zero negative-case commands on a passing turn');
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's suspicious
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(e => typeof e.exit_code === 'number' && e.exit_code !== 0);
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. Wrap expected-failure checks in a verifier that exits 0 only when the failure occurs as expected, or do not report "pass".`
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) {