agentxchain 2.23.0 → 2.24.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.23.0",
3
+ "version": "2.24.2",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,8 +1,9 @@
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
- # the published package can execute its CLI entrypoint, and runner package exports
5
- # are importable in a clean consumer project.
4
+ # the published package resolves through npx, the published package can execute its
5
+ # CLI entrypoint from the tarball, and runner package exports are importable in a
6
+ # clean consumer project.
6
7
  # Usage: bash scripts/release-postflight.sh --target-version <semver> [--tag vX.Y.Z]
7
8
  set -uo pipefail
8
9
 
@@ -100,6 +101,27 @@ trim_last_line() {
100
101
  printf '%s\n' "$1" | awk 'NF { line=$0 } END { gsub(/^[[:space:]]+|[[:space:]]+$/, "", line); print line }'
101
102
  }
102
103
 
104
+ extract_matching_line() {
105
+ local output="$1"
106
+ local expected="$2"
107
+ printf '%s\n' "$output" | awk -v expected="$expected" '
108
+ {
109
+ line=$0
110
+ gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
111
+ if (line == expected) {
112
+ print line
113
+ found=1
114
+ exit
115
+ }
116
+ }
117
+ END {
118
+ if (!found) {
119
+ exit 1
120
+ }
121
+ }
122
+ '
123
+ }
124
+
103
125
  run_install_smoke() {
104
126
  if [[ -z "$TARBALL_URL" ]]; then
105
127
  echo "registry tarball metadata unavailable for install smoke" >&2
@@ -139,6 +161,36 @@ run_install_smoke() {
139
161
  return "$version_status"
140
162
  }
141
163
 
164
+ run_npx_smoke() {
165
+ local smoke_root
166
+ local smoke_npmrc
167
+ local npx_output
168
+ local npx_status
169
+
170
+ smoke_root="$(mktemp -d "${TMPDIR:-/tmp}/agentxchain-npx-postflight.XXXXXX")"
171
+ mkdir -p "${smoke_root}/home" "${smoke_root}/cache" "${smoke_root}/npm-cache" "${smoke_root}/workspace"
172
+
173
+ smoke_npmrc="${smoke_root}/.npmrc"
174
+ echo "registry=https://registry.npmjs.org/" > "$smoke_npmrc"
175
+
176
+ npx_output="$(
177
+ (
178
+ cd "${smoke_root}/workspace" || exit 1
179
+ env -u NODE_AUTH_TOKEN \
180
+ HOME="${smoke_root}/home" \
181
+ XDG_CACHE_HOME="${smoke_root}/cache" \
182
+ NPM_CONFIG_CACHE="${smoke_root}/npm-cache" \
183
+ NPM_CONFIG_USERCONFIG="$smoke_npmrc" \
184
+ npx --yes -p "${PACKAGE_NAME}@${TARGET_VERSION}" -c "${PACKAGE_BIN_NAME} --version" 2>&1
185
+ )
186
+ )"
187
+ npx_status=$?
188
+
189
+ printf '%s\n' "$npx_output"
190
+ rm -rf "$smoke_root"
191
+ return "$npx_status"
192
+ }
193
+
142
194
  run_runner_export_smoke() {
143
195
  if [[ -z "$TARBALL_URL" ]]; then
144
196
  echo "registry tarball metadata unavailable for runner export smoke" >&2
@@ -261,17 +313,17 @@ run_with_retry() {
261
313
 
262
314
  echo "AgentXchain v${TARGET_VERSION} Release Postflight"
263
315
  echo "====================================="
264
- echo "Checks release truth after publish: tag, registry visibility, metadata, CLI install smoke, and package export smoke."
316
+ echo "Checks release truth after publish: tag, registry visibility, metadata, npx smoke, CLI install smoke, and package export smoke."
265
317
  echo ""
266
318
 
267
- echo "[1/6] Git tag"
319
+ echo "[1/7] Git tag"
268
320
  if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null 2>&1; then
269
321
  pass "Git tag ${TAG} exists locally"
270
322
  else
271
323
  fail "Git tag ${TAG} is missing locally"
272
324
  fi
273
325
 
274
- echo "[2/6] Registry version"
326
+ echo "[2/7] Registry version"
275
327
  if run_with_retry VERSION_OUTPUT "registry version" equals "${TARGET_VERSION}" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" version; then
276
328
  PUBLISHED_VERSION="$(trim_last_line "$VERSION_OUTPUT")"
277
329
  if [[ "$PUBLISHED_VERSION" == "$TARGET_VERSION" ]]; then
@@ -284,7 +336,7 @@ else
284
336
  printf '%s\n' "$VERSION_OUTPUT" | tail -20
285
337
  fi
286
338
 
287
- echo "[3/6] Registry tarball metadata"
339
+ echo "[3/7] Registry tarball metadata"
288
340
  if run_with_retry TARBALL_OUTPUT "registry tarball metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.tarball; then
289
341
  TARBALL_URL="$(trim_last_line "$TARBALL_OUTPUT")"
290
342
  if [[ -n "$TARBALL_URL" ]]; then
@@ -297,7 +349,7 @@ else
297
349
  printf '%s\n' "$TARBALL_OUTPUT" | tail -20
298
350
  fi
299
351
 
300
- echo "[4/6] Registry checksum metadata"
352
+ echo "[4/7] Registry checksum metadata"
301
353
  if run_with_retry INTEGRITY_OUTPUT "registry checksum metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.integrity; then
302
354
  REGISTRY_CHECKSUM="$(trim_last_line "$INTEGRITY_OUTPUT")"
303
355
  fi
@@ -312,7 +364,20 @@ else
312
364
  fail "registry did not return checksum metadata"
313
365
  fi
314
366
 
315
- echo "[5/6] Install smoke"
367
+ echo "[5/7] npx smoke"
368
+ if run_with_retry NPX_OUTPUT "npx smoke" nonempty "" run_npx_smoke; then
369
+ NPX_VERSION="$(extract_matching_line "$NPX_OUTPUT" "$TARGET_VERSION" 2>/dev/null || trim_last_line "$NPX_OUTPUT")"
370
+ if [[ "$NPX_VERSION" == "$TARGET_VERSION" ]]; then
371
+ pass "published npx command resolves and reports ${TARGET_VERSION}"
372
+ else
373
+ fail "published npx command reported '${NPX_VERSION}', expected '${TARGET_VERSION}'"
374
+ fi
375
+ else
376
+ fail "published npx smoke failed"
377
+ printf '%s\n' "$NPX_OUTPUT" | tail -20
378
+ fi
379
+
380
+ echo "[6/7] Install smoke"
316
381
  if run_with_retry EXEC_OUTPUT "install smoke" nonempty "" run_install_smoke; then
317
382
  EXEC_VERSION="$(trim_last_line "$EXEC_OUTPUT")"
318
383
  if [[ "$EXEC_VERSION" == "$TARGET_VERSION" ]]; then
@@ -325,7 +390,7 @@ else
325
390
  printf '%s\n' "$EXEC_OUTPUT" | tail -20
326
391
  fi
327
392
 
328
- echo "[6/6] Package export smoke"
393
+ echo "[7/7] Package export smoke"
329
394
  if run_with_retry RUNNER_EXPORT_OUTPUT "runner export smoke" nonempty "" run_runner_export_smoke; then
330
395
  RUNNER_EXPORT_JSON="$(trim_last_line "$RUNNER_EXPORT_OUTPUT")"
331
396
  RUNNER_EXPORT_VERSION="$(printf '%s' "$RUNNER_EXPORT_JSON" | node --input-type=module -e "process.stdin.setEncoding('utf8'); let raw=''; process.stdin.on('data', (chunk) => raw += chunk); process.stdin.on('end', () => { const parsed = JSON.parse(raw); console.log(parsed.runner_interface_version || ''); });")"
@@ -605,7 +605,7 @@ All acceptance criteria met. OBJ-002 (clock skew) noted for follow-up. OBJ-003 (
605
605
  console.log(chalk.dim(' ─'.repeat(26)));
606
606
  console.log('');
607
607
  console.log(` ${chalk.bold('Try it for real:')} agentxchain init --governed`);
608
- console.log(` ${chalk.bold('Step by step:')} https://agentxchain.dev/docs/first-turn`);
608
+ console.log(` ${chalk.bold('Step by step:')} https://agentxchain.dev/docs/getting-started`);
609
609
  console.log(` ${chalk.bold('Read more:')} https://agentxchain.dev/docs/quickstart`);
610
610
  console.log('');
611
611
  }
@@ -105,6 +105,7 @@ const GOVERNED_RUNTIMES = {
105
105
  'manual-pm': { type: 'manual' },
106
106
  'local-dev': DEFAULT_GOVERNED_LOCAL_DEV_RUNTIME,
107
107
  'api-qa': { type: 'api_proxy', provider: 'anthropic', model: 'claude-sonnet-4-6', auth_env: 'ANTHROPIC_API_KEY' },
108
+ 'manual-qa': { type: 'manual' },
108
109
  'manual-director': { type: 'manual' }
109
110
  };
110
111
 
@@ -684,6 +685,29 @@ async function initGoverned(opts) {
684
685
  console.log(` ${chalk.dim('Dev runtime:')} ${formatGovernedRuntimeCommand(localDevRuntime)} ${chalk.dim(`(${localDevRuntime.prompt_transport})`)}`);
685
686
  console.log(` ${chalk.dim('Protocol:')} governed convergence`);
686
687
  console.log('');
688
+
689
+ // Readiness hint: tell user which roles work immediately vs which need API keys
690
+ const allRuntimes = { ...GOVERNED_RUNTIMES, 'local-dev': localDevRuntime };
691
+ const needsKey = Object.entries(allRuntimes)
692
+ .filter(([, rt]) => rt.auth_env)
693
+ .map(([id, rt]) => ({ id, env: rt.auth_env }));
694
+ if (needsKey.length > 0) {
695
+ const envVars = [...new Set(needsKey.map(r => r.env))];
696
+ const roleNames = needsKey.map(r => r.id);
697
+ const hasKeys = envVars.every(v => process.env[v]);
698
+ if (hasKeys) {
699
+ console.log(` ${chalk.green('Ready:')} all runtimes configured (${envVars.join(', ')} detected)`);
700
+ } else {
701
+ console.log(` ${chalk.yellow('Mixed-mode:')} pm and eng_director work immediately (manual).`);
702
+ console.log(` ${chalk.yellow(' ')}${roleNames.join(', ')} need ${chalk.bold(envVars.join(', '))} to dispatch automatically.`);
703
+ console.log(` ${chalk.yellow(' ')}Without it, those turns fall back to manual input.`);
704
+ if (allRuntimes['manual-qa']) {
705
+ console.log(` ${chalk.yellow(' ')}No-key QA path: change ${chalk.bold('roles.qa.runtime')} from ${chalk.bold('"api-qa"')} to ${chalk.bold('"manual-qa"')} in ${chalk.bold('agentxchain.json')}.`);
706
+ }
707
+ }
708
+ console.log('');
709
+ }
710
+
687
711
  console.log(` ${chalk.cyan('Next:')}`);
688
712
  if (dir !== process.cwd()) {
689
713
  console.log(` ${chalk.bold(`cd ${targetLabel}`)}`);
@@ -691,6 +715,8 @@ async function initGoverned(opts) {
691
715
  console.log(` ${chalk.bold('agentxchain step')} ${chalk.dim('# run the first governed turn')}`);
692
716
  console.log(` ${chalk.bold('agentxchain status')} ${chalk.dim('# inspect phase, gate, and turn state')}`);
693
717
  console.log('');
718
+ console.log(` ${chalk.dim('Guide:')} https://agentxchain.dev/docs/getting-started`);
719
+ console.log('');
694
720
  }
695
721
 
696
722
  export async function initCommand(opts) {
@@ -47,7 +47,7 @@ export async function runCommand(opts) {
47
47
  process.exit(1);
48
48
  }
49
49
 
50
- const { root, config } = context;
50
+ const { root, config, rawConfig } = context;
51
51
 
52
52
  if (config.protocol_mode !== 'governed') {
53
53
  console.log(chalk.red('The run command is only available for governed projects.'));
@@ -112,6 +112,7 @@ export async function runCommand(opts) {
112
112
 
113
113
  // ── Track first-call for --role override ────────────────────────────────
114
114
  let firstSelectRole = true;
115
+ let qaMissingCredentialsFallback = null;
115
116
 
116
117
  // ── Callbacks ───────────────────────────────────────────────────────────
117
118
  const callbacks = {
@@ -134,6 +135,7 @@ export async function runCommand(opts) {
134
135
  const runtime = cfg.runtimes?.[runtimeId];
135
136
  const runtimeType = runtime?.type || role?.runtime_class || 'manual';
136
137
  const hooksConfig = cfg.hooks || {};
138
+ qaMissingCredentialsFallback = null;
137
139
 
138
140
  // Manual adapter is not supported in run mode
139
141
  if (runtimeType === 'manual') {
@@ -219,6 +221,18 @@ export async function runCommand(opts) {
219
221
 
220
222
  // Adapter failure
221
223
  if (!adapterResult.ok) {
224
+ if (shouldPrintManualQaFallback({
225
+ roleId,
226
+ runtimeId,
227
+ classified: adapterResult.classified,
228
+ rawConfig,
229
+ })) {
230
+ qaMissingCredentialsFallback = {
231
+ roleId,
232
+ runtimeId,
233
+ errorClass: adapterResult.classified.error_class,
234
+ };
235
+ }
222
236
  const errorDetail = adapterResult.classified
223
237
  ? `${adapterResult.classified.error_class}: ${adapterResult.classified.recovery}`
224
238
  : adapterResult.error;
@@ -317,6 +331,10 @@ export async function runCommand(opts) {
317
331
  }
318
332
  }
319
333
 
334
+ if (qaMissingCredentialsFallback) {
335
+ printManualQaFallback();
336
+ }
337
+
320
338
  // Recovery guidance for blocked/rejected states
321
339
  if (result.state && (result.stop_reason === 'blocked' || result.stop_reason === 'reject_exhausted' || result.stop_reason === 'dispatch_error')) {
322
340
  const recovery = deriveRecoveryDescriptor(result.state);
@@ -391,3 +409,18 @@ function promptUser(question) {
391
409
  rl.on('close', () => resolve(''));
392
410
  });
393
411
  }
412
+
413
+ function shouldPrintManualQaFallback({ roleId, runtimeId, classified, rawConfig }) {
414
+ return classified?.error_class === 'missing_credentials'
415
+ && roleId === 'qa'
416
+ && runtimeId === 'api-qa'
417
+ && rawConfig?.runtimes?.['manual-qa']?.type === 'manual';
418
+ }
419
+
420
+ function printManualQaFallback() {
421
+ console.log('');
422
+ console.log(chalk.dim(' No-key QA fallback:'));
423
+ console.log(chalk.dim(' - Edit agentxchain.json and change roles.qa.runtime from "api-qa" to "manual-qa"'));
424
+ console.log(chalk.dim(' - Then recover the retained QA turn with: agentxchain step --resume'));
425
+ console.log(chalk.dim(' - Guide: https://agentxchain.dev/docs/getting-started'));
426
+ }
@@ -71,7 +71,7 @@ export async function stepCommand(opts) {
71
71
  process.exit(1);
72
72
  }
73
73
 
74
- const { root, config } = context;
74
+ const { root, config, rawConfig } = context;
75
75
 
76
76
  if (config.protocol_mode !== 'governed') {
77
77
  console.log(chalk.red('The step command is only available for governed projects.'));
@@ -422,6 +422,18 @@ export async function stepCommand(opts) {
422
422
  console.log(chalk.dim(` Retry trace: ${apiResult.retry_trace_path}`));
423
423
  }
424
424
 
425
+ if (
426
+ apiResult.classified?.error_class === 'missing_credentials'
427
+ && roleId === 'qa'
428
+ && config.roles?.qa?.runtime_id === 'api-qa'
429
+ && rawConfig?.runtimes?.['manual-qa']?.type === 'manual'
430
+ ) {
431
+ console.log(chalk.dim(' No-key QA fallback:'));
432
+ console.log(chalk.dim(' - Edit agentxchain.json and change roles.qa.runtime from "api-qa" to "manual-qa"'));
433
+ console.log(chalk.dim(' - Then rerun: agentxchain step --resume'));
434
+ console.log(chalk.dim(' - Guide: https://agentxchain.dev/docs/getting-started'));
435
+ }
436
+
425
437
  console.log(chalk.dim('The turn remains assigned. You can:'));
426
438
  console.log(chalk.dim(' - Fix the issue and retry: agentxchain step --resume'));
427
439
  console.log(chalk.dim(' - Complete manually: edit .agentxchain/staging/turn-result.json'));
@@ -30,31 +30,100 @@ export function printManualDispatchInstructions(state, config, options = {}) {
30
30
  const role = config.roles?.[turn.assigned_role];
31
31
  const promptPath = getDispatchPromptPath(turn.turn_id);
32
32
  const stagingPath = getTurnStagingResultPath(turn.turn_id);
33
+ const phase = state.phase || 'planning';
34
+ const roleId = turn.assigned_role;
33
35
 
34
36
  const lines = [];
35
37
  lines.push('');
36
38
  lines.push(' +---------------------------------------------------------+');
37
39
  lines.push(' | MANUAL TURN REQUIRED |');
38
40
  lines.push(' | |');
39
- lines.push(` | Role: ${pad(turn.assigned_role, 46)}|`);
41
+ lines.push(` | Role: ${pad(roleId, 46)}|`);
40
42
  lines.push(` | Turn: ${pad(turn.turn_id, 46)}|`);
41
- lines.push(` | Phase: ${pad(state.phase, 46)}|`);
43
+ lines.push(` | Phase: ${pad(phase, 46)}|`);
42
44
  lines.push(` | Attempt: ${pad(String(turn.attempt), 46)}|`);
43
45
  lines.push(' | |');
44
46
  lines.push(` | Prompt: ${pad(promptPath, 46)}|`);
45
47
  lines.push(` | Result: ${pad(stagingPath, 46)}|`);
46
- lines.push(' | |');
47
- lines.push(' | 1. Read the prompt at the path above |');
48
- lines.push(' | 2. Complete the work described in the prompt |');
49
- lines.push(' | 3. Write your turn result JSON to the result path |');
50
- lines.push(' | |');
51
- lines.push(' | The step command will detect the file and proceed. |');
52
48
  lines.push(' +---------------------------------------------------------+');
53
49
  lines.push('');
50
+ lines.push(' Steps:');
51
+ lines.push(` 1. Read the prompt: cat ${promptPath}`);
52
+ lines.push(' 2. Do the work described in the prompt');
53
+ lines.push(` 3. Write turn-result.json to: ${stagingPath}`);
54
+ lines.push(' 4. The step command will detect the file and proceed');
55
+ lines.push('');
56
+
57
+ // Phase-aware guidance
58
+ const gateHints = getPhaseGateHints(phase, roleId, config);
59
+ if (gateHints.length > 0) {
60
+ lines.push(' Gate files to update this phase:');
61
+ for (const hint of gateHints) {
62
+ lines.push(` - ${hint}`);
63
+ }
64
+ lines.push('');
65
+ }
66
+
67
+ // Minimal turn-result example
68
+ lines.push(' Minimal turn-result.json:');
69
+ lines.push(' {');
70
+ lines.push(' "schema_version": "1.0",');
71
+ lines.push(` "run_id": "${state.run_id || 'run_...'}",`);
72
+ lines.push(` "turn_id": "${turn.turn_id}",`);
73
+ lines.push(` "role": "${roleId}",`);
74
+ lines.push(` "runtime_id": "${role?.runtime || 'manual'}",`);
75
+ lines.push(' "status": "completed",');
76
+ lines.push(' "summary": "...",');
77
+ lines.push(' "decisions": [{"id":"DEC-001","category":"scope","statement":"...","rationale":"..."}],');
78
+ lines.push(' "objections": [{"id":"OBJ-001","severity":"medium","statement":"...","status":"raised"}],');
79
+ lines.push(' "files_changed": [],');
80
+ lines.push(' "verification": {"status":"skipped","commands":[],"evidence_summary":"..."},');
81
+ lines.push(' "artifact": {"type":"review","ref":null},');
82
+ lines.push(` "proposed_next_role": "${getDefaultNextRole(roleId, config)}",`);
83
+ lines.push(' "phase_transition_request": null,');
84
+ lines.push(' "run_completion_request": null');
85
+ lines.push(' }');
86
+ lines.push('');
87
+ lines.push(' Docs: https://agentxchain.dev/docs/getting-started');
88
+ lines.push('');
54
89
 
55
90
  return lines.join('\n');
56
91
  }
57
92
 
93
+ /**
94
+ * Return gate-file hints relevant to the current phase and role.
95
+ */
96
+ function getPhaseGateHints(phase, roleId, config) {
97
+ const hints = [];
98
+ const gates = config.gates || {};
99
+
100
+ if (phase === 'planning' && (roleId === 'pm' || roleId === 'human')) {
101
+ hints.push('.planning/PM_SIGNOFF.md — change "Approved: NO" → "Approved: YES" when ready');
102
+ hints.push('.planning/ROADMAP.md — define phases and acceptance criteria');
103
+ hints.push('.planning/SYSTEM_SPEC.md — define ## Purpose, ## Interface, ## Acceptance Tests');
104
+ } else if (phase === 'implementation' && (roleId === 'dev' || roleId === 'human')) {
105
+ hints.push('.planning/IMPLEMENTATION_NOTES.md — record what you built and how to verify');
106
+ } else if (phase === 'qa' && (roleId === 'qa' || roleId === 'human')) {
107
+ hints.push('.planning/acceptance-matrix.md — mark each requirement PASS/FAIL');
108
+ hints.push('.planning/ship-verdict.md — change "## Verdict: PENDING" → "## Verdict: SHIP"');
109
+ hints.push('.planning/RELEASE_NOTES.md — user impact, verification summary, upgrade notes');
110
+ }
111
+
112
+ return hints;
113
+ }
114
+
115
+ /**
116
+ * Suggest a reasonable next role based on current role.
117
+ */
118
+ function getDefaultNextRole(roleId, config) {
119
+ const routing = config.routing || {};
120
+ if (routing[roleId]?.default_next) return routing[roleId].default_next;
121
+ if (roleId === 'pm') return 'dev';
122
+ if (roleId === 'dev') return 'qa';
123
+ if (roleId === 'qa') return 'human';
124
+ return 'human';
125
+ }
126
+
58
127
  /**
59
128
  * Wait for the staged turn result file to appear.
60
129
  *