compose-agentsmd 3.2.6 → 3.2.7

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 CHANGED
@@ -35,6 +35,8 @@ The tool reads `agent-ruleset.json` from the given root directory (default: curr
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
  Each composed rule section is also prefixed with the source file path that produced it.
37
37
 
38
+ When the output file is `AGENTS.md`, the CLI also prints a unified diff for `AGENTS.md` when it changes (and prints `AGENTS.md unchanged.` when it does not). This works even when the project is not under git. `--quiet` and `--json` suppress this output.
39
+
38
40
  ## Setup (init)
39
41
 
40
42
  For a project that does not have a ruleset yet, bootstrap one with `init`:
@@ -5,6 +5,7 @@ import os from "node:os";
5
5
  import { execFileSync } from "node:child_process";
6
6
  import readline from "node:readline";
7
7
  import { Ajv } from "ajv";
8
+ import { createTwoFilesPatch } from "diff";
8
9
  const DEFAULT_RULESET_NAME = "agent-ruleset.json";
9
10
  const DEFAULT_OUTPUT = "AGENTS.md";
10
11
  const DEFAULT_CACHE_ROOT = path.join(os.homedir(), ".agentsmd", "cache");
@@ -520,6 +521,7 @@ const composeRuleset = (rulesetPath, rootDir, options) => {
520
521
  const projectRuleset = readProjectRuleset(rulesetPath);
521
522
  const outputFileName = projectRuleset.output ?? DEFAULT_OUTPUT;
522
523
  const outputPath = resolveFrom(rulesetDir, outputFileName);
524
+ const composedOutputPath = normalizePath(path.relative(rootDir, outputPath));
523
525
  const { rulesRoot, resolvedRef } = resolveRulesRoot(rulesetDir, projectRuleset.source, options.refresh ?? false);
524
526
  const globalRoot = path.join(rulesRoot, "global");
525
527
  const domainsRoot = path.join(rulesRoot, "domains");
@@ -544,11 +546,42 @@ const composeRuleset = (rulesetPath, rootDir, options) => {
544
546
  const lintHeader = "<!-- markdownlint-disable MD025 -->";
545
547
  const toolRules = normalizeTrailingWhitespace(TOOL_RULES);
546
548
  const output = `${lintHeader}\n${[toolRules, ...parts].join("\n\n")}\n`;
549
+ let agentsMdDiff;
550
+ if (options.emitAgentsMdDiff && path.basename(outputPath) === DEFAULT_OUTPUT) {
551
+ const before = fs.existsSync(outputPath) ? fs.readFileSync(outputPath, "utf8") : "";
552
+ if (before === output) {
553
+ agentsMdDiff = { status: "unchanged" };
554
+ }
555
+ else {
556
+ agentsMdDiff = {
557
+ status: "updated",
558
+ patch: createTwoFilesPatch(`a/${composedOutputPath}`, `b/${composedOutputPath}`, before, output, "", "", { context: 3 })
559
+ };
560
+ }
561
+ }
547
562
  if (!options.dryRun) {
548
563
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
549
564
  fs.writeFileSync(outputPath, output, "utf8");
550
565
  }
551
- return normalizePath(path.relative(rootDir, outputPath));
566
+ return { output: composedOutputPath, agentsMdDiff };
567
+ };
568
+ const printAgentsMdDiffIfPresent = (result) => {
569
+ if (!result.agentsMdDiff) {
570
+ return;
571
+ }
572
+ if (result.agentsMdDiff.status === "unchanged") {
573
+ process.stdout.write("AGENTS.md unchanged.\n");
574
+ return;
575
+ }
576
+ process.stdout.write("AGENTS.md updated. ACTION (agent): refresh rule recognition from the diff below.\n");
577
+ process.stdout.write("--- BEGIN DIFF ---\n");
578
+ if (result.agentsMdDiff.patch) {
579
+ process.stdout.write(result.agentsMdDiff.patch);
580
+ if (!result.agentsMdDiff.patch.endsWith("\n")) {
581
+ process.stdout.write("\n");
582
+ }
583
+ }
584
+ process.stdout.write("--- END DIFF ---\n");
552
585
  };
553
586
  const LOCAL_RULES_TEMPLATE = "# Local Rules\n\n- Add project-specific instructions here.\n";
554
587
  const buildInitRuleset = (args) => {
@@ -678,13 +711,16 @@ const initProject = async (args, rootDir, rulesetName) => {
678
711
  }
679
712
  let composedOutput;
680
713
  if (args.compose) {
681
- composedOutput = composeRuleset(rulesetPath, rootDir, { refresh: args.refresh ?? false });
714
+ composedOutput = composeRuleset(rulesetPath, rootDir, {
715
+ refresh: args.refresh ?? false,
716
+ emitAgentsMdDiff: !args.quiet && !args.json
717
+ });
682
718
  }
683
719
  if (args.json) {
684
720
  process.stdout.write(JSON.stringify({
685
721
  initialized: [normalizePath(path.relative(rootDir, rulesetPath))],
686
722
  localRules: extraToWrite.map((filePath) => normalizePath(path.relative(rootDir, filePath))),
687
- composed: composedOutput ? [composedOutput] : [],
723
+ composed: composedOutput ? [composedOutput.output] : [],
688
724
  dryRun: false
689
725
  }, null, 2) + "\n");
690
726
  }
@@ -696,7 +732,8 @@ const initProject = async (args, rootDir, rulesetName) => {
696
732
  .join("\n")}\n`);
697
733
  }
698
734
  if (composedOutput) {
699
- process.stdout.write(`Composed AGENTS.md:\n- ${composedOutput}\n`);
735
+ process.stdout.write(`Composed AGENTS.md:\n- ${composedOutput.output}\n`);
736
+ printAgentsMdDiffIfPresent(composedOutput);
700
737
  }
701
738
  }
702
739
  };
@@ -777,12 +814,17 @@ const main = async () => {
777
814
  const rulesetDir = path.dirname(rulesetPath);
778
815
  const ruleset = readProjectRuleset(rulesetPath);
779
816
  applyRulesFromWorkspace(rulesetDir, ruleset.source);
780
- const output = composeRuleset(rulesetPath, rootDir, { refresh: true, dryRun: args.dryRun });
817
+ const output = composeRuleset(rulesetPath, rootDir, {
818
+ refresh: true,
819
+ dryRun: args.dryRun,
820
+ emitAgentsMdDiff: !args.quiet && !args.json
821
+ });
781
822
  if (args.json) {
782
- process.stdout.write(JSON.stringify({ composed: [output], dryRun: !!args.dryRun }, null, 2) + "\n");
823
+ process.stdout.write(JSON.stringify({ composed: [output.output], dryRun: !!args.dryRun }, null, 2) + "\n");
783
824
  }
784
825
  else if (!args.quiet) {
785
- process.stdout.write(`Composed AGENTS.md:\n- ${output}\n`);
826
+ process.stdout.write(`Composed AGENTS.md:\n- ${output.output}\n`);
827
+ printAgentsMdDiffIfPresent(output);
786
828
  }
787
829
  return;
788
830
  }
@@ -792,12 +834,19 @@ const main = async () => {
792
834
  }
793
835
  const outputs = rulesetFiles
794
836
  .sort()
795
- .map((rulesetPath) => composeRuleset(rulesetPath, rootDir, { refresh: args.refresh, dryRun: args.dryRun }));
837
+ .map((rulesetPath) => composeRuleset(rulesetPath, rootDir, {
838
+ refresh: args.refresh,
839
+ dryRun: args.dryRun,
840
+ emitAgentsMdDiff: !args.quiet && !args.json
841
+ }));
796
842
  if (args.json) {
797
- process.stdout.write(JSON.stringify({ composed: outputs, dryRun: !!args.dryRun }, null, 2) + "\n");
843
+ process.stdout.write(JSON.stringify({ composed: outputs.map((result) => result.output), dryRun: !!args.dryRun }, null, 2) + "\n");
798
844
  }
799
845
  else if (!args.quiet) {
800
- process.stdout.write(`Composed AGENTS.md:\n${outputs.map((file) => `- ${file}`).join("\n")}\n`);
846
+ process.stdout.write(`Composed AGENTS.md:\n${outputs.map((result) => `- ${result.output}`).join("\n")}\n`);
847
+ for (const result of outputs) {
848
+ printAgentsMdDiffIfPresent(result);
849
+ }
801
850
  }
802
851
  };
803
852
  const run = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compose-agentsmd",
3
- "version": "3.2.6",
3
+ "version": "3.2.7",
4
4
  "description": "CLI tools for composing per-project AGENTS.md files from modular rule sets",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -48,6 +48,7 @@
48
48
  "typescript": "^5.7.3"
49
49
  },
50
50
  "dependencies": {
51
- "ajv": "^8.17.1"
51
+ "ajv": "^8.17.1",
52
+ "diff": "^8.0.3"
52
53
  }
53
54
  }