agentxchain 2.155.20 → 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.20",
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) {
@@ -3312,8 +3405,39 @@ export function initializeGovernedRun(root, config, options = {}) {
3312
3405
  }
3313
3406
  }
3314
3407
 
3408
+ // BUG-74: When a new run starts from an idle-expansion-derived intent,
3409
+ // set charter_materialization_pending so the first PM turn receives the
3410
+ // "You MUST create or update these planning artifacts" directive.
3411
+ const intakeCtx = options.intakeContext;
3412
+ if (intakeCtx?.category === 'pm_idle_expansion_derived') {
3413
+ updatedState.charter_materialization_pending = {
3414
+ charter: intakeCtx.charter || null,
3415
+ acceptance_contract: Array.isArray(intakeCtx.acceptance_contract)
3416
+ ? intakeCtx.acceptance_contract
3417
+ : [],
3418
+ suppressed_transition: 'implementation',
3419
+ source_turn_id: null,
3420
+ recorded_at: new Date().toISOString(),
3421
+ };
3422
+ }
3423
+
3315
3424
  writeState(root, updatedState);
3316
3425
 
3426
+ // BUG-74: emit event so consumers know materialization is required from run start
3427
+ if (updatedState.charter_materialization_pending) {
3428
+ emitRunEvent(root, 'charter_materialization_required', {
3429
+ run_id: runId,
3430
+ phase: updatedState.phase,
3431
+ status: 'active',
3432
+ payload: {
3433
+ suppressed_transition: 'implementation',
3434
+ reason: 'New run from idle-expansion-derived intent must materialize charter into planning artifacts.',
3435
+ new_intake_charter: updatedState.charter_materialization_pending.charter,
3436
+ source: 'run_initialization',
3437
+ },
3438
+ });
3439
+ }
3440
+
3317
3441
  const startupIntents = archiveStaleIntentsForRun(root, runId, {
3318
3442
  protocolVersion: updatedState.protocol_version || '2.x',
3319
3443
  });
package/src/lib/intake.js CHANGED
@@ -1139,6 +1139,7 @@ export function startIntent(root, intentId, options = {}) {
1139
1139
  const initResult = initializeGovernedRun(root, config, {
1140
1140
  provenance: startProvenance,
1141
1141
  allow_terminal_restart: allowCompletedRestart,
1142
+ intakeContext,
1142
1143
  });
1143
1144
  if (!initResult.ok) {
1144
1145
  return { ok: false, error: `run initialization failed: ${initResult.error}`, exitCode: 1 };