compose-agentsmd 1.0.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 +101 -0
- package/dist/compose-agents.js +293 -0
- package/package.json +43 -0
- package/tools/compose-agents.cjs +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 metyatech
|
|
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,101 @@
|
|
|
1
|
+
# Compose AGENTS.md
|
|
2
|
+
|
|
3
|
+
This repository contains CLI tooling for composing per-project `AGENTS.md` files from modular rule sets.
|
|
4
|
+
|
|
5
|
+
It is intended to be used together with shared rule modules such as:
|
|
6
|
+
|
|
7
|
+
- `agent-rules/` (public rule modules)
|
|
8
|
+
- `agent-rules-private/` (optional, private-only rule modules)
|
|
9
|
+
|
|
10
|
+
## Install (global CLI)
|
|
11
|
+
|
|
12
|
+
After publishing to npm, install globally:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install -g compose-agentsmd
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This provides the `compose-agentsmd` command.
|
|
19
|
+
|
|
20
|
+
## Rules setup (this repository)
|
|
21
|
+
|
|
22
|
+
This repository expects the public rules to be available at `agent-rules/rules` via the `agent-rules/` submodule.
|
|
23
|
+
|
|
24
|
+
Initialize submodules and compose the rules:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
git submodule update --init --recursive
|
|
28
|
+
npm install
|
|
29
|
+
npm run compose
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The default ruleset for this repository is `agent-ruleset.json` and currently composes the `node` domain into `AGENTS.md`.
|
|
33
|
+
|
|
34
|
+
## Compose
|
|
35
|
+
|
|
36
|
+
From each project root, run:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
compose-agentsmd
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The tool searches for `agent-ruleset.json` under the given root directory (default: current working directory), and writes output files as specified by each ruleset. If `output` is omitted, it defaults to `AGENTS.md`.
|
|
43
|
+
|
|
44
|
+
### Rules root resolution (important for global installs)
|
|
45
|
+
|
|
46
|
+
When installed globally, the rules directory is usually outside the project. You can point to it in either of the following ways:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
compose-agentsmd --rules-root "C:/path/to/agent-rules/rules"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or via environment variable:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
set AGENT_RULES_ROOT=C:/path/to/agent-rules/rules
|
|
56
|
+
compose-agentsmd
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Rules root resolution precedence is:
|
|
60
|
+
|
|
61
|
+
- `--rules-root` CLI option
|
|
62
|
+
- `AGENT_RULES_ROOT` environment variable
|
|
63
|
+
- `rulesRoot` in the ruleset file
|
|
64
|
+
- Default: `agent-rules/rules` relative to the ruleset file
|
|
65
|
+
|
|
66
|
+
## Project ruleset format
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"output": "AGENTS.md",
|
|
71
|
+
"domains": ["node", "unreal"],
|
|
72
|
+
"rules": ["agent-rules-local/custom.md"]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- Global rules are always included from `agent-rules/rules/global`.
|
|
77
|
+
- `output` is optional; when omitted, `AGENTS.md` is used.
|
|
78
|
+
- `domains` selects domain folders under `agent-rules/rules/domains`.
|
|
79
|
+
- `rules` is optional and appends additional rule files.
|
|
80
|
+
|
|
81
|
+
Optional path overrides:
|
|
82
|
+
|
|
83
|
+
- `rulesRoot`: override `agent-rules/rules`.
|
|
84
|
+
- `globalDir`: override `global` (relative to `rulesRoot`).
|
|
85
|
+
- `domainsDir`: override `domains` (relative to `rulesRoot`).
|
|
86
|
+
|
|
87
|
+
### Optional arguments
|
|
88
|
+
|
|
89
|
+
- `--root <path>`: project root (defaults to current working directory)
|
|
90
|
+
- `--ruleset <path>`: only compose a single ruleset file
|
|
91
|
+
- `--ruleset-name <name>`: override the ruleset filename (default: `agent-ruleset.json`)
|
|
92
|
+
- `--rules-root <path>`: override the rules root for all rulesets (or set `AGENT_RULES_ROOT`)
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
npm install
|
|
98
|
+
npm run lint
|
|
99
|
+
npm run build
|
|
100
|
+
npm test
|
|
101
|
+
```
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const DEFAULT_RULESET_NAME = "agent-ruleset.json";
|
|
10
|
+
const DEFAULT_RULES_ROOT = "agent-rules/rules";
|
|
11
|
+
const DEFAULT_GLOBAL_DIR = "global";
|
|
12
|
+
const DEFAULT_DOMAINS_DIR = "domains";
|
|
13
|
+
const RULES_ROOT_ENV_VAR = "AGENT_RULES_ROOT";
|
|
14
|
+
const DEFAULT_OUTPUT = "AGENTS.md";
|
|
15
|
+
const DEFAULT_IGNORE_DIRS = new Set([
|
|
16
|
+
".git",
|
|
17
|
+
"agent-rules",
|
|
18
|
+
"agent-rules-private",
|
|
19
|
+
"agent-rules-local",
|
|
20
|
+
"agent-rules-tools",
|
|
21
|
+
"compose-agentsmd",
|
|
22
|
+
"node_modules",
|
|
23
|
+
"dist",
|
|
24
|
+
"build",
|
|
25
|
+
"out",
|
|
26
|
+
".next",
|
|
27
|
+
".turbo",
|
|
28
|
+
"coverage"
|
|
29
|
+
]);
|
|
30
|
+
const usage = `Usage: compose-agentsmd [--root <path>] [--ruleset <path>] [--ruleset-name <name>] [--rules-root <path>]
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
--root <path> Project root directory (default: current working directory)
|
|
34
|
+
--ruleset <path> Only compose a single ruleset file
|
|
35
|
+
--ruleset-name <name> Ruleset filename to search for (default: agent-ruleset.json)
|
|
36
|
+
--rules-root <path> Override rules root directory for all rulesets (or set ${RULES_ROOT_ENV_VAR})
|
|
37
|
+
`;
|
|
38
|
+
const parseArgs = (argv) => {
|
|
39
|
+
const args = {};
|
|
40
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
41
|
+
const arg = argv[i];
|
|
42
|
+
if (arg === "--help" || arg === "-h") {
|
|
43
|
+
args.help = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (arg === "--root") {
|
|
47
|
+
const value = argv[i + 1];
|
|
48
|
+
if (!value) {
|
|
49
|
+
throw new Error("Missing value for --root");
|
|
50
|
+
}
|
|
51
|
+
args.root = value;
|
|
52
|
+
i += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg === "--ruleset") {
|
|
56
|
+
const value = argv[i + 1];
|
|
57
|
+
if (!value) {
|
|
58
|
+
throw new Error("Missing value for --ruleset");
|
|
59
|
+
}
|
|
60
|
+
args.ruleset = value;
|
|
61
|
+
i += 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (arg === "--ruleset-name") {
|
|
65
|
+
const value = argv[i + 1];
|
|
66
|
+
if (!value) {
|
|
67
|
+
throw new Error("Missing value for --ruleset-name");
|
|
68
|
+
}
|
|
69
|
+
args.rulesetName = value;
|
|
70
|
+
i += 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (arg === "--rules-root") {
|
|
74
|
+
const value = argv[i + 1];
|
|
75
|
+
if (!value) {
|
|
76
|
+
throw new Error("Missing value for --rules-root");
|
|
77
|
+
}
|
|
78
|
+
args.rulesRoot = value;
|
|
79
|
+
i += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
83
|
+
}
|
|
84
|
+
return args;
|
|
85
|
+
};
|
|
86
|
+
const normalizeTrailingWhitespace = (content) => content.replace(/\s+$/u, "");
|
|
87
|
+
const normalizePath = (filePath) => filePath.replace(/\\/g, "/");
|
|
88
|
+
const isNonEmptyString = (value) => typeof value === "string" && value.trim() !== "";
|
|
89
|
+
const resolveFrom = (baseDir, targetPath) => {
|
|
90
|
+
if (node_path_1.default.isAbsolute(targetPath)) {
|
|
91
|
+
return targetPath;
|
|
92
|
+
}
|
|
93
|
+
return node_path_1.default.resolve(baseDir, targetPath);
|
|
94
|
+
};
|
|
95
|
+
const ensureFileExists = (filePath) => {
|
|
96
|
+
if (!node_fs_1.default.existsSync(filePath)) {
|
|
97
|
+
throw new Error(`Missing file: ${filePath}`);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const ensureDirectoryExists = (dirPath) => {
|
|
101
|
+
if (!node_fs_1.default.existsSync(dirPath)) {
|
|
102
|
+
throw new Error(`Missing directory: ${dirPath}`);
|
|
103
|
+
}
|
|
104
|
+
const stat = node_fs_1.default.statSync(dirPath);
|
|
105
|
+
if (!stat.isDirectory()) {
|
|
106
|
+
throw new Error(`Not a directory: ${dirPath}`);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const readJsonFile = (filePath) => {
|
|
110
|
+
const raw = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
111
|
+
return JSON.parse(raw);
|
|
112
|
+
};
|
|
113
|
+
const readProjectRuleset = (rulesetPath) => {
|
|
114
|
+
const parsed = readJsonFile(rulesetPath);
|
|
115
|
+
if (parsed.output === undefined) {
|
|
116
|
+
parsed.output = DEFAULT_OUTPUT;
|
|
117
|
+
}
|
|
118
|
+
else if (!isNonEmptyString(parsed.output)) {
|
|
119
|
+
throw new Error(`Invalid ruleset output in ${rulesetPath}`);
|
|
120
|
+
}
|
|
121
|
+
if (parsed.domains !== undefined) {
|
|
122
|
+
if (!Array.isArray(parsed.domains)) {
|
|
123
|
+
throw new Error(`"domains" must be an array in ${rulesetPath}`);
|
|
124
|
+
}
|
|
125
|
+
for (const domain of parsed.domains) {
|
|
126
|
+
if (!isNonEmptyString(domain)) {
|
|
127
|
+
throw new Error(`"domains" entries must be non-empty strings in ${rulesetPath}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (parsed.rules !== undefined) {
|
|
132
|
+
if (!Array.isArray(parsed.rules)) {
|
|
133
|
+
throw new Error(`"rules" must be an array in ${rulesetPath}`);
|
|
134
|
+
}
|
|
135
|
+
for (const rule of parsed.rules) {
|
|
136
|
+
if (!isNonEmptyString(rule)) {
|
|
137
|
+
throw new Error(`"rules" entries must be non-empty strings in ${rulesetPath}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return parsed;
|
|
142
|
+
};
|
|
143
|
+
const resolveRulesRoot = (rulesetDir, projectRuleset, options) => {
|
|
144
|
+
if (isNonEmptyString(options.cliRulesRoot)) {
|
|
145
|
+
return resolveFrom(rulesetDir, options.cliRulesRoot);
|
|
146
|
+
}
|
|
147
|
+
const envRulesRoot = process.env[RULES_ROOT_ENV_VAR];
|
|
148
|
+
if (isNonEmptyString(envRulesRoot)) {
|
|
149
|
+
return resolveFrom(rulesetDir, envRulesRoot);
|
|
150
|
+
}
|
|
151
|
+
if (isNonEmptyString(projectRuleset.rulesRoot)) {
|
|
152
|
+
return resolveFrom(rulesetDir, projectRuleset.rulesRoot);
|
|
153
|
+
}
|
|
154
|
+
return node_path_1.default.resolve(rulesetDir, DEFAULT_RULES_ROOT);
|
|
155
|
+
};
|
|
156
|
+
const resolveGlobalRoot = (rulesRoot, projectRuleset) => {
|
|
157
|
+
const globalDirName = isNonEmptyString(projectRuleset.globalDir)
|
|
158
|
+
? projectRuleset.globalDir
|
|
159
|
+
: DEFAULT_GLOBAL_DIR;
|
|
160
|
+
return node_path_1.default.resolve(rulesRoot, globalDirName);
|
|
161
|
+
};
|
|
162
|
+
const resolveDomainsRoot = (rulesRoot, projectRuleset) => {
|
|
163
|
+
const domainsDirName = isNonEmptyString(projectRuleset.domainsDir)
|
|
164
|
+
? projectRuleset.domainsDir
|
|
165
|
+
: DEFAULT_DOMAINS_DIR;
|
|
166
|
+
return node_path_1.default.resolve(rulesRoot, domainsDirName);
|
|
167
|
+
};
|
|
168
|
+
const collectMarkdownFiles = (rootDir) => {
|
|
169
|
+
ensureDirectoryExists(rootDir);
|
|
170
|
+
const results = [];
|
|
171
|
+
const pending = [rootDir];
|
|
172
|
+
while (pending.length > 0) {
|
|
173
|
+
const currentDir = pending.pop();
|
|
174
|
+
if (!currentDir) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const entries = node_fs_1.default.readdirSync(currentDir, { withFileTypes: true });
|
|
178
|
+
for (const entry of entries) {
|
|
179
|
+
const entryPath = node_path_1.default.join(currentDir, entry.name);
|
|
180
|
+
if (entry.isDirectory()) {
|
|
181
|
+
pending.push(entryPath);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (entry.isFile() && node_path_1.default.extname(entry.name).toLowerCase() === ".md") {
|
|
185
|
+
results.push(entryPath);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return results.sort((a, b) => {
|
|
190
|
+
const relA = normalizePath(node_path_1.default.relative(rootDir, a));
|
|
191
|
+
const relB = normalizePath(node_path_1.default.relative(rootDir, b));
|
|
192
|
+
return relA.localeCompare(relB);
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
const addRulePaths = (rulePaths, resolvedRules, seenRules) => {
|
|
196
|
+
for (const rulePath of rulePaths) {
|
|
197
|
+
const resolvedRulePath = node_path_1.default.resolve(rulePath);
|
|
198
|
+
if (seenRules.has(resolvedRulePath)) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
ensureFileExists(resolvedRulePath);
|
|
202
|
+
resolvedRules.push(resolvedRulePath);
|
|
203
|
+
seenRules.add(resolvedRulePath);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const composeRuleset = (rulesetPath, rootDir, options) => {
|
|
207
|
+
const rulesetDir = node_path_1.default.dirname(rulesetPath);
|
|
208
|
+
const projectRuleset = readProjectRuleset(rulesetPath);
|
|
209
|
+
const outputFileName = projectRuleset.output ?? DEFAULT_OUTPUT;
|
|
210
|
+
const outputPath = resolveFrom(rulesetDir, outputFileName);
|
|
211
|
+
const rulesRoot = resolveRulesRoot(rulesetDir, projectRuleset, {
|
|
212
|
+
cliRulesRoot: options.cliRulesRoot
|
|
213
|
+
});
|
|
214
|
+
const globalRoot = resolveGlobalRoot(rulesRoot, projectRuleset);
|
|
215
|
+
const domainsRoot = resolveDomainsRoot(rulesRoot, projectRuleset);
|
|
216
|
+
const resolvedRules = [];
|
|
217
|
+
const seenRules = new Set();
|
|
218
|
+
// Global rules always apply.
|
|
219
|
+
addRulePaths(collectMarkdownFiles(globalRoot), resolvedRules, seenRules);
|
|
220
|
+
const domains = Array.isArray(projectRuleset.domains) ? projectRuleset.domains : [];
|
|
221
|
+
for (const domain of domains) {
|
|
222
|
+
const domainRoot = node_path_1.default.resolve(domainsRoot, domain);
|
|
223
|
+
addRulePaths(collectMarkdownFiles(domainRoot), resolvedRules, seenRules);
|
|
224
|
+
}
|
|
225
|
+
const directRules = Array.isArray(projectRuleset.rules) ? projectRuleset.rules : [];
|
|
226
|
+
const directRulePaths = directRules.map((rulePath) => resolveFrom(rulesetDir, rulePath));
|
|
227
|
+
addRulePaths(directRulePaths, resolvedRules, seenRules);
|
|
228
|
+
const parts = resolvedRules.map((rulePath) => normalizeTrailingWhitespace(node_fs_1.default.readFileSync(rulePath, "utf8")));
|
|
229
|
+
const lintHeader = "<!-- markdownlint-disable MD025 -->";
|
|
230
|
+
const output = `${lintHeader}\n${parts.join("\n\n")}\n`;
|
|
231
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(outputPath), { recursive: true });
|
|
232
|
+
node_fs_1.default.writeFileSync(outputPath, output, "utf8");
|
|
233
|
+
return normalizePath(node_path_1.default.relative(rootDir, outputPath));
|
|
234
|
+
};
|
|
235
|
+
const findRulesetFiles = (rootDir, rulesetName) => {
|
|
236
|
+
const results = [];
|
|
237
|
+
const pending = [rootDir];
|
|
238
|
+
while (pending.length > 0) {
|
|
239
|
+
const currentDir = pending.pop();
|
|
240
|
+
if (!currentDir) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const entries = node_fs_1.default.readdirSync(currentDir, { withFileTypes: true });
|
|
244
|
+
for (const entry of entries) {
|
|
245
|
+
const entryPath = node_path_1.default.join(currentDir, entry.name);
|
|
246
|
+
if (entry.isDirectory()) {
|
|
247
|
+
if (DEFAULT_IGNORE_DIRS.has(entry.name)) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
pending.push(entryPath);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (entry.isFile() && entry.name === rulesetName) {
|
|
254
|
+
results.push(entryPath);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return results;
|
|
259
|
+
};
|
|
260
|
+
const getRulesetFiles = (rootDir, specificRuleset, rulesetName) => {
|
|
261
|
+
if (specificRuleset) {
|
|
262
|
+
const resolved = resolveFrom(rootDir, specificRuleset);
|
|
263
|
+
ensureFileExists(resolved);
|
|
264
|
+
return [resolved];
|
|
265
|
+
}
|
|
266
|
+
return findRulesetFiles(rootDir, rulesetName);
|
|
267
|
+
};
|
|
268
|
+
const main = () => {
|
|
269
|
+
const args = parseArgs(process.argv.slice(2));
|
|
270
|
+
if (args.help) {
|
|
271
|
+
process.stdout.write(`${usage}\n`);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const rootDir = args.root ? node_path_1.default.resolve(args.root) : process.cwd();
|
|
275
|
+
const rulesetName = args.rulesetName || DEFAULT_RULESET_NAME;
|
|
276
|
+
const rulesetFiles = getRulesetFiles(rootDir, args.ruleset, rulesetName);
|
|
277
|
+
if (rulesetFiles.length === 0) {
|
|
278
|
+
throw new Error(`No ruleset files named ${rulesetName} found under ${rootDir}`);
|
|
279
|
+
}
|
|
280
|
+
const outputs = rulesetFiles
|
|
281
|
+
.sort()
|
|
282
|
+
.map((rulesetPath) => composeRuleset(rulesetPath, rootDir, { cliRulesRoot: args.rulesRoot }));
|
|
283
|
+
process.stdout.write(`Composed AGENTS.md:\n${outputs.map((file) => `- ${file}`).join("\n")}\n`);
|
|
284
|
+
};
|
|
285
|
+
try {
|
|
286
|
+
main();
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
290
|
+
process.stderr.write(`${message}\n`);
|
|
291
|
+
process.stderr.write(`${usage}\n`);
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "compose-agentsmd",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tools for composing per-project AGENTS.md files from modular rule sets",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/metyatech/compose-agentsmd.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/metyatech/compose-agentsmd/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/metyatech/compose-agentsmd#readme",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"agents",
|
|
16
|
+
"agentsmd",
|
|
17
|
+
"rules",
|
|
18
|
+
"cli",
|
|
19
|
+
"markdown"
|
|
20
|
+
],
|
|
21
|
+
"type": "commonjs",
|
|
22
|
+
"bin": {
|
|
23
|
+
"compose-agentsmd": "dist/compose-agents.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"tools"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p tsconfig.json",
|
|
31
|
+
"lint": "tsc -p tsconfig.json --noEmit",
|
|
32
|
+
"prepack": "npm run build",
|
|
33
|
+
"test": "npm run build && node --test",
|
|
34
|
+
"compose": "npm run build && node dist/compose-agents.js"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^25.0.10",
|
|
41
|
+
"typescript": "^5.7.3"
|
|
42
|
+
}
|
|
43
|
+
}
|