agentxchain 2.129.0 → 2.130.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.
@@ -71,6 +71,7 @@ import { unblockCommand } from '../src/commands/unblock.js';
71
71
  import { injectCommand } from '../src/commands/inject.js';
72
72
  import { escalateCommand } from '../src/commands/escalate.js';
73
73
  import { acceptTurnCommand } from '../src/commands/accept-turn.js';
74
+ import { checkpointTurnCommand } from '../src/commands/checkpoint-turn.js';
74
75
  import { rejectTurnCommand } from '../src/commands/reject-turn.js';
75
76
  import { reissueTurnCommand } from '../src/commands/reissue-turn.js';
76
77
  import { proposalListCommand, proposalDiffCommand, proposalApplyCommand, proposalRejectCommand } from '../src/commands/proposal.js';
@@ -672,9 +673,16 @@ program
672
673
  .command('accept-turn')
673
674
  .description('Accept the currently staged governed turn result')
674
675
  .option('--turn <id>', 'Target a specific active turn when multiple turns exist')
676
+ .option('--checkpoint', 'Checkpoint the accepted turn to git immediately after acceptance')
675
677
  .option('--resolution <mode>', 'Conflict resolution mode for conflicted turns (standard, human_merge)', 'standard')
676
678
  .action(acceptTurnCommand);
677
679
 
680
+ program
681
+ .command('checkpoint-turn')
682
+ .description('Checkpoint the latest accepted turn into git so the next writable turn has a clean baseline')
683
+ .option('--turn <id>', 'Checkpoint a specific accepted turn from history')
684
+ .action(checkpointTurnCommand);
685
+
678
686
  program
679
687
  .command('reject-turn')
680
688
  .description('Reject the current governed turn result and retry or escalate')
@@ -727,6 +735,8 @@ program
727
735
  .option('--triage-approval <mode>', 'Triage policy for vision-derived intents: auto or human (default: config or auto)')
728
736
  .option('--max-idle-cycles <n>', 'Stop after N consecutive idle cycles with no derivable work (default: 3)', parseInt)
729
737
  .option('--session-budget <usd>', 'Cumulative session-level budget cap in USD for continuous mode', parseFloat)
738
+ .option('--auto-checkpoint', 'Auto-commit accepted writable turns after acceptance')
739
+ .option('--no-auto-checkpoint', 'Disable automatic checkpointing after accepted writable turns')
730
740
  .action(runCommand);
731
741
 
732
742
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.129.0",
3
+ "version": "2.130.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,6 +2,7 @@ import chalk from 'chalk';
2
2
  import { loadProjectContext } from '../lib/config.js';
3
3
  import { acceptGovernedTurn } from '../lib/governed-state.js';
4
4
  import { deriveRecoveryDescriptor } from '../lib/blocked-state.js';
5
+ import { checkpointAcceptedTurn } from '../lib/turn-checkpoint.js';
5
6
 
6
7
  export async function acceptTurnCommand(opts = {}) {
7
8
  const context = loadProjectContext();
@@ -166,6 +167,19 @@ export async function acceptTurnCommand(opts = {}) {
166
167
  if (accepted?.cost?.usd != null) {
167
168
  console.log(` ${chalk.dim('Cost:')} $${formatUsd(accepted.cost.usd)}`);
168
169
  }
170
+ if (opts.checkpoint) {
171
+ const checkpoint = checkpointAcceptedTurn(root, { turnId });
172
+ if (!checkpoint.ok) {
173
+ console.log(` ${chalk.yellow('Checkpoint:')} accepted but checkpoint failed`);
174
+ console.log(` ${chalk.dim('Action:')} ${checkpoint.error}`);
175
+ console.log(` ${chalk.dim('Retry:')} agentxchain checkpoint-turn --turn ${turnId}`);
176
+ console.log('');
177
+ process.exit(1);
178
+ }
179
+ if (!checkpoint.skipped) {
180
+ console.log(` ${chalk.dim('Checkpoint:')} ${checkpoint.checkpoint_sha}`);
181
+ }
182
+ }
169
183
  if (accepted?.verification_replay) {
170
184
  const verifiedAt = accepted.verification_replay.verified_at
171
185
  ? ` at ${accepted.verification_replay.verified_at}`
@@ -0,0 +1,35 @@
1
+ import chalk from 'chalk';
2
+ import { loadProjectContext } from '../lib/config.js';
3
+ import { checkpointAcceptedTurn } from '../lib/turn-checkpoint.js';
4
+
5
+ export async function checkpointTurnCommand(opts = {}) {
6
+ const context = loadProjectContext();
7
+ if (!context) {
8
+ console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
9
+ process.exit(1);
10
+ }
11
+
12
+ const { root, config } = context;
13
+ if (config.protocol_mode !== 'governed') {
14
+ console.log(chalk.red('The checkpoint-turn command is only available for governed projects.'));
15
+ process.exit(1);
16
+ }
17
+
18
+ const result = checkpointAcceptedTurn(root, { turnId: opts.turn });
19
+ if (!result.ok) {
20
+ console.log(chalk.red(result.error || 'Failed to checkpoint accepted turn.'));
21
+ process.exit(1);
22
+ }
23
+
24
+ if (result.already_checkpointed) {
25
+ console.log(chalk.yellow(`Turn ${result.turn.turn_id} already has checkpoint ${result.checkpoint_sha}.`));
26
+ return;
27
+ }
28
+
29
+ if (result.skipped) {
30
+ console.log(chalk.dim(result.reason));
31
+ return;
32
+ }
33
+
34
+ console.log(chalk.green(`Checkpointed ${result.turn.turn_id} at ${result.checkpoint_sha}.`));
35
+ }
@@ -17,7 +17,7 @@ import {
17
17
  summarizeRoleRuntimeCapability,
18
18
  summarizeRuntimeCapabilityContract,
19
19
  } from '../lib/runtime-capabilities.js';
20
- import { detectActiveTurnBindingDrift } from '../lib/governed-state.js';
20
+ import { detectActiveTurnBindingDrift, detectStateBundleDesync } from '../lib/governed-state.js';
21
21
  import { findPendingApprovedIntents } from '../lib/intake.js';
22
22
  import { checkCleanBaseline } from '../lib/repo-observer.js';
23
23
 
@@ -152,7 +152,36 @@ function governedDoctor(root, rawConfig, opts) {
152
152
  }
153
153
  }
154
154
 
155
- // 5c. Clean working tree pre-flight for authoritative/proposed roles
155
+ // 5c. BUG-18: State/bundle integrity active turns must have dispatch bundles
156
+ if (normalized && existsSync(join(root, '.agentxchain', 'state.json'))) {
157
+ try {
158
+ const stateData = loadProjectState(root, normalized);
159
+ const desync = detectStateBundleDesync(root, stateData);
160
+ if (!desync.ok) {
161
+ const desyncSummary = desync.desynced
162
+ .map(d => `${d.turn_id} (${d.role}): missing ${d.expected_path}`)
163
+ .join('; ');
164
+ checks.push({
165
+ id: 'bundle_integrity',
166
+ name: 'Dispatch bundle integrity',
167
+ level: 'fail',
168
+ detail: `Ghost turn(s): ${desyncSummary}. Run: agentxchain reissue-turn`,
169
+ desynced: desync.desynced,
170
+ });
171
+ } else if (Object.keys(stateData?.active_turns || {}).length > 0) {
172
+ checks.push({
173
+ id: 'bundle_integrity',
174
+ name: 'Dispatch bundle integrity',
175
+ level: 'pass',
176
+ detail: 'All active turns have dispatch bundles on disk',
177
+ });
178
+ }
179
+ } catch {
180
+ // State couldn't be loaded — skip integrity check
181
+ }
182
+ }
183
+
184
+ // 5d. Clean working tree pre-flight for authoritative/proposed roles
156
185
  if (normalized?.roles) {
157
186
  const writableRoles = Object.entries(normalized.roles)
158
187
  .filter(([, role]) => role.write_authority === 'authoritative' || role.write_authority === 'proposed')