helixevo 0.2.41 → 0.3.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.
@@ -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 }: { profiles: ProjectProfile[]; skillCount: number }) {
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[] }> }) {
123
123
  const [inputMode, setInputMode] = useState<InputMode>('local')
124
124
  const [projectPath, setProjectPath] = useState('')
125
125
  const [githubUrl, setGithubUrl] = useState('')
@@ -338,7 +338,7 @@ export default function ProjectsClient({ profiles, skillCount }: { profiles: Pro
338
338
  }
339
339
  />
340
340
 
341
- <div className="grid-4" style={{ marginBottom: 24 }}>
341
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 16, marginBottom: 24 }}>
342
342
  <MetricCard
343
343
  label="Portfolio"
344
344
  value={profiles.length}
@@ -351,6 +351,18 @@ export default function ProjectsClient({ profiles, skillCount }: { profiles: Pro
351
351
  sublabel={`Average matched-skill coverage across ${profiles.length || 1} project${profiles.length === 1 ? '' : 's'}`}
352
352
  tone="purple"
353
353
  />
354
+ <MetricCard
355
+ label="Project-analysis traces"
356
+ value={projectAnalysisTraces}
357
+ sublabel="Native activation snapshots captured from successful project analysis runs"
358
+ tone="green"
359
+ />
360
+ <MetricCard
361
+ label="Project pressure hotspots"
362
+ value={Object.keys(pressureByProject).length}
363
+ sublabel="Projects currently carrying persisted or compatibility-derived pressure signals"
364
+ tone="blue"
365
+ />
354
366
  <MetricCard
355
367
  label="High-priority gaps"
356
368
  value={aggregate.totalHighGaps}
@@ -724,6 +736,7 @@ export default function ProjectsClient({ profiles, skillCount }: { profiles: Pro
724
736
  {profiles.map(profile => {
725
737
  const highGaps = profile.gaps.filter(gap => gap.priority === 'high')
726
738
  const matchedPct = skillCount > 0 ? Math.round((profile.matchedSkills.length / skillCount) * 100) : 0
739
+ const projectPressure = pressureByProject[profile.name]
727
740
 
728
741
  return (
729
742
  <article key={profile.name} className="card" style={{ overflow: 'hidden' }}>
@@ -738,12 +751,18 @@ export default function ProjectsClient({ profiles, skillCount }: { profiles: Pro
738
751
  <span className={`hero-chip hero-chip-${profile.status === 'active' ? 'green' : 'blue'}`}>{profile.status}</span>
739
752
  <span className="hero-chip hero-chip-neutral">Analyzed {formatDate(profile.analyzedAt)}</span>
740
753
  <span className={`hero-chip hero-chip-${highGaps.length > 0 ? 'red' : 'green'}`}>{highGaps.length} high-priority gap{highGaps.length === 1 ? '' : 's'}</span>
754
+ {projectPressure ? (
755
+ <span className={`hero-chip hero-chip-${projectPressure.highPrioritySignals > 0 ? 'red' : 'blue'}`}>
756
+ {projectPressure.totalSignals} pressure signal{projectPressure.totalSignals === 1 ? '' : 's'}
757
+ </span>
758
+ ) : null}
741
759
  </div>
742
760
  <div style={{ fontSize: 24, fontWeight: 800, color: 'var(--text)', marginBottom: 8 }}>{profile.name}</div>
743
761
  <div style={{ fontSize: 13, color: 'var(--text-secondary)', lineHeight: 1.7, marginBottom: 12 }}>{profile.description}</div>
744
762
  <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
745
763
  {profile.techStack.map(tech => <span key={tech} className="badge badge-gray">{tech}</span>)}
746
764
  {profile.domains.map(domain => <span key={domain} className="badge badge-blue">{domain}</span>)}
765
+ {projectPressure?.topCapabilities.map((capability) => <span key={`${profile.name}-${capability}`} className="badge badge-yellow">{capability}</span>)}
747
766
  </div>
748
767
  </div>
749
768
 
@@ -1,4 +1,4 @@
1
- import { loadAllProjectProfiles, loadGraph } from '@/lib/data'
1
+ import { loadActivationTraces, loadAllProjectProfiles, loadGraph, loadPressureSignals } from '@/lib/data'
2
2
  import ProjectsClient from './client'
3
3
 
4
4
  export const dynamic = 'force-dynamic'
@@ -6,7 +6,34 @@ export const dynamic = 'force-dynamic'
6
6
  export default function ProjectsPage() {
7
7
  const profiles = loadAllProjectProfiles()
8
8
  const graph = loadGraph()
9
+ const activationTraces = loadActivationTraces()
10
+ const pressureSignals = loadPressureSignals()
9
11
  const skillCount = graph.nodes.length
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
+ )
10
37
 
11
- return <ProjectsClient profiles={profiles} skillCount={skillCount} />
38
+ return <ProjectsClient profiles={profiles} skillCount={skillCount} projectAnalysisTraces={projectAnalysisTraces} pressureByProject={pressureByProject} />
12
39
  }
@@ -32,6 +32,12 @@ interface KnowledgeBuffer {
32
32
  interface Props {
33
33
  buffer: KnowledgeBuffer
34
34
  projects: string[]
35
+ coevolution: {
36
+ pressureSignals: { total: number; native: number; derived: number }
37
+ topProjects: { projectId: string; totalSignals: number; highPrioritySignals: number; topCapabilities: string[] }[]
38
+ crossProjectGapAreas: { area: string; projects: string[]; count: number }[]
39
+ activeRecommendations: { action: 'research' | 'specialize' | 'create'; count: number }[]
40
+ }
35
41
  }
36
42
 
37
43
  type RunState = 'idle' | 'running' | 'success' | 'error' | 'stopped'
@@ -78,7 +84,7 @@ function consoleTone(state: RunState): 'neutral' | 'green' | 'red' | 'yellow' {
78
84
  return 'neutral'
79
85
  }
80
86
 
81
- export default function ResearchClient({ buffer, projects }: Props) {
87
+ export default function ResearchClient({ buffer, projects, coevolution }: Props) {
82
88
  const [runState, setRunState] = useState<RunState>('idle')
83
89
  const [output, setOutput] = useState('')
84
90
  const abortRef = useRef<AbortController | null>(null)
@@ -178,6 +184,66 @@ export default function ResearchClient({ buffer, projects }: Props) {
178
184
  ]}
179
185
  />
180
186
 
187
+ <SectionFrame
188
+ eyebrow="Why research now"
189
+ title="Current pressure handoff"
190
+ description="Research becomes more valuable when recurring project pressure points to capability gaps that should generalize beyond one local context."
191
+ tone="blue"
192
+ >
193
+ <div className="grid-3">
194
+ <MetricCard
195
+ label="Pressure signals"
196
+ value={coevolution.pressureSignals.total}
197
+ sublabel={`${coevolution.pressureSignals.native} native • ${coevolution.pressureSignals.derived} derived`}
198
+ tone="blue"
199
+ icon="◎"
200
+ />
201
+ <MetricCard
202
+ label="Pressured projects"
203
+ value={coevolution.topProjects.length}
204
+ sublabel="Projects currently producing the strongest adaptation demand"
205
+ tone="purple"
206
+ icon="◉"
207
+ />
208
+ <MetricCard
209
+ label="Cross-project gap motifs"
210
+ value={coevolution.crossProjectGapAreas.length}
211
+ sublabel="Recurring capability gaps that research can help generalize"
212
+ tone="green"
213
+ icon="⇄"
214
+ />
215
+ </div>
216
+
217
+ <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.2fr) minmax(0, 1fr)', gap: 16, marginTop: 16 }}>
218
+ <div style={{ padding: '14px 16px', borderRadius: 18, background: 'rgba(255,255,255,0.7)', border: '1px solid var(--border)' }}>
219
+ <div className="section-label" style={{ marginBottom: 10 }}>Top gap motifs</div>
220
+ {coevolution.crossProjectGapAreas.length === 0 ? (
221
+ <div className="signal-text">Recurring cross-project gap motifs will appear here once the same capability pressure shows up across multiple projects.</div>
222
+ ) : (
223
+ <div style={{ display: 'grid', gap: 8 }}>
224
+ {coevolution.crossProjectGapAreas.slice(0, 4).map((entry) => (
225
+ <div key={entry.area} style={{ padding: '12px 14px', borderRadius: 16, background: 'rgba(97,93,86,0.06)', border: '1px solid var(--border)' }}>
226
+ <div style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--text)' }}>{entry.area}</div>
227
+ <div style={{ fontSize: 11.5, color: 'var(--text-dim)', marginTop: 4 }}>{entry.count} projects • {entry.projects.join(', ')}</div>
228
+ </div>
229
+ ))}
230
+ </div>
231
+ )}
232
+ </div>
233
+
234
+ <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' }}>
237
+ {coevolution.activeRecommendations.length > 0 ? coevolution.activeRecommendations.map((entry) => (
238
+ <span key={entry.action} className={`hero-chip hero-chip-${entry.action === 'research' ? 'blue' : entry.action === 'specialize' ? 'green' : 'purple'}`}>
239
+ {entry.action} × {entry.count}
240
+ </span>
241
+ )) : <span className="badge badge-gray">No routed action pressure yet</span>}
242
+ </div>
243
+ </div>
244
+ </div>
245
+ </SectionFrame>
246
+
181
247
  <SectionFrame
182
248
  eyebrow="Pipeline"
183
249
  title="How research works"
@@ -1,4 +1,4 @@
1
- import { loadBuffer, listProjects } from '@/lib/data'
1
+ import { loadBuffer, loadCoEvolutionSummary, listProjects } from '@/lib/data'
2
2
  import ResearchClient from './client'
3
3
 
4
4
  export const dynamic = 'force-dynamic'
@@ -6,6 +6,7 @@ export const dynamic = 'force-dynamic'
6
6
  export default function ResearchPage() {
7
7
  const buffer = loadBuffer()
8
8
  const projects = listProjects()
9
+ const coevolution = loadCoEvolutionSummary()
9
10
 
10
- return <ResearchClient buffer={buffer} projects={projects} />
11
+ return <ResearchClient buffer={buffer} projects={projects} coevolution={coevolution} />
11
12
  }