helixevo 0.3.1 → 0.4.1

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.
@@ -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). Can render as TUI, Mermaid diagram, or sync to Obsidian.',
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 merge/split/conflict opportunities' },
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 (merge/split/conflict detection)' },
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.',