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.
@@ -45,10 +45,45 @@ interface ProjectPressureSummary {
45
45
 
46
46
  interface CoEvolutionSummary {
47
47
  pressureSignals: { total: number; native: number; derived: number }
48
+ pressureInterventions: { total: number; started: number; completed: number; failed: number; dryRun: number }
49
+ pressureLifecycle: { open: number; inProgress: number; addressed: number; stale: number; highPriorityOpen: number }
50
+ pressureMotifs: { total: number; open: number; inProgress: number; addressed: number; stale: number; promotionReady: number }
51
+ governance: {
52
+ activeMode: 'conservative' | 'balanced' | 'exploration' | 'project-critical' | 'consolidation-focused' | 'transfer-focused'
53
+ derivedMode: 'conservative' | 'balanced' | 'exploration' | 'project-critical' | 'consolidation-focused' | 'transfer-focused'
54
+ source: 'derived' | 'operator-selected'
55
+ manualMode?: 'conservative' | 'balanced' | 'exploration' | 'project-critical' | 'consolidation-focused' | 'transfer-focused'
56
+ rationale: string[]
57
+ profile: { reviewThreshold: number; riskTolerance: number; plasticityBias: number; explanation: string }
58
+ }
59
+ topologyReviews: {
60
+ total: number
61
+ open: number
62
+ accepted: number
63
+ rejected: number
64
+ deferred: number
65
+ highPriorityOpen: number
66
+ generatedFromManualReview: number
67
+ byChangeType: { merge: number; split: number; promote: number; demote: number; rewire: number; prune: number; consolidate: number }
68
+ }
69
+ topologyExecution: {
70
+ plans: number
71
+ pending: number
72
+ prepared: number
73
+ applied: number
74
+ rolledBack: number
75
+ failed: number
76
+ safeToApply: number
77
+ prepareOnly: number
78
+ snapshots: number
79
+ artifacts: number
80
+ }
48
81
  topProjects: ProjectPressureSummary[]
49
82
  rolePressure: { generalist: number; specialist: number; hybrid: number }
50
83
  crossProjectGapAreas: { area: string; projects: string[]; count: number }[]
51
84
  activeRecommendations: { action: 'research' | 'specialize' | 'create'; count: number }[]
85
+ governedRoutes: { route: 'research' | 'specialize' | 'evolve' | 'generalize' | 'manual-review'; total: number }[]
86
+ recentTransfers: { id: string; transferType: 'specialist-to-generalist' | 'generalist-to-project' | 'cross-project' | 'domain-transfer'; status?: 'candidate' | 'realized' | 'rejected'; summary?: string }[]
52
87
  }
53
88
 
54
89
  interface Props {
@@ -328,7 +363,27 @@ export default function NetworkClient({
328
363
  </div>
329
364
  <div className="metric-card-icon">⇄</div>
330
365
  </div>
331
- <div className="metric-card-sublabel">Recurring gap areas spanning multiple projects</div>
366
+ <div className="metric-card-sublabel">{coevolution.pressureMotifs.promotionReady} motifs currently recommend generalize</div>
367
+ </div>
368
+ <div className="metric-card metric-card-blue">
369
+ <div className="metric-card-header">
370
+ <div>
371
+ <div className="metric-card-label">Open pressure</div>
372
+ <div className="metric-card-value">{coevolution.pressureLifecycle.open}</div>
373
+ </div>
374
+ <div className="metric-card-icon">!</div>
375
+ </div>
376
+ <div className="metric-card-sublabel">{coevolution.pressureLifecycle.inProgress} in progress • {coevolution.pressureLifecycle.addressed} addressed</div>
377
+ </div>
378
+ <div className="metric-card metric-card-purple">
379
+ <div className="metric-card-header">
380
+ <div>
381
+ <div className="metric-card-label">Response interventions</div>
382
+ <div className="metric-card-value">{coevolution.pressureInterventions.total}</div>
383
+ </div>
384
+ <div className="metric-card-icon">↺</div>
385
+ </div>
386
+ <div className="metric-card-sublabel">{coevolution.pressureInterventions.completed} completed • {coevolution.pressureInterventions.dryRun} dry-run</div>
332
387
  </div>
333
388
  </div>
334
389
 
@@ -362,14 +417,33 @@ export default function NetworkClient({
362
417
  </div>
363
418
 
364
419
  <div style={{ padding: '16px 18px', borderRadius: 20, border: '1px solid var(--border)', background: 'rgba(255,255,255,0.72)' }}>
365
- <div className="section-label" style={{ marginBottom: 10 }}>Emergent routing signals</div>
420
+ <div className="section-label" style={{ marginBottom: 10 }}>Governed routing signals</div>
366
421
  <div style={{ display: 'grid', gap: 10 }}>
367
422
  <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
423
+ <span className={`hero-chip hero-chip-${coevolution.governance.activeMode === 'transfer-focused' ? 'purple' : coevolution.governance.activeMode === 'project-critical' ? 'yellow' : 'blue'}`}>
424
+ mode: {coevolution.governance.activeMode.replace(/-/g, ' ')}
425
+ </span>
426
+ <span className="badge badge-gray">{coevolution.governance.source}</span>
368
427
  {coevolution.activeRecommendations.length > 0 ? coevolution.activeRecommendations.map((entry) => (
369
428
  <span key={entry.action} className={`hero-chip hero-chip-${entry.action === 'research' ? 'blue' : entry.action === 'specialize' ? 'green' : 'purple'}`}>
370
429
  {entry.action} × {entry.count}
371
430
  </span>
372
431
  )) : <span className="badge badge-gray">No routed actions yet</span>}
432
+ <a href="/coevolution" className="hero-chip hero-chip-blue" style={{ textDecoration: 'none' }}>
433
+ Open co-evolution control
434
+ </a>
435
+ <a href="/topology" className="hero-chip hero-chip-purple" style={{ textDecoration: 'none' }}>
436
+ Open topology review
437
+ </a>
438
+ </div>
439
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
440
+ {coevolution.governedRoutes.length > 0 ? coevolution.governedRoutes.map((entry) => (
441
+ <span key={entry.route} className="badge badge-gray">{entry.route} × {entry.total}</span>
442
+ )) : <span className="badge badge-gray">No governed route counts yet</span>}
443
+ <span className="badge badge-gray">{coevolution.recentTransfers.filter((event) => event.status === 'realized').length} realized transfers</span>
444
+ <span className="badge badge-gray">{coevolution.topologyReviews.open} open topology reviews</span>
445
+ <span className="badge badge-gray">{coevolution.topologyExecution.prepared} prepared • {coevolution.topologyExecution.applied} applied</span>
446
+ <span className="badge badge-gray">{coevolution.topologyReviews.generatedFromManualReview} manual-route reviews</span>
373
447
  </div>
374
448
  {coevolution.crossProjectGapAreas.length > 0 ? (
375
449
  <div style={{ display: 'grid', gap: 8 }}>
@@ -36,6 +36,10 @@ export default function Overview() {
36
36
  { label: `${summary.failures.unresolved} unresolved corrections`, tone: summary.failures.unresolved > 0 ? 'yellow' : 'green' },
37
37
  { label: `${summary.evolution.runs} evolution runs`, tone: 'blue' },
38
38
  { label: `${frontier.programs.length}/${frontier.capacity} frontier slots`, tone: 'green' },
39
+ { label: `${coevolution.pressureMotifs.promotionReady} promotion-ready motifs`, tone: coevolution.pressureMotifs.promotionReady > 0 ? 'purple' : 'neutral' },
40
+ { label: `${coevolution.topologyReviews.open} topology reviews`, tone: coevolution.topologyReviews.open > 0 ? 'yellow' : 'green' },
41
+ { label: `${coevolution.topologyExecution.prepared} prepared structural plans`, tone: coevolution.topologyExecution.prepared > 0 ? 'blue' : 'neutral' },
42
+ { label: `mode: ${coevolution.governance.activeMode.replace(/-/g, ' ')}`, tone: coevolution.governance.activeMode === 'transfer-focused' ? 'purple' : coevolution.governance.activeMode === 'project-critical' ? 'yellow' : 'blue' },
39
43
  ]}
40
44
  actions={
41
45
  <Link href="/projects" className="metric-card-anchor" style={{ minWidth: 240, display: 'block' }}>
@@ -107,6 +111,38 @@ export default function Overview() {
107
111
  href="/network"
108
112
  icon="⇄"
109
113
  />
114
+ <MetricCard
115
+ label="Open pressure"
116
+ value={ontology.pressureLifecycle.open}
117
+ sublabel={`${ontology.pressureLifecycle.inProgress} in progress • ${ontology.pressureLifecycle.addressed} addressed`}
118
+ tone={ontology.pressureLifecycle.open > 0 ? 'yellow' : 'green'}
119
+ href="/coevolution"
120
+ icon="!"
121
+ />
122
+ <MetricCard
123
+ label="Recorded responses"
124
+ value={ontology.pressureInterventions.total}
125
+ sublabel={`${ontology.pressureInterventions.completed} completed • ${ontology.pressureInterventions.dryRun} dry-run`}
126
+ tone="blue"
127
+ href="/coevolution"
128
+ icon="↺"
129
+ />
130
+ <MetricCard
131
+ label="Pressure motifs"
132
+ value={ontology.pressureMotifs.total}
133
+ sublabel={`${ontology.pressureMotifs.promotionReady} promotion-ready • ${ontology.transferEvents.realized} realized transfers`}
134
+ tone="purple"
135
+ href="/coevolution"
136
+ icon="⇄"
137
+ />
138
+ <MetricCard
139
+ label="Topology review"
140
+ value={ontology.topologyReviews.open}
141
+ sublabel={`${ontology.topologyExecution.prepared} prepared • ${ontology.topologyExecution.applied} applied`}
142
+ tone={ontology.topologyReviews.open > 0 ? 'yellow' : ontology.topologyExecution.applied > 0 ? 'green' : 'blue'}
143
+ href="/topology"
144
+ icon="⇄"
145
+ />
110
146
  <MetricCard
111
147
  label="Observed mutation verbs"
112
148
  value={ontology.mutationOperationsObserved}
@@ -123,8 +159,18 @@ export default function Overview() {
123
159
  <span className="badge badge-gray">{ontology.artifacts.total} evolution artifacts ({ontology.artifacts.native} native • {ontology.artifacts.derived} derived)</span>
124
160
  <span className="badge badge-gray">{ontology.activationTraces.total} activation traces ({ontology.activationTraces.native} native • {ontology.activationTraces.derived} derived)</span>
125
161
  <span className="badge badge-gray">{ontology.pressureSignals.total} pressure signals ({ontology.pressureSignals.native} native • {ontology.pressureSignals.derived} derived)</span>
162
+ <span className="badge badge-gray">{ontology.pressureInterventions.total} response interventions logged</span>
163
+ <span className="badge badge-gray">{ontology.pressureLifecycle.open} open • {ontology.pressureLifecycle.inProgress} in progress • {ontology.pressureLifecycle.addressed} addressed</span>
164
+ <Link href="/coevolution" className="badge badge-blue" style={{ textDecoration: 'none' }}>Open co-evolution control</Link>
126
165
  <span className="badge badge-gray">{coevolution.topProjects.length} pressured project hotspots</span>
127
166
  <span className="badge badge-gray">{coevolution.crossProjectGapAreas.length} cross-project gap motifs</span>
167
+ <span className="badge badge-gray">{ontology.pressureMotifs.promotionReady} motifs currently recommend generalize</span>
168
+ <span className="badge badge-gray">{ontology.transferEvents.total} transfer events logged ({ontology.transferEvents.realized} realized)</span>
169
+ <span className="badge badge-gray">topology → {ontology.topologyReviews.open} open • {ontology.topologyReviews.accepted} accepted • {ontology.topologyReviews.generatedFromManualReview} manual-route</span>
170
+ <span className="badge badge-gray">execution → {ontology.topologyExecution.prepared} prepared • {ontology.topologyExecution.applied} applied • {ontology.topologyExecution.rolledBack} rolled back</span>
171
+ <Link href="/topology" className="badge badge-blue" style={{ textDecoration: 'none' }}>Open topology control</Link>
172
+ <span className="badge badge-gray">governance: {ontology.governance.activeMode.replace(/-/g, ' ')} ({ontology.governance.source})</span>
173
+ <span className="badge badge-gray">routes → research {ontology.governedRoutes.research} • specialize {ontology.governedRoutes.specialize} • evolve {ontology.governedRoutes.evolve} • generalize {ontology.governedRoutes.generalize} • manual-review {ontology.governedRoutes['manual-review']}</span>
128
174
  <span className="badge badge-gray">{ontology.enrichedSkillNodes} skills carry explicit brain metadata</span>
129
175
  <span className="badge badge-gray">{ontology.annotatedFailures.pressureSignals} failures annotated with pressure signals</span>
130
176
  </div>
@@ -119,7 +119,7 @@ function formatDate(value: string) {
119
119
  return new Date(value).toLocaleDateString()
120
120
  }
121
121
 
122
- export default function ProjectsClient({ profiles, skillCount, projectAnalysisTraces, pressureByProject }: { profiles: ProjectProfile[]; skillCount: number; projectAnalysisTraces: number; pressureByProject: Record<string, { projectId: string; totalSignals: number; nativeSignals: number; derivedSignals: number; highPrioritySignals: number; topCapabilities: string[] }> }) {
122
+ export default function ProjectsClient({ profiles, skillCount, projectAnalysisTraces, pressureByProject, promotionByProject }: { profiles: ProjectProfile[]; skillCount: number; projectAnalysisTraces: number; pressureByProject: Record<string, { projectId: string; totalSignals: number; nativeSignals: number; derivedSignals: number; highPrioritySignals: number; topCapabilities: string[]; openSignals: number; inProgressSignals: number; addressedSignals: number; staleSignals: number; activeInterventions: number; recentInterventionTypes: ('research' | 'specialize' | 'evolve' | 'generalize' | 'manual-review')[] }>; promotionByProject: Record<string, number> }) {
123
123
  const [inputMode, setInputMode] = useState<InputMode>('local')
124
124
  const [projectPath, setProjectPath] = useState('')
125
125
  const [githubUrl, setGithubUrl] = useState('')
@@ -182,7 +182,7 @@ export default function ProjectsClient({ profiles, skillCount, projectAnalysisTr
182
182
 
183
183
  const triggerSpecialize = async (projectName: string) => {
184
184
  setSpecializingProject(projectName)
185
- setProjectActionMessage({ tone: 'blue', text: `Requesting specialization workflow for ${projectName}…` })
185
+ setProjectActionMessage({ tone: 'blue', text: `Running specialization workflow for ${projectName}…` })
186
186
 
187
187
  try {
188
188
  const res = await fetch('/api/run', {
@@ -191,8 +191,46 @@ export default function ProjectsClient({ profiles, skillCount, projectAnalysisTr
191
191
  body: JSON.stringify({ command: 'specialize', project: projectName }),
192
192
  })
193
193
 
194
- if (!res.ok) throw new Error('Specialize request failed')
195
- setProjectActionMessage({ tone: 'green', text: `Specialization workflow requested for ${projectName}.` })
194
+ if (!res.ok || !res.body) throw new Error('Specialize request failed')
195
+
196
+ const reader = res.body.getReader()
197
+ const decoder = new TextDecoder()
198
+ let sseBuffer = ''
199
+ let succeeded = false
200
+
201
+ while (true) {
202
+ const { done, value } = await reader.read()
203
+ if (done) break
204
+
205
+ sseBuffer += decoder.decode(value, { stream: true })
206
+ const events = sseBuffer.split('\n\n')
207
+ sseBuffer = events.pop() ?? ''
208
+
209
+ for (const event of events) {
210
+ const lines = event.split('\n')
211
+ let eventType = ''
212
+ let eventData = ''
213
+ for (const line of lines) {
214
+ if (line.startsWith('event: ')) eventType = line.slice(7)
215
+ if (line.startsWith('data: ')) eventData = line.slice(6)
216
+ }
217
+ if (eventType === 'done' && eventData) {
218
+ try {
219
+ const result = JSON.parse(JSON.parse(eventData) as string) as { success: boolean }
220
+ succeeded = result.success
221
+ } catch {
222
+ succeeded = true
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ setProjectActionMessage({
229
+ tone: succeeded ? 'green' : 'red',
230
+ text: succeeded
231
+ ? `Specialization workflow completed for ${projectName}. Refresh the page to see new response data if needed.`
232
+ : `Specialization workflow for ${projectName} did not complete successfully.`,
233
+ })
196
234
  } catch {
197
235
  setProjectActionMessage({ tone: 'red', text: `Could not start specialization for ${projectName}.` })
198
236
  } finally {
@@ -358,9 +396,9 @@ export default function ProjectsClient({ profiles, skillCount, projectAnalysisTr
358
396
  tone="green"
359
397
  />
360
398
  <MetricCard
361
- label="Project pressure hotspots"
362
- value={Object.keys(pressureByProject).length}
363
- sublabel="Projects currently carrying persisted or compatibility-derived pressure signals"
399
+ label="Active response projects"
400
+ value={Object.values(pressureByProject).filter((project) => project.openSignals > 0 || project.inProgressSignals > 0).length}
401
+ sublabel="Projects with open or in-progress pressure that still needs follow-through"
364
402
  tone="blue"
365
403
  />
366
404
  <MetricCard
@@ -369,6 +407,12 @@ export default function ProjectsClient({ profiles, skillCount, projectAnalysisTr
369
407
  sublabel={aggregate.totalHighGaps > 0 ? 'Capability needs requiring near-term attention' : 'No critical gaps recorded right now'}
370
408
  tone={aggregate.totalHighGaps > 0 ? 'red' : 'green'}
371
409
  />
410
+ <MetricCard
411
+ label="Promotion feeders"
412
+ value={Object.values(promotionByProject).filter((count) => count > 0).length}
413
+ sublabel="Projects whose local pressure is now feeding broader generalization candidates"
414
+ tone="purple"
415
+ />
372
416
  <MetricCard
373
417
  label="Recommended next steps"
374
418
  value={aggregate.totalRecommendations}
@@ -737,6 +781,7 @@ export default function ProjectsClient({ profiles, skillCount, projectAnalysisTr
737
781
  const highGaps = profile.gaps.filter(gap => gap.priority === 'high')
738
782
  const matchedPct = skillCount > 0 ? Math.round((profile.matchedSkills.length / skillCount) * 100) : 0
739
783
  const projectPressure = pressureByProject[profile.name]
784
+ const promotionCount = promotionByProject[profile.name] ?? 0
740
785
 
741
786
  return (
742
787
  <article key={profile.name} className="card" style={{ overflow: 'hidden' }}>
@@ -752,10 +797,16 @@ export default function ProjectsClient({ profiles, skillCount, projectAnalysisTr
752
797
  <span className="hero-chip hero-chip-neutral">Analyzed {formatDate(profile.analyzedAt)}</span>
753
798
  <span className={`hero-chip hero-chip-${highGaps.length > 0 ? 'red' : 'green'}`}>{highGaps.length} high-priority gap{highGaps.length === 1 ? '' : 's'}</span>
754
799
  {projectPressure ? (
755
- <span className={`hero-chip hero-chip-${projectPressure.highPrioritySignals > 0 ? 'red' : 'blue'}`}>
756
- {projectPressure.totalSignals} pressure signal{projectPressure.totalSignals === 1 ? '' : 's'}
757
- </span>
800
+ <>
801
+ <span className={`hero-chip hero-chip-${projectPressure.highPrioritySignals > 0 ? 'red' : 'blue'}`}>
802
+ {projectPressure.totalSignals} pressure signal{projectPressure.totalSignals === 1 ? '' : 's'}
803
+ </span>
804
+ <span className={`hero-chip hero-chip-${projectPressure.openSignals > 0 ? 'yellow' : projectPressure.inProgressSignals > 0 ? 'blue' : projectPressure.addressedSignals > 0 ? 'green' : 'neutral'}`}>
805
+ {projectPressure.openSignals > 0 ? `${projectPressure.openSignals} open` : projectPressure.inProgressSignals > 0 ? `${projectPressure.inProgressSignals} in progress` : projectPressure.addressedSignals > 0 ? `${projectPressure.addressedSignals} addressed` : `${projectPressure.staleSignals} stale`}
806
+ </span>
807
+ </>
758
808
  ) : null}
809
+ {promotionCount > 0 ? <span className="hero-chip hero-chip-purple">{promotionCount} promotion-ready motif{promotionCount === 1 ? '' : 's'}</span> : null}
759
810
  </div>
760
811
  <div style={{ fontSize: 24, fontWeight: 800, color: 'var(--text)', marginBottom: 8 }}>{profile.name}</div>
761
812
  <div style={{ fontSize: 13, color: 'var(--text-secondary)', lineHeight: 1.7, marginBottom: 12 }}>{profile.description}</div>
@@ -803,6 +854,28 @@ export default function ProjectsClient({ profiles, skillCount, projectAnalysisTr
803
854
  {profile.path}
804
855
  </div>
805
856
  </div>
857
+
858
+ {projectPressure ? (
859
+ <div style={{
860
+ padding: '12px 14px',
861
+ borderRadius: 18,
862
+ background: 'rgba(59,130,246,0.08)',
863
+ border: '1px solid rgba(59,130,246,0.16)',
864
+ }}>
865
+ <div style={{ fontSize: 11, color: 'var(--blue)', marginBottom: 4, letterSpacing: 0.4, textTransform: 'uppercase' }}>
866
+ Response loop
867
+ </div>
868
+ <div style={{ fontSize: 12.5, color: 'var(--text)', fontWeight: 700, marginBottom: 6 }}>
869
+ {projectPressure.activeInterventions} active intervention{projectPressure.activeInterventions === 1 ? '' : 's'}
870
+ </div>
871
+ <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
872
+ <span className="badge badge-gray">{projectPressure.openSignals} open</span>
873
+ <span className="badge badge-blue">{projectPressure.inProgressSignals} in progress</span>
874
+ <span className="badge badge-green">{projectPressure.addressedSignals} addressed</span>
875
+ {promotionCount > 0 ? <span className="badge badge-purple">{promotionCount} feeding generalize</span> : null}
876
+ </div>
877
+ </div>
878
+ ) : null}
806
879
  </div>
807
880
  </div>
808
881
  </div>
@@ -1,4 +1,4 @@
1
- import { loadActivationTraces, loadAllProjectProfiles, loadGraph, loadPressureSignals } from '@/lib/data'
1
+ import { loadActivationTraces, loadAllProjectProfiles, loadCoEvolutionSummary, loadGraph } from '@/lib/data'
2
2
  import ProjectsClient from './client'
3
3
 
4
4
  export const dynamic = 'force-dynamic'
@@ -7,33 +7,14 @@ export default function ProjectsPage() {
7
7
  const profiles = loadAllProjectProfiles()
8
8
  const graph = loadGraph()
9
9
  const activationTraces = loadActivationTraces()
10
- const pressureSignals = loadPressureSignals()
10
+ const coevolution = loadCoEvolutionSummary()
11
11
  const skillCount = graph.nodes.length
12
12
  const projectAnalysisTraces = activationTraces.filter((trace) => trace.provenance === 'project-analysis').length
13
- const pressureByProject = Object.fromEntries(
14
- Object.entries(
15
- pressureSignals.reduce<Record<string, { projectId: string; totalSignals: number; nativeSignals: number; derivedSignals: number; highPrioritySignals: number; topCapabilities: string[] }>>((acc, signal) => {
16
- if (!signal.projectId) return acc
17
- const existing = acc[signal.projectId] ?? {
18
- projectId: signal.projectId,
19
- totalSignals: 0,
20
- nativeSignals: 0,
21
- derivedSignals: 0,
22
- highPrioritySignals: 0,
23
- topCapabilities: [],
24
- }
25
- existing.totalSignals += 1
26
- if (signal.provenance === 'failure-native' || signal.provenance === 'project-analysis-native') existing.nativeSignals += 1
27
- else existing.derivedSignals += 1
28
- if (signal.priority === 'high') existing.highPrioritySignals += 1
29
- if (signal.capability && !existing.topCapabilities.includes(signal.capability) && existing.topCapabilities.length < 3) {
30
- existing.topCapabilities.push(signal.capability)
31
- }
32
- acc[signal.projectId] = existing
33
- return acc
34
- }, {}),
35
- ),
36
- )
13
+ const pressureByProject = Object.fromEntries(coevolution.responseProjects.map((project) => [project.projectId, project]))
14
+ const promotionByProject = coevolution.promoteQueue.reduce<Record<string, number>>((acc, motif) => {
15
+ for (const projectId of motif.projectIds) acc[projectId] = (acc[projectId] ?? 0) + 1
16
+ return acc
17
+ }, {})
37
18
 
38
- return <ProjectsClient profiles={profiles} skillCount={skillCount} projectAnalysisTraces={projectAnalysisTraces} pressureByProject={pressureByProject} />
19
+ return <ProjectsClient profiles={profiles} skillCount={skillCount} projectAnalysisTraces={projectAnalysisTraces} pressureByProject={pressureByProject} promotionByProject={promotionByProject} />
39
20
  }
@@ -34,9 +34,17 @@ interface Props {
34
34
  projects: string[]
35
35
  coevolution: {
36
36
  pressureSignals: { total: number; native: number; derived: number }
37
+ pressureInterventions: { total: number; started: number; completed: number; failed: number; dryRun: number }
38
+ pressureLifecycle: { open: number; inProgress: number; addressed: number; stale: number; highPriorityOpen: number }
39
+ pressureMotifs: { total: number; open: number; inProgress: number; addressed: number; stale: number; promotionReady: number }
40
+ governance: { activeMode: 'conservative' | 'balanced' | 'exploration' | 'project-critical' | 'consolidation-focused' | 'transfer-focused'; rationale: string[] }
37
41
  topProjects: { projectId: string; totalSignals: number; highPrioritySignals: number; topCapabilities: string[] }[]
38
42
  crossProjectGapAreas: { area: string; projects: string[]; count: number }[]
39
43
  activeRecommendations: { action: 'research' | 'specialize' | 'create'; count: number }[]
44
+ governedRoutes: { route: 'research' | 'specialize' | 'evolve' | 'generalize' | 'manual-review'; total: number }[]
45
+ interventionLanes: { type: 'research' | 'specialize' | 'evolve' | 'generalize' | 'manual-review'; total: number; resolving: number; exploratory: number; none: number }[]
46
+ promoteQueue: { id: string; capability?: string; kind: string; recurrenceCount: number; projectIds: string[]; suggestedRoute: { route: 'research' | 'specialize' | 'evolve' | 'generalize' | 'manual-review'; reasons: string[] } }[]
47
+ recentTransfers: { id: string; transferType: 'specialist-to-generalist' | 'generalist-to-project' | 'cross-project' | 'domain-transfer'; status?: 'candidate' | 'realized' | 'rejected'; summary?: string }[]
40
48
  }
41
49
  }
42
50
 
@@ -190,7 +198,7 @@ export default function ResearchClient({ buffer, projects, coevolution }: Props)
190
198
  description="Research becomes more valuable when recurring project pressure points to capability gaps that should generalize beyond one local context."
191
199
  tone="blue"
192
200
  >
193
- <div className="grid-3">
201
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 16 }}>
194
202
  <MetricCard
195
203
  label="Pressure signals"
196
204
  value={coevolution.pressureSignals.total}
@@ -199,16 +207,23 @@ export default function ResearchClient({ buffer, projects, coevolution }: Props)
199
207
  icon="◎"
200
208
  />
201
209
  <MetricCard
202
- label="Pressured projects"
203
- value={coevolution.topProjects.length}
204
- sublabel="Projects currently producing the strongest adaptation demand"
210
+ label="Open research queue"
211
+ value={coevolution.pressureLifecycle.open}
212
+ sublabel={`${coevolution.pressureLifecycle.highPriorityOpen} high-priority still waiting for response`}
213
+ tone={coevolution.pressureLifecycle.open > 0 ? 'yellow' : 'green'}
214
+ icon="!"
215
+ />
216
+ <MetricCard
217
+ label="Recorded responses"
218
+ value={coevolution.pressureInterventions.total}
219
+ sublabel={`${coevolution.pressureInterventions.completed} completed • ${coevolution.pressureInterventions.dryRun} dry-run`}
205
220
  tone="purple"
206
- icon=""
221
+ icon=""
207
222
  />
208
223
  <MetricCard
209
224
  label="Cross-project gap motifs"
210
225
  value={coevolution.crossProjectGapAreas.length}
211
- sublabel="Recurring capability gaps that research can help generalize"
226
+ sublabel={`${coevolution.pressureMotifs.promotionReady} promotion-ready motifs now bias toward generalization`}
212
227
  tone="green"
213
228
  icon="⇄"
214
229
  />
@@ -232,14 +247,39 @@ export default function ResearchClient({ buffer, projects, coevolution }: Props)
232
247
  </div>
233
248
 
234
249
  <div style={{ padding: '14px 16px', borderRadius: 18, background: 'rgba(255,255,255,0.7)', border: '1px solid var(--border)' }}>
235
- <div className="section-label" style={{ marginBottom: 10 }}>Recommended routing pressure</div>
236
- <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
250
+ <div className="section-label" style={{ marginBottom: 10 }}>Governed routing pressure</div>
251
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 10 }}>
252
+ <span className={`hero-chip hero-chip-${coevolution.governance.activeMode === 'transfer-focused' ? 'purple' : coevolution.governance.activeMode === 'project-critical' ? 'yellow' : 'blue'}`}>
253
+ mode: {coevolution.governance.activeMode.replace(/-/g, ' ')}
254
+ </span>
237
255
  {coevolution.activeRecommendations.length > 0 ? coevolution.activeRecommendations.map((entry) => (
238
256
  <span key={entry.action} className={`hero-chip hero-chip-${entry.action === 'research' ? 'blue' : entry.action === 'specialize' ? 'green' : 'purple'}`}>
239
257
  {entry.action} × {entry.count}
240
258
  </span>
241
259
  )) : <span className="badge badge-gray">No routed action pressure yet</span>}
242
260
  </div>
261
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 10 }}>
262
+ {coevolution.governedRoutes.length > 0 ? coevolution.governedRoutes.map((entry) => (
263
+ <span key={entry.route} className="badge badge-gray">
264
+ {entry.route} × {entry.total}
265
+ </span>
266
+ )) : <span className="badge badge-gray">No governed route counts yet</span>}
267
+ </div>
268
+ <div style={{ display: 'grid', gap: 6 }}>
269
+ {coevolution.governance.rationale.slice(0, 2).map((reason, index) => (
270
+ <div key={`${reason}-${index}`} className="signal-text">• {reason}</div>
271
+ ))}
272
+ </div>
273
+ {coevolution.promoteQueue.length > 0 ? (
274
+ <div style={{ marginTop: 12, display: 'grid', gap: 8 }}>
275
+ {coevolution.promoteQueue.slice(0, 2).map((motif) => (
276
+ <div key={motif.id} style={{ padding: '10px 12px', borderRadius: 14, background: 'rgba(97,93,86,0.06)', border: '1px solid var(--border)' }}>
277
+ <div style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--text)' }}>{motif.capability ?? motif.kind}</div>
278
+ <div style={{ fontSize: 11.5, color: 'var(--text-dim)', marginTop: 4 }}>{motif.recurrenceCount} recurring signals • {motif.projectIds.length} projects • recommend {motif.suggestedRoute.route}</div>
279
+ </div>
280
+ ))}
281
+ </div>
282
+ ) : null}
243
283
  </div>
244
284
  </div>
245
285
  </SectionFrame>