codemaxxing 0.2.0 → 0.3.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.
@@ -3,6 +3,30 @@ import { join, extname } from "path";
3
3
  import { buildRepoMap } from "./repomap.js";
4
4
  import { buildSkillPrompts } from "./skills.js";
5
5
 
6
+ /**
7
+ * Load project rules from CODEMAXXING.md, .codemaxxing/CODEMAXXING.md, or .cursorrules
8
+ * Returns { content, source } or null if none found
9
+ */
10
+ export function loadProjectRules(cwd: string): { content: string; source: string } | null {
11
+ const candidates = [
12
+ { path: join(cwd, "CODEMAXXING.md"), source: "CODEMAXXING.md" },
13
+ { path: join(cwd, ".codemaxxing", "CODEMAXXING.md"), source: ".codemaxxing/CODEMAXXING.md" },
14
+ { path: join(cwd, ".cursorrules"), source: ".cursorrules" },
15
+ ];
16
+
17
+ for (const { path, source } of candidates) {
18
+ if (existsSync(path)) {
19
+ try {
20
+ const content = readFileSync(path, "utf-8").trim();
21
+ if (content) return { content, source };
22
+ } catch {
23
+ // skip unreadable files
24
+ }
25
+ }
26
+ }
27
+ return null;
28
+ }
29
+
6
30
  /**
7
31
  * Build a project context string by scanning the working directory
8
32
  */
@@ -31,15 +55,6 @@ export async function buildProjectContext(cwd: string): Promise<string> {
31
55
  lines.push(`Project files: ${found.join(", ")}`);
32
56
  }
33
57
 
34
- // Read PIERRE.md if it exists (like QWEN.md — project context file)
35
- const contextMd = join(cwd, "CODEMAXXING.md");
36
- if (existsSync(contextMd)) {
37
- const content = readFileSync(contextMd, "utf-8");
38
- lines.push("\n--- CODEMAXXING.md (project context) ---");
39
- lines.push(content.slice(0, 4000));
40
- lines.push("--- end CODEMAXXING.md ---");
41
- }
42
-
43
58
  // Read package.json for project info
44
59
  const pkgPath = join(cwd, "package.json");
45
60
  if (existsSync(pkgPath)) {
@@ -94,7 +109,7 @@ export async function buildProjectContext(cwd: string): Promise<string> {
94
109
  /**
95
110
  * Get the system prompt for the coding agent
96
111
  */
97
- export async function getSystemPrompt(projectContext: string, skillPrompts: string = ""): Promise<string> {
112
+ export async function getSystemPrompt(projectContext: string, skillPrompts: string = "", projectRules: string = ""): Promise<string> {
98
113
  const base = `You are CODEMAXXING, an AI coding assistant running in the terminal.
99
114
 
100
115
  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.
@@ -120,10 +135,17 @@ ${projectContext}
120
135
  - Be direct and helpful
121
136
  - If the user asks to "just do it", skip explanations and execute`;
122
137
 
138
+ let prompt = base;
139
+
140
+ if (projectRules) {
141
+ prompt += "\n\n--- Project Rules (CODEMAXXING.md) ---\n" + projectRules + "\n--- End Project Rules ---";
142
+ }
143
+
123
144
  if (skillPrompts) {
124
- return base + "\n\n## Active Skills\n" + skillPrompts;
145
+ prompt += "\n\n## Active Skills\n" + skillPrompts;
125
146
  }
126
- return base;
147
+
148
+ return prompt;
127
149
  }
128
150
 
129
151
  /**
@@ -0,0 +1,116 @@
1
+ import { existsSync } from "fs";
2
+ import { join, extname } from "path";
3
+ import { execSync } from "child_process";
4
+
5
+ interface LinterInfo {
6
+ name: string;
7
+ command: string;
8
+ }
9
+
10
+ /**
11
+ * Detect the project linter based on config files in the working directory
12
+ */
13
+ export function detectLinter(cwd: string): LinterInfo | null {
14
+ // JavaScript/TypeScript — check for biome first (faster), then eslint
15
+ if (existsSync(join(cwd, "biome.json")) || existsSync(join(cwd, "biome.jsonc"))) {
16
+ return { name: "Biome", command: "npx biome check" };
17
+ }
18
+ if (
19
+ existsSync(join(cwd, ".eslintrc")) ||
20
+ existsSync(join(cwd, ".eslintrc.js")) ||
21
+ existsSync(join(cwd, ".eslintrc.cjs")) ||
22
+ existsSync(join(cwd, ".eslintrc.json")) ||
23
+ existsSync(join(cwd, ".eslintrc.yml")) ||
24
+ existsSync(join(cwd, "eslint.config.js")) ||
25
+ existsSync(join(cwd, "eslint.config.mjs")) ||
26
+ existsSync(join(cwd, "eslint.config.ts"))
27
+ ) {
28
+ return { name: "ESLint", command: "npx eslint" };
29
+ }
30
+ // Check package.json for eslint dependency as fallback
31
+ if (existsSync(join(cwd, "package.json"))) {
32
+ try {
33
+ const pkg = JSON.parse(require("fs").readFileSync(join(cwd, "package.json"), "utf-8"));
34
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
35
+ if (allDeps["@biomejs/biome"]) {
36
+ return { name: "Biome", command: "npx biome check" };
37
+ }
38
+ if (allDeps["eslint"]) {
39
+ return { name: "ESLint", command: "npx eslint" };
40
+ }
41
+ } catch {
42
+ // ignore
43
+ }
44
+ }
45
+
46
+ // Python — ruff (fast) or flake8/pylint
47
+ if (existsSync(join(cwd, "ruff.toml")) || existsSync(join(cwd, ".ruff.toml"))) {
48
+ return { name: "Ruff", command: "ruff check" };
49
+ }
50
+ if (existsSync(join(cwd, "pyproject.toml"))) {
51
+ try {
52
+ const content = require("fs").readFileSync(join(cwd, "pyproject.toml"), "utf-8");
53
+ if (content.includes("[tool.ruff]")) {
54
+ return { name: "Ruff", command: "ruff check" };
55
+ }
56
+ } catch {
57
+ // ignore
58
+ }
59
+ return { name: "Ruff", command: "ruff check" };
60
+ }
61
+
62
+ // Rust
63
+ if (existsSync(join(cwd, "Cargo.toml"))) {
64
+ return { name: "Clippy", command: "cargo clippy --message-format=short --" };
65
+ }
66
+
67
+ // Go
68
+ if (existsSync(join(cwd, "go.mod"))) {
69
+ return { name: "golangci-lint", command: "golangci-lint run" };
70
+ }
71
+
72
+ return null;
73
+ }
74
+
75
+ /**
76
+ * Run the linter on a specific file and return errors (or null if clean)
77
+ */
78
+ export function runLinter(linter: LinterInfo, filePath: string, cwd: string): string | null {
79
+ // Skip files that the linter can't handle
80
+ const ext = extname(filePath).toLowerCase();
81
+ const jsExts = new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]);
82
+ const pyExts = new Set([".py", ".pyi"]);
83
+ const rsExts = new Set([".rs"]);
84
+ const goExts = new Set([".go"]);
85
+
86
+ // Only lint files matching the linter's language
87
+ if ((linter.name === "ESLint" || linter.name === "Biome") && !jsExts.has(ext)) return null;
88
+ if (linter.name === "Ruff" && !pyExts.has(ext)) return null;
89
+ if (linter.name === "Clippy" && !rsExts.has(ext)) return null;
90
+ if (linter.name === "golangci-lint" && !goExts.has(ext)) return null;
91
+
92
+ try {
93
+ // Clippy works on the whole project, not individual files
94
+ const command = linter.name === "Clippy"
95
+ ? linter.command
96
+ : `${linter.command} ${filePath}`;
97
+
98
+ execSync(command, {
99
+ cwd,
100
+ encoding: "utf-8",
101
+ timeout: 15000,
102
+ stdio: ["pipe", "pipe", "pipe"],
103
+ });
104
+ return null; // No errors
105
+ } catch (e: any) {
106
+ const output = (e.stdout || "") + (e.stderr || "");
107
+ const trimmed = output.trim();
108
+ if (!trimmed) return null;
109
+ // Limit output to avoid flooding context
110
+ const lines = trimmed.split("\n");
111
+ if (lines.length > 30) {
112
+ return lines.slice(0, 30).join("\n") + `\n... (${lines.length - 30} more lines)`;
113
+ }
114
+ return trimmed;
115
+ }
116
+ }