agentxchain 2.55.0 → 2.57.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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.55.0",
3
+ "version": "2.57.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',
@@ -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,
@@ -974,6 +976,11 @@ async function initGoverned(opts) {
974
976
  console.log(` ${chalk.bold('agentxchain step')} ${chalk.dim('# run the first governed turn')}`);
975
977
  console.log(` ${chalk.bold('agentxchain status')} ${chalk.dim('# inspect phase, gate, and turn state')}`);
976
978
  console.log('');
979
+ if (!config?.project?.goal) {
980
+ console.log(` ${chalk.dim('Tip:')} Add a project goal to guide agent context:`);
981
+ console.log(` ${chalk.bold('agentxchain init --governed --goal "Build a ..."')} ${chalk.dim('# or set project.goal in agentxchain.json')}`);
982
+ console.log('');
983
+ }
977
984
  console.log(` ${chalk.dim('Guide:')} https://agentxchain.dev/docs/getting-started`);
978
985
  console.log('');
979
986
  }
@@ -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'}\``,
@@ -57,6 +57,7 @@ export function buildInheritedContext(root, parentRunId) {
57
57
  parent_phases_completed: parentEntry.phases_completed || [],
58
58
  parent_roles_used: parentEntry.roles_used || [],
59
59
  parent_blocked_reason: parentEntry.blocked_reason || null,
60
+ parent_retrospective: parentEntry.retrospective || null,
60
61
  recent_decisions: recentDecisions,
61
62
  recent_accepted_turns: acceptedTurns,
62
63
  inherited_at: new Date().toISOString(),
@@ -93,6 +94,20 @@ export function renderInheritedContextMarkdown(inheritedContext, compact = false
93
94
  }
94
95
  lines.push('');
95
96
 
97
+ if (!compact && inheritedContext.parent_retrospective) {
98
+ lines.push('### Parent Retrospective');
99
+ lines.push('');
100
+ lines.push(`- **Headline:** ${inheritedContext.parent_retrospective.headline || 'n/a'}`);
101
+ lines.push(`- **Terminal reason:** ${inheritedContext.parent_retrospective.terminal_reason || 'unknown'}`);
102
+ if (inheritedContext.parent_retrospective.next_operator_action) {
103
+ lines.push(`- **Next operator action:** ${inheritedContext.parent_retrospective.next_operator_action}`);
104
+ }
105
+ if (inheritedContext.parent_retrospective.follow_on_hint) {
106
+ lines.push(`- **Follow-on hint:** ${inheritedContext.parent_retrospective.follow_on_hint}`);
107
+ }
108
+ lines.push('');
109
+ }
110
+
96
111
  if (!compact && inheritedContext.recent_decisions?.length) {
97
112
  lines.push('### Recent Decisions');
98
113
  lines.push('');
@@ -153,6 +168,7 @@ function buildPartialContext(parentRunId, parentEntry, decisions, turns, warning
153
168
  parent_phases_completed: parentEntry?.phases_completed || [],
154
169
  parent_roles_used: parentEntry?.roles_used || [],
155
170
  parent_blocked_reason: parentEntry?.blocked_reason || null,
171
+ parent_retrospective: parentEntry?.retrospective || null,
156
172
  recent_decisions: decisions,
157
173
  recent_accepted_turns: turns,
158
174
  inherited_at: new Date().toISOString(),
@@ -10,6 +10,7 @@
10
10
  import { readFileSync, appendFileSync, existsSync, mkdirSync } from 'fs';
11
11
  import { join, dirname } from 'path';
12
12
  import { normalizeRunProvenance } from './run-provenance.js';
13
+ import { deriveRecoveryDescriptor } from './blocked-state.js';
13
14
 
14
15
  const RUN_HISTORY_PATH = '.agentxchain/run-history.jsonl';
15
16
  const HISTORY_PATH = '.agentxchain/history.jsonl';
@@ -83,6 +84,12 @@ export function recordRunHistory(root, state, config, status) {
83
84
  connector_used: connectorUsed,
84
85
  model_used: modelUsed,
85
86
  provenance: normalizeRunProvenance(state?.provenance),
87
+ retrospective: buildRunRetrospective({
88
+ state,
89
+ config,
90
+ status,
91
+ historyEntries,
92
+ }),
86
93
  inheritance_snapshot: {
87
94
  recent_decisions: buildRecentDecisionSnapshot(ledgerEntries),
88
95
  recent_accepted_turns: buildRecentAcceptedTurnSnapshot(historyEntries),
@@ -300,7 +307,7 @@ function buildRecentDecisionSnapshot(entries) {
300
307
 
301
308
  function buildRecentAcceptedTurnSnapshot(entries) {
302
309
  return entries
303
- .filter((entry) => entry.status === 'accepted')
310
+ .filter((entry) => entry?.status !== 'rejected' && typeof entry?.turn_id === 'string' && typeof entry?.role === 'string')
304
311
  .slice(-MAX_INHERITANCE_TURNS)
305
312
  .map((entry) => ({
306
313
  turn_id: entry.turn_id || null,
@@ -309,3 +316,57 @@ function buildRecentAcceptedTurnSnapshot(entries) {
309
316
  phase: entry.phase || null,
310
317
  }));
311
318
  }
319
+
320
+ function buildRunRetrospective({ state, config, status, historyEntries }) {
321
+ const acceptedTurns = historyEntries.filter((entry) => entry && typeof entry === 'object');
322
+ const lastAcceptedTurn = acceptedTurns[acceptedTurns.length - 1] || null;
323
+ const recovery = status === 'blocked'
324
+ ? deriveRecoveryDescriptor(state, config)
325
+ : null;
326
+
327
+ return {
328
+ headline: buildRetrospectiveHeadline({ status, lastAcceptedTurn, recovery, acceptedTurns }),
329
+ terminal_reason: status === 'completed'
330
+ ? 'completed'
331
+ : recovery?.typed_reason || 'blocked',
332
+ next_operator_action: status === 'blocked'
333
+ ? recovery?.recovery_action || null
334
+ : null,
335
+ follow_on_hint: status === 'completed'
336
+ ? buildFollowOnHint(state, lastAcceptedTurn)
337
+ : null,
338
+ };
339
+ }
340
+
341
+ function buildRetrospectiveHeadline({ status, lastAcceptedTurn, recovery, acceptedTurns }) {
342
+ if (status === 'blocked') {
343
+ if (typeof recovery?.detail === 'string' && recovery.detail.trim()) {
344
+ return recovery.detail.trim();
345
+ }
346
+ if (typeof lastAcceptedTurn?.summary === 'string' && lastAcceptedTurn.summary.trim()) {
347
+ return `Run blocked after: ${lastAcceptedTurn.summary.trim()}`;
348
+ }
349
+ return 'Run blocked.';
350
+ }
351
+
352
+ if (typeof lastAcceptedTurn?.summary === 'string' && lastAcceptedTurn.summary.trim()) {
353
+ return lastAcceptedTurn.summary.trim();
354
+ }
355
+
356
+ return `Run completed after ${acceptedTurns.length} accepted turn(s).`;
357
+ }
358
+
359
+ function buildFollowOnHint(state, lastAcceptedTurn) {
360
+ const runId = state?.run_id;
361
+ if (typeof runId !== 'string' || !runId.trim()) {
362
+ return null;
363
+ }
364
+
365
+ const base = `If more scope remains, start a child run with \`agentxchain run --continue-from ${runId} --inherit-context\`.`;
366
+ const suggestedRole = lastAcceptedTurn?.proposed_next_role;
367
+ if (typeof suggestedRole === 'string' && suggestedRole.trim() && suggestedRole !== 'human') {
368
+ return `${base} The last accepted turn suggested \`${suggestedRole}\` next.`;
369
+ }
370
+
371
+ return base;
372
+ }