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,100 @@
1
+ import type { AgentPipelineStatus, RoleStatus } from '@/lib/types.js';
2
+ import styles from '@/styles/dashboard.module.css';
3
+
4
+ interface AgentPipelineStepperProps {
5
+ status?: AgentPipelineStatus;
6
+ }
7
+
8
+ function normalizeRoleStatus(value: RoleStatus | undefined): RoleStatus {
9
+ return value ?? 'unknown';
10
+ }
11
+
12
+ function roleLabel(role: 'planner' | 'builder' | 'qa'): string {
13
+ if (role === 'planner') {
14
+ return 'Planner';
15
+ }
16
+ if (role === 'builder') {
17
+ return 'Builder';
18
+ }
19
+ return 'QA';
20
+ }
21
+
22
+ function statusLabel(status: RoleStatus): string {
23
+ if (status === 'ready') {
24
+ return 'Ready';
25
+ }
26
+ if (status === 'running') {
27
+ return 'Running';
28
+ }
29
+ if (status === 'blocked') {
30
+ return 'Blocked';
31
+ }
32
+ if (status === 'done') {
33
+ return 'Done';
34
+ }
35
+ return 'Unknown';
36
+ }
37
+
38
+ function statusClass(status: RoleStatus): string {
39
+ if (status === 'done') {
40
+ return styles.stepDone;
41
+ }
42
+ if (status === 'running') {
43
+ return styles.stepRunning;
44
+ }
45
+ if (status === 'blocked') {
46
+ return styles.stepBlocked;
47
+ }
48
+ if (status === 'ready') {
49
+ return styles.stepReady;
50
+ }
51
+ return styles.stepUnknown;
52
+ }
53
+
54
+ function statusIcon(status: RoleStatus): string {
55
+ if (status === 'done') {
56
+ return '✓';
57
+ }
58
+ if (status === 'running') {
59
+ return '…';
60
+ }
61
+ if (status === 'blocked') {
62
+ return '!';
63
+ }
64
+ if (status === 'ready') {
65
+ return '○';
66
+ }
67
+ return '?';
68
+ }
69
+
70
+ export function AgentPipelineStepper({ status }: AgentPipelineStepperProps) {
71
+ const normalized: AgentPipelineStatus = {
72
+ planner: normalizeRoleStatus(status?.planner),
73
+ builder: normalizeRoleStatus(status?.builder),
74
+ qa: normalizeRoleStatus(status?.qa),
75
+ };
76
+
77
+ return (
78
+ <section className={styles.section}>
79
+ <h3 className={styles.sectionTitle}>Agent Pipeline</h3>
80
+ <ol className={styles.pipelineStepper} aria-label="Agent pipeline stepper">
81
+ {(['planner', 'builder', 'qa'] as const).map((role) => {
82
+ const current = normalized[role];
83
+ return (
84
+ <li key={role} className={`${styles.pipelineStep} ${statusClass(current)}`}>
85
+ <span
86
+ className={`${styles.pipelineIcon} ${current === 'running' ? styles.pipelineIconRunning : ''}`}
87
+ aria-hidden="true"
88
+ >
89
+ {statusIcon(current)}
90
+ </span>
91
+ <span>
92
+ <strong>{roleLabel(role)}</strong> <span>{statusLabel(current)}</span>
93
+ </span>
94
+ </li>
95
+ );
96
+ })}
97
+ </ol>
98
+ </section>
99
+ );
100
+ }
@@ -0,0 +1,58 @@
1
+ import { Fragment } from 'react';
2
+ import styles from '@/styles/dashboard.module.css';
3
+
4
+ interface DependencyEntry {
5
+ feature_id: string;
6
+ depends_on_unresolved: string[];
7
+ }
8
+
9
+ interface DependencyChainsProps {
10
+ chains: DependencyEntry[];
11
+ onSelectFeature: (featureId: string) => void;
12
+ }
13
+
14
+ export function DependencyChains({ chains, onSelectFeature }: DependencyChainsProps) {
15
+ return (
16
+ <section className={styles.section}>
17
+ <h2 className={styles.sectionTitle}>Dependency Chains</h2>
18
+ {chains.length === 0 ? (
19
+ <p className={styles.metaText}>No unresolved dependency chains.</p>
20
+ ) : (
21
+ <ul className={styles.planList}>
22
+ {chains.map((entry) => (
23
+ <li key={entry.feature_id} className={styles.planListItem}>
24
+ <button
25
+ type="button"
26
+ className={styles.listHeaderButton}
27
+ onClick={() => onSelectFeature(entry.feature_id)}
28
+ >
29
+ {entry.feature_id}
30
+ </button>
31
+ <span className={styles.metaText}> {' -> depends on -> '} </span>
32
+ {entry.depends_on_unresolved.length === 0 ? (
33
+ <span className={styles.metaText}>unknown</span>
34
+ ) : (
35
+ <span className={styles.inlineActions}>
36
+ {entry.depends_on_unresolved.map((dependency, index) => (
37
+ <Fragment key={dependency}>
38
+ <button
39
+ type="button"
40
+ className={styles.listHeaderButton}
41
+ onClick={() => onSelectFeature(dependency)}
42
+ >
43
+ {dependency}
44
+ </button>
45
+ {index < entry.depends_on_unresolved.length - 1 ? (
46
+ <span className={styles.metaText}>, </span>
47
+ ) : null}
48
+ </Fragment>
49
+ ))}
50
+ </span>
51
+ )}
52
+ </li>
53
+ ))}
54
+ </ul>
55
+ )}
56
+ </section>
57
+ );
58
+ }
@@ -1,14 +1,34 @@
1
- import { useEffect, useMemo, useState } from 'react';
2
- import { formatElapsedTime, projectQuery } from '@/lib/dashboard-utils.js';
3
- import type { FeatureDetail, FeatureLog, FeatureLogEntry } from '@/lib/types.js';
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ buildDashboardFeatureHref,
4
+ formatElapsedTime,
5
+ projectQuery,
6
+ } from '@/lib/dashboard-utils.js';
7
+ import type {
8
+ CostSummary,
9
+ FeatureDetail,
10
+ FeatureLog,
11
+ FeatureLogEntry,
12
+ QaTestIndex,
13
+ ReviewBrief,
14
+ } from '@/lib/types.js';
4
15
  import styles from '@/styles/dashboard.module.css';
16
+ import { AgentPipelineStepper } from '@/components/agent-pipeline-stepper';
5
17
  import { ConfirmationModal } from '@/components/confirmation-modal';
6
18
  import { DiffViewer } from '@/components/diff-viewer';
7
19
  import { EvidenceViewer } from '@/components/evidence-viewer';
20
+ import { FeatureCostPanel } from '@/components/feature-cost-panel';
8
21
  import { GateResults } from '@/components/gate-results';
22
+ import { GateStepDrilldown } from '@/components/gate-step-drilldown';
9
23
  import { HumanInputPanel } from '@/components/human-input-panel';
10
24
  import { PlanViewer } from '@/components/plan-viewer';
25
+ import { PlanRevisionTimeline } from '@/components/plan-revision-timeline';
26
+ import { PlanRiskAnnotations } from '@/components/plan-risk-annotations';
27
+ import { PlanScopeTree } from '@/components/plan-scope-tree';
11
28
  import { PrStatusCard } from '@/components/pr-status-card';
29
+ import { QaCoverageMap } from '@/components/qa-coverage-map';
30
+ import { ReviewBriefPanel } from '@/components/review-brief-panel';
31
+ import { VerificationSignals } from '@/components/verification-signals';
12
32
 
13
33
  type ReviewAction =
14
34
  | 'review.approve'
@@ -25,6 +45,9 @@ interface ActionResponse {
25
45
  interface DetailPanelProps {
26
46
  detail: FeatureDetail | null;
27
47
  project: string;
48
+ unresolvedDependencies?: string[];
49
+ flakySuspects?: string[];
50
+ isFocusView?: boolean;
28
51
  onClose: () => void;
29
52
  refreshDetail: (featureId: string) => Promise<void>;
30
53
  refreshStatus: () => Promise<void>;
@@ -51,6 +74,12 @@ const DEFAULT_HISTORY: HistoryState = {
51
74
  total: 0,
52
75
  };
53
76
 
77
+ interface ApiEnvelope<TData> {
78
+ ok: boolean;
79
+ data?: TData;
80
+ error?: { code?: string; message?: string; retryable?: boolean };
81
+ }
82
+
54
83
  async function callAction(
55
84
  action: ReviewAction,
56
85
  featureId: string,
@@ -68,6 +97,9 @@ async function callAction(
68
97
  export function DetailPanel({
69
98
  detail,
70
99
  project,
100
+ unresolvedDependencies = [],
101
+ flakySuspects = [],
102
+ isFocusView = false,
71
103
  onClose,
72
104
  refreshDetail,
73
105
  refreshStatus,
@@ -82,11 +114,18 @@ export function DetailPanel({
82
114
  const [history, setHistory] = useState<HistoryState>(DEFAULT_HISTORY);
83
115
  const [nowMs, setNowMs] = useState(Date.now());
84
116
  const [submitting, setSubmitting] = useState(false);
117
+ const [cost, setCost] = useState<CostSummary | null>(detail?.cost ?? null);
118
+ const [qaIndex, setQaIndex] = useState<QaTestIndex | null>(detail?.qa_test_index ?? null);
119
+ const [reviewBrief, setReviewBrief] = useState<ReviewBrief | null>(detail?.review_brief ?? null);
120
+ const [budgetLimitUsd, setBudgetLimitUsd] = useState<number | null>(null);
121
+ const costCacheRef = useRef<Map<string, { expiresAt: number; value: CostSummary | null }>>(
122
+ new Map(),
123
+ );
85
124
 
86
125
  useEffect(() => {
87
126
  const interval = setInterval(() => {
88
127
  setNowMs(Date.now());
89
- }, 1000);
128
+ }, 5000);
90
129
  return () => clearInterval(interval);
91
130
  }, []);
92
131
 
@@ -142,6 +181,126 @@ export function DetailPanel({
142
181
  };
143
182
  }, [detail, project]);
144
183
 
184
+ useEffect(() => {
185
+ let cancelled = false;
186
+ void (async () => {
187
+ try {
188
+ const response = await fetch(`/api/policy/budget${projectQuery(project)}`);
189
+ const body = (await response.json()) as ApiEnvelope<{
190
+ per_feature_limit_usd?: number | null;
191
+ }>;
192
+ if (cancelled) {
193
+ return;
194
+ }
195
+ setBudgetLimitUsd(
196
+ body.ok && typeof body.data?.per_feature_limit_usd === 'number'
197
+ ? body.data.per_feature_limit_usd
198
+ : null,
199
+ );
200
+ } catch {
201
+ if (!cancelled) {
202
+ setBudgetLimitUsd(null);
203
+ }
204
+ }
205
+ })();
206
+
207
+ return () => {
208
+ cancelled = true;
209
+ };
210
+ }, [project]);
211
+
212
+ useEffect(() => {
213
+ if (!detail) {
214
+ setCost(null);
215
+ setQaIndex(null);
216
+ setReviewBrief(null);
217
+ return;
218
+ }
219
+
220
+ const featureId = detail.feature.feature_id;
221
+ setCost(detail.cost ?? null);
222
+ setQaIndex(detail.qa_test_index ?? null);
223
+ setReviewBrief(detail.review_brief ?? null);
224
+
225
+ const controller = new AbortController();
226
+ let cancelled = false;
227
+
228
+ const fetchCost = async () => {
229
+ const cacheKey = `${project}:${featureId}`;
230
+ const cached = costCacheRef.current.get(cacheKey);
231
+ if (cached && cached.expiresAt > Date.now()) {
232
+ setCost(cached.value);
233
+ return;
234
+ }
235
+
236
+ try {
237
+ const response = await fetch(`/api/features/${featureId}/cost${projectQuery(project)}`, {
238
+ signal: controller.signal,
239
+ });
240
+ const body = (await response.json()) as ApiEnvelope<CostSummary>;
241
+ if (cancelled) {
242
+ return;
243
+ }
244
+ if (body.ok && body.data) {
245
+ setCost(body.data);
246
+ costCacheRef.current.set(cacheKey, {
247
+ expiresAt: Date.now() + 60_000,
248
+ value: body.data,
249
+ });
250
+ return;
251
+ }
252
+ setCost(null);
253
+ } catch {
254
+ if (!cancelled) {
255
+ setCost(null);
256
+ }
257
+ }
258
+ };
259
+
260
+ const fetchQaIndex = async () => {
261
+ try {
262
+ const response = await fetch(
263
+ `/api/features/${featureId}/test-index${projectQuery(project)}`,
264
+ { signal: controller.signal },
265
+ );
266
+ const body = (await response.json()) as ApiEnvelope<QaTestIndex>;
267
+ if (!cancelled) {
268
+ setQaIndex(body.ok && body.data ? body.data : null);
269
+ }
270
+ } catch {
271
+ if (!cancelled) {
272
+ setQaIndex(null);
273
+ }
274
+ }
275
+ };
276
+
277
+ const fetchReviewBrief = async () => {
278
+ try {
279
+ const response = await fetch(
280
+ `/api/features/${featureId}/review-brief${projectQuery(project)}`,
281
+ { signal: controller.signal },
282
+ );
283
+ const body = (await response.json()) as ApiEnvelope<ReviewBrief>;
284
+ if (!cancelled) {
285
+ setReviewBrief(body.ok && body.data ? body.data : null);
286
+ }
287
+ } catch {
288
+ if (!cancelled) {
289
+ setReviewBrief(null);
290
+ }
291
+ }
292
+ };
293
+
294
+ void fetchCost();
295
+ void fetchQaIndex();
296
+ void fetchReviewBrief();
297
+
298
+ return () => {
299
+ cancelled = true;
300
+ controller.abort();
301
+ };
302
+ }, [detail, project]);
303
+
145
304
  const phase = detail?.feature.phase ?? 'unknown';
146
305
 
147
306
  const shouldShowPlan = phase === 'planning' || phase === 'building';
@@ -235,6 +394,9 @@ export function DetailPanel({
235
394
  <h2 className={styles.panelTitle}>{detail.feature.feature_id}</h2>
236
395
  <p className={styles.metaText}>Phase: {phase}</p>
237
396
  <p className={styles.metaText}>Status: {detail.feature.status}</p>
397
+ {unresolvedDependencies.length > 0 ? (
398
+ <p className={styles.metaText}>Substate: Awaiting Dependencies</p>
399
+ ) : null}
238
400
  <p className={styles.metaText}>Branch: {detail.feature.branch ?? '-'}</p>
239
401
  <p className={styles.metaText}>Time in phase: {timeInPhase}</p>
240
402
  {detail.feature.activity_state === 'active' ? (
@@ -246,6 +408,50 @@ export function DetailPanel({
246
408
  </button>
247
409
  </header>
248
410
 
411
+ <div className={styles.inlineActions}>
412
+ <a
413
+ href={`/feature/${detail.feature.feature_id}${projectQuery(project)}`}
414
+ className={styles.linkButton}
415
+ >
416
+ Open Focus View
417
+ </a>
418
+ </div>
419
+
420
+ <AgentPipelineStepper status={detail.feature.role_status} />
421
+ <FeatureCostPanel cost={cost} budgetLimitUsd={budgetLimitUsd} />
422
+ {detail.feature.status === 'paused_budget' ? (
423
+ <div className={styles.banner}>Budget exhausted</div>
424
+ ) : null}
425
+ <VerificationSignals feature={detail.feature} plan={detail.plan} />
426
+ <PlanRevisionTimeline
427
+ plan={detail.plan}
428
+ featurePlanVersion={detail.feature.plan_version}
429
+ featureRevisionOf={detail.feature.revision_of}
430
+ featureRevisionReason={detail.feature.revision_reason}
431
+ />
432
+ <PlanRiskAnnotations plan={detail.plan} expandedByDefault={phase === 'ready_to_merge'} />
433
+ <PlanScopeTree plan={detail.plan} />
434
+ {unresolvedDependencies.length > 0 ? (
435
+ <div className={styles.section}>
436
+ <h3 className={styles.sectionTitle}>Unresolved Dependencies</h3>
437
+ <ul className={styles.planList}>
438
+ {unresolvedDependencies.map((dependency) => (
439
+ <li key={dependency} className={styles.planListItem}>
440
+ <a
441
+ className={styles.linkButton}
442
+ href={buildDashboardFeatureHref(dependency, project)}
443
+ >
444
+ {dependency}
445
+ </a>
446
+ </li>
447
+ ))}
448
+ </ul>
449
+ </div>
450
+ ) : null}
451
+ {phase === 'ready_to_merge' || phase === 'qa' ? (
452
+ <ReviewBriefPanel brief={reviewBrief} />
453
+ ) : null}
454
+
249
455
  {detail.feature.activity_state === 'waiting_input' ? (
250
456
  <HumanInputPanel
251
457
  featureId={detail.feature.feature_id}
@@ -262,6 +468,10 @@ export function DetailPanel({
262
468
 
263
469
  {shouldShowPlan ? <PlanViewer plan={detail.plan} /> : null}
264
470
  {shouldShowGates ? <GateResults feature={detail.feature} /> : null}
471
+ {shouldShowGates ? <GateStepDrilldown evidence={detail.gate_evidence} /> : null}
472
+ {phase === 'qa' || phase === 'ready_to_merge' ? (
473
+ <QaCoverageMap qaIndex={qaIndex} flakySuspects={flakySuspects} />
474
+ ) : null}
265
475
  {shouldShowDiff ? <DiffViewer diff={detail.diff} /> : null}
266
476
  {shouldShowEvidence ? (
267
477
  <EvidenceViewer
@@ -299,7 +509,7 @@ export function DetailPanel({
299
509
  ) : null}
300
510
 
301
511
  {phase === 'ready_to_merge' ? (
302
- <div className={styles.section}>
512
+ <div className={`${styles.section} ${isFocusView ? styles.stickyReviewFooter : ''}`}>
303
513
  <h3 className={styles.sectionTitle}>Review Actions</h3>
304
514
  <label className={styles.label} htmlFor="review-message">
305
515
  Review message
@@ -12,6 +12,7 @@ interface FeatureCardProps {
12
12
  feature: FeatureSummary;
13
13
  isSelected: boolean;
14
14
  isFocused: boolean;
15
+ awaitingDependencies?: boolean;
15
16
  onSelect: (featureId: string) => void;
16
17
  onFocus: (featureId: string) => void;
17
18
  }
@@ -86,6 +87,7 @@ export function FeatureCard({
86
87
  feature,
87
88
  isSelected,
88
89
  isFocused,
90
+ awaitingDependencies = false,
89
91
  onSelect,
90
92
  onFocus,
91
93
  }: FeatureCardProps) {
@@ -139,6 +141,9 @@ export function FeatureCard({
139
141
  <span className={`${styles.badge} ${styles.badgeNeutral}`}>
140
142
  activity {feature.activity_state ?? 'unknown'}
141
143
  </span>
144
+ {awaitingDependencies ? (
145
+ <span className={`${styles.badge} ${styles.badgePending}`}>Awaiting Dependencies</span>
146
+ ) : null}
142
147
  </div>
143
148
  </button>
144
149
  );
@@ -0,0 +1,66 @@
1
+ import type { CostSummary } from '@/lib/types.js';
2
+ import styles from '@/styles/dashboard.module.css';
3
+
4
+ interface FeatureCostPanelProps {
5
+ cost: CostSummary | null | undefined;
6
+ budgetLimitUsd?: number | null;
7
+ }
8
+
9
+ function budgetClass(ratio: number): string {
10
+ if (ratio >= 1) {
11
+ return styles.badgeFail;
12
+ }
13
+ if (ratio > 0.85) {
14
+ return styles.badgeFail;
15
+ }
16
+ if (ratio > 0.6) {
17
+ return styles.badgePending;
18
+ }
19
+ return styles.badgePass;
20
+ }
21
+
22
+ export function FeatureCostPanel({ cost, budgetLimitUsd = null }: FeatureCostPanelProps) {
23
+ if (!cost) {
24
+ return (
25
+ <div className={styles.section}>
26
+ <h3 className={styles.sectionTitle}>Feature Budget Meter</h3>
27
+ <p className={styles.metaText}>No cost data recorded yet.</p>
28
+ </div>
29
+ );
30
+ }
31
+
32
+ const hasLimit = typeof budgetLimitUsd === 'number' && budgetLimitUsd > 0;
33
+ const ratio = hasLimit ? cost.estimated_cost_usd / budgetLimitUsd : 0;
34
+ const stateLabel = hasLimit
35
+ ? ratio >= 1
36
+ ? 'Exceeded'
37
+ : ratio > 0.85
38
+ ? 'Critical'
39
+ : ratio > 0.6
40
+ ? 'Warning'
41
+ : 'Normal'
42
+ : 'No Limit';
43
+
44
+ return (
45
+ <details className={styles.section}>
46
+ <summary className={styles.sectionSummary}>Feature Budget Meter</summary>
47
+ <p className={styles.metaText}>Tokens: {cost.tokens_used.toLocaleString('en-US')}</p>
48
+ <p className={styles.metaText}>Estimated Cost: ${cost.estimated_cost_usd.toFixed(2)}</p>
49
+ <p className={styles.metaText}>Last Recorded: {cost.recorded_at ?? '-'}</p>
50
+ <div className={styles.badgeRow}>
51
+ <span className={`${styles.badge} ${hasLimit ? budgetClass(ratio) : styles.badgeNeutral}`}>
52
+ {stateLabel}
53
+ </span>
54
+ {hasLimit ? (
55
+ <span className={`${styles.badge} ${styles.badgeNeutral}`}>
56
+ {Math.min(999, Math.max(0, Math.round(ratio * 100)))}% of ${budgetLimitUsd.toFixed(2)}
57
+ </span>
58
+ ) : (
59
+ <span className={`${styles.badge} ${styles.badgeNeutral}`}>
60
+ No per-feature budget limit configured
61
+ </span>
62
+ )}
63
+ </div>
64
+ </details>
65
+ );
66
+ }