agentxchain 2.31.0 → 2.32.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/dashboard/app.js CHANGED
@@ -13,6 +13,7 @@ import { render as renderGate } from './components/gate.js';
13
13
  import { render as renderInitiative } from './components/initiative.js';
14
14
  import { render as renderCrossRepo } from './components/cross-repo.js';
15
15
  import { render as renderBlockers } from './components/blockers.js';
16
+ import { render as renderArtifacts } from './components/artifacts.js';
16
17
 
17
18
  const VIEWS = {
18
19
  timeline: { fetch: ['state', 'history', 'audit', 'annotations'], render: renderTimeline },
@@ -23,6 +24,7 @@ const VIEWS = {
23
24
  initiative: { fetch: ['coordinatorState', 'coordinatorBarriers', 'barrierLedger', 'coordinatorBlockers'], render: renderInitiative },
24
25
  'cross-repo': { fetch: ['coordinatorState', 'coordinatorHistory'], render: renderCrossRepo },
25
26
  blockers: { fetch: ['coordinatorBlockers'], render: renderBlockers },
27
+ artifacts: { fetch: ['workflowKitArtifacts'], render: renderArtifacts },
26
28
  };
27
29
 
28
30
  const API_MAP = {
@@ -37,6 +39,7 @@ const API_MAP = {
37
39
  barrierLedger: '/api/coordinator/barrier-ledger',
38
40
  coordinatorAudit: '/api/coordinator/hooks/audit',
39
41
  coordinatorBlockers: '/api/coordinator/blockers',
42
+ workflowKitArtifacts: '/api/workflow-kit-artifacts',
40
43
  };
41
44
 
42
45
  const viewState = {
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Workflow-Kit Artifacts view — renders live workflow-kit artifact status.
3
+ *
4
+ * Pure render function: takes data from /api/workflow-kit-artifacts, returns HTML.
5
+ * Ownership resolution and file-existence checks are server-side. This view
6
+ * renders the snapshot without reimplementing any logic.
7
+ *
8
+ * See: WORKFLOW_KIT_DASHBOARD_SPEC.md
9
+ */
10
+
11
+ function esc(str) {
12
+ if (!str) return '';
13
+ return String(str)
14
+ .replace(/&/g, '&')
15
+ .replace(/</g, '&lt;')
16
+ .replace(/>/g, '&gt;')
17
+ .replace(/"/g, '&quot;')
18
+ .replace(/'/g, '&#39;');
19
+ }
20
+
21
+ function badge(label, color = 'var(--text-dim)') {
22
+ return `<span class="badge" style="color:${color};border-color:${color}">${esc(label)}</span>`;
23
+ }
24
+
25
+ function statusIndicator(exists) {
26
+ if (exists) {
27
+ return `<span style="color:var(--green);font-weight:600" title="File exists">✓ exists</span>`;
28
+ }
29
+ return `<span style="color:var(--red);font-weight:600" title="File missing">✗ missing</span>`;
30
+ }
31
+
32
+ function renderArtifactRow(artifact) {
33
+ const isMissingRequired = !artifact.exists && artifact.required;
34
+ const rowStyle = isMissingRequired
35
+ ? ' style="border-left:3px solid var(--red)"'
36
+ : '';
37
+
38
+ return `<tr${rowStyle}>
39
+ <td class="mono">${esc(artifact.path)}</td>
40
+ <td>${artifact.required ? badge('required', 'var(--yellow)') : badge('optional', 'var(--text-dim)')}</td>
41
+ <td>${artifact.semantics ? `<span class="mono">${esc(artifact.semantics)}</span>` : '<span style="color:var(--text-dim)">—</span>'}</td>
42
+ <td>${artifact.owned_by ? esc(artifact.owned_by) : '<span style="color:var(--text-dim)">—</span>'}</td>
43
+ <td>${artifact.owner_resolution ? badge(artifact.owner_resolution, artifact.owner_resolution === 'explicit' ? 'var(--accent)' : 'var(--text-dim)') : '—'}</td>
44
+ <td>${statusIndicator(artifact.exists)}</td>
45
+ </tr>`;
46
+ }
47
+
48
+ export function render({ workflowKitArtifacts }) {
49
+ if (!workflowKitArtifacts) {
50
+ return `<div class="placeholder"><h2>Workflow Artifacts</h2><p>No workflow artifact data available. Ensure a governed run is active.</p></div>`;
51
+ }
52
+
53
+ if (workflowKitArtifacts.ok === false) {
54
+ const hint = workflowKitArtifacts.code === 'config_missing' || workflowKitArtifacts.code === 'state_missing'
55
+ ? ' Run <code>agentxchain init --governed</code> to get started.'
56
+ : '';
57
+ return `<div class="placeholder"><h2>Workflow Artifacts</h2><p>${esc(workflowKitArtifacts.error || 'Failed to load workflow artifact data.')}${hint}</p></div>`;
58
+ }
59
+
60
+ const data = workflowKitArtifacts;
61
+ const artifacts = data.artifacts;
62
+
63
+ // No workflow_kit configured
64
+ if (artifacts === null) {
65
+ return `<div class="placeholder"><h2>Workflow Artifacts</h2><p>No <code>workflow_kit</code> configured in <code>agentxchain.json</code>. Add a <code>workflow_kit</code> section to track phase artifacts.</p></div>`;
66
+ }
67
+
68
+ // Phase has no artifacts
69
+ if (!Array.isArray(artifacts) || artifacts.length === 0) {
70
+ return `<div class="placeholder"><h2>Workflow Artifacts</h2><p>Current phase <strong>${esc(data.phase || 'unknown')}</strong> has no workflow-kit artifacts defined.</p></div>`;
71
+ }
72
+
73
+ const missingRequired = artifacts.filter((a) => a.required && !a.exists).length;
74
+ const totalExists = artifacts.filter((a) => a.exists).length;
75
+
76
+ let html = `<div class="artifacts-view">`;
77
+
78
+ // Header
79
+ html += `<div class="run-header"><div class="run-meta">`;
80
+ if (data.phase) {
81
+ html += `<span class="phase-label">Phase: <strong>${esc(data.phase)}</strong></span>`;
82
+ }
83
+ html += `<span class="turn-count">${artifacts.length} artifact${artifacts.length !== 1 ? 's' : ''}</span>`;
84
+ html += `<span class="turn-count">${totalExists}/${artifacts.length} present</span>`;
85
+ if (missingRequired > 0) {
86
+ html += badge(`${missingRequired} missing required`, 'var(--red)');
87
+ }
88
+ html += `</div></div>`;
89
+
90
+ // Table
91
+ html += `<div class="section"><h3>Workflow-Kit Artifacts</h3>
92
+ <table class="data-table">
93
+ <thead>
94
+ <tr>
95
+ <th>Path</th>
96
+ <th>Required</th>
97
+ <th>Semantics</th>
98
+ <th>Owner</th>
99
+ <th>Resolution</th>
100
+ <th>Status</th>
101
+ </tr>
102
+ </thead>
103
+ <tbody>`;
104
+
105
+ for (const artifact of artifacts) {
106
+ html += renderArtifactRow(artifact);
107
+ }
108
+
109
+ html += `</tbody></table></div>`;
110
+
111
+ html += `</div>`;
112
+ return html;
113
+ }
@@ -382,6 +382,7 @@
382
382
  <a href="#blocked">Blocked</a>
383
383
  <a href="#gate">Gates</a>
384
384
  <a href="#blockers">Blockers</a>
385
+ <a href="#artifacts">Artifacts</a>
385
386
  </nav>
386
387
  <main id="view-container">
387
388
  <div class="placeholder">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.31.0",
3
+ "version": "2.32.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,6 +18,7 @@ import { readResource } from './state-reader.js';
18
18
  import { FileWatcher } from './file-watcher.js';
19
19
  import { approvePendingDashboardGate } from './actions.js';
20
20
  import { readCoordinatorBlockerSnapshot } from './coordinator-blockers.js';
21
+ import { readWorkflowKitArtifacts } from './workflow-kit-artifacts.js';
21
22
 
22
23
  const MIME_TYPES = {
23
24
  '.html': 'text/html; charset=utf-8',
@@ -279,6 +280,12 @@ export function createBridgeServer({ agentxchainDir, dashboardDir, port = 3847 }
279
280
  return;
280
281
  }
281
282
 
283
+ if (pathname === '/api/workflow-kit-artifacts') {
284
+ const result = readWorkflowKitArtifacts(workspacePath);
285
+ writeJson(res, result.status, result.body);
286
+ return;
287
+ }
288
+
282
289
  // API routes
283
290
  if (pathname.startsWith('/api/')) {
284
291
  const result = readResource(agentxchainDir, pathname);
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Workflow-kit artifact status — computed dashboard endpoint.
3
+ *
4
+ * Reads agentxchain.json config and .agentxchain/state.json to derive
5
+ * current-phase workflow-kit artifact status for live dashboard observation.
6
+ *
7
+ * Per DEC-WK-REPORT-002: ownership resolution uses explicit owned_by first,
8
+ * then falls back to routing[phase].entry_role.
9
+ *
10
+ * See: WORKFLOW_KIT_DASHBOARD_SPEC.md
11
+ */
12
+
13
+ import { existsSync } from 'fs';
14
+ import { join } from 'path';
15
+ import { loadConfig } from '../config.js';
16
+ import { readJsonFile } from './state-reader.js';
17
+
18
+ /**
19
+ * Read workflow-kit artifact status for the current phase.
20
+ *
21
+ * @param {string} workspacePath — project root (parent of .agentxchain/)
22
+ * @returns {{ ok: boolean, status: number, body: object }}
23
+ */
24
+ export function readWorkflowKitArtifacts(workspacePath) {
25
+ const configResult = loadConfig(workspacePath);
26
+ if (!configResult) {
27
+ return {
28
+ ok: false,
29
+ status: 404,
30
+ body: {
31
+ ok: false,
32
+ code: 'config_missing',
33
+ error: 'Project config not found. Run `agentxchain init --governed` first.',
34
+ },
35
+ };
36
+ }
37
+
38
+ const agentxchainDir = join(workspacePath, '.agentxchain');
39
+ const state = readJsonFile(agentxchainDir, 'state.json');
40
+ if (!state) {
41
+ return {
42
+ ok: false,
43
+ status: 404,
44
+ body: {
45
+ ok: false,
46
+ code: 'state_missing',
47
+ error: 'Run state not found. Run `agentxchain init --governed` first.',
48
+ },
49
+ };
50
+ }
51
+
52
+ const config = configResult.config;
53
+ const phase = state.phase || null;
54
+
55
+ if (!config.workflow_kit) {
56
+ return {
57
+ ok: true,
58
+ status: 200,
59
+ body: {
60
+ ok: true,
61
+ phase,
62
+ artifacts: null,
63
+ },
64
+ };
65
+ }
66
+
67
+ if (!phase) {
68
+ return {
69
+ ok: true,
70
+ status: 200,
71
+ body: {
72
+ ok: true,
73
+ phase: null,
74
+ artifacts: [],
75
+ },
76
+ };
77
+ }
78
+
79
+ const phaseConfig = config.workflow_kit.phases?.[phase];
80
+ if (!phaseConfig) {
81
+ return {
82
+ ok: true,
83
+ status: 200,
84
+ body: {
85
+ ok: true,
86
+ phase,
87
+ artifacts: [],
88
+ },
89
+ };
90
+ }
91
+
92
+ const artifacts = Array.isArray(phaseConfig.artifacts) ? phaseConfig.artifacts : [];
93
+ if (artifacts.length === 0) {
94
+ return {
95
+ ok: true,
96
+ status: 200,
97
+ body: {
98
+ ok: true,
99
+ phase,
100
+ artifacts: [],
101
+ },
102
+ };
103
+ }
104
+
105
+ const entryRole = config.routing?.[phase]?.entry_role || null;
106
+
107
+ const result = artifacts
108
+ .filter((a) => a && typeof a.path === 'string')
109
+ .map((a) => {
110
+ const hasExplicitOwner = typeof a.owned_by === 'string' && a.owned_by.length > 0;
111
+ return {
112
+ path: a.path,
113
+ required: a.required !== false,
114
+ semantics: a.semantics || null,
115
+ owned_by: hasExplicitOwner ? a.owned_by : entryRole,
116
+ owner_resolution: hasExplicitOwner ? 'explicit' : 'entry_role',
117
+ exists: existsSync(join(workspacePath, a.path)),
118
+ };
119
+ })
120
+ .sort((a, b) => a.path.localeCompare(b.path, 'en'));
121
+
122
+ return {
123
+ ok: true,
124
+ status: 200,
125
+ body: {
126
+ ok: true,
127
+ phase,
128
+ artifacts: result,
129
+ },
130
+ };
131
+ }