agentxchain 0.8.7 → 2.1.1

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.
Files changed (94) hide show
  1. package/README.md +123 -154
  2. package/bin/agentxchain.js +240 -8
  3. package/dashboard/app.js +305 -0
  4. package/dashboard/components/blocked.js +145 -0
  5. package/dashboard/components/cross-repo.js +126 -0
  6. package/dashboard/components/gate.js +311 -0
  7. package/dashboard/components/hooks.js +177 -0
  8. package/dashboard/components/initiative.js +147 -0
  9. package/dashboard/components/ledger.js +165 -0
  10. package/dashboard/components/timeline.js +222 -0
  11. package/dashboard/index.html +352 -0
  12. package/package.json +16 -7
  13. package/scripts/agentxchain-autonudge.applescript +32 -5
  14. package/scripts/live-api-proxy-preflight-smoke.sh +531 -0
  15. package/scripts/publish-from-tag.sh +88 -0
  16. package/scripts/release-postflight.sh +231 -0
  17. package/scripts/release-preflight.sh +167 -0
  18. package/scripts/run-autonudge.sh +1 -1
  19. package/src/adapters/claude-code.js +7 -14
  20. package/src/adapters/cursor-local.js +17 -16
  21. package/src/commands/accept-turn.js +160 -0
  22. package/src/commands/approve-completion.js +80 -0
  23. package/src/commands/approve-transition.js +85 -0
  24. package/src/commands/branch.js +2 -2
  25. package/src/commands/claim.js +84 -9
  26. package/src/commands/config.js +16 -0
  27. package/src/commands/dashboard.js +70 -0
  28. package/src/commands/doctor.js +9 -1
  29. package/src/commands/init.js +540 -5
  30. package/src/commands/migrate.js +348 -0
  31. package/src/commands/multi.js +549 -0
  32. package/src/commands/plugin.js +157 -0
  33. package/src/commands/reject-turn.js +204 -0
  34. package/src/commands/resume.js +389 -0
  35. package/src/commands/status.js +196 -3
  36. package/src/commands/step.js +947 -0
  37. package/src/commands/stop.js +65 -33
  38. package/src/commands/template-list.js +33 -0
  39. package/src/commands/template-set.js +279 -0
  40. package/src/commands/update.js +24 -3
  41. package/src/commands/validate.js +20 -11
  42. package/src/commands/verify.js +71 -0
  43. package/src/commands/watch.js +112 -25
  44. package/src/lib/adapters/api-proxy-adapter.js +1076 -0
  45. package/src/lib/adapters/local-cli-adapter.js +337 -0
  46. package/src/lib/adapters/manual-adapter.js +169 -0
  47. package/src/lib/blocked-state.js +94 -0
  48. package/src/lib/config.js +143 -12
  49. package/src/lib/context-compressor.js +121 -0
  50. package/src/lib/context-section-parser.js +220 -0
  51. package/src/lib/coordinator-acceptance.js +428 -0
  52. package/src/lib/coordinator-config.js +461 -0
  53. package/src/lib/coordinator-dispatch.js +276 -0
  54. package/src/lib/coordinator-gates.js +487 -0
  55. package/src/lib/coordinator-hooks.js +239 -0
  56. package/src/lib/coordinator-recovery.js +523 -0
  57. package/src/lib/coordinator-state.js +365 -0
  58. package/src/lib/cross-repo-context.js +247 -0
  59. package/src/lib/dashboard/bridge-server.js +284 -0
  60. package/src/lib/dashboard/file-watcher.js +93 -0
  61. package/src/lib/dashboard/state-reader.js +96 -0
  62. package/src/lib/dispatch-bundle.js +568 -0
  63. package/src/lib/dispatch-manifest.js +252 -0
  64. package/src/lib/filter-agents.js +12 -0
  65. package/src/lib/gate-evaluator.js +285 -0
  66. package/src/lib/generate-vscode.js +158 -68
  67. package/src/lib/governed-state.js +2139 -0
  68. package/src/lib/governed-templates.js +145 -0
  69. package/src/lib/hook-runner.js +788 -0
  70. package/src/lib/next-owner.js +61 -6
  71. package/src/lib/normalized-config.js +539 -0
  72. package/src/lib/notify.js +14 -12
  73. package/src/lib/plugin-config-schema.js +192 -0
  74. package/src/lib/plugins.js +692 -0
  75. package/src/lib/prompt-core.js +108 -0
  76. package/src/lib/protocol-conformance.js +291 -0
  77. package/src/lib/reference-conformance-adapter.js +717 -0
  78. package/src/lib/repo-observer.js +597 -0
  79. package/src/lib/repo.js +0 -31
  80. package/src/lib/safe-write.js +44 -0
  81. package/src/lib/schema.js +189 -0
  82. package/src/lib/schemas/turn-result.schema.json +205 -0
  83. package/src/lib/seed-prompt-polling.js +15 -73
  84. package/src/lib/seed-prompt.js +17 -63
  85. package/src/lib/token-budget.js +206 -0
  86. package/src/lib/token-counter.js +27 -0
  87. package/src/lib/turn-paths.js +67 -0
  88. package/src/lib/turn-result-validator.js +496 -0
  89. package/src/lib/validation.js +167 -19
  90. package/src/lib/verify-command.js +72 -0
  91. package/src/templates/governed/api-service.json +31 -0
  92. package/src/templates/governed/cli-tool.json +30 -0
  93. package/src/templates/governed/generic.json +10 -0
  94. package/src/templates/governed/web-app.json +30 -0
@@ -0,0 +1,348 @@
1
+ /**
2
+ * Migrate a legacy v3 AgentXchain project to governed mode.
3
+ *
4
+ * Non-destructive: backs up the original config, archives legacy coordination
5
+ * artifacts, and creates governed state + directory structure.
6
+ *
7
+ * Frozen rules (§32 + §38):
8
+ * - Legacy projects are supported, not upgraded silently.
9
+ * - No automatic rewrite on read.
10
+ * - Legacy history is archived, not backfilled into governed history.
11
+ * - Post-migration state is `paused`, not `active`.
12
+ */
13
+
14
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync, copyFileSync } from 'fs';
15
+ import { join } from 'path';
16
+ import chalk from 'chalk';
17
+ import inquirer from 'inquirer';
18
+ import { findProjectRoot, CONFIG_FILE } from '../lib/config.js';
19
+ import { loadNormalizedConfig, detectConfigVersion } from '../lib/normalized-config.js';
20
+
21
+ const LEGACY_ARTIFACTS = ['lock.json', 'state.json', 'state.md', 'history.jsonl', 'log.md'];
22
+
23
+ function inferPhase(legacyState) {
24
+ if (!legacyState) return 'planning';
25
+ const phase = String(legacyState.phase || '').toLowerCase();
26
+ if (phase.includes('build') || phase.includes('implement')) return 'implementation';
27
+ if (phase.includes('qa') || phase.includes('test')) return 'qa';
28
+ return 'planning';
29
+ }
30
+
31
+ export async function migrateCommand(opts) {
32
+ const root = findProjectRoot();
33
+ if (!root) {
34
+ console.error(chalk.red(' No agentxchain.json found. Run this from inside a project.'));
35
+ process.exit(1);
36
+ }
37
+
38
+ // Read raw config
39
+ let rawConfig;
40
+ try {
41
+ rawConfig = JSON.parse(readFileSync(join(root, CONFIG_FILE), 'utf8'));
42
+ } catch (err) {
43
+ console.error(chalk.red(` Could not read agentxchain.json: ${err.message}`));
44
+ process.exit(1);
45
+ }
46
+
47
+ const version = detectConfigVersion(rawConfig);
48
+ if (version === 4) {
49
+ console.log(chalk.yellow(' This project is already governed. Nothing to migrate.'));
50
+ return;
51
+ }
52
+ if (version !== 3) {
53
+ console.error(chalk.red(' Unrecognized config format. Only v3 projects can be migrated.'));
54
+ process.exit(1);
55
+ }
56
+
57
+ // Normalize to understand the current shape
58
+ const normalized = loadNormalizedConfig(rawConfig);
59
+ if (!normalized.ok) {
60
+ console.error(chalk.red(` Config validation failed: ${normalized.errors.join(', ')}`));
61
+ process.exit(1);
62
+ }
63
+
64
+ // Confirmation
65
+ if (!opts.yes) {
66
+ console.log('');
67
+ console.log(chalk.cyan(' Migration preview:'));
68
+ console.log(` Project: ${rawConfig.project}`);
69
+ console.log(` Agents: ${Object.keys(rawConfig.agents || {}).join(', ')}`);
70
+ console.log('');
71
+ console.log(chalk.dim(' This will:'));
72
+ console.log(chalk.dim(' 1. Back up agentxchain.json'));
73
+ console.log(chalk.dim(' 2. Rewrite config to governed mode'));
74
+ console.log(chalk.dim(' 3. Create .agentxchain/ directory structure'));
75
+ console.log(chalk.dim(' 4. Archive legacy coordination files'));
76
+ console.log(chalk.dim(' 5. Set status to paused (requires human review)'));
77
+ console.log('');
78
+
79
+ const { confirm } = await inquirer.prompt([{
80
+ type: 'confirm',
81
+ name: 'confirm',
82
+ message: 'Proceed with migration?',
83
+ default: true
84
+ }]);
85
+ if (!confirm) {
86
+ console.log(chalk.yellow(' Aborted.'));
87
+ return;
88
+ }
89
+ }
90
+
91
+ // Create directories
92
+ mkdirSync(join(root, '.agentxchain', 'backups'), { recursive: true });
93
+ mkdirSync(join(root, '.agentxchain', 'legacy'), { recursive: true });
94
+ mkdirSync(join(root, '.agentxchain', 'staging'), { recursive: true });
95
+ mkdirSync(join(root, '.agentxchain', 'prompts'), { recursive: true });
96
+ mkdirSync(join(root, '.agentxchain', 'reviews'), { recursive: true });
97
+ mkdirSync(join(root, '.agentxchain', 'dispatch'), { recursive: true });
98
+
99
+ // Step 1: Backup
100
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
101
+ const backupPath = join(root, '.agentxchain', 'backups', `agentxchain.v3.${timestamp}.json`);
102
+ copyFileSync(join(root, CONFIG_FILE), backupPath);
103
+
104
+ // Step 2: Build governed config from legacy
105
+ const norm = normalized.normalized;
106
+ const projectId = norm.project.id;
107
+ const projectName = norm.project.name;
108
+
109
+ const roles = {};
110
+ const runtimes = {};
111
+ const inferences = [];
112
+
113
+ for (const [id, role] of Object.entries(norm.roles)) {
114
+ const runtimeId = `manual-${id}`;
115
+ roles[id] = {
116
+ title: role.title,
117
+ mandate: role.mandate,
118
+ write_authority: role.write_authority,
119
+ runtime: runtimeId
120
+ };
121
+ runtimes[runtimeId] = { type: 'manual' };
122
+ inferences.push({
123
+ role: id,
124
+ field: 'write_authority',
125
+ inferred: role.write_authority,
126
+ note: 'Inferred from role name heuristic. Review and adjust if incorrect.'
127
+ });
128
+ inferences.push({
129
+ role: id,
130
+ field: 'runtime',
131
+ inferred: 'manual',
132
+ note: 'All legacy agents default to manual runtime. Change to local_cli or api_proxy as needed.'
133
+ });
134
+ }
135
+
136
+ const roleIds = Object.keys(roles);
137
+ const routing = {
138
+ planning: {
139
+ entry_role: roleIds[0] || 'pm',
140
+ allowed_next_roles: [...roleIds, 'human'],
141
+ exit_gate: 'planning_signoff'
142
+ },
143
+ implementation: {
144
+ entry_role: roleIds.find(r => r.includes('dev')) || roleIds[0],
145
+ allowed_next_roles: [...roleIds, 'human'],
146
+ exit_gate: 'implementation_complete'
147
+ },
148
+ qa: {
149
+ entry_role: roleIds.find(r => r.includes('qa')) || roleIds[0],
150
+ allowed_next_roles: [...roleIds, 'human'],
151
+ exit_gate: 'qa_ship_verdict'
152
+ }
153
+ };
154
+
155
+ const governedConfig = {
156
+ schema_version: '1.0',
157
+ template: 'generic',
158
+ project: {
159
+ id: projectId,
160
+ name: projectName,
161
+ default_branch: norm.project.default_branch
162
+ },
163
+ roles,
164
+ runtimes,
165
+ routing,
166
+ gates: {
167
+ planning_signoff: {
168
+ requires_files: ['.planning/PM_SIGNOFF.md', '.planning/ROADMAP.md'],
169
+ requires_human_approval: true
170
+ },
171
+ implementation_complete: {
172
+ requires_verification_pass: true
173
+ },
174
+ qa_ship_verdict: {
175
+ requires_files: ['.planning/acceptance-matrix.md', '.planning/ship-verdict.md'],
176
+ requires_human_approval: true
177
+ }
178
+ },
179
+ budget: {
180
+ per_turn_max_usd: 2.0,
181
+ per_run_max_usd: 50.0,
182
+ on_exceed: 'pause_and_escalate'
183
+ },
184
+ retention: {
185
+ talk_strategy: 'append_only',
186
+ history_strategy: 'jsonl_append_only'
187
+ },
188
+ prompts: Object.fromEntries(roleIds.map(id => [id, `.agentxchain/prompts/${id}.md`])),
189
+ rules: {
190
+ challenge_required: norm.rules.challenge_required,
191
+ max_turn_retries: 2,
192
+ max_deadlock_cycles: 2
193
+ }
194
+ };
195
+
196
+ // Step 3: Read legacy state for phase inference
197
+ let legacyState = null;
198
+ const legacyStatePath = join(root, 'state.json');
199
+ if (existsSync(legacyStatePath)) {
200
+ try { legacyState = JSON.parse(readFileSync(legacyStatePath, 'utf8')); } catch {}
201
+ }
202
+
203
+ const inferredPhase = inferPhase(legacyState);
204
+
205
+ const governedState = {
206
+ schema_version: '1.1',
207
+ run_id: null,
208
+ project_id: projectId,
209
+ status: 'paused',
210
+ phase: inferredPhase,
211
+ accepted_integration_ref: null,
212
+ active_turns: {},
213
+ turn_sequence: 0,
214
+ last_completed_turn_id: null,
215
+ blocked_on: 'human:migration-review',
216
+ blocked_reason: null,
217
+ escalation: null,
218
+ queued_phase_transition: null,
219
+ queued_run_completion: null,
220
+ phase_gate_status: {
221
+ planning_signoff: inferredPhase === 'planning' ? 'pending' : 'passed',
222
+ implementation_complete: inferredPhase === 'qa' ? 'passed' : 'pending',
223
+ qa_ship_verdict: 'pending'
224
+ },
225
+ budget_reservations: {},
226
+ budget_status: {
227
+ spent_usd: 0,
228
+ remaining_usd: governedConfig.budget.per_run_max_usd
229
+ }
230
+ };
231
+
232
+ // Step 4: Write governed config and state
233
+ writeFileSync(join(root, CONFIG_FILE), JSON.stringify(governedConfig, null, 2) + '\n');
234
+ writeFileSync(join(root, '.agentxchain', 'state.json'), JSON.stringify(governedState, null, 2) + '\n');
235
+ writeFileSync(join(root, '.agentxchain', 'history.jsonl'), '');
236
+ writeFileSync(join(root, '.agentxchain', 'decision-ledger.jsonl'), '');
237
+
238
+ // Step 5: Generate prompt templates
239
+ for (const [id, role] of Object.entries(roles)) {
240
+ const promptContent = `# ${role.title}\n\n## Identity\n\nYou are the **${role.title}** on this project.\n\n**Mandate:** ${role.mandate}\n\n**Write authority:** ${role.write_authority}\n**Runtime:** ${role.runtime}\n\n## Protocol Rules\n\n1. Challenge the previous turn explicitly.\n2. Do not claim verification you did not perform.\n3. Do not modify reserved state files under \`.agentxchain/\`.\n4. Emit structured turn result to \`.agentxchain/staging/<turn_id>/turn-result.json\`.\n5. Propose next role, but do not assume routing authority.\n`;
241
+ writeFileSync(join(root, '.agentxchain', 'prompts', `${id}.md`), promptContent);
242
+ }
243
+
244
+ // Step 6: Archive legacy artifacts
245
+ const archived = [];
246
+ for (const file of LEGACY_ARTIFACTS) {
247
+ const src = join(root, file);
248
+ if (existsSync(src)) {
249
+ const dest = join(root, '.agentxchain', 'legacy', file);
250
+ copyFileSync(src, dest);
251
+ archived.push(file);
252
+ }
253
+ }
254
+
255
+ // Step 7: Write migration report
256
+ const report = {
257
+ migrated_at: new Date().toISOString(),
258
+ original_version: 3,
259
+ target_version: '1.0',
260
+ project: projectName,
261
+ template: 'generic',
262
+ backup_path: `.agentxchain/backups/agentxchain.v3.${timestamp}.json`,
263
+ inferred_phase: inferredPhase,
264
+ archived_files: archived,
265
+ inferences,
266
+ post_migration_status: 'paused',
267
+ post_migration_blocked_on: 'human:migration-review',
268
+ requires_human_review: [
269
+ 'Verify write_authority for each role',
270
+ 'Update runtimes from manual to local_cli or api_proxy as needed',
271
+ 'Review inferred phase and gate status',
272
+ 'Review and customize prompt templates in .agentxchain/prompts/',
273
+ 'Review the migration state, then start the first governed turn with agentxchain resume'
274
+ ]
275
+ };
276
+
277
+ const reportMd = `# Migration Report
278
+
279
+ **Migrated at:** ${report.migrated_at}
280
+ **Original version:** v3 → governed mode (schema_version: "1.0")
281
+ **Project:** ${projectName}
282
+ **Template:** \`generic\` (explicit baseline; migrate does not infer project shape)
283
+ **Backup:** \`${report.backup_path}\`
284
+
285
+ ## Inferred Phase
286
+
287
+ **${inferredPhase}** (derived from legacy state)
288
+
289
+ ## Archived Legacy Files
290
+
291
+ ${archived.map(f => `- \`${f}\` → \`.agentxchain/legacy/${f}\``).join('\n') || '(none)'}
292
+
293
+ ## Inferred Fields Requiring Review
294
+
295
+ ${inferences.map(i => `- **${i.role}.${i.field}** = \`${i.inferred}\` — ${i.note}`).join('\n')}
296
+
297
+ ## Post-Migration Status
298
+
299
+ **Status:** paused
300
+ **Blocked on:** human:migration-review
301
+
302
+ ## Required Human Actions
303
+
304
+ ${report.requires_human_review.map((r, i) => `${i + 1}. ${r}`).join('\n')}
305
+ `;
306
+
307
+ writeFileSync(join(root, '.agentxchain', 'migration-report.md'), reportMd);
308
+
309
+ // Ensure planning artifacts exist
310
+ mkdirSync(join(root, '.planning'), { recursive: true });
311
+ const planningFiles = {
312
+ 'PM_SIGNOFF.md': `# PM Signoff — ${projectName}\n\nApproved: NO\n`,
313
+ 'ROADMAP.md': `# Roadmap — ${projectName}\n\n(Migrated from v3. Review and update.)\n`,
314
+ 'acceptance-matrix.md': `# Acceptance Matrix — ${projectName}\n\n(QA fills this.)\n`,
315
+ 'ship-verdict.md': `# Ship Verdict — ${projectName}\n\n## Verdict: PENDING\n`
316
+ };
317
+ for (const [file, content] of Object.entries(planningFiles)) {
318
+ const path = join(root, '.planning', file);
319
+ if (!existsSync(path)) {
320
+ writeFileSync(path, content);
321
+ }
322
+ }
323
+
324
+ // Output
325
+ if (opts.json) {
326
+ console.log(JSON.stringify(report, null, 2));
327
+ return;
328
+ }
329
+
330
+ console.log('');
331
+ console.log(chalk.green(` ✓ Migrated to governed mode`));
332
+ console.log('');
333
+ console.log(` ${chalk.dim('Backup:')} ${report.backup_path}`);
334
+ console.log(` ${chalk.dim('Archived:')} ${archived.join(', ') || '(none)'}`);
335
+ console.log(` ${chalk.dim('Phase:')} ${inferredPhase}`);
336
+ console.log(` ${chalk.dim('Status:')} paused (awaiting human review)`);
337
+ console.log('');
338
+ console.log(chalk.yellow(' ⚠ Review the migration report:'));
339
+ console.log(` ${chalk.bold('.agentxchain/migration-report.md')}`);
340
+ console.log('');
341
+ console.log(chalk.cyan(' Next:'));
342
+ console.log(` ${chalk.bold('agentxchain validate')} ${chalk.dim('# verify governed config')}`);
343
+ console.log(` ${chalk.bold('agentxchain status')} ${chalk.dim('# see governed state')}`);
344
+ console.log(` ${chalk.bold('agentxchain resume')} ${chalk.dim('# assign the first governed turn')}`);
345
+ console.log(` ${chalk.bold('agentxchain accept-turn')} ${chalk.dim('# accept a valid staged turn result')}`);
346
+ console.log(` ${chalk.bold('agentxchain reject-turn --reason "<reason>"')} ${chalk.dim('# retry or escalate a bad turn result')}`);
347
+ console.log('');
348
+ }