helixevo 0.2.25 → 0.2.27
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/dashboard/components/update-banner.tsx +68 -54
- package/dist/cli.js +111 -14
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useState, useEffect } from 'react'
|
|
4
4
|
|
|
5
5
|
const REGISTRY_URL = 'https://registry.npmjs.org/helixevo/latest'
|
|
6
|
-
const CHECK_INTERVAL_MS = 60 * 60 * 1000
|
|
6
|
+
const CHECK_INTERVAL_MS = 60 * 60 * 1000
|
|
7
7
|
|
|
8
8
|
function compareVersions(current: string, latest: string): boolean {
|
|
9
9
|
const c = current.split('.').map(Number)
|
|
@@ -15,7 +15,7 @@ function compareVersions(current: string, latest: string): boolean {
|
|
|
15
15
|
return false
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
type UpdateState = 'idle' | 'updating' | '
|
|
18
|
+
type UpdateState = 'idle' | 'updating' | 'installed' | 'error'
|
|
19
19
|
|
|
20
20
|
export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
21
21
|
const [latestVersion, setLatestVersion] = useState<string | null>(null)
|
|
@@ -23,10 +23,10 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
23
23
|
const [state, setState] = useState<UpdateState>('idle')
|
|
24
24
|
const [newVersion, setNewVersion] = useState<string | null>(null)
|
|
25
25
|
const [errorMsg, setErrorMsg] = useState<string | null>(null)
|
|
26
|
+
const [copied, setCopied] = useState(false)
|
|
26
27
|
|
|
27
28
|
useEffect(() => {
|
|
28
29
|
let mounted = true
|
|
29
|
-
|
|
30
30
|
async function check() {
|
|
31
31
|
try {
|
|
32
32
|
const res = await fetch(REGISTRY_URL)
|
|
@@ -37,7 +37,6 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
37
37
|
}
|
|
38
38
|
} catch {}
|
|
39
39
|
}
|
|
40
|
-
|
|
41
40
|
check()
|
|
42
41
|
const interval = setInterval(check, CHECK_INTERVAL_MS)
|
|
43
42
|
return () => { mounted = false; clearInterval(interval) }
|
|
@@ -54,49 +53,35 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
54
53
|
const data = await res.json()
|
|
55
54
|
|
|
56
55
|
if (data.success) {
|
|
57
|
-
setState('
|
|
56
|
+
setState('installed')
|
|
58
57
|
setNewVersion(data.version)
|
|
59
|
-
|
|
60
|
-
// Tell server to restart, then wait and reload
|
|
61
|
-
try {
|
|
62
|
-
await fetch('/api/restart', { method: 'POST' })
|
|
63
|
-
} catch {}
|
|
64
|
-
|
|
65
|
-
// Wait for: shutdown (2s) + port release (2s) + restart + compile (~8s)
|
|
66
|
-
// Then try to reload, retrying if server isn't ready
|
|
67
|
-
await new Promise(r => setTimeout(r, 6000))
|
|
68
|
-
|
|
69
|
-
for (let attempt = 0; attempt < 20; attempt++) {
|
|
70
|
-
try {
|
|
71
|
-
const check = await fetch('/?_bust=' + Date.now(), { cache: 'no-store' })
|
|
72
|
-
if (check.ok) {
|
|
73
|
-
window.location.href = window.location.pathname + '?updated=' + Date.now()
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
} catch {}
|
|
77
|
-
await new Promise(r => setTimeout(r, 2000))
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Final fallback
|
|
81
|
-
window.location.href = window.location.pathname + '?updated=' + Date.now()
|
|
82
58
|
} else {
|
|
83
59
|
setState('error')
|
|
84
60
|
setErrorMsg(data.error ?? 'Update failed')
|
|
85
61
|
}
|
|
86
|
-
} catch
|
|
62
|
+
} catch {
|
|
87
63
|
setState('error')
|
|
88
64
|
setErrorMsg('Network error — try running: npm install -g helixevo@latest')
|
|
89
65
|
}
|
|
90
66
|
}
|
|
91
67
|
|
|
68
|
+
const restartCmd = 'helixevo dashboard'
|
|
69
|
+
const handleCopy = async () => {
|
|
70
|
+
try {
|
|
71
|
+
await navigator.clipboard.writeText(restartCmd)
|
|
72
|
+
setCopied(true)
|
|
73
|
+
setTimeout(() => setCopied(false), 2000)
|
|
74
|
+
} catch {}
|
|
75
|
+
}
|
|
76
|
+
|
|
92
77
|
return (
|
|
93
78
|
<div style={{
|
|
94
79
|
position: 'fixed',
|
|
95
80
|
bottom: 20,
|
|
96
81
|
right: 20,
|
|
97
|
-
width:
|
|
82
|
+
width: 340,
|
|
98
83
|
background: 'var(--bg-card)',
|
|
99
|
-
border: `1px solid ${state === '
|
|
84
|
+
border: `1px solid ${state === 'installed' ? 'var(--green-border)' : state === 'error' ? 'var(--red-border)' : 'var(--purple-border)'}`,
|
|
100
85
|
borderRadius: 'var(--radius-lg)',
|
|
101
86
|
boxShadow: 'var(--shadow-xl)',
|
|
102
87
|
padding: '16px 18px',
|
|
@@ -123,7 +108,7 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
123
108
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
|
124
109
|
<div style={{
|
|
125
110
|
width: 28, height: 28, borderRadius: '50%',
|
|
126
|
-
background: state === '
|
|
111
|
+
background: state === 'installed' ? 'var(--green-light)'
|
|
127
112
|
: state === 'error' ? 'var(--red-light)'
|
|
128
113
|
: 'var(--purple-light)',
|
|
129
114
|
display: 'flex',
|
|
@@ -135,7 +120,7 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
135
120
|
borderTopColor: 'var(--purple)', borderRadius: '50%',
|
|
136
121
|
animation: 'updateSpin 0.8s linear infinite',
|
|
137
122
|
}} />
|
|
138
|
-
) : state === '
|
|
123
|
+
) : state === 'installed' ? (
|
|
139
124
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--green)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
140
125
|
<path d="M20 6L9 17l-5-5" />
|
|
141
126
|
</svg>
|
|
@@ -151,21 +136,21 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
151
136
|
</div>
|
|
152
137
|
<div>
|
|
153
138
|
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', letterSpacing: -0.2 }}>
|
|
154
|
-
{state === 'updating' ? '
|
|
155
|
-
: state === '
|
|
139
|
+
{state === 'updating' ? 'Installing...'
|
|
140
|
+
: state === 'installed' ? 'Installed!'
|
|
156
141
|
: state === 'error' ? 'Update Failed'
|
|
157
142
|
: 'Update Available'}
|
|
158
143
|
</div>
|
|
159
144
|
<div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
|
|
160
|
-
{state === '
|
|
161
|
-
? <>
|
|
145
|
+
{state === 'installed'
|
|
146
|
+
? <>v{newVersion} is ready</>
|
|
162
147
|
: <>v{currentVersion} → <span style={{ color: 'var(--green)', fontWeight: 600 }}>v{latestVersion}</span></>
|
|
163
148
|
}
|
|
164
149
|
</div>
|
|
165
150
|
</div>
|
|
166
151
|
</div>
|
|
167
152
|
|
|
168
|
-
{/*
|
|
153
|
+
{/* Idle — Update Now button */}
|
|
169
154
|
{state === 'idle' && (
|
|
170
155
|
<button
|
|
171
156
|
onClick={handleUpdate}
|
|
@@ -183,18 +168,16 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
183
168
|
alignItems: 'center',
|
|
184
169
|
justifyContent: 'center',
|
|
185
170
|
gap: 6,
|
|
186
|
-
transition: 'opacity 0.15s',
|
|
187
171
|
}}
|
|
188
|
-
onMouseOver={e => (e.currentTarget.style.opacity = '0.9')}
|
|
189
|
-
onMouseOut={e => (e.currentTarget.style.opacity = '1')}
|
|
190
172
|
>
|
|
191
173
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
192
|
-
<path d="
|
|
174
|
+
<path d="M12 19V5m-7 7l7-7 7 7" />
|
|
193
175
|
</svg>
|
|
194
176
|
Update Now
|
|
195
177
|
</button>
|
|
196
178
|
)}
|
|
197
179
|
|
|
180
|
+
{/* Updating spinner */}
|
|
198
181
|
{state === 'updating' && (
|
|
199
182
|
<div style={{
|
|
200
183
|
width: '100%',
|
|
@@ -209,21 +192,52 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
209
192
|
</div>
|
|
210
193
|
)}
|
|
211
194
|
|
|
212
|
-
{
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
195
|
+
{/* Installed — restart instructions */}
|
|
196
|
+
{state === 'installed' && (
|
|
197
|
+
<div>
|
|
198
|
+
<div style={{
|
|
199
|
+
padding: '10px 14px',
|
|
200
|
+
background: 'var(--green-light)',
|
|
201
|
+
borderRadius: 'var(--radius)',
|
|
202
|
+
fontSize: 12,
|
|
203
|
+
color: 'var(--green)',
|
|
204
|
+
fontWeight: 600,
|
|
205
|
+
marginBottom: 10,
|
|
206
|
+
textAlign: 'center',
|
|
207
|
+
}}>
|
|
208
|
+
v{newVersion} installed successfully
|
|
209
|
+
</div>
|
|
210
|
+
<div style={{ fontSize: 11, color: 'var(--text-dim)', marginBottom: 8, lineHeight: 1.5 }}>
|
|
211
|
+
Restart the dashboard to load the new version:
|
|
212
|
+
</div>
|
|
213
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
214
|
+
<div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
|
|
215
|
+
1. Press <kbd style={{ padding: '1px 5px', background: 'var(--bg-section)', borderRadius: 3, fontFamily: 'var(--font-mono)', fontSize: 10, border: '1px solid var(--border)' }}>Ctrl+C</kbd> in the terminal running the dashboard
|
|
216
|
+
</div>
|
|
217
|
+
<div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
|
|
218
|
+
2. Run:
|
|
219
|
+
</div>
|
|
220
|
+
<div
|
|
221
|
+
onClick={handleCopy}
|
|
222
|
+
style={{
|
|
223
|
+
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
224
|
+
padding: '7px 12px',
|
|
225
|
+
background: 'var(--bg-section)', border: '1px solid var(--border)',
|
|
226
|
+
borderRadius: 'var(--radius)',
|
|
227
|
+
fontFamily: 'var(--font-mono)', fontSize: 11,
|
|
228
|
+
color: 'var(--text)', cursor: 'pointer',
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
231
|
+
<span>$ {restartCmd}</span>
|
|
232
|
+
<span style={{ fontSize: 10, color: copied ? 'var(--green)' : 'var(--purple)', fontWeight: 600, fontFamily: 'var(--font)' }}>
|
|
233
|
+
{copied ? 'Copied!' : 'Copy'}
|
|
234
|
+
</span>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
224
237
|
</div>
|
|
225
238
|
)}
|
|
226
239
|
|
|
240
|
+
{/* Error */}
|
|
227
241
|
{state === 'error' && (
|
|
228
242
|
<>
|
|
229
243
|
<div style={{
|
package/dist/cli.js
CHANGED
|
@@ -9808,7 +9808,7 @@ init_llm();
|
|
|
9808
9808
|
import { join as join7 } from "node:path";
|
|
9809
9809
|
|
|
9810
9810
|
// src/prompts/cluster.ts
|
|
9811
|
-
function buildClusterPrompt(failures, skills) {
|
|
9811
|
+
function buildClusterPrompt(failures, skills, graph) {
|
|
9812
9812
|
const failuresJson = failures.map((f) => ({
|
|
9813
9813
|
id: f.id,
|
|
9814
9814
|
project: f.project,
|
|
@@ -9819,6 +9819,29 @@ function buildClusterPrompt(failures, skills) {
|
|
|
9819
9819
|
}));
|
|
9820
9820
|
const skillsList = skills.map((s) => `- ${s.slug}: ${s.meta.description}`).join(`
|
|
9821
9821
|
`);
|
|
9822
|
+
let graphSection = "";
|
|
9823
|
+
if (graph && graph.edges.length > 0) {
|
|
9824
|
+
const edgesList = graph.edges.map((e) => `- ${e.from} --[${e.type}]--> ${e.to} (strength: ${e.strength})`).join(`
|
|
9825
|
+
`);
|
|
9826
|
+
const clustersList = graph.clusters.map((c) => `- ${c.name}: [${c.skills.join(", ")}] (cohesion: ${c.cohesion})`).join(`
|
|
9827
|
+
`);
|
|
9828
|
+
graphSection = `
|
|
9829
|
+
## Skill Network Graph
|
|
9830
|
+
The following relationships exist between skills. Use this to identify when failures might affect multiple related skills.
|
|
9831
|
+
|
|
9832
|
+
### Relationships
|
|
9833
|
+
${edgesList}
|
|
9834
|
+
|
|
9835
|
+
### Clusters
|
|
9836
|
+
${clustersList}
|
|
9837
|
+
|
|
9838
|
+
When clustering failures, consider:
|
|
9839
|
+
- Failures in skills that are in the same cluster likely share root causes
|
|
9840
|
+
- "inherits" edges mean a child skill should follow parent skill rules
|
|
9841
|
+
- "enhances" edges mean skills complement each other
|
|
9842
|
+
- "conflicts" edges mean skills may have contradictory rules
|
|
9843
|
+
`;
|
|
9844
|
+
}
|
|
9822
9845
|
return `You are a failure pattern analyst.
|
|
9823
9846
|
|
|
9824
9847
|
## Task
|
|
@@ -9829,7 +9852,7 @@ ${JSON.stringify(failuresJson, null, 2)}
|
|
|
9829
9852
|
|
|
9830
9853
|
## Current Skills
|
|
9831
9854
|
${skillsList}
|
|
9832
|
-
|
|
9855
|
+
${graphSection}
|
|
9833
9856
|
## Output Requirements
|
|
9834
9857
|
Return a JSON object with this exact structure:
|
|
9835
9858
|
{
|
|
@@ -9846,7 +9869,8 @@ Return a JSON object with this exact structure:
|
|
|
9846
9869
|
- Do not create clusters with only 1 record (unless truly unique)
|
|
9847
9870
|
- Maximum 8 clusters
|
|
9848
9871
|
- Each record belongs to exactly one cluster
|
|
9849
|
-
- Cluster names should be specific (e.g., "WCAG color contrast violations", not "UI issues")
|
|
9872
|
+
- Cluster names should be specific (e.g., "WCAG color contrast violations", not "UI issues")
|
|
9873
|
+
- If skill graph is available, use relationships to identify which skills should be targeted together`;
|
|
9850
9874
|
}
|
|
9851
9875
|
function parseClusterOutput(raw) {
|
|
9852
9876
|
const data = raw;
|
|
@@ -9857,7 +9881,7 @@ function parseClusterOutput(raw) {
|
|
|
9857
9881
|
}
|
|
9858
9882
|
|
|
9859
9883
|
// src/prompts/proposer.ts
|
|
9860
|
-
function buildProposerPrompt(clusterType, failures, relevantSkill, relatedProposals) {
|
|
9884
|
+
function buildProposerPrompt(clusterType, failures, relevantSkill, relatedProposals, graphContext) {
|
|
9861
9885
|
const failureDetails = failures.map((f) => `- [${f.id}] User asked: "${f.userRequest}" → Agent did: "${f.agentAction}" → User corrected: "${f.correction}"`).join(`
|
|
9862
9886
|
`);
|
|
9863
9887
|
const skillContent = relevantSkill ? `### ${relevantSkill.slug}
|
|
@@ -9866,6 +9890,37 @@ ${relevantSkill.content}
|
|
|
9866
9890
|
\`\`\`` : "No existing skill covers this area.";
|
|
9867
9891
|
const historySection = relatedProposals.length > 0 ? relatedProposals.map((p) => `- [${p.id}] ${p.description} → ${p.outcome} (${p.outcomeReason})`).join(`
|
|
9868
9892
|
`) : "No prior proposals for this area.";
|
|
9893
|
+
let graphSection = "";
|
|
9894
|
+
if (graphContext && graphContext.relatedSkills.length > 0) {
|
|
9895
|
+
const relatedEdges = relevantSkill ? graphContext.edges.filter((e) => e.from === relevantSkill.slug || e.to === relevantSkill.slug).map((e) => {
|
|
9896
|
+
const other = e.from === relevantSkill.slug ? e.to : e.from;
|
|
9897
|
+
const direction = e.from === relevantSkill.slug ? "outgoing" : "incoming";
|
|
9898
|
+
return ` - ${e.type} (${direction}): ${other} (strength: ${e.strength})`;
|
|
9899
|
+
}).join(`
|
|
9900
|
+
`) : "";
|
|
9901
|
+
const relatedSkillSummaries = graphContext.relatedSkills.map((s) => {
|
|
9902
|
+
const preview = s.content.slice(0, 200).replace(/\n/g, " ");
|
|
9903
|
+
return ` - ${s.slug} (score: ${s.meta.score ?? "N/A"}, gen: ${s.meta.generation ?? 0}): ${preview}...`;
|
|
9904
|
+
}).join(`
|
|
9905
|
+
`);
|
|
9906
|
+
const clusterInfo = graphContext.cluster ? `This skill belongs to the "${graphContext.cluster.name}" cluster (cohesion: ${graphContext.cluster.cohesion}) with: ${graphContext.cluster.skills.join(", ")}` : "";
|
|
9907
|
+
graphSection = `
|
|
9908
|
+
## Skill Network Context
|
|
9909
|
+
${relevantSkill?.slug ?? "New skill"} has the following relationships in the skill graph:
|
|
9910
|
+
${relatedEdges || " No edges yet"}
|
|
9911
|
+
|
|
9912
|
+
${clusterInfo}
|
|
9913
|
+
|
|
9914
|
+
### Related Skills (summaries)
|
|
9915
|
+
${relatedSkillSummaries}
|
|
9916
|
+
|
|
9917
|
+
## Graph-Aware Guidelines
|
|
9918
|
+
- If this skill "inherits" from a parent, ensure your changes are consistent with the parent skill's rules
|
|
9919
|
+
- If this skill "enhances" another, check that your changes don't conflict with the enhanced skill
|
|
9920
|
+
- If related skills in the same cluster share similar rules, consider whether the fix should be applied at a more abstract level
|
|
9921
|
+
- If the correction addresses a pattern common across multiple related skills, note this in your justification — it may warrant propagation
|
|
9922
|
+
`;
|
|
9923
|
+
}
|
|
9869
9924
|
return `You are a skill evolution expert. Analyze failures and propose a concrete skill improvement.
|
|
9870
9925
|
|
|
9871
9926
|
## Failure Cluster: "${clusterType}"
|
|
@@ -9876,12 +9931,13 @@ ${skillContent}
|
|
|
9876
9931
|
|
|
9877
9932
|
## Prior Proposals (avoid repeating rejected approaches)
|
|
9878
9933
|
${historySection}
|
|
9879
|
-
|
|
9934
|
+
${graphSection}
|
|
9880
9935
|
## Instructions
|
|
9881
9936
|
1. Analyze the root cause shared by these failures
|
|
9882
9937
|
2. Determine: edit existing skill or create new one?
|
|
9883
|
-
3. Generate the exact SKILL.md content (complete file
|
|
9938
|
+
3. Generate the exact SKILL.md content (complete file with frontmatter)
|
|
9884
9939
|
4. Explain why this is better than any rejected prior proposals
|
|
9940
|
+
5. If graph context is available, consider whether the improvement should be structured hierarchically (core principles at top, specific rules below)
|
|
9885
9941
|
|
|
9886
9942
|
## Output
|
|
9887
9943
|
Return JSON:
|
|
@@ -9891,14 +9947,19 @@ Return JSON:
|
|
|
9891
9947
|
"proposedSkillMd": "full SKILL.md content with frontmatter",
|
|
9892
9948
|
"diff": "human-readable description of changes",
|
|
9893
9949
|
"justification": "why this addresses the root cause",
|
|
9894
|
-
"relatedIterations": ["prop_id"]
|
|
9950
|
+
"relatedIterations": ["prop_id"],
|
|
9951
|
+
"propagationSuggestions": ["skill-slug"] // skills that might benefit from similar changes (optional)
|
|
9895
9952
|
}
|
|
9896
9953
|
|
|
9897
9954
|
## Constraints
|
|
9898
9955
|
- YAGNI: every rule must map to a specific failure
|
|
9899
9956
|
- One skill at a time
|
|
9900
9957
|
- Preserve all existing effective rules when editing
|
|
9901
|
-
- Be specific — no vague "consider doing X" rules
|
|
9958
|
+
- Be specific — no vague "consider doing X" rules
|
|
9959
|
+
- When graph context shows skill relationships, organize content hierarchically:
|
|
9960
|
+
1. Inherited/shared principles (from parent skills)
|
|
9961
|
+
2. Core rules (this skill's domain)
|
|
9962
|
+
3. Specific patterns (from failures)`;
|
|
9902
9963
|
}
|
|
9903
9964
|
|
|
9904
9965
|
// src/prompts/replay.ts
|
|
@@ -10398,9 +10459,17 @@ async function evolveCommand(options) {
|
|
|
10398
10459
|
console.log(` Processing ${batch.length} failures (${failures.length} total unresolved)
|
|
10399
10460
|
`);
|
|
10400
10461
|
const skills = loadAllGeneralSkills();
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10462
|
+
const graph = loadSkillGraph();
|
|
10463
|
+
const hasGraph = graph.nodes.length > 0 && graph.edges.length > 0;
|
|
10464
|
+
if (verbose) {
|
|
10465
|
+
console.log(` Skills loaded: ${skills.map((s) => s.slug).join(", ")}`);
|
|
10466
|
+
if (hasGraph) {
|
|
10467
|
+
console.log(` Graph: ${graph.nodes.length} nodes, ${graph.edges.length} edges, ${graph.clusters.length} clusters`);
|
|
10468
|
+
} else {
|
|
10469
|
+
console.log(` Graph: not built yet (run "helixevo graph --rebuild" for graph-aware evolution)`);
|
|
10470
|
+
}
|
|
10471
|
+
console.log();
|
|
10472
|
+
}
|
|
10404
10473
|
const stagnation = getStagnationCount();
|
|
10405
10474
|
if (stagnation >= config.evolution.stopAfterNoImprovement) {
|
|
10406
10475
|
console.log(` ⚠ Stagnation detected (${stagnation} rounds with no improvement).`);
|
|
@@ -10408,7 +10477,7 @@ async function evolveCommand(options) {
|
|
|
10408
10477
|
`);
|
|
10409
10478
|
}
|
|
10410
10479
|
console.log(" Step 1: Clustering failures...");
|
|
10411
|
-
const clusterPrompt = buildClusterPrompt(batch, skills);
|
|
10480
|
+
const clusterPrompt = buildClusterPrompt(batch, skills, hasGraph ? graph : undefined);
|
|
10412
10481
|
const clusterResult = await chatJson({ prompt: clusterPrompt });
|
|
10413
10482
|
const clusters = parseClusterOutput(clusterResult);
|
|
10414
10483
|
console.log(` → ${clusters.clusters.length} cluster(s) found
|
|
@@ -10436,7 +10505,10 @@ async function evolveCommand(options) {
|
|
|
10436
10505
|
});
|
|
10437
10506
|
const related = findRelatedProposals(relevantSkill?.slug ?? "", cluster.type);
|
|
10438
10507
|
console.log(" Step 3: Generating proposal...");
|
|
10439
|
-
const
|
|
10508
|
+
const relatedSkillSlugs = hasGraph && relevantSkill ? graph.edges.filter((e) => e.from === relevantSkill.slug || e.to === relevantSkill.slug).map((e) => e.from === relevantSkill.slug ? e.to : e.from) : [];
|
|
10509
|
+
const relatedSkillContents = relatedSkillSlugs.map((slug) => skills.find((s) => s.slug === slug)).filter((s) => s !== null);
|
|
10510
|
+
const skillCluster = hasGraph ? graph.clusters.find((c) => c.skills.includes(relevantSkill?.slug ?? "")) : undefined;
|
|
10511
|
+
const proposerPrompt = buildProposerPrompt(cluster.type, clusterFailures, relevantSkill, related, hasGraph ? { relatedSkills: relatedSkillContents, edges: graph.edges, cluster: skillCluster } : undefined);
|
|
10440
10512
|
const proposalOutput = await chatJson({ prompt: proposerPrompt });
|
|
10441
10513
|
if (verbose) {
|
|
10442
10514
|
console.log(` Action: ${proposalOutput.action}`);
|
|
@@ -10554,8 +10626,15 @@ async function evolveCommand(options) {
|
|
|
10554
10626
|
if (evicted)
|
|
10555
10627
|
console.log(` Evicted: ${evicted.id} (score: ${evicted.score.toFixed(2)})`);
|
|
10556
10628
|
}
|
|
10629
|
+
if (proposalOutput.propagationSuggestions?.length) {
|
|
10630
|
+
console.log(` ↗ Propagation suggested to: ${proposalOutput.propagationSuggestions.join(", ")}`);
|
|
10631
|
+
console.log(" (Run evolve again or generalize to apply these)");
|
|
10632
|
+
}
|
|
10557
10633
|
} else if (finalAccepted && dryRun) {
|
|
10558
10634
|
console.log(` [DRY RUN] Would apply: ${proposal.targetSkill}`);
|
|
10635
|
+
if (proposalOutput.propagationSuggestions?.length) {
|
|
10636
|
+
console.log(` ↗ Would propagate to: ${proposalOutput.propagationSuggestions.join(", ")}`);
|
|
10637
|
+
}
|
|
10559
10638
|
}
|
|
10560
10639
|
proposals.push(proposal);
|
|
10561
10640
|
proposalCount++;
|
|
@@ -10569,7 +10648,25 @@ async function evolveCommand(options) {
|
|
|
10569
10648
|
proposals
|
|
10570
10649
|
};
|
|
10571
10650
|
addIteration(iteration);
|
|
10572
|
-
|
|
10651
|
+
if (hasGraph) {
|
|
10652
|
+
const accepted2 = proposals.filter((p) => p.outcome === "accepted");
|
|
10653
|
+
if (accepted2.length > 0) {
|
|
10654
|
+
console.log(" Step 7: Updating skill graph...");
|
|
10655
|
+
for (const p of accepted2) {
|
|
10656
|
+
const node = graph.nodes.find((n) => n.id === p.targetSkill);
|
|
10657
|
+
if (node) {
|
|
10658
|
+
node.generation = generation;
|
|
10659
|
+
node.score = (p.judges.taskCompletion.score + p.judges.correctionAlignment.score + p.judges.sideEffectCheck.score) / 30;
|
|
10660
|
+
node.lastEvolved = new Date().toISOString();
|
|
10661
|
+
}
|
|
10662
|
+
}
|
|
10663
|
+
graph.updated = new Date().toISOString();
|
|
10664
|
+
saveSkillGraph(graph);
|
|
10665
|
+
console.log(` ✓ Graph updated (${accepted2.length} node(s) evolved)
|
|
10666
|
+
`);
|
|
10667
|
+
}
|
|
10668
|
+
}
|
|
10669
|
+
console.log(" Step 8: Network health assessment...");
|
|
10573
10670
|
const health = await assessNetworkHealth(verbose);
|
|
10574
10671
|
if (verbose)
|
|
10575
10672
|
printHealthReport(health);
|
package/package.json
CHANGED