compose-agentsmd 3.0.0 → 3.2.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 +40 -2
- package/dist/compose-agents.js +312 -12
- package/package.json +1 -1
- package/tools/tool-rules.md +2 -1
- package/tools/usage.txt +19 -5
package/README.md
CHANGED
|
@@ -34,6 +34,24 @@ The tool reads `agent-ruleset.json` from the given root directory (default: curr
|
|
|
34
34
|
|
|
35
35
|
The tool prepends a small "Tool Rules" block to every generated `AGENTS.md` so agents know how to regenerate or update rules.
|
|
36
36
|
|
|
37
|
+
## Setup (init)
|
|
38
|
+
|
|
39
|
+
For a project that does not have a ruleset yet, bootstrap one with `init`:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
compose-agentsmd init --root path/to/project --yes
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Defaults:
|
|
46
|
+
|
|
47
|
+
- `source`: `github:owner/repo@latest`
|
|
48
|
+
- `domains`: empty
|
|
49
|
+
- `extra`: empty
|
|
50
|
+
- `global`: omitted (defaults to `true`)
|
|
51
|
+
- `output`: `AGENTS.md`
|
|
52
|
+
|
|
53
|
+
Use `--dry-run` to preview actions, `--force` to overwrite existing files, and `--compose` to generate `AGENTS.md` immediately.
|
|
54
|
+
|
|
37
55
|
## Updating shared rules
|
|
38
56
|
|
|
39
57
|
For GitHub sources, the tool keeps two locations:
|
|
@@ -53,11 +71,17 @@ compose-agentsmd apply-rules
|
|
|
53
71
|
|
|
54
72
|
## Project ruleset format
|
|
55
73
|
|
|
56
|
-
|
|
74
|
+
Ruleset files accept JSON with `//` or `/* */` comments.
|
|
75
|
+
|
|
76
|
+
```jsonc
|
|
57
77
|
{
|
|
58
|
-
|
|
78
|
+
// Rules source. Use github:owner/repo@ref or a local path.
|
|
79
|
+
"source": "github:owner/repo@latest",
|
|
80
|
+
// Domain folders under rules/domains.
|
|
59
81
|
"domains": ["node", "unreal"],
|
|
82
|
+
// Additional local rule files to append.
|
|
60
83
|
"extra": ["agent-rules-local/custom.md"],
|
|
84
|
+
// Output file name.
|
|
61
85
|
"output": "AGENTS.md"
|
|
62
86
|
}
|
|
63
87
|
```
|
|
@@ -85,8 +109,22 @@ Remote sources are cached under `~/.agentsmd/cache/<owner>/<repo>/<ref>/`. Use `
|
|
|
85
109
|
- `--ruleset-name <name>`: override the ruleset filename (default: `agent-ruleset.json`)
|
|
86
110
|
- `--refresh`: refresh cached remote rules
|
|
87
111
|
- `--clear-cache`: remove cached remote rules and exit
|
|
112
|
+
- `--version` / `-V`: show version and exit
|
|
113
|
+
- `--verbose` / `-v`: show verbose diagnostics
|
|
114
|
+
- `--source <source>`: rules source for `init`
|
|
115
|
+
- `--domains <list>`: comma-separated domains for `init`
|
|
116
|
+
- `--extra <list>`: comma-separated extra rules for `init`
|
|
117
|
+
- `--output <file>`: output filename for `init`
|
|
118
|
+
- `--no-domains`: initialize with no domains
|
|
119
|
+
- `--no-extra`: initialize without extra rule files
|
|
120
|
+
- `--no-global`: initialize without global rules
|
|
121
|
+
- `--compose`: compose `AGENTS.md` after `init`
|
|
122
|
+
- `--dry-run`: show init plan without writing files
|
|
123
|
+
- `--yes`: skip init confirmation prompt
|
|
124
|
+
- `--force`: overwrite existing files during init
|
|
88
125
|
- `edit-rules`: prepare or locate a writable rules workspace
|
|
89
126
|
- `apply-rules`: push workspace changes (if GitHub source) and regenerate rules with refresh
|
|
127
|
+
- `init`: generate a new ruleset and optional local rules file
|
|
90
128
|
|
|
91
129
|
## Development
|
|
92
130
|
|
package/dist/compose-agents.js
CHANGED
|
@@ -3,17 +3,22 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
|
+
import readline from "node:readline";
|
|
6
7
|
import { Ajv } from "ajv";
|
|
7
8
|
const DEFAULT_RULESET_NAME = "agent-ruleset.json";
|
|
8
9
|
const DEFAULT_OUTPUT = "AGENTS.md";
|
|
9
10
|
const DEFAULT_CACHE_ROOT = path.join(os.homedir(), ".agentsmd", "cache");
|
|
10
11
|
const DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), ".agentsmd", "workspace");
|
|
12
|
+
const DEFAULT_INIT_SOURCE = "github:owner/repo@latest";
|
|
13
|
+
const DEFAULT_INIT_DOMAINS = [];
|
|
14
|
+
const DEFAULT_INIT_EXTRA = [];
|
|
11
15
|
const RULESET_SCHEMA_PATH = new URL("../agent-ruleset.schema.json", import.meta.url);
|
|
16
|
+
const PACKAGE_JSON_PATH = new URL("../package.json", import.meta.url);
|
|
12
17
|
const TOOL_RULES_PATH = new URL("../tools/tool-rules.md", import.meta.url);
|
|
13
18
|
const USAGE_PATH = new URL("../tools/usage.txt", import.meta.url);
|
|
14
19
|
const parseArgs = (argv) => {
|
|
15
20
|
const args = {};
|
|
16
|
-
const knownCommands = new Set(["edit-rules", "apply-rules"]);
|
|
21
|
+
const knownCommands = new Set(["edit-rules", "apply-rules", "init"]);
|
|
17
22
|
const remaining = [...argv];
|
|
18
23
|
if (remaining.length > 0 && knownCommands.has(remaining[0])) {
|
|
19
24
|
args.command = remaining.shift();
|
|
@@ -24,6 +29,14 @@ const parseArgs = (argv) => {
|
|
|
24
29
|
args.help = true;
|
|
25
30
|
continue;
|
|
26
31
|
}
|
|
32
|
+
if (arg === "--version" || arg === "-V") {
|
|
33
|
+
args.version = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (arg === "--verbose" || arg === "-v") {
|
|
37
|
+
args.verbose = true;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
27
40
|
if (arg === "--root") {
|
|
28
41
|
const value = remaining[i + 1];
|
|
29
42
|
if (!value) {
|
|
@@ -59,6 +72,70 @@ const parseArgs = (argv) => {
|
|
|
59
72
|
args.clearCache = true;
|
|
60
73
|
continue;
|
|
61
74
|
}
|
|
75
|
+
if (arg === "--source") {
|
|
76
|
+
const value = remaining[i + 1];
|
|
77
|
+
if (!value) {
|
|
78
|
+
throw new Error("Missing value for --source");
|
|
79
|
+
}
|
|
80
|
+
args.source = value;
|
|
81
|
+
i += 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (arg === "--domains") {
|
|
85
|
+
const value = remaining[i + 1];
|
|
86
|
+
if (!value) {
|
|
87
|
+
throw new Error("Missing value for --domains");
|
|
88
|
+
}
|
|
89
|
+
args.domains = [...(args.domains ?? []), ...value.split(",").map((entry) => entry.trim())];
|
|
90
|
+
i += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (arg === "--no-domains") {
|
|
94
|
+
args.domains = [];
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (arg === "--extra") {
|
|
98
|
+
const value = remaining[i + 1];
|
|
99
|
+
if (!value) {
|
|
100
|
+
throw new Error("Missing value for --extra");
|
|
101
|
+
}
|
|
102
|
+
args.extra = [...(args.extra ?? []), ...value.split(",").map((entry) => entry.trim())];
|
|
103
|
+
i += 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (arg === "--no-extra") {
|
|
107
|
+
args.extra = [];
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (arg === "--output") {
|
|
111
|
+
const value = remaining[i + 1];
|
|
112
|
+
if (!value) {
|
|
113
|
+
throw new Error("Missing value for --output");
|
|
114
|
+
}
|
|
115
|
+
args.output = value;
|
|
116
|
+
i += 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (arg === "--no-global") {
|
|
120
|
+
args.global = false;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (arg === "--compose") {
|
|
124
|
+
args.compose = true;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (arg === "--dry-run") {
|
|
128
|
+
args.dryRun = true;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (arg === "--yes") {
|
|
132
|
+
args.yes = true;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (arg === "--force") {
|
|
136
|
+
args.force = true;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
62
139
|
throw new Error(`Unknown argument: ${arg}`);
|
|
63
140
|
}
|
|
64
141
|
return args;
|
|
@@ -66,7 +143,26 @@ const parseArgs = (argv) => {
|
|
|
66
143
|
const normalizeTrailingWhitespace = (content) => content.replace(/\s+$/u, "");
|
|
67
144
|
const normalizePath = (filePath) => filePath.replace(/\\/g, "/");
|
|
68
145
|
const isNonEmptyString = (value) => typeof value === "string" && value.trim() !== "";
|
|
146
|
+
const normalizeListOption = (values, label) => {
|
|
147
|
+
if (!values) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
const trimmed = values.map((value) => value.trim());
|
|
151
|
+
if (trimmed.some((value) => value.length === 0)) {
|
|
152
|
+
throw new Error(`Invalid value for ${label}`);
|
|
153
|
+
}
|
|
154
|
+
return [...new Set(trimmed)];
|
|
155
|
+
};
|
|
156
|
+
const askQuestion = (prompt) => new Promise((resolve) => {
|
|
157
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
158
|
+
rl.question(prompt, (answer) => {
|
|
159
|
+
rl.close();
|
|
160
|
+
resolve(answer);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
69
163
|
const usage = normalizeTrailingWhitespace(fs.readFileSync(USAGE_PATH, "utf8"));
|
|
164
|
+
const packageJson = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, "utf8"));
|
|
165
|
+
const getVersion = () => packageJson.version ?? "unknown";
|
|
70
166
|
const rulesetSchema = JSON.parse(fs.readFileSync(RULESET_SCHEMA_PATH, "utf8"));
|
|
71
167
|
const TOOL_RULES = normalizeTrailingWhitespace(fs.readFileSync(TOOL_RULES_PATH, "utf8"));
|
|
72
168
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
@@ -110,9 +206,69 @@ const ensureDirectoryExists = (dirPath) => {
|
|
|
110
206
|
throw new Error(`Not a directory: ${dirPath}`);
|
|
111
207
|
}
|
|
112
208
|
};
|
|
209
|
+
const stripJsonComments = (input) => {
|
|
210
|
+
let output = "";
|
|
211
|
+
let inString = false;
|
|
212
|
+
let stringChar = "";
|
|
213
|
+
let escaping = false;
|
|
214
|
+
let inLineComment = false;
|
|
215
|
+
let inBlockComment = false;
|
|
216
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
217
|
+
const char = input[i];
|
|
218
|
+
const next = input[i + 1];
|
|
219
|
+
if (inLineComment) {
|
|
220
|
+
if (char === "\n") {
|
|
221
|
+
inLineComment = false;
|
|
222
|
+
output += char;
|
|
223
|
+
}
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (inBlockComment) {
|
|
227
|
+
if (char === "*" && next === "/") {
|
|
228
|
+
inBlockComment = false;
|
|
229
|
+
i += 1;
|
|
230
|
+
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (inString) {
|
|
234
|
+
output += char;
|
|
235
|
+
if (escaping) {
|
|
236
|
+
escaping = false;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (char === "\\") {
|
|
240
|
+
escaping = true;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (char === stringChar) {
|
|
244
|
+
inString = false;
|
|
245
|
+
stringChar = "";
|
|
246
|
+
}
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (char === "/" && next === "/") {
|
|
250
|
+
inLineComment = true;
|
|
251
|
+
i += 1;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (char === "/" && next === "*") {
|
|
255
|
+
inBlockComment = true;
|
|
256
|
+
i += 1;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (char === "\"" || char === "'") {
|
|
260
|
+
inString = true;
|
|
261
|
+
stringChar = char;
|
|
262
|
+
output += char;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
output += char;
|
|
266
|
+
}
|
|
267
|
+
return output;
|
|
268
|
+
};
|
|
113
269
|
const readJsonFile = (filePath) => {
|
|
114
270
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
115
|
-
return JSON.parse(raw);
|
|
271
|
+
return JSON.parse(stripJsonComments(raw));
|
|
116
272
|
};
|
|
117
273
|
const readProjectRuleset = (rulesetPath) => {
|
|
118
274
|
const parsed = readJsonFile(rulesetPath);
|
|
@@ -379,6 +535,130 @@ const composeRuleset = (rulesetPath, rootDir, options) => {
|
|
|
379
535
|
fs.writeFileSync(outputPath, output, "utf8");
|
|
380
536
|
return normalizePath(path.relative(rootDir, outputPath));
|
|
381
537
|
};
|
|
538
|
+
const LOCAL_RULES_TEMPLATE = "# Local Rules\n\n- Add project-specific instructions here.\n";
|
|
539
|
+
const buildInitRuleset = (args) => {
|
|
540
|
+
const domains = normalizeListOption(args.domains, "--domains");
|
|
541
|
+
const extra = normalizeListOption(args.extra, "--extra");
|
|
542
|
+
const ruleset = {
|
|
543
|
+
source: args.source ?? DEFAULT_INIT_SOURCE,
|
|
544
|
+
output: args.output ?? DEFAULT_OUTPUT
|
|
545
|
+
};
|
|
546
|
+
if (args.global === false) {
|
|
547
|
+
ruleset.global = false;
|
|
548
|
+
}
|
|
549
|
+
const resolvedDomains = domains ?? DEFAULT_INIT_DOMAINS;
|
|
550
|
+
if (resolvedDomains.length > 0) {
|
|
551
|
+
ruleset.domains = resolvedDomains;
|
|
552
|
+
}
|
|
553
|
+
const resolvedExtra = extra ?? DEFAULT_INIT_EXTRA;
|
|
554
|
+
if (resolvedExtra.length > 0) {
|
|
555
|
+
ruleset.extra = resolvedExtra;
|
|
556
|
+
}
|
|
557
|
+
return ruleset;
|
|
558
|
+
};
|
|
559
|
+
const formatInitRuleset = (ruleset) => {
|
|
560
|
+
const domainsValue = JSON.stringify(ruleset.domains ?? []);
|
|
561
|
+
const extraValue = JSON.stringify(ruleset.extra ?? []);
|
|
562
|
+
const lines = [
|
|
563
|
+
"{",
|
|
564
|
+
' // Rules source. Use github:owner/repo@ref or a local path.',
|
|
565
|
+
` "source": "${ruleset.source}",`,
|
|
566
|
+
' // Domain folders under rules/domains.',
|
|
567
|
+
` "domains": ${domainsValue},`,
|
|
568
|
+
' // Additional local rule files to append.',
|
|
569
|
+
` "extra": ${extraValue},`
|
|
570
|
+
];
|
|
571
|
+
if (ruleset.global === false) {
|
|
572
|
+
lines.push(' // Include rules/global from the source.');
|
|
573
|
+
lines.push(' "global": false,');
|
|
574
|
+
}
|
|
575
|
+
lines.push(' // Output file name.');
|
|
576
|
+
lines.push(` "output": "${ruleset.output ?? DEFAULT_OUTPUT}"`);
|
|
577
|
+
lines.push("}");
|
|
578
|
+
return `${lines.join("\n")}\n`;
|
|
579
|
+
};
|
|
580
|
+
const formatPlan = (items, rootDir) => {
|
|
581
|
+
const lines = items.map((item) => {
|
|
582
|
+
const verb = item.action === "overwrite" ? "Overwrite" : "Create";
|
|
583
|
+
const relative = normalizePath(path.relative(rootDir, item.path));
|
|
584
|
+
return `- ${verb}: ${relative}`;
|
|
585
|
+
});
|
|
586
|
+
return `Init plan:\n${lines.join("\n")}\n`;
|
|
587
|
+
};
|
|
588
|
+
const confirmInit = async (args) => {
|
|
589
|
+
if (args.dryRun || args.yes) {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (!process.stdin.isTTY) {
|
|
593
|
+
throw new Error("Confirmation required. Re-run with --yes to continue.");
|
|
594
|
+
}
|
|
595
|
+
const answer = await askQuestion("Proceed with init? [y/N] ");
|
|
596
|
+
if (!/^y(es)?$/iu.test(answer.trim())) {
|
|
597
|
+
throw new Error("Init aborted.");
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
const initProject = async (args, rootDir, rulesetName) => {
|
|
601
|
+
const rulesetPath = args.ruleset ? resolveFrom(rootDir, args.ruleset) : path.join(rootDir, rulesetName);
|
|
602
|
+
const rulesetDir = path.dirname(rulesetPath);
|
|
603
|
+
const ruleset = buildInitRuleset(args);
|
|
604
|
+
const outputPath = resolveFrom(rulesetDir, ruleset.output ?? DEFAULT_OUTPUT);
|
|
605
|
+
const plan = [];
|
|
606
|
+
if (fs.existsSync(rulesetPath)) {
|
|
607
|
+
if (!args.force) {
|
|
608
|
+
throw new Error(`Ruleset already exists: ${normalizePath(rulesetPath)}`);
|
|
609
|
+
}
|
|
610
|
+
plan.push({ action: "overwrite", path: rulesetPath });
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
plan.push({ action: "create", path: rulesetPath });
|
|
614
|
+
}
|
|
615
|
+
const extraFiles = (ruleset.extra ?? []).map((rulePath) => resolveFrom(rulesetDir, rulePath));
|
|
616
|
+
const extraToWrite = [];
|
|
617
|
+
for (const extraPath of extraFiles) {
|
|
618
|
+
if (fs.existsSync(extraPath)) {
|
|
619
|
+
if (args.force) {
|
|
620
|
+
plan.push({ action: "overwrite", path: extraPath });
|
|
621
|
+
extraToWrite.push(extraPath);
|
|
622
|
+
}
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
plan.push({ action: "create", path: extraPath });
|
|
626
|
+
extraToWrite.push(extraPath);
|
|
627
|
+
}
|
|
628
|
+
if (args.compose) {
|
|
629
|
+
if (fs.existsSync(outputPath)) {
|
|
630
|
+
if (!args.force) {
|
|
631
|
+
throw new Error(`Output already exists: ${normalizePath(outputPath)} (use --force to overwrite)`);
|
|
632
|
+
}
|
|
633
|
+
plan.push({ action: "overwrite", path: outputPath });
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
plan.push({ action: "create", path: outputPath });
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
process.stdout.write(formatPlan(plan, rootDir));
|
|
640
|
+
if (args.dryRun) {
|
|
641
|
+
process.stdout.write("Dry run: no changes made.\n");
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
await confirmInit(args);
|
|
645
|
+
fs.mkdirSync(path.dirname(rulesetPath), { recursive: true });
|
|
646
|
+
fs.writeFileSync(`${rulesetPath}`, formatInitRuleset(ruleset), "utf8");
|
|
647
|
+
for (const extraPath of extraToWrite) {
|
|
648
|
+
fs.mkdirSync(path.dirname(extraPath), { recursive: true });
|
|
649
|
+
fs.writeFileSync(extraPath, LOCAL_RULES_TEMPLATE, "utf8");
|
|
650
|
+
}
|
|
651
|
+
process.stdout.write(`Initialized ruleset:\n- ${normalizePath(path.relative(rootDir, rulesetPath))}\n`);
|
|
652
|
+
if (extraToWrite.length > 0) {
|
|
653
|
+
process.stdout.write(`Initialized local rules:\n${extraToWrite
|
|
654
|
+
.map((filePath) => `- ${normalizePath(path.relative(rootDir, filePath))}`)
|
|
655
|
+
.join("\n")}\n`);
|
|
656
|
+
}
|
|
657
|
+
if (args.compose) {
|
|
658
|
+
const output = composeRuleset(rulesetPath, rootDir, { refresh: args.refresh ?? false });
|
|
659
|
+
process.stdout.write(`Composed AGENTS.md:\n- ${output}\n`);
|
|
660
|
+
}
|
|
661
|
+
};
|
|
382
662
|
const getRulesetFiles = (rootDir, specificRuleset, rulesetName) => {
|
|
383
663
|
if (specificRuleset) {
|
|
384
664
|
const resolved = resolveFrom(rootDir, specificRuleset);
|
|
@@ -398,8 +678,12 @@ const ensureSingleRuleset = (rulesetFiles, rootDir, rulesetName) => {
|
|
|
398
678
|
}
|
|
399
679
|
return rulesetFiles[0];
|
|
400
680
|
};
|
|
401
|
-
const main = () => {
|
|
681
|
+
const main = async () => {
|
|
402
682
|
const args = parseArgs(process.argv.slice(2));
|
|
683
|
+
if (args.version) {
|
|
684
|
+
process.stdout.write(`${getVersion()}\n`);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
403
687
|
if (args.help) {
|
|
404
688
|
process.stdout.write(`${usage}\n`);
|
|
405
689
|
return;
|
|
@@ -413,6 +697,15 @@ const main = () => {
|
|
|
413
697
|
const rulesetName = args.rulesetName || DEFAULT_RULESET_NAME;
|
|
414
698
|
const rulesetFiles = getRulesetFiles(rootDir, args.ruleset, rulesetName);
|
|
415
699
|
const command = args.command ?? "compose";
|
|
700
|
+
const logVerbose = (message) => {
|
|
701
|
+
if (args.verbose) {
|
|
702
|
+
process.stdout.write(`${message}\n`);
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
logVerbose("Verbose:");
|
|
706
|
+
logVerbose(`- Root: ${rootDir}`);
|
|
707
|
+
logVerbose(`- Ruleset name: ${rulesetName}`);
|
|
708
|
+
logVerbose(`- Ruleset files:\n${rulesetFiles.map((file) => ` - ${normalizePath(path.relative(rootDir, file))}`).join("\n")}`);
|
|
416
709
|
if (command === "edit-rules") {
|
|
417
710
|
const rulesetPath = ensureSingleRuleset(rulesetFiles, rootDir, rulesetName);
|
|
418
711
|
const rulesetDir = path.dirname(rulesetPath);
|
|
@@ -424,6 +717,10 @@ const main = () => {
|
|
|
424
717
|
process.stdout.write(`Rules workspace: ${workspaceRoot}\n`);
|
|
425
718
|
return;
|
|
426
719
|
}
|
|
720
|
+
if (command === "init") {
|
|
721
|
+
await initProject(args, rootDir, rulesetName);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
427
724
|
if (command === "apply-rules") {
|
|
428
725
|
const rulesetPath = ensureSingleRuleset(rulesetFiles, rootDir, rulesetName);
|
|
429
726
|
const rulesetDir = path.dirname(rulesetPath);
|
|
@@ -442,12 +739,15 @@ const main = () => {
|
|
|
442
739
|
.map((rulesetPath) => composeRuleset(rulesetPath, rootDir, { refresh: args.refresh }));
|
|
443
740
|
process.stdout.write(`Composed AGENTS.md:\n${outputs.map((file) => `- ${file}`).join("\n")}\n`);
|
|
444
741
|
};
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
742
|
+
const run = async () => {
|
|
743
|
+
try {
|
|
744
|
+
await main();
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
748
|
+
process.stderr.write(`${message}\n`);
|
|
749
|
+
process.stderr.write(`${usage}\n`);
|
|
750
|
+
process.exit(1);
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
void run();
|
package/package.json
CHANGED
package/tools/tool-rules.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Tool Rules (compose-agentsmd)
|
|
2
2
|
- Before starting any work, run `compose-agentsmd` from the project root.
|
|
3
3
|
- To update shared rules, run `compose-agentsmd edit-rules`, edit the workspace rules, then run `compose-agentsmd apply-rules`.
|
|
4
|
-
- Do not edit `AGENTS.md` directly; update the source rules and regenerate.
|
|
4
|
+
- Do not edit `AGENTS.md` directly; update the source rules and regenerate.
|
|
5
|
+
- When updating rules, include a detailed summary of what changed (added/removed/modified items) in the final response.
|
package/tools/usage.txt
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
Usage: compose-agentsmd [edit-rules|apply-rules] [--root <path>] [--ruleset <path>] [--ruleset-name <name>] [--refresh] [--clear-cache]
|
|
1
|
+
Usage: compose-agentsmd [edit-rules|apply-rules|init] [--root <path>] [--ruleset <path>] [--ruleset-name <name>] [--source <source>] [--domains <list>] [--extra <list>] [--output <file>] [--no-domains] [--no-extra] [--no-global] [--compose] [--dry-run] [--yes] [--force] [--refresh] [--clear-cache] [--version|-V] [--verbose|-v] [--help|-h]
|
|
2
2
|
|
|
3
3
|
Options:
|
|
4
|
-
--
|
|
5
|
-
--
|
|
4
|
+
--help, -h Show help and exit
|
|
5
|
+
--version, -V Show version and exit
|
|
6
|
+
--verbose, -v Show verbose diagnostics
|
|
7
|
+
--root <path> Project root directory (default: current working directory)
|
|
8
|
+
--ruleset <path> Only compose a single ruleset file
|
|
6
9
|
--ruleset-name <name> Ruleset filename in the project root (default: agent-ruleset.json)
|
|
7
|
-
--
|
|
8
|
-
--
|
|
10
|
+
--source <source> Rules source for init (default: github:owner/repo@latest)
|
|
11
|
+
--domains <list> Comma-separated domains for init (default: none)
|
|
12
|
+
--extra <list> Comma-separated extra rules for init
|
|
13
|
+
--output <file> Output filename for init (default: AGENTS.md)
|
|
14
|
+
--no-domains Initialize with no domains
|
|
15
|
+
--no-extra Initialize without extra rule files
|
|
16
|
+
--no-global Initialize without global rules
|
|
17
|
+
--compose Compose AGENTS.md after init
|
|
18
|
+
--dry-run Show init plan without writing files
|
|
19
|
+
--yes Skip init confirmation prompt
|
|
20
|
+
--force Overwrite existing files during init
|
|
21
|
+
--refresh Refresh cached remote rules
|
|
22
|
+
--clear-cache Remove cached remote rules and exit
|