compose-agentsmd 3.2.7 → 3.3.1
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 +12 -1
- package/agent-ruleset.schema.json +13 -0
- package/dist/compose-agents.js +86 -26
- package/package.json +1 -1
- package/tools/usage.txt +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,8 @@ compose-agentsmd
|
|
|
32
32
|
|
|
33
33
|
The tool reads `agent-ruleset.json` from the given root directory (default: current working directory), and writes the output file specified by the ruleset. If `output` is omitted, it defaults to `AGENTS.md`.
|
|
34
34
|
|
|
35
|
+
By default, compose also writes a `CLAUDE.md` companion file containing an `@...` import pointing to the primary output file. You can disable this with `claude.enabled: false` in the ruleset.
|
|
36
|
+
|
|
35
37
|
The tool prepends a small "Tool Rules" block to every generated `AGENTS.md` so agents know how to regenerate or update rules.
|
|
36
38
|
Each composed rule section is also prefixed with the source file path that produced it.
|
|
37
39
|
|
|
@@ -51,6 +53,7 @@ Defaults:
|
|
|
51
53
|
- `domains`: empty
|
|
52
54
|
- `extra`: empty
|
|
53
55
|
- `global`: omitted (defaults to `true`)
|
|
56
|
+
- `claude`: `{ "enabled": true, "output": "CLAUDE.md" }`
|
|
54
57
|
- `output`: `AGENTS.md`
|
|
55
58
|
|
|
56
59
|
Use `--dry-run` to preview actions, `--force` to overwrite existing files, and `--compose` to generate `AGENTS.md` immediately.
|
|
@@ -84,6 +87,11 @@ Ruleset files accept JSON with `//` or `/* */` comments.
|
|
|
84
87
|
"domains": ["node", "unreal"],
|
|
85
88
|
// Additional local rule files to append.
|
|
86
89
|
"extra": ["agent-rules-local/custom.md"],
|
|
90
|
+
// Optional Claude Code companion output.
|
|
91
|
+
"claude": {
|
|
92
|
+
"enabled": true,
|
|
93
|
+
"output": "CLAUDE.md"
|
|
94
|
+
},
|
|
87
95
|
// Output file name.
|
|
88
96
|
"output": "AGENTS.md"
|
|
89
97
|
}
|
|
@@ -95,6 +103,9 @@ Ruleset keys:
|
|
|
95
103
|
- `global` (optional): include `rules/global` (defaults to true). Omit this unless you want to disable globals.
|
|
96
104
|
- `domains` (optional): domain folders under `rules/domains/<domain>`.
|
|
97
105
|
- `extra` (optional): additional local rule files to append.
|
|
106
|
+
- `claude` (optional): companion settings for Claude Code.
|
|
107
|
+
- `claude.enabled` (optional): enable/disable companion generation (defaults to `true`).
|
|
108
|
+
- `claude.output` (optional): companion file path (defaults to `CLAUDE.md`).
|
|
98
109
|
- `output` (optional): output file name (defaults to `AGENTS.md`).
|
|
99
110
|
|
|
100
111
|
### Ruleset schema validation
|
|
@@ -121,7 +132,7 @@ Remote sources are cached under `~/.agentsmd/cache/<owner>/<repo>/<ref>/`. Use `
|
|
|
121
132
|
- `--no-domains`: initialize with no domains
|
|
122
133
|
- `--no-extra`: initialize without extra rule files
|
|
123
134
|
- `--no-global`: initialize without global rules
|
|
124
|
-
- `--compose`: compose
|
|
135
|
+
- `--compose`: compose output file(s) after `init`
|
|
125
136
|
- `--dry-run`: show init plan without writing files
|
|
126
137
|
- `--yes`: skip init confirmation prompt
|
|
127
138
|
- `--force`: overwrite existing files during init
|
|
@@ -16,6 +16,19 @@
|
|
|
16
16
|
"type": "string",
|
|
17
17
|
"minLength": 1
|
|
18
18
|
},
|
|
19
|
+
"claude": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"enabled": {
|
|
24
|
+
"type": "boolean"
|
|
25
|
+
},
|
|
26
|
+
"output": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"minLength": 1
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
19
32
|
"domains": {
|
|
20
33
|
"type": "array",
|
|
21
34
|
"items": {
|
package/dist/compose-agents.js
CHANGED
|
@@ -8,6 +8,7 @@ import { Ajv } from "ajv";
|
|
|
8
8
|
import { createTwoFilesPatch } from "diff";
|
|
9
9
|
const DEFAULT_RULESET_NAME = "agent-ruleset.json";
|
|
10
10
|
const DEFAULT_OUTPUT = "AGENTS.md";
|
|
11
|
+
const DEFAULT_CLAUDE_OUTPUT = "CLAUDE.md";
|
|
11
12
|
const DEFAULT_CACHE_ROOT = path.join(os.homedir(), ".agentsmd", "cache");
|
|
12
13
|
const DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), ".agentsmd", "workspace");
|
|
13
14
|
const DEFAULT_INIT_SOURCE = "github:owner/repo@latest";
|
|
@@ -276,6 +277,15 @@ const readProjectRuleset = (rulesetPath) => {
|
|
|
276
277
|
if (ruleset.output === undefined) {
|
|
277
278
|
ruleset.output = DEFAULT_OUTPUT;
|
|
278
279
|
}
|
|
280
|
+
if (ruleset.claude === undefined) {
|
|
281
|
+
ruleset.claude = {};
|
|
282
|
+
}
|
|
283
|
+
if (ruleset.claude.enabled === undefined) {
|
|
284
|
+
ruleset.claude.enabled = true;
|
|
285
|
+
}
|
|
286
|
+
if (ruleset.claude.output === undefined) {
|
|
287
|
+
ruleset.claude.output = DEFAULT_CLAUDE_OUTPUT;
|
|
288
|
+
}
|
|
279
289
|
if (ruleset.global === undefined) {
|
|
280
290
|
ruleset.global = true;
|
|
281
291
|
}
|
|
@@ -516,12 +526,25 @@ const formatRuleSourcePath = (rulePath, rulesRoot, rulesetDir, source, resolvedR
|
|
|
516
526
|
const result = normalizePath(path.relative(rulesetDir, rulePath));
|
|
517
527
|
return result;
|
|
518
528
|
};
|
|
529
|
+
const resolveOutputPaths = (rulesetDir, projectRuleset) => {
|
|
530
|
+
const primaryOutputPath = resolveFrom(rulesetDir, projectRuleset.output ?? DEFAULT_OUTPUT);
|
|
531
|
+
const claude = projectRuleset.claude ?? {};
|
|
532
|
+
const companionEnabled = claude.enabled !== false;
|
|
533
|
+
const configuredCompanionPath = resolveFrom(rulesetDir, claude.output ?? DEFAULT_CLAUDE_OUTPUT);
|
|
534
|
+
if (!companionEnabled || path.resolve(primaryOutputPath) === path.resolve(configuredCompanionPath)) {
|
|
535
|
+
return { primaryOutputPath };
|
|
536
|
+
}
|
|
537
|
+
return { primaryOutputPath, companionOutputPath: configuredCompanionPath };
|
|
538
|
+
};
|
|
539
|
+
const buildClaudeCompanionContent = (primaryOutputPath, companionOutputPath) => {
|
|
540
|
+
const relativeImportPath = normalizePath(path.relative(path.dirname(companionOutputPath), primaryOutputPath));
|
|
541
|
+
return `@${relativeImportPath}\n`;
|
|
542
|
+
};
|
|
519
543
|
const composeRuleset = (rulesetPath, rootDir, options) => {
|
|
520
544
|
const rulesetDir = path.dirname(rulesetPath);
|
|
521
545
|
const projectRuleset = readProjectRuleset(rulesetPath);
|
|
522
|
-
const
|
|
523
|
-
const
|
|
524
|
-
const composedOutputPath = normalizePath(path.relative(rootDir, outputPath));
|
|
546
|
+
const { primaryOutputPath, companionOutputPath } = resolveOutputPaths(rulesetDir, projectRuleset);
|
|
547
|
+
const composedOutputPath = normalizePath(path.relative(rootDir, primaryOutputPath));
|
|
525
548
|
const { rulesRoot, resolvedRef } = resolveRulesRoot(rulesetDir, projectRuleset.source, options.refresh ?? false);
|
|
526
549
|
const globalRoot = path.join(rulesRoot, "global");
|
|
527
550
|
const domainsRoot = path.join(rulesRoot, "domains");
|
|
@@ -545,25 +568,45 @@ const composeRuleset = (rulesetPath, rootDir, options) => {
|
|
|
545
568
|
});
|
|
546
569
|
const lintHeader = "<!-- markdownlint-disable MD025 -->";
|
|
547
570
|
const toolRules = normalizeTrailingWhitespace(TOOL_RULES);
|
|
548
|
-
const
|
|
571
|
+
const primaryOutputContent = `${lintHeader}\n${[toolRules, ...parts].join("\n\n")}\n`;
|
|
572
|
+
const composedFiles = [
|
|
573
|
+
{
|
|
574
|
+
absolutePath: primaryOutputPath,
|
|
575
|
+
relativePath: composedOutputPath,
|
|
576
|
+
content: primaryOutputContent
|
|
577
|
+
}
|
|
578
|
+
];
|
|
579
|
+
if (companionOutputPath) {
|
|
580
|
+
composedFiles.push({
|
|
581
|
+
absolutePath: companionOutputPath,
|
|
582
|
+
relativePath: normalizePath(path.relative(rootDir, companionOutputPath)),
|
|
583
|
+
content: buildClaudeCompanionContent(primaryOutputPath, companionOutputPath)
|
|
584
|
+
});
|
|
585
|
+
}
|
|
549
586
|
let agentsMdDiff;
|
|
550
|
-
if (options.emitAgentsMdDiff && path.basename(
|
|
551
|
-
const before = fs.existsSync(
|
|
552
|
-
if (before ===
|
|
587
|
+
if (options.emitAgentsMdDiff && path.basename(primaryOutputPath) === DEFAULT_OUTPUT) {
|
|
588
|
+
const before = fs.existsSync(primaryOutputPath) ? fs.readFileSync(primaryOutputPath, "utf8") : "";
|
|
589
|
+
if (before === primaryOutputContent) {
|
|
553
590
|
agentsMdDiff = { status: "unchanged" };
|
|
554
591
|
}
|
|
555
592
|
else {
|
|
556
593
|
agentsMdDiff = {
|
|
557
594
|
status: "updated",
|
|
558
|
-
patch: createTwoFilesPatch(`a/${composedOutputPath}`, `b/${composedOutputPath}`, before,
|
|
595
|
+
patch: createTwoFilesPatch(`a/${composedOutputPath}`, `b/${composedOutputPath}`, before, primaryOutputContent, "", "", { context: 3 })
|
|
559
596
|
};
|
|
560
597
|
}
|
|
561
598
|
}
|
|
562
599
|
if (!options.dryRun) {
|
|
563
|
-
|
|
564
|
-
|
|
600
|
+
for (const file of composedFiles) {
|
|
601
|
+
fs.mkdirSync(path.dirname(file.absolutePath), { recursive: true });
|
|
602
|
+
fs.writeFileSync(file.absolutePath, file.content, "utf8");
|
|
603
|
+
}
|
|
565
604
|
}
|
|
566
|
-
return {
|
|
605
|
+
return {
|
|
606
|
+
output: composedOutputPath,
|
|
607
|
+
outputs: composedFiles.map((file) => file.relativePath),
|
|
608
|
+
agentsMdDiff
|
|
609
|
+
};
|
|
567
610
|
};
|
|
568
611
|
const printAgentsMdDiffIfPresent = (result) => {
|
|
569
612
|
if (!result.agentsMdDiff) {
|
|
@@ -589,7 +632,11 @@ const buildInitRuleset = (args) => {
|
|
|
589
632
|
const extra = normalizeListOption(args.extra, "--extra");
|
|
590
633
|
const ruleset = {
|
|
591
634
|
source: args.source ?? DEFAULT_INIT_SOURCE,
|
|
592
|
-
output: args.output ?? DEFAULT_OUTPUT
|
|
635
|
+
output: args.output ?? DEFAULT_OUTPUT,
|
|
636
|
+
claude: {
|
|
637
|
+
enabled: true,
|
|
638
|
+
output: DEFAULT_CLAUDE_OUTPUT
|
|
639
|
+
}
|
|
593
640
|
};
|
|
594
641
|
if (args.global === false) {
|
|
595
642
|
ruleset.global = false;
|
|
@@ -607,6 +654,8 @@ const buildInitRuleset = (args) => {
|
|
|
607
654
|
const formatInitRuleset = (ruleset) => {
|
|
608
655
|
const domainsValue = JSON.stringify(ruleset.domains ?? []);
|
|
609
656
|
const extraValue = JSON.stringify(ruleset.extra ?? []);
|
|
657
|
+
const claudeEnabled = ruleset.claude?.enabled ?? true;
|
|
658
|
+
const claudeOutput = ruleset.claude?.output ?? DEFAULT_CLAUDE_OUTPUT;
|
|
610
659
|
const lines = [
|
|
611
660
|
"{",
|
|
612
661
|
' // Rules source. Use github:owner/repo@ref or a local path.',
|
|
@@ -620,6 +669,11 @@ const formatInitRuleset = (ruleset) => {
|
|
|
620
669
|
lines.push(' // Include rules/global from the source.');
|
|
621
670
|
lines.push(' "global": false,');
|
|
622
671
|
}
|
|
672
|
+
lines.push(' // Claude Code companion output settings.');
|
|
673
|
+
lines.push(' "claude": {');
|
|
674
|
+
lines.push(` "enabled": ${claudeEnabled ? "true" : "false"},`);
|
|
675
|
+
lines.push(` "output": "${claudeOutput}"`);
|
|
676
|
+
lines.push(" },");
|
|
623
677
|
lines.push(' // Output file name.');
|
|
624
678
|
lines.push(` "output": "${ruleset.output ?? DEFAULT_OUTPUT}"`);
|
|
625
679
|
lines.push("}");
|
|
@@ -649,7 +703,7 @@ const initProject = async (args, rootDir, rulesetName) => {
|
|
|
649
703
|
const rulesetPath = args.ruleset ? resolveFrom(rootDir, args.ruleset) : path.join(rootDir, rulesetName);
|
|
650
704
|
const rulesetDir = path.dirname(rulesetPath);
|
|
651
705
|
const ruleset = buildInitRuleset(args);
|
|
652
|
-
const
|
|
706
|
+
const outputPaths = resolveOutputPaths(rulesetDir, ruleset);
|
|
653
707
|
const plan = [];
|
|
654
708
|
if (fs.existsSync(rulesetPath)) {
|
|
655
709
|
if (!args.force) {
|
|
@@ -674,14 +728,20 @@ const initProject = async (args, rootDir, rulesetName) => {
|
|
|
674
728
|
extraToWrite.push(extraPath);
|
|
675
729
|
}
|
|
676
730
|
if (args.compose) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
731
|
+
const composedTargets = [outputPaths.primaryOutputPath];
|
|
732
|
+
if (outputPaths.companionOutputPath) {
|
|
733
|
+
composedTargets.push(outputPaths.companionOutputPath);
|
|
734
|
+
}
|
|
735
|
+
for (const composedTarget of composedTargets) {
|
|
736
|
+
if (fs.existsSync(composedTarget)) {
|
|
737
|
+
if (!args.force) {
|
|
738
|
+
throw new Error(`Output already exists: ${normalizePath(composedTarget)} (use --force to overwrite)`);
|
|
739
|
+
}
|
|
740
|
+
plan.push({ action: "overwrite", path: composedTarget });
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
plan.push({ action: "create", path: composedTarget });
|
|
680
744
|
}
|
|
681
|
-
plan.push({ action: "overwrite", path: outputPath });
|
|
682
|
-
}
|
|
683
|
-
else {
|
|
684
|
-
plan.push({ action: "create", path: outputPath });
|
|
685
745
|
}
|
|
686
746
|
}
|
|
687
747
|
if (!args.quiet && !args.json) {
|
|
@@ -720,7 +780,7 @@ const initProject = async (args, rootDir, rulesetName) => {
|
|
|
720
780
|
process.stdout.write(JSON.stringify({
|
|
721
781
|
initialized: [normalizePath(path.relative(rootDir, rulesetPath))],
|
|
722
782
|
localRules: extraToWrite.map((filePath) => normalizePath(path.relative(rootDir, filePath))),
|
|
723
|
-
composed: composedOutput ?
|
|
783
|
+
composed: composedOutput ? composedOutput.outputs : [],
|
|
724
784
|
dryRun: false
|
|
725
785
|
}, null, 2) + "\n");
|
|
726
786
|
}
|
|
@@ -732,7 +792,7 @@ const initProject = async (args, rootDir, rulesetName) => {
|
|
|
732
792
|
.join("\n")}\n`);
|
|
733
793
|
}
|
|
734
794
|
if (composedOutput) {
|
|
735
|
-
process.stdout.write(`Composed AGENTS.md:\n
|
|
795
|
+
process.stdout.write(`Composed AGENTS.md:\n${composedOutput.outputs.map((filePath) => `- ${filePath}`).join("\n")}\n`);
|
|
736
796
|
printAgentsMdDiffIfPresent(composedOutput);
|
|
737
797
|
}
|
|
738
798
|
}
|
|
@@ -820,10 +880,10 @@ const main = async () => {
|
|
|
820
880
|
emitAgentsMdDiff: !args.quiet && !args.json
|
|
821
881
|
});
|
|
822
882
|
if (args.json) {
|
|
823
|
-
process.stdout.write(JSON.stringify({ composed:
|
|
883
|
+
process.stdout.write(JSON.stringify({ composed: output.outputs, dryRun: !!args.dryRun }, null, 2) + "\n");
|
|
824
884
|
}
|
|
825
885
|
else if (!args.quiet) {
|
|
826
|
-
process.stdout.write(`Composed AGENTS.md:\n
|
|
886
|
+
process.stdout.write(`Composed AGENTS.md:\n${output.outputs.map((filePath) => `- ${filePath}`).join("\n")}\n`);
|
|
827
887
|
printAgentsMdDiffIfPresent(output);
|
|
828
888
|
}
|
|
829
889
|
return;
|
|
@@ -840,10 +900,10 @@ const main = async () => {
|
|
|
840
900
|
emitAgentsMdDiff: !args.quiet && !args.json
|
|
841
901
|
}));
|
|
842
902
|
if (args.json) {
|
|
843
|
-
process.stdout.write(JSON.stringify({ composed: outputs.
|
|
903
|
+
process.stdout.write(JSON.stringify({ composed: outputs.flatMap((result) => result.outputs), dryRun: !!args.dryRun }, null, 2) + "\n");
|
|
844
904
|
}
|
|
845
905
|
else if (!args.quiet) {
|
|
846
|
-
process.stdout.write(`Composed AGENTS.md:\n${outputs.
|
|
906
|
+
process.stdout.write(`Composed AGENTS.md:\n${outputs.flatMap((result) => result.outputs).map((filePath) => `- ${filePath}`).join("\n")}\n`);
|
|
847
907
|
for (const result of outputs) {
|
|
848
908
|
printAgentsMdDiffIfPresent(result);
|
|
849
909
|
}
|
package/package.json
CHANGED
package/tools/usage.txt
CHANGED
|
@@ -16,7 +16,7 @@ Options:
|
|
|
16
16
|
--no-domains Initialize with no domains
|
|
17
17
|
--no-extra Initialize without extra rule files
|
|
18
18
|
--no-global Initialize without global rules
|
|
19
|
-
--compose Compose
|
|
19
|
+
--compose Compose output file(s) after init
|
|
20
20
|
--dry-run Show init plan without writing files
|
|
21
21
|
--yes Skip init confirmation prompt
|
|
22
22
|
--force Overwrite existing files during init
|