agentxchain 2.62.0 → 2.63.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.
@@ -86,6 +86,8 @@ import {
86
86
  } from '../src/commands/plugin.js';
87
87
  import { templateSetCommand } from '../src/commands/template-set.js';
88
88
  import { templateListCommand } from '../src/commands/template-list.js';
89
+ import { roleCommand } from '../src/commands/role.js';
90
+ import { turnShowCommand } from '../src/commands/turn.js';
89
91
  import { templateValidateCommand } from '../src/commands/template-validate.js';
90
92
  import {
91
93
  multiInitCommand,
@@ -485,6 +487,33 @@ templateCmd
485
487
  .option('-j, --json', 'Output as JSON')
486
488
  .action(templateValidateCommand);
487
489
 
490
+ const roleCmd = program
491
+ .command('role')
492
+ .description('Inspect governed role definitions');
493
+
494
+ roleCmd
495
+ .command('list')
496
+ .description('List all defined roles with title, authority, and runtime')
497
+ .option('-j, --json', 'Output as JSON')
498
+ .action((opts) => roleCommand('list', null, opts));
499
+
500
+ roleCmd
501
+ .command('show <role_id>')
502
+ .description('Show detailed information for a single role')
503
+ .option('-j, --json', 'Output as JSON')
504
+ .action((roleId, opts) => roleCommand('show', roleId, opts));
505
+
506
+ const turnCmd = program
507
+ .command('turn')
508
+ .description('Inspect active governed turn dispatch bundles');
509
+
510
+ turnCmd
511
+ .command('show [turn_id]')
512
+ .description('Show a selected active turn and its dispatch artifacts')
513
+ .option('--artifact <name>', 'Print one artifact: assignment, prompt, context, or manifest')
514
+ .option('-j, --json', 'Output as JSON')
515
+ .action(turnShowCommand);
516
+
488
517
  const multiCmd = program
489
518
  .command('multi')
490
519
  .description('Multi-repo coordinator orchestration');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.62.0",
3
+ "version": "2.63.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -198,21 +198,53 @@ echo " OK: all 9 governed version surfaces reference ${TARGET_VERSION}"
198
198
 
199
199
  # 5. Auto-align Homebrew mirror to target version
200
200
  # The formula URL and README version/tarball are updated automatically.
201
- # The SHA256 is carried from the previous version — it is inherently a
201
+ # The SHA256 is carried from the previous committed formula — it is inherently a
202
202
  # post-publish artifact (npm registry tarballs are not byte-identical to
203
- # local npm-pack output). sync-homebrew.sh corrects the SHA after publish.
203
+ # local npm-pack output). Any working-tree SHA edit is overwritten here.
204
+ # sync-homebrew.sh corrects the SHA after publish.
204
205
  echo "[5/9] Auto-aligning Homebrew mirror to ${TARGET_VERSION}..."
205
206
  HOMEBREW_MIRROR="${REPO_ROOT}/cli/homebrew/agentxchain.rb"
206
207
  HOMEBREW_MIRROR_README="${REPO_ROOT}/cli/homebrew/README.md"
207
208
  TARBALL_URL="https://registry.npmjs.org/agentxchain/-/agentxchain-${TARGET_VERSION}.tgz"
208
209
  HOMEBREW_ALIGNED=false
210
+ COMMITTED_HOMEBREW_SHA=""
211
+
212
+ extract_formula_sha() {
213
+ sed -nE 's|^[[:space:]]*sha256 "([a-f0-9]{64})".*|\1|p' "$1" | head -n 1
214
+ }
209
215
 
210
216
  if [[ -f "$HOMEBREW_MIRROR" ]]; then
217
+ COMMITTED_FORMULA_TMP="$(mktemp "${TMPDIR:-/tmp}/agentxchain-homebrew-head.XXXXXX")"
218
+ if ! git -C "$REPO_ROOT" show "HEAD:cli/homebrew/agentxchain.rb" >"$COMMITTED_FORMULA_TMP" 2>/dev/null; then
219
+ rm -f "$COMMITTED_FORMULA_TMP"
220
+ echo "FAIL: could not load HEAD:cli/homebrew/agentxchain.rb to carry the pre-publish SHA" >&2
221
+ exit 1
222
+ fi
223
+ COMMITTED_HOMEBREW_SHA="$(extract_formula_sha "$COMMITTED_FORMULA_TMP")"
224
+ rm -f "$COMMITTED_FORMULA_TMP"
225
+ if [[ -z "$COMMITTED_HOMEBREW_SHA" ]]; then
226
+ echo "FAIL: HEAD:cli/homebrew/agentxchain.rb does not contain a parseable sha256" >&2
227
+ exit 1
228
+ fi
229
+
230
+ WORKTREE_HOMEBREW_SHA="$(extract_formula_sha "$HOMEBREW_MIRROR")"
231
+ if [[ -z "$WORKTREE_HOMEBREW_SHA" ]]; then
232
+ echo "FAIL: cli/homebrew/agentxchain.rb does not contain a parseable sha256" >&2
233
+ exit 1
234
+ fi
235
+
211
236
  ESCAPED_URL="$(printf '%s' "$TARBALL_URL" | sed 's/[&/\]/\\&/g')"
237
+ ESCAPED_SHA="$(printf '%s' "$COMMITTED_HOMEBREW_SHA" | sed 's/[&/\]/\\&/g')"
212
238
  sed -i.bak -E "s|^([[:space:]]*url \").*(\")|\1${ESCAPED_URL}\2|" "$HOMEBREW_MIRROR"
239
+ sed -i.bak -E "s|^([[:space:]]*sha256 \").*(\")|\1${ESCAPED_SHA}\2|" "$HOMEBREW_MIRROR"
213
240
  rm -f "${HOMEBREW_MIRROR}.bak"
214
241
  HOMEBREW_ALIGNED=true
215
242
  echo " OK: formula URL -> ${TARBALL_URL}"
243
+ if [[ "$WORKTREE_HOMEBREW_SHA" != "$COMMITTED_HOMEBREW_SHA" ]]; then
244
+ echo " OK: formula SHA normalized back to committed pre-publish SHA ${COMMITTED_HOMEBREW_SHA}"
245
+ else
246
+ echo " OK: formula SHA carried from committed pre-publish SHA ${COMMITTED_HOMEBREW_SHA}"
247
+ fi
216
248
  fi
217
249
 
218
250
  if [[ -f "$HOMEBREW_MIRROR_README" ]]; then
@@ -223,7 +255,7 @@ if [[ -f "$HOMEBREW_MIRROR_README" ]]; then
223
255
  fi
224
256
 
225
257
  if $HOMEBREW_ALIGNED; then
226
- echo " Note: SHA carried from previous version; sync-homebrew.sh will set the real registry SHA post-publish"
258
+ echo " Note: local npm pack output is not canonical release truth; sync-homebrew.sh will set the real registry SHA post-publish"
227
259
  else
228
260
  echo " Skipped: no Homebrew mirror files found"
229
261
  fi
@@ -346,4 +378,7 @@ if [[ "$SKIP_PREFLIGHT" -eq 1 ]]; then
346
378
  echo " npm run preflight:release:strict -- --target-version ${TARGET_VERSION}"
347
379
  fi
348
380
  echo ""
381
+ echo "Homebrew mirror is in Phase 1 (stale SHA from previous version)."
382
+ echo "After npm publish completes, run sync-homebrew.sh to reach Phase 3."
383
+ echo ""
349
384
  echo "Next: git push origin main --follow-tags"
@@ -0,0 +1,106 @@
1
+ import chalk from 'chalk';
2
+ import { loadProjectContext } from '../lib/config.js';
3
+
4
+ export function roleCommand(subcommand, roleId, opts) {
5
+ const context = loadProjectContext();
6
+ if (!context) {
7
+ console.log(chalk.red(' No agentxchain.json found. Run `agentxchain init` first.'));
8
+ process.exit(1);
9
+ }
10
+
11
+ const { rawConfig, version } = context;
12
+
13
+ if (version !== 4) {
14
+ console.log(chalk.red(' Not a governed AgentXchain project (requires v4 config).'));
15
+ process.exit(1);
16
+ }
17
+
18
+ const roles = rawConfig.roles || {};
19
+ const roleIds = Object.keys(roles);
20
+
21
+ if (subcommand === 'show') {
22
+ return showRole(roleId, roles, roleIds, opts);
23
+ }
24
+
25
+ // Default: list
26
+ return listRoles(roles, roleIds, opts);
27
+ }
28
+
29
+ function listRoles(roles, roleIds, opts) {
30
+ if (roleIds.length === 0) {
31
+ if (opts.json) {
32
+ console.log('[]');
33
+ } else {
34
+ console.log(' No roles defined.');
35
+ }
36
+ return;
37
+ }
38
+
39
+ if (opts.json) {
40
+ const output = roleIds.map((id) => ({
41
+ id,
42
+ title: roles[id].title,
43
+ mandate: roles[id].mandate,
44
+ write_authority: roles[id].write_authority,
45
+ runtime: roles[id].runtime,
46
+ }));
47
+ console.log(JSON.stringify(output, null, 2));
48
+ return;
49
+ }
50
+
51
+ console.log(chalk.bold(`\n Roles (${roleIds.length}):\n`));
52
+ for (const id of roleIds) {
53
+ const r = roles[id];
54
+ const authority = r.write_authority === 'authoritative'
55
+ ? chalk.green(r.write_authority)
56
+ : r.write_authority === 'proposed'
57
+ ? chalk.yellow(r.write_authority)
58
+ : chalk.dim(r.write_authority);
59
+ console.log(` ${chalk.cyan(id)} — ${r.title} [${authority}] → ${chalk.dim(r.runtime)}`);
60
+ }
61
+ console.log('');
62
+ console.log(chalk.dim(' Usage: agentxchain role show <role_id>\n'));
63
+ }
64
+
65
+ function showRole(roleId, roles, roleIds, opts) {
66
+ if (!roleId) {
67
+ console.log(chalk.red(' Missing role ID.'));
68
+ console.log(chalk.dim(` Usage: agentxchain role show <role_id>`));
69
+ if (roleIds.length > 0) {
70
+ console.log(chalk.dim(` Available: ${roleIds.join(', ')}`));
71
+ }
72
+ process.exit(1);
73
+ }
74
+
75
+ if (!roles[roleId]) {
76
+ console.log(chalk.red(` Unknown role: ${roleId}`));
77
+ console.log(chalk.dim(` Available: ${roleIds.join(', ')}`));
78
+ process.exit(1);
79
+ }
80
+
81
+ const r = roles[roleId];
82
+
83
+ if (opts.json) {
84
+ console.log(JSON.stringify({
85
+ id: roleId,
86
+ title: r.title,
87
+ mandate: r.mandate,
88
+ write_authority: r.write_authority,
89
+ runtime: r.runtime,
90
+ }, null, 2));
91
+ return;
92
+ }
93
+
94
+ const authority = r.write_authority === 'authoritative'
95
+ ? chalk.green(r.write_authority)
96
+ : r.write_authority === 'proposed'
97
+ ? chalk.yellow(r.write_authority)
98
+ : chalk.dim(r.write_authority);
99
+
100
+ console.log(chalk.bold(`\n Role: ${chalk.cyan(roleId)}\n`));
101
+ console.log(` Title: ${r.title}`);
102
+ console.log(` Mandate: ${r.mandate}`);
103
+ console.log(` Authority: ${authority}`);
104
+ console.log(` Runtime: ${chalk.dim(r.runtime)}`);
105
+ console.log('');
106
+ }
@@ -0,0 +1,210 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import chalk from 'chalk';
4
+ import { loadProjectContext, loadProjectState } from '../lib/config.js';
5
+ import { getActiveTurnCount, getActiveTurns } from '../lib/governed-state.js';
6
+ import {
7
+ getDispatchAssignmentPath,
8
+ getDispatchContextPath,
9
+ getDispatchManifestPath,
10
+ getDispatchPromptPath,
11
+ getDispatchTurnDir,
12
+ } from '../lib/turn-paths.js';
13
+
14
+ const ARTIFACTS = {
15
+ assignment: {
16
+ label: 'Assignment',
17
+ pathFor: getDispatchAssignmentPath,
18
+ parse: 'json',
19
+ },
20
+ prompt: {
21
+ label: 'Prompt',
22
+ pathFor: getDispatchPromptPath,
23
+ parse: 'text',
24
+ },
25
+ context: {
26
+ label: 'Context',
27
+ pathFor: getDispatchContextPath,
28
+ parse: 'text',
29
+ },
30
+ manifest: {
31
+ label: 'Manifest',
32
+ pathFor: getDispatchManifestPath,
33
+ parse: 'json',
34
+ },
35
+ };
36
+
37
+ export function turnShowCommand(turnId, opts) {
38
+ const context = requireGovernedContext();
39
+ const state = loadProjectState(context.root, context.config);
40
+
41
+ if (!state) {
42
+ console.log(chalk.red(' Governed state is missing or invalid.'));
43
+ process.exit(1);
44
+ }
45
+
46
+ const activeTurns = getActiveTurns(state);
47
+ const selectedTurnId = resolveTurnId(turnId, activeTurns);
48
+ const turn = activeTurns[selectedTurnId];
49
+ const artifacts = buildArtifactIndex(context.root, selectedTurnId);
50
+ const assignment = readJsonArtifact(artifacts.assignment.absPath);
51
+
52
+ if (opts.artifact) {
53
+ return printArtifact(selectedTurnId, turn, artifacts, opts.artifact, opts.json);
54
+ }
55
+
56
+ if (opts.json) {
57
+ console.log(JSON.stringify(buildTurnPayload(selectedTurnId, turn, state, artifacts, assignment), null, 2));
58
+ return;
59
+ }
60
+
61
+ printTurnSummary(selectedTurnId, turn, state, artifacts, assignment);
62
+ }
63
+
64
+ function requireGovernedContext() {
65
+ const context = loadProjectContext();
66
+ if (!context) {
67
+ console.log(chalk.red(' No agentxchain.json found. Run `agentxchain init` first.'));
68
+ process.exit(1);
69
+ }
70
+ if (context.config.protocol_mode !== 'governed') {
71
+ console.log(chalk.red(' The turn command is only available for governed projects.'));
72
+ process.exit(1);
73
+ }
74
+ return context;
75
+ }
76
+
77
+ function resolveTurnId(requestedTurnId, activeTurns) {
78
+ const turnIds = Object.keys(activeTurns);
79
+ if (requestedTurnId) {
80
+ if (!activeTurns[requestedTurnId]) {
81
+ console.log(chalk.red(` Unknown active turn: ${requestedTurnId}`));
82
+ if (turnIds.length > 0) {
83
+ console.log(chalk.dim(` Available: ${turnIds.join(', ')}`));
84
+ }
85
+ process.exit(1);
86
+ }
87
+ return requestedTurnId;
88
+ }
89
+
90
+ if (turnIds.length === 0) {
91
+ console.log(chalk.red(' No active turn found.'));
92
+ process.exit(1);
93
+ }
94
+
95
+ if (turnIds.length > 1) {
96
+ console.log(chalk.red(' Multiple active turns are present. Re-run with `agentxchain turn show <turn_id>`.'));
97
+ console.log(chalk.dim(` Available: ${turnIds.join(', ')}`));
98
+ process.exit(1);
99
+ }
100
+
101
+ return turnIds[0];
102
+ }
103
+
104
+ function buildArtifactIndex(root, turnId) {
105
+ return Object.fromEntries(
106
+ Object.entries(ARTIFACTS).map(([id, artifact]) => {
107
+ const relPath = artifact.pathFor(turnId);
108
+ const absPath = join(root, relPath);
109
+ return [id, {
110
+ id,
111
+ label: artifact.label,
112
+ relPath,
113
+ absPath,
114
+ exists: existsSync(absPath),
115
+ parse: artifact.parse,
116
+ }];
117
+ }),
118
+ );
119
+ }
120
+
121
+ function buildTurnPayload(turnId, turn, state, artifacts, assignment) {
122
+ return {
123
+ turn_id: turnId,
124
+ run_id: state.run_id || assignment?.run_id || null,
125
+ phase: state.phase || assignment?.phase || null,
126
+ role: turn.assigned_role,
127
+ runtime: turn.runtime_id,
128
+ status: turn.status,
129
+ attempt: turn.attempt,
130
+ dispatch_dir: getDispatchTurnDir(turnId),
131
+ staging_result_path: assignment?.staging_result_path || null,
132
+ active_turn_count: getActiveTurnCount(state),
133
+ artifacts: Object.fromEntries(
134
+ Object.entries(artifacts).map(([id, artifact]) => [id, {
135
+ path: artifact.relPath,
136
+ exists: artifact.exists,
137
+ }]),
138
+ ),
139
+ };
140
+ }
141
+
142
+ function printTurnSummary(turnId, turn, state, artifacts, assignment) {
143
+ console.log('');
144
+ console.log(chalk.bold(` Turn: ${chalk.cyan(turnId)}`));
145
+ console.log(chalk.dim(' ' + '─'.repeat(44)));
146
+ console.log(` ${chalk.dim('Run:')} ${state.run_id || assignment?.run_id || chalk.dim('unknown')}`);
147
+ console.log(` ${chalk.dim('Phase:')} ${state.phase || assignment?.phase || chalk.dim('unknown')}`);
148
+ console.log(` ${chalk.dim('Role:')} ${chalk.bold(turn.assigned_role)}`);
149
+ console.log(` ${chalk.dim('Runtime:')} ${turn.runtime_id}`);
150
+ console.log(` ${chalk.dim('Status:')} ${turn.status}`);
151
+ console.log(` ${chalk.dim('Attempt:')} ${turn.attempt}`);
152
+ console.log(` ${chalk.dim('Dispatch:')} ${getDispatchTurnDir(turnId)}`);
153
+ if (assignment?.staging_result_path) {
154
+ console.log(` ${chalk.dim('Staging:')} ${assignment.staging_result_path}`);
155
+ }
156
+ console.log('');
157
+ console.log(` ${chalk.dim('Artifacts:')}`);
158
+ for (const artifact of Object.values(artifacts)) {
159
+ const marker = artifact.exists ? chalk.green('exists') : chalk.red('missing');
160
+ console.log(` ${artifact.label.padEnd(10)} ${marker} ${artifact.relPath}`);
161
+ }
162
+ console.log('');
163
+ console.log(chalk.dim(` View one: agentxchain turn show ${turnId} --artifact prompt`));
164
+ console.log('');
165
+ }
166
+
167
+ function printArtifact(turnId, turn, artifacts, artifactId, jsonMode) {
168
+ const artifact = artifacts[artifactId];
169
+ if (!artifact) {
170
+ console.log(chalk.red(` Unknown artifact: ${artifactId}`));
171
+ console.log(chalk.dim(` Allowed: ${Object.keys(ARTIFACTS).join(', ')}`));
172
+ process.exit(1);
173
+ }
174
+ if (!artifact.exists) {
175
+ console.log(chalk.red(` Missing artifact: ${artifact.relPath}`));
176
+ process.exit(1);
177
+ }
178
+
179
+ const raw = readFileSync(artifact.absPath, 'utf8');
180
+ if (!jsonMode) {
181
+ process.stdout.write(raw.endsWith('\n') ? raw : `${raw}\n`);
182
+ return;
183
+ }
184
+
185
+ const content = artifact.parse === 'json'
186
+ ? JSON.parse(raw)
187
+ : raw;
188
+
189
+ console.log(JSON.stringify({
190
+ turn_id: turnId,
191
+ role: turn.assigned_role,
192
+ runtime: turn.runtime_id,
193
+ artifact: {
194
+ id: artifact.id,
195
+ path: artifact.relPath,
196
+ content,
197
+ },
198
+ }, null, 2));
199
+ }
200
+
201
+ function readJsonArtifact(absPath) {
202
+ if (!existsSync(absPath)) {
203
+ return null;
204
+ }
205
+ try {
206
+ return JSON.parse(readFileSync(absPath, 'utf8'));
207
+ } catch {
208
+ return null;
209
+ }
210
+ }