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.
- package/agentic/orchestrator/defaults/policy.defaults.yaml +1 -0
- package/agentic/orchestrator/policy.yaml +1 -0
- package/agentic/orchestrator/schemas/policy.schema.json +4 -0
- package/agentic/orchestrator/schemas/policy.user.schema.json +4 -0
- package/apps/control-plane/src/application/services/activity-monitor-service.ts +42 -0
- package/apps/control-plane/src/application/services/collision-queue-service.ts +48 -0
- package/apps/control-plane/src/application/services/cost-tracking-service.ts +80 -0
- package/apps/control-plane/src/application/services/feature-deletion-service.ts +57 -0
- package/apps/control-plane/src/application/services/feature-lifecycle-service.ts +65 -0
- package/apps/control-plane/src/application/services/feature-state-service.ts +87 -0
- package/apps/control-plane/src/application/services/gate-selection-service.ts +28 -0
- package/apps/control-plane/src/application/services/gate-service.ts +148 -0
- package/apps/control-plane/src/application/services/issue-tracker-service.ts +28 -0
- package/apps/control-plane/src/application/services/lock-service.ts +88 -0
- package/apps/control-plane/src/application/services/merge-service.ts +63 -0
- package/apps/control-plane/src/application/services/notifier-service.ts +70 -0
- package/apps/control-plane/src/application/services/patch-service.ts +95 -0
- package/apps/control-plane/src/application/services/performance-analytics-service.ts +90 -0
- package/apps/control-plane/src/application/services/plan-service.ts +184 -0
- package/apps/control-plane/src/application/services/pr-monitor-service.ts +58 -0
- package/apps/control-plane/src/application/services/qa-index-service.ts +68 -0
- package/apps/control-plane/src/application/services/reactions-service.ts +77 -0
- package/apps/control-plane/src/application/services/reporting-service.ts +79 -0
- package/apps/control-plane/src/application/services/run-lease-service.ts +79 -0
- package/apps/control-plane/src/application/tools/tool-router.ts +76 -0
- package/apps/control-plane/src/mcp/tool-runtime.ts +4 -0
- package/apps/control-plane/src/providers/providers.ts +96 -0
- package/apps/control-plane/src/providers/worker-provider-factory.ts +4 -0
- package/apps/control-plane/src/supervisor/build-wave-executor.ts +4 -0
- package/apps/control-plane/src/supervisor/planning-wave-executor.ts +4 -0
- package/apps/control-plane/src/supervisor/qa-wave-executor.ts +4 -0
- package/apps/control-plane/src/supervisor/run-coordinator.ts +4 -0
- package/apps/control-plane/src/supervisor/runtime.ts +4 -0
- package/apps/control-plane/src/supervisor/session-orchestrator.ts +4 -0
- package/apps/control-plane/test/dashboard-api.integration.spec.ts +310 -1
- package/apps/control-plane/test/dashboard-client.spec.ts +136 -1
- package/apps/control-plane/test/dashboard-live-feed.spec.ts +153 -0
- package/apps/control-plane/test/dashboard-ui-utils.spec.ts +132 -0
- package/apps/control-plane/test/mcp.spec.ts +96 -0
- package/apps/control-plane/test/tool-runtime.spec.ts +68 -0
- package/config/agentic/orchestrator/policy.yaml +1 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.d.ts +42 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.js +42 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/collision-queue-service.d.ts +48 -0
- package/dist/apps/control-plane/application/services/collision-queue-service.js +48 -0
- package/dist/apps/control-plane/application/services/collision-queue-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +79 -0
- package/dist/apps/control-plane/application/services/cost-tracking-service.js +76 -0
- package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +57 -0
- package/dist/apps/control-plane/application/services/feature-deletion-service.js +57 -0
- package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.d.ts +64 -0
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.js +61 -0
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-state-service.d.ts +86 -0
- package/dist/apps/control-plane/application/services/feature-state-service.js +83 -0
- package/dist/apps/control-plane/application/services/feature-state-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/gate-selection-service.d.ts +28 -0
- package/dist/apps/control-plane/application/services/gate-selection-service.js +28 -0
- package/dist/apps/control-plane/application/services/gate-selection-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/gate-service.d.ts +148 -0
- package/dist/apps/control-plane/application/services/gate-service.js +120 -0
- package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/issue-tracker-service.d.ts +28 -0
- package/dist/apps/control-plane/application/services/issue-tracker-service.js +28 -0
- package/dist/apps/control-plane/application/services/issue-tracker-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/lock-service.d.ts +88 -0
- package/dist/apps/control-plane/application/services/lock-service.js +64 -0
- package/dist/apps/control-plane/application/services/lock-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/merge-service.d.ts +62 -0
- package/dist/apps/control-plane/application/services/merge-service.js +59 -0
- package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/notifier-service.d.ts +70 -0
- package/dist/apps/control-plane/application/services/notifier-service.js +70 -0
- package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/patch-service.d.ts +94 -0
- package/dist/apps/control-plane/application/services/patch-service.js +91 -0
- package/dist/apps/control-plane/application/services/patch-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +89 -0
- package/dist/apps/control-plane/application/services/performance-analytics-service.js +86 -0
- package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/plan-service.d.ts +287 -0
- package/dist/apps/control-plane/application/services/plan-service.js +207 -0
- package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/pr-monitor-service.d.ts +58 -0
- package/dist/apps/control-plane/application/services/pr-monitor-service.js +58 -0
- package/dist/apps/control-plane/application/services/pr-monitor-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/qa-index-service.d.ts +67 -0
- package/dist/apps/control-plane/application/services/qa-index-service.js +64 -0
- package/dist/apps/control-plane/application/services/qa-index-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/reactions-service.d.ts +76 -0
- package/dist/apps/control-plane/application/services/reactions-service.js +73 -0
- package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/reporting-service.d.ts +78 -0
- package/dist/apps/control-plane/application/services/reporting-service.js +75 -0
- package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/run-lease-service.d.ts +79 -0
- package/dist/apps/control-plane/application/services/run-lease-service.js +79 -0
- package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
- package/dist/apps/control-plane/application/tools/tool-router.d.ts +76 -0
- package/dist/apps/control-plane/application/tools/tool-router.js +62 -0
- package/dist/apps/control-plane/application/tools/tool-router.js.map +1 -1
- package/dist/apps/control-plane/mcp/tool-runtime.d.ts +3 -0
- package/dist/apps/control-plane/mcp/tool-runtime.js +3 -0
- package/dist/apps/control-plane/mcp/tool-runtime.js.map +1 -1
- package/dist/apps/control-plane/providers/providers.d.ts +88 -0
- package/dist/apps/control-plane/providers/providers.js.map +1 -1
- package/dist/apps/control-plane/providers/worker-provider-factory.d.ts +3 -0
- package/dist/apps/control-plane/providers/worker-provider-factory.js.map +1 -1
- package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/build-wave-executor.js +3 -0
- package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/planning-wave-executor.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/planning-wave-executor.js +3 -0
- package/dist/apps/control-plane/supervisor/planning-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js +3 -0
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/run-coordinator.js +3 -0
- package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
- package/dist/apps/control-plane/supervisor/runtime.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/runtime.js +3 -0
- package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
- package/dist/apps/control-plane/supervisor/session-orchestrator.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/session-orchestrator.js +3 -0
- package/dist/apps/control-plane/supervisor/session-orchestrator.js.map +1 -1
- package/package.json +1 -1
- package/packages/web-dashboard/src/app/analytics/page.tsx +451 -0
- package/packages/web-dashboard/src/app/api/analytics/route.ts +62 -0
- package/packages/web-dashboard/src/app/api/collisions/route.ts +63 -0
- package/packages/web-dashboard/src/app/api/features/[id]/cost/route.ts +29 -0
- package/packages/web-dashboard/src/app/api/features/[id]/review-brief/route.ts +31 -0
- package/packages/web-dashboard/src/app/api/features/[id]/route.ts +5 -8
- package/packages/web-dashboard/src/app/api/features/[id]/test-index/route.ts +31 -0
- package/packages/web-dashboard/src/app/api/flaky/route.ts +20 -0
- package/packages/web-dashboard/src/app/api/policy/budget/route.ts +31 -0
- package/packages/web-dashboard/src/app/api/run/route.ts +162 -0
- package/packages/web-dashboard/src/app/api/status/route.ts +2 -5
- package/packages/web-dashboard/src/app/feature/[id]/page.tsx +112 -0
- package/packages/web-dashboard/src/app/page.tsx +379 -77
- package/packages/web-dashboard/src/components/agent-pipeline-stepper.tsx +100 -0
- package/packages/web-dashboard/src/components/dependency-chains.tsx +58 -0
- package/packages/web-dashboard/src/components/detail-panel.tsx +215 -5
- package/packages/web-dashboard/src/components/feature-card.tsx +5 -0
- package/packages/web-dashboard/src/components/feature-cost-panel.tsx +66 -0
- package/packages/web-dashboard/src/components/feature-list-view.tsx +336 -0
- package/packages/web-dashboard/src/components/gate-step-drilldown.tsx +92 -0
- package/packages/web-dashboard/src/components/kanban-board.tsx +3 -0
- package/packages/web-dashboard/src/components/live-event-feed.tsx +60 -0
- package/packages/web-dashboard/src/components/lock-resource-map.tsx +73 -0
- package/packages/web-dashboard/src/components/plan-revision-timeline.tsx +55 -0
- package/packages/web-dashboard/src/components/plan-risk-annotations.tsx +63 -0
- package/packages/web-dashboard/src/components/plan-scope-tree.tsx +285 -0
- package/packages/web-dashboard/src/components/qa-coverage-map.tsx +174 -0
- package/packages/web-dashboard/src/components/quick-launch-panel.tsx +70 -0
- package/packages/web-dashboard/src/components/review-brief-panel.tsx +75 -0
- package/packages/web-dashboard/src/components/run-health-panel.tsx +85 -0
- package/packages/web-dashboard/src/components/summary-bar.tsx +59 -2
- package/packages/web-dashboard/src/components/verification-signals.tsx +167 -0
- package/packages/web-dashboard/src/lib/analytics-utils.ts +3 -0
- package/packages/web-dashboard/src/lib/aop-client.ts +510 -11
- package/packages/web-dashboard/src/lib/api-envelope.ts +48 -0
- package/packages/web-dashboard/src/lib/authz-adapter.ts +33 -0
- package/packages/web-dashboard/src/lib/dashboard-utils.ts +304 -0
- package/packages/web-dashboard/src/lib/live-feed.ts +218 -0
- package/packages/web-dashboard/src/lib/policy-reader.ts +22 -0
- package/packages/web-dashboard/src/lib/types.ts +114 -0
- package/packages/web-dashboard/src/styles/dashboard.module.css +341 -1
- package/spec-files/{outstanding → completed}/agentic_orchestrator_dashboard_advanced_ux_spec.md +1 -1
- package/spec-files/outstanding/agentic_orchestrator_evidence_integrity_doctor_spec.md +561 -0
- package/spec-files/outstanding/agentic_orchestrator_kernel_simplification_spec.md +834 -0
- package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +12 -5
- package/spec-files/outstanding/agentic_orchestrator_observability_replay_spec.md +442 -0
- package/spec-files/progress.md +174 -1
|
@@ -16,6 +16,8 @@ export type QuickFilterKey =
|
|
|
16
16
|
| 'ready_to_merge'
|
|
17
17
|
| 'ci_failing';
|
|
18
18
|
|
|
19
|
+
type PhaseFilterValue = Exclude<FeatureSummary['phase'], undefined>;
|
|
20
|
+
|
|
19
21
|
export interface DashboardFilterState {
|
|
20
22
|
searchText: string;
|
|
21
23
|
phases: FeatureSummary['phase'][];
|
|
@@ -25,6 +27,45 @@ export interface DashboardFilterState {
|
|
|
25
27
|
quickFilter: QuickFilterKey | null;
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
export type DashboardViewMode = 'board' | 'list';
|
|
31
|
+
|
|
32
|
+
export interface DashboardRouteState {
|
|
33
|
+
project: string;
|
|
34
|
+
featureId: string | null;
|
|
35
|
+
viewMode: DashboardViewMode;
|
|
36
|
+
filters: DashboardFilterState;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface QuickLaunchAffordanceState {
|
|
40
|
+
available: boolean;
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
authorized: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ColumnFeatureIds {
|
|
46
|
+
phase: FeatureSummary['phase'];
|
|
47
|
+
featureIds: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const PHASE_VALUES: PhaseFilterValue[] = [
|
|
51
|
+
'planning',
|
|
52
|
+
'building',
|
|
53
|
+
'qa',
|
|
54
|
+
'ready_to_merge',
|
|
55
|
+
'merged',
|
|
56
|
+
'blocked',
|
|
57
|
+
'unknown',
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const ACTIVITY_VALUES: NonNullable<FeatureSummary['activity_state']>[] = [
|
|
61
|
+
'active',
|
|
62
|
+
'idle',
|
|
63
|
+
'waiting_input',
|
|
64
|
+
'blocked',
|
|
65
|
+
'exited',
|
|
66
|
+
'unknown',
|
|
67
|
+
];
|
|
68
|
+
|
|
28
69
|
export interface DashboardSummaryCounts {
|
|
29
70
|
active: number;
|
|
30
71
|
blocked: number;
|
|
@@ -33,6 +74,13 @@ export interface DashboardSummaryCounts {
|
|
|
33
74
|
ciFailing: number;
|
|
34
75
|
}
|
|
35
76
|
|
|
77
|
+
export interface SparklinePoint {
|
|
78
|
+
x: number;
|
|
79
|
+
y: number;
|
|
80
|
+
value: number;
|
|
81
|
+
index: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
36
84
|
export interface ParsedDiffLine {
|
|
37
85
|
raw: string;
|
|
38
86
|
type: 'add' | 'del' | 'context' | 'meta';
|
|
@@ -66,6 +114,262 @@ export function createDefaultFilters(): DashboardFilterState {
|
|
|
66
114
|
};
|
|
67
115
|
}
|
|
68
116
|
|
|
117
|
+
function parseListParam(value: string | null): string[] {
|
|
118
|
+
if (!value) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
return value
|
|
122
|
+
.split(',')
|
|
123
|
+
.map((item) => item.trim())
|
|
124
|
+
.filter((item) => item.length > 0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseBooleanFlag(value: string | null): boolean {
|
|
128
|
+
if (!value) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
const normalized = value.trim().toLowerCase();
|
|
132
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isQuickFilterKey(value: string): value is QuickFilterKey {
|
|
136
|
+
return (
|
|
137
|
+
value === 'active' ||
|
|
138
|
+
value === 'blocked' ||
|
|
139
|
+
value === 'awaiting_input' ||
|
|
140
|
+
value === 'ready_to_merge' ||
|
|
141
|
+
value === 'ci_failing'
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function parseFiltersFromSearchParams(params: URLSearchParams): DashboardFilterState {
|
|
146
|
+
const filters = createDefaultFilters();
|
|
147
|
+
filters.searchText = params.get('q') ?? '';
|
|
148
|
+
|
|
149
|
+
const phases = parseListParam(params.get('phase')).filter((item): item is PhaseFilterValue =>
|
|
150
|
+
PHASE_VALUES.includes(item as PhaseFilterValue),
|
|
151
|
+
);
|
|
152
|
+
const activities = parseListParam(params.get('activity')).filter(
|
|
153
|
+
(item): item is NonNullable<FeatureSummary['activity_state']> =>
|
|
154
|
+
ACTIVITY_VALUES.includes(item as NonNullable<FeatureSummary['activity_state']>),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
filters.phases = phases as FeatureSummary['phase'][];
|
|
158
|
+
filters.activities = activities;
|
|
159
|
+
filters.gateFailuresOnly = parseBooleanFlag(params.get('gate_failures'));
|
|
160
|
+
filters.ciFailuresOnly = parseBooleanFlag(params.get('ci_not_success'));
|
|
161
|
+
|
|
162
|
+
const quick = params.get('quick');
|
|
163
|
+
if (quick && isQuickFilterKey(quick)) {
|
|
164
|
+
filters.quickFilter = quick;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return filters;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function applyFiltersToSearchParams(
|
|
171
|
+
params: URLSearchParams,
|
|
172
|
+
filters: DashboardFilterState,
|
|
173
|
+
): void {
|
|
174
|
+
const searchText = filters.searchText.trim();
|
|
175
|
+
if (searchText.length > 0) {
|
|
176
|
+
params.set('q', searchText);
|
|
177
|
+
} else {
|
|
178
|
+
params.delete('q');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (filters.phases.length > 0) {
|
|
182
|
+
params.set('phase', filters.phases.join(','));
|
|
183
|
+
} else {
|
|
184
|
+
params.delete('phase');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (filters.activities.length > 0) {
|
|
188
|
+
params.set('activity', filters.activities.join(','));
|
|
189
|
+
} else {
|
|
190
|
+
params.delete('activity');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (filters.gateFailuresOnly) {
|
|
194
|
+
params.set('gate_failures', '1');
|
|
195
|
+
} else {
|
|
196
|
+
params.delete('gate_failures');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (filters.ciFailuresOnly) {
|
|
200
|
+
params.set('ci_not_success', '1');
|
|
201
|
+
} else {
|
|
202
|
+
params.delete('ci_not_success');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (filters.quickFilter) {
|
|
206
|
+
params.set('quick', filters.quickFilter);
|
|
207
|
+
} else {
|
|
208
|
+
params.delete('quick');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function parseFeatureId(value: string | null): string | null {
|
|
213
|
+
if (!value) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
const trimmed = value.trim();
|
|
217
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function parseDashboardRouteState(params: URLSearchParams): DashboardRouteState {
|
|
221
|
+
return {
|
|
222
|
+
project: params.get('project') ?? '',
|
|
223
|
+
featureId: parseFeatureId(params.get('feature')),
|
|
224
|
+
viewMode: params.get('view') === 'list' ? 'list' : 'board',
|
|
225
|
+
filters: parseFiltersFromSearchParams(params),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function applyDashboardRouteState(
|
|
230
|
+
params: URLSearchParams,
|
|
231
|
+
state: DashboardRouteState,
|
|
232
|
+
): void {
|
|
233
|
+
const project = state.project.trim();
|
|
234
|
+
const featureId = state.featureId?.trim() ?? '';
|
|
235
|
+
|
|
236
|
+
if (project.length > 0) {
|
|
237
|
+
params.set('project', project);
|
|
238
|
+
} else {
|
|
239
|
+
params.delete('project');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (featureId.length > 0) {
|
|
243
|
+
params.set('feature', featureId);
|
|
244
|
+
} else {
|
|
245
|
+
params.delete('feature');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (state.viewMode === 'list') {
|
|
249
|
+
params.set('view', 'list');
|
|
250
|
+
} else {
|
|
251
|
+
params.delete('view');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
applyFiltersToSearchParams(params, state.filters);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function buildDashboardFeatureHref(featureId: string, project: string): string {
|
|
258
|
+
const params = new URLSearchParams();
|
|
259
|
+
const nextFeature = featureId.trim();
|
|
260
|
+
const nextProject = project.trim();
|
|
261
|
+
|
|
262
|
+
if (nextProject.length > 0) {
|
|
263
|
+
params.set('project', nextProject);
|
|
264
|
+
}
|
|
265
|
+
if (nextFeature.length > 0) {
|
|
266
|
+
params.set('feature', nextFeature);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const query = params.toString();
|
|
270
|
+
return query.length > 0 ? `/?${query}` : '/';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function buildFeatureFocusHref(featureId: string, project: string): string {
|
|
274
|
+
const encodedFeatureId = encodeURIComponent(featureId.trim());
|
|
275
|
+
const params = new URLSearchParams();
|
|
276
|
+
const nextProject = project.trim();
|
|
277
|
+
if (nextProject.length > 0) {
|
|
278
|
+
params.set('project', nextProject);
|
|
279
|
+
}
|
|
280
|
+
const query = params.toString();
|
|
281
|
+
return query.length > 0
|
|
282
|
+
? `/feature/${encodedFeatureId}?${query}`
|
|
283
|
+
: `/feature/${encodedFeatureId}`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function isQuickLaunchAffordanceVisible(state: QuickLaunchAffordanceState): boolean {
|
|
287
|
+
return state.available && state.enabled && state.authorized;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function findCurrentColumnIndex(
|
|
291
|
+
columnFeatureIds: ColumnFeatureIds[],
|
|
292
|
+
focusedFeatureId: string | null,
|
|
293
|
+
): number {
|
|
294
|
+
if (focusedFeatureId) {
|
|
295
|
+
const focusedIndex = columnFeatureIds.findIndex((column) =>
|
|
296
|
+
column.featureIds.includes(focusedFeatureId),
|
|
297
|
+
);
|
|
298
|
+
if (focusedIndex >= 0) {
|
|
299
|
+
return focusedIndex;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return columnFeatureIds.findIndex((column) => column.featureIds.length > 0);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function getNextFocusedFeatureId(
|
|
306
|
+
columnFeatureIds: ColumnFeatureIds[],
|
|
307
|
+
focusedFeatureId: string | null,
|
|
308
|
+
key: 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight',
|
|
309
|
+
): string | null {
|
|
310
|
+
if (columnFeatureIds.length === 0) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const currentColumnIndex = findCurrentColumnIndex(columnFeatureIds, focusedFeatureId);
|
|
315
|
+
if (currentColumnIndex < 0) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const currentColumn = columnFeatureIds[currentColumnIndex];
|
|
320
|
+
if (currentColumn.featureIds.length === 0) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const existingRow = focusedFeatureId ? currentColumn.featureIds.indexOf(focusedFeatureId) : -1;
|
|
325
|
+
const currentRow = Math.max(0, existingRow);
|
|
326
|
+
const currentFeatureId = currentColumn.featureIds[currentRow] ?? null;
|
|
327
|
+
|
|
328
|
+
if (key === 'ArrowUp' || key === 'ArrowDown') {
|
|
329
|
+
const offset = key === 'ArrowUp' ? -1 : 1;
|
|
330
|
+
const nextRow = Math.min(currentColumn.featureIds.length - 1, Math.max(0, currentRow + offset));
|
|
331
|
+
return currentColumn.featureIds[nextRow] ?? currentFeatureId;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const direction = key === 'ArrowLeft' ? -1 : 1;
|
|
335
|
+
let cursor = currentColumnIndex;
|
|
336
|
+
while (cursor >= 0 && cursor < columnFeatureIds.length) {
|
|
337
|
+
cursor += direction;
|
|
338
|
+
if (cursor < 0 || cursor >= columnFeatureIds.length) {
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
const candidate = columnFeatureIds[cursor];
|
|
342
|
+
if (candidate.featureIds.length === 0) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const nextRow = Math.min(currentRow, candidate.featureIds.length - 1);
|
|
346
|
+
return candidate.featureIds[nextRow] ?? currentFeatureId;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return currentFeatureId;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function sparklinePath(values: number[], width: number, height: number): string {
|
|
353
|
+
const points = sparklinePoints(values, width, height);
|
|
354
|
+
return points
|
|
355
|
+
.map((point, index) => `${index === 0 ? 'M' : 'L'}${point.x.toFixed(2)} ${point.y.toFixed(2)}`)
|
|
356
|
+
.join(' ');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function sparklinePoints(values: number[], width: number, height: number): SparklinePoint[] {
|
|
360
|
+
if (values.length === 0) {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
const max = Math.max(1, ...values);
|
|
364
|
+
const step = values.length > 1 ? width / (values.length - 1) : width;
|
|
365
|
+
return values.map((value, index) => ({
|
|
366
|
+
x: index * step,
|
|
367
|
+
y: height - (value / max) * height,
|
|
368
|
+
value,
|
|
369
|
+
index,
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
|
|
69
373
|
export function applyQuickFilter(key: QuickFilterKey): DashboardFilterState {
|
|
70
374
|
const next = createDefaultFilters();
|
|
71
375
|
next.quickFilter = key;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { DashboardStatusPayload, FeedEvent, FeatureSummary, LockLease } from '@/lib/types.js';
|
|
2
|
+
|
|
3
|
+
const FEED_DEDUP_WINDOW_MS = 10_000;
|
|
4
|
+
const FEED_MAX_EVENTS = 100;
|
|
5
|
+
|
|
6
|
+
export interface LiveFeedState {
|
|
7
|
+
events: FeedEvent[];
|
|
8
|
+
paused: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type LiveFeedAction = { type: 'toggle_pause' } | { type: 'clear' };
|
|
12
|
+
|
|
13
|
+
function gateHasFailure(feature: FeatureSummary): boolean {
|
|
14
|
+
const gates = feature.gates;
|
|
15
|
+
if (!gates || typeof gates !== 'object') {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return [gates.fast, gates.full, gates.merge].some((value) => {
|
|
19
|
+
if (typeof value !== 'string') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const normalized = value.toLowerCase();
|
|
23
|
+
return normalized === 'fail' || normalized === 'failed' || normalized === 'error';
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function lockMapByResource(locks: LockLease[] | undefined): Map<string, LockLease> {
|
|
28
|
+
const map = new Map<string, LockLease>();
|
|
29
|
+
for (const lock of locks ?? []) {
|
|
30
|
+
map.set(lock.resource, lock);
|
|
31
|
+
}
|
|
32
|
+
return map;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildEvent(
|
|
36
|
+
index: number,
|
|
37
|
+
timestamp: string,
|
|
38
|
+
featureId: string,
|
|
39
|
+
type: FeedEvent['type'],
|
|
40
|
+
message: string,
|
|
41
|
+
severity: FeedEvent['severity'],
|
|
42
|
+
): FeedEvent {
|
|
43
|
+
return {
|
|
44
|
+
id: `${type}:${featureId}:${Date.parse(timestamp)}:${index}`,
|
|
45
|
+
timestamp,
|
|
46
|
+
feature_id: featureId,
|
|
47
|
+
type,
|
|
48
|
+
message,
|
|
49
|
+
severity,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function eventSignature(event: FeedEvent): string {
|
|
54
|
+
return `${event.feature_id}:${event.type}:${event.message}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function deriveFeedEventsFromSnapshotDiff(
|
|
58
|
+
previous: DashboardStatusPayload | null,
|
|
59
|
+
next: DashboardStatusPayload,
|
|
60
|
+
nowIso = new Date().toISOString(),
|
|
61
|
+
): FeedEvent[] {
|
|
62
|
+
if (!previous) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const events: FeedEvent[] = [];
|
|
67
|
+
const prevById = new Map(previous.features.map((feature) => [feature.feature_id, feature]));
|
|
68
|
+
const nextById = new Map(next.features.map((feature) => [feature.feature_id, feature]));
|
|
69
|
+
const allFeatureIds = new Set([...prevById.keys(), ...nextById.keys()]);
|
|
70
|
+
let index = 0;
|
|
71
|
+
|
|
72
|
+
for (const featureId of allFeatureIds) {
|
|
73
|
+
const prevFeature = prevById.get(featureId);
|
|
74
|
+
const nextFeature = nextById.get(featureId);
|
|
75
|
+
if (!prevFeature || !nextFeature) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (prevFeature.phase !== nextFeature.phase) {
|
|
80
|
+
const severity: FeedEvent['severity'] =
|
|
81
|
+
nextFeature.phase === 'blocked'
|
|
82
|
+
? 'error'
|
|
83
|
+
: nextFeature.phase === 'merged'
|
|
84
|
+
? 'success'
|
|
85
|
+
: 'info';
|
|
86
|
+
events.push(
|
|
87
|
+
buildEvent(
|
|
88
|
+
index++,
|
|
89
|
+
nowIso,
|
|
90
|
+
featureId,
|
|
91
|
+
'phase_change',
|
|
92
|
+
`${featureId} moved ${prevFeature.phase ?? 'unknown'} -> ${nextFeature.phase ?? 'unknown'}`,
|
|
93
|
+
severity,
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (JSON.stringify(prevFeature.gates ?? {}) !== JSON.stringify(nextFeature.gates ?? {})) {
|
|
99
|
+
events.push(
|
|
100
|
+
buildEvent(
|
|
101
|
+
index++,
|
|
102
|
+
nowIso,
|
|
103
|
+
featureId,
|
|
104
|
+
'gate_update',
|
|
105
|
+
`${featureId} gate status updated`,
|
|
106
|
+
gateHasFailure(nextFeature) ? 'warning' : 'info',
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (prevFeature.activity_state !== nextFeature.activity_state) {
|
|
112
|
+
events.push(
|
|
113
|
+
buildEvent(
|
|
114
|
+
index++,
|
|
115
|
+
nowIso,
|
|
116
|
+
featureId,
|
|
117
|
+
'activity_change',
|
|
118
|
+
`${featureId} activity ${prevFeature.activity_state ?? 'unknown'} -> ${nextFeature.activity_state ?? 'unknown'}`,
|
|
119
|
+
nextFeature.activity_state === 'blocked' ? 'warning' : 'info',
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const prevPr = prevFeature.pr ?? null;
|
|
125
|
+
const nextPr = nextFeature.pr ?? null;
|
|
126
|
+
if (JSON.stringify(prevPr) !== JSON.stringify(nextPr)) {
|
|
127
|
+
const ciStatus =
|
|
128
|
+
nextPr && typeof nextPr.ci_status === 'string' ? `CI ${nextPr.ci_status}` : 'PR updated';
|
|
129
|
+
events.push(
|
|
130
|
+
buildEvent(index++, nowIso, featureId, 'pr_update', `${featureId} ${ciStatus}`, 'info'),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const prevLocks = lockMapByResource(previous.lock_map);
|
|
136
|
+
const nextLocks = lockMapByResource(next.lock_map);
|
|
137
|
+
const allResources = new Set([...prevLocks.keys(), ...nextLocks.keys()]);
|
|
138
|
+
for (const resource of allResources) {
|
|
139
|
+
const prevLock = prevLocks.get(resource);
|
|
140
|
+
const nextLock = nextLocks.get(resource);
|
|
141
|
+
if (!nextLock || !prevLock) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (
|
|
145
|
+
prevLock.holder !== nextLock.holder ||
|
|
146
|
+
prevLock.is_stale !== nextLock.is_stale ||
|
|
147
|
+
prevLock.expires_at !== nextLock.expires_at
|
|
148
|
+
) {
|
|
149
|
+
events.push(
|
|
150
|
+
buildEvent(
|
|
151
|
+
index++,
|
|
152
|
+
nowIso,
|
|
153
|
+
nextLock.holder ?? 'system',
|
|
154
|
+
'lock_change',
|
|
155
|
+
`${resource} lock holder ${prevLock.holder ?? 'none'} -> ${nextLock.holder ?? 'none'}`,
|
|
156
|
+
nextLock.is_stale ? 'warning' : 'info',
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return events;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function appendFeedEvents(existing: FeedEvent[], incoming: FeedEvent[]): FeedEvent[] {
|
|
166
|
+
if (incoming.length === 0) {
|
|
167
|
+
return existing;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const latestSeenBySignature = new Map<string, number>();
|
|
171
|
+
for (const event of existing) {
|
|
172
|
+
const parsed = Date.parse(event.timestamp);
|
|
173
|
+
if (Number.isNaN(parsed)) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const signature = eventSignature(event);
|
|
177
|
+
const current = latestSeenBySignature.get(signature) ?? Number.MIN_SAFE_INTEGER;
|
|
178
|
+
if (parsed > current) {
|
|
179
|
+
latestSeenBySignature.set(signature, parsed);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const accepted: FeedEvent[] = [];
|
|
184
|
+
for (const event of incoming) {
|
|
185
|
+
const signature = eventSignature(event);
|
|
186
|
+
const eventTs = Date.parse(event.timestamp);
|
|
187
|
+
if (Number.isNaN(eventTs)) {
|
|
188
|
+
accepted.push(event);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const latestTs = latestSeenBySignature.get(signature);
|
|
192
|
+
if (latestTs != null && eventTs - latestTs < FEED_DEDUP_WINDOW_MS) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
latestSeenBySignature.set(signature, eventTs);
|
|
196
|
+
accepted.push(event);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (accepted.length === 0) {
|
|
200
|
+
return existing;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return [...accepted.reverse(), ...existing].slice(0, FEED_MAX_EVENTS);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function reduceLiveFeedState(state: LiveFeedState, action: LiveFeedAction): LiveFeedState {
|
|
207
|
+
if (action.type === 'toggle_pause') {
|
|
208
|
+
return {
|
|
209
|
+
...state,
|
|
210
|
+
paused: !state.paused,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
...state,
|
|
216
|
+
events: [],
|
|
217
|
+
};
|
|
218
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import YAML from 'yaml';
|
|
4
|
+
|
|
5
|
+
export async function readPolicyYaml(repoRoot: string): Promise<Record<string, unknown> | null> {
|
|
6
|
+
const candidates = [
|
|
7
|
+
path.join(repoRoot, 'config', 'agentic', 'orchestrator', 'policy.yaml'),
|
|
8
|
+
path.join(repoRoot, 'agentic', 'orchestrator', 'policy.yaml'),
|
|
9
|
+
];
|
|
10
|
+
for (const candidate of candidates) {
|
|
11
|
+
try {
|
|
12
|
+
const raw = await readFile(candidate, 'utf-8');
|
|
13
|
+
const parsed = YAML.parse(raw) as Record<string, unknown>;
|
|
14
|
+
if (parsed && typeof parsed === 'object') {
|
|
15
|
+
return parsed;
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
// continue to next path
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
@@ -1,9 +1,111 @@
|
|
|
1
|
+
export type RoleStatus = 'ready' | 'running' | 'blocked' | 'done' | 'unknown';
|
|
2
|
+
|
|
3
|
+
export interface AgentPipelineStatus {
|
|
4
|
+
planner: RoleStatus;
|
|
5
|
+
builder: RoleStatus;
|
|
6
|
+
qa: RoleStatus;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CostSummary {
|
|
10
|
+
feature_id: string;
|
|
11
|
+
tokens_used: number;
|
|
12
|
+
estimated_cost_usd: number;
|
|
13
|
+
recorded_at: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface QaTestIndexItem {
|
|
17
|
+
path: string;
|
|
18
|
+
status: 'pending' | 'running' | 'passed' | 'failed' | 'waived';
|
|
19
|
+
required_tests: string[];
|
|
20
|
+
last_run_at?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface QaTestIndex {
|
|
24
|
+
feature_id: string;
|
|
25
|
+
version: number;
|
|
26
|
+
items: QaTestIndexItem[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface LockLease {
|
|
30
|
+
resource: string;
|
|
31
|
+
holder: string | null;
|
|
32
|
+
expires_at: string | null;
|
|
33
|
+
is_stale: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ReviewBrief {
|
|
37
|
+
intent_summary: string;
|
|
38
|
+
scope_summary: string;
|
|
39
|
+
contract_risk_summary: string;
|
|
40
|
+
feasibility_score?: number;
|
|
41
|
+
feasibility_breakdown?: Record<string, number>;
|
|
42
|
+
gate_matrix: Record<string, string>;
|
|
43
|
+
unresolved_questions: string[];
|
|
44
|
+
evidence_refs: string[];
|
|
45
|
+
generated_at: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ProviderAnalytics {
|
|
49
|
+
provider: string;
|
|
50
|
+
model: string;
|
|
51
|
+
total_features: number;
|
|
52
|
+
success_count: number;
|
|
53
|
+
success_rate: number;
|
|
54
|
+
avg_retry_count: number;
|
|
55
|
+
avg_duration_ms: number;
|
|
56
|
+
avg_cost_usd: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface FeedEvent {
|
|
60
|
+
id: string;
|
|
61
|
+
timestamp: string;
|
|
62
|
+
feature_id: string;
|
|
63
|
+
type: 'phase_change' | 'gate_update' | 'activity_change' | 'pr_update' | 'lock_change';
|
|
64
|
+
message: string;
|
|
65
|
+
severity: 'info' | 'success' | 'warning' | 'error';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface GateStepResult {
|
|
69
|
+
name: string;
|
|
70
|
+
command: string;
|
|
71
|
+
status: 'pass' | 'fail' | 'unknown';
|
|
72
|
+
exit_code: number | null;
|
|
73
|
+
duration_ms: number | null;
|
|
74
|
+
log_path?: string | null;
|
|
75
|
+
output_tail?: string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface GateRunEvidence {
|
|
79
|
+
mode: string;
|
|
80
|
+
profile?: string | null;
|
|
81
|
+
overall: string;
|
|
82
|
+
finished_at?: string | null;
|
|
83
|
+
steps: GateStepResult[];
|
|
84
|
+
raw?: Record<string, unknown>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface RuntimeSession {
|
|
88
|
+
run_id?: string;
|
|
89
|
+
owner_instance_id?: string;
|
|
90
|
+
provider?: string | null;
|
|
91
|
+
model?: string | null;
|
|
92
|
+
started_at?: string | null;
|
|
93
|
+
last_heartbeat_at?: string | null;
|
|
94
|
+
lease_expires_at?: string | null;
|
|
95
|
+
orchestrator_epoch?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
1
98
|
export interface FeatureSummary {
|
|
2
99
|
id: string;
|
|
3
100
|
feature_id: string;
|
|
4
101
|
status: string;
|
|
5
102
|
phase?: 'planning' | 'building' | 'qa' | 'ready_to_merge' | 'merged' | 'blocked' | 'unknown';
|
|
6
103
|
plan_version?: number;
|
|
104
|
+
revision_of?: number | null;
|
|
105
|
+
revision_reason?: string | null;
|
|
106
|
+
role_status?: AgentPipelineStatus;
|
|
107
|
+
gate_retry_count?: number;
|
|
108
|
+
last_retry_at?: string | null;
|
|
7
109
|
locks_held?: string[];
|
|
8
110
|
gates?: {
|
|
9
111
|
fast?: string;
|
|
@@ -46,6 +148,9 @@ export interface FeaturesIndex {
|
|
|
46
148
|
blocked: string[];
|
|
47
149
|
merged: string[];
|
|
48
150
|
blocked_queue?: Array<{ feature_id: string; detected_at: string; collision_fingerprint: string }>;
|
|
151
|
+
lock_leases?: Record<string, { holder: string; lease_expires_at: string; stale?: boolean }>;
|
|
152
|
+
dep_blocked?: Array<{ feature_id: string; depends_on_unresolved: string[] }>;
|
|
153
|
+
runtime_sessions?: RuntimeSession;
|
|
49
154
|
}
|
|
50
155
|
|
|
51
156
|
export interface SSEEvent {
|
|
@@ -66,9 +171,18 @@ export interface FeatureDetail {
|
|
|
66
171
|
plan: Record<string, unknown> | null;
|
|
67
172
|
diff: string;
|
|
68
173
|
evidence: EvidenceArtifact[];
|
|
174
|
+
gate_evidence?: GateRunEvidence | null;
|
|
175
|
+
cost?: CostSummary | null;
|
|
176
|
+
qa_test_index?: QaTestIndex | null;
|
|
177
|
+
review_brief?: ReviewBrief | null;
|
|
69
178
|
}
|
|
70
179
|
|
|
71
180
|
export interface DashboardStatusPayload {
|
|
72
181
|
index: FeaturesIndex;
|
|
73
182
|
features: FeatureSummary[];
|
|
183
|
+
runtime?: RuntimeSession;
|
|
184
|
+
lock_map?: LockLease[];
|
|
185
|
+
dep_blocked?: Array<{ feature_id: string; depends_on_unresolved: string[] }>;
|
|
186
|
+
flaky_suspects?: string[];
|
|
187
|
+
metrics?: { total_cost_today_usd?: number; merge_histogram_14d?: number[] };
|
|
74
188
|
}
|