agentxchain 2.155.47 → 2.155.49

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.47",
3
+ "version": "2.155.49",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -491,8 +491,12 @@ function renderPrompt(role, roleId, turn, state, config, root) {
491
491
  lines.push('- `schema_version`: always `"1.0"`');
492
492
  lines.push('- `run_id`, `turn_id`, `role`, `runtime_id`: must match the values above exactly');
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
- lines.push('- `summary`: concise description of what you did this turn');
495
- lines.push('- `files_changed`: array of **strings** (file paths only). Do NOT use objects like `{path, change_type}` just the path string (e.g. `["src/cli.js", "tests/smoke.mjs"]`).');
494
+ lines.push('- `decisions`: REQUIRED array. Use `[]` when no new decisions were made; do not omit the field.');
495
+ lines.push('- `objections`: REQUIRED array. Use `[]` when no objections are raised; review_only roles must include at least one objection.');
496
+ lines.push('- `summary`: **REQUIRED** non-empty string. Do NOT omit this field.');
497
+ lines.push('- `runtime_id`: **REQUIRED**. Must match the runtime_id provided above exactly.');
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
+ lines.push('- `proposed_next_role`: **REQUIRED**. Must be in allowed_next_roles for the current phase, or `"human"`.');
496
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.');
497
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`.');
498
502
  lines.push('- `decisions[].category`: one of `implementation`, `architecture`, `scope`, `process`, `quality`, `release`');
@@ -506,7 +510,6 @@ function renderPrompt(role, roleId, turn, state, config, root) {
506
510
  lines.push('- If you make zero repo file edits, set `artifact.type` to `"review"` and `files_changed` to `[]`.');
507
511
  lines.push('- Only set `artifact.type` to `"workspace"` when you actually modified repo files and listed every changed path in `files_changed`.');
508
512
  lines.push('- Every `objections[]` item must include a non-empty `statement`; do not use `summary` or `detail` as a substitute.');
509
- lines.push('- `proposed_next_role`: must be in allowed_next_roles for current phase, or `human`');
510
513
  if (role.write_authority === 'review_only') {
511
514
  lines.push('- `objections`: **must be non-empty** (challenge requirement for review_only roles)');
512
515
  }
@@ -85,6 +85,9 @@ export function validateStagedTurnResult(root, state, config, opts = {}) {
85
85
  if (activeTurn) {
86
86
  const roleKey = activeTurn.assigned_role || activeTurn.role;
87
87
  normContext.assignedRole = roleKey;
88
+ if (activeTurn.runtime_id) {
89
+ normContext.runtimeId = activeTurn.runtime_id;
90
+ }
88
91
  const roleConfig = config?.roles?.[roleKey];
89
92
  if (roleConfig) {
90
93
  normContext.writeAuthority = roleConfig.write_authority;
@@ -1002,6 +1005,21 @@ export function normalizeTurnResult(tr, config, context = {}) {
1002
1005
  }
1003
1006
 
1004
1007
  const normalized = { ...tr };
1008
+
1009
+ // ── BUG-95: rename synonym fields before variable computation ────────
1010
+ // files_modified is an unambiguous synonym for files_changed.
1011
+ if (!('files_changed' in normalized) && Array.isArray(normalized.files_modified)) {
1012
+ corrections.push('files_changed: renamed from synonym files_modified');
1013
+ normalizationEvents.push({
1014
+ field: 'files_changed',
1015
+ original_value: null,
1016
+ normalized_value: '(renamed from files_modified)',
1017
+ rationale: 'files_modified_renamed_to_files_changed',
1018
+ });
1019
+ normalized.files_changed = normalized.files_modified;
1020
+ delete normalized.files_modified;
1021
+ }
1022
+
1005
1023
  const routing = config?.routing;
1006
1024
  const phaseNames = routing ? Object.keys(routing) : [];
1007
1025
  const currentPhase = context.phase;
@@ -1018,6 +1036,92 @@ export function normalizeTurnResult(tr, config, context = {}) {
1018
1036
  const hasExplicitNoEditLifecycleSignal = normalized.run_completion_request === true
1019
1037
  || (typeof normalized.phase_transition_request === 'string' && normalized.phase_transition_request.trim().length > 0);
1020
1038
 
1039
+ // ── BUG-94: default missing top-level required arrays ────────────────
1040
+ // Empty decisions/objections are schema-valid. Review-only challenge
1041
+ // requirements remain enforced later by protocol validation.
1042
+ if (!('decisions' in normalized)) {
1043
+ corrections.push('decisions: defaulted missing required array to []');
1044
+ normalizationEvents.push({
1045
+ field: 'decisions',
1046
+ original_value: null,
1047
+ normalized_value: [],
1048
+ rationale: 'missing_decisions_array_defaulted',
1049
+ });
1050
+ normalized.decisions = [];
1051
+ }
1052
+ if (!('objections' in normalized)) {
1053
+ corrections.push('objections: defaulted missing required array to []');
1054
+ normalizationEvents.push({
1055
+ field: 'objections',
1056
+ original_value: null,
1057
+ normalized_value: [],
1058
+ rationale: 'missing_objections_array_defaulted',
1059
+ });
1060
+ normalized.objections = [];
1061
+ }
1062
+
1063
+ // ── BUG-95: default missing runtime_id from dispatch context ─────────
1064
+ if (!normalized.runtime_id && context.runtimeId) {
1065
+ corrections.push(`runtime_id: defaulted from dispatch context "${context.runtimeId}"`);
1066
+ normalizationEvents.push({
1067
+ field: 'runtime_id',
1068
+ original_value: null,
1069
+ normalized_value: context.runtimeId,
1070
+ rationale: 'missing_runtime_id_defaulted_from_context',
1071
+ });
1072
+ normalized.runtime_id = context.runtimeId;
1073
+ }
1074
+
1075
+ // ── BUG-95: synthesize missing summary from available fields ─────────
1076
+ if (!normalized.summary || (typeof normalized.summary === 'string' && !normalized.summary.trim())) {
1077
+ const alt = (typeof normalized.milestone_title === 'string' && normalized.milestone_title.trim())
1078
+ ? normalized.milestone_title.trim()
1079
+ : (typeof normalized.milestone === 'string' && normalized.milestone.trim())
1080
+ ? `${normalized.role || 'agent'} turn for ${normalized.milestone.trim()}`
1081
+ : `${normalized.role || 'agent'} turn completed`;
1082
+ const src = (typeof normalized.milestone_title === 'string' && normalized.milestone_title.trim()) ? 'milestone_title'
1083
+ : (typeof normalized.milestone === 'string' && normalized.milestone.trim()) ? 'milestone' : 'fallback';
1084
+ corrections.push(`summary: synthesized from ${src}`);
1085
+ normalizationEvents.push({
1086
+ field: 'summary',
1087
+ original_value: normalized.summary ?? null,
1088
+ normalized_value: alt,
1089
+ rationale: `missing_summary_synthesized_from_${src}`,
1090
+ });
1091
+ normalized.summary = alt;
1092
+ }
1093
+
1094
+ // ── BUG-95: default missing artifact object ────────────────────────────
1095
+ if (!normalized.artifact || typeof normalized.artifact !== 'object' || Array.isArray(normalized.artifact)) {
1096
+ const hasFiles = Array.isArray(normalized.files_changed) && normalized.files_changed.length > 0;
1097
+ const inferredArtifact = { type: hasFiles ? 'workspace' : 'review' };
1098
+ corrections.push(`artifact: inferred ${JSON.stringify(inferredArtifact)} from files_changed`);
1099
+ normalizationEvents.push({
1100
+ field: 'artifact',
1101
+ original_value: normalized.artifact ?? null,
1102
+ normalized_value: inferredArtifact,
1103
+ rationale: 'missing_artifact_inferred_from_files_changed',
1104
+ });
1105
+ normalized.artifact = inferredArtifact;
1106
+ }
1107
+
1108
+ // ── BUG-95: default missing proposed_next_role ─────────────────────────
1109
+ if (!normalized.proposed_next_role) {
1110
+ let inferredRole = null;
1111
+ if (allowedNextRoles.length > 0) {
1112
+ inferredRole = allowedNextRoles.find(r => r !== assignedRole) || allowedNextRoles[0];
1113
+ }
1114
+ if (!inferredRole) inferredRole = 'pm';
1115
+ corrections.push(`proposed_next_role: defaulted to "${inferredRole}"`);
1116
+ normalizationEvents.push({
1117
+ field: 'proposed_next_role',
1118
+ original_value: null,
1119
+ normalized_value: inferredRole,
1120
+ rationale: 'missing_proposed_next_role_defaulted',
1121
+ });
1122
+ normalized.proposed_next_role = inferredRole;
1123
+ }
1124
+
1021
1125
  // ── BUG-90: normalize status synonyms ────────────────────────────────
1022
1126
  const STATUS_SYNONYMS = { complete: 'completed', success: 'completed', done: 'completed', error: 'failed', failure: 'failed' };
1023
1127
  if (typeof normalized.status === 'string' && !VALID_STATUSES.includes(normalized.status)) {