@compilr-dev/agents-coding 0.0.1
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 +788 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +75 -0
- package/dist/skills/index.d.ts +39 -0
- package/dist/skills/index.js +322 -0
- package/dist/tools/git/branch.d.ts +17 -0
- package/dist/tools/git/branch.js +264 -0
- package/dist/tools/git/commit.d.ts +23 -0
- package/dist/tools/git/commit.js +280 -0
- package/dist/tools/git/diff.d.ts +19 -0
- package/dist/tools/git/diff.js +221 -0
- package/dist/tools/git/index.d.ts +10 -0
- package/dist/tools/git/index.js +11 -0
- package/dist/tools/git/log.d.ts +19 -0
- package/dist/tools/git/log.js +235 -0
- package/dist/tools/git/stash.d.ts +17 -0
- package/dist/tools/git/stash.js +294 -0
- package/dist/tools/git/status.d.ts +19 -0
- package/dist/tools/git/status.js +160 -0
- package/dist/tools/git/types.d.ts +293 -0
- package/dist/tools/git/types.js +4 -0
- package/dist/tools/git/utils.d.ts +58 -0
- package/dist/tools/git/utils.js +197 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/project/detect.d.ts +19 -0
- package/dist/tools/project/detect.js +341 -0
- package/dist/tools/project/find-root.d.ts +21 -0
- package/dist/tools/project/find-root.js +239 -0
- package/dist/tools/project/index.d.ts +6 -0
- package/dist/tools/project/index.js +5 -0
- package/dist/tools/project/types.d.ts +83 -0
- package/dist/tools/project/types.js +4 -0
- package/dist/tools/runners/build.d.ts +19 -0
- package/dist/tools/runners/build.js +306 -0
- package/dist/tools/runners/format.d.ts +19 -0
- package/dist/tools/runners/format.js +376 -0
- package/dist/tools/runners/index.d.ts +9 -0
- package/dist/tools/runners/index.js +9 -0
- package/dist/tools/runners/lint.d.ts +19 -0
- package/dist/tools/runners/lint.js +356 -0
- package/dist/tools/runners/test.d.ts +19 -0
- package/dist/tools/runners/test.js +386 -0
- package/dist/tools/runners/types.d.ts +97 -0
- package/dist/tools/runners/types.js +4 -0
- package/dist/tools/runners/utils.d.ts +69 -0
- package/dist/tools/runners/utils.js +179 -0
- package/dist/tools/search/definition.d.ts +19 -0
- package/dist/tools/search/definition.js +305 -0
- package/dist/tools/search/index.d.ts +8 -0
- package/dist/tools/search/index.js +8 -0
- package/dist/tools/search/references.d.ts +19 -0
- package/dist/tools/search/references.js +179 -0
- package/dist/tools/search/todos.d.ts +19 -0
- package/dist/tools/search/todos.js +269 -0
- package/dist/tools/search/types.d.ts +132 -0
- package/dist/tools/search/types.js +4 -0
- package/dist/tools/search/utils.d.ts +45 -0
- package/dist/tools/search/utils.js +152 -0
- package/package.json +88 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Detection Tool
|
|
3
|
+
* Detects project type, package manager, and available tools
|
|
4
|
+
*/
|
|
5
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
|
|
8
|
+
/**
|
|
9
|
+
* Project detection rules - ordered by priority
|
|
10
|
+
*/
|
|
11
|
+
const PROJECT_RULES = [
|
|
12
|
+
// Node.js
|
|
13
|
+
{ type: 'node', files: ['package.json'], priority: 10 },
|
|
14
|
+
// Python
|
|
15
|
+
{ type: 'python', files: ['pyproject.toml'], packageManager: 'poetry', priority: 10 },
|
|
16
|
+
{ type: 'python', files: ['setup.py'], packageManager: 'pip', priority: 8 },
|
|
17
|
+
{ type: 'python', files: ['requirements.txt'], packageManager: 'pip', priority: 7 },
|
|
18
|
+
{ type: 'python', files: ['Pipfile'], packageManager: 'pip', priority: 9 },
|
|
19
|
+
// Rust
|
|
20
|
+
{ type: 'rust', files: ['Cargo.toml'], packageManager: 'cargo', priority: 10 },
|
|
21
|
+
// Go
|
|
22
|
+
{ type: 'go', files: ['go.mod'], packageManager: 'go', priority: 10 },
|
|
23
|
+
// Java
|
|
24
|
+
{ type: 'java', files: ['pom.xml'], packageManager: 'maven', priority: 10 },
|
|
25
|
+
{ type: 'java', files: ['build.gradle'], packageManager: 'gradle', priority: 10 },
|
|
26
|
+
{ type: 'java', files: ['build.gradle.kts'], packageManager: 'gradle', priority: 10 },
|
|
27
|
+
// Ruby
|
|
28
|
+
{ type: 'ruby', files: ['Gemfile'], packageManager: 'bundler', priority: 10 },
|
|
29
|
+
// PHP
|
|
30
|
+
{ type: 'php', files: ['composer.json'], packageManager: 'composer', priority: 10 },
|
|
31
|
+
// .NET
|
|
32
|
+
{ type: 'dotnet', files: ['*.csproj'], packageManager: 'dotnet', priority: 10 },
|
|
33
|
+
{ type: 'dotnet', files: ['*.sln'], packageManager: 'dotnet', priority: 9 },
|
|
34
|
+
{ type: 'dotnet', files: ['*.fsproj'], packageManager: 'dotnet', priority: 10 },
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Test framework detection rules
|
|
38
|
+
*/
|
|
39
|
+
const TEST_RULES = [
|
|
40
|
+
// Node.js
|
|
41
|
+
{ files: ['vitest.config.ts', 'vitest.config.js'], dependencies: ['vitest'], name: 'vitest' },
|
|
42
|
+
{ files: ['jest.config.ts', 'jest.config.js'], dependencies: ['jest'], name: 'jest' },
|
|
43
|
+
{ files: ['.mocharc.json', '.mocharc.js'], dependencies: ['mocha'], name: 'mocha' },
|
|
44
|
+
{ files: ['ava.config.js'], dependencies: ['ava'], name: 'ava' },
|
|
45
|
+
{ files: ['playwright.config.ts'], dependencies: ['@playwright/test'], name: 'playwright' },
|
|
46
|
+
{ files: ['cypress.config.ts'], dependencies: ['cypress'], name: 'cypress' },
|
|
47
|
+
// Python
|
|
48
|
+
{ files: ['pytest.ini', 'pyproject.toml'], name: 'pytest' },
|
|
49
|
+
{ files: ['setup.cfg'], name: 'unittest' },
|
|
50
|
+
// Rust
|
|
51
|
+
{ files: ['Cargo.toml'], name: 'cargo test' },
|
|
52
|
+
// Go
|
|
53
|
+
{ files: ['*_test.go'], name: 'go test' },
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* Linter detection rules
|
|
57
|
+
*/
|
|
58
|
+
const LINTER_RULES = [
|
|
59
|
+
// Node.js
|
|
60
|
+
{
|
|
61
|
+
files: ['eslint.config.js', 'eslint.config.mjs', '.eslintrc.js', '.eslintrc.json'],
|
|
62
|
+
dependencies: ['eslint'],
|
|
63
|
+
name: 'eslint',
|
|
64
|
+
},
|
|
65
|
+
{ files: ['biome.json'], dependencies: ['@biomejs/biome'], name: 'biome' },
|
|
66
|
+
{ files: ['tslint.json'], dependencies: ['tslint'], name: 'tslint' },
|
|
67
|
+
// Python
|
|
68
|
+
{ files: ['ruff.toml', 'pyproject.toml'], name: 'ruff' },
|
|
69
|
+
{ files: ['.pylintrc', 'pyproject.toml'], name: 'pylint' },
|
|
70
|
+
{ files: ['.flake8', 'setup.cfg'], name: 'flake8' },
|
|
71
|
+
// Rust
|
|
72
|
+
{ files: ['Cargo.toml'], name: 'clippy' },
|
|
73
|
+
// Go
|
|
74
|
+
{ files: ['.golangci.yml', '.golangci.yaml'], name: 'golangci-lint' },
|
|
75
|
+
];
|
|
76
|
+
/**
|
|
77
|
+
* Formatter detection rules
|
|
78
|
+
*/
|
|
79
|
+
const FORMATTER_RULES = [
|
|
80
|
+
// Node.js
|
|
81
|
+
{
|
|
82
|
+
files: ['.prettierrc', '.prettierrc.js', '.prettierrc.json'],
|
|
83
|
+
dependencies: ['prettier'],
|
|
84
|
+
name: 'prettier',
|
|
85
|
+
},
|
|
86
|
+
{ files: ['biome.json'], dependencies: ['@biomejs/biome'], name: 'biome' },
|
|
87
|
+
// Python
|
|
88
|
+
{ files: ['pyproject.toml'], name: 'black' },
|
|
89
|
+
{ files: ['ruff.toml', 'pyproject.toml'], name: 'ruff format' },
|
|
90
|
+
// Rust
|
|
91
|
+
{ files: ['rustfmt.toml', '.rustfmt.toml'], name: 'rustfmt' },
|
|
92
|
+
// Go
|
|
93
|
+
{ files: ['go.mod'], name: 'gofmt' },
|
|
94
|
+
];
|
|
95
|
+
/**
|
|
96
|
+
* Build tool detection rules
|
|
97
|
+
*/
|
|
98
|
+
const BUILD_RULES = [
|
|
99
|
+
// Node.js
|
|
100
|
+
{ files: ['vite.config.ts', 'vite.config.js'], dependencies: ['vite'], name: 'vite' },
|
|
101
|
+
{ files: ['webpack.config.js'], dependencies: ['webpack'], name: 'webpack' },
|
|
102
|
+
{ files: ['rollup.config.js'], dependencies: ['rollup'], name: 'rollup' },
|
|
103
|
+
{ files: ['esbuild.config.js'], dependencies: ['esbuild'], name: 'esbuild' },
|
|
104
|
+
{ files: ['tsconfig.json'], dependencies: ['typescript'], scripts: ['build'], name: 'tsc' },
|
|
105
|
+
{ files: ['turbo.json'], dependencies: ['turbo'], name: 'turborepo' },
|
|
106
|
+
{ files: ['nx.json'], dependencies: ['nx'], name: 'nx' },
|
|
107
|
+
// Rust
|
|
108
|
+
{ files: ['Cargo.toml'], name: 'cargo build' },
|
|
109
|
+
// Go
|
|
110
|
+
{ files: ['go.mod'], name: 'go build' },
|
|
111
|
+
];
|
|
112
|
+
/**
|
|
113
|
+
* Detect project tool
|
|
114
|
+
*/
|
|
115
|
+
export const detectProjectTool = defineTool({
|
|
116
|
+
name: 'detect_project',
|
|
117
|
+
description: 'Detect the type of project in a directory. ' +
|
|
118
|
+
'Returns project type (node, python, rust, go, java, etc.), ' +
|
|
119
|
+
'package manager, test framework, linter, formatter, and build tool.',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
path: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
description: 'Directory to analyze (default: current directory)',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
required: [],
|
|
129
|
+
},
|
|
130
|
+
execute: executeDetectProject,
|
|
131
|
+
});
|
|
132
|
+
/**
|
|
133
|
+
* Execute project detection
|
|
134
|
+
*/
|
|
135
|
+
async function executeDetectProject(input) {
|
|
136
|
+
const targetPath = input.path ?? process.cwd();
|
|
137
|
+
// Check if directory exists
|
|
138
|
+
try {
|
|
139
|
+
const stats = await stat(targetPath);
|
|
140
|
+
if (!stats.isDirectory()) {
|
|
141
|
+
return createErrorResult(`Not a directory: ${targetPath}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
146
|
+
return createErrorResult(`Directory not found: ${targetPath}`);
|
|
147
|
+
}
|
|
148
|
+
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
// List directory contents
|
|
152
|
+
const entries = await readdir(targetPath);
|
|
153
|
+
const configFiles = [];
|
|
154
|
+
// Detect project types
|
|
155
|
+
const detectedTypes = new Map();
|
|
156
|
+
for (const rule of PROJECT_RULES) {
|
|
157
|
+
for (const pattern of rule.files) {
|
|
158
|
+
if (pattern.includes('*')) {
|
|
159
|
+
// Glob pattern
|
|
160
|
+
const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
|
|
161
|
+
const matches = entries.filter((e) => regex.test(e));
|
|
162
|
+
if (matches.length > 0) {
|
|
163
|
+
configFiles.push(...matches);
|
|
164
|
+
const existing = detectedTypes.get(rule.type);
|
|
165
|
+
if (!existing || existing.priority < rule.priority) {
|
|
166
|
+
detectedTypes.set(rule.type, {
|
|
167
|
+
packageManager: rule.packageManager,
|
|
168
|
+
priority: rule.priority,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Exact match
|
|
175
|
+
if (entries.includes(pattern)) {
|
|
176
|
+
configFiles.push(pattern);
|
|
177
|
+
const existing = detectedTypes.get(rule.type);
|
|
178
|
+
if (!existing || existing.priority < rule.priority) {
|
|
179
|
+
detectedTypes.set(rule.type, {
|
|
180
|
+
packageManager: rule.packageManager,
|
|
181
|
+
priority: rule.priority,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Load package.json for additional detection
|
|
189
|
+
let packageJson = null;
|
|
190
|
+
if (entries.includes('package.json')) {
|
|
191
|
+
try {
|
|
192
|
+
const content = await readFile(join(targetPath, 'package.json'), 'utf-8');
|
|
193
|
+
packageJson = JSON.parse(content);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Ignore parse errors
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Detect Node.js package manager from lockfiles
|
|
200
|
+
let nodePackageManager;
|
|
201
|
+
if (entries.includes('pnpm-lock.yaml')) {
|
|
202
|
+
nodePackageManager = 'pnpm';
|
|
203
|
+
}
|
|
204
|
+
else if (entries.includes('yarn.lock')) {
|
|
205
|
+
nodePackageManager = 'yarn';
|
|
206
|
+
}
|
|
207
|
+
else if (entries.includes('package-lock.json')) {
|
|
208
|
+
nodePackageManager = 'npm';
|
|
209
|
+
}
|
|
210
|
+
// Update node package manager if detected
|
|
211
|
+
if (nodePackageManager && detectedTypes.has('node')) {
|
|
212
|
+
const nodeInfo = detectedTypes.get('node');
|
|
213
|
+
if (nodeInfo) {
|
|
214
|
+
detectedTypes.set('node', { ...nodeInfo, packageManager: nodePackageManager });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Determine primary type (highest priority)
|
|
218
|
+
let primaryType = 'unknown';
|
|
219
|
+
let primaryPackageManager;
|
|
220
|
+
let highestPriority = -1;
|
|
221
|
+
for (const [type, info] of detectedTypes) {
|
|
222
|
+
if (info.priority > highestPriority) {
|
|
223
|
+
highestPriority = info.priority;
|
|
224
|
+
primaryType = type;
|
|
225
|
+
primaryPackageManager = info.packageManager;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Detect tools
|
|
229
|
+
const testFramework = await detectTool(targetPath, entries, TEST_RULES, packageJson);
|
|
230
|
+
const linter = await detectTool(targetPath, entries, LINTER_RULES, packageJson);
|
|
231
|
+
const formatter = await detectTool(targetPath, entries, FORMATTER_RULES, packageJson);
|
|
232
|
+
const buildTool = await detectTool(targetPath, entries, BUILD_RULES, packageJson);
|
|
233
|
+
const result = {
|
|
234
|
+
type: primaryType,
|
|
235
|
+
types: Array.from(detectedTypes.keys()),
|
|
236
|
+
packageManager: primaryPackageManager,
|
|
237
|
+
testFramework,
|
|
238
|
+
linter,
|
|
239
|
+
formatter,
|
|
240
|
+
buildTool,
|
|
241
|
+
configFiles: [...new Set(configFiles)],
|
|
242
|
+
name: packageJson?.name,
|
|
243
|
+
};
|
|
244
|
+
return createSuccessResult(result);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Detect a tool from rules
|
|
252
|
+
*/
|
|
253
|
+
async function detectTool(targetPath, entries, rules, packageJson) {
|
|
254
|
+
for (const rule of rules) {
|
|
255
|
+
// Check files
|
|
256
|
+
for (const pattern of rule.files) {
|
|
257
|
+
if (pattern.includes('*')) {
|
|
258
|
+
const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
|
|
259
|
+
if (entries.some((e) => regex.test(e))) {
|
|
260
|
+
// Additional check for pyproject.toml - verify it contains the tool
|
|
261
|
+
if (pattern === 'pyproject.toml' && entries.includes('pyproject.toml')) {
|
|
262
|
+
try {
|
|
263
|
+
const content = await readFile(join(targetPath, 'pyproject.toml'), 'utf-8');
|
|
264
|
+
if ((rule.name === 'pytest' && content.includes('[tool.pytest')) ||
|
|
265
|
+
(rule.name === 'black' && content.includes('[tool.black')) ||
|
|
266
|
+
(rule.name === 'ruff' && content.includes('[tool.ruff')) ||
|
|
267
|
+
(rule.name === 'ruff format' && content.includes('[tool.ruff'))) {
|
|
268
|
+
return rule.name;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// Ignore read errors
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
return rule.name;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else if (entries.includes(pattern)) {
|
|
281
|
+
return rule.name;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Check package.json dependencies
|
|
285
|
+
if (packageJson && rule.dependencies) {
|
|
286
|
+
const allDeps = {
|
|
287
|
+
...packageJson.dependencies,
|
|
288
|
+
...packageJson.devDependencies,
|
|
289
|
+
};
|
|
290
|
+
for (const dep of rule.dependencies) {
|
|
291
|
+
if (allDeps[dep]) {
|
|
292
|
+
return rule.name;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// Check package.json scripts
|
|
297
|
+
if (packageJson?.scripts && rule.scripts) {
|
|
298
|
+
for (const script of rule.scripts) {
|
|
299
|
+
if (packageJson.scripts[script]) {
|
|
300
|
+
return rule.name;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Check if error is a Node.js error with code
|
|
309
|
+
*/
|
|
310
|
+
function isNodeError(error) {
|
|
311
|
+
return error instanceof Error && 'code' in error;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Factory function to create detect project tool with custom options
|
|
315
|
+
*/
|
|
316
|
+
export function createDetectProjectTool(options) {
|
|
317
|
+
return defineTool({
|
|
318
|
+
name: 'detect_project',
|
|
319
|
+
description: 'Detect the type of project in a directory. ' +
|
|
320
|
+
'Returns project type (node, python, rust, go, java, etc.), ' +
|
|
321
|
+
'package manager, test framework, linter, formatter, and build tool.',
|
|
322
|
+
inputSchema: {
|
|
323
|
+
type: 'object',
|
|
324
|
+
properties: {
|
|
325
|
+
path: {
|
|
326
|
+
type: 'string',
|
|
327
|
+
description: 'Directory to analyze (default: current directory)',
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
required: [],
|
|
331
|
+
},
|
|
332
|
+
execute: async (input) => {
|
|
333
|
+
let targetPath = input.path ?? '.';
|
|
334
|
+
// Resolve relative paths
|
|
335
|
+
if (options?.baseDir && !targetPath.startsWith('/')) {
|
|
336
|
+
targetPath = join(options.baseDir, targetPath);
|
|
337
|
+
}
|
|
338
|
+
return executeDetectProject({ path: targetPath });
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find Project Root Tool
|
|
3
|
+
* Finds the root directory of a project by looking for marker files
|
|
4
|
+
*/
|
|
5
|
+
import type { Tool } from '@compilr-dev/agents';
|
|
6
|
+
import type { FindProjectRootInput } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Find project root tool
|
|
9
|
+
*/
|
|
10
|
+
export declare const findProjectRootTool: Tool<FindProjectRootInput>;
|
|
11
|
+
/**
|
|
12
|
+
* Factory function to create find project root tool with custom options
|
|
13
|
+
*/
|
|
14
|
+
export declare function createFindProjectRootTool(options?: {
|
|
15
|
+
/** Base directory for relative paths */
|
|
16
|
+
baseDir?: string;
|
|
17
|
+
/** Default markers to use */
|
|
18
|
+
defaultMarkers?: string[];
|
|
19
|
+
/** Maximum depth to search */
|
|
20
|
+
maxDepth?: number;
|
|
21
|
+
}): Tool<FindProjectRootInput>;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find Project Root Tool
|
|
3
|
+
* Finds the root directory of a project by looking for marker files
|
|
4
|
+
*/
|
|
5
|
+
import { access, stat } from 'node:fs/promises';
|
|
6
|
+
import { join, dirname, resolve, sep } from 'node:path';
|
|
7
|
+
import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
|
|
8
|
+
/**
|
|
9
|
+
* Default marker files to look for when finding project root
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_MARKERS = [
|
|
12
|
+
// Version control
|
|
13
|
+
'.git',
|
|
14
|
+
'.hg',
|
|
15
|
+
'.svn',
|
|
16
|
+
// Node.js
|
|
17
|
+
'package.json',
|
|
18
|
+
// Python
|
|
19
|
+
'pyproject.toml',
|
|
20
|
+
'setup.py',
|
|
21
|
+
'setup.cfg',
|
|
22
|
+
// Rust
|
|
23
|
+
'Cargo.toml',
|
|
24
|
+
// Go
|
|
25
|
+
'go.mod',
|
|
26
|
+
// Java
|
|
27
|
+
'pom.xml',
|
|
28
|
+
'build.gradle',
|
|
29
|
+
'build.gradle.kts',
|
|
30
|
+
// Ruby
|
|
31
|
+
'Gemfile',
|
|
32
|
+
// PHP
|
|
33
|
+
'composer.json',
|
|
34
|
+
// .NET
|
|
35
|
+
'*.sln',
|
|
36
|
+
'*.csproj',
|
|
37
|
+
// Generic
|
|
38
|
+
'.project-root', // Explicit marker file
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Find project root tool
|
|
42
|
+
*/
|
|
43
|
+
export const findProjectRootTool = defineTool({
|
|
44
|
+
name: 'find_project_root',
|
|
45
|
+
description: 'Find the root directory of a project by looking for marker files. ' +
|
|
46
|
+
'Searches upward from the starting directory until a marker is found. ' +
|
|
47
|
+
'Common markers: .git, package.json, Cargo.toml, go.mod, etc.',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
path: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Starting directory (default: current directory)',
|
|
54
|
+
},
|
|
55
|
+
markers: {
|
|
56
|
+
type: 'array',
|
|
57
|
+
items: { type: 'string' },
|
|
58
|
+
description: 'Marker files to look for (default: common project files)',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
required: [],
|
|
62
|
+
},
|
|
63
|
+
execute: executeFindProjectRoot,
|
|
64
|
+
});
|
|
65
|
+
/**
|
|
66
|
+
* Execute find project root
|
|
67
|
+
*/
|
|
68
|
+
async function executeFindProjectRoot(input) {
|
|
69
|
+
const startPath = resolve(input.path ?? process.cwd());
|
|
70
|
+
const markers = input.markers ?? DEFAULT_MARKERS;
|
|
71
|
+
// Check if start path exists
|
|
72
|
+
try {
|
|
73
|
+
const stats = await stat(startPath);
|
|
74
|
+
if (!stats.isDirectory()) {
|
|
75
|
+
return createErrorResult(`Not a directory: ${startPath}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
80
|
+
return createErrorResult(`Directory not found: ${startPath}`);
|
|
81
|
+
}
|
|
82
|
+
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
let currentPath = startPath;
|
|
86
|
+
let depth = 0;
|
|
87
|
+
const root = getRootPath();
|
|
88
|
+
while (currentPath.length >= root.length) {
|
|
89
|
+
// Check each marker
|
|
90
|
+
for (const marker of markers) {
|
|
91
|
+
if (marker.includes('*')) {
|
|
92
|
+
// Glob pattern - check if any matching file exists
|
|
93
|
+
const found = await checkGlobMarker(currentPath, marker);
|
|
94
|
+
if (found) {
|
|
95
|
+
const result = {
|
|
96
|
+
root: currentPath,
|
|
97
|
+
foundMarker: found,
|
|
98
|
+
depth,
|
|
99
|
+
};
|
|
100
|
+
return createSuccessResult(result);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Exact match
|
|
105
|
+
const markerPath = join(currentPath, marker);
|
|
106
|
+
if (await fileExists(markerPath)) {
|
|
107
|
+
const result = {
|
|
108
|
+
root: currentPath,
|
|
109
|
+
foundMarker: marker,
|
|
110
|
+
depth,
|
|
111
|
+
};
|
|
112
|
+
return createSuccessResult(result);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Move up one directory
|
|
117
|
+
const parentPath = dirname(currentPath);
|
|
118
|
+
if (parentPath === currentPath) {
|
|
119
|
+
// Reached filesystem root
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
currentPath = parentPath;
|
|
123
|
+
depth++;
|
|
124
|
+
}
|
|
125
|
+
return createErrorResult(`No project root found. Searched from ${startPath} to filesystem root.`);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check if a file exists
|
|
133
|
+
*/
|
|
134
|
+
async function fileExists(path) {
|
|
135
|
+
try {
|
|
136
|
+
await access(path);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check for glob marker pattern (e.g., *.sln)
|
|
145
|
+
*/
|
|
146
|
+
async function checkGlobMarker(directory, pattern) {
|
|
147
|
+
const { readdir } = await import('node:fs/promises');
|
|
148
|
+
try {
|
|
149
|
+
const entries = await readdir(directory);
|
|
150
|
+
const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
if (regex.test(entry)) {
|
|
153
|
+
return entry;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get the root path for the current platform
|
|
164
|
+
*/
|
|
165
|
+
function getRootPath() {
|
|
166
|
+
// On Windows, root could be C:\, D:\, etc.
|
|
167
|
+
// On Unix, root is /
|
|
168
|
+
if (process.platform === 'win32') {
|
|
169
|
+
return process.cwd().split(sep)[0] + sep;
|
|
170
|
+
}
|
|
171
|
+
return '/';
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if error is a Node.js error with code
|
|
175
|
+
*/
|
|
176
|
+
function isNodeError(error) {
|
|
177
|
+
return error instanceof Error && 'code' in error;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Factory function to create find project root tool with custom options
|
|
181
|
+
*/
|
|
182
|
+
export function createFindProjectRootTool(options) {
|
|
183
|
+
return defineTool({
|
|
184
|
+
name: 'find_project_root',
|
|
185
|
+
description: 'Find the root directory of a project by looking for marker files. ' +
|
|
186
|
+
'Searches upward from the starting directory until a marker is found. ' +
|
|
187
|
+
'Common markers: .git, package.json, Cargo.toml, go.mod, etc.',
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: 'object',
|
|
190
|
+
properties: {
|
|
191
|
+
path: {
|
|
192
|
+
type: 'string',
|
|
193
|
+
description: 'Starting directory (default: current directory)',
|
|
194
|
+
},
|
|
195
|
+
markers: {
|
|
196
|
+
type: 'array',
|
|
197
|
+
items: { type: 'string' },
|
|
198
|
+
description: 'Marker files to look for (default: common project files)',
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
required: [],
|
|
202
|
+
},
|
|
203
|
+
execute: async (input) => {
|
|
204
|
+
let startPath = input.path ?? '.';
|
|
205
|
+
const markers = input.markers ?? options?.defaultMarkers ?? DEFAULT_MARKERS;
|
|
206
|
+
// Resolve relative paths
|
|
207
|
+
if (options?.baseDir && !startPath.startsWith('/')) {
|
|
208
|
+
startPath = join(options.baseDir, startPath);
|
|
209
|
+
}
|
|
210
|
+
// If maxDepth is set, limit the search
|
|
211
|
+
if (options?.maxDepth !== undefined) {
|
|
212
|
+
const absoluteStart = resolve(startPath);
|
|
213
|
+
let currentPath = absoluteStart;
|
|
214
|
+
let depth = 0;
|
|
215
|
+
while (depth <= options.maxDepth) {
|
|
216
|
+
// Check markers at current depth
|
|
217
|
+
for (const marker of markers) {
|
|
218
|
+
const markerPath = join(currentPath, marker);
|
|
219
|
+
if (await fileExists(markerPath)) {
|
|
220
|
+
const result = {
|
|
221
|
+
root: currentPath,
|
|
222
|
+
foundMarker: marker,
|
|
223
|
+
depth,
|
|
224
|
+
};
|
|
225
|
+
return createSuccessResult(result);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const parentPath = dirname(currentPath);
|
|
229
|
+
if (parentPath === currentPath)
|
|
230
|
+
break;
|
|
231
|
+
currentPath = parentPath;
|
|
232
|
+
depth++;
|
|
233
|
+
}
|
|
234
|
+
return createErrorResult(`No project root found within ${String(options.maxDepth)} directories of ${startPath}`);
|
|
235
|
+
}
|
|
236
|
+
return executeFindProjectRoot({ path: startPath, markers });
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project detection tools
|
|
3
|
+
*/
|
|
4
|
+
export { detectProjectTool, createDetectProjectTool } from './detect.js';
|
|
5
|
+
export { findProjectRootTool, createFindProjectRootTool } from './find-root.js';
|
|
6
|
+
export type { DetectProjectInput, DetectProjectResult, ProjectType, PackageManager, FindProjectRootInput, FindProjectRootResult, } from './types.js';
|