agentxchain 2.54.0 → 2.56.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.
@@ -128,6 +128,7 @@ program
128
128
  .option('--template <id>', 'Governed scaffold template: generic, api-service, cli-tool, library, web-app, enterprise-app')
129
129
  .option('--dev-command <parts...>', 'Governed local-dev command parts. Include {prompt} for argv prompt delivery.')
130
130
  .option('--dev-prompt-transport <mode>', 'Governed local-dev prompt transport: argv, stdin, dispatch_bundle_only')
131
+ .option('--goal <text>', 'Project goal — persisted in config and rendered in every dispatch bundle')
131
132
  .option('--schema-version <version>', 'Schema version (3 for legacy, or use --governed for current)')
132
133
  .action(initCommand);
133
134
 
@@ -68,6 +68,14 @@ function truncateId(id, len = 12) {
68
68
  return id.length > len ? id.slice(0, len) + '…' : id;
69
69
  }
70
70
 
71
+ function isInheritable(entry) {
72
+ const snap = entry?.inheritance_snapshot;
73
+ if (!snap) return false;
74
+ const hasDecisions = Array.isArray(snap.recent_decisions) && snap.recent_decisions.length > 0;
75
+ const hasTurns = Array.isArray(snap.recent_accepted_turns) && snap.recent_accepted_turns.length > 0;
76
+ return hasDecisions || hasTurns;
77
+ }
78
+
71
79
  function renderRow(entry, index) {
72
80
  const rowClass = entry.status === 'blocked'
73
81
  ? ' style="border-left:3px solid var(--yellow)"'
@@ -83,10 +91,15 @@ function renderRow(entry, index) {
83
91
  ? `<div class="blocked-hint" style="font-size:0.85em;color:var(--yellow);margin-top:2px">${esc(typeof entry.blocked_reason === 'string' ? entry.blocked_reason : entry.blocked_reason?.detail || entry.blocked_reason?.category || '')}</div>`
84
92
  : '';
85
93
 
94
+ const ctxIndicator = isInheritable(entry)
95
+ ? `<span title="Has inheritance snapshot — usable by child runs" style="color:var(--green)">✓</span>`
96
+ : `<span style="color:var(--text-dim)">—</span>`;
97
+
86
98
  return `<tr${rowClass}>
87
99
  <td style="color:var(--text-dim)">${index + 1}</td>
88
100
  <td class="mono" title="${esc(entry.run_id)}">${esc(truncateId(entry.run_id))}</td>
89
101
  <td>${statusBadge(entry.status)}${blockedInfo}</td>
102
+ <td>${ctxIndicator}</td>
90
103
  <td>${phases}</td>
91
104
  <td>${entry.total_turns ?? '—'}</td>
92
105
  <td>${formatCost(entry.total_cost_usd)}</td>
@@ -125,6 +138,7 @@ export function render({ runHistory }) {
125
138
  <th>#</th>
126
139
  <th>Run ID</th>
127
140
  <th>Status</th>
141
+ <th>Ctx</th>
128
142
  <th>Phases</th>
129
143
  <th>Turns</th>
130
144
  <th>Cost</th>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.54.0",
3
+ "version": "2.56.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,7 +19,7 @@ function makeConfig() {
19
19
  return {
20
20
  schema_version: 4,
21
21
  protocol_mode: 'governed',
22
- project: { id: 'agentxchain-demo', name: 'AgentXchain Demo', default_branch: 'main' },
22
+ project: { id: 'agentxchain-demo', name: 'AgentXchain Demo', goal: 'Build an auth token rotation service with expiry, graceful rollover, and audit logging', default_branch: 'main' },
23
23
  roles: {
24
24
  pm: {
25
25
  title: 'Product Manager',
@@ -7,7 +7,7 @@
7
7
  import { resolve } from 'path';
8
8
  import { existsSync, readFileSync } from 'fs';
9
9
  import chalk from 'chalk';
10
- import { queryRunHistory, queryRunLineage } from '../lib/run-history.js';
10
+ import { queryRunHistory, queryRunLineage, isInheritable } from '../lib/run-history.js';
11
11
  import { getRunTriggerLabel, summarizeRunProvenance } from '../lib/run-provenance.js';
12
12
 
13
13
  /**
@@ -46,11 +46,12 @@ export async function historyCommand(opts) {
46
46
  const turns = `${entry.total_turns || 0} turns`;
47
47
  const cost = entry.total_cost_usd != null ? `$${entry.total_cost_usd.toFixed(2)}` : '';
48
48
  const trigger = getRunTriggerLabel(entry.provenance);
49
+ const ctxMarker = isInheritable(entry) ? ' [ctx]' : '';
49
50
  const parentNote = entry.provenance?.parent_run_id
50
51
  ? ` from ${entry.provenance.parent_run_id.slice(0, 12)}`
51
52
  : '';
52
53
  const prefix = i === 0 ? ' ' : ' └─ ';
53
- console.log(`${prefix}${runId} ${status} ${pad(phases, 20)} ${pad(turns, 10)} ${pad(cost, 8)} (${trigger}${parentNote})`);
54
+ console.log(`${prefix}${runId} ${status} ${pad(phases, 20)} ${pad(turns, 10)} ${pad(cost, 8)} (${trigger}${parentNote})${ctxMarker}`);
54
55
  });
55
56
  return;
56
57
  }
@@ -63,7 +64,8 @@ export async function historyCommand(opts) {
63
64
  });
64
65
 
65
66
  if (opts.json) {
66
- console.log(JSON.stringify(entries, null, 2));
67
+ const enriched = entries.map(e => ({ ...e, inheritable: isInheritable(e) }));
68
+ console.log(JSON.stringify(enriched, null, 2));
67
69
  return;
68
70
  }
69
71
 
@@ -81,6 +83,7 @@ export async function historyCommand(opts) {
81
83
  pad('Run ID', 14),
82
84
  pad('Status', 11),
83
85
  pad('Trigger', 14),
86
+ pad('Ctx', 4),
84
87
  pad('Phases', 8),
85
88
  pad('Turns', 6),
86
89
  pad('Cost', 10),
@@ -96,6 +99,7 @@ export async function historyCommand(opts) {
96
99
  const runId = (entry.run_id || '—').slice(0, 12);
97
100
  const status = formatStatus(entry.status);
98
101
  const trigger = getRunTriggerLabel(entry.provenance);
102
+ const ctx = isInheritable(entry) ? '✓' : '—';
99
103
  const phases = String(entry.phases_completed?.length || 0);
100
104
  const turns = String(entry.total_turns || 0);
101
105
  const cost = entry.total_cost_usd != null
@@ -113,6 +117,7 @@ export async function historyCommand(opts) {
113
117
  pad(runId, 14),
114
118
  pad(status, 11),
115
119
  pad(trigger, 14),
120
+ pad(ctx, 4),
116
121
  pad(phases, 8),
117
122
  pad(turns, 6),
118
123
  pad(cost, 10),
@@ -656,12 +656,14 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
656
656
  .filter(Boolean)
657
657
  )].map((gateId) => [gateId, 'pending'])
658
658
  );
659
+ const projectGoal = runtimeOptions.goal;
659
660
  const config = {
660
661
  schema_version: '1.0',
661
662
  template: template.id,
662
663
  project: {
663
664
  id: projectId,
664
665
  name: projectName,
666
+ ...(typeof projectGoal === 'string' && projectGoal.trim() ? { goal: projectGoal.trim() } : {}),
665
667
  default_branch: 'main'
666
668
  },
667
669
  roles,
@@ -89,6 +89,7 @@ function renderGovernedStatus(context, opts) {
89
89
  version,
90
90
  protocol_mode: config.protocol_mode,
91
91
  template: config.template || 'generic',
92
+ project_goal: config.project?.goal || null,
92
93
  config,
93
94
  state,
94
95
  provenance: state?.provenance || null,
@@ -106,6 +107,9 @@ function renderGovernedStatus(context, opts) {
106
107
  console.log('');
107
108
 
108
109
  console.log(` ${chalk.dim('Project:')} ${config.project.name}`);
110
+ if (config.project.goal) {
111
+ console.log(` ${chalk.dim('Goal:')} ${config.project.goal}`);
112
+ }
109
113
  console.log(` ${chalk.dim('Protocol:')} ${chalk.cyan(`governed (v${version})`)}`);
110
114
  console.log(` ${chalk.dim('Template:')} ${config.template || 'generic'}`);
111
115
  console.log(` ${chalk.dim('Phase:')} ${state?.phase ? formatGovernedPhase(state.phase) : chalk.dim('unknown')}`);
@@ -512,6 +512,15 @@ function renderContext(state, config, root, turn, role) {
512
512
  }
513
513
  lines.push('');
514
514
 
515
+ // Project goal (when set in agentxchain.json)
516
+ const projectGoal = config?.project?.goal;
517
+ if (typeof projectGoal === 'string' && projectGoal.trim()) {
518
+ lines.push('## Project Goal');
519
+ lines.push('');
520
+ lines.push(projectGoal.trim());
521
+ lines.push('');
522
+ }
523
+
515
524
  // Inherited context from parent run (when --inherit-context was used)
516
525
  if (state.inherited_context) {
517
526
  // First turn gets the full rendering; subsequent turns get compact
package/src/lib/export.js CHANGED
@@ -295,11 +295,13 @@ export function buildRunExport(startDir = process.cwd()) {
295
295
  project: {
296
296
  id: config.project.id,
297
297
  name: config.project.name,
298
+ goal: config.project.goal || null,
298
299
  template: config.template || 'generic',
299
300
  protocol_mode: config.protocol_mode,
300
301
  schema_version: version,
301
302
  },
302
303
  summary: {
304
+ project_goal: config.project.goal || null,
303
305
  run_id: state?.run_id || null,
304
306
  status: state?.status || null,
305
307
  phase: state?.phase || null,
@@ -340,6 +340,16 @@ export function validateV4Config(data, projectRoot) {
340
340
  } else {
341
341
  if (typeof data.project.id !== 'string' || !data.project.id.trim()) errors.push('project.id must be a non-empty string');
342
342
  if (typeof data.project.name !== 'string' || !data.project.name.trim()) errors.push('project.name must be a non-empty string');
343
+ // Optional project.goal field
344
+ if (data.project.goal !== undefined && data.project.goal !== null) {
345
+ if (typeof data.project.goal !== 'string') {
346
+ errors.push('project.goal must be a string');
347
+ } else if (!data.project.goal.trim()) {
348
+ errors.push('project.goal must be a non-empty string when provided');
349
+ } else if (data.project.goal.trim().length > 500) {
350
+ errors.push('project.goal must be 500 characters or fewer');
351
+ }
352
+ }
343
353
  }
344
354
 
345
355
  // Roles
@@ -968,6 +978,7 @@ export function normalizeV4(raw) {
968
978
  project: {
969
979
  id: raw.project?.id || 'unknown',
970
980
  name: raw.project?.name || 'Unknown',
981
+ ...(typeof raw.project?.goal === 'string' && raw.project.goal.trim() ? { goal: raw.project.goal.trim() } : {}),
971
982
  default_branch: raw.project?.default_branch || 'main',
972
983
  },
973
984
  roles,
package/src/lib/report.js CHANGED
@@ -630,6 +630,7 @@ function buildRunSubject(artifact) {
630
630
  project: {
631
631
  id: artifact.project?.id || null,
632
632
  name: artifact.project?.name || null,
633
+ goal: artifact.project?.goal || null,
633
634
  template: artifact.project?.template || 'generic',
634
635
  protocol_mode: artifact.project?.protocol_mode || null,
635
636
  schema_version: artifact.project?.schema_version || null,
@@ -876,6 +877,7 @@ export function formatGovernanceReportText(report) {
876
877
  `Export kind: ${report.export_kind}`,
877
878
  'Verification: PASS',
878
879
  `Project: ${project.name || 'unknown'} (${project.id || 'unknown'})`,
880
+ ...(project.goal ? [`Goal: ${project.goal}`] : []),
879
881
  `Template: ${project.template}`,
880
882
  `Protocol: ${project.protocol_mode || 'unknown'} (config schema ${project.schema_version || 'unknown'})`,
881
883
  `Run: ${run.run_id || 'none'}`,
@@ -1285,6 +1287,7 @@ export function formatGovernanceReportMarkdown(report) {
1285
1287
  `- Export kind: \`${report.export_kind}\``,
1286
1288
  '- Verification: `pass`',
1287
1289
  `- Project: ${project.name || 'unknown'} (\`${project.id || 'unknown'}\`)`,
1290
+ ...(project.goal ? [`- Goal: ${project.goal}`] : []),
1288
1291
  `- Template: \`${project.template}\``,
1289
1292
  `- Protocol: \`${project.protocol_mode || 'unknown'}\` (config schema \`${project.schema_version || 'unknown'}\`)`,
1290
1293
  `- Run: \`${run.run_id || 'none'}\``,
@@ -249,6 +249,21 @@ export function validateParentRun(root, runId) {
249
249
  return { ok: true, entry };
250
250
  }
251
251
 
252
+ /**
253
+ * Check whether a run-history entry has a usable inheritance snapshot
254
+ * (at least one decision or one accepted turn available for child runs).
255
+ *
256
+ * @param {object} entry - a run-history record
257
+ * @returns {boolean}
258
+ */
259
+ export function isInheritable(entry) {
260
+ const snap = entry?.inheritance_snapshot;
261
+ if (!snap) return false;
262
+ const hasDecisions = Array.isArray(snap.recent_decisions) && snap.recent_decisions.length > 0;
263
+ const hasTurns = Array.isArray(snap.recent_accepted_turns) && snap.recent_accepted_turns.length > 0;
264
+ return hasDecisions || hasTurns;
265
+ }
266
+
252
267
  /**
253
268
  * Get the path to the run-history file.
254
269
  */