rex-claude 2.2.0 → 3.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/chunk-7AGI43F5.js +42 -0
- package/dist/context-FN5O5YBI.js +114 -0
- package/dist/gateway-EOVQXRON.js +198 -0
- package/dist/guards/completion-guard.sh +15 -2
- package/dist/guards/dangerous-cmd-guard.sh +2 -2
- package/dist/guards/error-pattern-guard.sh +45 -0
- package/dist/guards/notify-telegram.sh +34 -0
- package/dist/guards/test-protect-guard.sh +2 -2
- package/dist/guards/ui-checklist-guard.sh +1 -1
- package/dist/index.js +52 -13
- package/dist/{init-NXU37FCV.js → init-W3XGDQ6D.js} +159 -1
- package/dist/llm-YRORUH7E.js +9 -0
- package/dist/optimize-UKMAGQQE.js +148 -0
- package/dist/setup-AO3MW46W.js +252 -0
- package/dist/skills/build-validate/SKILL.md +23 -0
- package/dist/skills/code-review/SKILL.md +25 -0
- package/dist/skills/context-loader/SKILL.md +25 -0
- package/dist/skills/debug-assist/SKILL.md +26 -0
- package/dist/skills/deploy-checklist/SKILL.md +61 -0
- package/dist/skills/dstudio-design-system/SKILL.md +120 -0
- package/dist/skills/figma-workflow/SKILL.md +23 -0
- package/dist/skills/fix-issue/SKILL.md +43 -0
- package/dist/skills/new-rule/SKILL.md +19 -0
- package/dist/skills/notify/SKILL.md +26 -0
- package/dist/skills/one-shot/SKILL.md +18 -0
- package/dist/skills/pr-review-loop/SKILL.md +48 -0
- package/dist/skills/project-init/SKILL.md +45 -0
- package/dist/skills/research/SKILL.md +17 -0
- package/dist/skills/rex-boot/SKILL.md +64 -0
- package/dist/skills/spec-interview/SKILL.md +20 -0
- package/dist/skills/token-guard/SKILL.md +26 -0
- package/package.json +4 -4
- package/dist/optimize-NE47FMOP.js +0 -111
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/llm.ts
|
|
4
|
+
var OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434";
|
|
5
|
+
var PREFERRED_MODELS = ["qwen3.5:9b", "qwen3.5:4b", "qwen2.5:1.5b", "llama3.2", "mistral"];
|
|
6
|
+
async function detectModel() {
|
|
7
|
+
if (process.env.REX_LLM_MODEL) return process.env.REX_LLM_MODEL;
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(`${OLLAMA_URL}/api/tags`);
|
|
10
|
+
const data = await res.json();
|
|
11
|
+
const available = data.models.map((m) => m.name);
|
|
12
|
+
for (const pref of PREFERRED_MODELS) {
|
|
13
|
+
const base = pref.split(":")[0];
|
|
14
|
+
const match = available.find((a) => a.includes(base));
|
|
15
|
+
if (match) return match;
|
|
16
|
+
}
|
|
17
|
+
return available.find((a) => !a.includes("embed")) || available[0];
|
|
18
|
+
} catch {
|
|
19
|
+
return "qwen3.5:4b";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function llm(prompt, system, model) {
|
|
23
|
+
const useModel = model || await detectModel();
|
|
24
|
+
const res = await fetch(`${OLLAMA_URL}/api/generate`, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: { "Content-Type": "application/json" },
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
model: useModel,
|
|
29
|
+
prompt,
|
|
30
|
+
system,
|
|
31
|
+
stream: false
|
|
32
|
+
})
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) throw new Error(`Ollama generate failed: ${res.status}`);
|
|
35
|
+
const data = await res.json();
|
|
36
|
+
return data.response;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
detectModel,
|
|
41
|
+
llm
|
|
42
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/context.ts
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
|
+
import { join, basename } from "path";
|
|
6
|
+
var COLORS = {
|
|
7
|
+
reset: "\x1B[0m",
|
|
8
|
+
green: "\x1B[32m",
|
|
9
|
+
yellow: "\x1B[33m",
|
|
10
|
+
bold: "\x1B[1m",
|
|
11
|
+
dim: "\x1B[2m",
|
|
12
|
+
cyan: "\x1B[36m"
|
|
13
|
+
};
|
|
14
|
+
function detectStack(projectPath) {
|
|
15
|
+
const pkgPath = join(projectPath, "package.json");
|
|
16
|
+
const pkg = existsSync(pkgPath) ? JSON.parse(readFileSync(pkgPath, "utf-8")) : {};
|
|
17
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
18
|
+
const stacks = [
|
|
19
|
+
{
|
|
20
|
+
name: "Next.js",
|
|
21
|
+
detected: !!deps?.next,
|
|
22
|
+
mcpServers: ["next-devtools"],
|
|
23
|
+
skills: ["build-validate", "one-shot"]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "React",
|
|
27
|
+
detected: !!deps?.react,
|
|
28
|
+
mcpServers: [],
|
|
29
|
+
skills: ["figma-workflow", "dstudio-design-system"]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "Tailwind",
|
|
33
|
+
detected: !!deps?.tailwindcss || !!deps?.["@tailwindcss/vite"],
|
|
34
|
+
mcpServers: [],
|
|
35
|
+
skills: ["figma-workflow"]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "Playwright",
|
|
39
|
+
detected: !!deps?.["@playwright/test"] || !!deps?.playwright,
|
|
40
|
+
mcpServers: ["playwright"],
|
|
41
|
+
skills: []
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "Cloudflare Workers",
|
|
45
|
+
detected: !!deps?.wrangler || existsSync(join(projectPath, "wrangler.toml")),
|
|
46
|
+
mcpServers: [],
|
|
47
|
+
skills: ["deploy-checklist"]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "Flutter",
|
|
51
|
+
detected: existsSync(join(projectPath, "pubspec.yaml")),
|
|
52
|
+
mcpServers: [],
|
|
53
|
+
skills: []
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "CakePHP",
|
|
57
|
+
detected: existsSync(join(projectPath, "composer.json")) && readFileSync(join(projectPath, "composer.json"), "utf-8").includes("cakephp"),
|
|
58
|
+
mcpServers: [],
|
|
59
|
+
skills: []
|
|
60
|
+
}
|
|
61
|
+
];
|
|
62
|
+
return stacks;
|
|
63
|
+
}
|
|
64
|
+
async function context(targetPath) {
|
|
65
|
+
const line = "\u2550".repeat(45);
|
|
66
|
+
console.log(`
|
|
67
|
+
${line}`);
|
|
68
|
+
console.log(`${COLORS.bold} REX CONTEXT${COLORS.reset}`);
|
|
69
|
+
console.log(`${line}
|
|
70
|
+
`);
|
|
71
|
+
const absPath = join(process.cwd(), targetPath === "." ? "" : targetPath);
|
|
72
|
+
const projectName = basename(absPath);
|
|
73
|
+
console.log(` ${COLORS.cyan}Project:${COLORS.reset} ${projectName}`);
|
|
74
|
+
console.log(` ${COLORS.cyan}Path:${COLORS.reset} ${absPath}
|
|
75
|
+
`);
|
|
76
|
+
const stacks = detectStack(absPath);
|
|
77
|
+
const detected = stacks.filter((s) => s.detected);
|
|
78
|
+
if (detected.length === 0) {
|
|
79
|
+
console.log(` ${COLORS.yellow}No known stack detected.${COLORS.reset}`);
|
|
80
|
+
console.log();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
console.log(` ${COLORS.bold}Detected stack:${COLORS.reset}`);
|
|
84
|
+
for (const s of detected) {
|
|
85
|
+
console.log(` ${COLORS.green}\u2713${COLORS.reset} ${s.name}`);
|
|
86
|
+
}
|
|
87
|
+
const recommendedMcp = [...new Set(detected.flatMap((s) => s.mcpServers))];
|
|
88
|
+
const recommendedSkills = [...new Set(detected.flatMap((s) => s.skills))];
|
|
89
|
+
if (recommendedMcp.length > 0) {
|
|
90
|
+
console.log(`
|
|
91
|
+
${COLORS.bold}Recommended MCP servers:${COLORS.reset}`);
|
|
92
|
+
for (const mcp of recommendedMcp) {
|
|
93
|
+
console.log(` ${COLORS.cyan}\u2192${COLORS.reset} ${mcp}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (recommendedSkills.length > 0) {
|
|
97
|
+
console.log(`
|
|
98
|
+
${COLORS.bold}Recommended skills:${COLORS.reset}`);
|
|
99
|
+
for (const skill of recommendedSkills) {
|
|
100
|
+
console.log(` ${COLORS.cyan}\u2192${COLORS.reset} ${skill}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const hasClaudeMd = existsSync(join(absPath, "CLAUDE.md"));
|
|
104
|
+
if (!hasClaudeMd) {
|
|
105
|
+
console.log(`
|
|
106
|
+
${COLORS.yellow}!${COLORS.reset} No project CLAUDE.md found \u2014 consider running ${COLORS.cyan}/project-init${COLORS.reset}`);
|
|
107
|
+
}
|
|
108
|
+
console.log(`
|
|
109
|
+
${COLORS.dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${COLORS.reset}
|
|
110
|
+
`);
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
context
|
|
114
|
+
};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/gateway.ts
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
var COLORS = {
|
|
9
|
+
reset: "\x1B[0m",
|
|
10
|
+
green: "\x1B[32m",
|
|
11
|
+
yellow: "\x1B[33m",
|
|
12
|
+
red: "\x1B[31m",
|
|
13
|
+
dim: "\x1B[2m",
|
|
14
|
+
bold: "\x1B[1m",
|
|
15
|
+
cyan: "\x1B[36m"
|
|
16
|
+
};
|
|
17
|
+
function getCredentials() {
|
|
18
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
19
|
+
try {
|
|
20
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
21
|
+
const token2 = settings.env?.REX_TELEGRAM_BOT_TOKEN;
|
|
22
|
+
const chatId2 = settings.env?.REX_TELEGRAM_CHAT_ID;
|
|
23
|
+
if (token2 && chatId2) return { token: token2, chatId: chatId2 };
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
const token = process.env.REX_TELEGRAM_BOT_TOKEN;
|
|
27
|
+
const chatId = process.env.REX_TELEGRAM_CHAT_ID;
|
|
28
|
+
if (token && chatId) return { token, chatId };
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
async function sendMessage(token, chatId, text, parseMode = "Markdown") {
|
|
32
|
+
try {
|
|
33
|
+
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/json" },
|
|
36
|
+
body: JSON.stringify({ chat_id: chatId, text, parse_mode: parseMode })
|
|
37
|
+
});
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function runCommand(cmd) {
|
|
42
|
+
try {
|
|
43
|
+
return execSync(cmd, { timeout: 3e4, encoding: "utf-8" }).trim();
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return e.stderr?.trim() || e.message || "Command failed";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function handleCommand(text) {
|
|
49
|
+
const cmd = text.trim().toLowerCase();
|
|
50
|
+
if (cmd === "/status" || cmd === "/s") {
|
|
51
|
+
const out = runCommand("rex status");
|
|
52
|
+
return out || "REX status unavailable";
|
|
53
|
+
}
|
|
54
|
+
if (cmd === "/doctor" || cmd === "/d") {
|
|
55
|
+
const out = runCommand("rex doctor");
|
|
56
|
+
return out.replace(/\x1b\[[0-9;]*m/g, "").slice(0, 4e3);
|
|
57
|
+
}
|
|
58
|
+
if (cmd === "/ingest" || cmd === "/i") {
|
|
59
|
+
const out = runCommand("rex ingest");
|
|
60
|
+
return `*Ingest*
|
|
61
|
+
\`\`\`
|
|
62
|
+
${out.slice(0, 3e3)}
|
|
63
|
+
\`\`\``;
|
|
64
|
+
}
|
|
65
|
+
if (cmd.startsWith("/search ") || cmd.startsWith("/q ")) {
|
|
66
|
+
const query = text.replace(/^\/(search|q)\s+/i, "");
|
|
67
|
+
if (!query) return "Usage: /search <query>";
|
|
68
|
+
const out = runCommand(`rex search ${query}`);
|
|
69
|
+
return out ? `*Search:* ${query}
|
|
70
|
+
\`\`\`
|
|
71
|
+
${out.slice(0, 3e3)}
|
|
72
|
+
\`\`\`` : "No results";
|
|
73
|
+
}
|
|
74
|
+
if (cmd.startsWith("/llm ") || cmd.startsWith("/ask ")) {
|
|
75
|
+
const prompt = text.replace(/^\/(llm|ask)\s+/i, "");
|
|
76
|
+
if (!prompt) return "Usage: /llm <prompt>";
|
|
77
|
+
const out = runCommand(`rex llm "${prompt.replace(/"/g, '\\"')}"`);
|
|
78
|
+
return out.slice(0, 4e3);
|
|
79
|
+
}
|
|
80
|
+
if (cmd === "/optimize" || cmd === "/o") {
|
|
81
|
+
const out = runCommand("rex optimize");
|
|
82
|
+
return `*Optimize*
|
|
83
|
+
\`\`\`
|
|
84
|
+
${out.replace(/\x1b\[[0-9;]*m/g, "").slice(0, 3e3)}
|
|
85
|
+
\`\`\``;
|
|
86
|
+
}
|
|
87
|
+
if (cmd === "/git" || cmd === "/g") {
|
|
88
|
+
const branch = runCommand('git branch --show-current 2>/dev/null || echo "n/a"');
|
|
89
|
+
const status = runCommand("git status --short 2>/dev/null | head -15");
|
|
90
|
+
const lastCommit = runCommand('git log -1 --format="%s" 2>/dev/null || echo "n/a"');
|
|
91
|
+
return `*Git*
|
|
92
|
+
Branch: \`${branch}\`
|
|
93
|
+
Last: ${lastCommit}
|
|
94
|
+
\`\`\`
|
|
95
|
+
${status || "Clean"}
|
|
96
|
+
\`\`\``;
|
|
97
|
+
}
|
|
98
|
+
if (cmd.startsWith("/sh ") || cmd.startsWith("/run ")) {
|
|
99
|
+
const shellCmd = text.replace(/^\/(sh|run)\s+/i, "");
|
|
100
|
+
const blocked = ["rm -rf", "rm -r /", "mkfs", "dd if=", ":(){", "chmod -R 777", "git push --force main", "git push --force master"];
|
|
101
|
+
if (blocked.some((b) => shellCmd.toLowerCase().includes(b))) {
|
|
102
|
+
return "Blocked: dangerous command";
|
|
103
|
+
}
|
|
104
|
+
const out = runCommand(shellCmd);
|
|
105
|
+
return `\`$ ${shellCmd}\`
|
|
106
|
+
\`\`\`
|
|
107
|
+
${out.slice(0, 3500)}
|
|
108
|
+
\`\`\``;
|
|
109
|
+
}
|
|
110
|
+
if (cmd === "/help" || cmd === "/start" || cmd === "/h") {
|
|
111
|
+
return `*REX Gateway*
|
|
112
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
113
|
+
/status \u2014 Quick health check
|
|
114
|
+
/doctor \u2014 Full diagnostics
|
|
115
|
+
/ingest \u2014 Sync sessions to memory
|
|
116
|
+
/search <q> \u2014 Semantic search
|
|
117
|
+
/llm <prompt> \u2014 Ask local LLM
|
|
118
|
+
/optimize \u2014 Analyze CLAUDE.md
|
|
119
|
+
/git \u2014 Git status
|
|
120
|
+
/sh <cmd> \u2014 Run shell command
|
|
121
|
+
/help \u2014 This message`;
|
|
122
|
+
}
|
|
123
|
+
if (text.length > 3) {
|
|
124
|
+
try {
|
|
125
|
+
const check = await fetch("http://localhost:11434/api/tags");
|
|
126
|
+
if (check.ok) {
|
|
127
|
+
const out = runCommand(`rex llm "${text.replace(/"/g, '\\"')}"`);
|
|
128
|
+
if (out && !out.includes("rex-claude") && !out.includes("Commands:")) {
|
|
129
|
+
return out.slice(0, 4e3);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
return `Unknown command: "${text.slice(0, 50)}"
|
|
135
|
+
Send /help for available commands.`;
|
|
136
|
+
}
|
|
137
|
+
return "Send /help for available commands.";
|
|
138
|
+
}
|
|
139
|
+
async function gateway() {
|
|
140
|
+
const creds = getCredentials();
|
|
141
|
+
if (!creds) {
|
|
142
|
+
console.error(`${COLORS.red}No Telegram credentials found.${COLORS.reset}`);
|
|
143
|
+
console.error(`Run ${COLORS.cyan}rex setup${COLORS.reset} to configure Telegram gateway.`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
const { token, chatId } = creds;
|
|
147
|
+
console.log(`${COLORS.bold}REX Gateway${COLORS.reset} \u2014 Telegram long-polling active`);
|
|
148
|
+
console.log(`${COLORS.dim}Bot token: ...${token.slice(-8)}${COLORS.reset}`);
|
|
149
|
+
console.log(`${COLORS.dim}Chat ID: ${chatId}${COLORS.reset}`);
|
|
150
|
+
console.log(`${COLORS.dim}Press Ctrl+C to stop${COLORS.reset}
|
|
151
|
+
`);
|
|
152
|
+
let offset = 0;
|
|
153
|
+
try {
|
|
154
|
+
const flush = await fetch(`https://api.telegram.org/bot${token}/getUpdates?offset=-1`);
|
|
155
|
+
const flushData = await flush.json();
|
|
156
|
+
if (flushData.result?.length) {
|
|
157
|
+
offset = flushData.result[flushData.result.length - 1].update_id + 1;
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
await sendMessage(token, chatId, "\u{1F7E2} *REX Gateway* started\nSend /help for commands.");
|
|
162
|
+
const POLL_TIMEOUT = 30;
|
|
163
|
+
process.on("SIGINT", async () => {
|
|
164
|
+
console.log(`
|
|
165
|
+
${COLORS.dim}Shutting down...${COLORS.reset}`);
|
|
166
|
+
await sendMessage(token, chatId, "\u{1F534} *REX Gateway* stopped");
|
|
167
|
+
process.exit(0);
|
|
168
|
+
});
|
|
169
|
+
while (true) {
|
|
170
|
+
try {
|
|
171
|
+
const res = await fetch(
|
|
172
|
+
`https://api.telegram.org/bot${token}/getUpdates?offset=${offset}&timeout=${POLL_TIMEOUT}&allowed_updates=["message"]`
|
|
173
|
+
);
|
|
174
|
+
const data = await res.json();
|
|
175
|
+
if (!data.ok || !data.result?.length) continue;
|
|
176
|
+
for (const update of data.result) {
|
|
177
|
+
offset = update.update_id + 1;
|
|
178
|
+
const msg = update.message;
|
|
179
|
+
if (!msg?.text) continue;
|
|
180
|
+
if (String(msg.chat.id) !== chatId) {
|
|
181
|
+
console.log(`${COLORS.yellow}Ignored message from chat ${msg.chat.id}${COLORS.reset}`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const from = msg.from?.username ?? "?";
|
|
185
|
+
console.log(`${COLORS.cyan}@${from}${COLORS.reset}: ${msg.text}`);
|
|
186
|
+
const reply = await handleCommand(msg.text);
|
|
187
|
+
await sendMessage(token, chatId, reply);
|
|
188
|
+
console.log(`${COLORS.green}\u2192${COLORS.reset} ${reply.slice(0, 80)}...`);
|
|
189
|
+
}
|
|
190
|
+
} catch (err) {
|
|
191
|
+
console.error(`${COLORS.red}Poll error:${COLORS.reset} ${err.message}`);
|
|
192
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
export {
|
|
197
|
+
gateway
|
|
198
|
+
};
|
|
@@ -5,14 +5,27 @@
|
|
|
5
5
|
|
|
6
6
|
# Check for TODO/placeholder/stub patterns in recently modified files
|
|
7
7
|
MODIFIED_FILES=$(git diff --name-only HEAD 2>/dev/null)
|
|
8
|
-
|
|
8
|
+
STAGED_FILES=$(git diff --cached --name-only 2>/dev/null)
|
|
9
|
+
UNTRACKED_FILES=$(git ls-files --others --exclude-standard 2>/dev/null)
|
|
10
|
+
|
|
11
|
+
ALL_FILES=$(echo -e "${MODIFIED_FILES}\n${STAGED_FILES}\n${UNTRACKED_FILES}" | sort -u | grep -v '^$')
|
|
12
|
+
|
|
13
|
+
if [ -z "$ALL_FILES" ]; then
|
|
9
14
|
exit 0
|
|
10
15
|
fi
|
|
11
16
|
|
|
12
17
|
ISSUES=""
|
|
13
18
|
|
|
19
|
+
# Warn about untracked files that should probably be committed
|
|
20
|
+
if [ -n "$UNTRACKED_FILES" ]; then
|
|
21
|
+
UNTRACKED_CODE=$(echo "$UNTRACKED_FILES" | grep -E '\.(ts|tsx|js|jsx|py|rs|go|sh|css|html|vue|svelte)$' | head -5)
|
|
22
|
+
if [ -n "$UNTRACKED_CODE" ]; then
|
|
23
|
+
ISSUES="${ISSUES}\n⚠ Untracked code files (forgot to git add?):\n${UNTRACKED_CODE}\n"
|
|
24
|
+
fi
|
|
25
|
+
fi
|
|
26
|
+
|
|
14
27
|
# Check for incomplete implementation markers
|
|
15
|
-
for file in $
|
|
28
|
+
for file in $ALL_FILES; do
|
|
16
29
|
if [ ! -f "$file" ]; then continue; fi
|
|
17
30
|
|
|
18
31
|
# Skip non-code files
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
# Hook: PreToolUse (matcher: Bash)
|
|
4
4
|
# Prevents destructive commands from running without confirmation
|
|
5
5
|
|
|
6
|
-
# $
|
|
7
|
-
CMD="$TOOL_INPUT"
|
|
6
|
+
# $CLAUDE_TOOL_INPUT contains the command about to be executed
|
|
7
|
+
CMD="${CLAUDE_TOOL_INPUT:-$TOOL_INPUT}"
|
|
8
8
|
|
|
9
9
|
# Patterns that should ALWAYS be blocked or warned
|
|
10
10
|
BLOCKED_PATTERNS=(
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# REX Guard: Error Pattern Detector
|
|
3
|
+
# Hook: PostToolUse (matcher: Bash)
|
|
4
|
+
# Detects recurring error patterns in command output and suggests creating rules
|
|
5
|
+
|
|
6
|
+
TRACK_FILE="${HOME}/.claude/rex-error-patterns.json"
|
|
7
|
+
TOOL_OUTPUT="${CLAUDE_TOOL_OUTPUT:-$TOOL_OUTPUT}"
|
|
8
|
+
|
|
9
|
+
# Only process if there's output that looks like an error
|
|
10
|
+
if [ -z "$TOOL_OUTPUT" ]; then
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Extract error-like lines from output
|
|
15
|
+
ERRORS=$(echo "$TOOL_OUTPUT" | grep -iE '(error|ERR!|fatal|failed|ENOENT|EACCES|TypeError|SyntaxError|ReferenceError|Cannot find|Module not found|command not found)' | head -3)
|
|
16
|
+
|
|
17
|
+
if [ -z "$ERRORS" ]; then
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Normalize error to a pattern (strip file paths, line numbers, specific values)
|
|
22
|
+
PATTERN=$(echo "$ERRORS" | head -1 | sed 's/[0-9]\+/N/g' | sed 's|/[^ ]*||g' | sed 's/"[^"]*"/"..."/g' | cut -c1-100)
|
|
23
|
+
|
|
24
|
+
if [ -z "$PATTERN" ]; then
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Initialize tracking file if needed
|
|
29
|
+
if [ ! -f "$TRACK_FILE" ]; then
|
|
30
|
+
echo '{}' > "$TRACK_FILE"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Count occurrences of this pattern (using simple grep since jq may not be available)
|
|
34
|
+
COUNT=$(grep -c "$PATTERN" "$TRACK_FILE" 2>/dev/null || echo "0")
|
|
35
|
+
NEWCOUNT=$((COUNT + 1))
|
|
36
|
+
|
|
37
|
+
# Append pattern to tracking file
|
|
38
|
+
echo "{\"pattern\":\"$PATTERN\",\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" >> "$TRACK_FILE"
|
|
39
|
+
|
|
40
|
+
# After 3 occurrences, suggest creating a rule
|
|
41
|
+
if [ "$NEWCOUNT" -ge 3 ]; then
|
|
42
|
+
echo "REX Guard: Recurring error detected ($NEWCOUNT times): $PATTERN"
|
|
43
|
+
echo "Consider using /new-rule to create a permanent rule for this pattern."
|
|
44
|
+
echo "Call rex_learn with category 'lesson' to memorize: \"Recurring error: $PATTERN\""
|
|
45
|
+
fi
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# REX Guard: Telegram Notification on task completion
|
|
3
|
+
# Hook: Stop — fires when Claude finishes working
|
|
4
|
+
# Sends a short summary to Telegram for traceability
|
|
5
|
+
|
|
6
|
+
TELEGRAM_BOT_TOKEN="${REX_TELEGRAM_BOT_TOKEN:-}"
|
|
7
|
+
TELEGRAM_CHAT_ID="${REX_TELEGRAM_CHAT_ID:-}"
|
|
8
|
+
|
|
9
|
+
# Skip if not configured
|
|
10
|
+
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Build summary from git state
|
|
15
|
+
BRANCH=$(git branch --show-current 2>/dev/null || echo "n/a")
|
|
16
|
+
PROJECT=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
|
|
17
|
+
MODIFIED=$(git diff --name-only HEAD 2>/dev/null | wc -l | tr -d ' ')
|
|
18
|
+
STAGED=$(git diff --cached --name-only 2>/dev/null | wc -l | tr -d ' ')
|
|
19
|
+
LAST_COMMIT=$(git log -1 --format="%s" 2>/dev/null || echo "n/a")
|
|
20
|
+
TIMESTAMP=$(date +"%H:%M")
|
|
21
|
+
|
|
22
|
+
# Compose message
|
|
23
|
+
MSG="✅ *REX — Task Done*
|
|
24
|
+
━━━━━━━━━━━━━━
|
|
25
|
+
📁 \`${PROJECT}\` → \`${BRANCH}\`
|
|
26
|
+
📝 ${MODIFIED} modified, ${STAGED} staged
|
|
27
|
+
💬 ${LAST_COMMIT}
|
|
28
|
+
🕐 ${TIMESTAMP}"
|
|
29
|
+
|
|
30
|
+
# Send (fire-and-forget, no blocking)
|
|
31
|
+
curl -sf -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
32
|
+
-d "chat_id=${TELEGRAM_CHAT_ID}" \
|
|
33
|
+
-d "parse_mode=Markdown" \
|
|
34
|
+
-d "text=${MSG}" > /dev/null 2>&1 &
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
# Hook: PostToolUse (matcher: Edit|Write)
|
|
4
4
|
# Prevents the #1 LLM anti-pattern: modifying tests to match broken code
|
|
5
5
|
|
|
6
|
-
# $
|
|
7
|
-
INPUT="$TOOL_INPUT"
|
|
6
|
+
# $CLAUDE_TOOL_INPUT contains the file path and changes
|
|
7
|
+
INPUT="${CLAUDE_TOOL_INPUT:-$TOOL_INPUT}"
|
|
8
8
|
|
|
9
9
|
# Check if a test file was modified
|
|
10
10
|
if echo "$INPUT" | grep -qE '\.(test|spec)\.(ts|tsx|js|jsx|py)'; then
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Hook: PostToolUse (matcher: Edit|Write)
|
|
4
4
|
# Prevents the "missing states" anti-pattern: LLMs generate happy path only
|
|
5
5
|
|
|
6
|
-
INPUT="$TOOL_INPUT"
|
|
6
|
+
INPUT="${CLAUDE_TOOL_INPUT:-$TOOL_INPUT}"
|
|
7
7
|
|
|
8
8
|
# Only check UI component files
|
|
9
9
|
if ! echo "$INPUT" | grep -qE '\.(tsx|jsx|vue|svelte)'; then
|
package/dist/index.js
CHANGED
|
@@ -191,7 +191,9 @@ var EXPECTED_GUARDS = [
|
|
|
191
191
|
"test-protect-guard.sh",
|
|
192
192
|
"session-summary.sh",
|
|
193
193
|
"ui-checklist-guard.sh",
|
|
194
|
-
"scope-guard.sh"
|
|
194
|
+
"scope-guard.sh",
|
|
195
|
+
"error-pattern-guard.sh",
|
|
196
|
+
"notify-telegram.sh"
|
|
195
197
|
];
|
|
196
198
|
async function checkGuards(claudeDir) {
|
|
197
199
|
const results = [];
|
|
@@ -369,7 +371,7 @@ async function main() {
|
|
|
369
371
|
break;
|
|
370
372
|
}
|
|
371
373
|
case "init": {
|
|
372
|
-
const { init } = await import("./init-
|
|
374
|
+
const { init } = await import("./init-W3XGDQ6D.js");
|
|
373
375
|
await init();
|
|
374
376
|
break;
|
|
375
377
|
}
|
|
@@ -409,23 +411,51 @@ async function main() {
|
|
|
409
411
|
break;
|
|
410
412
|
}
|
|
411
413
|
case "optimize": {
|
|
412
|
-
const { optimize } = await import("./optimize-
|
|
413
|
-
|
|
414
|
+
const { optimize } = await import("./optimize-UKMAGQQE.js");
|
|
415
|
+
const applyFlag = process.argv.includes("--apply");
|
|
416
|
+
await optimize(applyFlag);
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
case "setup": {
|
|
420
|
+
const { setup } = await import("./setup-AO3MW46W.js");
|
|
421
|
+
await setup();
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
case "llm": {
|
|
425
|
+
const prompt = process.argv.slice(3).join(" ");
|
|
426
|
+
if (!prompt) {
|
|
427
|
+
console.error("Usage: rex llm <prompt>");
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
const { llm } = await import("./llm-YRORUH7E.js");
|
|
431
|
+
const result = await llm(prompt);
|
|
432
|
+
console.log(result);
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
case "context": {
|
|
436
|
+
const targetPath = process.argv[3] || process.cwd();
|
|
437
|
+
const { context } = await import("./context-FN5O5YBI.js");
|
|
438
|
+
await context(targetPath);
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
case "gateway": {
|
|
442
|
+
const { gateway } = await import("./gateway-EOVQXRON.js");
|
|
443
|
+
await gateway();
|
|
414
444
|
break;
|
|
415
445
|
}
|
|
416
446
|
case "startup": {
|
|
417
|
-
const { installStartup } = await import("./init-
|
|
447
|
+
const { installStartup } = await import("./init-W3XGDQ6D.js");
|
|
418
448
|
installStartup();
|
|
419
449
|
break;
|
|
420
450
|
}
|
|
421
451
|
case "startup-remove": {
|
|
422
|
-
const { uninstallStartup } = await import("./init-
|
|
452
|
+
const { uninstallStartup } = await import("./init-W3XGDQ6D.js");
|
|
423
453
|
uninstallStartup();
|
|
424
454
|
break;
|
|
425
455
|
}
|
|
426
456
|
case "--version":
|
|
427
457
|
case "-v":
|
|
428
|
-
console.log("rex-claude
|
|
458
|
+
console.log("rex-claude v3.0.0");
|
|
429
459
|
break;
|
|
430
460
|
case "help":
|
|
431
461
|
default:
|
|
@@ -440,15 +470,24 @@ ${COLORS.bold}Commands:${COLORS.reset}
|
|
|
440
470
|
rex startup-remove Remove LaunchAgent
|
|
441
471
|
|
|
442
472
|
${COLORS.bold}Memory (requires Ollama):${COLORS.reset}
|
|
443
|
-
rex ingest
|
|
444
|
-
rex search
|
|
445
|
-
rex optimize
|
|
473
|
+
rex ingest Sync session history to vector DB
|
|
474
|
+
rex search <query> Semantic search across past sessions
|
|
475
|
+
rex optimize Analyze CLAUDE.md with local LLM
|
|
476
|
+
rex optimize --apply Apply optimizations (with backup)
|
|
477
|
+
|
|
478
|
+
${COLORS.bold}LLM & Context:${COLORS.reset}
|
|
479
|
+
rex setup Install Ollama + models + Telegram gateway
|
|
480
|
+
rex llm <prompt> Query local LLM directly
|
|
481
|
+
rex context [path] Analyze project, recommend MCP/skills
|
|
482
|
+
|
|
483
|
+
${COLORS.bold}Telegram Gateway:${COLORS.reset}
|
|
484
|
+
rex gateway Start Telegram bot (long-polling, interactive)
|
|
446
485
|
|
|
447
486
|
${COLORS.bold}Info:${COLORS.reset}
|
|
448
|
-
rex help
|
|
449
|
-
rex --version
|
|
487
|
+
rex help Show this help
|
|
488
|
+
rex --version Show version
|
|
450
489
|
|
|
451
|
-
${COLORS.dim}After install: rex init \u2014 everything else is automatic.${COLORS.reset}
|
|
490
|
+
${COLORS.dim}After install: rex init && rex setup \u2014 everything else is automatic.${COLORS.reset}
|
|
452
491
|
`);
|
|
453
492
|
}
|
|
454
493
|
}
|