agentxchain 2.155.49 → 2.155.51

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.49",
3
+ "version": "2.155.51",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -489,7 +489,7 @@ function renderPrompt(role, roleId, turn, state, config, root) {
489
489
  lines.push('### Field Rules');
490
490
  lines.push('');
491
491
  lines.push('- `schema_version`: always `"1.0"`');
492
- lines.push('- `run_id`, `turn_id`, `role`, `runtime_id`: must match the values above exactly');
492
+ lines.push('- `run_id`, `turn_id`, `role`, `runtime_id`: must match the current assignment values above exactly. Do NOT copy `run_id` from old reports, history entries, previous dispatch bundles, or retained staging JSON.');
493
493
  lines.push('- `status`: one of `completed`, `blocked`, `needs_human`, `failed`. Do NOT use `complete`, `success`, `done`, or any other synonym — use the exact enum value `completed`.');
494
494
  lines.push('- `decisions`: REQUIRED array. Use `[]` when no new decisions were made; do not omit the field.');
495
495
  lines.push('- `objections`: REQUIRED array. Use `[]` when no objections are raised; review_only roles must include at least one objection.');
@@ -499,6 +499,7 @@ function renderPrompt(role, roleId, turn, state, config, root) {
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
501
  lines.push('- `decisions[].statement`: non-empty string describing the decision. Do NOT use `decision` or `description` as the field name — the field is `statement`.');
502
+ lines.push('- `decisions[].rationale`: REQUIRED non-empty string explaining why the decision was made. Do NOT omit this field.');
502
503
  lines.push('- `decisions[].category`: one of `implementation`, `architecture`, `scope`, `process`, `quality`, `release`');
503
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-`.');
504
505
  lines.push('- `objections[].severity`: one of `low`, `medium`, `high`, `blocking`');
@@ -78,6 +78,9 @@ export function validateStagedTurnResult(root, state, config, opts = {}) {
78
78
  const normContext = {};
79
79
  if (state) {
80
80
  normContext.phase = state.phase;
81
+ if (state.run_id) {
82
+ normContext.runId = state.run_id;
83
+ }
81
84
  // Prefer active_turns (the persisted schema field); fall back to the
82
85
  // current_turn compatibility alias for callers that pass a state shape
83
86
  // built outside loadProjectState() (e.g. raw fixtures). Both surfaces are
@@ -85,6 +88,12 @@ export function validateStagedTurnResult(root, state, config, opts = {}) {
85
88
  if (activeTurn) {
86
89
  const roleKey = activeTurn.assigned_role || activeTurn.role;
87
90
  normContext.assignedRole = roleKey;
91
+ if (activeTurn.turn_id) {
92
+ normContext.turnId = activeTurn.turn_id;
93
+ }
94
+ if (activeTurn.run_id) {
95
+ normContext.activeTurnRunId = activeTurn.run_id;
96
+ }
88
97
  if (activeTurn.runtime_id) {
89
98
  normContext.runtimeId = activeTurn.runtime_id;
90
99
  }
@@ -1006,6 +1015,29 @@ export function normalizeTurnResult(tr, config, context = {}) {
1006
1015
 
1007
1016
  const normalized = { ...tr };
1008
1017
 
1018
+ // ── BUG-97: recover stale run_id for retained active turns ────────────
1019
+ // run_id is state-owned identity. A staged result may safely inherit it
1020
+ // only when the staged turn_id proves it belongs to the active retained
1021
+ // turn and the active-turn/state run identity is coherent.
1022
+ const stagedTurnMatchesActive = Boolean(
1023
+ context.runId
1024
+ && context.turnId
1025
+ && typeof normalized.turn_id === 'string'
1026
+ && normalized.turn_id === context.turnId
1027
+ );
1028
+ const activeTurnRunIdCoherent = !context.activeTurnRunId || context.activeTurnRunId === context.runId;
1029
+ const stagedRunId = typeof normalized.run_id === 'string' ? normalized.run_id.trim() : '';
1030
+ if (stagedTurnMatchesActive && activeTurnRunIdCoherent && stagedRunId !== context.runId) {
1031
+ corrections.push(`run_id: rewritten from "${stagedRunId || '(missing)'}" to active state run_id "${context.runId}"`);
1032
+ normalizationEvents.push({
1033
+ field: 'run_id',
1034
+ original_value: normalized.run_id ?? null,
1035
+ normalized_value: context.runId,
1036
+ rationale: 'run_id_rewritten_from_active_turn_context',
1037
+ });
1038
+ normalized.run_id = context.runId;
1039
+ }
1040
+
1009
1041
  // ── BUG-95: rename synonym fields before variable computation ────────
1010
1042
  // files_modified is an unambiguous synonym for files_changed.
1011
1043
  if (!('files_changed' in normalized) && Array.isArray(normalized.files_modified)) {
@@ -1199,6 +1231,32 @@ export function normalizeTurnResult(tr, config, context = {}) {
1199
1231
  }
1200
1232
  }
1201
1233
 
1234
+ // Normalize missing rationale from existing decision text. Do not invent
1235
+ // rationale when the decision object has no meaningful source material.
1236
+ const rationale = typeof patched.rationale === 'string' ? patched.rationale.trim() : '';
1237
+ if (!rationale) {
1238
+ const rationaleSources = [
1239
+ ['reason', patched.reason],
1240
+ ['why', patched.why],
1241
+ ['description', patched.description],
1242
+ ['decision', patched.decision],
1243
+ ['statement', patched.statement],
1244
+ ];
1245
+ const source = rationaleSources.find(([, value]) => typeof value === 'string' && value.trim());
1246
+ if (source) {
1247
+ const [srcField, srcValue] = source;
1248
+ const alt = srcValue.trim();
1249
+ corrections.push(`decisions[${index}].rationale: copied from ${srcField}`);
1250
+ normalizationEvents.push({
1251
+ field: `decisions[${index}].rationale`,
1252
+ original_value: patched.rationale ?? null,
1253
+ normalized_value: alt,
1254
+ rationale: `copied_from_${srcField}`,
1255
+ });
1256
+ patched = { ...patched, rationale: alt };
1257
+ }
1258
+ }
1259
+
1202
1260
  // Default missing category to 'implementation'
1203
1261
  if (!patched.category || !VALID_CATEGORIES.includes(patched.category)) {
1204
1262
  const defaultCat = 'implementation';