agentxchain 2.155.21 → 2.155.22

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.21",
3
+ "version": "2.155.22",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
package/src/lib/config.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  reconcileApprovalPausesWithConfig,
10
10
  reconcileBudgetStatusWithConfig,
11
11
  reconcileRecoveryActionsWithConfig,
12
+ recoverStaleIdleExpansionRun,
12
13
  } from './governed-state.js';
13
14
 
14
15
  function attachLegacyCurrentTurnAlias(state) {
@@ -160,7 +161,10 @@ export function loadProjectState(root, config) {
160
161
  stateData = reconciledBudget.state;
161
162
  const reconciledRecovery = reconcileRecoveryActionsWithConfig(stateData, config);
162
163
  stateData = reconciledRecovery.state;
163
- if (normalized.changed || reconciledApprovals.changed || reconciledBudget.changed || reconciledRecovery.changed) {
164
+ // BUG-75: recover stale idle-expansion runs missing charter_materialization_pending
165
+ const staleRecovery = recoverStaleIdleExpansionRun(root, stateData);
166
+ stateData = staleRecovery.state;
167
+ if (normalized.changed || reconciledApprovals.changed || reconciledBudget.changed || reconciledRecovery.changed || staleRecovery.changed) {
164
168
  safeWriteJson(filePath, stateData);
165
169
  }
166
170
  }
@@ -2500,6 +2500,99 @@ export function normalizeGovernedStateShape(state) {
2500
2500
  return { state: stripLegacyCurrentTurn(nextState), changed };
2501
2501
  }
2502
2502
 
2503
+ // BUG-75: Recover stale idle-expansion runs created before BUG-74 that lack
2504
+ // charter_materialization_pending. These runs loop on gate_semantic_coverage
2505
+ // because the PM prompt never receives the materialization directive.
2506
+ const INTAKE_EVENTS_DIR = '.agentxchain/intake/events';
2507
+
2508
+ export function recoverStaleIdleExpansionRun(root, state) {
2509
+ if (!state || typeof state !== 'object') {
2510
+ return { state, changed: false };
2511
+ }
2512
+
2513
+ // Only recover active planning runs missing the materialization flag
2514
+ if (
2515
+ state.status !== 'active'
2516
+ || state.phase !== 'planning'
2517
+ || state.charter_materialization_pending
2518
+ ) {
2519
+ return { state, changed: false };
2520
+ }
2521
+
2522
+ // Trace the run back to its intake intent via provenance
2523
+ const intentId = state.provenance?.intake_intent_id;
2524
+ if (!intentId) {
2525
+ return { state, changed: false };
2526
+ }
2527
+
2528
+ // Read the intent file
2529
+ const intentPath = join(root, INTAKE_INTENTS_DIR, `${intentId}.json`);
2530
+ if (!existsSync(intentPath)) {
2531
+ return { state, changed: false };
2532
+ }
2533
+
2534
+ let intent;
2535
+ try {
2536
+ intent = JSON.parse(readFileSync(intentPath, 'utf8'));
2537
+ } catch {
2538
+ return { state, changed: false };
2539
+ }
2540
+
2541
+ const eventId = intent.event_id;
2542
+ if (!eventId) {
2543
+ return { state, changed: false };
2544
+ }
2545
+
2546
+ // Read the event file to check the category
2547
+ const eventPath = join(root, INTAKE_EVENTS_DIR, `${eventId}.json`);
2548
+ if (!existsSync(eventPath)) {
2549
+ return { state, changed: false };
2550
+ }
2551
+
2552
+ let event;
2553
+ try {
2554
+ event = JSON.parse(readFileSync(eventPath, 'utf8'));
2555
+ } catch {
2556
+ return { state, changed: false };
2557
+ }
2558
+
2559
+ if (event.category !== 'pm_idle_expansion_derived') {
2560
+ return { state, changed: false };
2561
+ }
2562
+
2563
+ // Reconstruct charter_materialization_pending from the intake context
2564
+ const recoveredState = {
2565
+ ...state,
2566
+ charter_materialization_pending: {
2567
+ charter: intent.charter || null,
2568
+ acceptance_contract: Array.isArray(intent.acceptance_contract)
2569
+ ? intent.acceptance_contract
2570
+ : [],
2571
+ suppressed_transition: 'implementation',
2572
+ source_turn_id: null,
2573
+ recorded_at: new Date().toISOString(),
2574
+ },
2575
+ };
2576
+
2577
+ // Emit recovery event
2578
+ emitRunEvent(root, 'charter_materialization_required', {
2579
+ run_id: state.run_id,
2580
+ phase: state.phase,
2581
+ status: state.status,
2582
+ payload: {
2583
+ suppressed_transition: 'implementation',
2584
+ reason: 'Stale run from idle-expansion-derived intent recovered missing charter_materialization_pending flag.',
2585
+ new_intake_charter: intent.charter || null,
2586
+ source: 'stale_run_recovery',
2587
+ recovered_missing_flag: true,
2588
+ intent_id: intentId,
2589
+ event_id: eventId,
2590
+ },
2591
+ });
2592
+
2593
+ return { state: recoveredState, changed: true };
2594
+ }
2595
+
2503
2596
  export function markRunBlocked(root, details) {
2504
2597
  const state = readState(root);
2505
2598
  if (!state) {