rex-claude 4.0.0 → 6.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.
- package/dist/agents-JIZXXASP.js +853 -0
- package/dist/app-3VWDSH5F.js +248 -0
- package/dist/audio-US2J627E.js +196 -0
- package/dist/audit-ZVTGE4L4.js +8 -0
- package/dist/call-AQZ3Z5SE.js +143 -0
- package/dist/chunk-5ND7JYY3.js +62 -0
- package/dist/chunk-6SRV2I2H.js +56 -0
- package/dist/{setup-AO3MW46W.js → chunk-A7ZLQUOX.js} +93 -16
- package/dist/chunk-E5UYN3W7.js +105 -0
- package/dist/chunk-HAHJD3QH.js +147 -0
- package/dist/{init-DLFEGD6O.js → chunk-KR7ISYZH.js} +328 -29
- package/dist/chunk-LTOM55UV.js +154 -0
- package/dist/chunk-PDX44BCA.js +11 -0
- package/dist/chunk-PPGYFMU5.js +67 -0
- package/dist/{chunk-7AGI43F5.js → chunk-WBMVBMWB.js} +4 -2
- package/dist/{context-FN5O5YBI.js → context-XNCG2M5Q.js} +2 -1
- package/dist/daemon-5KNSNFTD.js +208 -0
- package/dist/gateway-YLP66MCQ.js +2273 -0
- package/dist/hammerspoon/rex-call-watcher.lua +186 -0
- package/dist/index.js +309 -15
- package/dist/init-RDZFIBLA.js +30 -0
- package/dist/install-63JBDPRU.js +41 -0
- package/dist/{llm-YRORUH7E.js → llm-RALIPIMI.js} +2 -1
- package/dist/mcp_registry-DX4GGSP6.js +514 -0
- package/dist/migrate-GDO37TI5.js +87 -0
- package/dist/{optimize-UKMAGQQE.js → optimize-5TE5RKZV.js} +2 -1
- package/dist/paths-4SECM6E6.js +38 -0
- package/dist/preload-I3MYBVNU.js +78 -0
- package/dist/projects-V6TSLO7E.js +17 -0
- package/dist/{prune-2PPIVDXK.js → prune-B7F5B5OF.js} +2 -1
- package/dist/recategorize-YXYIMQLZ.js +155 -0
- package/dist/router-2JD34COX.js +12 -0
- package/dist/self-improve-YK7RCYF4.js +197 -0
- package/dist/setup-KNDTVFO6.js +8 -0
- package/dist/skills-AIWFY5NH.js +374 -0
- package/dist/voice-RITC3EVC.js +248 -0
- package/package.json +12 -3
- package/dist/gateway-EKMU5D7J.js +0 -784
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
findProject
|
|
4
|
+
} from "./chunk-LTOM55UV.js";
|
|
5
|
+
import "./chunk-PPGYFMU5.js";
|
|
6
|
+
import {
|
|
7
|
+
createLogger
|
|
8
|
+
} from "./chunk-5ND7JYY3.js";
|
|
9
|
+
import {
|
|
10
|
+
MEMORY_DB_PATH
|
|
11
|
+
} from "./chunk-6SRV2I2H.js";
|
|
12
|
+
import "./chunk-PDX44BCA.js";
|
|
13
|
+
|
|
14
|
+
// src/preload.ts
|
|
15
|
+
import Database from "better-sqlite3";
|
|
16
|
+
import * as sqliteVec from "sqlite-vec";
|
|
17
|
+
import { existsSync } from "fs";
|
|
18
|
+
var log = createLogger("preload");
|
|
19
|
+
var MAX_TOKENS = 200;
|
|
20
|
+
async function preload(cwd) {
|
|
21
|
+
if (!existsSync(MEMORY_DB_PATH)) {
|
|
22
|
+
log.debug("No memory DB found, skipping preload");
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
const project = findProject(cwd);
|
|
26
|
+
const sections = [];
|
|
27
|
+
let db;
|
|
28
|
+
try {
|
|
29
|
+
db = new Database(MEMORY_DB_PATH, { readonly: true });
|
|
30
|
+
sqliteVec.load(db);
|
|
31
|
+
db.pragma("journal_mode = WAL");
|
|
32
|
+
} catch (e) {
|
|
33
|
+
log.error(`Failed to open memory DB: ${e.message?.slice(0, 100)}`);
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
if (project) {
|
|
38
|
+
const recent = db.prepare(
|
|
39
|
+
"SELECT summary, category FROM memories WHERE content LIKE ? AND category != 'session' AND summary IS NOT NULL ORDER BY created_at DESC LIMIT 3"
|
|
40
|
+
).all(`%${project.name}%`);
|
|
41
|
+
if (recent.length) {
|
|
42
|
+
sections.push(`[REX Context] Project: ${project.name} | ${project.stack.join(", ")}`);
|
|
43
|
+
sections.push(`Last: ${recent[0].summary.slice(0, 80)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const lessons = db.prepare(
|
|
47
|
+
"SELECT summary FROM memories WHERE category = 'lesson' AND summary IS NOT NULL ORDER BY created_at DESC LIMIT 3"
|
|
48
|
+
).all();
|
|
49
|
+
if (lessons.length) {
|
|
50
|
+
sections.push("Lessons:");
|
|
51
|
+
for (const l of lessons) {
|
|
52
|
+
sections.push(` - ${l.summary.slice(0, 100)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (project) {
|
|
56
|
+
const patterns = db.prepare(
|
|
57
|
+
"SELECT summary FROM memories WHERE category = 'pattern' AND summary IS NOT NULL AND content LIKE ? ORDER BY created_at DESC LIMIT 2"
|
|
58
|
+
).all(`%${project.name}%`);
|
|
59
|
+
if (patterns.length) {
|
|
60
|
+
sections.push("Patterns:");
|
|
61
|
+
for (const p of patterns) {
|
|
62
|
+
sections.push(` - ${p.summary.slice(0, 100)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} finally {
|
|
67
|
+
db.close();
|
|
68
|
+
}
|
|
69
|
+
const output = sections.join("\n");
|
|
70
|
+
log.info(`Preloaded ${sections.length} sections for ${project?.name || cwd} (${output.length} chars)`);
|
|
71
|
+
if (output.length > MAX_TOKENS * 4) {
|
|
72
|
+
return output.slice(0, MAX_TOKENS * 4);
|
|
73
|
+
}
|
|
74
|
+
return output;
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
preload
|
|
78
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
findProject,
|
|
4
|
+
loadProjectIndex,
|
|
5
|
+
saveProjectIndex,
|
|
6
|
+
scanProjects
|
|
7
|
+
} from "./chunk-LTOM55UV.js";
|
|
8
|
+
import "./chunk-PPGYFMU5.js";
|
|
9
|
+
import "./chunk-5ND7JYY3.js";
|
|
10
|
+
import "./chunk-6SRV2I2H.js";
|
|
11
|
+
import "./chunk-PDX44BCA.js";
|
|
12
|
+
export {
|
|
13
|
+
findProject,
|
|
14
|
+
loadProjectIndex,
|
|
15
|
+
saveProjectIndex,
|
|
16
|
+
scanProjects
|
|
17
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-PDX44BCA.js";
|
|
2
3
|
|
|
3
4
|
// src/prune.ts
|
|
4
5
|
import { existsSync, statSync } from "fs";
|
|
@@ -49,7 +50,7 @@ ${line}`);
|
|
|
49
50
|
}
|
|
50
51
|
try {
|
|
51
52
|
Database = (await import(join(memDir, "node_modules", "better-sqlite3", "lib", "index.js"))).default;
|
|
52
|
-
sqliteVec = await import(join(memDir, "node_modules", "sqlite-vec", "
|
|
53
|
+
sqliteVec = await import(join(memDir, "node_modules", "sqlite-vec", "index.mjs"));
|
|
53
54
|
} catch (err) {
|
|
54
55
|
console.log(` ${COLORS.red}Cannot load database modules:${COLORS.reset} ${err.message}
|
|
55
56
|
`);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
pickModel
|
|
4
|
+
} from "./chunk-E5UYN3W7.js";
|
|
5
|
+
import {
|
|
6
|
+
loadConfig
|
|
7
|
+
} from "./chunk-PPGYFMU5.js";
|
|
8
|
+
import {
|
|
9
|
+
createLogger
|
|
10
|
+
} from "./chunk-5ND7JYY3.js";
|
|
11
|
+
import {
|
|
12
|
+
MEMORY_DB_PATH
|
|
13
|
+
} from "./chunk-6SRV2I2H.js";
|
|
14
|
+
import {
|
|
15
|
+
llm
|
|
16
|
+
} from "./chunk-WBMVBMWB.js";
|
|
17
|
+
import "./chunk-PDX44BCA.js";
|
|
18
|
+
|
|
19
|
+
// src/recategorize.ts
|
|
20
|
+
import Database from "better-sqlite3";
|
|
21
|
+
import * as sqliteVec from "sqlite-vec";
|
|
22
|
+
var log = createLogger("recategorize");
|
|
23
|
+
var OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434";
|
|
24
|
+
var VALID_CATEGORIES = ["debug", "fix", "pattern", "lesson", "architecture", "config", "project", "reference", "session"];
|
|
25
|
+
var CLASSIFY_PROMPT = (content) => `Classify this developer memory chunk. Output ONLY valid JSON, no markdown.
|
|
26
|
+
|
|
27
|
+
Categories: debug, fix, pattern, lesson, architecture, config, project, reference, session
|
|
28
|
+
|
|
29
|
+
- debug: debugging an issue, tracing errors
|
|
30
|
+
- fix: applying a fix or patch, solution found
|
|
31
|
+
- pattern: reusable code patterns, techniques
|
|
32
|
+
- lesson: lessons learned, mistakes to avoid
|
|
33
|
+
- architecture: system design, structure decisions
|
|
34
|
+
- config: configuration changes, setup steps
|
|
35
|
+
- project: project overview, stack, status
|
|
36
|
+
- reference: API docs, external knowledge, lib behavior
|
|
37
|
+
- session: general content (default fallback)
|
|
38
|
+
|
|
39
|
+
Content:
|
|
40
|
+
${content.slice(0, 1500)}
|
|
41
|
+
|
|
42
|
+
JSON output: {"category": "<one of the above>", "summary": "<1-2 sentence summary>"}`;
|
|
43
|
+
async function classifyChunk(content, routing, claudeFallback) {
|
|
44
|
+
const prompt = CLASSIFY_PROMPT(content);
|
|
45
|
+
if (routing !== "claude-only") {
|
|
46
|
+
try {
|
|
47
|
+
const model = await pickModel("categorize");
|
|
48
|
+
const res = await fetch(`${OLLAMA_URL}/api/generate`, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { "Content-Type": "application/json" },
|
|
51
|
+
body: JSON.stringify({ model, prompt, stream: false, options: { temperature: 0.3, num_ctx: 4096 } }),
|
|
52
|
+
signal: AbortSignal.timeout(15e3)
|
|
53
|
+
});
|
|
54
|
+
if (res.ok) {
|
|
55
|
+
const data = await res.json();
|
|
56
|
+
const parsed = parseJsonResponse(data.response);
|
|
57
|
+
if (parsed) return parsed;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (routing !== "ollama-only") {
|
|
63
|
+
try {
|
|
64
|
+
const result = await llm(prompt, void 0, claudeFallback);
|
|
65
|
+
return parseJsonResponse(result);
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
function parseJsonResponse(raw) {
|
|
72
|
+
let parsed = null;
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(raw);
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
if (!parsed) {
|
|
78
|
+
const fence = raw.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
79
|
+
if (fence) {
|
|
80
|
+
try {
|
|
81
|
+
parsed = JSON.parse(fence[1].trim());
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!parsed) {
|
|
87
|
+
const brace = raw.match(/\{[\s\S]*\}/);
|
|
88
|
+
if (brace) {
|
|
89
|
+
try {
|
|
90
|
+
parsed = JSON.parse(brace[0]);
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!parsed) return null;
|
|
96
|
+
const category = VALID_CATEGORIES.includes(parsed.category) ? parsed.category : "session";
|
|
97
|
+
const summary = typeof parsed.summary === "string" && parsed.summary.length > 5 ? parsed.summary : null;
|
|
98
|
+
if (!summary) return null;
|
|
99
|
+
return { category, summary };
|
|
100
|
+
}
|
|
101
|
+
async function recategorize(options = {}) {
|
|
102
|
+
const COLORS = { green: "\x1B[32m", yellow: "\x1B[33m", red: "\x1B[31m", cyan: "\x1B[36m", dim: "\x1B[2m", reset: "\x1B[0m", bold: "\x1B[1m" };
|
|
103
|
+
const config = loadConfig();
|
|
104
|
+
const batchSize = options.batch ?? 50;
|
|
105
|
+
const db = new Database(MEMORY_DB_PATH);
|
|
106
|
+
sqliteVec.load(db);
|
|
107
|
+
db.pragma("journal_mode = WAL");
|
|
108
|
+
const rows = db.prepare(
|
|
109
|
+
"SELECT id, content FROM memories WHERE category IN ('session', 'general') OR needs_reprocess = 1 LIMIT ?"
|
|
110
|
+
).all(batchSize);
|
|
111
|
+
log.info(`Starting recategorize: ${rows.length} memories to process (batch: ${batchSize})`);
|
|
112
|
+
console.log(`
|
|
113
|
+
${COLORS.bold}REX Recategorize${COLORS.reset}`);
|
|
114
|
+
console.log(`${COLORS.dim}Found ${rows.length} memories to process (batch: ${batchSize})${COLORS.reset}
|
|
115
|
+
`);
|
|
116
|
+
if (rows.length === 0) {
|
|
117
|
+
console.log(`${COLORS.green}All memories are already categorized.${COLORS.reset}`);
|
|
118
|
+
db.close();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (options.dryRun) {
|
|
122
|
+
console.log(`${COLORS.yellow}[dry-run] Would process ${rows.length} memories. Nothing saved.${COLORS.reset}`);
|
|
123
|
+
db.close();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const update = db.prepare("UPDATE memories SET category = ?, summary = ?, needs_reprocess = 0 WHERE id = ?");
|
|
127
|
+
let processed = 0;
|
|
128
|
+
let failed = 0;
|
|
129
|
+
const stats = {};
|
|
130
|
+
for (const row of rows) {
|
|
131
|
+
const result = await classifyChunk(row.content, config.llm.routing, config.llm.claudeFallback);
|
|
132
|
+
if (result) {
|
|
133
|
+
update.run(result.category, result.summary, row.id);
|
|
134
|
+
stats[result.category] = (stats[result.category] || 0) + 1;
|
|
135
|
+
processed++;
|
|
136
|
+
process.stdout.write(`\r ${COLORS.cyan}${processed}/${rows.length}${COLORS.reset} processed`);
|
|
137
|
+
} else {
|
|
138
|
+
log.warn(`Failed to classify memory id=${row.id}, flagged for retry`);
|
|
139
|
+
db.prepare("UPDATE memories SET needs_reprocess = 1 WHERE id = ?").run(row.id);
|
|
140
|
+
failed++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
log.info(`Done: ${processed} categorized, ${failed} failed`);
|
|
144
|
+
console.log(`
|
|
145
|
+
|
|
146
|
+
${COLORS.green}Done:${COLORS.reset} ${processed} categorized, ${failed} failed (flagged for retry)
|
|
147
|
+
`);
|
|
148
|
+
for (const [cat, count] of Object.entries(stats).sort((a, b) => b[1] - a[1])) {
|
|
149
|
+
console.log(` ${cat.padEnd(15)} ${count}`);
|
|
150
|
+
}
|
|
151
|
+
db.close();
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
recategorize
|
|
155
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
pickModel
|
|
4
|
+
} from "./chunk-E5UYN3W7.js";
|
|
5
|
+
import {
|
|
6
|
+
loadConfig
|
|
7
|
+
} from "./chunk-PPGYFMU5.js";
|
|
8
|
+
import {
|
|
9
|
+
createLogger
|
|
10
|
+
} from "./chunk-5ND7JYY3.js";
|
|
11
|
+
import {
|
|
12
|
+
MEMORY_DB_PATH,
|
|
13
|
+
SELF_IMPROVEMENT_DIR
|
|
14
|
+
} from "./chunk-6SRV2I2H.js";
|
|
15
|
+
import {
|
|
16
|
+
llm
|
|
17
|
+
} from "./chunk-WBMVBMWB.js";
|
|
18
|
+
import "./chunk-PDX44BCA.js";
|
|
19
|
+
|
|
20
|
+
// src/self-improve.ts
|
|
21
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
22
|
+
import { join } from "path";
|
|
23
|
+
import Database from "better-sqlite3";
|
|
24
|
+
import * as sqliteVec from "sqlite-vec";
|
|
25
|
+
var log = createLogger("self-improve");
|
|
26
|
+
function loadLessons() {
|
|
27
|
+
const path = join(SELF_IMPROVEMENT_DIR, "lessons.json");
|
|
28
|
+
if (!existsSync(path)) return [];
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
31
|
+
} catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function saveLessons(lessons) {
|
|
36
|
+
writeFileSync(join(SELF_IMPROVEMENT_DIR, "lessons.json"), JSON.stringify(lessons, null, 2));
|
|
37
|
+
}
|
|
38
|
+
function loadErrorPatterns() {
|
|
39
|
+
const path = join(SELF_IMPROVEMENT_DIR, "error-patterns.json");
|
|
40
|
+
if (!existsSync(path)) return [];
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
43
|
+
} catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function saveErrorPatterns(patterns) {
|
|
48
|
+
writeFileSync(join(SELF_IMPROVEMENT_DIR, "error-patterns.json"), JSON.stringify(patterns, null, 2));
|
|
49
|
+
}
|
|
50
|
+
async function selfReview() {
|
|
51
|
+
const COLORS = { green: "\x1B[32m", yellow: "\x1B[33m", dim: "\x1B[2m", reset: "\x1B[0m", bold: "\x1B[1m" };
|
|
52
|
+
const config = loadConfig();
|
|
53
|
+
if (!config.selfImprovement.enabled) {
|
|
54
|
+
log.info("Self-improvement disabled in config");
|
|
55
|
+
console.log(`${COLORS.dim}Self-improvement disabled in config.${COLORS.reset}`);
|
|
56
|
+
return { newLessons: 0, ruleCandidates: 0 };
|
|
57
|
+
}
|
|
58
|
+
if (!existsSync(MEMORY_DB_PATH)) {
|
|
59
|
+
log.warn(`No memory DB found at ${MEMORY_DB_PATH}`);
|
|
60
|
+
console.log(`${COLORS.yellow}No memory DB found at ${MEMORY_DB_PATH}${COLORS.reset}`);
|
|
61
|
+
return { newLessons: 0, ruleCandidates: 0 };
|
|
62
|
+
}
|
|
63
|
+
console.log(`
|
|
64
|
+
${COLORS.bold}REX Self-Review${COLORS.reset}
|
|
65
|
+
`);
|
|
66
|
+
const db = new Database(MEMORY_DB_PATH, { readonly: true });
|
|
67
|
+
sqliteVec.load(db);
|
|
68
|
+
db.pragma("journal_mode = WAL");
|
|
69
|
+
const recentLessons = db.prepare(
|
|
70
|
+
"SELECT id, summary, content, created_at FROM memories WHERE category = 'lesson' AND summary IS NOT NULL ORDER BY created_at DESC LIMIT 20"
|
|
71
|
+
).all();
|
|
72
|
+
const existingLessons = loadLessons();
|
|
73
|
+
const existingTexts = new Set(existingLessons.map((l) => l.text.toLowerCase().trim()));
|
|
74
|
+
let newCount = 0;
|
|
75
|
+
for (const mem of recentLessons) {
|
|
76
|
+
const text = mem.summary.trim();
|
|
77
|
+
if (existingTexts.has(text.toLowerCase())) {
|
|
78
|
+
const existing = existingLessons.find((l) => l.text.toLowerCase().trim() === text.toLowerCase());
|
|
79
|
+
if (existing) {
|
|
80
|
+
existing.occurrences++;
|
|
81
|
+
existing.lastSeen = mem.created_at;
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
existingLessons.push({
|
|
86
|
+
id: `lesson-${Date.now()}-${mem.id}`,
|
|
87
|
+
text,
|
|
88
|
+
category: "lesson",
|
|
89
|
+
occurrences: 1,
|
|
90
|
+
firstSeen: mem.created_at,
|
|
91
|
+
lastSeen: mem.created_at,
|
|
92
|
+
promoted: false,
|
|
93
|
+
dismissed: false
|
|
94
|
+
});
|
|
95
|
+
newCount++;
|
|
96
|
+
}
|
|
97
|
+
saveLessons(existingLessons);
|
|
98
|
+
log.info(`Lessons: ${existingLessons.length} total, ${newCount} new`);
|
|
99
|
+
console.log(` Lessons: ${existingLessons.length} total, ${newCount} new`);
|
|
100
|
+
const errorMemories = db.prepare(
|
|
101
|
+
"SELECT summary FROM memories WHERE category IN ('debug', 'fix') AND summary IS NOT NULL ORDER BY created_at DESC LIMIT 50"
|
|
102
|
+
).all();
|
|
103
|
+
const patterns = loadErrorPatterns();
|
|
104
|
+
let ruleCandidates = 0;
|
|
105
|
+
if (errorMemories.length >= 5) {
|
|
106
|
+
const errorSummaries = errorMemories.map((m) => m.summary).join("\n");
|
|
107
|
+
try {
|
|
108
|
+
const model = await pickModel("reason");
|
|
109
|
+
console.log(` Analyzing ${errorMemories.length} error/fix memories with ${model}...`);
|
|
110
|
+
const analysis = await llm(
|
|
111
|
+
`Analyze these error/fix summaries for recurring patterns. Output JSON array of patterns:
|
|
112
|
+
|
|
113
|
+
${errorSummaries.slice(0, 3e3)}
|
|
114
|
+
|
|
115
|
+
JSON: [{"pattern": "description", "count": estimated_occurrences, "suggestedRule": "rule text"}]`,
|
|
116
|
+
"You are a code pattern analyzer. Output ONLY valid JSON.",
|
|
117
|
+
model
|
|
118
|
+
);
|
|
119
|
+
let parsed = [];
|
|
120
|
+
try {
|
|
121
|
+
parsed = JSON.parse(analysis);
|
|
122
|
+
} catch {
|
|
123
|
+
const brace = analysis.match(/\[[\s\S]*\]/);
|
|
124
|
+
if (brace) {
|
|
125
|
+
try {
|
|
126
|
+
parsed = JSON.parse(brace[0]);
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const p of parsed) {
|
|
132
|
+
if (p.count >= config.selfImprovement.ruleThreshold) {
|
|
133
|
+
const exists = patterns.find((ep) => ep.pattern === p.pattern);
|
|
134
|
+
if (!exists) {
|
|
135
|
+
patterns.push({
|
|
136
|
+
pattern: p.pattern,
|
|
137
|
+
count: p.count,
|
|
138
|
+
firstSeen: (/* @__PURE__ */ new Date()).toISOString(),
|
|
139
|
+
lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
|
|
140
|
+
suggestedRule: p.suggestedRule
|
|
141
|
+
});
|
|
142
|
+
ruleCandidates++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
log.warn(`Pattern analysis skipped: ${e.message?.slice(0, 100)}`);
|
|
148
|
+
console.log(` ${COLORS.yellow}Pattern analysis skipped (LLM unavailable)${COLORS.reset}`);
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
console.log(` ${COLORS.dim}Not enough error memories for pattern analysis (need 5+, have ${errorMemories.length})${COLORS.reset}`);
|
|
152
|
+
}
|
|
153
|
+
saveErrorPatterns(patterns);
|
|
154
|
+
const activePatterns = patterns.filter((p) => p.count >= config.selfImprovement.ruleThreshold);
|
|
155
|
+
if (activePatterns.length) {
|
|
156
|
+
const md = `# Rule Candidates
|
|
157
|
+
|
|
158
|
+
These patterns were detected ${config.selfImprovement.ruleThreshold}+ times. Review and approve with \`rex promote-rule <index>\`.
|
|
159
|
+
|
|
160
|
+
` + activePatterns.map((p, i) => `## ${i + 1}. ${p.pattern}
|
|
161
|
+
|
|
162
|
+
- Count: ${p.count}
|
|
163
|
+
- First seen: ${p.firstSeen}
|
|
164
|
+
- Suggested rule: ${p.suggestedRule || "N/A"}
|
|
165
|
+
`).join("\n");
|
|
166
|
+
writeFileSync(join(SELF_IMPROVEMENT_DIR, "rule-candidates.md"), md);
|
|
167
|
+
console.log(` Rule candidates: ${activePatterns.length} (see ~/.claude/rex/self-improvement/rule-candidates.md)`);
|
|
168
|
+
}
|
|
169
|
+
db.close();
|
|
170
|
+
log.info(`Self-review done: ${newCount} new lessons, ${ruleCandidates} rule candidates`);
|
|
171
|
+
console.log(`
|
|
172
|
+
${COLORS.green}Done.${COLORS.reset} ${newCount} new lessons, ${ruleCandidates} rule candidates
|
|
173
|
+
`);
|
|
174
|
+
return { newLessons: newCount, ruleCandidates };
|
|
175
|
+
}
|
|
176
|
+
async function promoteRule(index) {
|
|
177
|
+
const patterns = loadErrorPatterns();
|
|
178
|
+
const active = patterns.filter((p) => p.count >= loadConfig().selfImprovement.ruleThreshold);
|
|
179
|
+
if (index < 1 || index > active.length) return false;
|
|
180
|
+
const pattern = active[index - 1];
|
|
181
|
+
if (!pattern.suggestedRule) return false;
|
|
182
|
+
const HOME = process.env.HOME || "~";
|
|
183
|
+
const rulesDir = join(HOME, ".claude", "rules");
|
|
184
|
+
const ruleName = pattern.pattern.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40);
|
|
185
|
+
const rulePath = join(rulesDir, `${ruleName}.md`);
|
|
186
|
+
writeFileSync(rulePath, `# ${pattern.pattern}
|
|
187
|
+
|
|
188
|
+
${pattern.suggestedRule}
|
|
189
|
+
|
|
190
|
+
<!-- Auto-generated by REX self-improvement on ${(/* @__PURE__ */ new Date()).toISOString()} -->
|
|
191
|
+
`);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
export {
|
|
195
|
+
promoteRule,
|
|
196
|
+
selfReview
|
|
197
|
+
};
|