codemaxxing 0.1.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.
@@ -0,0 +1,247 @@
1
+ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "fs";
2
+ import { join, relative } from "path";
3
+ import type { ChatCompletionTool } from "openai/resources/chat/completions";
4
+
5
+ /**
6
+ * Tool definitions for the OpenAI function calling API
7
+ */
8
+ export const FILE_TOOLS: ChatCompletionTool[] = [
9
+ {
10
+ type: "function",
11
+ function: {
12
+ name: "read_file",
13
+ description: "Read the contents of a file at the given path",
14
+ parameters: {
15
+ type: "object",
16
+ properties: {
17
+ path: {
18
+ type: "string",
19
+ description: "Path to the file to read (relative to project root)",
20
+ },
21
+ },
22
+ required: ["path"],
23
+ },
24
+ },
25
+ },
26
+ {
27
+ type: "function",
28
+ function: {
29
+ name: "write_file",
30
+ description:
31
+ "Write content to a file. Creates the file if it doesn't exist, overwrites if it does.",
32
+ parameters: {
33
+ type: "object",
34
+ properties: {
35
+ path: {
36
+ type: "string",
37
+ description: "Path to the file to write (relative to project root)",
38
+ },
39
+ content: {
40
+ type: "string",
41
+ description: "Content to write to the file",
42
+ },
43
+ },
44
+ required: ["path", "content"],
45
+ },
46
+ },
47
+ },
48
+ {
49
+ type: "function",
50
+ function: {
51
+ name: "list_files",
52
+ description:
53
+ "List files and directories in the given path. Returns file names and types.",
54
+ parameters: {
55
+ type: "object",
56
+ properties: {
57
+ path: {
58
+ type: "string",
59
+ description:
60
+ "Directory path to list (relative to project root, defaults to '.')",
61
+ },
62
+ recursive: {
63
+ type: "boolean",
64
+ description: "Whether to list files recursively (default: false)",
65
+ },
66
+ },
67
+ },
68
+ },
69
+ },
70
+ {
71
+ type: "function",
72
+ function: {
73
+ name: "search_files",
74
+ description: "Search for a text pattern across files in the project",
75
+ parameters: {
76
+ type: "object",
77
+ properties: {
78
+ pattern: {
79
+ type: "string",
80
+ description: "Text or regex pattern to search for",
81
+ },
82
+ path: {
83
+ type: "string",
84
+ description: "Directory to search in (defaults to project root)",
85
+ },
86
+ },
87
+ required: ["pattern"],
88
+ },
89
+ },
90
+ },
91
+ {
92
+ type: "function",
93
+ function: {
94
+ name: "run_command",
95
+ description:
96
+ "Execute a shell command and return the output. Use for running tests, builds, linters, etc.",
97
+ parameters: {
98
+ type: "object",
99
+ properties: {
100
+ command: {
101
+ type: "string",
102
+ description: "Shell command to execute",
103
+ },
104
+ },
105
+ required: ["command"],
106
+ },
107
+ },
108
+ },
109
+ ];
110
+
111
+ /**
112
+ * Execute a tool call and return the result
113
+ */
114
+ export async function executeTool(
115
+ name: string,
116
+ args: Record<string, unknown>,
117
+ cwd: string
118
+ ): Promise<string> {
119
+ switch (name) {
120
+ case "read_file": {
121
+ const filePath = join(cwd, args.path as string);
122
+ if (!existsSync(filePath)) return `Error: File not found: ${args.path}`;
123
+ try {
124
+ return readFileSync(filePath, "utf-8");
125
+ } catch (e) {
126
+ return `Error reading file: ${e}`;
127
+ }
128
+ }
129
+
130
+ case "write_file": {
131
+ const filePath = join(cwd, args.path as string);
132
+ try {
133
+ writeFileSync(filePath, args.content as string, "utf-8");
134
+ return `✅ Wrote ${(args.content as string).length} bytes to ${args.path}`;
135
+ } catch (e) {
136
+ return `Error writing file: ${e}`;
137
+ }
138
+ }
139
+
140
+ case "list_files": {
141
+ const dirPath = join(cwd, (args.path as string) || ".");
142
+ if (!existsSync(dirPath)) return `Error: Directory not found: ${args.path}`;
143
+ try {
144
+ const entries = listDir(dirPath, cwd, args.recursive as boolean);
145
+ return entries.join("\n");
146
+ } catch (e) {
147
+ return `Error listing files: ${e}`;
148
+ }
149
+ }
150
+
151
+ case "search_files": {
152
+ const searchPath = join(cwd, (args.path as string) || ".");
153
+ try {
154
+ return searchInFiles(searchPath, args.pattern as string, cwd);
155
+ } catch (e) {
156
+ return `Error searching: ${e}`;
157
+ }
158
+ }
159
+
160
+ case "run_command": {
161
+ try {
162
+ const { execSync } = await import("child_process");
163
+ const output = execSync(args.command as string, {
164
+ cwd,
165
+ encoding: "utf-8",
166
+ timeout: 30000,
167
+ maxBuffer: 1024 * 1024,
168
+ });
169
+ return output || "(no output)";
170
+ } catch (e: any) {
171
+ return `Command failed: ${e.stderr || e.message}`;
172
+ }
173
+ }
174
+
175
+ default:
176
+ return `Unknown tool: ${name}`;
177
+ }
178
+ }
179
+
180
+ function listDir(
181
+ dirPath: string,
182
+ cwd: string,
183
+ recursive: boolean = false,
184
+ depth: number = 0
185
+ ): string[] {
186
+ const entries: string[] = [];
187
+ const IGNORE = ["node_modules", ".git", "dist", ".next", "__pycache__"];
188
+
189
+ for (const entry of readdirSync(dirPath)) {
190
+ if (IGNORE.includes(entry)) continue;
191
+ const fullPath = join(dirPath, entry);
192
+ const rel = relative(cwd, fullPath);
193
+ const stat = statSync(fullPath);
194
+ const prefix = " ".repeat(depth);
195
+
196
+ if (stat.isDirectory()) {
197
+ entries.push(`${prefix}📁 ${rel}/`);
198
+ if (recursive && depth < 3) {
199
+ entries.push(...listDir(fullPath, cwd, true, depth + 1));
200
+ }
201
+ } else {
202
+ const size = stat.size > 1024 ? `${(stat.size / 1024).toFixed(1)}KB` : `${stat.size}B`;
203
+ entries.push(`${prefix}📄 ${rel} (${size})`);
204
+ }
205
+ }
206
+ return entries;
207
+ }
208
+
209
+ function searchInFiles(
210
+ dirPath: string,
211
+ pattern: string,
212
+ cwd: string
213
+ ): string {
214
+ const results: string[] = [];
215
+ const IGNORE = ["node_modules", ".git", "dist", ".next", "__pycache__"];
216
+ const regex = new RegExp(pattern, "gi");
217
+
218
+ function search(dir: string) {
219
+ for (const entry of readdirSync(dir)) {
220
+ if (IGNORE.includes(entry)) continue;
221
+ const fullPath = join(dir, entry);
222
+ const stat = statSync(fullPath);
223
+
224
+ if (stat.isDirectory()) {
225
+ search(fullPath);
226
+ } else if (stat.size < 100000) {
227
+ try {
228
+ const content = readFileSync(fullPath, "utf-8");
229
+ const lines = content.split("\n");
230
+ for (let i = 0; i < lines.length; i++) {
231
+ if (regex.test(lines[i])) {
232
+ results.push(`${relative(cwd, fullPath)}:${i + 1}: ${lines[i].trim()}`);
233
+ }
234
+ regex.lastIndex = 0;
235
+ }
236
+ } catch {
237
+ // skip binary files
238
+ }
239
+ }
240
+ }
241
+ }
242
+
243
+ search(dirPath);
244
+ return results.length > 0
245
+ ? results.slice(0, 50).join("\n")
246
+ : "No matches found.";
247
+ }
@@ -0,0 +1,146 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
2
+ import { join, extname } from "path";
3
+ import { buildRepoMap } from "./repomap.js";
4
+
5
+ /**
6
+ * Build a project context string by scanning the working directory
7
+ */
8
+ export async function buildProjectContext(cwd: string): Promise<string> {
9
+ const lines: string[] = [];
10
+ lines.push(`Project root: ${cwd}`);
11
+
12
+ // Check for common project files
13
+ const markers = [
14
+ "package.json",
15
+ "Cargo.toml",
16
+ "pyproject.toml",
17
+ "go.mod",
18
+ "Makefile",
19
+ "Dockerfile",
20
+ "README.md",
21
+ "CODEMAXXING.md",
22
+ ];
23
+
24
+ const found: string[] = [];
25
+ for (const m of markers) {
26
+ if (existsSync(join(cwd, m))) found.push(m);
27
+ }
28
+
29
+ if (found.length > 0) {
30
+ lines.push(`Project files: ${found.join(", ")}`);
31
+ }
32
+
33
+ // Read PIERRE.md if it exists (like QWEN.md — project context file)
34
+ const contextMd = join(cwd, "CODEMAXXING.md");
35
+ if (existsSync(contextMd)) {
36
+ const content = readFileSync(contextMd, "utf-8");
37
+ lines.push("\n--- CODEMAXXING.md (project context) ---");
38
+ lines.push(content.slice(0, 4000));
39
+ lines.push("--- end CODEMAXXING.md ---");
40
+ }
41
+
42
+ // Read package.json for project info
43
+ const pkgPath = join(cwd, "package.json");
44
+ if (existsSync(pkgPath)) {
45
+ try {
46
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
47
+ lines.push(`\nProject: ${pkg.name ?? "unknown"} v${pkg.version ?? "0.0.0"}`);
48
+ if (pkg.description) lines.push(`Description: ${pkg.description}`);
49
+ if (pkg.scripts) {
50
+ lines.push(`Available scripts: ${Object.keys(pkg.scripts).join(", ")}`);
51
+ }
52
+ } catch {
53
+ // ignore
54
+ }
55
+ }
56
+
57
+ // Quick file tree (top level only)
58
+ lines.push("\nProject structure:");
59
+ const IGNORE = ["node_modules", ".git", "dist", ".next", "__pycache__", ".DS_Store"];
60
+ try {
61
+ const entries = readdirSync(cwd)
62
+ .filter((e) => !IGNORE.includes(e))
63
+ .slice(0, 30);
64
+
65
+ for (const entry of entries) {
66
+ const fullPath = join(cwd, entry);
67
+ const stat = statSync(fullPath);
68
+ if (stat.isDirectory()) {
69
+ const count = readdirSync(fullPath).filter(
70
+ (e) => !IGNORE.includes(e)
71
+ ).length;
72
+ lines.push(` 📁 ${entry}/ (${count} items)`);
73
+ } else {
74
+ lines.push(` 📄 ${entry}`);
75
+ }
76
+ }
77
+ } catch {
78
+ lines.push(" (could not read directory)");
79
+ }
80
+
81
+ // Build and append repo map
82
+ try {
83
+ const repoMap = await buildRepoMap(cwd);
84
+ lines.push("\n" + repoMap);
85
+ } catch (e) {
86
+ // Repo map failed — continue without it
87
+ lines.push("\n## Repository Map\n (unable to build map)");
88
+ }
89
+
90
+ return lines.join("\n");
91
+ }
92
+
93
+ /**
94
+ * Get the system prompt for the coding agent
95
+ */
96
+ export async function getSystemPrompt(projectContext: string): Promise<string> {
97
+ return `You are CODEMAXXING, an AI coding assistant running in the terminal.
98
+
99
+ You help developers understand, write, debug, and refactor code. You have access to tools that let you read files, write files, list directories, search code, and run shell commands.
100
+
101
+ ## Rules
102
+ - Always read relevant files before making changes
103
+ - Explain what you're about to do before doing it
104
+ - When writing files, show the user what will change
105
+ - Be concise but thorough
106
+ - If you're unsure, ask — don't guess
107
+ - Use the run_command tool for building, testing, and linters
108
+ - Never delete files without explicit confirmation
109
+
110
+ ## Repository Map
111
+ The project context below includes a map of the codebase structure. Use this map to understand what files, functions, classes, and types exist where. Use read_file to see full implementations when needed.
112
+
113
+ ## Project Context
114
+ ${projectContext}
115
+
116
+ ## Behavior
117
+ - Respond in markdown
118
+ - Use code blocks with language tags
119
+ - Be direct and helpful
120
+ - If the user asks to "just do it", skip explanations and execute`;
121
+ }
122
+
123
+ /**
124
+ * Synchronous version for backwards compatibility (without repo map)
125
+ * @deprecated Use async buildProjectContext instead
126
+ */
127
+ export function buildProjectContextSync(cwd: string): string {
128
+ const lines: string[] = [];
129
+ lines.push(`Project root: ${cwd}`);
130
+
131
+ const markers = [
132
+ "package.json", "Cargo.toml", "pyproject.toml", "go.mod",
133
+ "Makefile", "Dockerfile", "README.md", "CODEMAXXING.md",
134
+ ];
135
+
136
+ const found: string[] = [];
137
+ for (const m of markers) {
138
+ if (existsSync(join(cwd, m))) found.push(m);
139
+ }
140
+
141
+ if (found.length > 0) {
142
+ lines.push(`Project files: ${found.join(", ")}`);
143
+ }
144
+
145
+ return lines.join("\n");
146
+ }
@@ -0,0 +1,117 @@
1
+ import { execSync } from "child_process";
2
+ import { existsSync } from "fs";
3
+ import { join } from "path";
4
+
5
+ /**
6
+ * Check if a directory is inside a git repo
7
+ */
8
+ export function isGitRepo(cwd: string): boolean {
9
+ try {
10
+ execSync("git rev-parse --is-inside-work-tree", {
11
+ cwd,
12
+ stdio: "pipe",
13
+ encoding: "utf-8",
14
+ });
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Get current branch name
23
+ */
24
+ export function getBranch(cwd: string): string {
25
+ try {
26
+ return execSync("git branch --show-current", {
27
+ cwd,
28
+ stdio: "pipe",
29
+ encoding: "utf-8",
30
+ }).trim();
31
+ } catch {
32
+ return "unknown";
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Get short git status (clean / dirty + count)
38
+ */
39
+ export function getStatus(cwd: string): string {
40
+ try {
41
+ const output = execSync("git status --porcelain", {
42
+ cwd,
43
+ stdio: "pipe",
44
+ encoding: "utf-8",
45
+ }).trim();
46
+ if (!output) return "clean";
47
+ const count = output.split("\n").length;
48
+ return `${count} changed`;
49
+ } catch {
50
+ return "unknown";
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Auto-commit a file change with a descriptive message
56
+ */
57
+ export function autoCommit(cwd: string, filePath: string, action: string): boolean {
58
+ try {
59
+ execSync(`git add "${filePath}"`, { cwd, stdio: "pipe" });
60
+ const msg = `codemaxxing: ${action} ${filePath}`;
61
+ execSync(`git commit -m "${msg}" --no-verify`, { cwd, stdio: "pipe" });
62
+ return true;
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Get diff of uncommitted changes (or last commit)
70
+ */
71
+ export function getDiff(cwd: string): string {
72
+ try {
73
+ // First try uncommitted changes
74
+ let diff = execSync("git diff", { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
75
+ // Include staged changes too
76
+ const staged = execSync("git diff --cached", { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
77
+ if (staged) diff = diff ? `${diff}\n${staged}` : staged;
78
+
79
+ // If no uncommitted changes, show last commit
80
+ if (!diff) {
81
+ diff = execSync("git diff HEAD~1 HEAD", { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
82
+ if (diff) return `(last commit)\n${diff}`;
83
+ }
84
+
85
+ return diff || "No changes.";
86
+ } catch {
87
+ return "Error getting diff.";
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Undo the last codemaxxing commit
93
+ */
94
+ export function undoLastCommit(cwd: string): { success: boolean; message: string } {
95
+ try {
96
+ // Check if last commit was from codemaxxing
97
+ const lastMsg = execSync("git log -1 --pretty=%s", {
98
+ cwd,
99
+ stdio: "pipe",
100
+ encoding: "utf-8",
101
+ }).trim();
102
+
103
+ if (!lastMsg.startsWith("codemaxxing:")) {
104
+ return {
105
+ success: false,
106
+ message: `Last commit is not from codemaxxing: "${lastMsg}"`,
107
+ };
108
+ }
109
+
110
+ execSync("git reset --soft HEAD~1", { cwd, stdio: "pipe" });
111
+ execSync("git restore --staged .", { cwd, stdio: "pipe" });
112
+
113
+ return { success: true, message: `Reverted: ${lastMsg}` };
114
+ } catch (e: any) {
115
+ return { success: false, message: `Error: ${e.message}` };
116
+ }
117
+ }