agentic-orchestrator 0.1.25 → 0.1.26

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.
Files changed (177) hide show
  1. package/agentic/orchestrator/defaults/policy.defaults.yaml +1 -0
  2. package/agentic/orchestrator/policy.yaml +1 -0
  3. package/agentic/orchestrator/schemas/policy.schema.json +4 -0
  4. package/agentic/orchestrator/schemas/policy.user.schema.json +4 -0
  5. package/apps/control-plane/src/application/services/activity-monitor-service.ts +42 -0
  6. package/apps/control-plane/src/application/services/collision-queue-service.ts +48 -0
  7. package/apps/control-plane/src/application/services/cost-tracking-service.ts +80 -0
  8. package/apps/control-plane/src/application/services/feature-deletion-service.ts +57 -0
  9. package/apps/control-plane/src/application/services/feature-lifecycle-service.ts +65 -0
  10. package/apps/control-plane/src/application/services/feature-state-service.ts +87 -0
  11. package/apps/control-plane/src/application/services/gate-selection-service.ts +28 -0
  12. package/apps/control-plane/src/application/services/gate-service.ts +148 -0
  13. package/apps/control-plane/src/application/services/issue-tracker-service.ts +28 -0
  14. package/apps/control-plane/src/application/services/lock-service.ts +88 -0
  15. package/apps/control-plane/src/application/services/merge-service.ts +63 -0
  16. package/apps/control-plane/src/application/services/notifier-service.ts +70 -0
  17. package/apps/control-plane/src/application/services/patch-service.ts +95 -0
  18. package/apps/control-plane/src/application/services/performance-analytics-service.ts +90 -0
  19. package/apps/control-plane/src/application/services/plan-service.ts +184 -0
  20. package/apps/control-plane/src/application/services/pr-monitor-service.ts +58 -0
  21. package/apps/control-plane/src/application/services/qa-index-service.ts +68 -0
  22. package/apps/control-plane/src/application/services/reactions-service.ts +77 -0
  23. package/apps/control-plane/src/application/services/reporting-service.ts +79 -0
  24. package/apps/control-plane/src/application/services/run-lease-service.ts +79 -0
  25. package/apps/control-plane/src/application/tools/tool-router.ts +76 -0
  26. package/apps/control-plane/src/mcp/tool-runtime.ts +4 -0
  27. package/apps/control-plane/src/providers/providers.ts +96 -0
  28. package/apps/control-plane/src/providers/worker-provider-factory.ts +4 -0
  29. package/apps/control-plane/src/supervisor/build-wave-executor.ts +4 -0
  30. package/apps/control-plane/src/supervisor/planning-wave-executor.ts +4 -0
  31. package/apps/control-plane/src/supervisor/qa-wave-executor.ts +4 -0
  32. package/apps/control-plane/src/supervisor/run-coordinator.ts +4 -0
  33. package/apps/control-plane/src/supervisor/runtime.ts +4 -0
  34. package/apps/control-plane/src/supervisor/session-orchestrator.ts +4 -0
  35. package/apps/control-plane/test/dashboard-api.integration.spec.ts +310 -1
  36. package/apps/control-plane/test/dashboard-client.spec.ts +136 -1
  37. package/apps/control-plane/test/dashboard-live-feed.spec.ts +153 -0
  38. package/apps/control-plane/test/dashboard-ui-utils.spec.ts +132 -0
  39. package/apps/control-plane/test/mcp.spec.ts +96 -0
  40. package/apps/control-plane/test/tool-runtime.spec.ts +68 -0
  41. package/config/agentic/orchestrator/policy.yaml +1 -0
  42. package/dist/apps/control-plane/application/services/activity-monitor-service.d.ts +42 -0
  43. package/dist/apps/control-plane/application/services/activity-monitor-service.js +42 -0
  44. package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -1
  45. package/dist/apps/control-plane/application/services/collision-queue-service.d.ts +48 -0
  46. package/dist/apps/control-plane/application/services/collision-queue-service.js +48 -0
  47. package/dist/apps/control-plane/application/services/collision-queue-service.js.map +1 -1
  48. package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +79 -0
  49. package/dist/apps/control-plane/application/services/cost-tracking-service.js +76 -0
  50. package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -1
  51. package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +57 -0
  52. package/dist/apps/control-plane/application/services/feature-deletion-service.js +57 -0
  53. package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
  54. package/dist/apps/control-plane/application/services/feature-lifecycle-service.d.ts +64 -0
  55. package/dist/apps/control-plane/application/services/feature-lifecycle-service.js +61 -0
  56. package/dist/apps/control-plane/application/services/feature-lifecycle-service.js.map +1 -1
  57. package/dist/apps/control-plane/application/services/feature-state-service.d.ts +86 -0
  58. package/dist/apps/control-plane/application/services/feature-state-service.js +83 -0
  59. package/dist/apps/control-plane/application/services/feature-state-service.js.map +1 -1
  60. package/dist/apps/control-plane/application/services/gate-selection-service.d.ts +28 -0
  61. package/dist/apps/control-plane/application/services/gate-selection-service.js +28 -0
  62. package/dist/apps/control-plane/application/services/gate-selection-service.js.map +1 -1
  63. package/dist/apps/control-plane/application/services/gate-service.d.ts +148 -0
  64. package/dist/apps/control-plane/application/services/gate-service.js +120 -0
  65. package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
  66. package/dist/apps/control-plane/application/services/issue-tracker-service.d.ts +28 -0
  67. package/dist/apps/control-plane/application/services/issue-tracker-service.js +28 -0
  68. package/dist/apps/control-plane/application/services/issue-tracker-service.js.map +1 -1
  69. package/dist/apps/control-plane/application/services/lock-service.d.ts +88 -0
  70. package/dist/apps/control-plane/application/services/lock-service.js +64 -0
  71. package/dist/apps/control-plane/application/services/lock-service.js.map +1 -1
  72. package/dist/apps/control-plane/application/services/merge-service.d.ts +62 -0
  73. package/dist/apps/control-plane/application/services/merge-service.js +59 -0
  74. package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
  75. package/dist/apps/control-plane/application/services/notifier-service.d.ts +70 -0
  76. package/dist/apps/control-plane/application/services/notifier-service.js +70 -0
  77. package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -1
  78. package/dist/apps/control-plane/application/services/patch-service.d.ts +94 -0
  79. package/dist/apps/control-plane/application/services/patch-service.js +91 -0
  80. package/dist/apps/control-plane/application/services/patch-service.js.map +1 -1
  81. package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +89 -0
  82. package/dist/apps/control-plane/application/services/performance-analytics-service.js +86 -0
  83. package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -1
  84. package/dist/apps/control-plane/application/services/plan-service.d.ts +287 -0
  85. package/dist/apps/control-plane/application/services/plan-service.js +207 -0
  86. package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
  87. package/dist/apps/control-plane/application/services/pr-monitor-service.d.ts +58 -0
  88. package/dist/apps/control-plane/application/services/pr-monitor-service.js +58 -0
  89. package/dist/apps/control-plane/application/services/pr-monitor-service.js.map +1 -1
  90. package/dist/apps/control-plane/application/services/qa-index-service.d.ts +67 -0
  91. package/dist/apps/control-plane/application/services/qa-index-service.js +64 -0
  92. package/dist/apps/control-plane/application/services/qa-index-service.js.map +1 -1
  93. package/dist/apps/control-plane/application/services/reactions-service.d.ts +76 -0
  94. package/dist/apps/control-plane/application/services/reactions-service.js +73 -0
  95. package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -1
  96. package/dist/apps/control-plane/application/services/reporting-service.d.ts +78 -0
  97. package/dist/apps/control-plane/application/services/reporting-service.js +75 -0
  98. package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
  99. package/dist/apps/control-plane/application/services/run-lease-service.d.ts +79 -0
  100. package/dist/apps/control-plane/application/services/run-lease-service.js +79 -0
  101. package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
  102. package/dist/apps/control-plane/application/tools/tool-router.d.ts +76 -0
  103. package/dist/apps/control-plane/application/tools/tool-router.js +62 -0
  104. package/dist/apps/control-plane/application/tools/tool-router.js.map +1 -1
  105. package/dist/apps/control-plane/mcp/tool-runtime.d.ts +3 -0
  106. package/dist/apps/control-plane/mcp/tool-runtime.js +3 -0
  107. package/dist/apps/control-plane/mcp/tool-runtime.js.map +1 -1
  108. package/dist/apps/control-plane/providers/providers.d.ts +88 -0
  109. package/dist/apps/control-plane/providers/providers.js.map +1 -1
  110. package/dist/apps/control-plane/providers/worker-provider-factory.d.ts +3 -0
  111. package/dist/apps/control-plane/providers/worker-provider-factory.js.map +1 -1
  112. package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +3 -0
  113. package/dist/apps/control-plane/supervisor/build-wave-executor.js +3 -0
  114. package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
  115. package/dist/apps/control-plane/supervisor/planning-wave-executor.d.ts +3 -0
  116. package/dist/apps/control-plane/supervisor/planning-wave-executor.js +3 -0
  117. package/dist/apps/control-plane/supervisor/planning-wave-executor.js.map +1 -1
  118. package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +3 -0
  119. package/dist/apps/control-plane/supervisor/qa-wave-executor.js +3 -0
  120. package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
  121. package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +3 -0
  122. package/dist/apps/control-plane/supervisor/run-coordinator.js +3 -0
  123. package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
  124. package/dist/apps/control-plane/supervisor/runtime.d.ts +3 -0
  125. package/dist/apps/control-plane/supervisor/runtime.js +3 -0
  126. package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
  127. package/dist/apps/control-plane/supervisor/session-orchestrator.d.ts +3 -0
  128. package/dist/apps/control-plane/supervisor/session-orchestrator.js +3 -0
  129. package/dist/apps/control-plane/supervisor/session-orchestrator.js.map +1 -1
  130. package/package.json +1 -1
  131. package/packages/web-dashboard/src/app/analytics/page.tsx +451 -0
  132. package/packages/web-dashboard/src/app/api/analytics/route.ts +62 -0
  133. package/packages/web-dashboard/src/app/api/collisions/route.ts +63 -0
  134. package/packages/web-dashboard/src/app/api/features/[id]/cost/route.ts +29 -0
  135. package/packages/web-dashboard/src/app/api/features/[id]/review-brief/route.ts +31 -0
  136. package/packages/web-dashboard/src/app/api/features/[id]/route.ts +5 -8
  137. package/packages/web-dashboard/src/app/api/features/[id]/test-index/route.ts +31 -0
  138. package/packages/web-dashboard/src/app/api/flaky/route.ts +20 -0
  139. package/packages/web-dashboard/src/app/api/policy/budget/route.ts +31 -0
  140. package/packages/web-dashboard/src/app/api/run/route.ts +162 -0
  141. package/packages/web-dashboard/src/app/api/status/route.ts +2 -5
  142. package/packages/web-dashboard/src/app/feature/[id]/page.tsx +112 -0
  143. package/packages/web-dashboard/src/app/page.tsx +379 -77
  144. package/packages/web-dashboard/src/components/agent-pipeline-stepper.tsx +100 -0
  145. package/packages/web-dashboard/src/components/dependency-chains.tsx +58 -0
  146. package/packages/web-dashboard/src/components/detail-panel.tsx +215 -5
  147. package/packages/web-dashboard/src/components/feature-card.tsx +5 -0
  148. package/packages/web-dashboard/src/components/feature-cost-panel.tsx +66 -0
  149. package/packages/web-dashboard/src/components/feature-list-view.tsx +336 -0
  150. package/packages/web-dashboard/src/components/gate-step-drilldown.tsx +92 -0
  151. package/packages/web-dashboard/src/components/kanban-board.tsx +3 -0
  152. package/packages/web-dashboard/src/components/live-event-feed.tsx +60 -0
  153. package/packages/web-dashboard/src/components/lock-resource-map.tsx +73 -0
  154. package/packages/web-dashboard/src/components/plan-revision-timeline.tsx +55 -0
  155. package/packages/web-dashboard/src/components/plan-risk-annotations.tsx +63 -0
  156. package/packages/web-dashboard/src/components/plan-scope-tree.tsx +285 -0
  157. package/packages/web-dashboard/src/components/qa-coverage-map.tsx +174 -0
  158. package/packages/web-dashboard/src/components/quick-launch-panel.tsx +70 -0
  159. package/packages/web-dashboard/src/components/review-brief-panel.tsx +75 -0
  160. package/packages/web-dashboard/src/components/run-health-panel.tsx +85 -0
  161. package/packages/web-dashboard/src/components/summary-bar.tsx +59 -2
  162. package/packages/web-dashboard/src/components/verification-signals.tsx +167 -0
  163. package/packages/web-dashboard/src/lib/analytics-utils.ts +3 -0
  164. package/packages/web-dashboard/src/lib/aop-client.ts +510 -11
  165. package/packages/web-dashboard/src/lib/api-envelope.ts +48 -0
  166. package/packages/web-dashboard/src/lib/authz-adapter.ts +33 -0
  167. package/packages/web-dashboard/src/lib/dashboard-utils.ts +304 -0
  168. package/packages/web-dashboard/src/lib/live-feed.ts +218 -0
  169. package/packages/web-dashboard/src/lib/policy-reader.ts +22 -0
  170. package/packages/web-dashboard/src/lib/types.ts +114 -0
  171. package/packages/web-dashboard/src/styles/dashboard.module.css +341 -1
  172. package/spec-files/{outstanding → completed}/agentic_orchestrator_dashboard_advanced_ux_spec.md +1 -1
  173. package/spec-files/outstanding/agentic_orchestrator_evidence_integrity_doctor_spec.md +561 -0
  174. package/spec-files/outstanding/agentic_orchestrator_kernel_simplification_spec.md +834 -0
  175. package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +12 -5
  176. package/spec-files/outstanding/agentic_orchestrator_observability_replay_spec.md +442 -0
  177. package/spec-files/progress.md +174 -1
@@ -0,0 +1,85 @@
1
+ import type { RuntimeSession } from '@/lib/types.js';
2
+ import styles from '@/styles/dashboard.module.css';
3
+
4
+ interface RunHealthPanelProps {
5
+ runtime: RuntimeSession | undefined;
6
+ nowMs: number;
7
+ }
8
+
9
+ function ageSeconds(iso: string | null | undefined, nowMs: number): number | null {
10
+ if (!iso) {
11
+ return null;
12
+ }
13
+ const parsed = Date.parse(iso);
14
+ if (Number.isNaN(parsed)) {
15
+ return null;
16
+ }
17
+ return Math.max(0, Math.floor((nowMs - parsed) / 1000));
18
+ }
19
+
20
+ function formatSeconds(value: number | null): string {
21
+ if (value == null) {
22
+ return '-';
23
+ }
24
+ const minutes = Math.floor(value / 60);
25
+ const seconds = value % 60;
26
+ return `${minutes}m ${seconds}s`;
27
+ }
28
+
29
+ function healthFromHeartbeat(heartbeatAgeSec: number | null): {
30
+ label: 'Healthy' | 'Degraded' | 'Critical';
31
+ className: string;
32
+ } {
33
+ if (heartbeatAgeSec == null) {
34
+ return { label: 'Critical', className: styles.badgeFail };
35
+ }
36
+ if (heartbeatAgeSec > 120) {
37
+ return { label: 'Critical', className: styles.badgeFail };
38
+ }
39
+ if (heartbeatAgeSec >= 30) {
40
+ return { label: 'Degraded', className: styles.badgePending };
41
+ }
42
+ return { label: 'Healthy', className: styles.badgePass };
43
+ }
44
+
45
+ export function RunHealthPanel({ runtime, nowMs }: RunHealthPanelProps) {
46
+ if (!runtime) {
47
+ return (
48
+ <section className={styles.section}>
49
+ <h2 className={styles.sectionTitle}>Run Health</h2>
50
+ <p className={styles.metaText}>No active orchestrator run.</p>
51
+ </section>
52
+ );
53
+ }
54
+
55
+ const heartbeatAgeSec = ageSeconds(runtime.last_heartbeat_at ?? null, nowMs);
56
+ const startedAgeSec = ageSeconds(runtime.started_at ?? null, nowMs);
57
+ const leaseRemainingSec = (() => {
58
+ if (!runtime.lease_expires_at) {
59
+ return null;
60
+ }
61
+ const parsed = Date.parse(runtime.lease_expires_at);
62
+ if (Number.isNaN(parsed)) {
63
+ return null;
64
+ }
65
+ return Math.max(0, Math.floor((parsed - nowMs) / 1000));
66
+ })();
67
+ const health = healthFromHeartbeat(heartbeatAgeSec);
68
+
69
+ return (
70
+ <section className={styles.section}>
71
+ <h2 className={styles.sectionTitle}>Run Health</h2>
72
+ <div className={styles.badgeRow}>
73
+ <span className={`${styles.badge} ${health.className}`}>{health.label}</span>
74
+ <span className={`${styles.badge} ${styles.badgeNeutral}`}>
75
+ {runtime.provider ?? 'unknown'} / {runtime.model ?? 'unknown'}
76
+ </span>
77
+ </div>
78
+ <p className={styles.metaText}>Run: {runtime.run_id ?? '-'}</p>
79
+ <p className={styles.metaText}>Owner: {runtime.owner_instance_id ?? '-'}</p>
80
+ <p className={styles.metaText}>Uptime: {formatSeconds(startedAgeSec)}</p>
81
+ <p className={styles.metaText}>Heartbeat age: {formatSeconds(heartbeatAgeSec)}</p>
82
+ <p className={styles.metaText}>Lease remaining: {formatSeconds(leaseRemainingSec)}</p>
83
+ </section>
84
+ );
85
+ }
@@ -1,13 +1,37 @@
1
+ import Link from 'next/link';
1
2
  import styles from '@/styles/dashboard.module.css';
2
- import type { DashboardSummaryCounts, QuickFilterKey } from '@/lib/dashboard-utils.js';
3
+ import {
4
+ sparklinePath,
5
+ sparklinePoints,
6
+ type DashboardSummaryCounts,
7
+ type QuickFilterKey,
8
+ } from '@/lib/dashboard-utils.js';
3
9
 
4
10
  interface SummaryBarProps {
5
11
  counts: DashboardSummaryCounts;
6
12
  activeQuickFilter: QuickFilterKey | null;
7
13
  onQuickFilter: (key: QuickFilterKey) => void;
14
+ mergeHistogram14d?: number[];
15
+ totalCostTodayUsd?: number;
16
+ analyticsHref: string;
8
17
  }
9
18
 
10
- export function SummaryBar({ counts, activeQuickFilter, onQuickFilter }: SummaryBarProps) {
19
+ function formatCost(value: number | undefined): string {
20
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
21
+ return '$0.00';
22
+ }
23
+ return `$${value.toFixed(2)}`;
24
+ }
25
+
26
+ export function SummaryBar({
27
+ counts,
28
+ activeQuickFilter,
29
+ onQuickFilter,
30
+ mergeHistogram14d = [],
31
+ totalCostTodayUsd,
32
+ analyticsHref,
33
+ }: SummaryBarProps) {
34
+ const trendPoints = sparklinePoints(mergeHistogram14d, 180, 40);
11
35
  const tiles: Array<{
12
36
  key: QuickFilterKey;
13
37
  label: string;
@@ -60,6 +84,39 @@ export function SummaryBar({ counts, activeQuickFilter, onQuickFilter }: Summary
60
84
  </button>
61
85
  );
62
86
  })}
87
+ <Link href={analyticsHref} className={styles.summaryTile} aria-label="Open cost analytics">
88
+ <span className={styles.summaryLabel}>Cost Today</span>
89
+ <span className={styles.summaryValue}>{formatCost(totalCostTodayUsd)}</span>
90
+ <span className={styles.summaryLabel}>Server aggregate</span>
91
+ </Link>
92
+ <Link href={analyticsHref} className={styles.summaryTile} aria-label="Open analytics">
93
+ <span className={styles.summaryLabel}>Throughput (14d)</span>
94
+ {mergeHistogram14d.length > 0 ? (
95
+ <svg
96
+ viewBox="0 0 180 40"
97
+ width="100%"
98
+ height={40}
99
+ role="img"
100
+ aria-label="14 day merge trend sparkline"
101
+ >
102
+ <path d={sparklinePath(mergeHistogram14d, 180, 40)} fill="none" stroke="#7fc6ff" />
103
+ {trendPoints.map((point) => (
104
+ <circle
105
+ key={`trend-point-${point.index}`}
106
+ className={styles.sparklinePoint}
107
+ cx={point.x}
108
+ cy={point.y}
109
+ r={3}
110
+ >
111
+ <title>{`Day ${point.index + 1}: ${point.value} merges`}</title>
112
+ </circle>
113
+ ))}
114
+ </svg>
115
+ ) : (
116
+ <span className={styles.metaText}>No merge trend yet</span>
117
+ )}
118
+ <span className={styles.summaryLabel}>Open analytics</span>
119
+ </Link>
63
120
  </section>
64
121
  );
65
122
  }
@@ -0,0 +1,167 @@
1
+ import type { FeatureSummary } from '@/lib/types.js';
2
+ import styles from '@/styles/dashboard.module.css';
3
+
4
+ interface VerificationSignalsProps {
5
+ feature: FeatureSummary;
6
+ plan: Record<string, unknown> | null;
7
+ }
8
+
9
+ type VerificationState = 'verified' | 'at_risk' | 'unverified';
10
+
11
+ interface SignalItem {
12
+ label: string;
13
+ status: VerificationState;
14
+ reason: string;
15
+ evidence_ref?: string;
16
+ }
17
+
18
+ function parseCriteria(
19
+ plan: Record<string, unknown> | null,
20
+ ): Array<string | Record<string, unknown>> {
21
+ if (!plan) {
22
+ return [];
23
+ }
24
+ const candidates = [plan.acceptance_criteria, plan.acceptance, plan.criteria, plan.outcomes];
25
+ for (const candidate of candidates) {
26
+ if (Array.isArray(candidate)) {
27
+ return candidate.filter(
28
+ (item): item is string | Record<string, unknown> =>
29
+ typeof item === 'string' || (item != null && typeof item === 'object'),
30
+ );
31
+ }
32
+ }
33
+ return [];
34
+ }
35
+
36
+ function gateHasFailure(feature: FeatureSummary): boolean {
37
+ const gates = feature.gates;
38
+ if (!gates || typeof gates !== 'object') {
39
+ return false;
40
+ }
41
+ return Object.values(gates).some((value) => String(value).toLowerCase() === 'fail');
42
+ }
43
+
44
+ function baseStatus(feature: FeatureSummary): VerificationState {
45
+ if (gateHasFailure(feature) || feature.phase === 'blocked') {
46
+ return 'at_risk';
47
+ }
48
+ if (feature.phase === 'ready_to_merge' || feature.phase === 'merged') {
49
+ return 'verified';
50
+ }
51
+ return 'unverified';
52
+ }
53
+
54
+ function parseSignalItem(
55
+ item: string | Record<string, unknown>,
56
+ fallbackState: VerificationState,
57
+ ): SignalItem {
58
+ if (typeof item === 'string') {
59
+ return {
60
+ label: item,
61
+ status: fallbackState,
62
+ reason:
63
+ fallbackState === 'verified'
64
+ ? 'Verified by current artifact evidence.'
65
+ : fallbackState === 'at_risk'
66
+ ? 'Gate or status signals indicate risk.'
67
+ : 'Insufficient artifact evidence recorded yet.',
68
+ };
69
+ }
70
+
71
+ const label =
72
+ typeof item.label === 'string'
73
+ ? item.label
74
+ : typeof item.criterion === 'string'
75
+ ? item.criterion
76
+ : typeof item.text === 'string'
77
+ ? item.text
78
+ : 'Acceptance criterion';
79
+ const statusRaw = typeof item.status === 'string' ? item.status.toLowerCase() : '';
80
+ const status: VerificationState =
81
+ statusRaw === 'verified' || statusRaw === 'pass'
82
+ ? 'verified'
83
+ : statusRaw === 'at_risk' || statusRaw === 'risk' || statusRaw === 'fail'
84
+ ? 'at_risk'
85
+ : statusRaw === 'unverified' || statusRaw === 'unknown'
86
+ ? 'unverified'
87
+ : fallbackState;
88
+ const reason =
89
+ typeof item.reason === 'string'
90
+ ? item.reason
91
+ : status === 'verified'
92
+ ? 'Verified by current artifact evidence.'
93
+ : status === 'at_risk'
94
+ ? 'Gate or status signals indicate risk.'
95
+ : 'Insufficient artifact evidence recorded yet.';
96
+ const evidenceRef =
97
+ typeof item.evidence_ref === 'string'
98
+ ? item.evidence_ref
99
+ : typeof item.evidence === 'string'
100
+ ? item.evidence
101
+ : undefined;
102
+ return { label, status, reason, evidence_ref: evidenceRef };
103
+ }
104
+
105
+ function statusClass(status: VerificationState): string {
106
+ if (status === 'verified') {
107
+ return styles.badgePass;
108
+ }
109
+ if (status === 'at_risk') {
110
+ return styles.badgeFail;
111
+ }
112
+ return styles.badgeNeutral;
113
+ }
114
+
115
+ function statusLabel(status: VerificationState): string {
116
+ if (status === 'verified') {
117
+ return 'Verified';
118
+ }
119
+ if (status === 'at_risk') {
120
+ return 'At Risk';
121
+ }
122
+ return 'Unverified';
123
+ }
124
+
125
+ export function VerificationSignals({ feature, plan }: VerificationSignalsProps) {
126
+ const fallbackState = baseStatus(feature);
127
+ const criteria = parseCriteria(plan);
128
+ const signals =
129
+ criteria.length > 0
130
+ ? criteria.map((item) => parseSignalItem(item, fallbackState))
131
+ : [
132
+ {
133
+ label: 'Acceptance criteria evidence',
134
+ status: fallbackState,
135
+ reason: 'No explicit acceptance criteria list found in plan.',
136
+ } satisfies SignalItem,
137
+ ];
138
+
139
+ const verifiedCount = signals.filter((item) => item.status === 'verified').length;
140
+ const atRiskCount = signals.filter((item) => item.status === 'at_risk').length;
141
+ const unverifiedCount = signals.filter((item) => item.status === 'unverified').length;
142
+
143
+ return (
144
+ <details className={styles.section} open>
145
+ <summary className={styles.sectionSummary}>Verification Signals</summary>
146
+ <p className={styles.metaText}>
147
+ {verifiedCount} verified, {atRiskCount} at risk, {unverifiedCount} unverified.
148
+ </p>
149
+ <ul className={styles.planList}>
150
+ {signals.map((signal, index) => (
151
+ <li key={`${signal.label}-${index}`} className={styles.planListItem}>
152
+ <div className={styles.badgeRow}>
153
+ <span className={`${styles.badge} ${statusClass(signal.status)}`}>
154
+ {statusLabel(signal.status)}
155
+ </span>
156
+ <span>{signal.label}</span>
157
+ </div>
158
+ <p className={styles.metaText}>{signal.reason}</p>
159
+ {signal.evidence_ref ? (
160
+ <p className={styles.metaText}>Evidence: {signal.evidence_ref}</p>
161
+ ) : null}
162
+ </li>
163
+ ))}
164
+ </ul>
165
+ </details>
166
+ );
167
+ }
@@ -0,0 +1,3 @@
1
+ export function shouldPollAnalytics(tabHidden: boolean): boolean {
2
+ return !tabHidden;
3
+ }