agents-md-gen 0.10.0 → 0.12.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 CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0
4
+
5
+ - New **Typecheck** and **Format** commands in the generated file — what an
6
+ agent should run before committing. Only emitted with a clear signal:
7
+ package.json `typecheck`/`type-check`/`tsc` and `format`/`fmt` scripts;
8
+ ruff or black, mypy or pyright config (Python); `cargo check`/`cargo fmt`
9
+ (Rust); `gofmt` (Go); `deno fmt` and deno tasks; `dart format`;
10
+ `mix format`.
11
+ - Fix: the Python lint command lacked the `poetry run`/`uv run`/`pipenv run`
12
+ prefix the other commands had, so it failed when ruff/flake8 only lived in
13
+ the project's virtualenv.
14
+ - README: pin a version when using `--check` in CI.
15
+
16
+ ## 0.11.0
17
+
18
+ - Fix: Bun projects using the text `bun.lock` (the default since Bun 1.2)
19
+ were reported as npm. Both `bun.lock` and `bun.lockb` count now.
20
+ - Honor corepack's `packageManager` field in package.json over lockfiles.
21
+ - More JS frameworks: Remix, Expo, React Native, Nuxt, SvelteKit, Astro,
22
+ Angular, Hono, Electron; meta-frameworks replace the library they wrap
23
+ (Nuxt instead of Vue, SvelteKit instead of Svelte). Playwright as a test
24
+ runner.
25
+ - Detect Deno projects (`deno.json`/`deno.jsonc`, its tasks, Fresh/Hono).
26
+ - A lone `Pipfile` (no lock yet) is detected as a pipenv Python project.
27
+ - `homepage` points at the public distribution repo; it used to fall back
28
+ to the private source repo, which 404s for visitors.
29
+ - The README's "real output" example is now checked against the actual
30
+ CLI output by the test suite, so it can't drift again.
31
+
3
32
  ## 0.10.0
4
33
 
5
34
  - Detect web frameworks in ecosystems that always reported none:
package/README.md CHANGED
@@ -53,9 +53,12 @@ docker run --rm -v "$PWD":/workdir ghcr.io/peshino2012/agents-md-gen
53
53
 
54
54
  ## What it detects
55
55
 
56
- - **Node.js / TypeScript** — package manager (npm/yarn/pnpm/bun), framework
57
- (React, Next.js, Vue, Svelte, Express, Fastify, NestJS), test runner
58
- (Jest, Vitest, Mocha, Ava, node:test)
56
+ - **Node.js / TypeScript** — package manager (npm/yarn/pnpm/bun, honoring the
57
+ `packageManager` field), framework (Next.js, Remix, React, Expo/React
58
+ Native, Nuxt, Vue, SvelteKit, Svelte, Astro, Angular, Express, Fastify,
59
+ NestJS, Hono, Electron), test runner (Vitest, Jest, Mocha, Ava,
60
+ node:test, Playwright)
61
+ - **Deno** — `deno.json`/`deno.jsonc` tasks, Fresh/Hono
59
62
  - **Python** — package manager (pip/poetry/uv/pipenv), Django/FastAPI/Flask,
60
63
  pytest or the Django test runner, ruff
61
64
  - **Rust** — Cargo, workspaces, Axum/Actix Web/Rocket/Tauri, clippy
@@ -121,6 +124,11 @@ longer matches what the repo would generate, so a stale file (say, a new
121
124
  test framework got added but nobody regenerated it) gets caught instead of
122
125
  silently rotting. Pair it with the GitHub Action above.
123
126
 
127
+ New releases detect more things, which changes the generated file — so with
128
+ `--check`, pin the version you generated with (`npx agents-md-gen@0.12.0
129
+ --check`, or the action's `version` input) and bump it on purpose, rather
130
+ than letting `@latest` fail CI the day a release lands.
131
+
124
132
  ## Example
125
133
 
126
134
  Real output — this is what `npx agents-md-gen` produces when run against
@@ -141,7 +149,6 @@ this package's own repo:
141
149
 
142
150
  ## Structure
143
151
 
144
- - `.github/` — GitHub config (CI, templates)
145
152
  - `CHANGELOG.md`
146
153
  - `Dockerfile`
147
154
  - `LICENSE`
@@ -153,7 +160,6 @@ this package's own repo:
153
160
 
154
161
  ## Notes
155
162
 
156
- - CI: GitHub Actions (test.yml)
157
163
  - License: MIT
158
164
  - Docker: Dockerfile
159
165
  ````
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "agents-md-gen",
3
- "version": "0.10.0",
3
+ "version": "0.12.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
+ "homepage": "https://github.com/Peshino2012/homebrew-tap#readme",
6
7
  "funding": {
7
8
  "type": "individual",
8
9
  "url": "https://www.npmjs.com/package/agents-md-gen"
@@ -58,6 +59,11 @@
58
59
  "fastapi",
59
60
  "flask",
60
61
  "rails",
62
+ "deno",
63
+ "bun",
64
+ "nuxt",
65
+ "sveltekit",
66
+ "astro",
61
67
  "cli",
62
68
  "codegen",
63
69
  "developer-tools"
package/src/detect.js CHANGED
@@ -31,30 +31,49 @@ function detectNode(root) {
31
31
  const pkg = readJson(root, 'package.json');
32
32
  if (!pkg) return null;
33
33
 
34
+ // corepack's `packageManager` field ("pnpm@9.12.0") is explicit, so it wins
35
+ // over lockfile sniffing -- and it's the only signal when no lockfile is
36
+ // committed.
37
+ const declared = typeof pkg.packageManager === 'string' ? pkg.packageManager.split('@')[0] : null;
34
38
  let packageManager = 'npm';
35
- if (exists(root, 'pnpm-lock.yaml')) packageManager = 'pnpm';
39
+ if (['npm', 'pnpm', 'yarn', 'bun'].includes(declared)) packageManager = declared;
40
+ else if (exists(root, 'pnpm-lock.yaml')) packageManager = 'pnpm';
36
41
  else if (exists(root, 'yarn.lock')) packageManager = 'yarn';
37
- else if (exists(root, 'bun.lockb')) packageManager = 'bun';
42
+ else if (exists(root, 'bun.lock') || exists(root, 'bun.lockb')) packageManager = 'bun';
38
43
 
39
44
  const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
40
45
  const has = (name) => Object.prototype.hasOwnProperty.call(deps, name);
41
46
 
42
- const frameworks = [];
43
- if (has('next')) frameworks.push('Next.js');
44
- if (has('react') && !has('next')) frameworks.push('React');
45
- if (has('vue')) frameworks.push('Vue');
46
- if (has('svelte')) frameworks.push('Svelte');
47
- if (has('express')) frameworks.push('Express');
48
- if (has('fastify')) frameworks.push('Fastify');
49
- if (has('@nestjs/core')) frameworks.push('NestJS');
50
- if (has('vite')) frameworks.push('Vite');
51
- if (has('typescript')) frameworks.push('TypeScript');
47
+ // Meta-frameworks are listed instead of the library they're built on
48
+ // (Nuxt, not Vue; SvelteKit, not Svelte).
49
+ const isRemix = has('@remix-run/react') || has('@remix-run/node');
50
+ const frameworks = [
51
+ has('next') && 'Next.js',
52
+ isRemix && 'Remix',
53
+ has('expo') && 'Expo',
54
+ has('react-native') && !has('expo') && 'React Native',
55
+ has('react') && !has('next') && !isRemix && !has('react-native') && 'React',
56
+ has('nuxt') && 'Nuxt',
57
+ has('vue') && !has('nuxt') && 'Vue',
58
+ has('@sveltejs/kit') && 'SvelteKit',
59
+ has('svelte') && !has('@sveltejs/kit') && 'Svelte',
60
+ has('astro') && 'Astro',
61
+ has('@angular/core') && 'Angular',
62
+ has('express') && 'Express',
63
+ has('fastify') && 'Fastify',
64
+ has('@nestjs/core') && 'NestJS',
65
+ has('hono') && 'Hono',
66
+ has('electron') && 'Electron',
67
+ has('vite') && 'Vite',
68
+ has('typescript') && 'TypeScript',
69
+ ].filter(Boolean);
52
70
 
53
71
  const testFramework = has('vitest') ? 'vitest'
54
72
  : has('jest') ? 'jest'
55
73
  : has('mocha') ? 'mocha'
56
74
  : has('ava') ? 'ava'
57
75
  : (pkg.scripts && pkg.scripts.test && pkg.scripts.test.includes('node --test')) ? 'node:test'
76
+ : has('@playwright/test') ? 'playwright'
58
77
  : null;
59
78
 
60
79
  const run = (script) => (pkg.scripts && pkg.scripts[script]) ? `${packageManager} run ${script}` : null;
@@ -65,6 +84,8 @@ function detectNode(root) {
65
84
  test: run('test'),
66
85
  lint: run('lint'),
67
86
  dev: run('dev') || run('start'),
87
+ typecheck: run('typecheck') || run('type-check') || run('tsc'),
88
+ format: run('format') || run('fmt'),
68
89
  };
69
90
 
70
91
  return {
@@ -78,16 +99,47 @@ function detectNode(root) {
78
99
  };
79
100
  }
80
101
 
102
+ function detectDeno(root) {
103
+ const file = ['deno.json', 'deno.jsonc'].find((f) => exists(root, f));
104
+ if (!file) return null;
105
+ const config = readJsonWithComments(root, file) || {};
106
+ const tasks = config.tasks || {};
107
+ const task = (name) => (tasks[name] ? `deno task ${name}` : null);
108
+ const text = readText(root, file) || '';
109
+
110
+ return {
111
+ ecosystem: 'Deno',
112
+ name: config.name || null,
113
+ description: null,
114
+ packageManager: 'deno',
115
+ frameworks: [
116
+ /\$fresh\/|@fresh\/core/.test(text) && 'Fresh',
117
+ /\bhono\b/.test(text) && 'Hono',
118
+ ].filter(Boolean),
119
+ testFramework: 'deno test',
120
+ commands: {
121
+ install: null,
122
+ build: task('build'),
123
+ test: task('test') || 'deno test',
124
+ lint: task('lint') || 'deno lint',
125
+ dev: task('dev') || task('start'),
126
+ typecheck: task('check') || task('typecheck'),
127
+ format: task('fmt') || task('format') || 'deno fmt',
128
+ },
129
+ };
130
+ }
131
+
81
132
  function detectPython(root) {
82
133
  const hasPyproject = exists(root, 'pyproject.toml');
83
134
  const hasRequirements = exists(root, 'requirements.txt');
84
135
  const hasSetupPy = exists(root, 'setup.py');
85
- if (!hasPyproject && !hasRequirements && !hasSetupPy) return null;
136
+ const hasPipfile = exists(root, 'Pipfile');
137
+ if (!hasPyproject && !hasRequirements && !hasSetupPy && !hasPipfile) return null;
86
138
 
87
139
  let packageManager = 'pip';
88
140
  if (exists(root, 'poetry.lock')) packageManager = 'poetry';
89
141
  else if (exists(root, 'uv.lock')) packageManager = 'uv';
90
- else if (exists(root, 'Pipfile.lock')) packageManager = 'pipenv';
142
+ else if (hasPipfile) packageManager = 'pipenv';
91
143
 
92
144
  const pyproject = readText(root, 'pyproject.toml') || '';
93
145
  const usesPytest = exists(root, 'pytest.ini') || exists(root, 'conftest.py') || /pytest/.test(pyproject);
@@ -114,6 +166,16 @@ function detectPython(root) {
114
166
 
115
167
  const run = { poetry: 'poetry run ', uv: 'uv run ', pipenv: 'pipenv run ' }[packageManager] || '';
116
168
 
169
+ const usesBlack = /^\[tool\.black\]/m.test(pyproject) || hasPythonDep(depsText, 'black');
170
+ const usesMypy = /^\[tool\.mypy\]/m.test(pyproject) || exists(root, 'mypy.ini') || hasPythonDep(depsText, 'mypy');
171
+ const usesPyright = /^\[tool\.pyright\]/m.test(pyproject) || exists(root, 'pyrightconfig.json');
172
+ let formatCmd = null;
173
+ if (usesRuff) formatCmd = `${run}ruff format .`;
174
+ else if (usesBlack) formatCmd = `${run}black .`;
175
+ let typecheckCmd = null;
176
+ if (usesMypy) typecheckCmd = `${run}mypy .`;
177
+ else if (usesPyright) typecheckCmd = `${run}pyright`;
178
+
117
179
  let testCmd = null;
118
180
  if (usesPytest) testCmd = `${run}pytest`;
119
181
  else if (hasManagePy) testCmd = `${run}python manage.py test`;
@@ -129,8 +191,10 @@ function detectPython(root) {
129
191
  install: installCmd,
130
192
  build: null,
131
193
  test: testCmd,
132
- lint: usesRuff ? 'ruff check .' : (exists(root, '.flake8') ? 'flake8' : null),
194
+ lint: usesRuff ? `${run}ruff check .` : (exists(root, '.flake8') ? `${run}flake8` : null),
133
195
  dev: hasManagePy ? `${run}python manage.py runserver` : null,
196
+ typecheck: typecheckCmd,
197
+ format: formatCmd,
134
198
  },
135
199
  };
136
200
  }
@@ -167,6 +231,8 @@ function detectRust(root) {
167
231
  test: 'cargo test',
168
232
  lint: 'cargo clippy',
169
233
  dev: null,
234
+ typecheck: 'cargo check',
235
+ format: 'cargo fmt',
170
236
  },
171
237
  };
172
238
  }
@@ -194,6 +260,7 @@ function detectGo(root) {
194
260
  test: 'go test ./...',
195
261
  lint: (exists(root, '.golangci.yml') || exists(root, '.golangci.yaml')) ? 'golangci-lint run' : 'go vet ./...',
196
262
  dev: null,
263
+ format: 'gofmt -w .',
197
264
  },
198
265
  };
199
266
  }
@@ -413,6 +480,7 @@ function detectDart(root) {
413
480
  test: isFlutter ? 'flutter test' : 'dart test',
414
481
  lint: 'dart analyze',
415
482
  dev: isFlutter ? 'flutter run' : null,
483
+ format: 'dart format .',
416
484
  },
417
485
  };
418
486
  }
@@ -435,6 +503,7 @@ function detectElixir(root) {
435
503
  test: exists(root, 'test') ? 'mix test' : null,
436
504
  lint: exists(root, '.credo.exs') ? 'mix credo' : null,
437
505
  dev: null,
506
+ format: 'mix format',
438
507
  },
439
508
  };
440
509
  }
@@ -643,7 +712,7 @@ function detectMonorepo(root) {
643
712
  }
644
713
 
645
714
  function detectProject(root) {
646
- const detectors = [detectNode, detectPython, detectRust, detectGo, detectRuby, detectJava, detectPhp, detectDotnet, detectSwift, detectDart, detectElixir, detectCpp, detectHaskell, detectScala, detectZig, detectJulia];
715
+ const detectors = [detectNode, detectDeno, detectPython, detectRust, detectGo, detectRuby, detectJava, detectPhp, detectDotnet, detectSwift, detectDart, detectElixir, detectCpp, detectHaskell, detectScala, detectZig, detectJulia];
647
716
  const stacks = detectors.map((fn) => fn(root)).filter(Boolean);
648
717
 
649
718
  return {
package/src/render.js CHANGED
@@ -27,6 +27,8 @@ function renderCommands(stack) {
27
27
  if (c.build) lines.push(`- Build: \`${c.build}\``);
28
28
  if (c.test) lines.push(`- Test: \`${c.test}\``);
29
29
  if (c.lint) lines.push(`- Lint: \`${c.lint}\``);
30
+ if (c.typecheck) lines.push(`- Typecheck: \`${c.typecheck}\``);
31
+ if (c.format) lines.push(`- Format: \`${c.format}\``);
30
32
  return lines;
31
33
  }
32
34