agents-md-gen 0.2.0 → 0.4.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 CHANGED
@@ -21,6 +21,8 @@ on immediately.
21
21
  - **Go** — go.mod, golangci-lint
22
22
  - **Ruby** — Bundler, RSpec
23
23
  - **Java/Kotlin** — Maven, Gradle (with wrapper detection)
24
+ - **Monorepos** — Turborepo, Nx, Lerna, npm/yarn/pnpm workspaces, listing
25
+ each detected sub-package
24
26
  - CI (GitHub Actions), license, and top-level project structure
25
27
 
26
28
  ## GitHub Action
@@ -47,12 +49,18 @@ npx agents-md-gen ./some/repo # target a different directory
47
49
  npx agents-md-gen --also-claude-md
48
50
  npx agents-md-gen --dry-run # print instead of writing
49
51
  npx agents-md-gen --force # overwrite an existing file
52
+ npx agents-md-gen --check # exit 1 if missing/stale, writes nothing (CI)
50
53
  ```
51
54
 
52
55
  It won't overwrite an existing `AGENTS.md`/`CLAUDE.md` unless you pass
53
56
  `--force` — this is meant to bootstrap a file you then edit by hand, not to
54
57
  clobber one you already wrote.
55
58
 
59
+ `--check` is for CI: it fails the build if `AGENTS.md` doesn't exist or no
60
+ longer matches what the repo would generate, so a stale file (say, a new
61
+ test framework got added but nobody regenerated it) gets caught instead of
62
+ silently rotting. Pair it with the GitHub Action below.
63
+
56
64
  ## Example
57
65
 
58
66
  Real output — this is what `npx agents-md-gen` produces when run against
package/bin/cli.js CHANGED
@@ -11,6 +11,7 @@ function main() {
11
11
  'also-claude-md': { type: 'boolean', default: false },
12
12
  force: { type: 'boolean', short: 'f', default: false },
13
13
  'dry-run': { type: 'boolean', default: false },
14
+ check: { type: 'boolean', default: false },
14
15
  help: { type: 'boolean', short: 'h', default: false },
15
16
  },
16
17
  allowPositionals: true,
@@ -26,6 +27,7 @@ Options:
26
27
  --also-claude-md Also write an identical CLAUDE.md
27
28
  -f, --force Overwrite existing file(s)
28
29
  --dry-run Print to stdout instead of writing
30
+ --check Exit 1 if the file is missing or out of date (CI use, writes nothing)
29
31
  -h, --help Show this help
30
32
  `);
31
33
  return;
@@ -33,6 +35,25 @@ Options:
33
35
 
34
36
  const cwd = positionals[0] ? path.resolve(positionals[0]) : process.cwd();
35
37
 
38
+ if (values.check) {
39
+ const { upToDate, existed, target } = run({
40
+ cwd,
41
+ output: values.output,
42
+ check: true,
43
+ });
44
+ if (upToDate) {
45
+ console.log(`${target} is up to date`);
46
+ return;
47
+ }
48
+ console.error(
49
+ existed
50
+ ? `${target} is out of date - run agents-md-gen --force to update it`
51
+ : `${target} does not exist - run agents-md-gen to create it`
52
+ );
53
+ process.exitCode = 1;
54
+ return;
55
+ }
56
+
36
57
  const { written, skipped } = run({
37
58
  cwd,
38
59
  output: values.output,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-md-gen",
3
- "version": "0.2.0",
3
+ "version": "0.4.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
  "bin": {
@@ -29,6 +29,9 @@
29
29
  "java",
30
30
  "maven",
31
31
  "gradle",
32
+ "monorepo",
33
+ "turborepo",
34
+ "nx",
32
35
  "cli",
33
36
  "codegen",
34
37
  "developer-tools"
package/src/detect.js CHANGED
@@ -229,6 +229,11 @@ function detectRuby(root) {
229
229
  };
230
230
  }
231
231
 
232
+ // Excluded so re-running the generator doesn't see its own prior output as
233
+ // part of the repo's structure - otherwise the file it just wrote would
234
+ // never match a freshly rendered one, breaking `--check`.
235
+ const OWN_OUTPUT_FILES = new Set(['AGENTS.md', 'CLAUDE.md']);
236
+
232
237
  function listStructure(root) {
233
238
  let entries;
234
239
  try {
@@ -239,6 +244,7 @@ function listStructure(root) {
239
244
  return entries
240
245
  .filter((e) => !e.name.startsWith('.') || e.name === '.github')
241
246
  .filter((e) => !IGNORE_DIRS.has(e.name))
247
+ .filter((e) => !OWN_OUTPUT_FILES.has(e.name))
242
248
  .map((e) => (e.isDirectory() ? `${e.name}/` : e.name))
243
249
  .sort();
244
250
  }
@@ -266,6 +272,50 @@ function detectLicense(root) {
266
272
  return null;
267
273
  }
268
274
 
275
+ function detectMonorepo(root) {
276
+ const pkg = readJson(root, 'package.json');
277
+ const patterns = [];
278
+
279
+ if (pkg && Array.isArray(pkg.workspaces)) {
280
+ patterns.push(...pkg.workspaces);
281
+ } else if (pkg && pkg.workspaces && Array.isArray(pkg.workspaces.packages)) {
282
+ patterns.push(...pkg.workspaces.packages);
283
+ }
284
+
285
+ const pnpmWorkspace = readText(root, 'pnpm-workspace.yaml');
286
+ if (pnpmWorkspace) {
287
+ for (const m of pnpmWorkspace.matchAll(/^\s*-\s*['"]?([^'"\n]+)['"]?\s*$/gm)) {
288
+ patterns.push(m[1].trim());
289
+ }
290
+ }
291
+
292
+ const tool = exists(root, 'turbo.json') ? 'Turborepo'
293
+ : exists(root, 'nx.json') ? 'Nx'
294
+ : exists(root, 'lerna.json') ? 'Lerna'
295
+ : patterns.length ? 'workspaces'
296
+ : null;
297
+
298
+ if (!tool) return null;
299
+
300
+ const packages = new Set();
301
+ for (const pattern of patterns) {
302
+ const dirPath = path.join(root, pattern.replace(/\/\*+$/, ''));
303
+ let entries;
304
+ try {
305
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
306
+ } catch {
307
+ continue;
308
+ }
309
+ for (const e of entries) {
310
+ if (!e.isDirectory()) continue;
311
+ const subPkg = readJson(dirPath, e.name, 'package.json');
312
+ if (subPkg) packages.add(subPkg.name || e.name);
313
+ }
314
+ }
315
+
316
+ return { tool, packages: [...packages].sort() };
317
+ }
318
+
269
319
  function detectProject(root) {
270
320
  const detectors = [detectNode, detectPython, detectRust, detectGo, detectRuby, detectJava];
271
321
  const stacks = detectors.map((fn) => fn(root)).filter(Boolean);
@@ -273,6 +323,7 @@ function detectProject(root) {
273
323
  return {
274
324
  root,
275
325
  stacks,
326
+ monorepo: detectMonorepo(root),
276
327
  structure: listStructure(root),
277
328
  ci: detectCI(root),
278
329
  license: detectLicense(root),
package/src/index.js CHANGED
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const { detectProject } = require('./detect');
5
5
  const { renderAgentsMd } = require('./render');
6
6
 
7
- function run({ cwd, output, alsoClaudeMd, force, dryRun }) {
7
+ function run({ cwd, output, alsoClaudeMd, force, dryRun, check }) {
8
8
  const project = detectProject(cwd);
9
9
  const content = renderAgentsMd(project);
10
10
 
@@ -13,6 +13,13 @@ function run({ cwd, output, alsoClaudeMd, force, dryRun }) {
13
13
  return { written: [], skipped: [] };
14
14
  }
15
15
 
16
+ if (check) {
17
+ const fullPath = path.join(cwd, output);
18
+ const existing = fs.existsSync(fullPath) ? fs.readFileSync(fullPath, 'utf8') : null;
19
+ const upToDate = existing === content;
20
+ return { upToDate, existed: existing !== null, target: output };
21
+ }
22
+
16
23
  const targets = [output];
17
24
  if (alsoClaudeMd && !targets.includes('CLAUDE.md')) targets.push('CLAUDE.md');
18
25
 
package/src/render.js CHANGED
@@ -51,6 +51,16 @@ function renderStack(stack) {
51
51
  return parts.join('\n');
52
52
  }
53
53
 
54
+ function renderMonorepo(monorepo) {
55
+ if (!monorepo) return null;
56
+ const lines = [`This is a monorepo (${monorepo.tool}).`];
57
+ if (monorepo.packages.length) {
58
+ lines.push('', 'Packages:', '');
59
+ lines.push(...monorepo.packages.map((p) => `- \`${p}\``));
60
+ }
61
+ return ['## Monorepo', '', ...lines].join('\n');
62
+ }
63
+
54
64
  function renderStructure(structure) {
55
65
  if (!structure.length) return null;
56
66
  const lines = structure.map((entry) => {
@@ -87,6 +97,9 @@ function renderAgentsMd(project) {
87
97
  }
88
98
  }
89
99
 
100
+ const monorepo = renderMonorepo(project.monorepo);
101
+ if (monorepo) sections.push('', monorepo);
102
+
90
103
  const structure = renderStructure(project.structure);
91
104
  if (structure) sections.push('', structure);
92
105