agentxchain 2.155.55 → 2.155.57

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.57",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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`, `description`, or `summary` as the field name — the field is `statement`.');
501
+ lines.push('- `decisions[].statement`: non-empty string describing the decision. Do NOT use `decision`, `description`, `summary`, or `title` 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 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) {
@@ -1218,10 +1285,12 @@ export function normalizeTurnResult(tr, config, context = {}) {
1218
1285
  if (!stmt) {
1219
1286
  const alt = typeof patched.decision === 'string' ? patched.decision.trim()
1220
1287
  : typeof patched.description === 'string' ? patched.description.trim()
1221
- : typeof patched.summary === 'string' ? patched.summary.trim() : '';
1288
+ : typeof patched.summary === 'string' ? patched.summary.trim()
1289
+ : typeof patched.title === 'string' ? patched.title.trim() : '';
1222
1290
  if (alt) {
1223
1291
  const srcField = typeof patched.decision === 'string' && patched.decision.trim() ? 'decision'
1224
- : typeof patched.description === 'string' && patched.description.trim() ? 'description' : 'summary';
1292
+ : typeof patched.description === 'string' && patched.description.trim() ? 'description'
1293
+ : typeof patched.summary === 'string' && patched.summary.trim() ? 'summary' : 'title';
1225
1294
  corrections.push(`decisions[${index}].statement: copied from ${srcField}`);
1226
1295
  normalizationEvents.push({
1227
1296
  field: `decisions[${index}].statement`,