agentxchain 2.99.0 → 2.100.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.99.0",
3
+ "version": "2.100.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const repoRoot = resolve(__dirname, '..', '..');
9
+ const releasesDir = join(repoRoot, 'website-v2', 'docs', 'releases');
10
+
11
+ function parseReleaseFile(file) {
12
+ const match = /^v(\d+)-(\d+)-(\d+)\.mdx$/.exec(file);
13
+ if (!match) {
14
+ throw new Error(`Unparseable release note filename: ${file}`);
15
+ }
16
+ return {
17
+ file,
18
+ version: match.slice(1).map(Number),
19
+ };
20
+ }
21
+
22
+ function compareDesc(left, right) {
23
+ for (let i = 0; i < 3; i += 1) {
24
+ if (left.version[i] !== right.version[i]) {
25
+ return right.version[i] - left.version[i];
26
+ }
27
+ }
28
+ return 0;
29
+ }
30
+
31
+ function updateSidebarPosition(content, nextPosition) {
32
+ if (!content.startsWith('---\n')) {
33
+ throw new Error('Release note is missing YAML frontmatter opening delimiter');
34
+ }
35
+
36
+ const frontmatterEnd = content.indexOf('\n---\n', 4);
37
+ if (frontmatterEnd === -1) {
38
+ throw new Error('Release note is missing YAML frontmatter closing delimiter');
39
+ }
40
+
41
+ const frontmatter = content.slice(0, frontmatterEnd + 5);
42
+ const body = content.slice(frontmatterEnd + 5);
43
+ const expectedLine = `sidebar_position: ${nextPosition}`;
44
+
45
+ let updatedFrontmatter;
46
+ if (/^sidebar_position:\s*\d+\s*$/m.test(frontmatter)) {
47
+ updatedFrontmatter = frontmatter.replace(/^sidebar_position:\s*\d+\s*$/m, expectedLine);
48
+ } else {
49
+ updatedFrontmatter = frontmatter.replace(/^---\n/, `---\n${expectedLine}\n`);
50
+ }
51
+
52
+ const updated = updatedFrontmatter + body;
53
+ return {
54
+ updated,
55
+ changed: updated !== content,
56
+ };
57
+ }
58
+
59
+ const releaseFiles = readdirSync(releasesDir)
60
+ .filter((file) => /^v\d+-\d+-\d+\.mdx$/.test(file))
61
+ .map(parseReleaseFile)
62
+ .sort(compareDesc);
63
+
64
+ if (releaseFiles.length === 0) {
65
+ throw new Error(`No release notes found in ${releasesDir}`);
66
+ }
67
+
68
+ let changedCount = 0;
69
+ for (const [index, release] of releaseFiles.entries()) {
70
+ const filePath = join(releasesDir, release.file);
71
+ const content = readFileSync(filePath, 'utf8');
72
+ const { updated, changed } = updateSidebarPosition(content, index);
73
+ if (changed) {
74
+ writeFileSync(filePath, updated);
75
+ changedCount += 1;
76
+ }
77
+ }
78
+
79
+ console.log(
80
+ `Normalized release note sidebar positions across ${releaseFiles.length} files (${changedCount} updated).`,
81
+ );
@@ -192,13 +192,18 @@ if [[ "${#SURFACE_ERRORS[@]}" -gt 0 ]]; then
192
192
  fi
193
193
  echo " OK: all 8 governed version surfaces reference ${TARGET_VERSION}"
194
194
 
195
- # 5. Auto-align Homebrew mirror to target version
195
+ # 5. Normalize release-note sidebar ordering
196
+ echo "[5/10] Normalizing release-note sidebar positions..."
197
+ node "${CLI_DIR}/scripts/normalize-release-note-sidebar-positions.mjs"
198
+ echo " OK: release-note sidebar positions normalized newest-first"
199
+
200
+ # 6. Auto-align Homebrew mirror to target version
196
201
  # The formula URL and README version/tarball are updated automatically.
197
202
  # The SHA256 is carried from the previous committed formula — it is inherently a
198
203
  # post-publish artifact (npm registry tarballs are not byte-identical to
199
204
  # local npm-pack output). Any working-tree SHA edit is overwritten here.
200
205
  # sync-homebrew.sh corrects the SHA after publish.
201
- echo "[5/9] Auto-aligning Homebrew mirror to ${TARGET_VERSION}..."
206
+ echo "[6/10] Auto-aligning Homebrew mirror to ${TARGET_VERSION}..."
202
207
  HOMEBREW_MIRROR="${REPO_ROOT}/cli/homebrew/agentxchain.rb"
203
208
  HOMEBREW_MIRROR_README="${REPO_ROOT}/cli/homebrew/README.md"
204
209
  TARBALL_URL="https://registry.npmjs.org/agentxchain/-/agentxchain-${TARGET_VERSION}.tgz"
@@ -256,13 +261,13 @@ else
256
261
  echo " Skipped: no Homebrew mirror files found"
257
262
  fi
258
263
 
259
- # 6. Update version files (no git operations)
260
- echo "[6/9] Updating version files..."
264
+ # 7. Update version files (no git operations)
265
+ echo "[7/10] Updating version files..."
261
266
  npm version "$TARGET_VERSION" --no-git-tag-version
262
267
  echo " OK: package.json updated to ${TARGET_VERSION}"
263
268
 
264
- # 7. Stage version files
265
- echo "[7/9] Staging version files..."
269
+ # 8. Stage version files
270
+ echo "[8/10] Staging version files..."
266
271
  git add -- package.json
267
272
  if [[ -f package-lock.json ]]; then
268
273
  git add -- package-lock.json
@@ -270,10 +275,11 @@ fi
270
275
  for rel_path in "${ALLOWED_RELEASE_PATHS[@]}"; do
271
276
  stage_if_present "$rel_path"
272
277
  done
278
+ git -C "$REPO_ROOT" add -- website-v2/docs/releases
273
279
  echo " OK: version files and allowed release surfaces staged"
274
280
 
275
- # 8. Create release commit
276
- echo "[8/9] Creating release commit..."
281
+ # 9. Create release commit
282
+ echo "[9/10] Creating release commit..."
277
283
  git commit -m "${TARGET_VERSION}"
278
284
  RELEASE_SHA=$(git rev-parse HEAD)
279
285
  COMMIT_MSG=$(git log -1 --format=%s)
@@ -283,13 +289,13 @@ if [[ "$COMMIT_MSG" != "$TARGET_VERSION" ]]; then
283
289
  fi
284
290
  echo " OK: commit ${RELEASE_SHA:0:7} with message '${TARGET_VERSION}'"
285
291
 
286
- # 8.5. Inline preflight gate — tests, pack, and docs build must pass before tag
292
+ # 9.5. Inline preflight gate — tests, pack, and docs build must pass before tag
287
293
  if [[ "$SKIP_PREFLIGHT" -eq 1 ]]; then
288
294
  echo ""
289
- echo "[8.5/10] Inline preflight gate SKIPPED (--skip-preflight)"
295
+ echo "[9.5/11] Inline preflight gate SKIPPED (--skip-preflight)"
290
296
  else
291
297
  echo ""
292
- echo "[8.5/10] Running inline preflight gate..."
298
+ echo "[9.5/11] Running inline preflight gate..."
293
299
  echo " Running test suite..."
294
300
 
295
301
  # Install MCP example deps if needed (same as release-preflight.sh)
@@ -342,8 +348,8 @@ else
342
348
  echo " Inline preflight gate passed — proceeding to tag"
343
349
  fi
344
350
 
345
- # 9. Create annotated tag
346
- echo "[9/10] Creating annotated tag..."
351
+ # 10. Create annotated tag
352
+ echo "[10/11] Creating annotated tag..."
347
353
  git tag -a "v${TARGET_VERSION}" -m "v${TARGET_VERSION}"
348
354
  TAG_SHA=$(git rev-parse "v${TARGET_VERSION}")
349
355
  if [[ -z "$TAG_SHA" ]]; then
@@ -132,6 +132,8 @@ function formatExportDiffText(diff) {
132
132
  .map((entry) => `${entry.key}: ${formatValue(entry.left)} -> ${formatValue(entry.right)}${formatDelta(entry.delta, entry.label)}`));
133
133
  }
134
134
 
135
+ appendRegressionSection(lines, diff.regressions);
136
+
135
137
  return lines.join('\n');
136
138
  }
137
139
 
@@ -144,6 +146,18 @@ function appendChangedSection(lines, heading, items) {
144
146
  }
145
147
  }
146
148
 
149
+ function appendRegressionSection(lines, regressions) {
150
+ if (!regressions || regressions.length === 0) return;
151
+ lines.push('');
152
+ lines.push(chalk.bold.red('Governance Regressions:'));
153
+ for (const reg of regressions) {
154
+ const severityTag = reg.severity === 'error'
155
+ ? chalk.red(`[${reg.severity}]`)
156
+ : chalk.yellow(`[${reg.severity}]`);
157
+ lines.push(`${severityTag} ${reg.id}: ${reg.message}`);
158
+ }
159
+ }
160
+
147
161
  function listChangeItems(entry) {
148
162
  const items = [];
149
163
  if (entry.added.length > 0) {
@@ -1,6 +1,8 @@
1
1
  import { existsSync, readFileSync } from 'fs';
2
2
  import { resolve } from 'path';
3
3
 
4
+ const FAILED_STATUSES = new Set(['failed', 'error', 'crashed']);
5
+
4
6
  const RUN_EXPORT_SCALAR_FIELDS = [
5
7
  ['run_id', 'Run ID'],
6
8
  ['status', 'Status'],
@@ -119,6 +121,7 @@ function buildRunExportDiff(leftArtifact, rightArtifact, refs) {
119
121
  };
120
122
 
121
123
  const changed = hasChanged(scalar_changes, numeric_changes, list_changes);
124
+ const regressions = detectRunRegressions(left, right);
122
125
 
123
126
  return {
124
127
  comparison_mode: 'export',
@@ -132,6 +135,9 @@ function buildRunExportDiff(leftArtifact, rightArtifact, refs) {
132
135
  scalar_changes,
133
136
  numeric_changes,
134
137
  list_changes,
138
+ regressions,
139
+ regression_count: regressions.length,
140
+ has_regressions: regressions.length > 0,
135
141
  };
136
142
  }
137
143
 
@@ -153,6 +159,7 @@ function buildCoordinatorExportDiff(leftArtifact, rightArtifact, refs) {
153
159
  || repo_status_changes.some((entry) => entry.changed)
154
160
  || repo_export_changes.some((entry) => entry.changed)
155
161
  || event_type_changes.some((entry) => entry.changed);
162
+ const regressions = detectCoordinatorRegressions(left, right);
156
163
 
157
164
  return {
158
165
  comparison_mode: 'export',
@@ -169,12 +176,18 @@ function buildCoordinatorExportDiff(leftArtifact, rightArtifact, refs) {
169
176
  repo_status_changes,
170
177
  repo_export_changes,
171
178
  event_type_changes,
179
+ regressions,
180
+ regression_count: regressions.length,
181
+ has_regressions: regressions.length > 0,
172
182
  };
173
183
  }
174
184
 
175
185
  function normalizeRunExport(artifact) {
176
186
  const summary = artifact.summary || {};
177
187
  const repoDecisions = summary.repo_decisions || {};
188
+ const state = artifact.state || {};
189
+ const budgetStatus = state.budget_status || {};
190
+ const phaseGateStatus = state.phase_gate_status || {};
178
191
  return {
179
192
  export_kind: artifact.export_kind,
180
193
  run_id: summary.run_id || null,
@@ -197,6 +210,9 @@ function normalizeRunExport(artifact) {
197
210
  retained_turn_ids: normalizeStringArray(summary.retained_turn_ids),
198
211
  active_repo_decision_ids: normalizeStringArray((repoDecisions.active || []).map((entry) => entry?.id)),
199
212
  overridden_repo_decision_ids: normalizeStringArray((repoDecisions.overridden || []).map((entry) => entry?.id)),
213
+ budget_warn_mode: budgetStatus.warn_mode === true,
214
+ budget_exhausted: budgetStatus.exhausted === true,
215
+ phase_gate_status: normalizeGateStatusMap(phaseGateStatus),
200
216
  };
201
217
  }
202
218
 
@@ -345,3 +361,168 @@ function toNumber(value) {
345
361
  function isEqual(left, right) {
346
362
  return JSON.stringify(left) === JSON.stringify(right);
347
363
  }
364
+
365
+ function normalizeGateStatusMap(gateStatus) {
366
+ if (!gateStatus || typeof gateStatus !== 'object' || Array.isArray(gateStatus)) return {};
367
+ return Object.fromEntries(
368
+ Object.entries(gateStatus)
369
+ .filter(([key]) => typeof key === 'string' && key.trim().length > 0)
370
+ .map(([key, value]) => [key.trim(), typeof value === 'string' ? value.trim() : String(value)]),
371
+ );
372
+ }
373
+
374
+ const GATE_PASSED_STATES = new Set(['passed', 'approved', 'satisfied']);
375
+ const GATE_FAILED_STATES = new Set(['failed', 'blocked', 'rejected']);
376
+
377
+ function detectRunRegressions(left, right) {
378
+ const regressions = [];
379
+ let counter = 0;
380
+
381
+ // Status regression: completed/active -> failed/error/crashed
382
+ if (left.status && right.status && !FAILED_STATUSES.has(left.status) && FAILED_STATUSES.has(right.status)) {
383
+ regressions.push({
384
+ id: `REG-STATUS-${String(++counter).padStart(3, '0')}`,
385
+ category: 'status',
386
+ severity: 'error',
387
+ message: `Run status regressed from ${left.status} to ${right.status}`,
388
+ field: 'status',
389
+ left: left.status,
390
+ right: right.status,
391
+ });
392
+ }
393
+
394
+ // Budget warn_mode regression
395
+ if (left.budget_warn_mode === false && right.budget_warn_mode === true) {
396
+ regressions.push({
397
+ id: `REG-BUDGET-WARN-${String(++counter).padStart(3, '0')}`,
398
+ category: 'budget',
399
+ severity: 'warning',
400
+ message: 'Budget entered warn mode (over budget)',
401
+ field: 'budget_warn_mode',
402
+ left: false,
403
+ right: true,
404
+ });
405
+ }
406
+
407
+ // Budget exhausted regression
408
+ if (left.budget_exhausted === false && right.budget_exhausted === true) {
409
+ regressions.push({
410
+ id: `REG-BUDGET-EXHAUST-${String(++counter).padStart(3, '0')}`,
411
+ category: 'budget',
412
+ severity: 'error',
413
+ message: 'Budget exhausted',
414
+ field: 'budget_exhausted',
415
+ left: false,
416
+ right: true,
417
+ });
418
+ }
419
+
420
+ // Decision override count increase
421
+ const leftOverrides = typeof left.overridden_repo_decision_count === 'number' ? left.overridden_repo_decision_count : 0;
422
+ const rightOverrides = typeof right.overridden_repo_decision_count === 'number' ? right.overridden_repo_decision_count : 0;
423
+ if (rightOverrides > leftOverrides) {
424
+ regressions.push({
425
+ id: `REG-DECISION-OVERRIDE-${String(++counter).padStart(3, '0')}`,
426
+ category: 'decisions',
427
+ severity: 'warning',
428
+ message: `Repo decision overrides increased from ${leftOverrides} to ${rightOverrides}`,
429
+ field: 'overridden_repo_decision_count',
430
+ left: leftOverrides,
431
+ right: rightOverrides,
432
+ });
433
+ }
434
+
435
+ // Gate regressions: passed/approved -> failed/blocked
436
+ const allGateIds = new Set([...Object.keys(left.phase_gate_status || {}), ...Object.keys(right.phase_gate_status || {})]);
437
+ for (const gateId of allGateIds) {
438
+ const leftGate = (left.phase_gate_status || {})[gateId] || null;
439
+ const rightGate = (right.phase_gate_status || {})[gateId] || null;
440
+ if (leftGate && rightGate && GATE_PASSED_STATES.has(leftGate) && GATE_FAILED_STATES.has(rightGate)) {
441
+ regressions.push({
442
+ id: `REG-GATE-${String(++counter).padStart(3, '0')}`,
443
+ category: 'gate',
444
+ severity: 'error',
445
+ message: `Gate "${gateId}" regressed from ${leftGate} to ${rightGate}`,
446
+ field: `phase_gate_status.${gateId}`,
447
+ left: leftGate,
448
+ right: rightGate,
449
+ });
450
+ }
451
+ }
452
+
453
+ return regressions;
454
+ }
455
+
456
+ function detectCoordinatorRegressions(left, right) {
457
+ // Start with the run-level regressions that apply to coordinator summaries
458
+ const regressions = detectRunRegressions(left, right);
459
+ let counter = regressions.length;
460
+
461
+ // Repo status regressions: child repo completed -> failed
462
+ const allRepoIds = new Set([...Object.keys(left.repo_run_statuses || {}), ...Object.keys(right.repo_run_statuses || {})]);
463
+ for (const repoId of allRepoIds) {
464
+ const leftStatus = (left.repo_run_statuses || {})[repoId] || null;
465
+ const rightStatus = (right.repo_run_statuses || {})[repoId] || null;
466
+ if (leftStatus && rightStatus && !FAILED_STATUSES.has(leftStatus) && FAILED_STATUSES.has(rightStatus)) {
467
+ regressions.push({
468
+ id: `REG-REPO-STATUS-${String(++counter).padStart(3, '0')}`,
469
+ category: 'repo_status',
470
+ severity: 'error',
471
+ message: `Child repo "${repoId}" status regressed from ${leftStatus} to ${rightStatus}`,
472
+ field: `repo_run_statuses.${repoId}`,
473
+ left: leftStatus,
474
+ right: rightStatus,
475
+ });
476
+ }
477
+ }
478
+
479
+ // Repo export regressions: ok true -> false
480
+ const allExportRepoIds = new Set([...Object.keys(left.repo_export_status || {}), ...Object.keys(right.repo_export_status || {})]);
481
+ for (const repoId of allExportRepoIds) {
482
+ const leftOk = (left.repo_export_status || {})[repoId];
483
+ const rightOk = (right.repo_export_status || {})[repoId];
484
+ if (leftOk === true && rightOk === false) {
485
+ regressions.push({
486
+ id: `REG-REPO-EXPORT-${String(++counter).padStart(3, '0')}`,
487
+ category: 'repo_export',
488
+ severity: 'error',
489
+ message: `Child repo "${repoId}" export regressed from ok to failed`,
490
+ field: `repo_export_status.${repoId}`,
491
+ left: true,
492
+ right: false,
493
+ });
494
+ }
495
+ }
496
+
497
+ // Barrier count decrease
498
+ const leftBarriers = typeof left.barrier_count === 'number' ? left.barrier_count : 0;
499
+ const rightBarriers = typeof right.barrier_count === 'number' ? right.barrier_count : 0;
500
+ if (leftBarriers > 0 && rightBarriers < leftBarriers) {
501
+ regressions.push({
502
+ id: `REG-BARRIER-${String(++counter).padStart(3, '0')}`,
503
+ category: 'barrier',
504
+ severity: 'warning',
505
+ message: `Barrier count decreased from ${leftBarriers} to ${rightBarriers}`,
506
+ field: 'barrier_count',
507
+ left: leftBarriers,
508
+ right: rightBarriers,
509
+ });
510
+ }
511
+
512
+ // Event loss: total_events decreased
513
+ const leftEvents = typeof left.total_events === 'number' ? left.total_events : 0;
514
+ const rightEvents = typeof right.total_events === 'number' ? right.total_events : 0;
515
+ if (leftEvents > 0 && rightEvents < leftEvents) {
516
+ regressions.push({
517
+ id: `REG-EVENT-LOSS-${String(++counter).padStart(3, '0')}`,
518
+ category: 'events',
519
+ severity: 'warning',
520
+ message: `Aggregated event count decreased from ${leftEvents} to ${rightEvents}`,
521
+ field: 'total_events',
522
+ left: leftEvents,
523
+ right: rightEvents,
524
+ });
525
+ }
526
+
527
+ return regressions;
528
+ }