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.
- package/README.md +72 -6
- package/dist/agent.d.ts +27 -0
- package/dist/agent.js +108 -2
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +6 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +9 -0
- package/dist/exec.d.ts +7 -0
- package/dist/exec.js +154 -0
- package/dist/index.js +83 -4
- package/dist/skills/registry.js +954 -0
- package/dist/utils/context.d.ts +9 -1
- package/dist/utils/context.js +31 -11
- package/dist/utils/lint.d.ts +13 -0
- package/dist/utils/lint.js +108 -0
- package/package.json +1 -1
- package/src/agent.ts +124 -2
- package/src/cli.ts +5 -1
- package/src/config.ts +11 -0
- package/src/exec.ts +171 -0
- package/src/index.tsx +81 -3
- package/src/skills/registry.ts +954 -0
- package/src/utils/context.ts +34 -12
- package/src/utils/lint.ts +116 -0
package/src/utils/context.ts
CHANGED
|
@@ -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
|
-
|
|
145
|
+
prompt += "\n\n## Active Skills\n" + skillPrompts;
|
|
125
146
|
}
|
|
126
|
-
|
|
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
|
+
}
|