agentxchain 2.29.0 → 2.31.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.29.0",
3
+ "version": "2.31.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,6 +10,7 @@ const SECTION_DEFINITIONS = [
10
10
  { id: 'last_turn_verification', header: null, required: false },
11
11
  { id: 'blockers', header: 'Blockers', required: true },
12
12
  { id: 'escalation', header: 'Escalation', required: true },
13
+ { id: 'workflow_artifacts', header: 'Workflow Artifacts', required: false },
13
14
  { id: 'gate_required_files', header: 'Gate Required Files', required: false },
14
15
  { id: 'phase_gate_status', header: 'Phase Gate Status', required: false },
15
16
  ];
@@ -88,6 +89,7 @@ export function renderContextSections(sections) {
88
89
 
89
90
  appendTopLevelSection(lines, 'Blockers', [sectionMap.get('blockers')?.content]);
90
91
  appendTopLevelSection(lines, 'Escalation', [sectionMap.get('escalation')?.content]);
92
+ appendTopLevelSection(lines, 'Workflow Artifacts', [sectionMap.get('workflow_artifacts')?.content]);
91
93
  appendTopLevelSection(lines, 'Gate Required Files', [sectionMap.get('gate_required_files')?.content]);
92
94
  appendTopLevelSection(lines, 'Phase Gate Status', [sectionMap.get('phase_gate_status')?.content]);
93
95
 
@@ -248,6 +248,31 @@ function renderPrompt(role, roleId, turn, state, config, root) {
248
248
  lines.push('');
249
249
  }
250
250
 
251
+ const workflowResponsibilities = getWorkflowPromptResponsibilities(config, phase, roleId, root);
252
+ if (workflowResponsibilities.length > 0) {
253
+ const isReviewOnlyOwner = role.write_authority === 'review_only';
254
+ lines.push('## Workflow-Kit Responsibilities');
255
+ lines.push('');
256
+ if (isReviewOnlyOwner) {
257
+ lines.push(`You are accountable for reviewing and attesting to these workflow-kit artifacts in phase \`${phase}\`:`);
258
+ } else {
259
+ lines.push(`You are accountable for producing these workflow-kit artifacts in phase \`${phase}\`:`);
260
+ }
261
+ lines.push('');
262
+ for (const artifact of workflowResponsibilities) {
263
+ const requiredLabel = artifact.required ? 'required' : 'optional';
264
+ const semanticsLabel = artifact.semantics ? `\`${artifact.semantics}\`` : '—';
265
+ lines.push(`- \`${artifact.path}\` — ${requiredLabel}; semantics: ${semanticsLabel}; status: ${artifact.status}`);
266
+ }
267
+ lines.push('');
268
+ if (isReviewOnlyOwner) {
269
+ lines.push('You cannot write repo files directly. Your accountability means you must confirm these artifacts exist, meet quality standards, and satisfy their semantic requirements. If a required artifact you own is missing, escalate to the producing role — do not request phase transition.');
270
+ } else {
271
+ lines.push('Do not request phase transition or run completion while a required workflow-kit artifact you own is missing or incomplete.');
272
+ }
273
+ lines.push('');
274
+ }
275
+
251
276
  // Gate requirements
252
277
  if (gateConfig) {
253
278
  lines.push('## Phase Exit Gate');
@@ -421,6 +446,47 @@ function renderPrompt(role, roleId, turn, state, config, root) {
421
446
  };
422
447
  }
423
448
 
449
+ function getWorkflowPromptResponsibilities(config, phase, roleId, root) {
450
+ const artifacts = config?.workflow_kit?.phases?.[phase]?.artifacts;
451
+ if (!Array.isArray(artifacts) || artifacts.length === 0) {
452
+ return [];
453
+ }
454
+
455
+ const entryRole = config?.routing?.[phase]?.entry_role || null;
456
+ const responsibilities = [];
457
+
458
+ for (const artifact of artifacts) {
459
+ if (!artifact?.path) {
460
+ continue;
461
+ }
462
+
463
+ const owner = typeof artifact.owned_by === 'string' && artifact.owned_by
464
+ ? artifact.owned_by
465
+ : null;
466
+ const responsibleRole = owner || entryRole;
467
+ if (responsibleRole !== roleId) {
468
+ continue;
469
+ }
470
+
471
+ const absPath = join(root, artifact.path);
472
+ let exists = false;
473
+ try {
474
+ exists = existsSync(absPath);
475
+ } catch {
476
+ exists = false;
477
+ }
478
+
479
+ responsibilities.push({
480
+ path: artifact.path,
481
+ required: artifact.required !== false,
482
+ semantics: artifact.semantics || null,
483
+ status: exists ? 'exists' : 'MISSING',
484
+ });
485
+ }
486
+
487
+ return responsibilities;
488
+ }
489
+
424
490
  // ── Context Rendering ───────────────────────────────────────────────────────
425
491
 
426
492
  function renderContext(state, config, root, turn, role) {
@@ -621,8 +687,59 @@ function renderContext(state, config, root, turn, role) {
621
687
  lines.push('');
622
688
  }
623
689
 
624
- // Phase gate requirements
690
+ // Workflow-kit artifacts for the current phase
625
691
  const phase = state.phase;
692
+ const wkArtifacts = config?.workflow_kit?.phases?.[phase]?.artifacts;
693
+ if (Array.isArray(wkArtifacts) && wkArtifacts.length > 0) {
694
+ lines.push('## Workflow Artifacts');
695
+ lines.push('');
696
+ lines.push(`Current phase **${phase}** declares the following artifacts:`);
697
+ lines.push('');
698
+ lines.push('| Artifact | Required | Semantics | Owner | Status |');
699
+ lines.push('|----------|----------|-----------|-------|--------|');
700
+ const isReviewRole = role?.write_authority === 'review_only';
701
+ const reviewPreviews = [];
702
+ for (const art of wkArtifacts) {
703
+ if (!art?.path) continue;
704
+ const absPath = join(root, art.path);
705
+ let exists = false;
706
+ try { exists = existsSync(absPath); } catch { /* treat as missing */ }
707
+ const req = art.required !== false ? 'yes' : 'no';
708
+ const owner = art.owned_by || '—';
709
+ const status = exists ? 'exists' : 'MISSING';
710
+ const semCol = art.semantics ? `\`${art.semantics}\`` : '—';
711
+ lines.push(`| \`${art.path}\` | ${req} | ${semCol} | ${owner} | ${status} |`);
712
+ if (isReviewRole && exists) {
713
+ reviewPreviews.push(art);
714
+ }
715
+ }
716
+ lines.push('');
717
+ if (reviewPreviews.length > 0) {
718
+ for (const art of reviewPreviews) {
719
+ const absPath = join(root, art.path);
720
+ const preview = buildGateFilePreview(absPath);
721
+ if (preview) {
722
+ lines.push(`### \`${art.path}\``);
723
+ lines.push('');
724
+ const semantic = extractGateFileSemantic(art.path, preview.raw);
725
+ if (semantic) {
726
+ lines.push(`**Semantic: ${semantic}**`);
727
+ lines.push('');
728
+ }
729
+ lines.push('```');
730
+ lines.push(preview.content);
731
+ lines.push('```');
732
+ if (preview.truncated) {
733
+ lines.push('');
734
+ lines.push(`_Preview truncated after ${GATE_FILE_PREVIEW_MAX_LINES} lines._`);
735
+ }
736
+ lines.push('');
737
+ }
738
+ }
739
+ }
740
+ }
741
+
742
+ // Phase gate requirements
626
743
  const routing = config.routing?.[phase];
627
744
  const exitGate = routing?.exit_gate;
628
745
  const gateConfig = exitGate ? config.gates?.[exitGate] : null;
package/src/lib/export.js CHANGED
@@ -31,6 +31,7 @@ const INCLUDED_ROOTS = [
31
31
  '.agentxchain/transactions/accept',
32
32
  '.agentxchain/intake',
33
33
  '.agentxchain/multirepo',
34
+ '.planning',
34
35
  ];
35
36
 
36
37
  function sha256(buffer) {
@@ -628,6 +628,25 @@ export function validateWorkflowKitConfig(wk, routing, roles) {
628
628
  errors.push(`${prefix} owned_by "${artifact.owned_by}" is not a valid role ID (must be lowercase alphanumeric with hyphens/underscores)`);
629
629
  } else if (roles && typeof roles === 'object' && !roles[artifact.owned_by]) {
630
630
  errors.push(`${prefix} owned_by "${artifact.owned_by}" does not reference a defined role`);
631
+ } else if (
632
+ artifact.required !== false &&
633
+ roles && typeof roles === 'object' &&
634
+ roles[artifact.owned_by]?.write_authority === 'review_only'
635
+ ) {
636
+ // Check if any authoritative/proposed role exists in this phase's routing
637
+ const phaseRouting = routing?.[phase];
638
+ const phaseRoles = new Set([
639
+ ...(phaseRouting?.allowed_next_roles || []),
640
+ ...(phaseRouting?.entry_role ? [phaseRouting.entry_role] : []),
641
+ ]);
642
+ const hasWriter = [...phaseRoles].some(rid =>
643
+ roles[rid]?.write_authority === 'authoritative' || roles[rid]?.write_authority === 'proposed',
644
+ );
645
+ if (!hasWriter) {
646
+ warnings.push(
647
+ `${prefix} owned_by "${artifact.owned_by}" is a review_only role in phase "${phase}" with no authoritative or proposed role — nobody can write this required artifact`,
648
+ );
649
+ }
631
650
  }
632
651
  }
633
652
  }
package/src/lib/report.js CHANGED
@@ -471,6 +471,38 @@ function deriveRepoStatusCounts(repoStatuses) {
471
471
  return counts;
472
472
  }
473
473
 
474
+ export function extractWorkflowKitArtifacts(artifact) {
475
+ const config = artifact.config;
476
+ if (!config || typeof config !== 'object' || !config.workflow_kit) return null;
477
+
478
+ const phase = artifact.summary?.phase || artifact.state?.phase;
479
+ if (!phase) return null;
480
+
481
+ const phaseConfig = config.workflow_kit.phases?.[phase];
482
+ if (!phaseConfig) return [];
483
+
484
+ const artifacts = Array.isArray(phaseConfig.artifacts) ? phaseConfig.artifacts : [];
485
+ if (artifacts.length === 0) return [];
486
+
487
+ const entryRole = config.routing?.[phase]?.entry_role || null;
488
+ const fileKeys = new Set(Object.keys(artifact.files || {}));
489
+
490
+ return artifacts
491
+ .filter((a) => a && typeof a.path === 'string')
492
+ .map((a) => {
493
+ const hasExplicitOwner = typeof a.owned_by === 'string' && a.owned_by.length > 0;
494
+ return {
495
+ path: a.path,
496
+ required: a.required !== false,
497
+ semantics: a.semantics || null,
498
+ owned_by: hasExplicitOwner ? a.owned_by : entryRole,
499
+ owner_resolution: hasExplicitOwner ? 'explicit' : 'entry_role',
500
+ exists: fileKeys.has(a.path),
501
+ };
502
+ })
503
+ .sort((a, b) => a.path.localeCompare(b.path, 'en'));
504
+ }
505
+
474
506
  function buildRunSubject(artifact) {
475
507
  const activeTurns = artifact.summary?.active_turn_ids || [];
476
508
  const retainedTurns = artifact.summary?.retained_turn_ids || [];
@@ -518,6 +550,7 @@ function buildRunSubject(artifact) {
518
550
  gate_summary: gateSummary,
519
551
  intake_links: intakeLinks,
520
552
  recovery_summary: recoverySummary,
553
+ workflow_kit_artifacts: extractWorkflowKitArtifacts(artifact),
521
554
  },
522
555
  artifacts: {
523
556
  history_entries: artifact.summary?.history_entries || 0,
@@ -810,6 +843,17 @@ export function formatGovernanceReportText(report) {
810
843
  lines.push(` Turn retained: ${run.recovery_summary.turn_retained == null ? 'n/a' : yesNo(run.recovery_summary.turn_retained)}`);
811
844
  }
812
845
 
846
+ if (Array.isArray(run.workflow_kit_artifacts) && run.workflow_kit_artifacts.length > 0) {
847
+ lines.push('', `Workflow Artifacts (${run.phase || 'unknown'} phase):`);
848
+ for (const art of run.workflow_kit_artifacts) {
849
+ const req = art.required ? 'required' : 'optional';
850
+ const sem = art.semantics || 'none';
851
+ const owner = art.owned_by ? `${art.owned_by} (${art.owner_resolution})` : 'none';
852
+ const status = art.exists ? 'exists' : 'missing';
853
+ lines.push(` ${art.path} | ${req} | ${sem} | owner: ${owner} | ${status}`);
854
+ }
855
+ }
856
+
813
857
  return lines.join('\n');
814
858
  }
815
859
 
@@ -1066,6 +1110,19 @@ export function formatGovernanceReportMarkdown(report) {
1066
1110
  lines.push(`- Turn retained: \`${run.recovery_summary.turn_retained == null ? 'n/a' : yesNo(run.recovery_summary.turn_retained)}\``);
1067
1111
  }
1068
1112
 
1113
+ if (Array.isArray(run.workflow_kit_artifacts) && run.workflow_kit_artifacts.length > 0) {
1114
+ lines.push('', '## Workflow Artifacts', '');
1115
+ lines.push(`Phase: \`${run.phase || 'unknown'}\``, '');
1116
+ lines.push('| Artifact | Required | Semantics | Owner | Resolution | Status |', '|----------|----------|-----------|-------|------------|--------|');
1117
+ for (const art of run.workflow_kit_artifacts) {
1118
+ const req = art.required ? 'yes' : 'no';
1119
+ const sem = art.semantics ? `\`${art.semantics}\`` : 'none';
1120
+ const owner = art.owned_by ? `\`${art.owned_by}\`` : 'none';
1121
+ const status = art.exists ? 'exists' : '**missing**';
1122
+ lines.push(`| \`${art.path}\` | ${req} | ${sem} | ${owner} | ${art.owner_resolution} | ${status} |`);
1123
+ }
1124
+ }
1125
+
1069
1126
  return lines.join('\n');
1070
1127
  }
1071
1128