compose-agentsmd 4.0.0 → 5.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/README.md +5 -0
- package/agent-ruleset.schema.json +2 -2
- package/dist/compose-agents.js +41 -28
- package/dist/git-fallback.js +7 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -115,11 +115,16 @@ Ruleset keys:
|
|
|
115
115
|
- `global` (optional): write `rules/global` to user-global instruction files (defaults to true). Set `false` to skip global writes.
|
|
116
116
|
- `domains` (optional): domain folders under `rules/domains/<domain>`.
|
|
117
117
|
- `extra` (optional): additional local rule files to append.
|
|
118
|
+
- `budget` (optional): global-rule budget thresholds in `o200k_base` tokens.
|
|
119
|
+
- `budget.totalTokens` (optional): total token budget for the composed global instruction output (defaults to `4500`).
|
|
120
|
+
- `budget.moduleTokens` (optional): per-module token budget for each composed global rule section (defaults to `400`).
|
|
118
121
|
- `claude` (optional): repository companion settings for Claude Code.
|
|
119
122
|
- `claude.enabled` (optional): enable/disable companion generation (defaults to `true`).
|
|
120
123
|
- `claude.output` (optional): companion file path (defaults to `CLAUDE.md`).
|
|
121
124
|
- `output` (optional): repository output file name (defaults to `AGENTS.md`).
|
|
122
125
|
|
|
126
|
+
When the composed global instruction output exceeds either budget, the CLI emits a warning to `stderr`. The machine-readable `--json` output includes the tokenizer name, total token count, and any over-budget modules.
|
|
127
|
+
|
|
123
128
|
### Ruleset schema validation
|
|
124
129
|
|
|
125
130
|
`compose-agentsmd` validates rulesets against `agent-ruleset.schema.json` on every run. If the ruleset does not conform to the schema, the tool exits with a schema error.
|
package/dist/compose-agents.js
CHANGED
|
@@ -6,6 +6,8 @@ import { execFileSync } from "node:child_process";
|
|
|
6
6
|
import readline from "node:readline";
|
|
7
7
|
import { Ajv } from "ajv";
|
|
8
8
|
import { createTwoFilesPatch } from "diff";
|
|
9
|
+
import { countTokens } from "gpt-tokenizer";
|
|
10
|
+
import { prepareGitFallbackDestination } from "./git-fallback.js";
|
|
9
11
|
const DEFAULT_RULESET_NAME = "agent-ruleset.json";
|
|
10
12
|
const DEFAULT_OUTPUT = "AGENTS.md";
|
|
11
13
|
const DEFAULT_CLAUDE_OUTPUT = "CLAUDE.md";
|
|
@@ -22,8 +24,9 @@ const RULESET_SCHEMA_PATH = new URL("../agent-ruleset.schema.json", import.meta.
|
|
|
22
24
|
const PACKAGE_JSON_PATH = new URL("../package.json", import.meta.url);
|
|
23
25
|
const TOOL_RULES_PATH = new URL("../tools/tool-rules.md", import.meta.url);
|
|
24
26
|
const USAGE_PATH = new URL("../tools/usage.txt", import.meta.url);
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
+
const BUDGET_TOKENIZER = "o200k_base";
|
|
28
|
+
const DEFAULT_TOTAL_BUDGET = 4500;
|
|
29
|
+
const DEFAULT_MODULE_BUDGET = 400;
|
|
27
30
|
const LINT_HEADER = "<!-- markdownlint-disable MD025 -->";
|
|
28
31
|
const readValueArg = (remaining, index, flag) => {
|
|
29
32
|
const value = remaining[index + 1];
|
|
@@ -441,7 +444,7 @@ const cloneAtRef = (repoUrl, ref, destination) => {
|
|
|
441
444
|
execGit(["clone", "--depth", "1", "--branch", ref, repoUrl, destination]);
|
|
442
445
|
};
|
|
443
446
|
const fetchCommit = (repoUrl, commitHash, destination) => {
|
|
444
|
-
|
|
447
|
+
prepareGitFallbackDestination(destination);
|
|
445
448
|
execGit(["init"], destination);
|
|
446
449
|
execGit(["remote", "add", "origin", repoUrl], destination);
|
|
447
450
|
execGit(["fetch", "--depth", "1", "origin", commitHash], destination);
|
|
@@ -584,6 +587,12 @@ const buildInstructionContent = (parts, includeToolRules) => {
|
|
|
584
587
|
}
|
|
585
588
|
return `${LINT_HEADER}\n${sections.join("\n\n")}\n`;
|
|
586
589
|
};
|
|
590
|
+
const countBudgetTokens = (content) => {
|
|
591
|
+
if (content.length === 0) {
|
|
592
|
+
return 0;
|
|
593
|
+
}
|
|
594
|
+
return countTokens(content);
|
|
595
|
+
};
|
|
587
596
|
const buildScopeDiff = (scope, targetPaths, desiredContent, rootDir) => {
|
|
588
597
|
if (targetPaths.length === 0) {
|
|
589
598
|
return undefined;
|
|
@@ -634,32 +643,36 @@ const composeRuleset = (rulesetPath, rootDir, options) => {
|
|
|
634
643
|
const extraRules = Array.isArray(projectRuleset.extra) ? projectRuleset.extra : [];
|
|
635
644
|
const directRulePaths = extraRules.map((rulePath) => resolveFrom(rulesetDir, rulePath));
|
|
636
645
|
addRulePaths(directRulePaths, resolvedRepositoryRules, seenRepositoryRules);
|
|
637
|
-
const totalBudget = projectRuleset.budget?.
|
|
638
|
-
const moduleBudget = projectRuleset.budget?.
|
|
639
|
-
const normalizedGlobalRoot = normalizePath(path.resolve(globalRoot));
|
|
640
|
-
const globalRuleFiles = resolvedGlobalRules.filter((p) => normalizePath(p).startsWith(`${normalizedGlobalRoot}/`));
|
|
641
|
-
const moduleLineCounts = globalRuleFiles.map((filePath) => {
|
|
642
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
643
|
-
return { name: path.basename(filePath), lines: content.split("\n").length };
|
|
644
|
-
});
|
|
645
|
-
const totalLines = moduleLineCounts.reduce((sum, m) => sum + m.lines, 0);
|
|
646
|
-
const overBudgetModules = moduleLineCounts.filter((m) => m.lines > moduleBudget);
|
|
647
|
-
const budgetResult = {
|
|
648
|
-
totalLines,
|
|
649
|
-
totalBudget,
|
|
650
|
-
moduleBudget,
|
|
651
|
-
overBudgetModules,
|
|
652
|
-
exceeded: totalLines > totalBudget || overBudgetModules.length > 0
|
|
653
|
-
};
|
|
646
|
+
const totalBudget = projectRuleset.budget?.totalTokens ?? DEFAULT_TOTAL_BUDGET;
|
|
647
|
+
const moduleBudget = projectRuleset.budget?.moduleTokens ?? DEFAULT_MODULE_BUDGET;
|
|
654
648
|
const buildRuleParts = (rulePaths) => rulePaths.map((rulePath) => {
|
|
655
649
|
const body = normalizeTrailingWhitespace(fs.readFileSync(rulePath, "utf8"));
|
|
656
650
|
const sourcePath = formatRuleSourcePath(rulePath, rulesRoot, rulesetDir, projectRuleset.source, resolvedRef);
|
|
657
|
-
return
|
|
651
|
+
return {
|
|
652
|
+
name: path.basename(rulePath),
|
|
653
|
+
content: `Source: ${sourcePath}\n\n${body}`
|
|
654
|
+
};
|
|
658
655
|
});
|
|
659
656
|
const repositoryParts = buildRuleParts(resolvedRepositoryRules);
|
|
660
657
|
const globalParts = buildRuleParts(resolvedGlobalRules);
|
|
661
|
-
const
|
|
662
|
-
const
|
|
658
|
+
const repositoryContentParts = repositoryParts.map((part) => part.content);
|
|
659
|
+
const globalContentParts = globalParts.map((part) => part.content);
|
|
660
|
+
const primaryOutputContent = buildInstructionContent(repositoryContentParts, true);
|
|
661
|
+
const globalOutputContent = buildInstructionContent(globalContentParts, false);
|
|
662
|
+
const moduleTokenCounts = globalParts.map((part) => ({
|
|
663
|
+
name: part.name,
|
|
664
|
+
tokens: countBudgetTokens(part.content)
|
|
665
|
+
}));
|
|
666
|
+
const totalTokens = countBudgetTokens(globalOutputContent);
|
|
667
|
+
const overBudgetModules = moduleTokenCounts.filter((module) => module.tokens > moduleBudget);
|
|
668
|
+
const budgetResult = {
|
|
669
|
+
tokenizer: BUDGET_TOKENIZER,
|
|
670
|
+
totalTokens,
|
|
671
|
+
totalBudget,
|
|
672
|
+
moduleBudget,
|
|
673
|
+
overBudgetModules,
|
|
674
|
+
exceeded: totalTokens > totalBudget || overBudgetModules.length > 0
|
|
675
|
+
};
|
|
663
676
|
const repositoryOutputs = [toDisplayPath(rootDir, primaryOutputPath)];
|
|
664
677
|
const globalOutputs = globalOutputPaths.map((filePath) => toDisplayPath(rootDir, filePath));
|
|
665
678
|
const composedFiles = [
|
|
@@ -747,14 +760,14 @@ const formatComposedOutputs = (result) => {
|
|
|
747
760
|
return `${lines.join("\n")}\n`;
|
|
748
761
|
};
|
|
749
762
|
const formatBudgetWarning = (result) => {
|
|
750
|
-
const totalInfo = result.
|
|
751
|
-
? `: ${result.
|
|
763
|
+
const totalInfo = result.totalTokens > result.totalBudget
|
|
764
|
+
? `: ${result.totalTokens}/${result.totalBudget} tokens`
|
|
752
765
|
: "";
|
|
753
|
-
const lines = [`⚠ Global rules budget exceeded${totalInfo}`];
|
|
766
|
+
const lines = [`⚠ Global rules budget exceeded (${result.tokenizer})${totalInfo}`];
|
|
754
767
|
if (result.overBudgetModules.length > 0) {
|
|
755
|
-
lines.push(` Over-budget modules (
|
|
768
|
+
lines.push(` Over-budget modules (> ${result.moduleBudget} tokens):`);
|
|
756
769
|
for (const mod of result.overBudgetModules) {
|
|
757
|
-
lines.push(` ${mod.name}: ${mod.
|
|
770
|
+
lines.push(` ${mod.name}: ${mod.tokens} tokens`);
|
|
758
771
|
}
|
|
759
772
|
}
|
|
760
773
|
return `${lines.join("\n")}\n`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "compose-agentsmd",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "CLI tools for composing repository-local and user-global agent instruction files from modular rule sets",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -62,7 +62,8 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"ajv": "^8.17.1",
|
|
65
|
-
"diff": "^8.0.3"
|
|
65
|
+
"diff": "^8.0.3",
|
|
66
|
+
"gpt-tokenizer": "^3.4.0"
|
|
66
67
|
},
|
|
67
68
|
"overrides": {
|
|
68
69
|
"minimatch": "^10.2.4",
|