compose-agentsmd 3.4.3 → 3.5.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.
@@ -42,6 +42,20 @@
42
42
  "type": "string",
43
43
  "minLength": 1
44
44
  }
45
+ },
46
+ "budget": {
47
+ "type": "object",
48
+ "additionalProperties": false,
49
+ "properties": {
50
+ "totalLines": {
51
+ "type": "integer",
52
+ "minimum": 1
53
+ },
54
+ "moduleLines": {
55
+ "type": "integer",
56
+ "minimum": 1
57
+ }
58
+ }
45
59
  }
46
60
  }
47
61
  }
@@ -18,6 +18,8 @@ const RULESET_SCHEMA_PATH = new URL("../agent-ruleset.schema.json", import.meta.
18
18
  const PACKAGE_JSON_PATH = new URL("../package.json", import.meta.url);
19
19
  const TOOL_RULES_PATH = new URL("../tools/tool-rules.md", import.meta.url);
20
20
  const USAGE_PATH = new URL("../tools/usage.txt", import.meta.url);
21
+ const DEFAULT_TOTAL_BUDGET = 350;
22
+ const DEFAULT_MODULE_BUDGET = 30;
21
23
  const readValueArg = (remaining, index, flag) => {
22
24
  const value = remaining[index + 1];
23
25
  if (!value) {
@@ -564,6 +566,23 @@ const composeRuleset = (rulesetPath, rootDir, options) => {
564
566
  const extraRules = Array.isArray(projectRuleset.extra) ? projectRuleset.extra : [];
565
567
  const directRulePaths = extraRules.map((rulePath) => resolveFrom(rulesetDir, rulePath));
566
568
  addRulePaths(directRulePaths, resolvedRules, seenRules);
569
+ const totalBudget = projectRuleset.budget?.totalLines ?? DEFAULT_TOTAL_BUDGET;
570
+ const moduleBudget = projectRuleset.budget?.moduleLines ?? DEFAULT_MODULE_BUDGET;
571
+ const normalizedGlobalRoot = normalizePath(path.resolve(globalRoot));
572
+ const globalRuleFiles = resolvedRules.filter((p) => normalizePath(p).startsWith(`${normalizedGlobalRoot}/`));
573
+ const moduleLineCounts = globalRuleFiles.map((filePath) => {
574
+ const content = fs.readFileSync(filePath, "utf8");
575
+ return { name: path.basename(filePath), lines: content.split("\n").length };
576
+ });
577
+ const totalLines = moduleLineCounts.reduce((sum, m) => sum + m.lines, 0);
578
+ const overBudgetModules = moduleLineCounts.filter((m) => m.lines > moduleBudget);
579
+ const budgetResult = {
580
+ totalLines,
581
+ totalBudget,
582
+ moduleBudget,
583
+ overBudgetModules,
584
+ exceeded: totalLines > totalBudget || overBudgetModules.length > 0
585
+ };
567
586
  const parts = resolvedRules.map((rulePath) => {
568
587
  const body = normalizeTrailingWhitespace(fs.readFileSync(rulePath, "utf8"));
569
588
  const sourcePath = formatRuleSourcePath(rulePath, rulesRoot, rulesetDir, projectRuleset.source, resolvedRef);
@@ -610,7 +629,8 @@ const composeRuleset = (rulesetPath, rootDir, options) => {
610
629
  return {
611
630
  output: composedOutputPath,
612
631
  outputs: composedFiles.map((file) => file.relativePath),
613
- agentsMdDiff
632
+ agentsMdDiff,
633
+ budgetResult
614
634
  };
615
635
  };
616
636
  const printAgentsMdDiffIfPresent = (result) => {
@@ -631,6 +651,19 @@ const printAgentsMdDiffIfPresent = (result) => {
631
651
  }
632
652
  process.stdout.write("--- END DIFF ---\n");
633
653
  };
654
+ const formatBudgetWarning = (result) => {
655
+ const totalInfo = result.totalLines > result.totalBudget
656
+ ? `: ${result.totalLines}/${result.totalBudget} lines`
657
+ : "";
658
+ const lines = [`⚠ Global rules budget exceeded${totalInfo}`];
659
+ if (result.overBudgetModules.length > 0) {
660
+ lines.push(` Over-budget modules (>${result.moduleBudget} lines):`);
661
+ for (const mod of result.overBudgetModules) {
662
+ lines.push(` ${mod.name}: ${mod.lines} lines`);
663
+ }
664
+ }
665
+ return `${lines.join("\n")}\n`;
666
+ };
634
667
  const LOCAL_RULES_TEMPLATE = "# Local Rules\n\n- Add project-specific instructions here.\n";
635
668
  const buildInitRuleset = (args) => {
636
669
  const domains = normalizeListOption(args.domains, "--domains");
@@ -788,7 +821,8 @@ const initProject = async (args, rootDir, rulesetName) => {
788
821
  initialized: [normalizePath(path.relative(rootDir, rulesetPath))],
789
822
  localRules: extraToWrite.map((filePath) => normalizePath(path.relative(rootDir, filePath))),
790
823
  composed: composedOutput ? composedOutput.outputs : [],
791
- dryRun: false
824
+ dryRun: false,
825
+ ...(composedOutput ? { budget: composedOutput.budgetResult } : {})
792
826
  }, null, 2) + "\n");
793
827
  }
794
828
  else if (!args.quiet) {
@@ -801,6 +835,9 @@ const initProject = async (args, rootDir, rulesetName) => {
801
835
  if (composedOutput) {
802
836
  process.stdout.write(`Composed AGENTS.md:\n${composedOutput.outputs.map((filePath) => `- ${filePath}`).join("\n")}\n`);
803
837
  printAgentsMdDiffIfPresent(composedOutput);
838
+ if (composedOutput.budgetResult.exceeded) {
839
+ process.stderr.write(formatBudgetWarning(composedOutput.budgetResult));
840
+ }
804
841
  }
805
842
  }
806
843
  };
@@ -887,11 +924,14 @@ const main = async () => {
887
924
  emitAgentsMdDiff: !args.quiet && !args.json
888
925
  });
889
926
  if (args.json) {
890
- process.stdout.write(JSON.stringify({ composed: output.outputs, dryRun: !!args.dryRun }, null, 2) + "\n");
927
+ process.stdout.write(JSON.stringify({ composed: output.outputs, dryRun: !!args.dryRun, budget: output.budgetResult }, null, 2) + "\n");
891
928
  }
892
929
  else if (!args.quiet) {
893
930
  process.stdout.write(`Composed AGENTS.md:\n${output.outputs.map((filePath) => `- ${filePath}`).join("\n")}\n`);
894
931
  printAgentsMdDiffIfPresent(output);
932
+ if (output.budgetResult.exceeded) {
933
+ process.stderr.write(formatBudgetWarning(output.budgetResult));
934
+ }
895
935
  }
896
936
  return;
897
937
  }
@@ -905,7 +945,11 @@ const main = async () => {
905
945
  emitAgentsMdDiff: !args.quiet && !args.json
906
946
  }));
907
947
  if (args.json) {
908
- process.stdout.write(JSON.stringify({ composed: outputs.flatMap((result) => result.outputs), dryRun: !!args.dryRun }, null, 2) + "\n");
948
+ process.stdout.write(JSON.stringify({
949
+ composed: outputs.flatMap((result) => result.outputs),
950
+ dryRun: !!args.dryRun,
951
+ budget: outputs[0].budgetResult
952
+ }, null, 2) + "\n");
909
953
  }
910
954
  else if (!args.quiet) {
911
955
  process.stdout.write(`Composed AGENTS.md:\n${outputs
@@ -915,6 +959,11 @@ const main = async () => {
915
959
  for (const result of outputs) {
916
960
  printAgentsMdDiffIfPresent(result);
917
961
  }
962
+ for (const result of outputs) {
963
+ if (result.budgetResult.exceeded) {
964
+ process.stderr.write(formatBudgetWarning(result.budgetResult));
965
+ }
966
+ }
918
967
  }
919
968
  };
920
969
  const run = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compose-agentsmd",
3
- "version": "3.4.3",
3
+ "version": "3.5.1",
4
4
  "description": "CLI tools for composing per-project AGENTS.md files from modular rule sets",
5
5
  "license": "MIT",
6
6
  "repository": {