agents-md-gen 0.9.1 → 0.10.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/CHANGELOG.md +13 -0
- package/README.md +8 -6
- package/package.json +5 -1
- package/src/detect.js +56 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
- Detect web frameworks in ecosystems that always reported none:
|
|
6
|
+
Django/FastAPI/Flask (Python), Rails/Sinatra (Ruby), Gin/Echo/Fiber/chi
|
|
7
|
+
(Go), Axum/Actix Web/Rocket/Tauri (Rust).
|
|
8
|
+
- Django projects get `python manage.py runserver` as the dev command and
|
|
9
|
+
`python manage.py test` when pytest isn't set up; Rails apps get
|
|
10
|
+
`bin/rails server` and `bin/rails test`.
|
|
11
|
+
- Python commands are prefixed with `pipenv run` for pipenv projects (like
|
|
12
|
+
`poetry run`/`uv run` already were).
|
|
13
|
+
- README: the Scoop install command pointed at a private repo and 404'd;
|
|
14
|
+
it now adds the public `Peshino2012/homebrew-tap` bucket.
|
|
15
|
+
|
|
3
16
|
## 0.9.1
|
|
4
17
|
|
|
5
18
|
- README: the GitHub Action moved to `Peshino2012/homebrew-tap/action`,
|
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ on immediately.
|
|
|
14
14
|
On Windows, you can also install it with [Scoop](https://scoop.sh):
|
|
15
15
|
|
|
16
16
|
```
|
|
17
|
-
scoop
|
|
17
|
+
scoop bucket add peshino2012 https://github.com/Peshino2012/homebrew-tap
|
|
18
|
+
scoop install peshino2012/agents-md-gen
|
|
18
19
|
```
|
|
19
20
|
|
|
20
21
|
On macOS/Linux, install it with [Homebrew](https://brew.sh):
|
|
@@ -55,10 +56,11 @@ docker run --rm -v "$PWD":/workdir ghcr.io/peshino2012/agents-md-gen
|
|
|
55
56
|
- **Node.js / TypeScript** — package manager (npm/yarn/pnpm/bun), framework
|
|
56
57
|
(React, Next.js, Vue, Svelte, Express, Fastify, NestJS), test runner
|
|
57
58
|
(Jest, Vitest, Mocha, Ava, node:test)
|
|
58
|
-
- **Python** — package manager (pip/poetry/uv/pipenv),
|
|
59
|
-
|
|
60
|
-
- **
|
|
61
|
-
- **
|
|
59
|
+
- **Python** — package manager (pip/poetry/uv/pipenv), Django/FastAPI/Flask,
|
|
60
|
+
pytest or the Django test runner, ruff
|
|
61
|
+
- **Rust** — Cargo, workspaces, Axum/Actix Web/Rocket/Tauri, clippy
|
|
62
|
+
- **Go** — go.mod, Gin/Echo/Fiber/chi, golangci-lint
|
|
63
|
+
- **Ruby** — Bundler, Rails/Sinatra, RSpec or minitest
|
|
62
64
|
- **Java/Kotlin** — Maven, Gradle (with wrapper detection)
|
|
63
65
|
- **PHP** — Composer, Laravel/Symfony detection, PHPUnit
|
|
64
66
|
- **.NET/C#** — NuGet, xUnit/NUnit/MSTest detection
|
|
@@ -117,7 +119,7 @@ clobber one you already wrote.
|
|
|
117
119
|
`--check` is for CI: it fails the build if `AGENTS.md` doesn't exist or no
|
|
118
120
|
longer matches what the repo would generate, so a stale file (say, a new
|
|
119
121
|
test framework got added but nobody regenerated it) gets caught instead of
|
|
120
|
-
silently rotting. Pair it with the GitHub Action
|
|
122
|
+
silently rotting. Pair it with the GitHub Action above.
|
|
121
123
|
|
|
122
124
|
## Example
|
|
123
125
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-md-gen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Generate a solid AGENTS.md / CLAUDE.md for any repo by detecting its stack, commands, and structure",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"funding": {
|
|
@@ -54,6 +54,10 @@
|
|
|
54
54
|
"scala",
|
|
55
55
|
"zig",
|
|
56
56
|
"julia",
|
|
57
|
+
"django",
|
|
58
|
+
"fastapi",
|
|
59
|
+
"flask",
|
|
60
|
+
"rails",
|
|
57
61
|
"cli",
|
|
58
62
|
"codegen",
|
|
59
63
|
"developer-tools"
|
package/src/detect.js
CHANGED
|
@@ -94,6 +94,17 @@ function detectPython(root) {
|
|
|
94
94
|
const nameMatch = pyproject.match(/^name\s*=\s*"([^"]+)"/m);
|
|
95
95
|
const usesRuff = /\bruff\b/.test(pyproject) || exists(root, 'ruff.toml');
|
|
96
96
|
|
|
97
|
+
const depsText = [pyproject, readText(root, 'requirements.txt'), readText(root, 'Pipfile')]
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.join('\n');
|
|
100
|
+
const hasManagePy = exists(root, 'manage.py');
|
|
101
|
+
const usesDjango = hasManagePy || hasPythonDep(depsText, 'django');
|
|
102
|
+
const frameworks = [
|
|
103
|
+
usesDjango ? 'Django' : null,
|
|
104
|
+
hasPythonDep(depsText, 'fastapi') ? 'FastAPI' : null,
|
|
105
|
+
hasPythonDep(depsText, 'flask') ? 'Flask' : null,
|
|
106
|
+
].filter(Boolean);
|
|
107
|
+
|
|
97
108
|
const installCmd = {
|
|
98
109
|
poetry: 'poetry install',
|
|
99
110
|
uv: 'uv sync',
|
|
@@ -101,38 +112,54 @@ function detectPython(root) {
|
|
|
101
112
|
pip: hasPyproject ? 'pip install -e .' : 'pip install -r requirements.txt',
|
|
102
113
|
}[packageManager];
|
|
103
114
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
115
|
+
const run = { poetry: 'poetry run ', uv: 'uv run ', pipenv: 'pipenv run ' }[packageManager] || '';
|
|
116
|
+
|
|
117
|
+
let testCmd = null;
|
|
118
|
+
if (usesPytest) testCmd = `${run}pytest`;
|
|
119
|
+
else if (hasManagePy) testCmd = `${run}python manage.py test`;
|
|
107
120
|
|
|
108
121
|
return {
|
|
109
122
|
ecosystem: 'Python',
|
|
110
123
|
name: nameMatch ? nameMatch[1] : null,
|
|
111
124
|
description: null,
|
|
112
125
|
packageManager,
|
|
113
|
-
frameworks
|
|
114
|
-
testFramework: usesPytest ? 'pytest' : null,
|
|
126
|
+
frameworks,
|
|
127
|
+
testFramework: usesPytest ? 'pytest' : (hasManagePy ? 'Django test runner' : null),
|
|
115
128
|
commands: {
|
|
116
129
|
install: installCmd,
|
|
117
130
|
build: null,
|
|
118
131
|
test: testCmd,
|
|
119
132
|
lint: usesRuff ? 'ruff check .' : (exists(root, '.flake8') ? 'flake8' : null),
|
|
120
|
-
dev: null,
|
|
133
|
+
dev: hasManagePy ? `${run}python manage.py runserver` : null,
|
|
121
134
|
},
|
|
122
135
|
};
|
|
123
136
|
}
|
|
124
137
|
|
|
138
|
+
// Matches a PyPI requirement for exactly `name` in pyproject/requirements/
|
|
139
|
+
// Pipfile text: `django>=5`, `"Django[argon2]"`, `django = "*"` -- but not
|
|
140
|
+
// `django-environ` or `djangorestframework`.
|
|
141
|
+
function hasPythonDep(text, name) {
|
|
142
|
+
return new RegExp(`(^|["'\\s,\\[])${name}(\\s*[<>=~!\\[;"',]|\\s*$)`, 'im').test(text);
|
|
143
|
+
}
|
|
144
|
+
|
|
125
145
|
function detectRust(root) {
|
|
126
146
|
const cargoToml = readText(root, 'Cargo.toml');
|
|
127
147
|
if (!cargoToml) return null;
|
|
128
148
|
const nameMatch = cargoToml.match(/^name\s*=\s*"([^"]+)"/m);
|
|
129
149
|
const isWorkspace = /^\[workspace\]/m.test(cargoToml);
|
|
150
|
+
const hasCrate = (name) => new RegExp(`^\\s*${name}\\s*=`, 'm').test(cargoToml);
|
|
151
|
+
const rustFrameworks = [
|
|
152
|
+
['axum', 'Axum'],
|
|
153
|
+
['actix-web', 'Actix Web'],
|
|
154
|
+
['rocket', 'Rocket'],
|
|
155
|
+
['tauri', 'Tauri'],
|
|
156
|
+
].filter(([crate]) => hasCrate(crate)).map(([, label]) => label);
|
|
130
157
|
return {
|
|
131
158
|
ecosystem: 'Rust',
|
|
132
159
|
name: nameMatch ? nameMatch[1] : null,
|
|
133
160
|
description: null,
|
|
134
161
|
packageManager: 'cargo',
|
|
135
|
-
frameworks: isWorkspace ? ['Cargo workspace'] : [],
|
|
162
|
+
frameworks: [...(isWorkspace ? ['Cargo workspace'] : []), ...rustFrameworks],
|
|
136
163
|
testFramework: 'cargo test',
|
|
137
164
|
commands: {
|
|
138
165
|
install: 'cargo build',
|
|
@@ -148,12 +175,18 @@ function detectGo(root) {
|
|
|
148
175
|
const goMod = readText(root, 'go.mod');
|
|
149
176
|
if (!goMod) return null;
|
|
150
177
|
const moduleMatch = goMod.match(/^module\s+(\S+)/m);
|
|
178
|
+
const goFrameworks = [
|
|
179
|
+
['github.com/gin-gonic/gin', 'Gin'],
|
|
180
|
+
['github.com/labstack/echo', 'Echo'],
|
|
181
|
+
['github.com/gofiber/fiber', 'Fiber'],
|
|
182
|
+
['github.com/go-chi/chi', 'chi'],
|
|
183
|
+
];
|
|
151
184
|
return {
|
|
152
185
|
ecosystem: 'Go',
|
|
153
186
|
name: moduleMatch ? moduleMatch[1] : null,
|
|
154
187
|
description: null,
|
|
155
188
|
packageManager: 'go modules',
|
|
156
|
-
frameworks: [],
|
|
189
|
+
frameworks: goFrameworks.filter(([mod]) => goMod.includes(mod)).map(([, label]) => label),
|
|
157
190
|
testFramework: 'go test',
|
|
158
191
|
commands: {
|
|
159
192
|
install: 'go mod download',
|
|
@@ -210,21 +243,31 @@ function detectJava(root) {
|
|
|
210
243
|
}
|
|
211
244
|
|
|
212
245
|
function detectRuby(root) {
|
|
213
|
-
|
|
246
|
+
const gemfile = readText(root, 'Gemfile');
|
|
247
|
+
if (gemfile === null) return null;
|
|
214
248
|
const hasRspec = exists(root, '.rspec') || exists(root, 'spec');
|
|
249
|
+
const hasGem = (name) => new RegExp(`^\\s*gem\\s+["']${name}["']`, 'm').test(gemfile);
|
|
250
|
+
const isRails = hasGem('rails') || exists(root, 'bin', 'rails');
|
|
251
|
+
const rails = exists(root, 'bin', 'rails') ? 'bin/rails' : 'bundle exec rails';
|
|
252
|
+
|
|
253
|
+
let testCmd = null;
|
|
254
|
+
if (hasRspec) testCmd = 'bundle exec rspec';
|
|
255
|
+
else if (isRails) testCmd = `${rails} test`;
|
|
256
|
+
else if (exists(root, 'Rakefile')) testCmd = 'bundle exec rake test';
|
|
257
|
+
|
|
215
258
|
return {
|
|
216
259
|
ecosystem: 'Ruby',
|
|
217
260
|
name: null,
|
|
218
261
|
description: null,
|
|
219
262
|
packageManager: 'bundler',
|
|
220
|
-
frameworks: [],
|
|
221
|
-
testFramework: hasRspec ? 'rspec' : null,
|
|
263
|
+
frameworks: [isRails ? 'Rails' : null, hasGem('sinatra') ? 'Sinatra' : null].filter(Boolean),
|
|
264
|
+
testFramework: hasRspec ? 'rspec' : (isRails ? 'minitest' : null),
|
|
222
265
|
commands: {
|
|
223
266
|
install: 'bundle install',
|
|
224
267
|
build: null,
|
|
225
|
-
test:
|
|
268
|
+
test: testCmd,
|
|
226
269
|
lint: exists(root, '.rubocop.yml') ? 'bundle exec rubocop' : null,
|
|
227
|
-
dev: null,
|
|
270
|
+
dev: isRails ? `${rails} server` : null,
|
|
228
271
|
},
|
|
229
272
|
};
|
|
230
273
|
}
|