agentxchain 2.136.1 → 2.137.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.
@@ -66,6 +66,7 @@ import { kickoffCommand } from '../src/commands/kickoff.js';
66
66
  import { rebindCommand } from '../src/commands/rebind.js';
67
67
  import { branchCommand } from '../src/commands/branch.js';
68
68
  import { migrateCommand } from '../src/commands/migrate.js';
69
+ import { migrateIntentsCommand } from '../src/commands/migrate-intents.js';
69
70
  import { resumeCommand } from '../src/commands/resume.js';
70
71
  import { unblockCommand } from '../src/commands/unblock.js';
71
72
  import { injectCommand } from '../src/commands/inject.js';
@@ -645,6 +646,13 @@ program
645
646
  .option('-j, --json', 'Output migration report as JSON')
646
647
  .action(migrateCommand);
647
648
 
649
+ program
650
+ .command('migrate-intents')
651
+ .description('Archive legacy intents with no run scope (pre-BUG-34 repair)')
652
+ .option('-j, --json', 'Output as JSON')
653
+ .option('--dry-run', 'List legacy intents without modifying them')
654
+ .action(migrateIntentsCommand);
655
+
648
656
  program
649
657
  .command('resume')
650
658
  .description('Resume a governed project: initialize or continue a run and assign the next turn')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.136.1",
3
+ "version": "2.137.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,121 @@
1
+ /**
2
+ * One-shot repair command for legacy intents stuck with approved_run_id: null.
3
+ *
4
+ * Belt-and-suspenders insurance for BUG-41: the automatic startup migration
5
+ * is now idempotent, but operators who already have stuck repos need a direct
6
+ * lever that works without starting a governed run.
7
+ */
8
+
9
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import chalk from 'chalk';
12
+
13
+ import { findProjectRoot } from '../lib/config.js';
14
+ import { migratePreBug34Intents } from '../lib/intent-startup-migration.js';
15
+
16
+ function loadRunId(root) {
17
+ const statePath = join(root, '.agentxchain', 'state.json');
18
+ if (!existsSync(statePath)) return 'manual-migration';
19
+ try {
20
+ const state = JSON.parse(readFileSync(statePath, 'utf8'));
21
+ return state.run_id || 'manual-migration';
22
+ } catch {
23
+ return 'manual-migration';
24
+ }
25
+ }
26
+
27
+ function listLegacyIntents(root) {
28
+ const intentsDir = join(root, '.agentxchain', 'intake', 'intents');
29
+ if (!existsSync(intentsDir)) return [];
30
+
31
+ const DISPATCHABLE = new Set(['planned', 'approved']);
32
+ const results = [];
33
+
34
+ for (const file of readdirSync(intentsDir)) {
35
+ if (!file.endsWith('.json') || file.startsWith('.tmp-')) continue;
36
+ const intentPath = join(intentsDir, file);
37
+ let intent;
38
+ try {
39
+ intent = JSON.parse(readFileSync(intentPath, 'utf8'));
40
+ } catch {
41
+ continue;
42
+ }
43
+ if (!intent || !DISPATCHABLE.has(intent.status)) continue;
44
+ if (intent.cross_run_durable === true) continue;
45
+ if (intent.approved_run_id) continue;
46
+ results.push({ file, intent_id: intent.intent_id || file.replace('.json', ''), status: intent.status });
47
+ }
48
+ return results;
49
+ }
50
+
51
+ export function migrateIntentsCommand(opts) {
52
+ const root = findProjectRoot();
53
+ if (!root) {
54
+ if (opts.json) {
55
+ console.log(JSON.stringify({ error: 'No agentxchain.json found. Run from inside a governed project.' }));
56
+ } else {
57
+ console.error(chalk.red(' No agentxchain.json found. Run this from inside a governed project.'));
58
+ }
59
+ process.exit(1);
60
+ }
61
+
62
+ const legacyIntents = listLegacyIntents(root);
63
+
64
+ if (opts.dryRun) {
65
+ if (opts.json) {
66
+ console.log(JSON.stringify({
67
+ archived_count: legacyIntents.length,
68
+ archived_intent_ids: legacyIntents.map(i => i.intent_id),
69
+ dry_run: true,
70
+ message: legacyIntents.length > 0
71
+ ? `Would archive ${legacyIntents.length} pre-BUG-34 intent(s)`
72
+ : 'No legacy intents found',
73
+ }, null, 2));
74
+ } else {
75
+ if (legacyIntents.length === 0) {
76
+ console.log(chalk.green(' No legacy intents found. Nothing to migrate.'));
77
+ } else {
78
+ console.log(chalk.yellow(` Would archive ${legacyIntents.length} legacy intent(s):`));
79
+ for (const item of legacyIntents) {
80
+ console.log(` ${chalk.dim('•')} ${item.intent_id} (${item.status})`);
81
+ }
82
+ }
83
+ }
84
+ return;
85
+ }
86
+
87
+ if (legacyIntents.length === 0) {
88
+ if (opts.json) {
89
+ console.log(JSON.stringify({
90
+ archived_count: 0,
91
+ archived_intent_ids: [],
92
+ dry_run: false,
93
+ message: 'No legacy intents found',
94
+ }, null, 2));
95
+ } else {
96
+ console.log(chalk.green(' No legacy intents found. Nothing to migrate.'));
97
+ }
98
+ return;
99
+ }
100
+
101
+ const runId = loadRunId(root);
102
+ const result = migratePreBug34Intents(root, runId);
103
+
104
+ if (opts.json) {
105
+ console.log(JSON.stringify({
106
+ archived_count: result.archived_migration_count,
107
+ archived_intent_ids: result.archived_migration_intent_ids,
108
+ dry_run: false,
109
+ message: result.migration_notice || `Archived ${result.archived_migration_count} pre-BUG-34 intent(s)`,
110
+ }, null, 2));
111
+ } else {
112
+ if (result.archived_migration_count === 0) {
113
+ console.log(chalk.green(' No legacy intents found. Nothing to migrate.'));
114
+ } else {
115
+ console.log(chalk.green(` ✓ ${result.migration_notice}`));
116
+ for (const id of result.archived_migration_intent_ids) {
117
+ console.log(` ${chalk.dim('•')} ${id}`);
118
+ }
119
+ }
120
+ }
121
+ }
@@ -154,7 +154,7 @@ function reconcileContinuousStartupState(context, session, contOpts, log) {
154
154
  sessionChanged = true;
155
155
  }
156
156
 
157
- if (scopedRunId && session.startup_reconciled_run_id !== scopedRunId) {
157
+ if (scopedRunId) {
158
158
  const startupIntents = archiveStaleIntentsForRun(root, scopedRunId, {
159
159
  protocolVersion: governedState?.protocol_version || config?.schema_version || '2.x',
160
160
  });
@@ -172,8 +172,10 @@ function reconcileContinuousStartupState(context, session, contOpts, log) {
172
172
  const migrationNotice = formatLegacyIntentMigrationNotice(startupIntents.archived_migration_intent_ids);
173
173
  if (migrationNotice) log(migrationNotice);
174
174
  }
175
- session.startup_reconciled_run_id = scopedRunId;
176
- sessionChanged = true;
175
+ if (session.startup_reconciled_run_id !== scopedRunId) {
176
+ session.startup_reconciled_run_id = scopedRunId;
177
+ sessionChanged = true;
178
+ }
177
179
  }
178
180
 
179
181
  if (sessionChanged) {