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
|
@@ -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">
|
|
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 }}>
|
|
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 }}>
|
package/dashboard/app/page.tsx
CHANGED
|
@@ -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: `
|
|
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
|
-
|
|
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="
|
|
362
|
-
value={Object.
|
|
363
|
-
sublabel="Projects
|
|
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
|
-
|
|
756
|
-
{
|
|
757
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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="
|
|
203
|
-
value={coevolution.
|
|
204
|
-
sublabel=
|
|
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=
|
|
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 }}>
|
|
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>
|