helixevo 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +28 -6
- package/dashboard/app/api/governance/route.ts +49 -0
- package/dashboard/app/api/run/route.ts +31 -4
- package/dashboard/app/api/topology-apply/route.ts +80 -0
- package/dashboard/app/api/topology-review/route.ts +51 -0
- package/dashboard/app/coevolution/client.tsx +560 -0
- package/dashboard/app/coevolution/page.tsx +9 -0
- package/dashboard/app/commands/page.tsx +24 -3
- package/dashboard/app/guide/page.tsx +12 -2
- package/dashboard/app/network/client.tsx +76 -2
- package/dashboard/app/page.tsx +46 -0
- package/dashboard/app/projects/client.tsx +83 -10
- package/dashboard/app/projects/page.tsx +8 -27
- package/dashboard/app/research/client.tsx +48 -8
- package/dashboard/app/topology/client.tsx +575 -0
- package/dashboard/app/topology/page.tsx +10 -0
- package/dashboard/components/sidebar-nav.tsx +2 -0
- package/dashboard/lib/data.ts +1301 -3
- package/dist/cli.js +1775 -50
- package/package.json +2 -2
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import { useRef, useState } from 'react'
|
|
5
|
+
import { ConsolePanel } from '@/components/console-panel'
|
|
6
|
+
import { MetricCard } from '@/components/metric-card'
|
|
7
|
+
import { PageHero } from '@/components/page-hero'
|
|
8
|
+
import { SectionFrame } from '@/components/section-frame'
|
|
9
|
+
|
|
10
|
+
type RunState = 'idle' | 'running' | 'success' | 'error' | 'stopped'
|
|
11
|
+
type CommandName = 'evolve' | 'research' | 'generalize'
|
|
12
|
+
type PressureInterventionType = 'research' | 'specialize' | 'evolve' | 'generalize' | 'manual-review'
|
|
13
|
+
|
|
14
|
+
type Summary = {
|
|
15
|
+
pressureSignals: { total: number; native: number; derived: number }
|
|
16
|
+
pressureInterventions: { total: number; started: number; completed: number; failed: number; dryRun: number }
|
|
17
|
+
pressureLifecycle: { open: number; inProgress: number; addressed: number; stale: number; highPriorityOpen: number }
|
|
18
|
+
pressureMotifs: { total: number; open: number; inProgress: number; addressed: number; stale: number; promotionReady: number }
|
|
19
|
+
governance: {
|
|
20
|
+
activeMode: 'conservative' | 'balanced' | 'exploration' | 'project-critical' | 'consolidation-focused' | 'transfer-focused'
|
|
21
|
+
derivedMode: 'conservative' | 'balanced' | 'exploration' | 'project-critical' | 'consolidation-focused' | 'transfer-focused'
|
|
22
|
+
source: 'derived' | 'operator-selected'
|
|
23
|
+
manualMode?: 'conservative' | 'balanced' | 'exploration' | 'project-critical' | 'consolidation-focused' | 'transfer-focused'
|
|
24
|
+
rationale: string[]
|
|
25
|
+
profile: { reviewThreshold: number; riskTolerance: number; plasticityBias: number; explanation: string }
|
|
26
|
+
}
|
|
27
|
+
topologyReviews: {
|
|
28
|
+
total: number
|
|
29
|
+
open: number
|
|
30
|
+
accepted: number
|
|
31
|
+
rejected: number
|
|
32
|
+
deferred: number
|
|
33
|
+
highPriorityOpen: number
|
|
34
|
+
generatedFromManualReview: number
|
|
35
|
+
byChangeType: { merge: number; split: number; promote: number; demote: number; rewire: number; prune: number; consolidate: number }
|
|
36
|
+
}
|
|
37
|
+
topologyExecution: {
|
|
38
|
+
plans: number
|
|
39
|
+
pending: number
|
|
40
|
+
prepared: number
|
|
41
|
+
applied: number
|
|
42
|
+
rolledBack: number
|
|
43
|
+
failed: number
|
|
44
|
+
safeToApply: number
|
|
45
|
+
prepareOnly: number
|
|
46
|
+
snapshots: number
|
|
47
|
+
artifacts: number
|
|
48
|
+
}
|
|
49
|
+
topProjects: { projectId: string; totalSignals: number; nativeSignals: number; derivedSignals: number; highPrioritySignals: number; topCapabilities: string[] }[]
|
|
50
|
+
responseProjects: { projectId: string; totalSignals: number; nativeSignals: number; derivedSignals: number; highPrioritySignals: number; topCapabilities: string[]; openSignals: number; inProgressSignals: number; addressedSignals: number; staleSignals: number; activeInterventions: number; recentInterventionTypes: PressureInterventionType[] }[]
|
|
51
|
+
crossProjectGapAreas: { area: string; projects: string[]; count: number }[]
|
|
52
|
+
activeRecommendations: { action: 'research' | 'specialize' | 'create'; count: number }[]
|
|
53
|
+
governedRoutes: { route: PressureInterventionType; total: number }[]
|
|
54
|
+
interventionLanes: { type: PressureInterventionType; total: number; resolving: number; exploratory: number; none: number }[]
|
|
55
|
+
backlog: {
|
|
56
|
+
id: string
|
|
57
|
+
kind: string
|
|
58
|
+
projectId?: string
|
|
59
|
+
capability?: string
|
|
60
|
+
description?: string
|
|
61
|
+
priority?: 'high' | 'medium' | 'low'
|
|
62
|
+
suggestedAction?: 'research' | 'specialize' | 'create'
|
|
63
|
+
lifecycle: 'open' | 'in-progress' | 'addressed' | 'stale'
|
|
64
|
+
responseSummary: string
|
|
65
|
+
detectedAt: string
|
|
66
|
+
lastActivityAt: string
|
|
67
|
+
interventionTypes: PressureInterventionType[]
|
|
68
|
+
routeRecommendation?: {
|
|
69
|
+
route: PressureInterventionType
|
|
70
|
+
scope: 'local' | 'project' | 'network'
|
|
71
|
+
confidence: number
|
|
72
|
+
governanceMode: Summary['governance']['activeMode']
|
|
73
|
+
triggeredBy: string
|
|
74
|
+
reasons: string[]
|
|
75
|
+
}
|
|
76
|
+
}[]
|
|
77
|
+
promoteQueue: {
|
|
78
|
+
id: string
|
|
79
|
+
key: string
|
|
80
|
+
kind: string
|
|
81
|
+
capability?: string
|
|
82
|
+
description: string
|
|
83
|
+
projectIds: string[]
|
|
84
|
+
recurrenceCount: number
|
|
85
|
+
highPriorityCount: number
|
|
86
|
+
lifecycle: 'open' | 'in-progress' | 'addressed' | 'stale'
|
|
87
|
+
responseSummary: string
|
|
88
|
+
lastActivityAt: string
|
|
89
|
+
interventionTypes: PressureInterventionType[]
|
|
90
|
+
suggestedRoute: {
|
|
91
|
+
route: PressureInterventionType
|
|
92
|
+
scope: 'local' | 'project' | 'network'
|
|
93
|
+
confidence: number
|
|
94
|
+
governanceMode: Summary['governance']['activeMode']
|
|
95
|
+
triggeredBy: string
|
|
96
|
+
reasons: string[]
|
|
97
|
+
}
|
|
98
|
+
}[]
|
|
99
|
+
recentAddressed: { id: string; kind: string; projectId?: string; capability?: string; description?: string; addressedAt?: string; responseSummary: string; interventionTypes: PressureInterventionType[] }[]
|
|
100
|
+
recentInterventions: { id: string; interventionType: PressureInterventionType; status: 'started' | 'completed' | 'failed' | 'dry-run'; impact: 'exploratory' | 'structural' | 'resolving' | 'none'; projectId?: string; capabilities?: string[]; reasonSummary: string; completedAt?: string; createdAt: string }[]
|
|
101
|
+
recentTransfers: { id: string; timestamp: string; transferType: 'specialist-to-generalist' | 'generalist-to-project' | 'cross-project' | 'domain-transfer'; targetSkillIds?: string[]; capabilityIds?: string[]; motifIds?: string[]; status?: 'candidate' | 'realized' | 'rejected'; summary?: string }[]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface Props {
|
|
105
|
+
summary: Summary
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function consoleTone(state: RunState): 'neutral' | 'green' | 'red' | 'yellow' {
|
|
109
|
+
if (state === 'success') return 'green'
|
|
110
|
+
if (state === 'error') return 'red'
|
|
111
|
+
if (state === 'stopped') return 'yellow'
|
|
112
|
+
return 'neutral'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function toneForLifecycle(lifecycle: 'open' | 'in-progress' | 'addressed' | 'stale') {
|
|
116
|
+
if (lifecycle === 'addressed') return 'green'
|
|
117
|
+
if (lifecycle === 'in-progress') return 'blue'
|
|
118
|
+
if (lifecycle === 'stale') return 'gray'
|
|
119
|
+
return 'yellow'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function toneForRoute(route: PressureInterventionType) {
|
|
123
|
+
if (route === 'generalize') return 'purple'
|
|
124
|
+
if (route === 'evolve') return 'green'
|
|
125
|
+
if (route === 'specialize') return 'blue'
|
|
126
|
+
if (route === 'manual-review') return 'yellow'
|
|
127
|
+
return 'gray'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function toneForMode(mode: Summary['governance']['activeMode']): 'blue' | 'green' | 'purple' | 'yellow' | 'neutral' {
|
|
131
|
+
if (mode === 'transfer-focused') return 'purple'
|
|
132
|
+
if (mode === 'project-critical') return 'yellow'
|
|
133
|
+
if (mode === 'exploration') return 'blue'
|
|
134
|
+
if (mode === 'conservative') return 'neutral'
|
|
135
|
+
if (mode === 'consolidation-focused') return 'green'
|
|
136
|
+
return 'neutral'
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function formatDate(value: string | undefined) {
|
|
140
|
+
if (!value) return '—'
|
|
141
|
+
return new Date(value).toLocaleString()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function formatMode(mode: Summary['governance']['activeMode']) {
|
|
145
|
+
return mode.split('-').join(' ')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default function CoEvolutionClient({ summary }: Props) {
|
|
149
|
+
const [runState, setRunState] = useState<RunState>('idle')
|
|
150
|
+
const [activeCommand, setActiveCommand] = useState<CommandName | null>(null)
|
|
151
|
+
const [output, setOutput] = useState('')
|
|
152
|
+
const abortRef = useRef<AbortController | null>(null)
|
|
153
|
+
const outputRef = useRef<HTMLPreElement | null>(null)
|
|
154
|
+
|
|
155
|
+
const handleRun = async (command: CommandName) => {
|
|
156
|
+
setActiveCommand(command)
|
|
157
|
+
setRunState('running')
|
|
158
|
+
setOutput('')
|
|
159
|
+
const controller = new AbortController()
|
|
160
|
+
abortRef.current = controller
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const res = await fetch('/api/run', {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers: { 'Content-Type': 'application/json' },
|
|
166
|
+
body: JSON.stringify({ command }),
|
|
167
|
+
signal: controller.signal,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
if (!res.body) {
|
|
171
|
+
setOutput('No response body')
|
|
172
|
+
setRunState('error')
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const reader = res.body.getReader()
|
|
177
|
+
const decoder = new TextDecoder()
|
|
178
|
+
let sseBuffer = ''
|
|
179
|
+
|
|
180
|
+
while (true) {
|
|
181
|
+
const { done, value } = await reader.read()
|
|
182
|
+
if (done) break
|
|
183
|
+
|
|
184
|
+
sseBuffer += decoder.decode(value, { stream: true })
|
|
185
|
+
const events = sseBuffer.split('\n\n')
|
|
186
|
+
sseBuffer = events.pop() ?? ''
|
|
187
|
+
|
|
188
|
+
for (const event of events) {
|
|
189
|
+
const lines = event.split('\n')
|
|
190
|
+
let eventType = ''
|
|
191
|
+
let eventData = ''
|
|
192
|
+
|
|
193
|
+
for (const line of lines) {
|
|
194
|
+
if (line.startsWith('event: ')) eventType = line.slice(7)
|
|
195
|
+
if (line.startsWith('data: ')) eventData = line.slice(6)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (eventType === 'output' && eventData) {
|
|
199
|
+
try {
|
|
200
|
+
const text = JSON.parse(eventData) as string
|
|
201
|
+
setOutput((prev) => prev + text)
|
|
202
|
+
setTimeout(() => {
|
|
203
|
+
if (outputRef.current) outputRef.current.scrollTop = outputRef.current.scrollHeight
|
|
204
|
+
}, 10)
|
|
205
|
+
} catch {}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (eventType === 'done' && eventData) {
|
|
209
|
+
try {
|
|
210
|
+
const result = JSON.parse(JSON.parse(eventData) as string) as { success: boolean; stopped?: boolean }
|
|
211
|
+
setRunState(result.stopped ? 'stopped' : result.success ? 'success' : 'error')
|
|
212
|
+
} catch {
|
|
213
|
+
setRunState('success')
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
setRunState((prev) => prev === 'running' ? 'success' : prev)
|
|
220
|
+
} catch (err: unknown) {
|
|
221
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
222
|
+
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
223
|
+
setOutput((prev) => prev + '\n\n[Stopped by user]')
|
|
224
|
+
setRunState('stopped')
|
|
225
|
+
} else {
|
|
226
|
+
setOutput((prev) => prev || 'Network error')
|
|
227
|
+
setRunState('error')
|
|
228
|
+
}
|
|
229
|
+
} finally {
|
|
230
|
+
abortRef.current = null
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const handleStop = async () => {
|
|
235
|
+
if (abortRef.current) abortRef.current.abort()
|
|
236
|
+
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<div style={{ display: 'grid', gap: 22 }}>
|
|
241
|
+
<PageHero
|
|
242
|
+
eyebrow="Response loop"
|
|
243
|
+
title="Co-Evolution Control"
|
|
244
|
+
description="Route adaptation under explicit governance, see which recurring motifs should be promoted, and track how local pressure turns into reusable network knowledge."
|
|
245
|
+
chips={[
|
|
246
|
+
{ label: `${summary.pressureLifecycle.open} open`, tone: summary.pressureLifecycle.open > 0 ? 'yellow' : 'green' },
|
|
247
|
+
{ label: `${summary.pressureMotifs.promotionReady} promotion-ready motifs`, tone: summary.pressureMotifs.promotionReady > 0 ? 'purple' : 'neutral' },
|
|
248
|
+
{ label: `${summary.topologyReviews.open} topology reviews`, tone: summary.topologyReviews.open > 0 ? 'yellow' : 'green' },
|
|
249
|
+
{ label: `${summary.recentTransfers.length} recent transfers`, tone: summary.recentTransfers.length > 0 ? 'green' : 'neutral' },
|
|
250
|
+
{ label: formatMode(summary.governance.activeMode), tone: toneForMode(summary.governance.activeMode) },
|
|
251
|
+
]}
|
|
252
|
+
actions={
|
|
253
|
+
<div style={{ display: 'grid', gap: 12 }}>
|
|
254
|
+
<div className="hero-note-card">
|
|
255
|
+
<div className="hero-note-label">Active governance</div>
|
|
256
|
+
<div className="hero-note-title">{formatMode(summary.governance.activeMode)}</div>
|
|
257
|
+
<div className="hero-note-copy">{summary.governance.rationale[0] ?? 'Governed routing keeps local fixes and reusable promotion in balance.'}</div>
|
|
258
|
+
<div style={{ marginTop: 8, display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
|
259
|
+
<span className="badge badge-gray">source: {summary.governance.source}</span>
|
|
260
|
+
<span className="badge badge-gray">review threshold {(summary.governance.profile.reviewThreshold * 100).toFixed(0)}%</span>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', justifyContent: 'flex-end' }}>
|
|
264
|
+
<button onClick={() => handleRun('research')} disabled={runState === 'running'} className="badge badge-blue" style={{ border: 'none', cursor: 'pointer' }}>Run research</button>
|
|
265
|
+
<button onClick={() => handleRun('evolve')} disabled={runState === 'running'} className="badge badge-green" style={{ border: 'none', cursor: 'pointer' }}>Run evolve</button>
|
|
266
|
+
<button onClick={() => handleRun('generalize')} disabled={runState === 'running'} className="badge badge-purple" style={{ border: 'none', cursor: 'pointer' }}>Run generalize</button>
|
|
267
|
+
<Link href="/topology" className="badge badge-gray">Open topology</Link>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
}
|
|
271
|
+
/>
|
|
272
|
+
|
|
273
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 16 }}>
|
|
274
|
+
<MetricCard label="Open pressure" value={summary.pressureLifecycle.open} sublabel={`${summary.pressureLifecycle.highPriorityOpen} high-priority waiting for response`} tone={summary.pressureLifecycle.open > 0 ? 'yellow' : 'green'} icon="!" />
|
|
275
|
+
<MetricCard label="Promotion-ready motifs" value={summary.pressureMotifs.promotionReady} sublabel={`${summary.pressureMotifs.total} recurring motifs tracked`} tone="purple" icon="⇄" />
|
|
276
|
+
<MetricCard label="Topology reviews" value={summary.topologyReviews.open} sublabel={`${summary.topologyReviews.accepted} accepted • ${summary.topologyReviews.deferred} deferred`} tone={summary.topologyReviews.open > 0 ? 'yellow' : 'green'} icon="◇" />
|
|
277
|
+
<MetricCard label="Prepared topology" value={summary.topologyExecution.prepared} sublabel={`${summary.topologyExecution.applied} applied • ${summary.topologyExecution.rolledBack} rolled back`} tone={summary.topologyExecution.prepared > 0 ? 'blue' : summary.topologyExecution.applied > 0 ? 'green' : 'neutral'} icon="↑" />
|
|
278
|
+
<MetricCard label="Recorded interventions" value={summary.pressureInterventions.total} sublabel={`${summary.pressureInterventions.completed} completed • ${summary.pressureInterventions.dryRun} dry-run`} tone="blue" icon="↺" />
|
|
279
|
+
<MetricCard label="Realized transfers" value={summary.recentTransfers.filter((event) => event.status === 'realized').length} sublabel={`${summary.pressureMotifs.addressed} motifs now addressed`} tone="green" icon="↑" />
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<SectionFrame
|
|
283
|
+
eyebrow="Topology control"
|
|
284
|
+
title="Structural review can now progress into execution"
|
|
285
|
+
description="Mixed-signal manual-review routes, graph optimize candidates, and transfer-backed promotion opportunities now persist into a dedicated topology queue and can progress into prepared or applied structural execution."
|
|
286
|
+
actions={<Link href="/topology" className="badge badge-gray">Open topology control</Link>}
|
|
287
|
+
>
|
|
288
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 16 }}>
|
|
289
|
+
<div className="metric-card metric-card-blue">
|
|
290
|
+
<div className="metric-card-header">
|
|
291
|
+
<div>
|
|
292
|
+
<div className="metric-card-label">Open structural review</div>
|
|
293
|
+
<div className="metric-card-value">{summary.topologyReviews.open}</div>
|
|
294
|
+
</div>
|
|
295
|
+
<div className="metric-card-icon">⇄</div>
|
|
296
|
+
</div>
|
|
297
|
+
<div className="metric-card-sublabel">{summary.topologyReviews.highPriorityOpen} high-priority items waiting for decision</div>
|
|
298
|
+
</div>
|
|
299
|
+
<div className="metric-card metric-card-purple">
|
|
300
|
+
<div className="metric-card-header">
|
|
301
|
+
<div>
|
|
302
|
+
<div className="metric-card-label">Manual-route backlog</div>
|
|
303
|
+
<div className="metric-card-value">{summary.topologyReviews.generatedFromManualReview}</div>
|
|
304
|
+
</div>
|
|
305
|
+
<div className="metric-card-icon">!</div>
|
|
306
|
+
</div>
|
|
307
|
+
<div className="metric-card-sublabel">pressure routed into review instead of another blind automatic pass</div>
|
|
308
|
+
</div>
|
|
309
|
+
<div className="metric-card metric-card-green">
|
|
310
|
+
<div className="metric-card-header">
|
|
311
|
+
<div>
|
|
312
|
+
<div className="metric-card-label">Prepared / applied</div>
|
|
313
|
+
<div className="metric-card-value">{summary.topologyExecution.prepared + summary.topologyExecution.applied}</div>
|
|
314
|
+
</div>
|
|
315
|
+
<div className="metric-card-icon">✓</div>
|
|
316
|
+
</div>
|
|
317
|
+
<div className="metric-card-sublabel">{summary.topologyExecution.prepared} prepared • {summary.topologyExecution.applied} applied • {summary.topologyExecution.snapshots} snapshots</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
</SectionFrame>
|
|
321
|
+
|
|
322
|
+
<div className="grid-2">
|
|
323
|
+
<SectionFrame
|
|
324
|
+
eyebrow="Governance"
|
|
325
|
+
title="Why the loop is routing this way"
|
|
326
|
+
description="Milestone 5 turns routing into an interpretable layer: you can see the current governance bias and which response paths dominate the backlog."
|
|
327
|
+
>
|
|
328
|
+
<div style={{ display: 'grid', gap: 14 }}>
|
|
329
|
+
<div style={{ padding: '14px 16px', borderRadius: 18, background: 'rgba(255,255,255,0.72)', border: '1px solid var(--border)' }}>
|
|
330
|
+
<div className="section-label" style={{ marginBottom: 8 }}>Mode rationale</div>
|
|
331
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 10 }}>
|
|
332
|
+
<span className={`hero-chip hero-chip-${toneForMode(summary.governance.activeMode)}`}>{formatMode(summary.governance.activeMode)}</span>
|
|
333
|
+
<span className="badge badge-gray">{summary.pressureMotifs.total} motifs</span>
|
|
334
|
+
<span className="badge badge-gray">{summary.governedRoutes.length} routed lanes visible</span>
|
|
335
|
+
</div>
|
|
336
|
+
<div style={{ display: 'grid', gap: 6 }}>
|
|
337
|
+
{summary.governance.rationale.map((reason, index) => (
|
|
338
|
+
<div key={`${reason}-${index}`} className="signal-text">• {reason}</div>
|
|
339
|
+
))}
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
344
|
+
{summary.governedRoutes.length > 0 ? summary.governedRoutes.map((entry) => (
|
|
345
|
+
<span key={entry.route} className={`hero-chip hero-chip-${toneForRoute(entry.route)}`}>
|
|
346
|
+
{entry.route} × {entry.total}
|
|
347
|
+
</span>
|
|
348
|
+
)) : <span className="badge badge-gray">No governed route counts yet</span>}
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
</SectionFrame>
|
|
352
|
+
|
|
353
|
+
<SectionFrame
|
|
354
|
+
eyebrow="Promotion queue"
|
|
355
|
+
title="Recurring motifs that should generalize"
|
|
356
|
+
description="These are the strongest current candidates for promoting repeated local pressure into reusable network knowledge."
|
|
357
|
+
actions={<Link href="/network" className="badge badge-gray">Open network</Link>}
|
|
358
|
+
>
|
|
359
|
+
<div className="summary-list">
|
|
360
|
+
{summary.promoteQueue.length > 0 ? summary.promoteQueue.map((motif) => (
|
|
361
|
+
<div key={motif.id} className="summary-row" style={{ alignItems: 'flex-start' }}>
|
|
362
|
+
<div className="summary-row-main">
|
|
363
|
+
<div className="summary-row-title">{motif.capability ?? motif.kind}</div>
|
|
364
|
+
<div className="summary-row-meta">{motif.projectIds.length} projects • {motif.recurrenceCount} recurring signals • {motif.responseSummary}</div>
|
|
365
|
+
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 8 }}>
|
|
366
|
+
<span className={`badge badge-${toneForLifecycle(motif.lifecycle)}`}>{motif.lifecycle}</span>
|
|
367
|
+
<span className="badge badge-purple">recommend {motif.suggestedRoute.route}</span>
|
|
368
|
+
<span className="badge badge-gray">confidence {(motif.suggestedRoute.confidence * 100).toFixed(0)}%</span>
|
|
369
|
+
{motif.projectIds.slice(0, 3).map((projectId) => <span key={`${motif.id}-${projectId}`} className="badge badge-gray">{projectId}</span>)}
|
|
370
|
+
</div>
|
|
371
|
+
<div style={{ display: 'grid', gap: 4, marginTop: 10 }}>
|
|
372
|
+
{motif.suggestedRoute.reasons.slice(0, 2).map((reason, index) => (
|
|
373
|
+
<div key={`${motif.id}-reason-${index}`} className="signal-text">• {reason}</div>
|
|
374
|
+
))}
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
)) : (
|
|
379
|
+
<div className="empty-state" style={{ padding: 24 }}>
|
|
380
|
+
<div className="empty-state-title">No promotion-ready motifs yet</div>
|
|
381
|
+
<div className="empty-state-desc">Recurring cross-project demand will appear here once the same pressure region repeats often enough to justify generalization.</div>
|
|
382
|
+
</div>
|
|
383
|
+
)}
|
|
384
|
+
</div>
|
|
385
|
+
</SectionFrame>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
<SectionFrame
|
|
389
|
+
eyebrow="Response lanes"
|
|
390
|
+
title="Intervention mix"
|
|
391
|
+
description="See which response modes are carrying the loop right now, and whether they are producing exploratory versus resolving work."
|
|
392
|
+
>
|
|
393
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))', gap: 16 }}>
|
|
394
|
+
{summary.interventionLanes.length > 0 ? summary.interventionLanes.map((lane) => (
|
|
395
|
+
<div key={lane.type} className={`metric-card metric-card-${toneForRoute(lane.type)}`}>
|
|
396
|
+
<div className="metric-card-header">
|
|
397
|
+
<div>
|
|
398
|
+
<div className="metric-card-label">{lane.type}</div>
|
|
399
|
+
<div className="metric-card-value">{lane.total}</div>
|
|
400
|
+
</div>
|
|
401
|
+
<div className="metric-card-icon">⇄</div>
|
|
402
|
+
</div>
|
|
403
|
+
<div className="metric-card-sublabel">{lane.resolving} resolving • {lane.exploratory} exploratory • {lane.none} non-resolving</div>
|
|
404
|
+
</div>
|
|
405
|
+
)) : (
|
|
406
|
+
<div className="empty-state" style={{ gridColumn: '1 / -1', padding: 24 }}>
|
|
407
|
+
<div className="empty-state-title">No intervention records yet</div>
|
|
408
|
+
<div className="empty-state-desc">Run research, evolve, or generalize to start populating the governed response ledger.</div>
|
|
409
|
+
</div>
|
|
410
|
+
)}
|
|
411
|
+
</div>
|
|
412
|
+
</SectionFrame>
|
|
413
|
+
|
|
414
|
+
{runState !== 'idle' ? (
|
|
415
|
+
<ConsolePanel
|
|
416
|
+
tone={consoleTone(runState)}
|
|
417
|
+
title={<span>{runState === 'running' ? `Running ${activeCommand}…` : runState === 'success' ? `${activeCommand} completed` : runState === 'stopped' ? `${activeCommand} stopped` : `${activeCommand} failed`}</span>}
|
|
418
|
+
actions={runState === 'running' ? <button onClick={handleStop} className="badge badge-red" style={{ border: 'none', cursor: 'pointer' }}>Stop</button> : <button onClick={() => window.location.reload()} className="badge badge-gray" style={{ border: 'none', cursor: 'pointer' }}>Refresh data</button>}
|
|
419
|
+
>
|
|
420
|
+
<pre ref={outputRef} style={{ margin: 0, padding: '16px 18px', fontSize: 11.5, lineHeight: 1.62, color: 'var(--text-secondary)', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
421
|
+
{output || 'Waiting for command output…'}
|
|
422
|
+
</pre>
|
|
423
|
+
</ConsolePanel>
|
|
424
|
+
) : null}
|
|
425
|
+
|
|
426
|
+
<div className="grid-2">
|
|
427
|
+
<SectionFrame
|
|
428
|
+
eyebrow="Project hotspots"
|
|
429
|
+
title="Where the loop is under the most pressure"
|
|
430
|
+
description="Projects with the heaviest open or in-progress response demand."
|
|
431
|
+
actions={<Link href="/projects" className="badge badge-gray">Open projects</Link>}
|
|
432
|
+
>
|
|
433
|
+
<div className="summary-list">
|
|
434
|
+
{summary.responseProjects.length > 0 ? summary.responseProjects.map((project) => (
|
|
435
|
+
<div key={project.projectId} className="summary-row" style={{ alignItems: 'flex-start' }}>
|
|
436
|
+
<div className="summary-row-main">
|
|
437
|
+
<div className="summary-row-title">{project.projectId}</div>
|
|
438
|
+
<div className="summary-row-meta">{project.totalSignals} signals • {project.highPrioritySignals} high-priority • {project.activeInterventions} active interventions</div>
|
|
439
|
+
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 8 }}>
|
|
440
|
+
<span className="badge badge-yellow">{project.openSignals} open</span>
|
|
441
|
+
<span className="badge badge-blue">{project.inProgressSignals} in progress</span>
|
|
442
|
+
<span className="badge badge-green">{project.addressedSignals} addressed</span>
|
|
443
|
+
{project.recentInterventionTypes.map((type) => <span key={`${project.projectId}-${type}`} className={`badge badge-${toneForRoute(type)}`}>{type}</span>)}
|
|
444
|
+
</div>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
)) : (
|
|
448
|
+
<div className="empty-state" style={{ padding: 24 }}>
|
|
449
|
+
<div className="empty-state-title">No project response hotspots yet</div>
|
|
450
|
+
<div className="empty-state-desc">Project-linked pressure will appear here once failures and project analysis produce current demand.</div>
|
|
451
|
+
</div>
|
|
452
|
+
)}
|
|
453
|
+
</div>
|
|
454
|
+
</SectionFrame>
|
|
455
|
+
|
|
456
|
+
<SectionFrame
|
|
457
|
+
eyebrow="Recent transfers"
|
|
458
|
+
title="Reusable knowledge promotion"
|
|
459
|
+
description="When recurring pressure gets promoted into reusable network knowledge, the transfer evidence appears here."
|
|
460
|
+
actions={<Link href="/evolution" className="badge badge-gray">Open evolution</Link>}
|
|
461
|
+
>
|
|
462
|
+
<div className="summary-list">
|
|
463
|
+
{summary.recentTransfers.length > 0 ? summary.recentTransfers.map((event) => (
|
|
464
|
+
<div key={event.id} className="summary-row" style={{ alignItems: 'flex-start' }}>
|
|
465
|
+
<div className="summary-row-main">
|
|
466
|
+
<div className="summary-row-title">{event.summary ?? event.transferType}</div>
|
|
467
|
+
<div className="summary-row-meta">{event.transferType} • {formatDate(event.timestamp)}</div>
|
|
468
|
+
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 8 }}>
|
|
469
|
+
<span className={`badge badge-${event.status === 'realized' ? 'green' : event.status === 'candidate' ? 'purple' : 'gray'}`}>{event.status ?? 'candidate'}</span>
|
|
470
|
+
{(event.targetSkillIds ?? []).slice(0, 2).map((skill) => <span key={`${event.id}-${skill}`} className="badge badge-gray">{skill}</span>)}
|
|
471
|
+
{(event.capabilityIds ?? []).slice(0, 2).map((capability) => <span key={`${event.id}-${capability}`} className="badge badge-gray">{capability}</span>)}
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
)) : (
|
|
476
|
+
<div className="empty-state" style={{ padding: 24 }}>
|
|
477
|
+
<div className="empty-state-title">No transfer evidence yet</div>
|
|
478
|
+
<div className="empty-state-desc">Once recurring pressure is promoted into reusable generalized skill knowledge, realized transfer evidence will appear here.</div>
|
|
479
|
+
</div>
|
|
480
|
+
)}
|
|
481
|
+
</div>
|
|
482
|
+
</SectionFrame>
|
|
483
|
+
</div>
|
|
484
|
+
|
|
485
|
+
<SectionFrame
|
|
486
|
+
eyebrow="Act now"
|
|
487
|
+
title="Governed pressure queue"
|
|
488
|
+
description="Open and in-progress pressure sorted by urgency, including the route recommendation and the reasons behind it."
|
|
489
|
+
>
|
|
490
|
+
<div style={{ display: 'grid', gap: 10 }}>
|
|
491
|
+
{summary.backlog.length > 0 ? summary.backlog.map((signal) => (
|
|
492
|
+
<div key={signal.id} style={{ padding: '14px 16px', borderRadius: 18, border: '1px solid var(--border)', background: 'rgba(255,255,255,0.7)' }}>
|
|
493
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'flex-start', flexWrap: 'wrap' }}>
|
|
494
|
+
<div style={{ flex: 1 }}>
|
|
495
|
+
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{signal.capability ?? signal.kind}</div>
|
|
496
|
+
<div style={{ fontSize: 12, color: 'var(--text-dim)', marginTop: 4, lineHeight: 1.6 }}>{signal.description ?? signal.responseSummary}</div>
|
|
497
|
+
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 10 }}>
|
|
498
|
+
<span className={`badge badge-${toneForLifecycle(signal.lifecycle)}`}>{signal.lifecycle}</span>
|
|
499
|
+
{signal.priority ? <span className="badge badge-gray">{signal.priority}</span> : null}
|
|
500
|
+
{signal.projectId ? <span className="badge badge-gray">{signal.projectId}</span> : null}
|
|
501
|
+
{signal.routeRecommendation ? <span className={`badge badge-${toneForRoute(signal.routeRecommendation.route)}`}>recommend {signal.routeRecommendation.route}</span> : null}
|
|
502
|
+
{signal.routeRecommendation ? <span className="badge badge-gray">{signal.routeRecommendation.scope}</span> : null}
|
|
503
|
+
{signal.interventionTypes.map((type) => <span key={`${signal.id}-${type}`} className="badge badge-gray">{type}</span>)}
|
|
504
|
+
</div>
|
|
505
|
+
{signal.routeRecommendation?.reasons?.length ? (
|
|
506
|
+
<div style={{ display: 'grid', gap: 4, marginTop: 10 }}>
|
|
507
|
+
{signal.routeRecommendation.reasons.slice(0, 2).map((reason, index) => (
|
|
508
|
+
<div key={`${signal.id}-reason-${index}`} className="signal-text">• {reason}</div>
|
|
509
|
+
))}
|
|
510
|
+
</div>
|
|
511
|
+
) : null}
|
|
512
|
+
</div>
|
|
513
|
+
<div style={{ minWidth: 180, textAlign: 'right', fontSize: 11.5, color: 'var(--text-dim)', lineHeight: 1.7 }}>
|
|
514
|
+
<div>Detected {formatDate(signal.detectedAt)}</div>
|
|
515
|
+
<div>Last activity {formatDate(signal.lastActivityAt)}</div>
|
|
516
|
+
{signal.routeRecommendation ? <div>Confidence {(signal.routeRecommendation.confidence * 100).toFixed(0)}%</div> : null}
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
520
|
+
)) : (
|
|
521
|
+
<div className="empty-state" style={{ padding: 24 }}>
|
|
522
|
+
<div className="empty-state-title">No open pressure backlog</div>
|
|
523
|
+
<div className="empty-state-desc">Once pressure enters the system, open and in-progress items will be queued here with governed routing recommendations.</div>
|
|
524
|
+
</div>
|
|
525
|
+
)}
|
|
526
|
+
</div>
|
|
527
|
+
</SectionFrame>
|
|
528
|
+
|
|
529
|
+
<SectionFrame
|
|
530
|
+
eyebrow="Recent interventions"
|
|
531
|
+
title="Latest recorded responses"
|
|
532
|
+
description="A raw operator log of how the system has recently responded to pressure."
|
|
533
|
+
actions={<Link href="/research" className="badge badge-gray">Open research</Link>}
|
|
534
|
+
>
|
|
535
|
+
<div className="summary-list">
|
|
536
|
+
{summary.recentInterventions.length > 0 ? summary.recentInterventions.map((intervention) => (
|
|
537
|
+
<div key={intervention.id} className="summary-row" style={{ alignItems: 'flex-start' }}>
|
|
538
|
+
<div className="summary-row-main">
|
|
539
|
+
<div className="summary-row-title">{intervention.interventionType}</div>
|
|
540
|
+
<div className="summary-row-meta">{intervention.reasonSummary}</div>
|
|
541
|
+
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 8 }}>
|
|
542
|
+
<span className="badge badge-gray">{intervention.status}</span>
|
|
543
|
+
<span className="badge badge-gray">{intervention.impact}</span>
|
|
544
|
+
{intervention.projectId ? <span className="badge badge-blue">{intervention.projectId}</span> : null}
|
|
545
|
+
{(intervention.capabilities ?? []).slice(0, 3).map((capability) => <span key={`${intervention.id}-${capability}`} className="badge badge-gray">{capability}</span>)}
|
|
546
|
+
<span className="badge badge-gray">{formatDate(intervention.completedAt ?? intervention.createdAt)}</span>
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
)) : (
|
|
551
|
+
<div className="empty-state" style={{ padding: 24 }}>
|
|
552
|
+
<div className="empty-state-title">No response records yet</div>
|
|
553
|
+
<div className="empty-state-desc">Command-level intervention records will appear here once the response loop starts operating.</div>
|
|
554
|
+
</div>
|
|
555
|
+
)}
|
|
556
|
+
</div>
|
|
557
|
+
</SectionFrame>
|
|
558
|
+
</div>
|
|
559
|
+
)
|
|
560
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { loadCoEvolutionSummary } from '@/lib/data'
|
|
2
|
+
import CoEvolutionClient from './client'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export default function CoEvolutionPage() {
|
|
7
|
+
const summary = loadCoEvolutionSummary()
|
|
8
|
+
return <CoEvolutionClient summary={summary} />
|
|
9
|
+
}
|
|
@@ -111,18 +111,18 @@ const COMMANDS: CommandInfo[] = [
|
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
name: 'graph',
|
|
114
|
-
description: 'Visualize and manage the skill network graph. Shows relationships between skills (depends, enhances, conflicts, co-evolves)
|
|
114
|
+
description: 'Visualize and manage the skill network graph. Shows relationships between skills (depends, enhances, conflicts, co-evolves), and graph optimize now persists a topology review queue for merge, split, promote, and rewire candidates.',
|
|
115
115
|
usage: 'helixevo graph [options]',
|
|
116
116
|
examples: [
|
|
117
117
|
{ cmd: 'helixevo graph', desc: 'Show skill network in terminal (instant)' },
|
|
118
118
|
{ cmd: 'helixevo graph --rebuild', desc: 'Re-infer all relationships via LLM' },
|
|
119
|
-
{ cmd: 'helixevo graph --optimize', desc: 'Detect
|
|
119
|
+
{ cmd: 'helixevo graph --optimize', desc: 'Detect structural candidates and refresh the topology review queue' },
|
|
120
120
|
{ cmd: 'helixevo graph --mermaid', desc: 'Open interactive Mermaid diagram in browser' },
|
|
121
121
|
{ cmd: 'helixevo graph --obsidian ~/vault', desc: 'Sync skill graph to an Obsidian vault' },
|
|
122
122
|
],
|
|
123
123
|
options: [
|
|
124
124
|
{ flag: '--rebuild', desc: 'Force rebuild — re-infer relationships via LLM call' },
|
|
125
|
-
{ flag: '--optimize', desc: 'Run network optimization
|
|
125
|
+
{ flag: '--optimize', desc: 'Run network optimization and persist topology review candidates' },
|
|
126
126
|
{ flag: '--mermaid', desc: 'Render as Mermaid diagram in browser' },
|
|
127
127
|
{ flag: '--obsidian <path>', desc: 'Sync to Obsidian vault at the given path' },
|
|
128
128
|
{ flag: '--verbose', desc: 'Show detailed analysis' },
|
|
@@ -131,6 +131,27 @@ const COMMANDS: CommandInfo[] = [
|
|
|
131
131
|
needsLLM: true,
|
|
132
132
|
runnable: { command: 'graph-rebuild', label: 'Rebuild Graph', icon: 'M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1', color: 'var(--purple)' },
|
|
133
133
|
},
|
|
134
|
+
{
|
|
135
|
+
name: 'topology',
|
|
136
|
+
description: 'Move reviewed topology candidates through an explicit structural execution pipeline. Prepare accepted candidates, apply only the safe subset, inspect snapshots and artifacts, and roll back applied structural overrides when needed.',
|
|
137
|
+
usage: 'helixevo topology [options]',
|
|
138
|
+
examples: [
|
|
139
|
+
{ cmd: 'helixevo topology --status', desc: 'Show current review and execution state for topology control' },
|
|
140
|
+
{ cmd: 'helixevo topology --prepare topology_rewire_abc', desc: 'Prepare an accepted topology review candidate' },
|
|
141
|
+
{ cmd: 'helixevo topology --apply topology_plan_abc', desc: 'Apply a safe prepared topology plan' },
|
|
142
|
+
{ cmd: 'helixevo topology --rollback topology_plan_abc', desc: 'Roll back an applied topology plan using the before-apply snapshot' },
|
|
143
|
+
],
|
|
144
|
+
options: [
|
|
145
|
+
{ flag: '--status', desc: 'Show topology review and execution state' },
|
|
146
|
+
{ flag: '--prepare <candidateId>', desc: 'Prepare an accepted topology review candidate' },
|
|
147
|
+
{ flag: '--apply <planId>', desc: 'Apply a safe prepared topology plan' },
|
|
148
|
+
{ flag: '--rollback <planId>', desc: 'Roll back an applied topology plan' },
|
|
149
|
+
{ flag: '--verbose', desc: 'Show detailed recent topology plans in the status view' },
|
|
150
|
+
],
|
|
151
|
+
category: 'network',
|
|
152
|
+
needsLLM: false,
|
|
153
|
+
note: 'Only accepted review candidates can enter apply-plan generation. Milestone 7 applies the safe subset and keeps riskier changes in prepare-only mode.',
|
|
154
|
+
},
|
|
134
155
|
{
|
|
135
156
|
name: 'research',
|
|
136
157
|
description: 'Proactive skill discovery via web research. Identifies gaps in your skill network, generates hypotheses, searches the web for solutions, and creates draft skills from discoveries.',
|
|
@@ -342,8 +342,8 @@ export default function GuidePage() {
|
|
|
342
342
|
</div>
|
|
343
343
|
<Callout type="tip">
|
|
344
344
|
HelixEvo now exposes a visible <strong>brain foundation</strong> in the dashboard: ontology-backed summaries,
|
|
345
|
-
activation traces, evolution artifacts,
|
|
346
|
-
Research, and Evolution surfaces.
|
|
345
|
+
activation traces, evolution artifacts, pressure signals, governance steering, topology review, and reviewed topology execution all feed the Overview,
|
|
346
|
+
Skill Network, Topology, Projects, Research, and Evolution surfaces.
|
|
347
347
|
</Callout>
|
|
348
348
|
</Section>
|
|
349
349
|
|
|
@@ -908,6 +908,16 @@ generation: 3
|
|
|
908
908
|
├── failures.jsonl # Captured failure records (append-only)
|
|
909
909
|
├── activation-traces.jsonl # Native + derived activation traces
|
|
910
910
|
├── pressure-signals.jsonl # Native + derived adaptation pressure
|
|
911
|
+
├── pressure-interventions.jsonl # Routed intervention ledger across response lanes
|
|
912
|
+
├── transfer-events.jsonl # Promotion / transfer evidence across motifs and projects
|
|
913
|
+
├── governance-state.json # Operator-selected governance steering state
|
|
914
|
+
├── topology-review-candidates.json # Persisted structural review queue
|
|
915
|
+
├── topology-review-decisions.jsonl # Accept / reject / defer decision ledger
|
|
916
|
+
├── topology-overrides.json # Applied safe structural topology overrides
|
|
917
|
+
├── topology-snapshots.json # Snapshot refs for reviewed execution and rollback
|
|
918
|
+
├── topology-apply-plans.json # Prepared reviewed topology plans
|
|
919
|
+
├── topology-executions.jsonl # Prepared/applied/rolled-back execution ledger
|
|
920
|
+
├── topology-artifacts.jsonl # Evidence artifacts for reviewed structural execution
|
|
911
921
|
├── evolution-artifacts.jsonl # Proposal/iteration evidence artifacts
|
|
912
922
|
├── frontier.json # Pareto frontier (top-K programs)
|
|
913
923
|
├── evolution-history.json # All evolution iterations + proposals
|