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 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 golden cases
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 golden cases
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 golden cases |
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
- ├── golden-cases.jsonl # Regression test cases
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 golden cases for [${partners.join(', ')}] after this change`
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">Golden cases + cross-skill</div>
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">v0.2.6</div>
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 golden test cases for each skill. It also creates the data directory at <code>~/.helix/</code>.
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 golden test cases. Scans ~/.agents/skills/ and creates the HelixEvo data directory.',
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 golden cases
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 golden cases for that skill AND co-evolved partner skills. Must maintain ≥95% pass rate."
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="Golden cases and cross-skill validation ensure quality.">
697
- <h3 className="guide-h3">Golden Cases</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
- Golden cases are regression test scenarios tied to specific skills. They&apos;re created when:
700
+ Skill tests are regression test scenarios tied to specific skills. They&apos;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 golden case</li>
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 golden case stores the input, context, and expected behavior. During regression testing,
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 golden cases from co-evolved, dependent, and enhancing
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&apos;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 golden cases against Skill A's changes
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 golden case pass rate (0-1)." def="0.95" />
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.maxGoldenCases" type="number" desc="Maximum golden cases per skill." def="50" />
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
- "maxGoldenCases": 50
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
- ├── golden-cases.jsonl # Regression test cases (append-only)
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">Golden Case</div>
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 golden cases against Skill A&apos;s changes. If partner pass rate drops below 95%,
953
+ partners. It tests their skill tests against Skill A&apos;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 v0.2.6</div>
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>
@@ -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 }}>v0.2.0</code>
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
+ &times;
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} &rarr; <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
+ }
@@ -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 loadGoldenCases(): { id: string; skill: string; input: string }[] {
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 goldenCases = loadGoldenCases()
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
- goldenCases: goldenCases.length,
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
- maxGoldenCases: 50
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 loadGoldenCases() {
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 appendGoldenCase(gc) {
9233
- appendJsonl("golden-cases.jsonl", gc);
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 join3 } from "node:path";
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/golden-gen.ts
9581
- function buildGoldenGenPrompt(skill) {
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(join3(sgDir, "config.json"))) {
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
- join3(homedir2(), ".agents", "skills"),
9619
- join3(homedir2(), ".craft-agent", "workspaces")
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 = join3(p, ws.name, "skills");
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 = join3(generalDir, skill.slug);
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
- if (!options.skipGolden) {
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 golden cases...
9690
+ console.log(` Generating skill tests...
9656
9691
  `);
9657
9692
  for (const skill of generalSkills) {
9658
9693
  try {
9659
- const prompt = buildGoldenGenPrompt(skill);
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
- appendGoldenCase(gc);
9709
+ appendSkillTest(gc);
9675
9710
  }
9676
- console.log(` ✓ ${skill.slug}: ${output.cases.length} golden cases`);
9711
+ console.log(` ✓ ${skill.slug}: ${output.cases.length} skill tests`);
9677
9712
  } catch (err) {
9678
- console.log(` ✗ ${skill.slug}: failed to generate golden cases (${err})`);
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 join6 } from "node:path";
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 = loadGoldenCases();
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 golden cases for ${skillSlug}, skipping regression`);
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 promoteToGoldenCase(failure, skillSlug, replayResult) {
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
- appendGoldenCase(gc);
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 = loadGoldenCases();
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 join4 } from "node:path";
10124
- var CANARY_DIR = join4(getHelixDir(), "canary");
10125
- var BACKUPS_DIR = join4(getHelixDir(), "backups");
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 join4(getHelixDir(), "canary-registry.json");
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 = join4(BACKUPS_DIR, `${slug}_${version}_${Date.now()}`);
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 join5 } from "node:path";
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 = join5(getGeneralSkillsPath(), candidate.suggestedName);
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 = join6(getGeneralSkillsPath(), skillSlug2);
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
- promoteToGoldenCase(testFailure, skillSlug2, replayResult);
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 join7 } from "node:path";
10577
- var BUFFER_PATH = () => join7(getHelixDir(), "knowledge-buffer.json");
10578
- var DRAFTS_DIR = () => join7(getHelixDir(), "drafts");
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 = join7(DRAFTS_DIR(), draft.skillName);
10670
+ const draftDir = join8(DRAFTS_DIR(), draft.skillName);
10636
10671
  ensureDir(draftDir);
10637
- writeFileSync5(join7(draftDir, "SKILL.md"), draft.skillMd);
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 = join7(DRAFTS_DIR(), draft.skillName);
10686
+ const draftDir = join8(DRAFTS_DIR(), draft.skillName);
10652
10687
  ensureDir(draftDir);
10653
- writeFileSync5(join7(draftDir, "SKILL.md"), draft.skillMd);
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 goldenCases = loadGoldenCases();
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(` Golden cases: ${goldenCases.length}`);
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 join8 } from "node:path";
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 ?? join8(getHelixDir(), "reports", `${date}.md`);
10838
- ensureDir(join8(getHelixDir(), "reports"));
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 join9 } from "node:path";
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 = join9(getGeneralSkillsPath(), candidate.suggestedName);
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 join10 } from "node:path";
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 = join10(process.cwd(), ".helix", "skills");
11092
- const skillDir = join10(projectSkillsDir, candidate.suggestedName);
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 join12 } from "node:path";
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 join11 } from "node:path";
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 = join11(vaultPath, "Skills");
11547
- const reportsDir = join11(vaultPath, "Reports");
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 = join11(skillsDir, `${node.id}.md`);
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(join11(vaultPath, "HelixEvo Index.md"), indexNote);
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(join11(reportsDir, `Evolution ${date}.md`), report);
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 = join12(tmpdir(), "helix-network.html");
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 join13 } from "node:path";
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 = join13(getGeneralSkillsPath(), hypothesis.skillName);
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 = join13(p, file);
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 join14, dirname as dirname2 } from "node:path";
12315
- import { existsSync as existsSync10, cpSync as cpSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, rmSync as rmSync2 } from "node:fs";
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/skillgraph/src/commands/dashboard.ts";
12319
- var HELIX_DASHBOARD_DIR = join14(homedir3(), ".helix", "dashboard");
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(join14(dir, "node_modules"))) {
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(join14(HELIX_DASHBOARD_DIR, "package.json"))) {
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
- join14(process.cwd(), "dashboard")
12408
+ join15(process.cwd(), "dashboard")
12374
12409
  ];
12375
12410
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
12376
- candidates.push(join14(home, "Documents", "GitHub", "helixevo", "dashboard"));
12377
- candidates.push(join14(home, "Documents", "GitHub", "skillgraph", "dashboard"));
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(join14(dir, "package.json")) && !dir.includes("node_modules")) {
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 : fileURLToPath(import.meta.url);
12389
- const pkgRoot = dirname2(dirname2(thisFile));
12390
- candidates.push(join14(pkgRoot, "dashboard"));
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(join14(globalPrefix, "lib", "node_modules", "helixevo", "dashboard"));
12395
- candidates.push(join14(globalPrefix, "node_modules", "helixevo", "dashboard"));
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(join14(dir, "package.json"))) {
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 destPkg = join14(HELIX_DASHBOARD_DIR, "package.json");
12406
- if (existsSync10(destPkg)) {
12407
- try {
12408
- const srcTime = statSync2(join14(sourceDir, "package.json")).mtimeMs;
12409
- const dstTime = statSync2(destPkg).mtimeMs;
12410
- if (srcTime <= dstTime) {
12411
- return HELIX_DASHBOARD_DIR;
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 = join14(sourceDir, item.name);
12422
- const dest = join14(HELIX_DASHBOARD_DIR, item.name);
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
- const oldModules = join14(HELIX_DASHBOARD_DIR, "node_modules");
12426
- if (existsSync10(oldModules))
12427
- rmSync2(oldModules, { recursive: true });
12428
- const oldLock = join14(HELIX_DASHBOARD_DIR, "package-lock.json");
12429
- if (existsSync10(oldLock))
12430
- rmSync2(oldLock);
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 join16 } from "node:path";
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 readFileSync8, existsSync as existsSync11, statSync as statSync3 } from "node:fs";
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) ? statSync3(eventsPath).size : 0;
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 = statSync3(eventsPath).size;
12595
+ const currentSize = statSync2(eventsPath).size;
12537
12596
  if (currentSize <= lastSize)
12538
12597
  return;
12539
- const content = readFileSync8(eventsPath, "utf-8");
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 readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync12 } from "node:fs";
12582
- import { join as join15 } from "node:path";
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 join15(getHelixDir(), "metrics.json");
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(readFileSync9(path, "utf-8"));
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
- writeFileSync9(getMetricsPath(), JSON.stringify(store, null, 2));
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 ?? join16(process.cwd(), "events.jsonl");
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("0.2.7").addHelpText("after", `
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 golden cases
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 golden cases").option("--skills-paths <paths...>", "Paths to scan for existing skills").option("--skip-golden", "Skip golden case generation").action(initCommand);
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.7",
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",