closed-loop-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of closed-loop-cli might be problematic. Click here for more details.
- package/dist/dashboard/server.js +237 -0
- package/dist/index.js +272 -0
- package/dist/orchestrator/agent-prompts.js +42 -0
- package/dist/orchestrator/autogenesis.js +973 -0
- package/dist/orchestrator/dgm-archive.js +223 -0
- package/dist/orchestrator/event-stream.js +103 -0
- package/dist/orchestrator/fitness-evaluator.js +99 -0
- package/dist/orchestrator/meta-agent.js +421 -0
- package/dist/orchestrator/microagent-registry.js +134 -0
- package/dist/orchestrator/mutation-strategies.js +174 -0
- package/dist/orchestrator/prompt-benchmark.js +102 -0
- package/dist/orchestrator/prompt-optimizer.js +169 -0
- package/dist/orchestrator/refactor-scanner.js +222 -0
- package/dist/orchestrator/research-manager.js +104 -0
- package/dist/orchestrator/rulez.js +135 -0
- package/dist/orchestrator/sahoo-gateway.js +261 -0
- package/dist/orchestrator/state-manager.js +121 -0
- package/dist/orchestrator/task-agent.js +444 -0
- package/dist/orchestrator/telegram-bot.js +374 -0
- package/dist/orchestrator/types.js +2 -0
- package/dist/tests/dynamic/dependencies.test.js +37 -0
- package/dist/tests/dynamic/dummy.test.js +7 -0
- package/dist/tests/dynamic/fuzzy-patch.test.js +68 -0
- package/dist/tests/dynamic/indexer.test.js +60 -0
- package/dist/tests/dynamic/openhands.test.js +83 -0
- package/dist/tests/dynamic/skills.test.js +88 -0
- package/dist/tests/run-tests.js +294 -0
- package/dist/tools/diff-tools.js +24 -0
- package/dist/tools/file-tools.js +191 -0
- package/dist/tools/indexer.js +301 -0
- package/dist/tools/math-helper.js +6 -0
- package/dist/tools/repo-map.js +122 -0
- package/dist/tools/search-tools.js +271 -0
- package/dist/tools/shell-tools.js +75 -0
- package/dist/tools/skills.js +122 -0
- package/dist/tools/tui-tools.js +82 -0
- package/docs/AI_Arch_Opt_Anti_Gaming.md +227 -0
- package/docs/AI_Self_Improvement_Safety.md +457 -0
- package/docs/Anthropic AI Agents_ Capabilities and Concerns.md +134 -0
- package/docs/Auto_ClosedLoop_AI_Agent.md +415 -0
- package/docs/Autonomous AI Agents_ Closing the Loop.docx +0 -0
- package/docs/Secure_AI_Sandbox_Framework.md +358 -0
- package/docs/skills/add-file-existence-check-utility.json +9 -0
- package/docs/skills/add-utility-function-for-file-existence-check.json +9 -0
- package/docs/skills/add-utility-function-to-module.json +9 -0
- package/docs/skills/extract-command-runner-utility.json +9 -0
- package/docs/skills/file-existence-check-utility.json +9 -0
- package/package.json +36 -0
- package/src/dashboard/public/index.css +1334 -0
- package/src/dashboard/public/index.html +385 -0
- package/src/dashboard/public/index.js +1059 -0
- package/src/dashboard/server.ts +209 -0
- package/src/index.ts +256 -0
- package/src/orchestrator/agent-prompts.ts +43 -0
- package/src/orchestrator/autogenesis.ts +1078 -0
- package/src/orchestrator/dgm-archive.ts +257 -0
- package/src/orchestrator/event-stream.ts +90 -0
- package/src/orchestrator/fitness-evaluator.ts +154 -0
- package/src/orchestrator/meta-agent.ts +434 -0
- package/src/orchestrator/microagent-registry.ts +115 -0
- package/src/orchestrator/microagents/git-helper.md +11 -0
- package/src/orchestrator/microagents/test-fixer.md +10 -0
- package/src/orchestrator/microagents/typescript-expert.md +11 -0
- package/src/orchestrator/mutation-strategies.ts +214 -0
- package/src/orchestrator/research-manager.ts +88 -0
- package/src/orchestrator/rulez.ts +118 -0
- package/src/orchestrator/sahoo-gateway.ts +300 -0
- package/src/orchestrator/state-manager.ts +161 -0
- package/src/orchestrator/system-prompt.txt +1 -0
- package/src/orchestrator/task-agent.ts +461 -0
- package/src/orchestrator/telegram-bot.ts +358 -0
- package/src/tests/dynamic/dependencies.test.ts +48 -0
- package/src/tests/dynamic/dummy.test.ts +4 -0
- package/src/tests/dynamic/fuzzy-patch.test.ts +42 -0
- package/src/tests/dynamic/indexer.test.ts +31 -0
- package/src/tests/dynamic/openhands.test.ts +59 -0
- package/src/tests/dynamic/skills.test.ts +63 -0
- package/src/tests/run-tests.ts +296 -0
- package/src/tools/diff-tools.ts +27 -0
- package/src/tools/file-tools.ts +187 -0
- package/src/tools/indexer.ts +325 -0
- package/src/tools/repo-map.ts +96 -0
- package/src/tools/search-tools.ts +258 -0
- package/src/tools/shell-tools.ts +90 -0
- package/src/tools/skills.ts +101 -0
- package/src/tools/tui-tools.ts +87 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DGMArchive = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const ARCHIVE_FILENAME = 'dgm-archive.json';
|
|
40
|
+
const DEFAULT_MAX_SIZE = 20;
|
|
41
|
+
/**
|
|
42
|
+
* DGM Archive Manager
|
|
43
|
+
*
|
|
44
|
+
* เก็บ population ของ agent snapshots และจัดการ selection สำหรับ
|
|
45
|
+
* Darwin Gödel Machine evolution loop ตาม paper:
|
|
46
|
+
* "Darwin Gödel Machine: Open-Ended Evolution of Self-Improving Agents"
|
|
47
|
+
*/
|
|
48
|
+
class DGMArchive {
|
|
49
|
+
archivePath;
|
|
50
|
+
entries;
|
|
51
|
+
constructor(workspaceRoot) {
|
|
52
|
+
const root = workspaceRoot || process.cwd();
|
|
53
|
+
this.archivePath = path.join(root, ARCHIVE_FILENAME);
|
|
54
|
+
this.entries = this.load();
|
|
55
|
+
}
|
|
56
|
+
// ─────────────────────────────────────────
|
|
57
|
+
// Persistence
|
|
58
|
+
// ─────────────────────────────────────────
|
|
59
|
+
load() {
|
|
60
|
+
if (!fs.existsSync(this.archivePath)) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const raw = fs.readFileSync(this.archivePath, 'utf-8');
|
|
65
|
+
const parsed = JSON.parse(raw);
|
|
66
|
+
return Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
save() {
|
|
73
|
+
const data = {
|
|
74
|
+
version: '1.0.0',
|
|
75
|
+
lastUpdated: new Date().toISOString(),
|
|
76
|
+
entries: this.entries
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
fs.writeFileSync(this.archivePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.error('[DGMArchive] Failed to save archive:', err);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ─────────────────────────────────────────
|
|
86
|
+
// Core Operations
|
|
87
|
+
// ─────────────────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* เพิ่ม snapshot ใหม่เข้า archive
|
|
90
|
+
* @returns entry ที่เพิ่งเพิ่มเข้าไป
|
|
91
|
+
*/
|
|
92
|
+
addEntry(entry) {
|
|
93
|
+
const newEntry = {
|
|
94
|
+
...entry,
|
|
95
|
+
id: this.generateId(),
|
|
96
|
+
timestamp: new Date().toISOString()
|
|
97
|
+
};
|
|
98
|
+
this.entries.push(newEntry);
|
|
99
|
+
this.save();
|
|
100
|
+
return newEntry;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* เลือก parent entry สำหรับ mutation รอบต่อไป
|
|
104
|
+
* ใช้ fitness-weighted sampling (DGM parent selection strategy)
|
|
105
|
+
*
|
|
106
|
+
* @param strategy 'fitness_weighted' | 'best' | 'random'
|
|
107
|
+
*/
|
|
108
|
+
getBestParent(strategy = 'fitness_weighted') {
|
|
109
|
+
if (this.entries.length === 0)
|
|
110
|
+
return null;
|
|
111
|
+
if (strategy === 'best') {
|
|
112
|
+
return [...this.entries].sort((a, b) => b.fitness - a.fitness)[0];
|
|
113
|
+
}
|
|
114
|
+
if (strategy === 'random') {
|
|
115
|
+
return this.entries[Math.floor(Math.random() * this.entries.length)];
|
|
116
|
+
}
|
|
117
|
+
// fitness_weighted: สุ่มโดยน้ำหนักตาม fitness score (DGM default)
|
|
118
|
+
return this.fitnessWeightedSample();
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Trace lineage จาก entry ไปถึง origin
|
|
122
|
+
* @returns ordered array ตั้งแต่ origin จนถึง entry นี้
|
|
123
|
+
*/
|
|
124
|
+
getLineage(entryId) {
|
|
125
|
+
const lineage = [];
|
|
126
|
+
let current = this.findById(entryId);
|
|
127
|
+
while (current) {
|
|
128
|
+
lineage.unshift(current);
|
|
129
|
+
if (!current.parentId)
|
|
130
|
+
break;
|
|
131
|
+
current = this.findById(current.parentId);
|
|
132
|
+
}
|
|
133
|
+
return lineage;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* ตัด entries เก่าเพื่อป้องกัน archive ใหญ่เกินไป
|
|
137
|
+
* จะเก็บ entries ที่มี fitness สูงสุดไว้ก่อน
|
|
138
|
+
*/
|
|
139
|
+
pruneOldEntries(maxSize = DEFAULT_MAX_SIZE) {
|
|
140
|
+
if (this.entries.length <= maxSize)
|
|
141
|
+
return 0;
|
|
142
|
+
// เรียง fitness สูงสุดก่อน แล้วตัดส่วนที่เกิน
|
|
143
|
+
const sorted = [...this.entries].sort((a, b) => b.fitness - a.fitness);
|
|
144
|
+
const pruned = this.entries.length - maxSize;
|
|
145
|
+
this.entries = sorted.slice(0, maxSize);
|
|
146
|
+
this.save();
|
|
147
|
+
return pruned;
|
|
148
|
+
}
|
|
149
|
+
// ─────────────────────────────────────────
|
|
150
|
+
// Query / Stats
|
|
151
|
+
// ─────────────────────────────────────────
|
|
152
|
+
getAll() {
|
|
153
|
+
return [...this.entries];
|
|
154
|
+
}
|
|
155
|
+
findById(id) {
|
|
156
|
+
return this.entries.find(e => e.id === id);
|
|
157
|
+
}
|
|
158
|
+
getBestEntry() {
|
|
159
|
+
if (this.entries.length === 0)
|
|
160
|
+
return null;
|
|
161
|
+
return [...this.entries].sort((a, b) => b.fitness - a.fitness)[0];
|
|
162
|
+
}
|
|
163
|
+
getStats() {
|
|
164
|
+
if (this.entries.length === 0) {
|
|
165
|
+
return {
|
|
166
|
+
totalEntries: 0,
|
|
167
|
+
bestFitness: 0,
|
|
168
|
+
averageFitness: 0,
|
|
169
|
+
bestEntryId: null,
|
|
170
|
+
latestEntryId: null,
|
|
171
|
+
generationDepth: 0
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const sorted = [...this.entries].sort((a, b) => b.fitness - a.fitness);
|
|
175
|
+
const best = sorted[0];
|
|
176
|
+
const latest = [...this.entries].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())[0];
|
|
177
|
+
const avgFitness = this.entries.reduce((sum, e) => sum + e.fitness, 0) / this.entries.length;
|
|
178
|
+
const depth = latest ? this.getLineage(latest.id).length : 0;
|
|
179
|
+
return {
|
|
180
|
+
totalEntries: this.entries.length,
|
|
181
|
+
bestFitness: best.fitness,
|
|
182
|
+
averageFitness: Math.round(avgFitness * 1000) / 1000,
|
|
183
|
+
bestEntryId: best.id,
|
|
184
|
+
latestEntryId: latest?.id ?? null,
|
|
185
|
+
generationDepth: depth
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* ดึง history ล่าสุด N entries (เรียงตามเวลา)
|
|
190
|
+
*/
|
|
191
|
+
getRecentHistory(n = 10) {
|
|
192
|
+
return [...this.entries]
|
|
193
|
+
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
|
194
|
+
.slice(0, n);
|
|
195
|
+
}
|
|
196
|
+
// ─────────────────────────────────────────
|
|
197
|
+
// Private Helpers
|
|
198
|
+
// ─────────────────────────────────────────
|
|
199
|
+
/**
|
|
200
|
+
* Fitness-weighted random sampling (Roulette Wheel Selection)
|
|
201
|
+
* สูตรจาก DGM paper: เลือก parent ตาม probability ∝ fitness
|
|
202
|
+
*/
|
|
203
|
+
fitnessWeightedSample() {
|
|
204
|
+
const totalFitness = this.entries.reduce((sum, e) => sum + Math.max(e.fitness, 0.01), 0);
|
|
205
|
+
let r = Math.random() * totalFitness;
|
|
206
|
+
for (const entry of this.entries) {
|
|
207
|
+
r -= Math.max(entry.fitness, 0.01);
|
|
208
|
+
if (r <= 0)
|
|
209
|
+
return entry;
|
|
210
|
+
}
|
|
211
|
+
// fallback: คืน entry สุดท้าย
|
|
212
|
+
return this.entries[this.entries.length - 1];
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* สร้าง unique ID แบบ timestamp + random
|
|
216
|
+
*/
|
|
217
|
+
generateId() {
|
|
218
|
+
const ts = Date.now().toString(36);
|
|
219
|
+
const rand = Math.random().toString(36).substring(2, 6);
|
|
220
|
+
return `dgm_${ts}_${rand}`;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
exports.DGMArchive = DGMArchive;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.EventStream = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
class EventStream {
|
|
40
|
+
static instance;
|
|
41
|
+
filePath;
|
|
42
|
+
events = [];
|
|
43
|
+
constructor() {
|
|
44
|
+
const workspaceRoot = process.cwd();
|
|
45
|
+
this.filePath = path.join(workspaceRoot, 'event-stream-history.json');
|
|
46
|
+
this.loadEvents();
|
|
47
|
+
}
|
|
48
|
+
static getInstance() {
|
|
49
|
+
if (!EventStream.instance) {
|
|
50
|
+
EventStream.instance = new EventStream();
|
|
51
|
+
}
|
|
52
|
+
return EventStream.instance;
|
|
53
|
+
}
|
|
54
|
+
loadEvents() {
|
|
55
|
+
try {
|
|
56
|
+
if (fs.existsSync(this.filePath)) {
|
|
57
|
+
const data = fs.readFileSync(this.filePath, 'utf-8');
|
|
58
|
+
this.events = JSON.parse(data);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
this.events = [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
console.error('[EventStream] Failed to load event stream history:', e);
|
|
66
|
+
this.events = [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
saveEvents() {
|
|
70
|
+
try {
|
|
71
|
+
fs.writeFileSync(this.filePath, JSON.stringify(this.events, null, 2), 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
console.error('[EventStream] Failed to save event stream history:', e);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
publish(source, type, name, content, metadata) {
|
|
78
|
+
const event = {
|
|
79
|
+
id: Math.random().toString(36).substring(2, 11),
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
source,
|
|
82
|
+
type,
|
|
83
|
+
name,
|
|
84
|
+
content,
|
|
85
|
+
metadata
|
|
86
|
+
};
|
|
87
|
+
this.events.push(event);
|
|
88
|
+
// Cap at 1000 events to prevent massive file sizes
|
|
89
|
+
if (this.events.length > 1000) {
|
|
90
|
+
this.events.shift();
|
|
91
|
+
}
|
|
92
|
+
this.saveEvents();
|
|
93
|
+
return event;
|
|
94
|
+
}
|
|
95
|
+
getEvents() {
|
|
96
|
+
return this.events;
|
|
97
|
+
}
|
|
98
|
+
clear() {
|
|
99
|
+
this.events = [];
|
|
100
|
+
this.saveEvents();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.EventStream = EventStream;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateFitness = evaluateFitness;
|
|
4
|
+
exports.compareToBaseline = compareToBaseline;
|
|
5
|
+
exports.parseTestOutput = parseTestOutput;
|
|
6
|
+
const shell_tools_1 = require("../tools/shell-tools");
|
|
7
|
+
/**
|
|
8
|
+
* วัด empirical fitness จาก test suite ปัจจุบัน
|
|
9
|
+
*
|
|
10
|
+
* DGM approach: fitness วัดจากผล unit test จริง ไม่ใช่ heuristic เพียงอย่างเดียว
|
|
11
|
+
* "Each new agent version is evaluated empirically using coding benchmarks"
|
|
12
|
+
*/
|
|
13
|
+
async function evaluateFitness() {
|
|
14
|
+
// Step 1: ตรวจ compile
|
|
15
|
+
const compileRes = await (0, shell_tools_1.runCommand)('npm run build');
|
|
16
|
+
const compileSuccess = compileRes.exitCode === 0;
|
|
17
|
+
if (!compileSuccess) {
|
|
18
|
+
return {
|
|
19
|
+
passRate: 0,
|
|
20
|
+
passCount: 0,
|
|
21
|
+
failCount: 0,
|
|
22
|
+
totalTests: 0,
|
|
23
|
+
score: 0,
|
|
24
|
+
compileSuccess: false,
|
|
25
|
+
summary: `Compilation failed — fitness = 0`,
|
|
26
|
+
rawOutput: compileRes.stdout + compileRes.stderr
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Step 2: run tests
|
|
30
|
+
const testRes = await (0, shell_tools_1.runCommand)('npm test');
|
|
31
|
+
const rawOutput = (testRes.stdout || '') + (testRes.stderr || '');
|
|
32
|
+
// Step 3: parse test output
|
|
33
|
+
const parsed = parseTestOutput(rawOutput);
|
|
34
|
+
// Step 4: คำนวณ composite score
|
|
35
|
+
// score = pass_rate ถ้า compile ผ่าน, 0 ถ้า compile fail
|
|
36
|
+
const score = compileSuccess ? parsed.passRate : 0;
|
|
37
|
+
const summary = `Compile: ${compileSuccess ? 'PASS' : 'FAIL'} | ` +
|
|
38
|
+
`Tests: ${parsed.passCount}/${parsed.totalTests} passed ` +
|
|
39
|
+
`(${(parsed.passRate * 100).toFixed(1)}%) | Score: ${score.toFixed(3)}`;
|
|
40
|
+
return {
|
|
41
|
+
...parsed,
|
|
42
|
+
score,
|
|
43
|
+
compileSuccess,
|
|
44
|
+
summary,
|
|
45
|
+
rawOutput
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* เปรียบเทียบ fitness ระหว่าง baseline snapshot กับ candidate snapshot
|
|
50
|
+
*/
|
|
51
|
+
function compareToBaseline(candidate, baseline) {
|
|
52
|
+
const delta = candidate.score - baseline.score;
|
|
53
|
+
const improved = delta > 0;
|
|
54
|
+
const message = improved
|
|
55
|
+
? `✔ Fitness improved: ${baseline.score.toFixed(3)} → ${candidate.score.toFixed(3)} (+${delta.toFixed(3)})`
|
|
56
|
+
: delta === 0
|
|
57
|
+
? `= Fitness unchanged: ${candidate.score.toFixed(3)}`
|
|
58
|
+
: `✘ Fitness regressed: ${baseline.score.toFixed(3)} → ${candidate.score.toFixed(3)} (${delta.toFixed(3)})`;
|
|
59
|
+
return { improved, delta, baseline, candidate, message };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parse test runner output เพื่อนับ pass/fail counts
|
|
63
|
+
* รองรับ format ของ test runner ปัจจุบันในโปรเจกต์
|
|
64
|
+
*/
|
|
65
|
+
function parseTestOutput(output) {
|
|
66
|
+
let passCount = 0;
|
|
67
|
+
let failCount = 0;
|
|
68
|
+
// Pattern 1: "[Pass] <test name>" ซึ่งเป็น format ของ run-tests.ts ปัจจุบัน
|
|
69
|
+
const passMatches = output.match(/\[Pass\]/g);
|
|
70
|
+
const failMatches = output.match(/\[Fail\]/g);
|
|
71
|
+
if (passMatches || failMatches) {
|
|
72
|
+
passCount = passMatches ? passMatches.length : 0;
|
|
73
|
+
failCount = failMatches ? failMatches.length : 0;
|
|
74
|
+
}
|
|
75
|
+
// Pattern 2: Mocha format — "X passing" / "X failing"
|
|
76
|
+
const mochaPassing = output.match(/(\d+)\s+passing/i);
|
|
77
|
+
const mochaFailing = output.match(/(\d+)\s+failing/i);
|
|
78
|
+
if (mochaPassing || mochaFailing) {
|
|
79
|
+
passCount = mochaPassing ? parseInt(mochaPassing[1], 10) : passCount;
|
|
80
|
+
failCount = mochaFailing ? parseInt(mochaFailing[1], 10) : failCount;
|
|
81
|
+
}
|
|
82
|
+
// Pattern 3: Jest format — "Tests: X passed, Y failed"
|
|
83
|
+
const jestLine = output.match(/Tests:\s+(\d+)\s+passed(?:,\s+(\d+)\s+failed)?/i);
|
|
84
|
+
if (jestLine) {
|
|
85
|
+
passCount = parseInt(jestLine[1], 10) || passCount;
|
|
86
|
+
failCount = jestLine[2] ? parseInt(jestLine[2], 10) : failCount;
|
|
87
|
+
}
|
|
88
|
+
// Pattern 4: "ALL BINARY ASSERTION TESTS PASSED" — หมายถึง 100% ผ่าน
|
|
89
|
+
if (output.includes('ALL BINARY ASSERTION TESTS PASSED')) {
|
|
90
|
+
// ถ้าตรวจจับ pass ไม่ได้ชัดเจนให้ fallback = ผ่านทั้งหมด
|
|
91
|
+
if (passCount === 0 && failCount === 0) {
|
|
92
|
+
passCount = 11; // จำนวน tests ใน run-tests.ts (Test 1–11)
|
|
93
|
+
failCount = 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const totalTests = passCount + failCount;
|
|
97
|
+
const passRate = totalTests > 0 ? passCount / totalTests : (failCount === 0 ? 1 : 0);
|
|
98
|
+
return { passRate, passCount, failCount, totalTests };
|
|
99
|
+
}
|