agentxchain 2.128.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:
@@ -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')
@@ -625,6 +639,7 @@ program
625
639
  .description('Resume a governed project: initialize or continue a run and assign the next turn')
626
640
  .option('--role <role>', 'Override the target role (default: phase entry role)')
627
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')
628
643
  .action(resumeCommand);
629
644
 
630
645
  program
@@ -636,7 +651,7 @@ program
636
651
  .command('inject <description>')
637
652
  .description('Inject a priority work item into the intake queue (composed record + triage + approve)')
638
653
  .option('--priority <level>', 'Priority level (p0, p1, p2, p3)', 'p0')
639
- .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')
640
655
  .option('--charter <text>', 'Delivery charter (defaults to description)')
641
656
  .option('--acceptance <text>', 'Comma-separated acceptance criteria')
642
657
  .option('--approver <name>', 'Approver identity', 'human')
@@ -668,14 +683,23 @@ program
668
683
  .option('--reassign', 'Immediately re-dispatch a conflicted turn with conflict context')
669
684
  .action(rejectTurnCommand);
670
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
+
671
693
  program
672
694
  .command('step')
673
695
  .description('Run a single governed turn: assign, dispatch, wait, validate, accept/reject')
674
696
  .option('--role <role>', 'Override the target role (default: phase entry role)')
675
697
  .option('--resume', 'Resume waiting for an already-active turn')
676
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')
677
700
  .option('--poll <seconds>', 'Polling interval for manual adapter in seconds', '2')
678
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)')
679
703
  .option('--auto-reject', 'Auto-reject and retry on validation failure')
680
704
  .action(stepCommand);
681
705
 
@@ -911,7 +935,7 @@ intakeCmd
911
935
  .description('Triage a detected intent — set priority, template, charter, and acceptance')
912
936
  .option('--intent <id>', 'Intent ID to triage')
913
937
  .option('--priority <level>', 'Priority level (p0, p1, p2, p3)')
914
- .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)')
915
939
  .option('--charter <text>', 'Delivery charter text')
916
940
  .option('--acceptance <text>', 'Comma-separated acceptance criteria')
917
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.128.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
 
@@ -513,6 +517,16 @@ function formatRuntimeSummary(runtimeId, runtime) {
513
517
  return `${command} (${runtime.prompt_transport || runtime.type})`;
514
518
  }
515
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
+
516
530
  function hasTemplateDefinedRouting(template) {
517
531
  return Boolean(template?.scaffold_blueprint?.routing && Object.keys(template.scaffold_blueprint.routing).length > 0);
518
532
  }
@@ -620,9 +634,15 @@ function buildScaffoldConfigFromTemplate(template, localDevRuntime, workflowKitC
620
634
  (Array.isArray(runtimeOptions.devCommand) && runtimeOptions.devCommand.length > 0)
621
635
  || (typeof runtimeOptions.devPromptTransport === 'string' && runtimeOptions.devPromptTransport.trim())
622
636
  );
623
-
624
- if (!blueprint || Object.values(roles).some((role) => role?.runtime === 'local-dev') || explicitLocalDevRequested) {
625
- 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
+ }
626
646
  }
627
647
 
628
648
  if (explicitLocalDevRequested && roles?.dev?.runtime === 'manual-dev') {
@@ -813,14 +833,23 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
813
833
  // TALK.md
814
834
  writeFileSync(join(dir, 'TALK.md'), `# ${projectName} — Team Talk File\n\nCanonical human-readable handoff log for all agents.\n\n---\n\n`);
815
835
 
816
- // .gitignore additions
836
+ // .gitignore additions with inline comments so operators know what to commit vs. ignore
817
837
  const gitignorePath = join(dir, '.gitignore');
818
- 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/'];
819
848
  if (!existsSync(gitignorePath)) {
820
- writeFileSync(gitignorePath, requiredIgnores.join('\n') + '\n');
849
+ writeFileSync(gitignorePath, gitignoreContent);
821
850
  } else {
822
851
  const existingIgnore = readFileSync(gitignorePath, 'utf8');
823
- const missing = requiredIgnores.filter(entry => !existingIgnore.split(/\r?\n/).includes(entry));
852
+ const missing = requiredPaths.filter(entry => !existingIgnore.split(/\r?\n/).includes(entry));
824
853
  if (missing.length > 0) {
825
854
  const prefix = existingIgnore.endsWith('\n') ? '' : '\n';
826
855
  writeFileSync(gitignorePath, existingIgnore + prefix + missing.join('\n') + '\n');
@@ -864,6 +893,8 @@ async function initGoverned(opts) {
864
893
  console.error(' cli-tool Governed scaffold for a CLI tool');
865
894
  console.error(' library Governed scaffold for a reusable package');
866
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');
867
898
  process.exit(1);
868
899
  }
869
900
  selectedTemplate = loadGovernedTemplate(templateId);
@@ -74,7 +74,7 @@ export async function injectCommand(description, opts) {
74
74
  console.log('');
75
75
  console.log(chalk.red.bold(' ⚡ Preemption marker written'));
76
76
  console.log(chalk.dim(' The current run will yield after the active turn completes.'));
77
- console.log(chalk.dim(' The scheduler/continuous loop will pick up this intent next.'));
77
+ console.log(chalk.dim(' The next dispatch (manual resume, step --resume, or continuous loop) will consume this intent.'));
78
78
  }
79
79
 
80
80
  console.log('');