helixevo 0.2.7 → 0.2.9
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/README.md +4 -4
- package/dashboard/app/api/skills/route.ts +1 -1
- package/dashboard/app/guide/page.tsx +21 -20
- package/dashboard/app/layout.tsx +4 -1
- package/dashboard/components/update-banner.tsx +149 -0
- package/dashboard/lib/data.ts +5 -3
- package/dashboard/lib/version.ts +1 -0
- package/dashboard/next.config.mjs +28 -0
- package/dist/cli.js +260 -112
- package/dist/postinstall.js +21 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ HelixEvo builds on ideas from [EvoSkill](https://arxiv.org/abs/2603.02766) and [
|
|
|
12
12
|
|
|
13
13
|
Every proposed change goes through:
|
|
14
14
|
1. **3 independent LLM judges** (Task Completion, Correction Alignment, Side-Effect Check)
|
|
15
|
-
2. **Regression testing** against
|
|
15
|
+
2. **Regression testing** against skill tests
|
|
16
16
|
3. **3-day canary deployment** with auto-rollback
|
|
17
17
|
|
|
18
18
|
## Prerequisites
|
|
@@ -57,7 +57,7 @@ npm link
|
|
|
57
57
|
## Quick Start
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
# 1. Initialize — imports existing skills + generates
|
|
60
|
+
# 1. Initialize — imports existing skills + generates skill tests
|
|
61
61
|
helixevo init
|
|
62
62
|
|
|
63
63
|
# 2. Capture failures from a session
|
|
@@ -80,7 +80,7 @@ helixevo dashboard
|
|
|
80
80
|
| `helixevo watch` | Always-on learning: auto-capture + auto-evolve |
|
|
81
81
|
| `helixevo metrics` | Correction rates, skill trends, evolution impact |
|
|
82
82
|
| `helixevo health` | Network health: cohesion, coverage, balance, transfer |
|
|
83
|
-
| `helixevo init` | Import existing skills + generate
|
|
83
|
+
| `helixevo init` | Import existing skills + generate skill tests |
|
|
84
84
|
| `helixevo capture <session>` | Extract failures from a session file |
|
|
85
85
|
| `helixevo evolve` | Evolve skills from captured failures |
|
|
86
86
|
| `helixevo generalize` | Promote cross-project patterns ↑ |
|
|
@@ -126,7 +126,7 @@ All data is stored in `~/.helix/`:
|
|
|
126
126
|
├── failures.jsonl # Captured failures
|
|
127
127
|
├── frontier.json # Pareto frontier (top-k configurations)
|
|
128
128
|
├── evolution-history.json # All evolution runs + proposals
|
|
129
|
-
├──
|
|
129
|
+
├── skill-tests.jsonl # Regression test cases
|
|
130
130
|
├── skill-graph.json # Cached network (nodes + edges)
|
|
131
131
|
├── canary-registry.json # Active canary deployments
|
|
132
132
|
├── knowledge-buffer.json # Research discoveries + drafts
|
|
@@ -122,7 +122,7 @@ function analyzeNetworkAdaptation(
|
|
|
122
122
|
})
|
|
123
123
|
result.suggestions.push({
|
|
124
124
|
type: 'rewire',
|
|
125
|
-
description: `Review
|
|
125
|
+
description: `Review skill tests for [${partners.join(', ')}] after this change`
|
|
126
126
|
})
|
|
127
127
|
}
|
|
128
128
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useRef } from 'react'
|
|
4
|
+
import { VERSION } from '../../lib/version'
|
|
4
5
|
|
|
5
6
|
// ─── Table of Contents ──────────────────────────────────────────
|
|
6
7
|
const TOC = [
|
|
@@ -148,7 +149,7 @@ function ArchitectureDiagram() {
|
|
|
148
149
|
<div className="guide-diagram-box guide-diagram-check" style={{ direction: 'ltr' }}>
|
|
149
150
|
<div className="guide-diagram-box-label">Validate</div>
|
|
150
151
|
<div className="guide-diagram-box-title">Regression Tests</div>
|
|
151
|
-
<div className="guide-diagram-box-desc">
|
|
152
|
+
<div className="guide-diagram-box-desc">Skill tests + cross-skill</div>
|
|
152
153
|
</div>
|
|
153
154
|
<div className="guide-diagram-arrow" style={{ direction: 'ltr' }}>←</div>
|
|
154
155
|
<div className="guide-diagram-box guide-diagram-judge" style={{ direction: 'ltr' }}>
|
|
@@ -226,7 +227,7 @@ export default function GuidePage() {
|
|
|
226
227
|
<nav className="guide-toc">
|
|
227
228
|
<div className="guide-toc-header">
|
|
228
229
|
<div className="guide-toc-title">Documentation</div>
|
|
229
|
-
<div className="guide-toc-version">
|
|
230
|
+
<div className="guide-toc-version">v{VERSION}</div>
|
|
230
231
|
</div>
|
|
231
232
|
{TOC.map(item => (
|
|
232
233
|
<a
|
|
@@ -317,7 +318,7 @@ cd helixevo && npm install && npm run build && npm link`}</Code>
|
|
|
317
318
|
<Code title="Terminal">{`helixevo init`}</Code>
|
|
318
319
|
<p className="guide-text-sm">
|
|
319
320
|
This scans your existing SKILL.md files (from <code>~/.agents/skills/</code>), imports them into HelixEvo,
|
|
320
|
-
and generates
|
|
321
|
+
and generates skill tests for each skill. It also creates the data directory at <code>~/.helix/</code>.
|
|
321
322
|
</p>
|
|
322
323
|
</Step>
|
|
323
324
|
|
|
@@ -370,7 +371,7 @@ helixevo status`}</Code>
|
|
|
370
371
|
},
|
|
371
372
|
{
|
|
372
373
|
cmd: 'helixevo init',
|
|
373
|
-
desc: 'Import existing skills and generate
|
|
374
|
+
desc: 'Import existing skills and generate skill tests. Scans ~/.agents/skills/ and creates the HelixEvo data directory.',
|
|
374
375
|
flags: ['--verbose'],
|
|
375
376
|
},
|
|
376
377
|
{
|
|
@@ -545,7 +546,7 @@ Project B: "Use FlashList not FlatList" (React Native perf)
|
|
|
545
546
|
→ Abstract skill created: "react-native-performance" (domain layer)
|
|
546
547
|
→ Project A skill inherits from it
|
|
547
548
|
→ Project B skill inherits from it
|
|
548
|
-
→ Domain skill tested against all
|
|
549
|
+
→ Domain skill tested against all skill tests
|
|
549
550
|
→ Deployed if regression passes`}</Code>
|
|
550
551
|
<Callout type="tip">
|
|
551
552
|
Auto-generalization is the key to the <strong>double helix</strong> metaphor: as projects evolve, skills
|
|
@@ -601,7 +602,7 @@ Project B: "Use FlashList not FlatList" (React Native perf)
|
|
|
601
602
|
<div className="guide-pipeline-connector" />
|
|
602
603
|
<PipelineStep
|
|
603
604
|
icon="5" title="Regression Testing" color="var(--red)"
|
|
604
|
-
desc="The modified skill is tested against all
|
|
605
|
+
desc="The modified skill is tested against all skill tests for that skill AND co-evolved partner skills. Must maintain ≥95% pass rate."
|
|
605
606
|
/>
|
|
606
607
|
<div className="guide-pipeline-connector" />
|
|
607
608
|
<PipelineStep
|
|
@@ -693,29 +694,29 @@ Project B: "Use FlashList not FlatList" (React Native perf)
|
|
|
693
694
|
</Section>
|
|
694
695
|
|
|
695
696
|
{/* ─── Regression Testing ─── */}
|
|
696
|
-
<Section id="regression" title="Regression Testing" subtitle="
|
|
697
|
-
<h3 className="guide-h3">
|
|
697
|
+
<Section id="regression" title="Regression Testing" subtitle="Skill tests and cross-skill validation ensure quality.">
|
|
698
|
+
<h3 className="guide-h3">Skill Tests</h3>
|
|
698
699
|
<p className="guide-text">
|
|
699
|
-
|
|
700
|
+
Skill tests are regression test scenarios tied to specific skills. They're created when:
|
|
700
701
|
</p>
|
|
701
702
|
<ul className="guide-list">
|
|
702
703
|
<li><strong>Init:</strong> Automatically generated from existing SKILL.md files during <code>helixevo init</code></li>
|
|
703
|
-
<li><strong>Evolution:</strong> When a failure is resolved, the scenario is promoted to a
|
|
704
|
+
<li><strong>Evolution:</strong> When a failure is resolved, the scenario is promoted to a skill test</li>
|
|
704
705
|
</ul>
|
|
705
706
|
<p className="guide-text">
|
|
706
|
-
Each
|
|
707
|
+
Each skill test stores the input, context, and expected behavior. During regression testing,
|
|
707
708
|
an LLM judge evaluates whether the modified skill would still handle each scenario correctly.
|
|
708
709
|
</p>
|
|
709
710
|
|
|
710
711
|
<h3 className="guide-h3">Cross-Skill Regression</h3>
|
|
711
712
|
<p className="guide-text">
|
|
712
|
-
When skill A is modified, HelixEvo also tests
|
|
713
|
+
When skill A is modified, HelixEvo also tests skill tests from co-evolved, dependent, and enhancing
|
|
713
714
|
partner skills. This catches silent incompatibilities where changing one skill breaks a related skill's behavior.
|
|
714
715
|
</p>
|
|
715
716
|
<Code title="How it works">{`Skill A evolves
|
|
716
717
|
→ Load skill graph edges
|
|
717
718
|
→ Find partners (co-evolves, depends, enhances)
|
|
718
|
-
→ Test partner
|
|
719
|
+
→ Test partner skill tests against Skill A's changes
|
|
719
720
|
→ Block if partner pass rate < 95%`}</Code>
|
|
720
721
|
</Section>
|
|
721
722
|
|
|
@@ -796,10 +797,10 @@ generation: 3
|
|
|
796
797
|
<div className="guide-params">
|
|
797
798
|
<Param name="quality.judgePassScore" type="number" desc="Minimum judge score to pass (1-10)." def="7" />
|
|
798
799
|
<Param name="quality.judgeConsensusMin" type="number" desc="Minimum judges that must pass." def="2" />
|
|
799
|
-
<Param name="quality.regressionPassRate" type="number" desc="Minimum
|
|
800
|
+
<Param name="quality.regressionPassRate" type="number" desc="Minimum skill test pass rate (0-1)." def="0.95" />
|
|
800
801
|
<Param name="quality.canaryDurationDays" type="number" desc="Days to monitor canary deployments." def="3" />
|
|
801
802
|
<Param name="quality.autoRollbackThreshold" type="number" desc="Failure rate multiplier triggering rollback." def="1.5" />
|
|
802
|
-
<Param name="quality.
|
|
803
|
+
<Param name="quality.maxSkillTests" type="number" desc="Maximum skill tests per skill." def="50" />
|
|
803
804
|
</div>
|
|
804
805
|
|
|
805
806
|
<Code title="~/.helix/config.json">{`{
|
|
@@ -819,7 +820,7 @@ generation: 3
|
|
|
819
820
|
"regressionPassRate": 0.95,
|
|
820
821
|
"canaryDurationDays": 3,
|
|
821
822
|
"autoRollbackThreshold": 1.5,
|
|
822
|
-
"
|
|
823
|
+
"maxSkillTests": 50
|
|
823
824
|
}
|
|
824
825
|
}`}</Code>
|
|
825
826
|
</Section>
|
|
@@ -831,7 +832,7 @@ generation: 3
|
|
|
831
832
|
├── failures.jsonl # Captured failure records (append-only)
|
|
832
833
|
├── frontier.json # Pareto frontier (top-K programs)
|
|
833
834
|
├── evolution-history.json # All evolution iterations + proposals
|
|
834
|
-
├──
|
|
835
|
+
├── skill-tests.jsonl # Regression test cases (append-only)
|
|
835
836
|
├── skill-graph.json # Cached network (nodes + edges)
|
|
836
837
|
├── canary-registry.json # Active canary deployments
|
|
837
838
|
├── knowledge-buffer.json # Research discoveries + drafts
|
|
@@ -858,7 +859,7 @@ generation: 3
|
|
|
858
859
|
}`}</Code>
|
|
859
860
|
</div>
|
|
860
861
|
<div className="guide-data-card">
|
|
861
|
-
<div className="guide-data-title">
|
|
862
|
+
<div className="guide-data-title">Skill Test</div>
|
|
862
863
|
<Code>{`{
|
|
863
864
|
"id": "gc_react_42",
|
|
864
865
|
"skill": "react-patterns",
|
|
@@ -949,7 +950,7 @@ generation: 3
|
|
|
949
950
|
</FAQItem>
|
|
950
951
|
<FAQItem q="How does cross-skill regression work?">
|
|
951
952
|
When Skill A evolves, HelixEvo checks the skill graph for co-evolved, dependent, and enhancing
|
|
952
|
-
partners. It tests their
|
|
953
|
+
partners. It tests their skill tests against Skill A's changes. If partner pass rate drops below 95%,
|
|
953
954
|
the proposal is rejected.
|
|
954
955
|
</FAQItem>
|
|
955
956
|
<FAQItem q="How does the knowledge buffer work?">
|
|
@@ -982,7 +983,7 @@ generation: 3
|
|
|
982
983
|
{/* Footer */}
|
|
983
984
|
<div className="guide-footer">
|
|
984
985
|
<div className="guide-footer-content">
|
|
985
|
-
<div style={{ fontSize: 13, fontWeight: 600 }}>HelixEvo
|
|
986
|
+
<div style={{ fontSize: 13, fontWeight: 600 }}>HelixEvo v{VERSION}</div>
|
|
986
987
|
<div style={{ fontSize: 12, color: 'var(--text-dim)', marginTop: 4 }}>
|
|
987
988
|
Self-evolving skill ecosystem for AI agents · MIT License
|
|
988
989
|
</div>
|
package/dashboard/app/layout.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import './globals.css'
|
|
2
2
|
import type { Metadata } from 'next'
|
|
3
3
|
import Link from 'next/link'
|
|
4
|
+
import { VERSION } from '../lib/version'
|
|
5
|
+
import { UpdateBanner } from '../components/update-banner'
|
|
4
6
|
|
|
5
7
|
export const metadata: Metadata = {
|
|
6
8
|
title: 'HelixEvo',
|
|
@@ -64,7 +66,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
64
66
|
<div style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--green)' }} />
|
|
65
67
|
<span>System Active</span>
|
|
66
68
|
</div>
|
|
67
|
-
<code style={{ fontSize: 10, background: 'var(--bg-section)', padding: '2px 6px', borderRadius: 4 }}>
|
|
69
|
+
<code style={{ fontSize: 10, background: 'var(--bg-section)', padding: '2px 6px', borderRadius: 4 }}>v{VERSION}</code>
|
|
68
70
|
</div>
|
|
69
71
|
</nav>
|
|
70
72
|
|
|
@@ -73,6 +75,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
73
75
|
{children}
|
|
74
76
|
</main>
|
|
75
77
|
</div>
|
|
78
|
+
<UpdateBanner currentVersion={VERSION} />
|
|
76
79
|
</body>
|
|
77
80
|
</html>
|
|
78
81
|
)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react'
|
|
4
|
+
|
|
5
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/helixevo/latest'
|
|
6
|
+
const CHECK_INTERVAL_MS = 60 * 60 * 1000 // re-check every hour
|
|
7
|
+
|
|
8
|
+
function compareVersions(current: string, latest: string): boolean {
|
|
9
|
+
const c = current.split('.').map(Number)
|
|
10
|
+
const l = latest.split('.').map(Number)
|
|
11
|
+
for (let i = 0; i < 3; i++) {
|
|
12
|
+
if ((l[i] ?? 0) > (c[i] ?? 0)) return true
|
|
13
|
+
if ((l[i] ?? 0) < (c[i] ?? 0)) return false
|
|
14
|
+
}
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
19
|
+
const [latestVersion, setLatestVersion] = useState<string | null>(null)
|
|
20
|
+
const [dismissed, setDismissed] = useState(false)
|
|
21
|
+
const [copied, setCopied] = useState(false)
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
let mounted = true
|
|
25
|
+
|
|
26
|
+
async function check() {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(REGISTRY_URL)
|
|
29
|
+
if (!res.ok) return
|
|
30
|
+
const data = await res.json()
|
|
31
|
+
if (mounted && data.version && compareVersions(currentVersion, data.version)) {
|
|
32
|
+
setLatestVersion(data.version)
|
|
33
|
+
}
|
|
34
|
+
} catch {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
check()
|
|
38
|
+
const interval = setInterval(check, CHECK_INTERVAL_MS)
|
|
39
|
+
return () => { mounted = false; clearInterval(interval) }
|
|
40
|
+
}, [currentVersion])
|
|
41
|
+
|
|
42
|
+
if (!latestVersion || dismissed) return null
|
|
43
|
+
|
|
44
|
+
const command = 'npm install -g helixevo@latest'
|
|
45
|
+
|
|
46
|
+
const handleCopy = async () => {
|
|
47
|
+
try {
|
|
48
|
+
await navigator.clipboard.writeText(command)
|
|
49
|
+
setCopied(true)
|
|
50
|
+
setTimeout(() => setCopied(false), 2000)
|
|
51
|
+
} catch {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div style={{
|
|
56
|
+
position: 'fixed',
|
|
57
|
+
bottom: 20,
|
|
58
|
+
right: 20,
|
|
59
|
+
width: 320,
|
|
60
|
+
background: 'var(--bg-card)',
|
|
61
|
+
border: '1px solid var(--purple-border)',
|
|
62
|
+
borderRadius: 'var(--radius-lg)',
|
|
63
|
+
boxShadow: 'var(--shadow-xl)',
|
|
64
|
+
padding: '16px 18px',
|
|
65
|
+
zIndex: 9999,
|
|
66
|
+
animation: 'updateSlideIn 0.4s ease-out',
|
|
67
|
+
}}>
|
|
68
|
+
{/* Close button */}
|
|
69
|
+
<button
|
|
70
|
+
onClick={() => setDismissed(true)}
|
|
71
|
+
style={{
|
|
72
|
+
position: 'absolute', top: 8, right: 10,
|
|
73
|
+
background: 'none', border: 'none', cursor: 'pointer',
|
|
74
|
+
color: 'var(--text-dim)', fontSize: 18, lineHeight: 1,
|
|
75
|
+
padding: '2px 4px',
|
|
76
|
+
}}
|
|
77
|
+
aria-label="Dismiss"
|
|
78
|
+
>
|
|
79
|
+
×
|
|
80
|
+
</button>
|
|
81
|
+
|
|
82
|
+
{/* Header */}
|
|
83
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
|
|
84
|
+
<div style={{
|
|
85
|
+
width: 28, height: 28, borderRadius: '50%',
|
|
86
|
+
background: 'var(--purple-light)', display: 'flex',
|
|
87
|
+
alignItems: 'center', justifyContent: 'center',
|
|
88
|
+
}}>
|
|
89
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--purple)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
90
|
+
<path d="M12 19V5m-7 7l7-7 7 7" />
|
|
91
|
+
</svg>
|
|
92
|
+
</div>
|
|
93
|
+
<div>
|
|
94
|
+
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', letterSpacing: -0.2 }}>
|
|
95
|
+
Update Available
|
|
96
|
+
</div>
|
|
97
|
+
<div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
|
|
98
|
+
v{currentVersion} → <span style={{ color: 'var(--green)', fontWeight: 600 }}>v{latestVersion}</span>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{/* Command */}
|
|
104
|
+
<div
|
|
105
|
+
onClick={handleCopy}
|
|
106
|
+
style={{
|
|
107
|
+
background: 'var(--bg-section)',
|
|
108
|
+
border: '1px solid var(--border)',
|
|
109
|
+
borderRadius: 'var(--radius)',
|
|
110
|
+
padding: '8px 12px',
|
|
111
|
+
fontFamily: 'var(--font-mono)',
|
|
112
|
+
fontSize: 11,
|
|
113
|
+
color: 'var(--text-secondary)',
|
|
114
|
+
cursor: 'pointer',
|
|
115
|
+
display: 'flex',
|
|
116
|
+
alignItems: 'center',
|
|
117
|
+
justifyContent: 'space-between',
|
|
118
|
+
gap: 8,
|
|
119
|
+
transition: 'border-color 0.15s',
|
|
120
|
+
}}
|
|
121
|
+
title="Click to copy"
|
|
122
|
+
>
|
|
123
|
+
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
124
|
+
$ {command}
|
|
125
|
+
</span>
|
|
126
|
+
<span style={{
|
|
127
|
+
fontSize: 10, fontFamily: 'var(--font)', fontWeight: 600,
|
|
128
|
+
color: copied ? 'var(--green)' : 'var(--purple)',
|
|
129
|
+
whiteSpace: 'nowrap',
|
|
130
|
+
flexShrink: 0,
|
|
131
|
+
}}>
|
|
132
|
+
{copied ? 'Copied!' : 'Copy'}
|
|
133
|
+
</span>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{/* Subtle hint */}
|
|
137
|
+
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 8, textAlign: 'center' }}>
|
|
138
|
+
Run in terminal, then refresh dashboard
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<style>{`
|
|
142
|
+
@keyframes updateSlideIn {
|
|
143
|
+
from { opacity: 0; transform: translateY(16px) scale(0.97); }
|
|
144
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
145
|
+
}
|
|
146
|
+
`}</style>
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
}
|
package/dashboard/lib/data.ts
CHANGED
|
@@ -89,7 +89,9 @@ export function loadHistory(): { iterations: Iteration[] } {
|
|
|
89
89
|
return readJson<{ iterations: Iteration[] }>('evolution-history.json', { iterations: [] })
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
export function
|
|
92
|
+
export function loadSkillTests(): { id: string; skill: string; input: string }[] {
|
|
93
|
+
const newFile = readJsonl('skill-tests.jsonl')
|
|
94
|
+
if (newFile.length > 0) return newFile
|
|
93
95
|
return readJsonl('golden-cases.jsonl')
|
|
94
96
|
}
|
|
95
97
|
|
|
@@ -126,7 +128,7 @@ export function getDashboardSummary() {
|
|
|
126
128
|
const history = loadHistory()
|
|
127
129
|
const buffer = loadBuffer()
|
|
128
130
|
const canaries = loadCanaries()
|
|
129
|
-
const
|
|
131
|
+
const skillTests = loadSkillTests()
|
|
130
132
|
|
|
131
133
|
const evolved = graph.nodes.filter(n => n.generation > 0)
|
|
132
134
|
const totalProposals = history.iterations.flatMap(i => i.proposals)
|
|
@@ -141,6 +143,6 @@ export function getDashboardSummary() {
|
|
|
141
143
|
evolution: { runs: history.iterations.length, accepted: accepted.length, rejected: rejected.length },
|
|
142
144
|
buffer: { discoveries: buffer.discoveries.length, drafts: buffer.drafts.length },
|
|
143
145
|
canaries: canaries.entries.length,
|
|
144
|
-
|
|
146
|
+
skillTests: skillTests.length,
|
|
145
147
|
}
|
|
146
148
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = process.env.HELIXEVO_VERSION ?? '0.0.0'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFileSync } from 'fs'
|
|
2
|
+
import { join, dirname } from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
6
|
+
|
|
7
|
+
function getVersion() {
|
|
8
|
+
const candidates = [
|
|
9
|
+
join(__dirname, '..', 'package.json'), // dev: repo root
|
|
10
|
+
join(__dirname, 'package.json'), // fallback
|
|
11
|
+
]
|
|
12
|
+
for (const p of candidates) {
|
|
13
|
+
try {
|
|
14
|
+
const pkg = JSON.parse(readFileSync(p, 'utf-8'))
|
|
15
|
+
if (pkg.name === 'helixevo' && pkg.version) return pkg.version
|
|
16
|
+
} catch {}
|
|
17
|
+
}
|
|
18
|
+
return '0.0.0'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** @type {import('next').NextConfig} */
|
|
22
|
+
const nextConfig = {
|
|
23
|
+
env: {
|
|
24
|
+
HELIXEVO_VERSION: getVersion(),
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default nextConfig
|
package/dist/cli.js
CHANGED
|
@@ -2129,7 +2129,7 @@ var init_config = __esm(() => {
|
|
|
2129
2129
|
regressionPassRate: 0.95,
|
|
2130
2130
|
canaryDurationDays: 3,
|
|
2131
2131
|
autoRollbackThreshold: 1.5,
|
|
2132
|
-
|
|
2132
|
+
maxSkillTests: 50
|
|
2133
2133
|
},
|
|
2134
2134
|
reporting: {
|
|
2135
2135
|
schedule: "0 8 * * *",
|
|
@@ -9226,11 +9226,14 @@ function loadHistory() {
|
|
|
9226
9226
|
function saveHistory(history) {
|
|
9227
9227
|
writeJson("evolution-history.json", history);
|
|
9228
9228
|
}
|
|
9229
|
-
function
|
|
9229
|
+
function loadSkillTests() {
|
|
9230
|
+
const newFile = readJsonl("skill-tests.jsonl");
|
|
9231
|
+
if (newFile.length > 0)
|
|
9232
|
+
return newFile;
|
|
9230
9233
|
return readJsonl("golden-cases.jsonl");
|
|
9231
9234
|
}
|
|
9232
|
-
function
|
|
9233
|
-
appendJsonl("
|
|
9235
|
+
function appendSkillTest(gc) {
|
|
9236
|
+
appendJsonl("skill-tests.jsonl", gc);
|
|
9234
9237
|
}
|
|
9235
9238
|
function loadSkillGraph() {
|
|
9236
9239
|
return readJson("skill-graph.json", {
|
|
@@ -9573,12 +9576,12 @@ init_config();
|
|
|
9573
9576
|
init_skills();
|
|
9574
9577
|
init_data();
|
|
9575
9578
|
init_llm();
|
|
9576
|
-
import { join as
|
|
9579
|
+
import { join as join4 } from "node:path";
|
|
9577
9580
|
import { homedir as homedir2 } from "node:os";
|
|
9578
9581
|
import { existsSync as existsSync4, cpSync } from "node:fs";
|
|
9579
9582
|
|
|
9580
|
-
// src/prompts/
|
|
9581
|
-
function
|
|
9583
|
+
// src/prompts/test-gen.ts
|
|
9584
|
+
function buildTestGenPrompt(skill) {
|
|
9582
9585
|
return `Read this skill and generate 3 typical usage scenarios where the skill should guide correct behavior.
|
|
9583
9586
|
|
|
9584
9587
|
## Skill: ${skill.meta.name}
|
|
@@ -9602,21 +9605,29 @@ Return JSON:
|
|
|
9602
9605
|
- Expected behavior should be concrete enough to verify against`;
|
|
9603
9606
|
}
|
|
9604
9607
|
|
|
9608
|
+
// src/version.ts
|
|
9609
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
9610
|
+
import { join as join3, dirname as dirname2 } from "node:path";
|
|
9611
|
+
import { fileURLToPath } from "node:url";
|
|
9612
|
+
var require2 = createRequire2(import.meta.url);
|
|
9613
|
+
var pkg = require2(join3(dirname2(fileURLToPath(import.meta.url)), "..", "package.json"));
|
|
9614
|
+
var VERSION = pkg.version;
|
|
9615
|
+
|
|
9605
9616
|
// src/commands/init.ts
|
|
9606
9617
|
async function initCommand(options) {
|
|
9607
|
-
console.log(`\uD83E\uDDEC Initializing HelixEvo...
|
|
9618
|
+
console.log(`\uD83E\uDDEC Initializing HelixEvo v${VERSION}...
|
|
9608
9619
|
`);
|
|
9609
9620
|
const sgDir = getHelixDir();
|
|
9610
9621
|
const generalDir = getGeneralSkillsPath();
|
|
9611
9622
|
ensureDir(sgDir);
|
|
9612
9623
|
ensureDir(generalDir);
|
|
9613
|
-
if (!existsSync4(
|
|
9624
|
+
if (!existsSync4(join4(sgDir, "config.json"))) {
|
|
9614
9625
|
saveConfig(DEFAULT_CONFIG);
|
|
9615
9626
|
console.log(" ✓ Created config.json");
|
|
9616
9627
|
}
|
|
9617
9628
|
const defaultPaths = [
|
|
9618
|
-
|
|
9619
|
-
|
|
9629
|
+
join4(homedir2(), ".agents", "skills"),
|
|
9630
|
+
join4(homedir2(), ".craft-agent", "workspaces")
|
|
9620
9631
|
];
|
|
9621
9632
|
const scanPaths = options.skillsPaths ?? defaultPaths;
|
|
9622
9633
|
const expandedPaths = [];
|
|
@@ -9625,7 +9636,7 @@ async function initCommand(options) {
|
|
|
9625
9636
|
const { readdirSync: readdirSync2 } = await import("node:fs");
|
|
9626
9637
|
const workspaces = readdirSync2(p, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
|
|
9627
9638
|
for (const ws of workspaces) {
|
|
9628
|
-
const skillsDir =
|
|
9639
|
+
const skillsDir = join4(p, ws.name, "skills");
|
|
9629
9640
|
if (existsSync4(skillsDir))
|
|
9630
9641
|
expandedPaths.push(skillsDir);
|
|
9631
9642
|
}
|
|
@@ -9638,7 +9649,7 @@ async function initCommand(options) {
|
|
|
9638
9649
|
`);
|
|
9639
9650
|
let imported = 0;
|
|
9640
9651
|
for (const skill of existingSkills) {
|
|
9641
|
-
const targetDir =
|
|
9652
|
+
const targetDir = join4(generalDir, skill.slug);
|
|
9642
9653
|
if (existsSync4(targetDir)) {
|
|
9643
9654
|
console.log(` → ${skill.slug}: already exists, skipping`);
|
|
9644
9655
|
continue;
|
|
@@ -9650,13 +9661,37 @@ async function initCommand(options) {
|
|
|
9650
9661
|
console.log(`
|
|
9651
9662
|
Imported ${imported} new skills
|
|
9652
9663
|
`);
|
|
9653
|
-
|
|
9664
|
+
const allSkills = loadAllGeneralSkills();
|
|
9665
|
+
if (allSkills.length > 0) {
|
|
9666
|
+
const nodes = allSkills.map((s) => ({
|
|
9667
|
+
id: s.slug,
|
|
9668
|
+
name: s.slug,
|
|
9669
|
+
layer: "system",
|
|
9670
|
+
score: 0.7,
|
|
9671
|
+
generation: 0,
|
|
9672
|
+
status: "active",
|
|
9673
|
+
tags: [],
|
|
9674
|
+
failureCount: 0,
|
|
9675
|
+
lastEvolved: new Date().toISOString()
|
|
9676
|
+
}));
|
|
9677
|
+
const graph = {
|
|
9678
|
+
updated: new Date().toISOString(),
|
|
9679
|
+
nodes,
|
|
9680
|
+
edges: [],
|
|
9681
|
+
clusters: []
|
|
9682
|
+
};
|
|
9683
|
+
saveSkillGraph(graph);
|
|
9684
|
+
console.log(` ✓ Generated skill graph (${nodes.length} nodes)`);
|
|
9685
|
+
console.log(` Run "helixevo graph --rebuild" later for full relationship inference
|
|
9686
|
+
`);
|
|
9687
|
+
}
|
|
9688
|
+
if (!options.skipTests) {
|
|
9654
9689
|
const generalSkills = loadAllGeneralSkills();
|
|
9655
|
-
console.log(` Generating
|
|
9690
|
+
console.log(` Generating skill tests...
|
|
9656
9691
|
`);
|
|
9657
9692
|
for (const skill of generalSkills) {
|
|
9658
9693
|
try {
|
|
9659
|
-
const prompt =
|
|
9694
|
+
const prompt = buildTestGenPrompt(skill);
|
|
9660
9695
|
const output = await chatJson({ prompt });
|
|
9661
9696
|
for (const c of output.cases) {
|
|
9662
9697
|
const gc = {
|
|
@@ -9671,11 +9706,11 @@ async function initCommand(options) {
|
|
|
9671
9706
|
lastResult: "pass",
|
|
9672
9707
|
consecutivePasses: 1
|
|
9673
9708
|
};
|
|
9674
|
-
|
|
9709
|
+
appendSkillTest(gc);
|
|
9675
9710
|
}
|
|
9676
|
-
console.log(` ✓ ${skill.slug}: ${output.cases.length}
|
|
9711
|
+
console.log(` ✓ ${skill.slug}: ${output.cases.length} skill tests`);
|
|
9677
9712
|
} catch (err) {
|
|
9678
|
-
console.log(` ✗ ${skill.slug}: failed to generate
|
|
9713
|
+
console.log(` ✗ ${skill.slug}: failed to generate skill tests (${err})`);
|
|
9679
9714
|
}
|
|
9680
9715
|
}
|
|
9681
9716
|
}
|
|
@@ -9770,7 +9805,7 @@ init_config();
|
|
|
9770
9805
|
init_data();
|
|
9771
9806
|
init_skills();
|
|
9772
9807
|
init_llm();
|
|
9773
|
-
import { join as
|
|
9808
|
+
import { join as join7 } from "node:path";
|
|
9774
9809
|
|
|
9775
9810
|
// src/prompts/cluster.ts
|
|
9776
9811
|
function buildClusterPrompt(failures, skills) {
|
|
@@ -9989,11 +10024,11 @@ init_config();
|
|
|
9989
10024
|
init_llm();
|
|
9990
10025
|
async function runRegression(skillSlug, newSkillContent, verbose = false) {
|
|
9991
10026
|
const config = loadConfig();
|
|
9992
|
-
const allCases =
|
|
10027
|
+
const allCases = loadSkillTests();
|
|
9993
10028
|
const cases = allCases.filter((gc) => gc.skill === skillSlug);
|
|
9994
10029
|
if (cases.length === 0) {
|
|
9995
10030
|
if (verbose)
|
|
9996
|
-
console.log(` No
|
|
10031
|
+
console.log(` No skill tests for ${skillSlug}, skipping regression`);
|
|
9997
10032
|
return { total: 0, passed: 0, passRate: 1, failures: [] };
|
|
9998
10033
|
}
|
|
9999
10034
|
const failures = [];
|
|
@@ -10020,7 +10055,7 @@ async function runRegression(skillSlug, newSkillContent, verbose = false) {
|
|
|
10020
10055
|
failures
|
|
10021
10056
|
};
|
|
10022
10057
|
}
|
|
10023
|
-
function
|
|
10058
|
+
function promoteToSkillTest(failure, skillSlug, replayResult) {
|
|
10024
10059
|
const gc = {
|
|
10025
10060
|
id: `gc_${skillSlug}_${Date.now() % 1e5}`,
|
|
10026
10061
|
addedAt: new Date().toISOString(),
|
|
@@ -10033,7 +10068,7 @@ function promoteToGoldenCase(failure, skillSlug, replayResult) {
|
|
|
10033
10068
|
lastResult: "pass",
|
|
10034
10069
|
consecutivePasses: 1
|
|
10035
10070
|
};
|
|
10036
|
-
|
|
10071
|
+
appendSkillTest(gc);
|
|
10037
10072
|
}
|
|
10038
10073
|
function buildRegressionJudgePrompt(gc, skillContent) {
|
|
10039
10074
|
return `You are a regression test judge. Determine if a modified skill can still handle this scenario correctly.
|
|
@@ -10072,7 +10107,7 @@ async function runCrossSkillRegression(skillSlug, newSkillContent, verbose = fal
|
|
|
10072
10107
|
return { total: 0, passed: 0, passRate: 1, testedSkills: [] };
|
|
10073
10108
|
}
|
|
10074
10109
|
const config = loadConfig();
|
|
10075
|
-
const allCases =
|
|
10110
|
+
const allCases = loadSkillTests();
|
|
10076
10111
|
const partnerCases = allCases.filter((gc) => partners.includes(gc.skill));
|
|
10077
10112
|
if (partnerCases.length === 0) {
|
|
10078
10113
|
return { total: 0, passed: 0, passRate: 1, testedSkills: partners };
|
|
@@ -10120,11 +10155,11 @@ Return JSON: { "score": <number>, "reason": "<one sentence>" }`;
|
|
|
10120
10155
|
// src/core/canary.ts
|
|
10121
10156
|
init_config();
|
|
10122
10157
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6, cpSync as cpSync2, rmSync } from "node:fs";
|
|
10123
|
-
import { join as
|
|
10124
|
-
var CANARY_DIR =
|
|
10125
|
-
var BACKUPS_DIR =
|
|
10158
|
+
import { join as join5 } from "node:path";
|
|
10159
|
+
var CANARY_DIR = join5(getHelixDir(), "canary");
|
|
10160
|
+
var BACKUPS_DIR = join5(getHelixDir(), "backups");
|
|
10126
10161
|
function getRegistryPath() {
|
|
10127
|
-
return
|
|
10162
|
+
return join5(getHelixDir(), "canary-registry.json");
|
|
10128
10163
|
}
|
|
10129
10164
|
function loadRegistry() {
|
|
10130
10165
|
const path = getRegistryPath();
|
|
@@ -10137,7 +10172,7 @@ function saveRegistry(registry) {
|
|
|
10137
10172
|
}
|
|
10138
10173
|
function backupSkill(skillPath, slug, version) {
|
|
10139
10174
|
ensureDir(BACKUPS_DIR);
|
|
10140
|
-
const backupPath =
|
|
10175
|
+
const backupPath = join5(BACKUPS_DIR, `${slug}_${version}_${Date.now()}`);
|
|
10141
10176
|
if (existsSync6(skillPath)) {
|
|
10142
10177
|
cpSync2(skillPath, backupPath, { recursive: true });
|
|
10143
10178
|
}
|
|
@@ -10219,7 +10254,7 @@ init_config();
|
|
|
10219
10254
|
init_skills();
|
|
10220
10255
|
init_data();
|
|
10221
10256
|
init_llm();
|
|
10222
|
-
import { join as
|
|
10257
|
+
import { join as join6 } from "node:path";
|
|
10223
10258
|
async function autoGeneralize(candidates, verbose = false, dryRun = false) {
|
|
10224
10259
|
const created = [];
|
|
10225
10260
|
const skipped = [];
|
|
@@ -10288,7 +10323,7 @@ Keep it focused and actionable. No filler.`
|
|
|
10288
10323
|
meta.lastEvolved = new Date().toISOString();
|
|
10289
10324
|
meta.tags = [...meta.tags ?? [], "auto-generalized"];
|
|
10290
10325
|
meta.enhances = candidate.sourceSkills;
|
|
10291
|
-
const skillDir =
|
|
10326
|
+
const skillDir = join6(getGeneralSkillsPath(), candidate.suggestedName);
|
|
10292
10327
|
writeSkill(skillDir, meta, content);
|
|
10293
10328
|
created.push(candidate.suggestedName);
|
|
10294
10329
|
console.log(` ✓ Created: ${candidate.suggestedName} (domain layer)`);
|
|
@@ -10485,7 +10520,7 @@ async function evolveCommand(options) {
|
|
|
10485
10520
|
if (finalAccepted && !dryRun) {
|
|
10486
10521
|
const { meta, content } = parseSkillMd(proposalOutput.proposedSkillMd);
|
|
10487
10522
|
const skillSlug2 = proposalOutput.targetSkill ?? proposal.targetSkill;
|
|
10488
|
-
const skillDir =
|
|
10523
|
+
const skillDir = join7(getGeneralSkillsPath(), skillSlug2);
|
|
10489
10524
|
meta.generation = generation;
|
|
10490
10525
|
meta.score = avgScore;
|
|
10491
10526
|
meta.lastEvolved = new Date().toISOString();
|
|
@@ -10496,7 +10531,7 @@ async function evolveCommand(options) {
|
|
|
10496
10531
|
const skillFailureCount = allFailures.filter((f) => f.skillsActive.includes(skillSlug2)).length;
|
|
10497
10532
|
deployCanary(skillSlug2, `v${generation}`, backupPath, config.quality.canaryDurationDays, skillFailureCount);
|
|
10498
10533
|
console.log(` \uD83D\uDC25 Canary deployed: ${config.quality.canaryDurationDays} day monitoring period`);
|
|
10499
|
-
|
|
10534
|
+
promoteToSkillTest(testFailure, skillSlug2, replayResult);
|
|
10500
10535
|
const program2 = {
|
|
10501
10536
|
id: `gen${generation}-${skillSlug2}`,
|
|
10502
10537
|
generation,
|
|
@@ -10573,9 +10608,9 @@ init_skills();
|
|
|
10573
10608
|
// src/core/knowledge-buffer.ts
|
|
10574
10609
|
init_config();
|
|
10575
10610
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "node:fs";
|
|
10576
|
-
import { join as
|
|
10577
|
-
var BUFFER_PATH = () =>
|
|
10578
|
-
var DRAFTS_DIR = () =>
|
|
10611
|
+
import { join as join8 } from "node:path";
|
|
10612
|
+
var BUFFER_PATH = () => join8(getHelixDir(), "knowledge-buffer.json");
|
|
10613
|
+
var DRAFTS_DIR = () => join8(getHelixDir(), "drafts");
|
|
10579
10614
|
var MAX_DISCOVERIES = 50;
|
|
10580
10615
|
var MAX_DRAFTS = 10;
|
|
10581
10616
|
var DECAY_RATE = 0.1;
|
|
@@ -10632,9 +10667,9 @@ function saveDraft(draft) {
|
|
|
10632
10667
|
existing.avgScore = draft.avgScore;
|
|
10633
10668
|
existing.iteration++;
|
|
10634
10669
|
existing.lastIterated = new Date().toISOString();
|
|
10635
|
-
const draftDir =
|
|
10670
|
+
const draftDir = join8(DRAFTS_DIR(), draft.skillName);
|
|
10636
10671
|
ensureDir(draftDir);
|
|
10637
|
-
writeFileSync5(
|
|
10672
|
+
writeFileSync5(join8(draftDir, "SKILL.md"), draft.skillMd);
|
|
10638
10673
|
}
|
|
10639
10674
|
} else {
|
|
10640
10675
|
buffer.drafts.push({
|
|
@@ -10648,9 +10683,9 @@ function saveDraft(draft) {
|
|
|
10648
10683
|
iteration: 1,
|
|
10649
10684
|
lastIterated: new Date().toISOString()
|
|
10650
10685
|
});
|
|
10651
|
-
const draftDir =
|
|
10686
|
+
const draftDir = join8(DRAFTS_DIR(), draft.skillName);
|
|
10652
10687
|
ensureDir(draftDir);
|
|
10653
|
-
writeFileSync5(
|
|
10688
|
+
writeFileSync5(join8(draftDir, "SKILL.md"), draft.skillMd);
|
|
10654
10689
|
}
|
|
10655
10690
|
trimAndSave(buffer);
|
|
10656
10691
|
}
|
|
@@ -10703,7 +10738,7 @@ async function statusCommand() {
|
|
|
10703
10738
|
const unresolved = failures.filter((f) => !f.resolved);
|
|
10704
10739
|
const frontier = loadFrontier();
|
|
10705
10740
|
const skills = loadAllGeneralSkills();
|
|
10706
|
-
const
|
|
10741
|
+
const skillTests = loadSkillTests();
|
|
10707
10742
|
const stagnation = getStagnationCount();
|
|
10708
10743
|
const recentIter = getRecentIterations(7);
|
|
10709
10744
|
console.log(`\uD83E\uDDEC HelixEvo Status
|
|
@@ -10722,7 +10757,7 @@ async function statusCommand() {
|
|
|
10722
10757
|
}
|
|
10723
10758
|
console.log(`
|
|
10724
10759
|
Failures: ${unresolved.length} unresolved / ${failures.length} total`);
|
|
10725
|
-
console.log(`
|
|
10760
|
+
console.log(` Skill tests: ${skillTests.length}`);
|
|
10726
10761
|
const buffer = getBufferStats();
|
|
10727
10762
|
console.log(`
|
|
10728
10763
|
Knowledge Buffer:`);
|
|
@@ -10750,7 +10785,7 @@ async function statusCommand() {
|
|
|
10750
10785
|
init_data();
|
|
10751
10786
|
init_skills();
|
|
10752
10787
|
import { writeFileSync as writeFileSync6 } from "node:fs";
|
|
10753
|
-
import { join as
|
|
10788
|
+
import { join as join9 } from "node:path";
|
|
10754
10789
|
init_config();
|
|
10755
10790
|
async function reportCommand(options) {
|
|
10756
10791
|
const days = parseInt(options.days ?? "1");
|
|
@@ -10834,8 +10869,8 @@ async function reportCommand(options) {
|
|
|
10834
10869
|
report += `- **${s.slug}**${evolved}
|
|
10835
10870
|
`;
|
|
10836
10871
|
}
|
|
10837
|
-
const outputPath = options.output ??
|
|
10838
|
-
ensureDir(
|
|
10872
|
+
const outputPath = options.output ?? join9(getHelixDir(), "reports", `${date}.md`);
|
|
10873
|
+
ensureDir(join9(getHelixDir(), "reports"));
|
|
10839
10874
|
writeFileSync6(outputPath, report);
|
|
10840
10875
|
console.log(report);
|
|
10841
10876
|
console.log(`
|
|
@@ -10846,7 +10881,7 @@ async function reportCommand(options) {
|
|
|
10846
10881
|
init_config();
|
|
10847
10882
|
init_skills();
|
|
10848
10883
|
init_llm();
|
|
10849
|
-
import { join as
|
|
10884
|
+
import { join as join10 } from "node:path";
|
|
10850
10885
|
async function generalizeCommand(options) {
|
|
10851
10886
|
const verbose = options.verbose ?? false;
|
|
10852
10887
|
const dryRun = options.dryRun ?? false;
|
|
@@ -10909,7 +10944,7 @@ async function generalizeCommand(options) {
|
|
|
10909
10944
|
meta.generation = 1;
|
|
10910
10945
|
meta.score = candidate.confidence;
|
|
10911
10946
|
meta.lastEvolved = new Date().toISOString();
|
|
10912
|
-
const skillDir =
|
|
10947
|
+
const skillDir = join10(getGeneralSkillsPath(), candidate.suggestedName);
|
|
10913
10948
|
writeSkill(skillDir, meta, content);
|
|
10914
10949
|
console.log(` ✓ Created: ${candidate.suggestedName} (${candidate.suggestedLayer} layer)
|
|
10915
10950
|
`);
|
|
@@ -11017,7 +11052,7 @@ Return JSON:
|
|
|
11017
11052
|
init_data();
|
|
11018
11053
|
init_skills();
|
|
11019
11054
|
init_llm();
|
|
11020
|
-
import { join as
|
|
11055
|
+
import { join as join11 } from "node:path";
|
|
11021
11056
|
async function specializeCommand(options) {
|
|
11022
11057
|
const verbose = options.verbose ?? false;
|
|
11023
11058
|
const dryRun = options.dryRun ?? false;
|
|
@@ -11088,8 +11123,8 @@ async function specializeCommand(options) {
|
|
|
11088
11123
|
meta.generation = 1;
|
|
11089
11124
|
meta.score = candidate.confidence;
|
|
11090
11125
|
meta.lastEvolved = new Date().toISOString();
|
|
11091
|
-
const projectSkillsDir =
|
|
11092
|
-
const skillDir =
|
|
11126
|
+
const projectSkillsDir = join11(process.cwd(), ".helix", "skills");
|
|
11127
|
+
const skillDir = join11(projectSkillsDir, candidate.suggestedName);
|
|
11093
11128
|
writeSkill(skillDir, meta, content);
|
|
11094
11129
|
console.log(` ✓ Created: ${candidate.suggestedName} (project layer, parent: ${candidate.domainSkill})
|
|
11095
11130
|
`);
|
|
@@ -11186,7 +11221,7 @@ Return JSON:
|
|
|
11186
11221
|
// src/commands/graph.ts
|
|
11187
11222
|
import { writeFileSync as writeFileSync8 } from "node:fs";
|
|
11188
11223
|
import { execSync } from "node:child_process";
|
|
11189
|
-
import { join as
|
|
11224
|
+
import { join as join13 } from "node:path";
|
|
11190
11225
|
import { tmpdir } from "node:os";
|
|
11191
11226
|
|
|
11192
11227
|
// src/core/network.ts
|
|
@@ -11539,29 +11574,29 @@ function detectMergeCandidates(enhances, coEvolves, skills) {
|
|
|
11539
11574
|
init_data();
|
|
11540
11575
|
init_skills();
|
|
11541
11576
|
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync3 } from "node:fs";
|
|
11542
|
-
import { join as
|
|
11577
|
+
import { join as join12 } from "node:path";
|
|
11543
11578
|
function syncToObsidian(vaultPath, verbose = false) {
|
|
11544
11579
|
const graph = loadSkillGraph();
|
|
11545
11580
|
const skills = loadAllGeneralSkills();
|
|
11546
|
-
const skillsDir =
|
|
11547
|
-
const reportsDir =
|
|
11581
|
+
const skillsDir = join12(vaultPath, "Skills");
|
|
11582
|
+
const reportsDir = join12(vaultPath, "Reports");
|
|
11548
11583
|
mkdirSync3(skillsDir, { recursive: true });
|
|
11549
11584
|
mkdirSync3(reportsDir, { recursive: true });
|
|
11550
11585
|
for (const node of graph.nodes) {
|
|
11551
11586
|
const skill = skills.find((s) => s.slug === node.id);
|
|
11552
11587
|
const note = generateSkillNote(node, graph.edges, skill ?? undefined);
|
|
11553
|
-
const notePath =
|
|
11588
|
+
const notePath = join12(skillsDir, `${node.id}.md`);
|
|
11554
11589
|
writeFileSync7(notePath, note);
|
|
11555
11590
|
if (verbose)
|
|
11556
11591
|
console.log(` ✓ ${node.id}.md`);
|
|
11557
11592
|
}
|
|
11558
11593
|
const indexNote = generateIndexNote(graph);
|
|
11559
|
-
writeFileSync7(
|
|
11594
|
+
writeFileSync7(join12(vaultPath, "HelixEvo Index.md"), indexNote);
|
|
11560
11595
|
const recent = getRecentIterations(7);
|
|
11561
11596
|
if (recent.length > 0) {
|
|
11562
11597
|
const report = generateEvolutionReport(recent);
|
|
11563
11598
|
const date = new Date().toISOString().slice(0, 10);
|
|
11564
|
-
writeFileSync7(
|
|
11599
|
+
writeFileSync7(join12(reportsDir, `Evolution ${date}.md`), report);
|
|
11565
11600
|
}
|
|
11566
11601
|
console.log(` ✓ Synced ${graph.nodes.length} skills to ${vaultPath}`);
|
|
11567
11602
|
}
|
|
@@ -11920,7 +11955,7 @@ ${mermaidCode}
|
|
|
11920
11955
|
});
|
|
11921
11956
|
</script>
|
|
11922
11957
|
</body></html>`;
|
|
11923
|
-
const htmlPath =
|
|
11958
|
+
const htmlPath = join13(tmpdir(), "helix-network.html");
|
|
11924
11959
|
writeFileSync8(htmlPath, html);
|
|
11925
11960
|
execSync(`open "${htmlPath}"`);
|
|
11926
11961
|
console.log(` ✓ Opened in browser`);
|
|
@@ -11985,7 +12020,7 @@ function renderScoreBar(score) {
|
|
|
11985
12020
|
init_config();
|
|
11986
12021
|
init_skills();
|
|
11987
12022
|
init_llm();
|
|
11988
|
-
import { join as
|
|
12023
|
+
import { join as join14 } from "node:path";
|
|
11989
12024
|
import { readFileSync as readFileSync7, existsSync as existsSync9 } from "node:fs";
|
|
11990
12025
|
async function researchCommand(options) {
|
|
11991
12026
|
const verbose = options.verbose ?? false;
|
|
@@ -12101,7 +12136,7 @@ Only include discoveries that are NOT already covered by current skills.`
|
|
|
12101
12136
|
meta.score = result.avgScore / 10;
|
|
12102
12137
|
meta.lastEvolved = new Date().toISOString();
|
|
12103
12138
|
meta.tags = [...meta.tags ?? [], "research-discovered"];
|
|
12104
|
-
const skillDir =
|
|
12139
|
+
const skillDir = join14(getGeneralSkillsPath(), hypothesis.skillName);
|
|
12105
12140
|
writeSkill(skillDir, meta, content);
|
|
12106
12141
|
console.log(` ✓ Created: ${hypothesis.skillName} (from research)
|
|
12107
12142
|
`);
|
|
@@ -12149,7 +12184,7 @@ async function understandGoals(projectPath, skills) {
|
|
|
12149
12184
|
const paths = projectPath ? [projectPath] : [process.cwd()];
|
|
12150
12185
|
for (const p of paths) {
|
|
12151
12186
|
for (const file of ["README.md", "CLAUDE.md", "package.json", ".agents/AGENTS.md"]) {
|
|
12152
|
-
const fullPath =
|
|
12187
|
+
const fullPath = join14(p, file);
|
|
12153
12188
|
if (existsSync9(fullPath)) {
|
|
12154
12189
|
const content = readFileSync7(fullPath, "utf-8").slice(0, 2000);
|
|
12155
12190
|
context.push(`## ${file}
|
|
@@ -12311,12 +12346,12 @@ ${replay.slice(0, 800)}`
|
|
|
12311
12346
|
|
|
12312
12347
|
// src/commands/dashboard.ts
|
|
12313
12348
|
import { execSync as execSync2, spawn as spawn2 } from "node:child_process";
|
|
12314
|
-
import { join as
|
|
12315
|
-
import { existsSync as existsSync10, cpSync as cpSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2,
|
|
12316
|
-
import { fileURLToPath } from "node:url";
|
|
12349
|
+
import { join as join15, dirname as dirname3 } from "node:path";
|
|
12350
|
+
import { existsSync as existsSync10, cpSync as cpSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
12351
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12317
12352
|
import { homedir as homedir3 } from "node:os";
|
|
12318
|
-
var __filename = "/Users/tianchichen/Documents/GitHub/
|
|
12319
|
-
var HELIX_DASHBOARD_DIR =
|
|
12353
|
+
var __filename = "/Users/tianchichen/Documents/GitHub/helixevo/src/commands/dashboard.ts";
|
|
12354
|
+
var HELIX_DASHBOARD_DIR = join15(homedir3(), ".helix", "dashboard");
|
|
12320
12355
|
async function dashboardCommand() {
|
|
12321
12356
|
const dir = prepareDashboard();
|
|
12322
12357
|
if (!dir) {
|
|
@@ -12327,7 +12362,7 @@ async function dashboardCommand() {
|
|
|
12327
12362
|
console.error(" cd helixevo/dashboard && npm install && npx next dev --port 3847");
|
|
12328
12363
|
process.exit(1);
|
|
12329
12364
|
}
|
|
12330
|
-
if (!existsSync10(
|
|
12365
|
+
if (!existsSync10(join15(dir, "node_modules"))) {
|
|
12331
12366
|
console.log(" Installing dashboard dependencies...");
|
|
12332
12367
|
try {
|
|
12333
12368
|
execSync2("npm install --no-audit --no-fund", { cwd: dir, stdio: "inherit" });
|
|
@@ -12338,7 +12373,7 @@ async function dashboardCommand() {
|
|
|
12338
12373
|
process.exit(1);
|
|
12339
12374
|
}
|
|
12340
12375
|
}
|
|
12341
|
-
console.log(` \uD83C\uDF10 Starting HelixEvo Dashboard at http://localhost:3847
|
|
12376
|
+
console.log(` \uD83C\uDF10 Starting HelixEvo Dashboard v${VERSION} at http://localhost:3847
|
|
12342
12377
|
`);
|
|
12343
12378
|
const child = spawn2("npx", ["next", "dev", "--port", "3847"], {
|
|
12344
12379
|
cwd: dir,
|
|
@@ -12363,20 +12398,20 @@ function prepareDashboard() {
|
|
|
12363
12398
|
const npmSource = findNpmDashboard();
|
|
12364
12399
|
if (npmSource)
|
|
12365
12400
|
return copyToHelix(npmSource);
|
|
12366
|
-
if (existsSync10(
|
|
12401
|
+
if (existsSync10(join15(HELIX_DASHBOARD_DIR, "package.json"))) {
|
|
12367
12402
|
return HELIX_DASHBOARD_DIR;
|
|
12368
12403
|
}
|
|
12369
12404
|
return null;
|
|
12370
12405
|
}
|
|
12371
12406
|
function findDevDashboard() {
|
|
12372
12407
|
const candidates = [
|
|
12373
|
-
|
|
12408
|
+
join15(process.cwd(), "dashboard")
|
|
12374
12409
|
];
|
|
12375
12410
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
12376
|
-
candidates.push(
|
|
12377
|
-
candidates.push(
|
|
12411
|
+
candidates.push(join15(home, "Documents", "GitHub", "helixevo", "dashboard"));
|
|
12412
|
+
candidates.push(join15(home, "Documents", "GitHub", "skillgraph", "dashboard"));
|
|
12378
12413
|
for (const dir of candidates) {
|
|
12379
|
-
if (existsSync10(
|
|
12414
|
+
if (existsSync10(join15(dir, "package.json")) && !dir.includes("node_modules")) {
|
|
12380
12415
|
return dir;
|
|
12381
12416
|
}
|
|
12382
12417
|
}
|
|
@@ -12385,60 +12420,84 @@ function findDevDashboard() {
|
|
|
12385
12420
|
function findNpmDashboard() {
|
|
12386
12421
|
const candidates = [];
|
|
12387
12422
|
try {
|
|
12388
|
-
const thisFile = typeof __filename !== "undefined" ? __filename :
|
|
12389
|
-
const pkgRoot =
|
|
12390
|
-
candidates.push(
|
|
12423
|
+
const thisFile = typeof __filename !== "undefined" ? __filename : fileURLToPath2(import.meta.url);
|
|
12424
|
+
const pkgRoot = dirname3(dirname3(thisFile));
|
|
12425
|
+
candidates.push(join15(pkgRoot, "dashboard"));
|
|
12391
12426
|
} catch {}
|
|
12392
12427
|
try {
|
|
12393
12428
|
const globalPrefix = execSync2("npm prefix -g", { encoding: "utf-8" }).trim();
|
|
12394
|
-
candidates.push(
|
|
12395
|
-
candidates.push(
|
|
12429
|
+
candidates.push(join15(globalPrefix, "lib", "node_modules", "helixevo", "dashboard"));
|
|
12430
|
+
candidates.push(join15(globalPrefix, "node_modules", "helixevo", "dashboard"));
|
|
12396
12431
|
} catch {}
|
|
12397
12432
|
for (const dir of candidates) {
|
|
12398
|
-
if (existsSync10(
|
|
12433
|
+
if (existsSync10(join15(dir, "package.json"))) {
|
|
12399
12434
|
return dir;
|
|
12400
12435
|
}
|
|
12401
12436
|
}
|
|
12402
12437
|
return null;
|
|
12403
12438
|
}
|
|
12439
|
+
function getInstalledDashboardVersion() {
|
|
12440
|
+
try {
|
|
12441
|
+
const versionFile = join15(HELIX_DASHBOARD_DIR, ".helixevo-version");
|
|
12442
|
+
if (!existsSync10(versionFile))
|
|
12443
|
+
return null;
|
|
12444
|
+
return readFileSync8(versionFile, "utf-8").trim();
|
|
12445
|
+
} catch {
|
|
12446
|
+
return null;
|
|
12447
|
+
}
|
|
12448
|
+
}
|
|
12404
12449
|
function copyToHelix(sourceDir) {
|
|
12405
|
-
const
|
|
12406
|
-
if (
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
} catch {}
|
|
12450
|
+
const installedVersion = getInstalledDashboardVersion();
|
|
12451
|
+
if (installedVersion === VERSION) {
|
|
12452
|
+
return HELIX_DASHBOARD_DIR;
|
|
12453
|
+
}
|
|
12454
|
+
if (installedVersion) {
|
|
12455
|
+
console.log(` Updating dashboard: v${installedVersion} → v${VERSION}`);
|
|
12456
|
+
} else {
|
|
12457
|
+
console.log(" Setting up dashboard at ~/.helix/dashboard...");
|
|
12414
12458
|
}
|
|
12415
|
-
console.log(" Setting up dashboard at ~/.helix/dashboard...");
|
|
12416
12459
|
mkdirSync4(HELIX_DASHBOARD_DIR, { recursive: true });
|
|
12460
|
+
let depsChanged = true;
|
|
12461
|
+
try {
|
|
12462
|
+
const srcDeps = readFileSync8(join15(sourceDir, "package.json"), "utf-8");
|
|
12463
|
+
const dstDeps = readFileSync8(join15(HELIX_DASHBOARD_DIR, "package.json"), "utf-8");
|
|
12464
|
+
const srcPkg = JSON.parse(srcDeps);
|
|
12465
|
+
const dstPkg = JSON.parse(dstDeps);
|
|
12466
|
+
depsChanged = JSON.stringify(srcPkg.dependencies) !== JSON.stringify(dstPkg.dependencies) || JSON.stringify(srcPkg.devDependencies) !== JSON.stringify(dstPkg.devDependencies);
|
|
12467
|
+
} catch {
|
|
12468
|
+
depsChanged = true;
|
|
12469
|
+
}
|
|
12417
12470
|
const items = readdirSync2(sourceDir, { withFileTypes: true });
|
|
12418
12471
|
for (const item of items) {
|
|
12419
12472
|
if (item.name === "node_modules" || item.name === ".next" || item.name === "package-lock.json")
|
|
12420
12473
|
continue;
|
|
12421
|
-
const src =
|
|
12422
|
-
const dest =
|
|
12474
|
+
const src = join15(sourceDir, item.name);
|
|
12475
|
+
const dest = join15(HELIX_DASHBOARD_DIR, item.name);
|
|
12423
12476
|
cpSync3(src, dest, { recursive: true });
|
|
12424
12477
|
}
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12478
|
+
if (depsChanged) {
|
|
12479
|
+
const oldModules = join15(HELIX_DASHBOARD_DIR, "node_modules");
|
|
12480
|
+
if (existsSync10(oldModules))
|
|
12481
|
+
rmSync2(oldModules, { recursive: true });
|
|
12482
|
+
const oldLock = join15(HELIX_DASHBOARD_DIR, "package-lock.json");
|
|
12483
|
+
if (existsSync10(oldLock))
|
|
12484
|
+
rmSync2(oldLock);
|
|
12485
|
+
}
|
|
12486
|
+
const nextCache = join15(HELIX_DASHBOARD_DIR, ".next");
|
|
12487
|
+
if (existsSync10(nextCache))
|
|
12488
|
+
rmSync2(nextCache, { recursive: true });
|
|
12489
|
+
writeFileSync9(join15(HELIX_DASHBOARD_DIR, ".helixevo-version"), VERSION);
|
|
12431
12490
|
return HELIX_DASHBOARD_DIR;
|
|
12432
12491
|
}
|
|
12433
12492
|
|
|
12434
12493
|
// src/commands/watch.ts
|
|
12435
|
-
import { join as
|
|
12494
|
+
import { join as join17 } from "node:path";
|
|
12436
12495
|
import { existsSync as existsSync13 } from "node:fs";
|
|
12437
12496
|
|
|
12438
12497
|
// src/core/auto-capture.ts
|
|
12439
12498
|
init_data();
|
|
12440
12499
|
init_llm();
|
|
12441
|
-
import { readFileSync as
|
|
12500
|
+
import { readFileSync as readFileSync9, existsSync as existsSync11, statSync as statSync2 } from "node:fs";
|
|
12442
12501
|
var CORRECTION_SIGNALS = [
|
|
12443
12502
|
/\bno[, ]+(?:that's|thats|that is)\s+(?:wrong|incorrect|not right)/i,
|
|
12444
12503
|
/\bdon'?t\s+do\s+(?:that|it\s+like\s+that)/i,
|
|
@@ -12526,17 +12585,17 @@ If no corrections found, return: { "corrections": [] }`
|
|
|
12526
12585
|
}
|
|
12527
12586
|
function watchEvents(options) {
|
|
12528
12587
|
const { eventsPath, project, onCapture, onError, pollIntervalMs = 5000 } = options;
|
|
12529
|
-
let lastSize = existsSync11(eventsPath) ?
|
|
12588
|
+
let lastSize = existsSync11(eventsPath) ? statSync2(eventsPath).size : 0;
|
|
12530
12589
|
let messageBuffer = [];
|
|
12531
12590
|
let pendingExtraction = false;
|
|
12532
12591
|
const interval = setInterval(async () => {
|
|
12533
12592
|
try {
|
|
12534
12593
|
if (!existsSync11(eventsPath))
|
|
12535
12594
|
return;
|
|
12536
|
-
const currentSize =
|
|
12595
|
+
const currentSize = statSync2(eventsPath).size;
|
|
12537
12596
|
if (currentSize <= lastSize)
|
|
12538
12597
|
return;
|
|
12539
|
-
const content =
|
|
12598
|
+
const content = readFileSync9(eventsPath, "utf-8");
|
|
12540
12599
|
const lines = content.trim().split(`
|
|
12541
12600
|
`).filter(Boolean);
|
|
12542
12601
|
const newLines = lines.slice(messageBuffer.length);
|
|
@@ -12578,22 +12637,22 @@ init_data();
|
|
|
12578
12637
|
// src/core/metrics.ts
|
|
12579
12638
|
init_config();
|
|
12580
12639
|
init_data();
|
|
12581
|
-
import { readFileSync as
|
|
12582
|
-
import { join as
|
|
12640
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync10, existsSync as existsSync12 } from "node:fs";
|
|
12641
|
+
import { join as join16 } from "node:path";
|
|
12583
12642
|
function getMetricsPath() {
|
|
12584
|
-
return
|
|
12643
|
+
return join16(getHelixDir(), "metrics.json");
|
|
12585
12644
|
}
|
|
12586
12645
|
function loadMetrics() {
|
|
12587
12646
|
const path = getMetricsPath();
|
|
12588
12647
|
if (!existsSync12(path)) {
|
|
12589
12648
|
return { updated: new Date().toISOString(), skills: [], globalCorrectionRate: [], evolutionImpact: [] };
|
|
12590
12649
|
}
|
|
12591
|
-
return JSON.parse(
|
|
12650
|
+
return JSON.parse(readFileSync10(path, "utf-8"));
|
|
12592
12651
|
}
|
|
12593
12652
|
function saveMetrics(store) {
|
|
12594
12653
|
ensureDir(getHelixDir());
|
|
12595
12654
|
store.updated = new Date().toISOString();
|
|
12596
|
-
|
|
12655
|
+
writeFileSync10(getMetricsPath(), JSON.stringify(store, null, 2));
|
|
12597
12656
|
}
|
|
12598
12657
|
function updateMetrics() {
|
|
12599
12658
|
const failures = loadFailures();
|
|
@@ -12768,7 +12827,7 @@ async function watchCommand(options) {
|
|
|
12768
12827
|
const verbose = options.verbose ?? false;
|
|
12769
12828
|
const autoEvolve = options.evolve !== false;
|
|
12770
12829
|
const project = options.project ?? null;
|
|
12771
|
-
const eventsPath = options.events ??
|
|
12830
|
+
const eventsPath = options.events ?? join17(process.cwd(), "events.jsonl");
|
|
12772
12831
|
console.log(`\uD83E\uDDEC HelixEvo Watch Mode — Always-On Learning
|
|
12773
12832
|
`);
|
|
12774
12833
|
console.log(` Events: ${eventsPath}`);
|
|
@@ -12922,14 +12981,100 @@ async function metricsCommand(options) {
|
|
|
12922
12981
|
}
|
|
12923
12982
|
}
|
|
12924
12983
|
|
|
12984
|
+
// src/utils/update-check.ts
|
|
12985
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync11, existsSync as existsSync14, mkdirSync as mkdirSync5 } from "node:fs";
|
|
12986
|
+
import { join as join18 } from "node:path";
|
|
12987
|
+
import { homedir as homedir4 } from "node:os";
|
|
12988
|
+
var HELIX_DIR2 = join18(homedir4(), ".helix");
|
|
12989
|
+
var CACHE_PATH = join18(HELIX_DIR2, "update-check.json");
|
|
12990
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
12991
|
+
var REGISTRY_URL = "https://registry.npmjs.org/helixevo/latest";
|
|
12992
|
+
function readCache() {
|
|
12993
|
+
try {
|
|
12994
|
+
if (!existsSync14(CACHE_PATH))
|
|
12995
|
+
return null;
|
|
12996
|
+
return JSON.parse(readFileSync11(CACHE_PATH, "utf-8"));
|
|
12997
|
+
} catch {
|
|
12998
|
+
return null;
|
|
12999
|
+
}
|
|
13000
|
+
}
|
|
13001
|
+
function writeCache(cache) {
|
|
13002
|
+
try {
|
|
13003
|
+
if (!existsSync14(HELIX_DIR2))
|
|
13004
|
+
mkdirSync5(HELIX_DIR2, { recursive: true });
|
|
13005
|
+
writeFileSync11(CACHE_PATH, JSON.stringify(cache));
|
|
13006
|
+
} catch {}
|
|
13007
|
+
}
|
|
13008
|
+
function compareVersions(current, latest) {
|
|
13009
|
+
const c = current.split(".").map(Number);
|
|
13010
|
+
const l = latest.split(".").map(Number);
|
|
13011
|
+
for (let i = 0;i < 3; i++) {
|
|
13012
|
+
if ((l[i] ?? 0) > (c[i] ?? 0))
|
|
13013
|
+
return true;
|
|
13014
|
+
if ((l[i] ?? 0) < (c[i] ?? 0))
|
|
13015
|
+
return false;
|
|
13016
|
+
}
|
|
13017
|
+
return false;
|
|
13018
|
+
}
|
|
13019
|
+
async function fetchLatestVersion() {
|
|
13020
|
+
try {
|
|
13021
|
+
const controller = new AbortController;
|
|
13022
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
13023
|
+
const res = await fetch(REGISTRY_URL, { signal: controller.signal });
|
|
13024
|
+
clearTimeout(timeout);
|
|
13025
|
+
if (!res.ok)
|
|
13026
|
+
return null;
|
|
13027
|
+
const data = await res.json();
|
|
13028
|
+
return data.version ?? null;
|
|
13029
|
+
} catch {
|
|
13030
|
+
return null;
|
|
13031
|
+
}
|
|
13032
|
+
}
|
|
13033
|
+
async function checkForUpdates() {
|
|
13034
|
+
try {
|
|
13035
|
+
const cache = readCache();
|
|
13036
|
+
if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
|
|
13037
|
+
if (compareVersions(VERSION, cache.latestVersion)) {
|
|
13038
|
+
printUpdateBanner(cache.latestVersion);
|
|
13039
|
+
}
|
|
13040
|
+
return;
|
|
13041
|
+
}
|
|
13042
|
+
const latest = await fetchLatestVersion();
|
|
13043
|
+
if (!latest)
|
|
13044
|
+
return;
|
|
13045
|
+
writeCache({ lastCheck: Date.now(), latestVersion: latest });
|
|
13046
|
+
if (compareVersions(VERSION, latest)) {
|
|
13047
|
+
printUpdateBanner(latest);
|
|
13048
|
+
}
|
|
13049
|
+
} catch {}
|
|
13050
|
+
}
|
|
13051
|
+
function printUpdateBanner(latestVersion) {
|
|
13052
|
+
const yellow = "\x1B[33m";
|
|
13053
|
+
const green = "\x1B[32m";
|
|
13054
|
+
const cyan = "\x1B[36m";
|
|
13055
|
+
const bold = "\x1B[1m";
|
|
13056
|
+
const dim = "\x1B[2m";
|
|
13057
|
+
const reset = "\x1B[0m";
|
|
13058
|
+
console.log();
|
|
13059
|
+
console.log(`${yellow} ╭──────────────────────────────────────────────╮${reset}`);
|
|
13060
|
+
console.log(`${yellow} │ │${reset}`);
|
|
13061
|
+
console.log(`${yellow} │ ${bold}Update available!${reset}${yellow} ${dim}v${VERSION}${reset}${yellow} → ${green}${bold}v${latestVersion}${reset}${yellow} │${reset}`);
|
|
13062
|
+
console.log(`${yellow} │ │${reset}`);
|
|
13063
|
+
console.log(`${yellow} │ ${reset}Run ${cyan}npm install -g helixevo@latest${reset}${yellow} │${reset}`);
|
|
13064
|
+
console.log(`${yellow} │ ${reset}to update${yellow} │${reset}`);
|
|
13065
|
+
console.log(`${yellow} │ │${reset}`);
|
|
13066
|
+
console.log(`${yellow} ╰──────────────────────────────────────────────╯${reset}`);
|
|
13067
|
+
console.log();
|
|
13068
|
+
}
|
|
13069
|
+
|
|
12925
13070
|
// src/cli.ts
|
|
12926
13071
|
var program2 = new Command;
|
|
12927
|
-
program2.name("helixevo").description("Self-evolving skill ecosystem for AI agents").version(
|
|
13072
|
+
program2.name("helixevo").description("Self-evolving skill ecosystem for AI agents").version(VERSION).addHelpText("after", `
|
|
12928
13073
|
Examples:
|
|
12929
13074
|
$ helixevo watch Always-on learning (auto-capture + auto-evolve)
|
|
12930
13075
|
$ helixevo watch --project myapp Watch with project context
|
|
12931
13076
|
$ helixevo metrics Show correction rates and evolution impact
|
|
12932
|
-
$ helixevo init Import skills + generate
|
|
13077
|
+
$ helixevo init Import skills + generate skill tests
|
|
12933
13078
|
$ helixevo status Show system health
|
|
12934
13079
|
$ helixevo evolve --verbose Evolve skills from failures
|
|
12935
13080
|
$ helixevo evolve --dry-run Preview proposals without applying
|
|
@@ -12943,7 +13088,7 @@ Examples:
|
|
|
12943
13088
|
$ helixevo graph --optimize Detect merge/split/conflicts
|
|
12944
13089
|
$ helixevo report --days 7 Weekly evolution report
|
|
12945
13090
|
$ helixevo capture <session.json> Extract failures from session`);
|
|
12946
|
-
program2.command("init").description("Import existing skills + generate
|
|
13091
|
+
program2.command("init").description("Import existing skills + generate skill tests").option("--skills-paths <paths...>", "Paths to scan for existing skills").option("--skip-tests", "Skip skill test generation").action(initCommand);
|
|
12947
13092
|
program2.command("capture").description("Capture failures from a Craft Agent session").argument("<sessionPath>", "Path to session conversation file").option("--project <name>", "Project name override").action(captureCommand);
|
|
12948
13093
|
program2.command("evolve").description("Evolve skills from failures [--dry-run] [--verbose] [--max-proposals <n>]").option("--dry-run", "Show proposals without applying").option("--verbose", "Show detailed LLM interactions").option("--max-proposals <n>", "Max proposals per run", "5").action(evolveCommand);
|
|
12949
13094
|
program2.command("generalize").description("Promote cross-skill patterns to higher layer ↑ [--dry-run] [--verbose]").option("--dry-run", "Show candidates without applying").option("--verbose", "Show detailed analysis").action(generalizeCommand);
|
|
@@ -12960,4 +13105,7 @@ program2.command("health").description("Assess network health: cohesion, coverag
|
|
|
12960
13105
|
printHealthReport2(health);
|
|
12961
13106
|
});
|
|
12962
13107
|
program2.command("metrics").description("Show correction rates, skill trends, and evolution impact").option("--verbose", "Show detailed per-skill breakdown").action(metricsCommand);
|
|
13108
|
+
program2.hook("postAction", () => {
|
|
13109
|
+
checkForUpdates().catch(() => {});
|
|
13110
|
+
});
|
|
12963
13111
|
program2.parse();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/postinstall.ts
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { join, dirname } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
var require2 = createRequire(import.meta.url);
|
|
8
|
+
var pkg = require2(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"));
|
|
9
|
+
var green = "\x1B[32m";
|
|
10
|
+
var cyan = "\x1B[36m";
|
|
11
|
+
var bold = "\x1B[1m";
|
|
12
|
+
var dim = "\x1B[2m";
|
|
13
|
+
var reset = "\x1B[0m";
|
|
14
|
+
console.log();
|
|
15
|
+
console.log(` ${green}✓${reset} ${bold}HelixEvo v${pkg.version}${reset} installed successfully`);
|
|
16
|
+
console.log();
|
|
17
|
+
console.log(` ${dim}Get started:${reset}`);
|
|
18
|
+
console.log(` ${cyan}helixevo init${reset} Import skills + generate skill tests`);
|
|
19
|
+
console.log(` ${cyan}helixevo dashboard${reset} Open web dashboard`);
|
|
20
|
+
console.log(` ${cyan}helixevo watch${reset} Always-on learning mode`);
|
|
21
|
+
console.log();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helixevo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Self-evolving skill ecosystem for AI agents. Skills and projects co-evolve through multi-judge evaluation and a Pareto frontier.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,15 +14,17 @@
|
|
|
14
14
|
"dashboard/package.json",
|
|
15
15
|
"dashboard/tsconfig.json",
|
|
16
16
|
"dashboard/next-env.d.ts",
|
|
17
|
+
"dashboard/next.config.mjs",
|
|
17
18
|
"README.md",
|
|
18
19
|
"LICENSE"
|
|
19
20
|
],
|
|
20
21
|
"scripts": {
|
|
21
|
-
"build": "bun build src/cli.ts --outdir dist --target node",
|
|
22
|
+
"build": "bun build src/cli.ts --outdir dist --target node && bun build src/postinstall.ts --outdir dist --target node --entry-naming postinstall.js",
|
|
22
23
|
"dev": "bun run src/cli.ts",
|
|
23
24
|
"typecheck": "tsc --noEmit",
|
|
24
25
|
"prepare": "npm run build",
|
|
25
|
-
"prepublishOnly": "npm run typecheck && npm run build"
|
|
26
|
+
"prepublishOnly": "npm run typecheck && npm run build",
|
|
27
|
+
"postinstall": "node dist/postinstall.js 2>/dev/null || true"
|
|
26
28
|
},
|
|
27
29
|
"dependencies": {
|
|
28
30
|
"commander": "^13.1.0",
|