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.
Files changed (33) hide show
  1. package/dist/chunk-7AGI43F5.js +42 -0
  2. package/dist/context-FN5O5YBI.js +114 -0
  3. package/dist/gateway-EOVQXRON.js +198 -0
  4. package/dist/guards/completion-guard.sh +15 -2
  5. package/dist/guards/dangerous-cmd-guard.sh +2 -2
  6. package/dist/guards/error-pattern-guard.sh +45 -0
  7. package/dist/guards/notify-telegram.sh +34 -0
  8. package/dist/guards/test-protect-guard.sh +2 -2
  9. package/dist/guards/ui-checklist-guard.sh +1 -1
  10. package/dist/index.js +52 -13
  11. package/dist/{init-NXU37FCV.js → init-W3XGDQ6D.js} +159 -1
  12. package/dist/llm-YRORUH7E.js +9 -0
  13. package/dist/optimize-UKMAGQQE.js +148 -0
  14. package/dist/setup-AO3MW46W.js +252 -0
  15. package/dist/skills/build-validate/SKILL.md +23 -0
  16. package/dist/skills/code-review/SKILL.md +25 -0
  17. package/dist/skills/context-loader/SKILL.md +25 -0
  18. package/dist/skills/debug-assist/SKILL.md +26 -0
  19. package/dist/skills/deploy-checklist/SKILL.md +61 -0
  20. package/dist/skills/dstudio-design-system/SKILL.md +120 -0
  21. package/dist/skills/figma-workflow/SKILL.md +23 -0
  22. package/dist/skills/fix-issue/SKILL.md +43 -0
  23. package/dist/skills/new-rule/SKILL.md +19 -0
  24. package/dist/skills/notify/SKILL.md +26 -0
  25. package/dist/skills/one-shot/SKILL.md +18 -0
  26. package/dist/skills/pr-review-loop/SKILL.md +48 -0
  27. package/dist/skills/project-init/SKILL.md +45 -0
  28. package/dist/skills/research/SKILL.md +17 -0
  29. package/dist/skills/rex-boot/SKILL.md +64 -0
  30. package/dist/skills/spec-interview/SKILL.md +20 -0
  31. package/dist/skills/token-guard/SKILL.md +26 -0
  32. package/package.json +4 -4
  33. 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
- if [ -z "$MODIFIED_FILES" ]; then
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 $MODIFIED_FILES; do
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
- # $TOOL_INPUT contains the command about to be executed
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
- # $TOOL_INPUT contains the file path and changes
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-NXU37FCV.js");
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-NE47FMOP.js");
413
- await optimize();
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-NXU37FCV.js");
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-NXU37FCV.js");
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 v2.2.0");
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 Sync session history to vector DB
444
- rex search Semantic search across past sessions
445
- rex optimize Analyze CLAUDE.md with local LLM
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 Show this help
449
- rex --version Show 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
  }