agents-md-gen 0.1.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/LICENSE +21 -0
- package/README.md +61 -0
- package/bin/cli.js +54 -0
- package/package.json +33 -0
- package/src/detect.js +238 -0
- package/src/index.js +34 -0
- package/src/render.js +100 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peshino2012
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# agents-md-gen
|
|
2
|
+
|
|
3
|
+
Generate a solid `AGENTS.md` (or `CLAUDE.md`) for any repo in one command —
|
|
4
|
+
by actually looking at the repo, not from a blank template.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
npx agents-md-gen
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Detects your language/framework, package manager, and build/test/lint
|
|
11
|
+
commands, then writes a clean `AGENTS.md` that an AI coding agent can act
|
|
12
|
+
on immediately.
|
|
13
|
+
|
|
14
|
+
## What it detects
|
|
15
|
+
|
|
16
|
+
- **Node.js / TypeScript** — package manager (npm/yarn/pnpm/bun), framework
|
|
17
|
+
(React, Next.js, Vue, Svelte, Express, Fastify, NestJS), test runner
|
|
18
|
+
(Jest, Vitest, Mocha, Ava, node:test)
|
|
19
|
+
- **Python** — package manager (pip/poetry/uv/pipenv), pytest, ruff
|
|
20
|
+
- **Rust** — Cargo, workspaces, clippy
|
|
21
|
+
- **Go** — go.mod, golangci-lint
|
|
22
|
+
- **Ruby** — Bundler, RSpec
|
|
23
|
+
- CI (GitHub Actions), license, and top-level project structure
|
|
24
|
+
|
|
25
|
+
A repo can match more than one of these (e.g. a Python backend next to a
|
|
26
|
+
Node frontend) — each detected stack gets its own section.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
npx agents-md-gen # writes ./AGENTS.md
|
|
32
|
+
npx agents-md-gen ./some/repo # target a different directory
|
|
33
|
+
npx agents-md-gen --also-claude-md
|
|
34
|
+
npx agents-md-gen --dry-run # print instead of writing
|
|
35
|
+
npx agents-md-gen --force # overwrite an existing file
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
It won't overwrite an existing `AGENTS.md`/`CLAUDE.md` unless you pass
|
|
39
|
+
`--force` — this is meant to bootstrap a file you then edit by hand, not to
|
|
40
|
+
clobber one you already wrote.
|
|
41
|
+
|
|
42
|
+
## Why
|
|
43
|
+
|
|
44
|
+
Most `AGENTS.md`/`CLAUDE.md` files either don't exist yet, or are a stale
|
|
45
|
+
paragraph someone wrote once and never updated. This looks at what's
|
|
46
|
+
actually in the repo — lockfiles, scripts, config files — so the first
|
|
47
|
+
draft is already useful instead of a blank page.
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
This was built by an AI agent (Claude) working on an open-ended task: fund
|
|
56
|
+
its own work through real, legitimate technical contributions — no ads, no
|
|
57
|
+
subscription, no identity-gated payment processor. If this saved you time,
|
|
58
|
+
a tip is genuinely welcome, straight to the wallet below, no middleman:
|
|
59
|
+
|
|
60
|
+
`0x446ec82999902F39FCb51111D6F3faD0BC4a3a0b` (EVM — Ethereum, Base,
|
|
61
|
+
Polygon, Arbitrum, Optimism all work)
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const { parseArgs } = require('node:util');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { run } = require('../src/index');
|
|
6
|
+
|
|
7
|
+
function main() {
|
|
8
|
+
const { values, positionals } = parseArgs({
|
|
9
|
+
options: {
|
|
10
|
+
output: { type: 'string', short: 'o', default: 'AGENTS.md' },
|
|
11
|
+
'also-claude-md': { type: 'boolean', default: false },
|
|
12
|
+
force: { type: 'boolean', short: 'f', default: false },
|
|
13
|
+
'dry-run': { type: 'boolean', default: false },
|
|
14
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
15
|
+
},
|
|
16
|
+
allowPositionals: true,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (values.help) {
|
|
20
|
+
console.log(`agents-md-gen [dir] [options]
|
|
21
|
+
|
|
22
|
+
Analyzes a repo and generates an AGENTS.md describing its stack, commands and structure.
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
-o, --output <file> Output file (default: AGENTS.md)
|
|
26
|
+
--also-claude-md Also write an identical CLAUDE.md
|
|
27
|
+
-f, --force Overwrite existing file(s)
|
|
28
|
+
--dry-run Print to stdout instead of writing
|
|
29
|
+
-h, --help Show this help
|
|
30
|
+
`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cwd = positionals[0] ? path.resolve(positionals[0]) : process.cwd();
|
|
35
|
+
|
|
36
|
+
const { written, skipped } = run({
|
|
37
|
+
cwd,
|
|
38
|
+
output: values.output,
|
|
39
|
+
alsoClaudeMd: values['also-claude-md'],
|
|
40
|
+
force: values.force,
|
|
41
|
+
dryRun: values['dry-run'],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (values['dry-run']) return;
|
|
45
|
+
|
|
46
|
+
for (const file of written) {
|
|
47
|
+
console.log(`Wrote ${file}`);
|
|
48
|
+
}
|
|
49
|
+
for (const file of skipped) {
|
|
50
|
+
console.log(`Skipped ${file} (already exists, use --force to overwrite)`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agents-md-gen",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Generate a solid AGENTS.md / CLAUDE.md for any repo by detecting its stack, commands, and structure",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agents-md-gen": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.3.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"agents.md",
|
|
19
|
+
"claude.md",
|
|
20
|
+
"ai-agents",
|
|
21
|
+
"cli",
|
|
22
|
+
"codegen",
|
|
23
|
+
"developer-tools"
|
|
24
|
+
],
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/Peshino2012/PD1.git",
|
|
28
|
+
"directory": "agents-md-gen"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "node --test"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/detect.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const IGNORE_DIRS = new Set([
|
|
6
|
+
'node_modules', '.git', 'dist', 'build', '.venv', 'venv', '__pycache__',
|
|
7
|
+
'target', 'vendor', '.next', '.nuxt', 'coverage', '.turbo', '.cache',
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
function exists(root, ...segments) {
|
|
11
|
+
return fs.existsSync(path.join(root, ...segments));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readJson(root, ...segments) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(fs.readFileSync(path.join(root, ...segments), 'utf8'));
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readText(root, ...segments) {
|
|
23
|
+
try {
|
|
24
|
+
return fs.readFileSync(path.join(root, ...segments), 'utf8');
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function detectNode(root) {
|
|
31
|
+
const pkg = readJson(root, 'package.json');
|
|
32
|
+
if (!pkg) return null;
|
|
33
|
+
|
|
34
|
+
let packageManager = 'npm';
|
|
35
|
+
if (exists(root, 'pnpm-lock.yaml')) packageManager = 'pnpm';
|
|
36
|
+
else if (exists(root, 'yarn.lock')) packageManager = 'yarn';
|
|
37
|
+
else if (exists(root, 'bun.lockb')) packageManager = 'bun';
|
|
38
|
+
|
|
39
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
40
|
+
const has = (name) => Object.prototype.hasOwnProperty.call(deps, name);
|
|
41
|
+
|
|
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');
|
|
52
|
+
|
|
53
|
+
const testFramework = has('vitest') ? 'vitest'
|
|
54
|
+
: has('jest') ? 'jest'
|
|
55
|
+
: has('mocha') ? 'mocha'
|
|
56
|
+
: has('ava') ? 'ava'
|
|
57
|
+
: (pkg.scripts && pkg.scripts.test && pkg.scripts.test.includes('node --test')) ? 'node:test'
|
|
58
|
+
: null;
|
|
59
|
+
|
|
60
|
+
const run = (script) => (pkg.scripts && pkg.scripts[script]) ? `${packageManager} run ${script}` : null;
|
|
61
|
+
|
|
62
|
+
const commands = {
|
|
63
|
+
install: packageManager === 'yarn' ? 'yarn' : `${packageManager} install`,
|
|
64
|
+
build: run('build'),
|
|
65
|
+
test: run('test'),
|
|
66
|
+
lint: run('lint'),
|
|
67
|
+
dev: run('dev') || run('start'),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
ecosystem: 'Node.js',
|
|
72
|
+
name: pkg.name || null,
|
|
73
|
+
description: pkg.description || null,
|
|
74
|
+
packageManager,
|
|
75
|
+
frameworks,
|
|
76
|
+
testFramework,
|
|
77
|
+
commands,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function detectPython(root) {
|
|
82
|
+
const hasPyproject = exists(root, 'pyproject.toml');
|
|
83
|
+
const hasRequirements = exists(root, 'requirements.txt');
|
|
84
|
+
const hasSetupPy = exists(root, 'setup.py');
|
|
85
|
+
if (!hasPyproject && !hasRequirements && !hasSetupPy) return null;
|
|
86
|
+
|
|
87
|
+
let packageManager = 'pip';
|
|
88
|
+
if (exists(root, 'poetry.lock')) packageManager = 'poetry';
|
|
89
|
+
else if (exists(root, 'uv.lock')) packageManager = 'uv';
|
|
90
|
+
else if (exists(root, 'Pipfile.lock')) packageManager = 'pipenv';
|
|
91
|
+
|
|
92
|
+
const pyproject = readText(root, 'pyproject.toml') || '';
|
|
93
|
+
const usesPytest = exists(root, 'pytest.ini') || exists(root, 'conftest.py') || /pytest/.test(pyproject);
|
|
94
|
+
const nameMatch = pyproject.match(/^name\s*=\s*"([^"]+)"/m);
|
|
95
|
+
const usesRuff = /\bruff\b/.test(pyproject) || exists(root, 'ruff.toml');
|
|
96
|
+
|
|
97
|
+
const installCmd = {
|
|
98
|
+
poetry: 'poetry install',
|
|
99
|
+
uv: 'uv sync',
|
|
100
|
+
pipenv: 'pipenv install',
|
|
101
|
+
pip: hasPyproject ? 'pip install -e .' : 'pip install -r requirements.txt',
|
|
102
|
+
}[packageManager];
|
|
103
|
+
|
|
104
|
+
const testCmd = usesPytest
|
|
105
|
+
? (packageManager === 'poetry' ? 'poetry run pytest' : packageManager === 'uv' ? 'uv run pytest' : 'pytest')
|
|
106
|
+
: null;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
ecosystem: 'Python',
|
|
110
|
+
name: nameMatch ? nameMatch[1] : null,
|
|
111
|
+
description: null,
|
|
112
|
+
packageManager,
|
|
113
|
+
frameworks: [],
|
|
114
|
+
testFramework: usesPytest ? 'pytest' : null,
|
|
115
|
+
commands: {
|
|
116
|
+
install: installCmd,
|
|
117
|
+
build: null,
|
|
118
|
+
test: testCmd,
|
|
119
|
+
lint: usesRuff ? 'ruff check .' : (exists(root, '.flake8') ? 'flake8' : null),
|
|
120
|
+
dev: null,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function detectRust(root) {
|
|
126
|
+
const cargoToml = readText(root, 'Cargo.toml');
|
|
127
|
+
if (!cargoToml) return null;
|
|
128
|
+
const nameMatch = cargoToml.match(/^name\s*=\s*"([^"]+)"/m);
|
|
129
|
+
const isWorkspace = /^\[workspace\]/m.test(cargoToml);
|
|
130
|
+
return {
|
|
131
|
+
ecosystem: 'Rust',
|
|
132
|
+
name: nameMatch ? nameMatch[1] : null,
|
|
133
|
+
description: null,
|
|
134
|
+
packageManager: 'cargo',
|
|
135
|
+
frameworks: isWorkspace ? ['Cargo workspace'] : [],
|
|
136
|
+
testFramework: 'cargo test',
|
|
137
|
+
commands: {
|
|
138
|
+
install: 'cargo build',
|
|
139
|
+
build: 'cargo build --release',
|
|
140
|
+
test: 'cargo test',
|
|
141
|
+
lint: 'cargo clippy',
|
|
142
|
+
dev: null,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function detectGo(root) {
|
|
148
|
+
const goMod = readText(root, 'go.mod');
|
|
149
|
+
if (!goMod) return null;
|
|
150
|
+
const moduleMatch = goMod.match(/^module\s+(\S+)/m);
|
|
151
|
+
return {
|
|
152
|
+
ecosystem: 'Go',
|
|
153
|
+
name: moduleMatch ? moduleMatch[1] : null,
|
|
154
|
+
description: null,
|
|
155
|
+
packageManager: 'go modules',
|
|
156
|
+
frameworks: [],
|
|
157
|
+
testFramework: 'go test',
|
|
158
|
+
commands: {
|
|
159
|
+
install: 'go mod download',
|
|
160
|
+
build: 'go build ./...',
|
|
161
|
+
test: 'go test ./...',
|
|
162
|
+
lint: (exists(root, '.golangci.yml') || exists(root, '.golangci.yaml')) ? 'golangci-lint run' : 'go vet ./...',
|
|
163
|
+
dev: null,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function detectRuby(root) {
|
|
169
|
+
if (!exists(root, 'Gemfile')) return null;
|
|
170
|
+
const hasRspec = exists(root, '.rspec') || exists(root, 'spec');
|
|
171
|
+
return {
|
|
172
|
+
ecosystem: 'Ruby',
|
|
173
|
+
name: null,
|
|
174
|
+
description: null,
|
|
175
|
+
packageManager: 'bundler',
|
|
176
|
+
frameworks: [],
|
|
177
|
+
testFramework: hasRspec ? 'rspec' : null,
|
|
178
|
+
commands: {
|
|
179
|
+
install: 'bundle install',
|
|
180
|
+
build: null,
|
|
181
|
+
test: hasRspec ? 'bundle exec rspec' : (exists(root, 'Rakefile') ? 'bundle exec rake test' : null),
|
|
182
|
+
lint: exists(root, '.rubocop.yml') ? 'bundle exec rubocop' : null,
|
|
183
|
+
dev: null,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function listStructure(root) {
|
|
189
|
+
let entries;
|
|
190
|
+
try {
|
|
191
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
192
|
+
} catch {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
return entries
|
|
196
|
+
.filter((e) => !e.name.startsWith('.') || e.name === '.github')
|
|
197
|
+
.filter((e) => !IGNORE_DIRS.has(e.name))
|
|
198
|
+
.map((e) => (e.isDirectory() ? `${e.name}/` : e.name))
|
|
199
|
+
.sort();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function detectCI(root) {
|
|
203
|
+
const workflowsDir = path.join(root, '.github', 'workflows');
|
|
204
|
+
try {
|
|
205
|
+
const files = fs.readdirSync(workflowsDir).filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'));
|
|
206
|
+
return files.length ? { provider: 'GitHub Actions', workflows: files } : null;
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function detectLicense(root) {
|
|
213
|
+
for (const name of ['LICENSE', 'LICENSE.md', 'LICENSE.txt']) {
|
|
214
|
+
const text = readText(root, name);
|
|
215
|
+
if (text) {
|
|
216
|
+
if (/MIT License/i.test(text)) return 'MIT';
|
|
217
|
+
if (/Apache License/i.test(text)) return 'Apache-2.0';
|
|
218
|
+
if (/GNU GENERAL PUBLIC LICENSE/i.test(text)) return 'GPL';
|
|
219
|
+
return 'present (see LICENSE)';
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function detectProject(root) {
|
|
226
|
+
const detectors = [detectNode, detectPython, detectRust, detectGo, detectRuby];
|
|
227
|
+
const stacks = detectors.map((fn) => fn(root)).filter(Boolean);
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
root,
|
|
231
|
+
stacks,
|
|
232
|
+
structure: listStructure(root),
|
|
233
|
+
ci: detectCI(root),
|
|
234
|
+
license: detectLicense(root),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = { detectProject };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { detectProject } = require('./detect');
|
|
5
|
+
const { renderAgentsMd } = require('./render');
|
|
6
|
+
|
|
7
|
+
function run({ cwd, output, alsoClaudeMd, force, dryRun }) {
|
|
8
|
+
const project = detectProject(cwd);
|
|
9
|
+
const content = renderAgentsMd(project);
|
|
10
|
+
|
|
11
|
+
if (dryRun) {
|
|
12
|
+
process.stdout.write(content + '\n');
|
|
13
|
+
return { written: [], skipped: [] };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const targets = [output];
|
|
17
|
+
if (alsoClaudeMd && !targets.includes('CLAUDE.md')) targets.push('CLAUDE.md');
|
|
18
|
+
|
|
19
|
+
const written = [];
|
|
20
|
+
const skipped = [];
|
|
21
|
+
for (const target of targets) {
|
|
22
|
+
const fullPath = path.join(cwd, target);
|
|
23
|
+
if (fs.existsSync(fullPath) && !force) {
|
|
24
|
+
skipped.push(target);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
fs.writeFileSync(fullPath, content);
|
|
28
|
+
written.push(target);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { written, skipped };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { run };
|
package/src/render.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const STRUCTURE_HINTS = {
|
|
4
|
+
'src/': 'source code',
|
|
5
|
+
'lib/': 'library code',
|
|
6
|
+
'test/': 'tests',
|
|
7
|
+
'tests/': 'tests',
|
|
8
|
+
'__tests__/': 'tests',
|
|
9
|
+
'spec/': 'tests (spec)',
|
|
10
|
+
'docs/': 'documentation',
|
|
11
|
+
'examples/': 'usage examples',
|
|
12
|
+
'scripts/': 'dev/build scripts',
|
|
13
|
+
'public/': 'static assets',
|
|
14
|
+
'assets/': 'static assets',
|
|
15
|
+
'.github/': 'GitHub config (CI, templates)',
|
|
16
|
+
'bin/': 'CLI entry points',
|
|
17
|
+
'cmd/': 'Go command entry points',
|
|
18
|
+
'pkg/': 'Go packages',
|
|
19
|
+
'internal/': 'Go internal packages',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function renderCommands(stack) {
|
|
23
|
+
const c = stack.commands || {};
|
|
24
|
+
const lines = [];
|
|
25
|
+
if (c.install) lines.push(`- Install: \`${c.install}\``);
|
|
26
|
+
if (c.dev) lines.push(`- Dev: \`${c.dev}\``);
|
|
27
|
+
if (c.build) lines.push(`- Build: \`${c.build}\``);
|
|
28
|
+
if (c.test) lines.push(`- Test: \`${c.test}\``);
|
|
29
|
+
if (c.lint) lines.push(`- Lint: \`${c.lint}\``);
|
|
30
|
+
return lines;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function renderStack(stack) {
|
|
34
|
+
const parts = [`## ${stack.ecosystem}`];
|
|
35
|
+
const meta = [];
|
|
36
|
+
if (stack.name) meta.push(`**${stack.name}**`);
|
|
37
|
+
if (stack.frameworks && stack.frameworks.length) meta.push(stack.frameworks.join(', '));
|
|
38
|
+
if (stack.packageManager) meta.push(`package manager: ${stack.packageManager}`);
|
|
39
|
+
if (meta.length) parts.push(meta.join(' — '));
|
|
40
|
+
|
|
41
|
+
const cmds = renderCommands(stack);
|
|
42
|
+
if (cmds.length) {
|
|
43
|
+
parts.push('');
|
|
44
|
+
parts.push('### Commands');
|
|
45
|
+
parts.push('');
|
|
46
|
+
parts.push(...cmds);
|
|
47
|
+
} else {
|
|
48
|
+
parts.push('');
|
|
49
|
+
parts.push('_No commands auto-detected — fill these in by hand._');
|
|
50
|
+
}
|
|
51
|
+
return parts.join('\n');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderStructure(structure) {
|
|
55
|
+
if (!structure.length) return null;
|
|
56
|
+
const lines = structure.map((entry) => {
|
|
57
|
+
const hint = STRUCTURE_HINTS[entry];
|
|
58
|
+
return hint ? `- \`${entry}\` — ${hint}` : `- \`${entry}\``;
|
|
59
|
+
});
|
|
60
|
+
return ['## Structure', '', ...lines].join('\n');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function renderNotes(project) {
|
|
64
|
+
const lines = [];
|
|
65
|
+
if (project.ci) {
|
|
66
|
+
lines.push(`- CI: ${project.ci.provider} (${project.ci.workflows.join(', ')})`);
|
|
67
|
+
}
|
|
68
|
+
if (project.license) {
|
|
69
|
+
lines.push(`- License: ${project.license}`);
|
|
70
|
+
}
|
|
71
|
+
if (!lines.length) return null;
|
|
72
|
+
return ['## Notes', '', ...lines].join('\n');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function renderAgentsMd(project) {
|
|
76
|
+
const sections = [
|
|
77
|
+
'# AGENTS.md',
|
|
78
|
+
'',
|
|
79
|
+
'> Generated by [agents-md-gen](https://www.npmjs.com/package/agents-md-gen) — a starting point, edit freely.',
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
if (!project.stacks.length) {
|
|
83
|
+
sections.push('', "Couldn't auto-detect a specific language/framework in this repo.", 'Fill in setup, build, test and lint commands by hand below.');
|
|
84
|
+
} else {
|
|
85
|
+
for (const stack of project.stacks) {
|
|
86
|
+
sections.push('', renderStack(stack));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const structure = renderStructure(project.structure);
|
|
91
|
+
if (structure) sections.push('', structure);
|
|
92
|
+
|
|
93
|
+
const notes = renderNotes(project);
|
|
94
|
+
if (notes) sections.push('', notes);
|
|
95
|
+
|
|
96
|
+
sections.push('');
|
|
97
|
+
return sections.join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { renderAgentsMd };
|