agentxchain 2.13.0 → 2.15.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
@@ -14,6 +14,7 @@ Legacy IDE-window coordination is still shipped as a compatibility mode for team
14
14
  - [Adapter reference](https://agentxchain.dev/docs/adapters/)
15
15
  - [Protocol spec (v6)](https://agentxchain.dev/docs/protocol/)
16
16
  - [Protocol reference](https://agentxchain.dev/docs/protocol-reference/)
17
+ - [Build your own runner](https://agentxchain.dev/docs/build-your-own-runner/)
17
18
  - [Why governed multi-agent delivery matters](https://agentxchain.dev/why/)
18
19
 
19
20
  ## Install
@@ -99,10 +100,10 @@ For initiatives spanning multiple governed repos, use the coordinator to add cro
99
100
  npx agentxchain init --governed --template api-service --dir repos/backend -y
100
101
  npx agentxchain init --governed --template web-app --dir repos/frontend -y
101
102
  agentxchain multi init
102
- agentxchain multi step --repo backend --role pm
103
+ agentxchain multi step --json
103
104
  ```
104
105
 
105
- See the [multi-repo quickstart](https://agentxchain.dev/docs/quickstart#multi-repo-cold-start) for the full cold-start walkthrough.
106
+ If the coordinator enters `blocked`, fix the cause and run `agentxchain multi resume` before continuing with `multi step` or `multi approve-gate`. See the [multi-repo quickstart](https://agentxchain.dev/docs/quickstart#multi-repo-cold-start) for the full cold-start walkthrough.
106
107
 
107
108
  ### Migrate a legacy project
108
109
 
@@ -131,7 +132,10 @@ agentxchain step
131
132
  | `template validate` | Prove the template registry, workflow-kit scaffold contract, and planning artifact completeness (`--json` exposes a `workflow_kit` block) |
132
133
  | `verify protocol` | Run the shipped protocol conformance suite against a target implementation |
133
134
  | `dashboard` | Open the local governance dashboard in your browser for repo-local runs or multi-repo coordinator initiatives, including pending gate approvals |
134
- | `plugin install|list|remove` | Install, inspect, or remove governed hook plugins backed by `agentxchain-plugin.json` manifests |
135
+ | `multi init\|status\|step\|resume\|approve-gate\|resync` | Run the multi-repo coordinator lifecycle, including blocked-state recovery via `multi resume` |
136
+ | `intake record\|triage\|approve\|plan\|start\|scan\|resolve` | Continuous-delivery intake: turn delivery signals into governed work items |
137
+ | `intake handoff` | Bridge a planned intake intent to a coordinator workstream for multi-repo execution |
138
+ | `plugin install\|list\|remove` | Install, inspect, or remove governed hook plugins backed by `agentxchain-plugin.json` manifests |
135
139
 
136
140
  ### Shared utilities
137
141
 
@@ -196,6 +200,7 @@ TALK.md
196
200
 
197
201
  - `manual`: implemented
198
202
  - `local_cli`: implemented
203
+ - `mcp`: implemented for stdio and streamable HTTP tool-contract dispatch
199
204
  - `api_proxy`: implemented for synchronous review-only turns and stages a provider-backed result during `step`
200
205
 
201
206
  ## Legacy IDE Mode
@@ -88,6 +88,7 @@ import {
88
88
  multiInitCommand,
89
89
  multiStatusCommand,
90
90
  multiStepCommand,
91
+ multiResumeCommand,
91
92
  multiApproveGateCommand,
92
93
  multiResyncCommand,
93
94
  } from '../src/commands/multi.js';
@@ -96,6 +97,7 @@ import { intakeTriageCommand } from '../src/commands/intake-triage.js';
96
97
  import { intakeApproveCommand } from '../src/commands/intake-approve.js';
97
98
  import { intakePlanCommand } from '../src/commands/intake-plan.js';
98
99
  import { intakeStartCommand } from '../src/commands/intake-start.js';
100
+ import { intakeHandoffCommand } from '../src/commands/intake-handoff.js';
99
101
  import { intakeScanCommand } from '../src/commands/intake-scan.js';
100
102
  import { intakeResolveCommand } from '../src/commands/intake-resolve.js';
101
103
  import { intakeStatusCommand } from '../src/commands/intake-status.js';
@@ -415,6 +417,12 @@ multiCmd
415
417
  .option('-j, --json', 'Output as JSON')
416
418
  .action(multiStepCommand);
417
419
 
420
+ multiCmd
421
+ .command('resume')
422
+ .description('Clear a blocked coordinator state after operator recovery')
423
+ .option('-j, --json', 'Output as JSON')
424
+ .action(multiResumeCommand);
425
+
418
426
  multiCmd
419
427
  .command('approve-gate')
420
428
  .description('Approve a pending phase transition or completion gate')
@@ -449,7 +457,7 @@ intakeCmd
449
457
  intakeCmd
450
458
  .command('triage')
451
459
  .description('Triage a detected intent — set priority, template, charter, and acceptance')
452
- .requiredOption('--intent <id>', 'Intent ID to triage')
460
+ .option('--intent <id>', 'Intent ID to triage')
453
461
  .option('--priority <level>', 'Priority level (p0, p1, p2, p3)')
454
462
  .option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app)')
455
463
  .option('--charter <text>', 'Delivery charter text')
@@ -486,10 +494,19 @@ intakeCmd
486
494
  .option('-j, --json', 'Output as JSON')
487
495
  .action(intakeStartCommand);
488
496
 
497
+ intakeCmd
498
+ .command('handoff')
499
+ .description('Hand off a planned intent to a coordinator workstream')
500
+ .option('--intent <id>', 'Intent ID to hand off')
501
+ .option('--coordinator-root <path>', 'Path to the coordinator workspace root')
502
+ .option('--workstream <id>', 'Coordinator workstream ID')
503
+ .option('-j, --json', 'Output as JSON')
504
+ .action(intakeHandoffCommand);
505
+
489
506
  intakeCmd
490
507
  .command('scan')
491
508
  .description('Scan a structured source snapshot into intake events')
492
- .requiredOption('--source <id>', 'Source type: ci_failure, git_ref_change, schedule')
509
+ .option('--source <id>', 'Source type: ci_failure, git_ref_change, schedule')
493
510
  .option('--file <path>', 'Path to snapshot JSON file')
494
511
  .option('--stdin', 'Read snapshot from stdin')
495
512
  .option('-j, --json', 'Output as JSON')
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.13.0",
3
+ "version": "2.15.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "agentxchain": "./bin/agentxchain.js"
8
8
  },
9
+ "exports": {
10
+ ".": "./bin/agentxchain.js",
11
+ "./runner-interface": "./src/lib/runner-interface.js",
12
+ "./run-loop": "./src/lib/run-loop.js"
13
+ },
9
14
  "files": [
10
15
  "bin/",
11
16
  "src/",
@@ -21,6 +26,7 @@
21
26
  "preflight:release": "bash scripts/release-preflight.sh",
22
27
  "preflight:release:strict": "bash scripts/release-preflight.sh --strict",
23
28
  "postflight:release": "bash scripts/release-postflight.sh",
29
+ "postflight:downstream": "bash scripts/release-downstream-truth.sh",
24
30
  "build:macos": "bun build bin/agentxchain.js --compile --target=bun-darwin-arm64 --outfile=dist/agentxchain-macos-arm64",
25
31
  "build:linux": "bun build bin/agentxchain.js --compile --target=bun-linux-x64 --outfile=dist/agentxchain-linux-x64",
26
32
  "publish:npm": "bash scripts/publish-npm.sh"
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env bash
2
+ # Release downstream truth — run after all downstream surfaces are updated.
3
+ # Verifies: GitHub release exists, Homebrew tap SHA and URL match registry tarball.
4
+ # Usage: bash scripts/release-downstream-truth.sh --target-version <semver>
5
+ set -uo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ CLI_DIR="${SCRIPT_DIR}/.."
9
+ REPO_ROOT="${CLI_DIR}/.."
10
+ cd "$CLI_DIR"
11
+
12
+ TARGET_VERSION=""
13
+ RETRY_ATTEMPTS="${RELEASE_DOWNSTREAM_RETRY_ATTEMPTS:-3}"
14
+ RETRY_DELAY_SECONDS="${RELEASE_DOWNSTREAM_RETRY_DELAY_SECONDS:-5}"
15
+
16
+ usage() {
17
+ echo "Usage: bash scripts/release-downstream-truth.sh --target-version <semver>" >&2
18
+ }
19
+
20
+ while [[ $# -gt 0 ]]; do
21
+ case "$1" in
22
+ --target-version)
23
+ if [[ -z "${2:-}" ]]; then
24
+ echo "Error: --target-version requires a semver argument" >&2
25
+ usage
26
+ exit 1
27
+ fi
28
+ if ! [[ "$2" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
29
+ echo "Invalid semver: $2" >&2
30
+ usage
31
+ exit 1
32
+ fi
33
+ TARGET_VERSION="$2"
34
+ shift 2
35
+ ;;
36
+ *)
37
+ usage
38
+ exit 1
39
+ ;;
40
+ esac
41
+ done
42
+
43
+ if [[ -z "$TARGET_VERSION" ]]; then
44
+ echo "Error: --target-version is required" >&2
45
+ usage
46
+ exit 1
47
+ fi
48
+
49
+ PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).name)")"
50
+ CANONICAL_HOMEBREW_FORMULA_URL="${AGENTXCHAIN_DOWNSTREAM_FORMULA_URL:-https://raw.githubusercontent.com/shivamtiwari93/homebrew-tap/main/Formula/agentxchain.rb}"
51
+
52
+ PASS=0
53
+ FAIL=0
54
+
55
+ pass() { PASS=$((PASS + 1)); echo " PASS: $1"; }
56
+ fail() { FAIL=$((FAIL + 1)); echo " FAIL: $1"; }
57
+
58
+ echo "AgentXchain v${TARGET_VERSION} Downstream Release Truth"
59
+ echo "====================================="
60
+ echo "Checks downstream surfaces after publish: GitHub release, canonical Homebrew tap."
61
+ echo ""
62
+
63
+ # --- Check 1: GitHub Release ---
64
+ echo "[1/3] GitHub release"
65
+ if ! command -v gh >/dev/null 2>&1; then
66
+ fail "gh CLI not available — cannot verify GitHub release"
67
+ else
68
+ GH_FOUND=false
69
+ for attempt in $(seq 1 "$RETRY_ATTEMPTS"); do
70
+ GH_TAG="$(gh release view "v${TARGET_VERSION}" --json tagName -q '.tagName' 2>/dev/null || true)"
71
+ if [[ "$GH_TAG" == "v${TARGET_VERSION}" ]]; then
72
+ GH_FOUND=true
73
+ break
74
+ fi
75
+ if [[ "$attempt" -lt "$RETRY_ATTEMPTS" ]]; then
76
+ echo " INFO: GitHub release not found (attempt ${attempt}/${RETRY_ATTEMPTS}); retrying in ${RETRY_DELAY_SECONDS}s..."
77
+ sleep "$RETRY_DELAY_SECONDS"
78
+ fi
79
+ done
80
+ if $GH_FOUND; then
81
+ pass "GitHub release v${TARGET_VERSION} exists"
82
+ else
83
+ fail "GitHub release v${TARGET_VERSION} not found after ${RETRY_ATTEMPTS} attempts"
84
+ fi
85
+ fi
86
+
87
+ # --- Get registry tarball URL and compute SHA ---
88
+ echo "[2/3] Canonical Homebrew tap SHA matches registry tarball"
89
+ REGISTRY_TARBALL_URL="$(npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.tarball 2>/dev/null || true)"
90
+ FORMULA_CONTENT="$(curl -fsSL "$CANONICAL_HOMEBREW_FORMULA_URL" 2>/dev/null || true)"
91
+ if [[ -z "$REGISTRY_TARBALL_URL" ]]; then
92
+ fail "cannot fetch registry tarball URL for ${PACKAGE_NAME}@${TARGET_VERSION}"
93
+ elif [[ -z "$FORMULA_CONTENT" ]]; then
94
+ fail "cannot fetch canonical Homebrew formula from ${CANONICAL_HOMEBREW_FORMULA_URL}"
95
+ else
96
+ REGISTRY_SHA="$(curl -sL "$REGISTRY_TARBALL_URL" | shasum -a 256 | awk '{print $1}')"
97
+ if [[ -z "$REGISTRY_SHA" ]]; then
98
+ fail "cannot compute SHA256 of registry tarball"
99
+ else
100
+ FORMULA_SHA="$(printf '%s\n' "$FORMULA_CONTENT" | grep -E '^\s*sha256\s+"' | sed 's/.*sha256 *"\([a-f0-9]*\)".*/\1/')"
101
+ if [[ "$REGISTRY_SHA" == "$FORMULA_SHA" ]]; then
102
+ pass "canonical Homebrew formula SHA256 matches registry tarball (${REGISTRY_SHA:0:16}...)"
103
+ else
104
+ fail "canonical Homebrew formula SHA256 mismatch: formula=${FORMULA_SHA:0:16}... registry=${REGISTRY_SHA:0:16}..."
105
+ fi
106
+ fi
107
+ fi
108
+
109
+ # --- Check 3: Homebrew tap URL matches registry tarball URL ---
110
+ echo "[3/3] Canonical Homebrew tap URL matches registry tarball"
111
+ if [[ -z "$REGISTRY_TARBALL_URL" ]]; then
112
+ fail "cannot verify URL — registry tarball URL unavailable"
113
+ elif [[ -z "$FORMULA_CONTENT" ]]; then
114
+ fail "cannot verify URL — canonical Homebrew formula unavailable"
115
+ else
116
+ FORMULA_URL="$(printf '%s\n' "$FORMULA_CONTENT" | grep -E '^\s*url\s+"' | sed 's/.*url *"\([^"]*\)".*/\1/')"
117
+ if [[ "$FORMULA_URL" == "$REGISTRY_TARBALL_URL" ]]; then
118
+ pass "canonical Homebrew formula URL matches registry tarball"
119
+ else
120
+ fail "canonical Homebrew formula URL mismatch: formula=${FORMULA_URL} registry=${REGISTRY_TARBALL_URL}"
121
+ fi
122
+ fi
123
+
124
+ echo ""
125
+ echo "====================================="
126
+ echo "Results: ${PASS} passed, ${FAIL} failed"
127
+ if [ "$FAIL" -gt 0 ]; then
128
+ echo "DOWNSTREAM TRUTH FAILED — at least one downstream surface is inconsistent."
129
+ exit 1
130
+ fi
131
+
132
+ echo "DOWNSTREAM TRUTH PASSED — all downstream surfaces are consistent."
133
+ exit 0
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env bash
2
2
  # Release postflight — run this after publish succeeds.
3
3
  # Verifies: release tag exists, npm registry serves the version, metadata is present,
4
- # and the published package can execute its CLI entrypoint.
4
+ # the published package can execute its CLI entrypoint, and runner package exports
5
+ # are importable in a clean consumer project.
5
6
  # Usage: bash scripts/release-postflight.sh --target-version <semver> [--tag vX.Y.Z]
6
7
  set -uo pipefail
7
8
 
@@ -15,7 +16,7 @@ RETRY_ATTEMPTS="${RELEASE_POSTFLIGHT_RETRY_ATTEMPTS:-12}"
15
16
  RETRY_DELAY_SECONDS="${RELEASE_POSTFLIGHT_RETRY_DELAY_SECONDS:-10}"
16
17
 
17
18
  usage() {
18
- echo "Usage: bash scripts/release-postflight.sh --target-version <semver> [--tag vX.Y.Z]" >&2
19
+ echo "Usage: bash scripts/release-postflight.sh --target-version <semver> [--tag vX.Y.Z]" >&2
19
20
  }
20
21
 
21
22
  while [[ $# -gt 0 ]]; do
@@ -76,6 +77,7 @@ TARBALL_URL=""
76
77
  REGISTRY_CHECKSUM=""
77
78
  PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).name)")"
78
79
  PACKAGE_BIN_NAME="$(node -e "const pkg = JSON.parse(require('fs').readFileSync('package.json', 'utf8')); if (typeof pkg.bin === 'string') { console.log(pkg.name); process.exit(0); } const names = Object.keys(pkg.bin || {}); if (names.length !== 1) { console.error('package.json bin must declare exactly one entry'); process.exit(1); } console.log(names[0]);")"
80
+ RUNNER_INTERFACE_VERSION_EXPECTED="$(node --input-type=module -e "import('./src/lib/runner-interface.js').then((mod) => { console.log(mod.RUNNER_INTERFACE_VERSION); }).catch((error) => { console.error(error.message); process.exit(1); });")"
79
81
 
80
82
  pass() { PASS=$((PASS + 1)); echo " PASS: $1"; }
81
83
  fail() { FAIL=$((FAIL + 1)); echo " FAIL: $1"; }
@@ -136,6 +138,67 @@ run_install_smoke() {
136
138
  return "$version_status"
137
139
  }
138
140
 
141
+ run_runner_export_smoke() {
142
+ if [[ -z "$TARBALL_URL" ]]; then
143
+ echo "registry tarball metadata unavailable for runner export smoke" >&2
144
+ return 1
145
+ fi
146
+
147
+ local smoke_root
148
+ local consumer_root
149
+ local smoke_npmrc
150
+ local install_status
151
+ local node_status
152
+
153
+ smoke_root="$(mktemp -d "${TMPDIR:-/tmp}/agentxchain-runner-postflight.XXXXXX")"
154
+ consumer_root="${smoke_root}/consumer"
155
+ mkdir -p "$consumer_root"
156
+
157
+ smoke_npmrc="${smoke_root}/.npmrc"
158
+ echo "registry=https://registry.npmjs.org/" > "$smoke_npmrc"
159
+
160
+ cat > "${consumer_root}/package.json" <<'EOF'
161
+ {
162
+ "name": "agentxchain-runner-postflight-smoke",
163
+ "private": true,
164
+ "type": "module"
165
+ }
166
+ EOF
167
+
168
+ (
169
+ cd "$consumer_root" || exit 1
170
+ env -u NODE_AUTH_TOKEN NPM_CONFIG_USERCONFIG="$smoke_npmrc" \
171
+ npm install "$TARBALL_URL" >/dev/null 2>&1
172
+ )
173
+ install_status=$?
174
+ if [[ "$install_status" -ne 0 ]]; then
175
+ rm -rf "$smoke_root"
176
+ return "$install_status"
177
+ fi
178
+
179
+ cat > "${consumer_root}/runner-export-smoke.mjs" <<'EOF'
180
+ import { RUNNER_INTERFACE_VERSION, loadContext } from 'agentxchain/runner-interface';
181
+ import { runLoop } from 'agentxchain/run-loop';
182
+
183
+ if (typeof loadContext !== 'function') {
184
+ throw new Error('loadContext export missing');
185
+ }
186
+
187
+ if (typeof runLoop !== 'function') {
188
+ throw new Error('runLoop export missing');
189
+ }
190
+
191
+ console.log(RUNNER_INTERFACE_VERSION);
192
+ EOF
193
+
194
+ local runner_output
195
+ runner_output="$(cd "$consumer_root" && node runner-export-smoke.mjs 2>&1)"
196
+ node_status=$?
197
+ printf '%s\n' "$runner_output"
198
+ rm -rf "$smoke_root"
199
+ return "$node_status"
200
+ }
201
+
139
202
  run_with_retry() {
140
203
  local __output_var="$1"
141
204
  local description="$2"
@@ -189,17 +252,17 @@ run_with_retry() {
189
252
 
190
253
  echo "AgentXchain v${TARGET_VERSION} Release Postflight"
191
254
  echo "====================================="
192
- echo "Checks release truth after publish: tag, registry visibility, metadata, and install smoke."
255
+ echo "Checks release truth after publish: tag, registry visibility, metadata, CLI install smoke, and runner export smoke."
193
256
  echo ""
194
257
 
195
- echo "[1/5] Git tag"
258
+ echo "[1/6] Git tag"
196
259
  if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null 2>&1; then
197
260
  pass "Git tag ${TAG} exists locally"
198
261
  else
199
262
  fail "Git tag ${TAG} is missing locally"
200
263
  fi
201
264
 
202
- echo "[2/5] Registry version"
265
+ echo "[2/6] Registry version"
203
266
  if run_with_retry VERSION_OUTPUT "registry version" equals "${TARGET_VERSION}" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" version; then
204
267
  PUBLISHED_VERSION="$(trim_last_line "$VERSION_OUTPUT")"
205
268
  if [[ "$PUBLISHED_VERSION" == "$TARGET_VERSION" ]]; then
@@ -212,7 +275,7 @@ else
212
275
  printf '%s\n' "$VERSION_OUTPUT" | tail -20
213
276
  fi
214
277
 
215
- echo "[3/5] Registry tarball metadata"
278
+ echo "[3/6] Registry tarball metadata"
216
279
  if run_with_retry TARBALL_OUTPUT "registry tarball metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.tarball; then
217
280
  TARBALL_URL="$(trim_last_line "$TARBALL_OUTPUT")"
218
281
  if [[ -n "$TARBALL_URL" ]]; then
@@ -225,7 +288,7 @@ else
225
288
  printf '%s\n' "$TARBALL_OUTPUT" | tail -20
226
289
  fi
227
290
 
228
- echo "[4/5] Registry checksum metadata"
291
+ echo "[4/6] Registry checksum metadata"
229
292
  if run_with_retry INTEGRITY_OUTPUT "registry checksum metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.integrity; then
230
293
  REGISTRY_CHECKSUM="$(trim_last_line "$INTEGRITY_OUTPUT")"
231
294
  fi
@@ -240,7 +303,7 @@ else
240
303
  fail "registry did not return checksum metadata"
241
304
  fi
242
305
 
243
- echo "[5/5] Install smoke"
306
+ echo "[5/6] Install smoke"
244
307
  if run_with_retry EXEC_OUTPUT "install smoke" nonempty "" run_install_smoke; then
245
308
  EXEC_VERSION="$(trim_last_line "$EXEC_OUTPUT")"
246
309
  if [[ "$EXEC_VERSION" == "$TARGET_VERSION" ]]; then
@@ -253,6 +316,19 @@ else
253
316
  printf '%s\n' "$EXEC_OUTPUT" | tail -20
254
317
  fi
255
318
 
319
+ echo "[6/6] Runner export smoke"
320
+ if run_with_retry RUNNER_EXPORT_OUTPUT "runner export smoke" nonempty "" run_runner_export_smoke; then
321
+ RUNNER_EXPORT_VERSION="$(trim_last_line "$RUNNER_EXPORT_OUTPUT")"
322
+ if [[ "$RUNNER_EXPORT_VERSION" == "$RUNNER_INTERFACE_VERSION_EXPECTED" ]]; then
323
+ pass "published runner exports import with interface ${RUNNER_INTERFACE_VERSION_EXPECTED}"
324
+ else
325
+ fail "published runner exports reported interface '${RUNNER_EXPORT_VERSION}', expected '${RUNNER_INTERFACE_VERSION_EXPECTED}'"
326
+ fi
327
+ else
328
+ fail "published runner exports install smoke failed"
329
+ printf '%s\n' "$RUNNER_EXPORT_OUTPUT" | tail -20
330
+ fi
331
+
256
332
  echo ""
257
333
  echo "====================================="
258
334
  echo "Results: ${PASS} passed, ${FAIL} failed"
@@ -1,17 +1,9 @@
1
1
  import chalk from 'chalk';
2
- import { findProjectRoot } from '../lib/config.js';
3
2
  import { approveIntent } from '../lib/intake.js';
3
+ import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
4
4
 
5
5
  export async function intakeApproveCommand(opts) {
6
- const root = findProjectRoot(process.cwd());
7
- if (!root) {
8
- if (opts.json) {
9
- console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
10
- } else {
11
- console.log(chalk.red('agentxchain.json not found'));
12
- }
13
- process.exit(2);
14
- }
6
+ const root = requireIntakeWorkspaceOrExit(opts);
15
7
 
16
8
  if (!opts.intent) {
17
9
  const msg = '--intent <id> is required';
@@ -0,0 +1,58 @@
1
+ import chalk from 'chalk';
2
+ import { handoffIntent } from '../lib/intake.js';
3
+ import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
4
+
5
+ export async function intakeHandoffCommand(opts) {
6
+ const root = requireIntakeWorkspaceOrExit(opts);
7
+
8
+ if (!opts.intent) {
9
+ const msg = '--intent <id> is required';
10
+ if (opts.json) {
11
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
12
+ } else {
13
+ console.log(chalk.red(msg));
14
+ }
15
+ process.exit(1);
16
+ }
17
+
18
+ if (!opts.coordinatorRoot) {
19
+ const msg = '--coordinator-root <path> is required';
20
+ if (opts.json) {
21
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
22
+ } else {
23
+ console.log(chalk.red(msg));
24
+ }
25
+ process.exit(1);
26
+ }
27
+
28
+ if (!opts.workstream) {
29
+ const msg = '--workstream <id> is required';
30
+ if (opts.json) {
31
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
32
+ } else {
33
+ console.log(chalk.red(msg));
34
+ }
35
+ process.exit(1);
36
+ }
37
+
38
+ const result = handoffIntent(root, opts.intent, {
39
+ coordinatorRoot: opts.coordinatorRoot,
40
+ workstreamId: opts.workstream,
41
+ });
42
+
43
+ if (opts.json) {
44
+ console.log(JSON.stringify(result, null, 2));
45
+ } else if (result.ok) {
46
+ console.log('');
47
+ console.log(chalk.green(` Handed off intent ${result.intent.intent_id}`));
48
+ console.log(` Workstream: ${result.intent.target_workstream.workstream_id}`);
49
+ console.log(` Super Run: ${result.super_run_id}`);
50
+ console.log(` Handoff: ${result.handoff_path}`);
51
+ console.log(chalk.dim(' Status: planned → executing (coordinator-managed)'));
52
+ console.log('');
53
+ } else {
54
+ console.log(chalk.red(` ${result.error}`));
55
+ }
56
+
57
+ process.exit(result.exitCode);
58
+ }
@@ -1,18 +1,9 @@
1
1
  import chalk from 'chalk';
2
- import { findProjectRoot } from '../lib/config.js';
3
2
  import { planIntent } from '../lib/intake.js';
4
- import { basename } from 'node:path';
3
+ import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
5
4
 
6
5
  export async function intakePlanCommand(opts) {
7
- const root = findProjectRoot(process.cwd());
8
- if (!root) {
9
- if (opts.json) {
10
- console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
11
- } else {
12
- console.log(chalk.red('agentxchain.json not found'));
13
- }
14
- process.exit(2);
15
- }
6
+ const root = requireIntakeWorkspaceOrExit(opts);
16
7
 
17
8
  if (!opts.intent) {
18
9
  const msg = '--intent <id> is required';
@@ -1,19 +1,11 @@
1
1
  import chalk from 'chalk';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
- import { findProjectRoot } from '../lib/config.js';
5
4
  import { recordEvent } from '../lib/intake.js';
5
+ import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
6
6
 
7
7
  export async function intakeRecordCommand(opts) {
8
- const root = findProjectRoot(process.cwd());
9
- if (!root) {
10
- if (opts.json) {
11
- console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
12
- } else {
13
- console.log(chalk.red('agentxchain.json not found'));
14
- }
15
- process.exit(2);
16
- }
8
+ const root = requireIntakeWorkspaceOrExit(opts);
17
9
 
18
10
  let payload;
19
11
  try {
@@ -1,17 +1,9 @@
1
1
  import chalk from 'chalk';
2
- import { findProjectRoot } from '../lib/config.js';
3
2
  import { resolveIntent } from '../lib/intake.js';
3
+ import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
4
4
 
5
5
  export async function intakeResolveCommand(opts) {
6
- const root = findProjectRoot(process.cwd());
7
- if (!root) {
8
- if (opts.json) {
9
- console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
10
- } else {
11
- console.log(chalk.red('agentxchain.json not found'));
12
- }
13
- process.exit(2);
14
- }
6
+ const root = requireIntakeWorkspaceOrExit(opts);
15
7
 
16
8
  if (!opts.intent) {
17
9
  const msg = '--intent <id> is required';
@@ -1,19 +1,11 @@
1
1
  import chalk from 'chalk';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
- import { findProjectRoot } from '../lib/config.js';
5
4
  import { scanSource, SCAN_SOURCES } from '../lib/intake.js';
5
+ import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
6
6
 
7
7
  export async function intakeScanCommand(opts) {
8
- const root = findProjectRoot(process.cwd());
9
- if (!root) {
10
- if (opts.json) {
11
- console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
12
- } else {
13
- console.log(chalk.red('agentxchain.json not found'));
14
- }
15
- process.exit(2);
16
- }
8
+ const root = requireIntakeWorkspaceOrExit(opts);
17
9
 
18
10
  if (!opts.source) {
19
11
  const msg = `--source is required. Supported scan sources: ${SCAN_SOURCES.join(', ')}`;
@@ -1,17 +1,9 @@
1
1
  import chalk from 'chalk';
2
- import { findProjectRoot } from '../lib/config.js';
3
2
  import { startIntent } from '../lib/intake.js';
3
+ import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
4
4
 
5
5
  export async function intakeStartCommand(opts) {
6
- const root = findProjectRoot(process.cwd());
7
- if (!root) {
8
- if (opts.json) {
9
- console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
10
- } else {
11
- console.log(chalk.red('agentxchain.json not found'));
12
- }
13
- process.exit(2);
14
- }
6
+ const root = requireIntakeWorkspaceOrExit(opts);
15
7
 
16
8
  if (!opts.intent) {
17
9
  const msg = '--intent <id> is required';
@@ -1,17 +1,9 @@
1
1
  import chalk from 'chalk';
2
- import { findProjectRoot } from '../lib/config.js';
3
2
  import { intakeStatus } from '../lib/intake.js';
3
+ import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
4
4
 
5
5
  export async function intakeStatusCommand(opts) {
6
- const root = findProjectRoot(process.cwd());
7
- if (!root) {
8
- if (opts.json) {
9
- console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
10
- } else {
11
- console.log(chalk.red('agentxchain.json not found'));
12
- }
13
- process.exit(2);
14
- }
6
+ const root = requireIntakeWorkspaceOrExit(opts);
15
7
 
16
8
  const result = intakeStatus(root, opts.intent || null);
17
9
 
@@ -72,6 +64,10 @@ function printIntentDetail(intent, event) {
72
64
  console.log(` ${chalk.dim('Charter:')} ${intent.charter || '—'}`);
73
65
  console.log(` ${chalk.dim('Created:')} ${intent.created_at}`);
74
66
  console.log(` ${chalk.dim('Updated:')} ${intent.updated_at}`);
67
+ if (intent.target_workstream) {
68
+ console.log(` ${chalk.dim('Workstream:')} ${intent.target_workstream.workstream_id}`);
69
+ console.log(` ${chalk.dim('Super Run:')} ${intent.target_workstream.super_run_id}`);
70
+ }
75
71
 
76
72
  if (intent.acceptance_contract && intent.acceptance_contract.length > 0) {
77
73
  console.log('');
@@ -1,17 +1,9 @@
1
1
  import chalk from 'chalk';
2
- import { findProjectRoot } from '../lib/config.js';
3
2
  import { triageIntent } from '../lib/intake.js';
3
+ import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
4
4
 
5
5
  export async function intakeTriageCommand(opts) {
6
- const root = findProjectRoot(process.cwd());
7
- if (!root) {
8
- if (opts.json) {
9
- console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
10
- } else {
11
- console.log(chalk.red('agentxchain.json not found'));
12
- }
13
- process.exit(2);
14
- }
6
+ const root = requireIntakeWorkspaceOrExit(opts);
15
7
 
16
8
  if (!opts.intent) {
17
9
  const msg = '--intent <id> is required';