agentxchain 2.89.0 → 2.90.0

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.89.0",
3
+ "version": "2.90.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -267,7 +267,7 @@ function evaluateBarrierEffects(workspacePath, state, config, repoId, workstream
267
267
  snapshotChanged = true;
268
268
  }
269
269
  }
270
- if (barrier.type === 'interface_alignment') {
270
+ if (barrier.type === 'interface_alignment' || barrier.type === 'named_decisions') {
271
271
  const satisfiedRepos = getAlignedReposForBarrier(barrier, history);
272
272
  if (JSON.stringify(barrier.satisfied_repos || []) !== JSON.stringify(satisfiedRepos)) {
273
273
  barrier.satisfied_repos = satisfiedRepos;
@@ -41,7 +41,7 @@ export function getAcceptedReposForWorkstream(history, workstreamId, requiredRep
41
41
 
42
42
  export function getAlignedReposForBarrier(barrier, history) {
43
43
  const requiredRepos = Array.isArray(barrier.required_repos) ? barrier.required_repos : [];
44
- const alignmentDecisionIds = barrier.alignment_decision_ids || {};
44
+ const alignmentDecisionIds = barrier.required_decision_ids_by_repo || barrier.alignment_decision_ids || {};
45
45
  const { repoDecisionIds } = collectAcceptedDecisionIds(history, barrier.workstream_id, requiredRepos);
46
46
  const alignedRepos = [];
47
47
 
@@ -105,6 +105,7 @@ export function computeBarrierStatus(barrier, history, config) {
105
105
  return computeOrderedRepoSequenceStatus(barrier, history, config);
106
106
 
107
107
  case 'interface_alignment':
108
+ case 'named_decisions':
108
109
  return computeInterfaceAlignmentStatus(barrier, history);
109
110
 
110
111
  case 'shared_human_gate':
@@ -11,6 +11,7 @@ const VALID_PHASE_NAME = /^[a-z][a-z0-9_-]*$/;
11
11
  const VALID_BARRIER_TYPES = new Set([
12
12
  'all_repos_accepted',
13
13
  'interface_alignment',
14
+ 'named_decisions',
14
15
  'ordered_repo_sequence',
15
16
  'shared_human_gate',
16
17
  ]);
@@ -151,34 +152,30 @@ function validateWorkstreams(raw, repoIds, errors) {
151
152
  );
152
153
  }
153
154
 
154
- validateInterfaceAlignment(workstreamId, workstream, errors);
155
+ validateDecisionRequirementBarrier(workstreamId, workstream, errors);
155
156
  }
156
157
 
157
158
  detectWorkstreamCycles(raw.workstreams, errors);
158
159
  return workstreamIds;
159
160
  }
160
161
 
161
- function validateInterfaceAlignment(workstreamId, workstream, errors) {
162
- if (workstream.completion_barrier !== 'interface_alignment') {
163
- return;
164
- }
165
-
166
- const alignment = workstream.interface_alignment;
167
- if (!alignment || typeof alignment !== 'object' || Array.isArray(alignment)) {
162
+ function validateDecisionIdsByRepo(workstreamId, workstream, errors, sectionName, errorPrefix) {
163
+ const section = workstream[sectionName];
164
+ if (!section || typeof section !== 'object' || Array.isArray(section)) {
168
165
  pushError(
169
166
  errors,
170
- 'workstream_interface_alignment_invalid',
171
- `workstream "${workstreamId}" with completion_barrier "interface_alignment" must declare interface_alignment.decision_ids_by_repo`,
167
+ `${errorPrefix}_invalid`,
168
+ `workstream "${workstreamId}" with completion_barrier "${workstream.completion_barrier}" must declare ${sectionName}.decision_ids_by_repo`,
172
169
  );
173
170
  return;
174
171
  }
175
172
 
176
- const byRepo = alignment.decision_ids_by_repo;
173
+ const byRepo = section.decision_ids_by_repo;
177
174
  if (!byRepo || typeof byRepo !== 'object' || Array.isArray(byRepo)) {
178
175
  pushError(
179
176
  errors,
180
- 'workstream_interface_alignment_decisions_invalid',
181
- `workstream "${workstreamId}" interface_alignment.decision_ids_by_repo must be an object`,
177
+ `${errorPrefix}_decisions_invalid`,
178
+ `workstream "${workstreamId}" ${sectionName}.decision_ids_by_repo must be an object`,
182
179
  );
183
180
  return;
184
181
  }
@@ -190,8 +187,8 @@ function validateInterfaceAlignment(workstreamId, workstream, errors) {
190
187
  if (!(repoId in byRepo)) {
191
188
  pushError(
192
189
  errors,
193
- 'workstream_interface_alignment_repo_missing',
194
- `workstream "${workstreamId}" must declare interface_alignment.decision_ids_by_repo["${repoId}"]`,
190
+ `${errorPrefix}_repo_missing`,
191
+ `workstream "${workstreamId}" must declare ${sectionName}.decision_ids_by_repo["${repoId}"]`,
195
192
  );
196
193
  continue;
197
194
  }
@@ -200,8 +197,8 @@ function validateInterfaceAlignment(workstreamId, workstream, errors) {
200
197
  if (!Array.isArray(decisionIds) || decisionIds.length === 0) {
201
198
  pushError(
202
199
  errors,
203
- 'workstream_interface_alignment_repo_invalid',
204
- `workstream "${workstreamId}" interface_alignment.decision_ids_by_repo["${repoId}"] must be a non-empty array`,
200
+ `${errorPrefix}_repo_invalid`,
201
+ `workstream "${workstreamId}" ${sectionName}.decision_ids_by_repo["${repoId}"] must be a non-empty array`,
205
202
  );
206
203
  continue;
207
204
  }
@@ -211,16 +208,16 @@ function validateInterfaceAlignment(workstreamId, workstream, errors) {
211
208
  if (typeof decisionId !== 'string' || !/^DEC-\d+$/.test(decisionId)) {
212
209
  pushError(
213
210
  errors,
214
- 'workstream_interface_alignment_decision_invalid',
215
- `workstream "${workstreamId}" interface_alignment decision "${decisionId}" for repo "${repoId}" must match DEC-NNN`,
211
+ `${errorPrefix}_decision_invalid`,
212
+ `workstream "${workstreamId}" ${sectionName} decision "${decisionId}" for repo "${repoId}" must match DEC-NNN`,
216
213
  );
217
214
  continue;
218
215
  }
219
216
  if (seen.has(decisionId)) {
220
217
  pushError(
221
218
  errors,
222
- 'workstream_interface_alignment_decision_duplicate',
223
- `workstream "${workstreamId}" interface_alignment decision "${decisionId}" is duplicated for repo "${repoId}"`,
219
+ `${errorPrefix}_decision_duplicate`,
220
+ `workstream "${workstreamId}" ${sectionName} decision "${decisionId}" is duplicated for repo "${repoId}"`,
224
221
  );
225
222
  continue;
226
223
  }
@@ -232,13 +229,49 @@ function validateInterfaceAlignment(workstreamId, workstream, errors) {
232
229
  if (!repoIdSet.has(repoId)) {
233
230
  pushError(
234
231
  errors,
235
- 'workstream_interface_alignment_repo_unknown',
236
- `workstream "${workstreamId}" interface_alignment references undeclared repo "${repoId}"`,
232
+ `${errorPrefix}_repo_unknown`,
233
+ `workstream "${workstreamId}" ${sectionName} references undeclared repo "${repoId}"`,
237
234
  );
238
235
  }
239
236
  }
240
237
  }
241
238
 
239
+ function validateDecisionRequirementBarrier(workstreamId, workstream, errors) {
240
+ if (workstream.completion_barrier === 'interface_alignment') {
241
+ validateDecisionIdsByRepo(
242
+ workstreamId,
243
+ workstream,
244
+ errors,
245
+ 'interface_alignment',
246
+ 'workstream_interface_alignment',
247
+ );
248
+ return;
249
+ }
250
+
251
+ if (workstream.completion_barrier === 'named_decisions') {
252
+ validateDecisionIdsByRepo(
253
+ workstreamId,
254
+ workstream,
255
+ errors,
256
+ 'named_decisions',
257
+ 'workstream_named_decisions',
258
+ );
259
+ }
260
+ }
261
+
262
+ function normalizeDecisionIdsByRepo(section) {
263
+ return section?.decision_ids_by_repo
264
+ ? {
265
+ decision_ids_by_repo: Object.fromEntries(
266
+ Object.entries(section.decision_ids_by_repo).map(([repoId, decisionIds]) => [
267
+ repoId,
268
+ Array.isArray(decisionIds) ? [...new Set(decisionIds)] : [],
269
+ ]),
270
+ ),
271
+ }
272
+ : null;
273
+ }
274
+
242
275
  function detectWorkstreamCycles(workstreams, errors) {
243
276
  const visiting = new Set();
244
277
  const visited = new Set();
@@ -451,16 +484,8 @@ export function normalizeCoordinatorConfig(raw) {
451
484
  entry_repo: workstream.entry_repo,
452
485
  depends_on: Array.isArray(workstream.depends_on) ? [...new Set(workstream.depends_on)] : [],
453
486
  completion_barrier: workstream.completion_barrier,
454
- interface_alignment: workstream.interface_alignment?.decision_ids_by_repo
455
- ? {
456
- decision_ids_by_repo: Object.fromEntries(
457
- Object.entries(workstream.interface_alignment.decision_ids_by_repo).map(([repoId, decisionIds]) => [
458
- repoId,
459
- Array.isArray(decisionIds) ? [...new Set(decisionIds)] : [],
460
- ]),
461
- ),
462
- }
463
- : null,
487
+ interface_alignment: normalizeDecisionIdsByRepo(workstream.interface_alignment),
488
+ named_decisions: normalizeDecisionIdsByRepo(workstream.named_decisions),
464
489
  },
465
490
  ]),
466
491
  ),
@@ -366,7 +366,7 @@ export function resyncFromRepoAuthority(workspacePath, state, config) {
366
366
  barriersChanged = true;
367
367
  }
368
368
  }
369
- if (barrier.type === 'interface_alignment') {
369
+ if (barrier.type === 'interface_alignment' || barrier.type === 'named_decisions') {
370
370
  const satisfiedRepos = getAlignedReposForBarrier(barrier, fullHistory);
371
371
  if (JSON.stringify(barrier.satisfied_repos || []) !== JSON.stringify(satisfiedRepos)) {
372
372
  barrier.satisfied_repos = satisfiedRepos;
@@ -112,6 +112,9 @@ function bootstrapBarriers(config) {
112
112
  status: 'pending',
113
113
  required_repos: [...workstream.repos],
114
114
  satisfied_repos: [],
115
+ required_decision_ids_by_repo: workstream.named_decisions?.decision_ids_by_repo
116
+ || workstream.interface_alignment?.decision_ids_by_repo
117
+ || null,
115
118
  alignment_decision_ids: workstream.interface_alignment?.decision_ids_by_repo || null,
116
119
  created_at: new Date().toISOString(),
117
120
  };
@@ -61,6 +61,7 @@ function collectActiveBarriers(barriers, workstreamIds, targetRepoId) {
61
61
  type: barrier.type || 'unknown',
62
62
  status: barrier.status,
63
63
  notes: barrier.notes || null,
64
+ required_decision_ids_by_repo: barrier.required_decision_ids_by_repo || barrier.alignment_decision_ids || null,
64
65
  alignment_decision_ids: barrier.alignment_decision_ids || null,
65
66
  }));
66
67
  }
@@ -78,13 +79,13 @@ function buildRequiredFollowups(workstreamId, dependencyIds, upstreamAcceptances
78
79
 
79
80
  for (const barrier of activeBarriers) {
80
81
  if (
81
- barrier.type === 'interface_alignment'
82
- && barrier.alignment_decision_ids
83
- && Array.isArray(barrier.alignment_decision_ids[targetRepoId])
84
- && barrier.alignment_decision_ids[targetRepoId].length > 0
82
+ (barrier.type === 'interface_alignment' || barrier.type === 'named_decisions')
83
+ && barrier.required_decision_ids_by_repo
84
+ && Array.isArray(barrier.required_decision_ids_by_repo[targetRepoId])
85
+ && barrier.required_decision_ids_by_repo[targetRepoId].length > 0
85
86
  ) {
86
87
  followups.push(
87
- `Accept declared interface-alignment decisions for ${targetRepoId}: ${barrier.alignment_decision_ids[targetRepoId].join(', ')}.`,
88
+ `Accept declared decision requirements for ${targetRepoId}: ${barrier.required_decision_ids_by_repo[targetRepoId].join(', ')}.`,
88
89
  );
89
90
  }
90
91
 
@@ -146,12 +147,12 @@ function renderContextMarkdown(snapshot) {
146
147
  for (const barrier of snapshot.active_barriers) {
147
148
  let suffix = '';
148
149
  if (
149
- barrier.type === 'interface_alignment'
150
- && barrier.alignment_decision_ids
151
- && Array.isArray(barrier.alignment_decision_ids[snapshot.target_repo_id])
152
- && barrier.alignment_decision_ids[snapshot.target_repo_id].length > 0
150
+ (barrier.type === 'interface_alignment' || barrier.type === 'named_decisions')
151
+ && barrier.required_decision_ids_by_repo
152
+ && Array.isArray(barrier.required_decision_ids_by_repo[snapshot.target_repo_id])
153
+ && barrier.required_decision_ids_by_repo[snapshot.target_repo_id].length > 0
153
154
  ) {
154
- suffix = ` Required decision IDs for ${snapshot.target_repo_id}: ${barrier.alignment_decision_ids[snapshot.target_repo_id].join(', ')}.`;
155
+ suffix = ` Required decision IDs for ${snapshot.target_repo_id}: ${barrier.required_decision_ids_by_repo[snapshot.target_repo_id].join(', ')}.`;
155
156
  }
156
157
  lines.push(`- ${barrier.barrier_id}: ${barrier.type} (${barrier.status})${suffix}`);
157
158
  }