agentxchain 2.127.0 → 2.129.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.
package/README.md CHANGED
@@ -15,6 +15,7 @@ Legacy IDE-window coordination is still shipped as a compatibility mode for team
15
15
  - [CLI reference](https://agentxchain.dev/docs/cli/)
16
16
  - [Lights-Out Scheduling](https://agentxchain.dev/docs/lights-out-scheduling/)
17
17
  - [Templates](https://agentxchain.dev/docs/templates/)
18
+ - [Automation Patterns](https://agentxchain.dev/docs/automation-patterns/)
18
19
  - [Export schema reference](https://agentxchain.dev/docs/export-schema/)
19
20
  - [Adapter reference](https://agentxchain.dev/docs/adapters/)
20
21
  - [Protocol v7](https://agentxchain.dev/docs/protocol/)
@@ -107,6 +108,7 @@ Built-in governed templates:
107
108
  - `cli-tool`: command surface, platform support, distribution checklist
108
109
  - `library`: public API, compatibility policy, release and adoption checklist
109
110
  - `web-app`: user flows, UI acceptance, browser support
111
+ - `full-local-cli`: human-gated automation pattern with PM, Dev, QA, and Director all on authoritative `local_cli`
110
112
  - `enterprise-app`: enterprise planning artifacts plus blueprint-backed `architect` and `security_reviewer` phases
111
113
 
112
114
  Inspect the shipped template surfaces instead of inferring them from docs:
@@ -172,7 +174,8 @@ agentxchain step
172
174
  | `template validate` | Prove the template registry, workflow-kit scaffold contract, and planning artifact completeness (`--json` exposes a `workflow_kit` block) |
173
175
  | `verify turn` | Replay a staged turn's declared machine-evidence commands to confirm reproducibility before acceptance |
174
176
  | `replay turn` | Replay an accepted turn's machine-evidence commands from history for audit and drift detection |
175
- | `verify protocol` | Run the shipped protocol conformance suite against a target implementation |
177
+ | `conformance check` | Preferred front door for the shipped protocol conformance suite |
178
+ | `verify protocol` | Compatibility alias for the shipped protocol conformance suite |
176
179
  | `dashboard` | Open the live local governance dashboard in your browser for the current repo/workspace or multi-repo coordinator initiative, including pending gate approvals |
177
180
  | `run [--auto-approve] [--max-turns N] [--dry-run]` | Drive a governed run from start to completion — dispatches turns, handles gates, manages rejection/retry |
178
181
 
@@ -247,9 +250,11 @@ Partial coordinator artifacts are first-class here too: `audit` and `report` kee
247
250
  AgentXchain ships a conformance kit under `.agentxchain-conformance/`. Use it to prove a runner or fork still implements the governed workflow contract:
248
251
 
249
252
  ```bash
250
- agentxchain verify protocol --tier 3 --target .
253
+ agentxchain conformance check --tier 3 --target .
251
254
  ```
252
255
 
256
+ `agentxchain verify protocol` remains available as a compatibility alias.
257
+
253
258
  Useful flags:
254
259
 
255
260
  - `--tier 1|2|3`: maximum conformance tier to verify
@@ -72,6 +72,7 @@ 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
74
  import { rejectTurnCommand } from '../src/commands/reject-turn.js';
75
+ import { reissueTurnCommand } from '../src/commands/reissue-turn.js';
75
76
  import { proposalListCommand, proposalDiffCommand, proposalApplyCommand, proposalRejectCommand } from '../src/commands/proposal.js';
76
77
  import { stepCommand } from '../src/commands/step.js';
77
78
  import { runCommand } from '../src/commands/run.js';
@@ -124,7 +125,7 @@ import { eventsCommand } from '../src/commands/events.js';
124
125
  import { connectorCheckCommand, connectorValidateCommand } from '../src/commands/connector.js';
125
126
  import { scheduleDaemonCommand, scheduleListCommand, scheduleRunDueCommand, scheduleStatusCommand } from '../src/commands/schedule.js';
126
127
  import { chainLatestCommand, chainListCommand, chainShowCommand } from '../src/commands/chain.js';
127
- import { missionAttachChainCommand, missionListCommand, missionPlanApproveCommand, missionPlanAutopilotCommand, missionPlanCommand, missionPlanLaunchCommand, missionPlanListCommand, missionPlanShowCommand, missionShowCommand, missionStartCommand } from '../src/commands/mission.js';
128
+ import { missionAttachChainCommand, missionBindCoordinatorCommand, missionListCommand, missionPlanApproveCommand, missionPlanAutopilotCommand, missionPlanCommand, missionPlanLaunchCommand, missionPlanListCommand, missionPlanShowCommand, missionShowCommand, missionStartCommand } from '../src/commands/mission.js';
128
129
 
129
130
  const __dirname = dirname(fileURLToPath(import.meta.url));
130
131
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
@@ -142,7 +143,7 @@ program
142
143
  .option('-y, --yes', 'Skip guided prompts, use defaults')
143
144
  .option('--governed', 'Create a governed project (orchestrator-owned state)')
144
145
  .option('--dir <path>', 'Scaffold target directory. Use "." for in-place bootstrap.')
145
- .option('--template <id>', 'Governed scaffold template: generic, api-service, cli-tool, library, web-app, enterprise-app')
146
+ .option('--template <id>', 'Governed scaffold template: generic, api-service, cli-tool, library, web-app, full-local-cli, enterprise-app')
146
147
  .option('--dev-command <parts...>', 'Governed local-dev command parts. Include {prompt} for argv prompt delivery.')
147
148
  .option('--dev-prompt-transport <mode>', 'Governed local-dev prompt transport: argv, stdin, dispatch_bundle_only')
148
149
  .option('--goal <text>', 'Project goal — persisted in config and rendered in every dispatch bundle')
@@ -445,10 +446,23 @@ missionCmd
445
446
  .option('--constraint <text>', 'Add a constraint to the planner when using --plan (repeatable)', collectOption, [])
446
447
  .option('--role-hint <role>', 'Hint available roles to the planner when using --plan (repeatable)', collectOption, [])
447
448
  .option('--planner-output-file <path>', 'Read planner JSON output from a file instead of calling the configured planner')
449
+ .option('--multi', 'Create a multi-repo mission with coordinator initialization')
450
+ .option('--coordinator-config <path>', 'Path to agentxchain-multi.json (required with --multi)')
451
+ .option('--coordinator-workspace <path>', 'Coordinator workspace path (defaults to project root)')
448
452
  .option('-j, --json', 'Output as JSON')
449
453
  .option('-d, --dir <path>', 'Project directory')
450
454
  .action(missionStartCommand);
451
455
 
456
+ missionCmd
457
+ .command('bind-coordinator [mission_id]')
458
+ .description('Bind an existing coordinator super_run to a mission')
459
+ .requiredOption('--super-run-id <id>', 'Coordinator super_run_id to bind')
460
+ .option('--coordinator-config <path>', 'Path to agentxchain-multi.json')
461
+ .option('--coordinator-workspace <path>', 'Coordinator workspace path')
462
+ .option('-j, --json', 'Output as JSON')
463
+ .option('-d, --dir <path>', 'Project directory')
464
+ .action(missionBindCoordinatorCommand);
465
+
452
466
  missionCmd
453
467
  .command('list')
454
468
  .description('List mission artifacts newest first')
@@ -542,6 +556,22 @@ program
542
556
  .option('-j, --json', 'Output as JSON')
543
557
  .action(validateCommand);
544
558
 
559
+ const conformanceCmd = program
560
+ .command('conformance')
561
+ .description('Run protocol conformance checks against local or remote implementations');
562
+
563
+ conformanceCmd
564
+ .command('check')
565
+ .description('Run the shipped protocol conformance fixture suite against a target implementation')
566
+ .option('--tier <tier>', 'Conformance tier to verify (1, 2, or 3)', '1')
567
+ .option('--surface <surface>', 'Restrict verification to a single surface')
568
+ .option('--target <path>', 'Target root containing .agentxchain-conformance/capabilities.json', '.')
569
+ .option('--remote <url>', 'Remote HTTP conformance endpoint base URL')
570
+ .option('--token <token>', 'Bearer token for remote HTTP conformance endpoint')
571
+ .option('--timeout <ms>', 'Per-fixture remote HTTP timeout in milliseconds', '30000')
572
+ .option('--format <format>', 'Output format: text or json', 'text')
573
+ .action(verifyProtocolCommand);
574
+
545
575
  const verifyCmd = program
546
576
  .command('verify')
547
577
  .description('Verify governed turns, export artifacts, and protocol conformance targets');
@@ -609,6 +639,7 @@ program
609
639
  .description('Resume a governed project: initialize or continue a run and assign the next turn')
610
640
  .option('--role <role>', 'Override the target role (default: phase entry role)')
611
641
  .option('--turn <id>', 'Target a specific retained turn when multiple exist')
642
+ .option('--no-intent', 'Do not bind the next queued approved/planned intake intent to the next turn')
612
643
  .action(resumeCommand);
613
644
 
614
645
  program
@@ -620,7 +651,7 @@ program
620
651
  .command('inject <description>')
621
652
  .description('Inject a priority work item into the intake queue (composed record + triage + approve)')
622
653
  .option('--priority <level>', 'Priority level (p0, p1, p2, p3)', 'p0')
623
- .option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, enterprise-app)', 'generic')
654
+ .option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, full-local-cli, enterprise-app)', 'generic')
624
655
  .option('--charter <text>', 'Delivery charter (defaults to description)')
625
656
  .option('--acceptance <text>', 'Comma-separated acceptance criteria')
626
657
  .option('--approver <name>', 'Approver identity', 'human')
@@ -652,14 +683,23 @@ program
652
683
  .option('--reassign', 'Immediately re-dispatch a conflicted turn with conflict context')
653
684
  .action(rejectTurnCommand);
654
685
 
686
+ program
687
+ .command('reissue-turn')
688
+ .description('Invalidate an active turn and reissue it against current repo state (baseline drift recovery)')
689
+ .option('--turn <id>', 'Target a specific active turn when multiple turns exist')
690
+ .option('--reason <reason>', 'Reason for the reissue (e.g., baseline drift, runtime rebinding)')
691
+ .action(reissueTurnCommand);
692
+
655
693
  program
656
694
  .command('step')
657
695
  .description('Run a single governed turn: assign, dispatch, wait, validate, accept/reject')
658
696
  .option('--role <role>', 'Override the target role (default: phase entry role)')
659
697
  .option('--resume', 'Resume waiting for an already-active turn')
660
698
  .option('--turn <id>', 'Target a specific active turn (required with --resume when multiple turns exist)')
699
+ .option('--no-intent', 'Do not bind the next queued approved/planned intake intent to the next turn')
661
700
  .option('--poll <seconds>', 'Polling interval for manual adapter in seconds', '2')
662
701
  .option('--verbose', 'Stream local_cli subprocess output while the turn is running')
702
+ .option('--stream', 'Stream live subprocess output to terminal (alias for --verbose)')
663
703
  .option('--auto-reject', 'Auto-reject and retry on validation failure')
664
704
  .action(stepCommand);
665
705
 
@@ -895,7 +935,7 @@ intakeCmd
895
935
  .description('Triage a detected intent — set priority, template, charter, and acceptance')
896
936
  .option('--intent <id>', 'Intent ID to triage')
897
937
  .option('--priority <level>', 'Priority level (p0, p1, p2, p3)')
898
- .option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, enterprise-app)')
938
+ .option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, full-local-cli, enterprise-app)')
899
939
  .option('--charter <text>', 'Delivery charter text')
900
940
  .option('--acceptance <text>', 'Comma-separated acceptance criteria')
901
941
  .option('--suppress', 'Suppress the intent instead of triaging')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.127.0",
3
+ "version": "2.129.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,6 +19,18 @@ cd "$CLI_DIR"
19
19
 
20
20
  TARGET_VERSION=""
21
21
 
22
+ FORMULA_PATH="${CLI_DIR}/homebrew/agentxchain.rb"
23
+
24
+ formula_url() {
25
+ local formula_path="$1"
26
+ grep -E '^\s*url\s+"' "$formula_path" | sed 's/.*url *"\([^"]*\)".*/\1/' || true
27
+ }
28
+
29
+ formula_sha() {
30
+ local formula_path="$1"
31
+ grep -E '^\s*sha256\s+"' "$formula_path" | sed 's/.*sha256 *"\([a-f0-9]*\)".*/\1/' || true
32
+ }
33
+
22
34
  while [[ $# -gt 0 ]]; do
23
35
  case "$1" in
24
36
  --target-version)
@@ -59,18 +71,56 @@ echo "[2/4] Syncing repo mirror to published tarball..."
59
71
  bash "${SCRIPT_DIR}/sync-homebrew.sh" --target-version "$TARGET_VERSION"
60
72
  echo " OK: repo mirror synced"
61
73
 
62
- # Step 3: Run the full test suite WITHOUT the preflight skip
63
- echo "[3/4] Running full test suite (no preflight skip)..."
64
- echo " This verifies the Homebrew mirror contract passes with the real SHA."
74
+ # Step 3: Explicitly prove the repo mirror now matches registry truth
75
+ echo "[3/5] Verifying repo mirror against registry tarball..."
76
+ TARBALL_URL="$(npm view "agentxchain@${TARGET_VERSION}" dist.tarball 2>/dev/null || echo "")"
77
+ if [[ -z "$TARBALL_URL" ]]; then
78
+ echo " FAIL: npm did not return dist.tarball for agentxchain@${TARGET_VERSION}"
79
+ exit 1
80
+ fi
81
+
82
+ TARBALL_SHA="$(curl -sL "$TARBALL_URL" | shasum -a 256 | awk '{print $1}')"
83
+ if [[ -z "$TARBALL_SHA" ]] || [[ ${#TARBALL_SHA} -ne 64 ]]; then
84
+ echo " FAIL: could not compute valid SHA256 from registry tarball"
85
+ exit 1
86
+ fi
87
+
88
+ if [[ ! -f "$FORMULA_PATH" ]]; then
89
+ echo " FAIL: Homebrew formula not found at ${FORMULA_PATH}"
90
+ exit 1
91
+ fi
92
+
93
+ FORMULA_URL="$(formula_url "$FORMULA_PATH")"
94
+ FORMULA_SHA="$(formula_sha "$FORMULA_PATH")"
95
+
96
+ if [[ "$FORMULA_URL" != "$TARBALL_URL" ]]; then
97
+ echo " FAIL: repo mirror formula URL does not match registry tarball"
98
+ echo " formula: ${FORMULA_URL:-<missing>}"
99
+ echo " registry: ${TARBALL_URL}"
100
+ exit 1
101
+ fi
102
+ echo " OK: repo mirror formula URL matches registry tarball"
103
+
104
+ if [[ "$FORMULA_SHA" != "$TARBALL_SHA" ]]; then
105
+ echo " FAIL: repo mirror formula SHA256 does not match registry tarball"
106
+ echo " formula: ${FORMULA_SHA:-<missing>}"
107
+ echo " registry: ${TARBALL_SHA}"
108
+ exit 1
109
+ fi
110
+ echo " OK: repo mirror formula SHA256 matches registry tarball"
111
+
112
+ # Step 4: Run the full test suite WITHOUT the preflight skip
113
+ echo "[4/5] Running full test suite (no preflight skip)..."
114
+ echo " This verifies the broader Homebrew mirror contract passes with the real SHA."
65
115
  npm test
66
116
  echo " OK: full test suite green"
67
117
 
68
- # Step 4: Summary
118
+ # Step 5: Summary
69
119
  echo ""
70
120
  echo "============================================="
71
121
  echo "Post-publish verification PASSED."
72
122
  echo " - npm: agentxchain@${TARGET_VERSION} live"
73
- echo " - repo mirror: SHA synced to published tarball"
123
+ echo " - repo mirror: formula URL and SHA match the published tarball"
74
124
  echo " - test suite: green without preflight skip"
75
125
  echo ""
76
126
  echo "Main is now in Phase 3 (post-sync). Commit and push the mirror update."
@@ -27,7 +27,11 @@ function printText(result, exitCode) {
27
27
  console.log('');
28
28
 
29
29
  for (const connector of result.connectors) {
30
- const badge = connector.level === 'pass' ? chalk.green('PASS') : chalk.red('FAIL');
30
+ const badge = connector.level === 'pass'
31
+ ? chalk.green('PASS')
32
+ : connector.level === 'warn'
33
+ ? chalk.yellow('WARN')
34
+ : chalk.red('FAIL');
31
35
  console.log(` ${badge} ${connector.runtime_id} (${connector.type})`);
32
36
  console.log(` ${chalk.dim('Target:')} ${connector.target}`);
33
37
  console.log(` ${chalk.dim('Probe:')} ${connector.probe_kind}`);
@@ -44,12 +48,22 @@ function printText(result, exitCode) {
44
48
  console.log(` ${chalk.dim('Time:')} ${connector.latency_ms}ms`);
45
49
  }
46
50
  console.log(` ${chalk.dim('Detail:')} ${connector.detail}`);
51
+ if (Array.isArray(connector.authority_warnings) && connector.authority_warnings.length > 0) {
52
+ for (const warning of connector.authority_warnings) {
53
+ console.log(` ${chalk.yellow('⚠')} ${warning.detail}`);
54
+ if (warning.fix) {
55
+ console.log(` ${chalk.dim('Fix:')} ${warning.fix}`);
56
+ }
57
+ }
58
+ }
47
59
  }
48
60
 
49
61
  console.log('');
50
62
  const summary = result.overall === 'pass'
51
63
  ? chalk.green(` ✓ ${result.pass_count}/${result.connectors.length} connectors passed`)
52
- : chalk.red(` ${result.fail_count} connector failure(s), ${result.pass_count} passed`);
64
+ : result.overall === 'warn'
65
+ ? chalk.yellow(` ⚠ ${result.warn_count} connector warning(s), ${result.pass_count} passed`)
66
+ : chalk.red(` ${result.fail_count} connector failure(s), ${result.pass_count} passed`);
53
67
  console.log(summary);
54
68
  console.log('');
55
69
  process.exit(exitCode);
@@ -110,6 +124,7 @@ export async function connectorCheckCommand(runtimeId, options = {}) {
110
124
  overall: result.overall,
111
125
  timeout_ms: result.timeout_ms,
112
126
  pass_count: result.pass_count,
127
+ warn_count: result.warn_count,
113
128
  fail_count: result.fail_count,
114
129
  connectors: result.connectors,
115
130
  };
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'fs';
2
2
  import { execFileSync, execSync } from 'child_process';
3
3
  import { join } from 'path';
4
4
  import chalk from 'chalk';
5
- import { loadConfig, loadLock, findProjectRoot } from '../lib/config.js';
5
+ import { loadConfig, loadLock, findProjectRoot, loadProjectState } from '../lib/config.js';
6
6
  import { validateProject } from '../lib/validation.js';
7
7
  import { runAdmissionControl } from '../lib/admission-control.js';
8
8
  import { getWatchPid } from './watch.js';
@@ -10,12 +10,16 @@ import { getDashboardPid, getDashboardSession } from './dashboard.js';
10
10
  import { loadNormalizedConfig, detectConfigVersion } from '../lib/normalized-config.js';
11
11
  import { readDaemonState, evaluateDaemonStatus } from '../lib/run-schedule.js';
12
12
  import { getGovernedVersionSurface, formatGovernedVersionLabel } from '../lib/protocol-version.js';
13
+ import { getCliVersionHealth } from '../lib/cli-version.js';
13
14
  import { PLUGIN_MANIFEST_FILE } from '../lib/plugins.js';
14
15
  import {
15
16
  getRoleRuntimeCapabilityContract,
16
17
  summarizeRoleRuntimeCapability,
17
18
  summarizeRuntimeCapabilityContract,
18
19
  } from '../lib/runtime-capabilities.js';
20
+ import { detectActiveTurnBindingDrift } from '../lib/governed-state.js';
21
+ import { findPendingApprovedIntents } from '../lib/intake.js';
22
+ import { checkCleanBaseline } from '../lib/repo-observer.js';
19
23
 
20
24
  export async function doctorCommand(opts = {}) {
21
25
  const root = findProjectRoot(process.cwd());
@@ -55,6 +59,9 @@ export async function doctorCommand(opts = {}) {
55
59
 
56
60
  function governedDoctor(root, rawConfig, opts) {
57
61
  const checks = [];
62
+ const cliVersionHealth = getCliVersionHealth();
63
+
64
+ checks.push(buildCliVersionCheck(cliVersionHealth));
58
65
 
59
66
  // 1. Config validation
60
67
  const configResult = loadNormalizedConfig(rawConfig, root);
@@ -113,6 +120,63 @@ function governedDoctor(root, rawConfig, opts) {
113
120
  checks.push({ id: 'state_health', name: 'State health', level: 'warn', detail: 'No state file yet (first run pending)' });
114
121
  }
115
122
 
123
+ // 5b. Active turn binding drift (B-7: detect runtime rebinding mid-run)
124
+ if (normalized && existsSync(join(root, '.agentxchain', 'state.json'))) {
125
+ try {
126
+ const stateData = loadProjectState(root, normalized);
127
+ const drifts = detectActiveTurnBindingDrift(stateData, normalized);
128
+ if (drifts.length > 0) {
129
+ const driftSummary = drifts.map(d => {
130
+ const parts = [];
131
+ if (d.runtime_changed) parts.push(`runtime ${d.old_runtime} → ${d.new_runtime}`);
132
+ if (d.authority_changed) parts.push(`authority ${d.old_authority} → ${d.new_authority}`);
133
+ return `${d.turn_id} (${d.role_id}): ${parts.join(', ')}`;
134
+ }).join('; ');
135
+ checks.push({
136
+ id: 'binding_drift',
137
+ name: 'Active turn binding',
138
+ level: 'warn',
139
+ detail: `Stale binding: ${driftSummary}. Run: agentxchain reissue-turn`,
140
+ drifts,
141
+ });
142
+ } else if (Object.keys(stateData?.active_turns || {}).length > 0) {
143
+ checks.push({
144
+ id: 'binding_drift',
145
+ name: 'Active turn binding',
146
+ level: 'pass',
147
+ detail: 'Active turns match current config bindings',
148
+ });
149
+ }
150
+ } catch {
151
+ // State couldn't be loaded — skip drift check
152
+ }
153
+ }
154
+
155
+ // 5c. Clean working tree pre-flight for authoritative/proposed roles
156
+ if (normalized?.roles) {
157
+ const writableRoles = Object.entries(normalized.roles)
158
+ .filter(([, role]) => role.write_authority === 'authoritative' || role.write_authority === 'proposed')
159
+ .map(([id]) => id);
160
+ if (writableRoles.length > 0) {
161
+ const cleanCheck = checkCleanBaseline(root, 'authoritative');
162
+ if (cleanCheck.clean) {
163
+ checks.push({
164
+ id: 'clean_baseline',
165
+ name: 'Clean working tree',
166
+ level: 'pass',
167
+ detail: 'Working tree is clean for authoritative/proposed turns',
168
+ });
169
+ } else {
170
+ checks.push({
171
+ id: 'clean_baseline',
172
+ name: 'Clean working tree',
173
+ level: 'warn',
174
+ detail: `${cleanCheck.reason} Writable roles: ${writableRoles.join(', ')}`,
175
+ });
176
+ }
177
+ }
178
+ }
179
+
116
180
  // 6. Schedule health (only when schedules configured)
117
181
  const schedules = normalized?.schedules;
118
182
  const hasSchedules = schedules && typeof schedules === 'object' && Object.keys(schedules).length > 0;
@@ -258,6 +322,20 @@ function governedDoctor(root, rawConfig, opts) {
258
322
  }
259
323
  }
260
324
 
325
+ // 11. Pending intake intents (BUG-15 — informational)
326
+ {
327
+ const pendingIntents = findPendingApprovedIntents(root);
328
+ if (pendingIntents.length > 0) {
329
+ const summary = pendingIntents.map(pi => `[${pi.priority}] ${pi.intent_id}`).join(', ');
330
+ checks.push({
331
+ id: 'pending_intents',
332
+ name: 'Pending intents',
333
+ level: 'info',
334
+ detail: `${pendingIntents.length} approved intent(s) in queue, next turn will consume them: ${summary}`,
335
+ });
336
+ }
337
+ }
338
+
261
339
  // Compute summary
262
340
  const failCount = checks.filter(c => c.level === 'fail').length;
263
341
  const warnCount = checks.filter(c => c.level === 'warn').length;
@@ -270,6 +348,10 @@ function governedDoctor(root, rawConfig, opts) {
270
348
  project: projectId,
271
349
  ...versionSurface,
272
350
  config_version: versionSurface.config_generation,
351
+ cli_version: cliVersionHealth.current_version,
352
+ docs_min_cli_version: cliVersionHealth.docs_min_cli_version,
353
+ cli_version_status: cliVersionHealth.status,
354
+ cli_version_source: cliVersionHealth.source,
273
355
  overall,
274
356
  connector_probe_recommended: connectorProbe.recommended,
275
357
  connector_probe_runtime_ids: connectorProbe.runtimeIds,
@@ -312,6 +394,11 @@ function governedDoctor(root, rawConfig, opts) {
312
394
  } else {
313
395
  console.log(chalk.red(` Not ready: ${failCount} failure${failCount > 1 ? 's' : ''}, ${warnCount} warning${warnCount > 1 ? 's' : ''}.`));
314
396
  }
397
+ if (cliVersionHealth.stale) {
398
+ console.log(chalk.yellow(' Fix: npm install -g agentxchain@latest'));
399
+ console.log(chalk.yellow(' brew upgrade agentxchain'));
400
+ console.log(chalk.yellow(' npx --yes -p agentxchain@latest -c "agentxchain doctor"'));
401
+ }
315
402
  if (failCount === 0 && connectorProbe.recommended) {
316
403
  console.log(chalk.dim(` Next: ${connectorProbe.detail}`));
317
404
  }
@@ -341,6 +428,33 @@ function attachRuntimeContract(baseCheck, rtId, rt, boundRoleEntries) {
341
428
  };
342
429
  }
343
430
 
431
+ function buildCliVersionCheck(cliVersionHealth) {
432
+ if (cliVersionHealth.status === 'stale') {
433
+ return {
434
+ id: 'cli_version',
435
+ name: 'CLI version',
436
+ level: 'warn',
437
+ detail: `${cliVersionHealth.detail} Fix with npm install -g agentxchain@latest, brew upgrade agentxchain, or npx --yes -p agentxchain@latest -c "agentxchain doctor".`,
438
+ };
439
+ }
440
+
441
+ if (cliVersionHealth.status === 'ok') {
442
+ return {
443
+ id: 'cli_version',
444
+ name: 'CLI version',
445
+ level: 'pass',
446
+ detail: cliVersionHealth.detail,
447
+ };
448
+ }
449
+
450
+ return {
451
+ id: 'cli_version',
452
+ name: 'CLI version',
453
+ level: 'info',
454
+ detail: cliVersionHealth.detail,
455
+ };
456
+ }
457
+
344
458
  function checkRuntimeReachable(rtId, rt, boundRoleEntries = []) {
345
459
  const base = { id: `runtime_${rtId}`, name: `Runtime: ${rtId}` };
346
460
 
@@ -449,8 +563,10 @@ function legacyDoctor(root, opts) {
449
563
 
450
564
  const { config } = result;
451
565
  const lock = loadLock(root);
566
+ const cliVersionHealth = getCliVersionHealth();
452
567
  const checks = [];
453
568
 
569
+ checks.push(buildCliVersionCheck(cliVersionHealth));
454
570
  checks.push(checkFile('agentxchain.json', existsSync(join(root, 'agentxchain.json')), 'Project config exists'));
455
571
  checks.push(checkFile('lock.json', !!lock, 'Lock file exists'));
456
572
  checks.push(checkBinary('cursor', 'Cursor CLI available (optional for non-macOS launch)'));
@@ -489,6 +605,11 @@ function legacyDoctor(root, opts) {
489
605
  } else {
490
606
  console.log(chalk.red(` Not ready: ${failCount} blocking issue(s).`));
491
607
  }
608
+ if (cliVersionHealth.stale) {
609
+ console.log(chalk.yellow(' Fix: npm install -g agentxchain@latest'));
610
+ console.log(chalk.yellow(' brew upgrade agentxchain'));
611
+ console.log(chalk.yellow(' npx --yes -p agentxchain@latest -c "agentxchain doctor"'));
612
+ }
492
613
  console.log('');
493
614
  }
494
615
 
@@ -61,12 +61,16 @@ function printEvent(evt) {
61
61
  const runId = evt.run_id ? evt.run_id.slice(0, 12) : '—';
62
62
  const phase = evt.phase || '—';
63
63
  const turnInfo = evt.turn?.role_id ? ` [${evt.turn.role_id}]` : '';
64
+ const intentInfo = evt.intent_id ? ` intent=${evt.intent_id}` : '';
64
65
  const conflictDetail = evt.event_type === 'turn_conflicted'
65
66
  ? ` — ${formatConflictDetail(evt)}`
66
67
  : '';
67
68
  const rejectionDetail = evt.event_type === 'turn_rejected' && evt.payload?.reason
68
69
  ? ` — ${evt.payload.reason}${evt.payload.failed_stage ? ` (${evt.payload.failed_stage})` : ''}`
69
70
  : '';
71
+ const acceptanceFailedDetail = evt.event_type === 'acceptance_failed' && evt.payload?.reason
72
+ ? ` — ${evt.payload.reason}${evt.payload.stage ? ` (${evt.payload.stage})` : ''}`
73
+ : '';
70
74
  const phaseTransitionDetail = evt.event_type === 'phase_entered' && evt.payload?.from && evt.payload?.to
71
75
  ? ` ${evt.payload.from} → ${evt.payload.to}${evt.payload.trigger ? ` (${evt.payload.trigger})` : ''}`
72
76
  : '';
@@ -78,7 +82,7 @@ function printEvent(evt) {
78
82
  : evt.event_type === 'human_escalation_resolved' && evt.payload?.escalation_id
79
83
  ? ` ${evt.payload.escalation_id} via ${evt.payload.resolved_via || '?'}`
80
84
  : '';
81
- console.log(`${chalk.dim(ts)} ${type} ${chalk.cyan(runId)} ${phase}${turnInfo}${conflictDetail}${rejectionDetail}${phaseTransitionDetail}${gateFailedDetail}${humanEscalationDetail}`);
85
+ console.log(`${chalk.dim(ts)} ${type} ${chalk.cyan(runId)} ${phase}${turnInfo}${intentInfo}${conflictDetail}${rejectionDetail}${acceptanceFailedDetail}${phaseTransitionDetail}${gateFailedDetail}${humanEscalationDetail}`);
82
86
  }
83
87
 
84
88
  function formatConflictDetail(evt) {
@@ -116,6 +120,8 @@ function colorEventType(type) {
116
120
  turn_dispatched: chalk.blue,
117
121
  turn_accepted: chalk.green,
118
122
  turn_rejected: chalk.yellow,
123
+ acceptance_failed: chalk.red.bold,
124
+ turn_reissued: chalk.cyan,
119
125
  turn_conflicted: chalk.redBright,
120
126
  phase_entered: chalk.magenta,
121
127
  escalation_raised: chalk.red.bold,
@@ -257,9 +257,11 @@ You are QA. Your mandate: **${role.mandate}**
257
257
  - \`.planning/ship-verdict.md\` — your overall ship/no-ship recommendation
258
258
  - \`.planning/RELEASE_NOTES.md\` — user-facing release notes with impact and verification summary
259
259
 
260
- ## You Cannot Modify Code
260
+ ## File Access
261
261
 
262
- You have \`review_only\` write authority. You may NOT modify product files. You may only create/modify files under \`.planning/\` and \`.agentxchain/reviews/\`. Your artifact type must be \`review\`.
262
+ ${role.write_authority === 'review_only'
263
+ ? 'You have `review_only` write authority. You may NOT modify product files. You may only create/modify files under `.planning/` and `.agentxchain/reviews/`. Your artifact type must be `review`.'
264
+ : 'You have direct write access for this role. Use it to create or update QA-owned evidence and verification artifacts directly in the repo. Do not make unrelated product changes. Your artifact type should normally be `workspace` when you modify repo files directly.'}
263
265
 
264
266
  ## Runtime Truth
265
267
 
@@ -337,9 +339,11 @@ You are invoked when the normal PM → Dev → QA loop is stuck:
337
339
  - You may NOT override human decisions — escalate to \`human\` if needed
338
340
  - Every override must be recorded as a decision with clear rationale
339
341
 
340
- ## You Cannot Modify Code
342
+ ## File Access
341
343
 
342
- You have \`review_only\` write authority. Like QA, you must raise at least one objection (protocol requirement). Your artifact type is \`review\`.
344
+ ${role.write_authority === 'review_only'
345
+ ? 'You have `review_only` write authority. Like QA, you must raise at least one objection (protocol requirement). Your artifact type is `review`.'
346
+ : 'You have direct write access for this role. Use it sparingly to resolve tactical deadlocks or correct governance artifacts when necessary. Do not use Director authority as a shortcut around the normal PM -> Dev -> QA loop.'}
343
347
 
344
348
  ## Objection Requirement
345
349
 
@@ -441,10 +445,20 @@ function commandHasPromptPlaceholder(parts = []) {
441
445
  return parts.some((part) => typeof part === 'string' && part.includes('{prompt}'));
442
446
  }
443
447
 
448
+ function normalizeGovernedDevCommand(parts = []) {
449
+ if (!Array.isArray(parts) || parts.length === 0) {
450
+ return null;
451
+ }
452
+ const [head, ...rest] = parts;
453
+ const normalizedHead = String(head || '').trim().split(/\s+/).filter(Boolean);
454
+ const normalizedRest = rest
455
+ .map((part) => String(part).trim())
456
+ .filter(Boolean);
457
+ return [...normalizedHead, ...normalizedRest];
458
+ }
459
+
444
460
  function resolveGovernedLocalDevRuntime(opts = {}) {
445
- const customCommand = Array.isArray(opts.devCommand)
446
- ? opts.devCommand.map((part) => String(part).trim()).filter(Boolean)
447
- : null;
461
+ const customCommand = normalizeGovernedDevCommand(opts.devCommand);
448
462
  const explicitTransport = typeof opts.devPromptTransport === 'string' && opts.devPromptTransport.trim()
449
463
  ? opts.devPromptTransport.trim()
450
464
  : null;
@@ -503,6 +517,16 @@ function formatRuntimeSummary(runtimeId, runtime) {
503
517
  return `${command} (${runtime.prompt_transport || runtime.type})`;
504
518
  }
505
519
 
520
+ function runtimeMatchesDefaultGovernedLocalCli(runtime) {
521
+ if (!runtime || runtime.type !== 'local_cli') return false;
522
+ const command = Array.isArray(runtime.command) ? runtime.command : [];
523
+ return (
524
+ runtime.cwd === DEFAULT_GOVERNED_LOCAL_DEV_RUNTIME.cwd
525
+ && runtime.prompt_transport === DEFAULT_GOVERNED_LOCAL_DEV_RUNTIME.prompt_transport
526
+ && JSON.stringify(command) === JSON.stringify(DEFAULT_GOVERNED_LOCAL_DEV_RUNTIME.command)
527
+ );
528
+ }
529
+
506
530
  function hasTemplateDefinedRouting(template) {
507
531
  return Boolean(template?.scaffold_blueprint?.routing && Object.keys(template.scaffold_blueprint.routing).length > 0);
508
532
  }
@@ -610,9 +634,15 @@ function buildScaffoldConfigFromTemplate(template, localDevRuntime, workflowKitC
610
634
  (Array.isArray(runtimeOptions.devCommand) && runtimeOptions.devCommand.length > 0)
611
635
  || (typeof runtimeOptions.devPromptTransport === 'string' && runtimeOptions.devPromptTransport.trim())
612
636
  );
613
-
614
- if (!blueprint || Object.values(roles).some((role) => role?.runtime === 'local-dev') || explicitLocalDevRequested) {
615
- runtimes['local-dev'] = localDevRuntime;
637
+ const defaultLocalRuntimeIds = Object.entries(runtimes)
638
+ .filter(([, runtime]) => runtimeMatchesDefaultGovernedLocalCli(runtime))
639
+ .map(([runtimeId]) => runtimeId);
640
+
641
+ if (!blueprint || defaultLocalRuntimeIds.length > 0 || explicitLocalDevRequested) {
642
+ const runtimeIdsToReplace = defaultLocalRuntimeIds.length > 0 ? defaultLocalRuntimeIds : ['local-dev'];
643
+ for (const runtimeId of runtimeIdsToReplace) {
644
+ runtimes[runtimeId] = cloneJsonCompatible(localDevRuntime);
645
+ }
616
646
  }
617
647
 
618
648
  if (explicitLocalDevRequested && roles?.dev?.runtime === 'manual-dev') {
@@ -803,14 +833,23 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
803
833
  // TALK.md
804
834
  writeFileSync(join(dir, 'TALK.md'), `# ${projectName} — Team Talk File\n\nCanonical human-readable handoff log for all agents.\n\n---\n\n`);
805
835
 
806
- // .gitignore additions
836
+ // .gitignore additions with inline comments so operators know what to commit vs. ignore
807
837
  const gitignorePath = join(dir, '.gitignore');
808
- const requiredIgnores = ['.env', '.agentxchain/staging/', '.agentxchain/dispatch/'];
838
+ const gitignoreContent = [
839
+ '# AgentXchain — secrets',
840
+ '.env',
841
+ '',
842
+ '# AgentXchain — transient execution artifacts (never commit)',
843
+ '.agentxchain/staging/',
844
+ '.agentxchain/dispatch/',
845
+ '.agentxchain/transactions/',
846
+ ].join('\n') + '\n';
847
+ const requiredPaths = ['.env', '.agentxchain/staging/', '.agentxchain/dispatch/', '.agentxchain/transactions/'];
809
848
  if (!existsSync(gitignorePath)) {
810
- writeFileSync(gitignorePath, requiredIgnores.join('\n') + '\n');
849
+ writeFileSync(gitignorePath, gitignoreContent);
811
850
  } else {
812
851
  const existingIgnore = readFileSync(gitignorePath, 'utf8');
813
- const missing = requiredIgnores.filter(entry => !existingIgnore.split(/\r?\n/).includes(entry));
852
+ const missing = requiredPaths.filter(entry => !existingIgnore.split(/\r?\n/).includes(entry));
814
853
  if (missing.length > 0) {
815
854
  const prefix = existingIgnore.endsWith('\n') ? '' : '\n';
816
855
  writeFileSync(gitignorePath, existingIgnore + prefix + missing.join('\n') + '\n');
@@ -854,6 +893,8 @@ async function initGoverned(opts) {
854
893
  console.error(' cli-tool Governed scaffold for a CLI tool');
855
894
  console.error(' library Governed scaffold for a reusable package');
856
895
  console.error(' web-app Governed scaffold for a web application');
896
+ console.error(' full-local-cli Human-gated automation pattern with all roles on local_cli');
897
+ console.error(' enterprise-app Governed scaffold for multi-role enterprise delivery');
857
898
  process.exit(1);
858
899
  }
859
900
  selectedTemplate = loadGovernedTemplate(templateId);