agentsmap 0.1.0 → 0.1.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 CHANGED
@@ -30,6 +30,12 @@ Interactive by default — prompts you for each file's purpose. Use `--non-inter
30
30
  agentsmap init --non-interactive
31
31
  ```
32
32
 
33
+ Include `AGENTS.md` files from installed dependencies:
34
+
35
+ ```bash
36
+ agentsmap init --deps
37
+ ```
38
+
33
39
  ### `agentsmap validate`
34
40
 
35
41
  Check that your `AGENTS.map.md` is valid: all listed paths exist, required fields are present, no duplicates, no path traversal.
@@ -49,29 +55,17 @@ Exits with code 1 on errors. Warnings (like unlisted `AGENTS.md` files) don't fa
49
55
 
50
56
  ### `agentsmap resolve <path>`
51
57
 
52
- Show which `AGENTS.md` files apply to a given path, ranked by specificity.
58
+ Show which `AGENTS.md` files apply to a given path, ranked by priority then specificity.
53
59
 
54
60
  ```bash
55
61
  agentsmap resolve src/services/payments/checkout.ts
56
62
  ```
57
63
 
58
- Output:
64
+ Use `--tag` to find entries by domain instead of path:
59
65
 
60
- ```
61
- 2 AGENTS.md file(s) apply to src/services/payments/checkout.ts:
62
-
63
- (most specific)
64
- services/payments/AGENTS.md
65
- Purpose: PCI rules, Stripe integration patterns.
66
- Matched pattern: services/payments/**
67
- Specificity: 6
68
- Owners: @payments-team
69
-
70
- (#2)
71
- AGENTS.md
72
- Purpose: Global repo conventions.
73
- Matched pattern: **
74
- Specificity: 0
66
+ ```bash
67
+ agentsmap resolve --tag frontend
68
+ agentsmap resolve --tag backend,compliance
75
69
  ```
76
70
 
77
71
  Use `--json` for machine-readable output:
@@ -88,6 +82,12 @@ Find all `AGENTS.md` files in your repo and show whether they're listed in the m
88
82
  agentsmap discover
89
83
  ```
90
84
 
85
+ Include dependencies:
86
+
87
+ ```bash
88
+ agentsmap discover --deps
89
+ ```
90
+
91
91
  Output shows listed files with `+` and unlisted with `?`, along with suggested purposes.
92
92
 
93
93
  ## Programmatic API
@@ -120,7 +120,18 @@ const result = validate(map, "/path/to/repo");
120
120
  - Path: /services/payments/AGENTS.md
121
121
  - Purpose: PCI rules, Stripe patterns.
122
122
  - Applies to: /services/payments/**
123
+ - Priority: critical
123
124
  - Owners: @payments-team
125
+ - Tags: backend, compliance
126
+ ```
127
+
128
+ Entries can also reference dependencies:
129
+
130
+ ```markdown
131
+ - Path: /node_modules/@acme/ui/AGENTS.md
132
+ - Purpose: Acme UI component conventions, theming, a11y.
133
+ - Applies to: /src/components/**
134
+ - Tags: frontend
124
135
  ```
125
136
 
126
137
  When an agent enters your repo, it reads this file, matches entries by glob pattern, and loads the most specific instructions. If the map is missing or stale, agents fall back to scanning — nothing breaks.
@@ -6,6 +6,11 @@
6
6
  * Returns POSIX-style relative paths from rootDir.
7
7
  */
8
8
  export declare function discoverAgentFiles(rootDir: string): string[];
9
+ /**
10
+ * Scan node_modules for AGENTS.md files shipped by dependencies.
11
+ * Only checks the root of each package (not recursive).
12
+ */
13
+ export declare function discoverDependencyAgentFiles(rootDir: string): string[];
9
14
  /**
10
15
  * Read the first meaningful line from an AGENTS.md file to infer a purpose.
11
16
  * Skips blank lines and heading markers.
@@ -59,6 +59,53 @@ function scanDir(currentDir, rootDir, results) {
59
59
  }
60
60
  }
61
61
  }
62
+ /**
63
+ * Scan node_modules for AGENTS.md files shipped by dependencies.
64
+ * Only checks the root of each package (not recursive).
65
+ */
66
+ export function discoverDependencyAgentFiles(rootDir) {
67
+ const results = [];
68
+ const nmDir = path.join(rootDir, "node_modules");
69
+ if (!fs.existsSync(nmDir))
70
+ return results;
71
+ let entries;
72
+ try {
73
+ entries = fs.readdirSync(nmDir, { withFileTypes: true });
74
+ }
75
+ catch {
76
+ return results;
77
+ }
78
+ for (const entry of entries) {
79
+ if (!entry.isDirectory())
80
+ continue;
81
+ if (entry.name.startsWith("@")) {
82
+ // Scoped packages: @scope/package
83
+ const scopeDir = path.join(nmDir, entry.name);
84
+ let scopedEntries;
85
+ try {
86
+ scopedEntries = fs.readdirSync(scopeDir, { withFileTypes: true });
87
+ }
88
+ catch {
89
+ continue;
90
+ }
91
+ for (const pkg of scopedEntries) {
92
+ if (!pkg.isDirectory())
93
+ continue;
94
+ checkPackageForAgentsMd(path.join(scopeDir, pkg.name), `node_modules/${entry.name}/${pkg.name}`, results);
95
+ }
96
+ }
97
+ else if (!entry.name.startsWith(".")) {
98
+ checkPackageForAgentsMd(path.join(nmDir, entry.name), `node_modules/${entry.name}`, results);
99
+ }
100
+ }
101
+ return results.sort();
102
+ }
103
+ function checkPackageForAgentsMd(pkgDir, relativePkgPath, results) {
104
+ const agentsPath = path.join(pkgDir, "AGENTS.md");
105
+ if (fs.existsSync(agentsPath)) {
106
+ results.push(`${relativePkgPath}/AGENTS.md`);
107
+ }
108
+ }
62
109
  /**
63
110
  * Read the first meaningful line from an AGENTS.md file to infer a purpose.
64
111
  * Skips blank lines and heading markers.
@@ -8,6 +8,8 @@ export declare function defaultScope(agentsPath: string): string[];
8
8
  export declare function createMap(entries: Array<{
9
9
  path: string;
10
10
  purpose: string;
11
+ priority?: "critical" | "high" | "normal" | "low";
12
+ last_modified?: string;
11
13
  owners?: string[];
12
14
  tags?: string[];
13
15
  }>): AgentsMap;
package/dist/generator.js CHANGED
@@ -21,6 +21,10 @@ export function createMap(entries) {
21
21
  scope: defaultScope(e.path),
22
22
  purpose: e.purpose,
23
23
  };
24
+ if (e.priority && e.priority !== "normal")
25
+ entry.priority = e.priority;
26
+ if (e.last_modified)
27
+ entry.last_modified = e.last_modified;
24
28
  if (e.owners && e.owners.length > 0)
25
29
  entry.owners = e.owners;
26
30
  if (e.tags && e.tags.length > 0)
@@ -43,6 +47,12 @@ export function toMarkdown(map) {
43
47
  lines.push(`- Path: /${entry.path}`);
44
48
  lines.push(` - Purpose: ${entry.purpose}`);
45
49
  lines.push(` - Applies to: ${entry.scope.map((s) => `/${s}`).join(", ")}`);
50
+ if (entry.priority && entry.priority !== "normal") {
51
+ lines.push(` - Priority: ${entry.priority}`);
52
+ }
53
+ if (entry.last_modified) {
54
+ lines.push(` - Last modified: ${entry.last_modified}`);
55
+ }
46
56
  if (entry.owners && entry.owners.length > 0) {
47
57
  lines.push(` - Owners: ${entry.owners.join(", ")}`);
48
58
  }
package/dist/index.js CHANGED
@@ -11,14 +11,14 @@ import * as path from "node:path";
11
11
  import * as readline from "node:readline";
12
12
  import { parseMap, findMap } from "./parser.js";
13
13
  import { validate } from "./validator.js";
14
- import { resolveEntries, formatMatch } from "./resolver.js";
15
- import { discoverAgentFiles, inferPurpose } from "./discoverer.js";
14
+ import { resolveEntries, resolveByTag, formatMatch } from "./resolver.js";
15
+ import { discoverAgentFiles, discoverDependencyAgentFiles, inferPurpose } from "./discoverer.js";
16
16
  import { createMap, toMarkdown } from "./generator.js";
17
17
  const program = new Command();
18
18
  program
19
19
  .name("agentsmap")
20
20
  .description("CLI tool for the AGENTS.map specification — discover, validate, and resolve AGENTS.md files.")
21
- .version("0.1.0");
21
+ .version("0.1.1");
22
22
  // ──────────────────────────────────────────────────────────────────────────────
23
23
  // init
24
24
  // ──────────────────────────────────────────────────────────────────────────────
@@ -26,10 +26,18 @@ program
26
26
  .command("init")
27
27
  .description("Scan for AGENTS.md files and generate an AGENTS.map.md file.")
28
28
  .option("--non-interactive", "Skip interactive prompts; use inferred or placeholder purposes.")
29
+ .option("--deps", "Include AGENTS.md files from installed dependencies (node_modules).")
29
30
  .action(async (opts) => {
30
31
  const cwd = process.cwd();
31
32
  console.log(chalk.blue("Scanning for AGENTS.md files..."));
32
33
  const files = discoverAgentFiles(cwd);
34
+ if (opts.deps) {
35
+ const depFiles = discoverDependencyAgentFiles(cwd);
36
+ if (depFiles.length > 0) {
37
+ console.log(chalk.blue(`Found ${depFiles.length} dependency AGENTS.md file(s).`));
38
+ files.push(...depFiles);
39
+ }
40
+ }
33
41
  if (files.length === 0) {
34
42
  console.log(chalk.yellow("No AGENTS.md files found in this directory tree."));
35
43
  console.log(chalk.dim("Create an AGENTS.md file and run this command again."));
@@ -137,9 +145,10 @@ program
137
145
  // resolve
138
146
  // ──────────────────────────────────────────────────────────────────────────────
139
147
  program
140
- .command("resolve <target-path>")
141
- .description("Show which AGENTS.md files apply to a given path.")
148
+ .command("resolve [target-path]")
149
+ .description("Show which AGENTS.md files apply to a given path or tag.")
142
150
  .option("--json", "Output in JSON format.")
151
+ .option("--tag <tags>", "Find entries by tag (comma-separated) instead of path.")
143
152
  .action((targetPath, opts) => {
144
153
  const cwd = process.cwd();
145
154
  let map;
@@ -151,6 +160,38 @@ program
151
160
  console.log(chalk.red(err.message));
152
161
  process.exit(1);
153
162
  }
163
+ // Tag-based resolution
164
+ if (opts.tag) {
165
+ const tags = opts.tag.split(",").map((t) => t.trim());
166
+ const matches = resolveByTag(map, tags);
167
+ if (opts.json) {
168
+ const output = matches.map((m) => ({
169
+ path: m.entry.path,
170
+ purpose: m.entry.purpose,
171
+ matchedPattern: m.matchedPattern,
172
+ priority: m.entry.priority ?? "normal",
173
+ owners: m.entry.owners,
174
+ tags: m.entry.tags,
175
+ }));
176
+ console.log(JSON.stringify(output, null, 2));
177
+ return;
178
+ }
179
+ if (matches.length === 0) {
180
+ console.log(chalk.yellow(`No entries tagged "${opts.tag}".`));
181
+ return;
182
+ }
183
+ console.log(chalk.blue(`${matches.length} entry(s) tagged ${chalk.bold(opts.tag)}:\n`));
184
+ for (const match of matches) {
185
+ console.log(formatMatch(match));
186
+ console.log();
187
+ }
188
+ return;
189
+ }
190
+ // Path-based resolution
191
+ if (!targetPath) {
192
+ console.log(chalk.red("Provide a target path or use --tag."));
193
+ process.exit(1);
194
+ }
154
195
  const matches = resolveEntries(map, targetPath);
155
196
  if (opts.json) {
156
197
  const output = matches.map((m) => ({
@@ -158,6 +199,7 @@ program
158
199
  purpose: m.entry.purpose,
159
200
  matchedPattern: m.matchedPattern,
160
201
  specificity: m.specificity,
202
+ priority: m.entry.priority ?? "normal",
161
203
  owners: m.entry.owners,
162
204
  tags: m.entry.tags,
163
205
  }));
@@ -184,10 +226,24 @@ program
184
226
  program
185
227
  .command("discover")
186
228
  .description("Scan for all AGENTS.md files and show their listing status.")
187
- .action(() => {
229
+ .option("--deps", "Include AGENTS.md files from installed dependencies (node_modules).")
230
+ .action((opts) => {
188
231
  const cwd = process.cwd();
189
232
  console.log(chalk.blue("Scanning for AGENTS.md files...\n"));
190
233
  const files = discoverAgentFiles(cwd);
234
+ if (opts.deps) {
235
+ const depFiles = discoverDependencyAgentFiles(cwd);
236
+ if (depFiles.length > 0) {
237
+ console.log(chalk.blue(`Dependencies with AGENTS.md (${depFiles.length}):\n`));
238
+ for (const f of depFiles) {
239
+ const purpose = inferPurpose(path.join(cwd, f));
240
+ console.log(` ${chalk.magenta("⬡")} ${f}`);
241
+ console.log(` ${chalk.dim(purpose)}`);
242
+ }
243
+ console.log();
244
+ files.push(...depFiles);
245
+ }
246
+ }
191
247
  if (files.length === 0) {
192
248
  console.log(chalk.yellow("No AGENTS.md files found."));
193
249
  return;
package/dist/parser.js CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import * as fs from "node:fs";
5
5
  import * as path from "node:path";
6
+ import * as posixPath from "node:path/posix";
6
7
  const MAP_FILENAME = "AGENTS.map.md";
7
8
  /** Find the AGENTS.map.md file in the given directory. */
8
9
  export function findMap(dir) {
@@ -72,6 +73,18 @@ export function parseMarkdown(content) {
72
73
  .map((t) => t.trim());
73
74
  continue;
74
75
  }
76
+ // Match "- Priority: ..."
77
+ const priorityMatch = trimmed.match(/^-\s+Priority:\s+(.+)$/i);
78
+ if (priorityMatch) {
79
+ currentEntry.priority = priorityMatch[1].trim().toLowerCase();
80
+ continue;
81
+ }
82
+ // Match "- Last modified: ..."
83
+ const lastModifiedMatch = trimmed.match(/^-\s+Last\s+modified:\s+(.+)$/i);
84
+ if (lastModifiedMatch) {
85
+ currentEntry.last_modified = lastModifiedMatch[1].trim();
86
+ continue;
87
+ }
75
88
  // Match "- Last reviewed: ..."
76
89
  const reviewedMatch = trimmed.match(/^-\s+Last\s+reviewed:\s+(.+)$/i);
77
90
  if (reviewedMatch) {
@@ -88,13 +101,24 @@ export function parseMarkdown(content) {
88
101
  entries,
89
102
  };
90
103
  }
104
+ /** Derive a default scope from an entry path. */
105
+ function defaultScopeFromPath(entryPath) {
106
+ const dir = posixPath.dirname(entryPath);
107
+ if (dir === ".")
108
+ return ["**"];
109
+ return [`${dir}/**`];
110
+ }
91
111
  /** Finalize a partially-parsed entry with defaults for missing fields. */
92
112
  function finalizeEntry(partial) {
93
113
  const entry = {
94
114
  path: partial.path,
95
- scope: partial.scope ?? ["**"],
115
+ scope: partial.scope ?? defaultScopeFromPath(partial.path),
96
116
  purpose: partial.purpose ?? "",
97
117
  };
118
+ if (partial.priority)
119
+ entry.priority = partial.priority;
120
+ if (partial.last_modified)
121
+ entry.last_modified = partial.last_modified;
98
122
  if (partial.owners)
99
123
  entry.owners = partial.owners;
100
124
  if (partial.tags)
@@ -17,6 +17,11 @@ export declare function calculateSpecificity(pattern: string): number;
17
17
  * Returns matches sorted by specificity (most specific first).
18
18
  */
19
19
  export declare function resolveEntries(map: AgentsMap, targetPath: string): ResolveMatch[];
20
+ /**
21
+ * Resolve entries by tag, regardless of scope.
22
+ * Returns entries that have at least one of the given tags.
23
+ */
24
+ export declare function resolveByTag(map: AgentsMap, tags: string[]): ResolveMatch[];
20
25
  /**
21
26
  * Format a resolved match for human-readable display.
22
27
  */
package/dist/resolver.js CHANGED
@@ -2,6 +2,13 @@
2
2
  * Path resolution and scope matching for AGENTS.map entries.
3
3
  */
4
4
  import picomatch from "picomatch";
5
+ /** Map priority to a numeric rank (higher = more important). */
6
+ const PRIORITY_RANK = {
7
+ critical: 4,
8
+ high: 3,
9
+ normal: 2,
10
+ low: 1,
11
+ };
5
12
  /**
6
13
  * Calculate the specificity of a glob pattern.
7
14
  * More specific patterns have higher scores.
@@ -65,8 +72,42 @@ export function resolveEntries(map, targetPath) {
65
72
  }
66
73
  }
67
74
  }
68
- // Sort by specificity (most specific first)
69
- matches.sort((a, b) => b.specificity - a.specificity);
75
+ // Sort by priority (critical first), then specificity (most specific first)
76
+ matches.sort((a, b) => {
77
+ const aPriority = PRIORITY_RANK[a.entry.priority ?? "normal"];
78
+ const bPriority = PRIORITY_RANK[b.entry.priority ?? "normal"];
79
+ if (bPriority !== aPriority)
80
+ return bPriority - aPriority;
81
+ return b.specificity - a.specificity;
82
+ });
83
+ return matches;
84
+ }
85
+ /**
86
+ * Resolve entries by tag, regardless of scope.
87
+ * Returns entries that have at least one of the given tags.
88
+ */
89
+ export function resolveByTag(map, tags) {
90
+ const normalizedTags = tags.map((t) => t.toLowerCase());
91
+ const matches = [];
92
+ for (const entry of map.entries) {
93
+ if (!entry.tags || entry.tags.length === 0)
94
+ continue;
95
+ const entryTags = entry.tags.map((t) => t.toLowerCase());
96
+ const matched = normalizedTags.find((t) => entryTags.includes(t));
97
+ if (matched) {
98
+ matches.push({
99
+ entry,
100
+ matchedPattern: `tag:${matched}`,
101
+ specificity: 0,
102
+ });
103
+ }
104
+ }
105
+ // Sort by priority
106
+ matches.sort((a, b) => {
107
+ const aPriority = PRIORITY_RANK[a.entry.priority ?? "normal"];
108
+ const bPriority = PRIORITY_RANK[b.entry.priority ?? "normal"];
109
+ return bPriority - aPriority;
110
+ });
70
111
  return matches;
71
112
  }
72
113
  /**
@@ -78,6 +119,12 @@ export function formatMatch(match) {
78
119
  lines.push(` Purpose: ${match.entry.purpose}`);
79
120
  lines.push(` Matched pattern: ${match.matchedPattern}`);
80
121
  lines.push(` Specificity: ${match.specificity}`);
122
+ if (match.entry.priority && match.entry.priority !== "normal") {
123
+ lines.push(` Priority: ${match.entry.priority}`);
124
+ }
125
+ if (match.entry.last_modified) {
126
+ lines.push(` Last modified: ${match.entry.last_modified}`);
127
+ }
81
128
  if (match.entry.owners && match.entry.owners.length > 0) {
82
129
  lines.push(` Owners: ${match.entry.owners.join(", ")}`);
83
130
  }
package/dist/types.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * AGENTS.map type definitions (v1 spec).
3
3
  */
4
+ /** Priority levels for entries, inspired by sitemap priority. */
5
+ export type Priority = "critical" | "high" | "normal" | "low";
4
6
  /** A single entry in the AGENTS.map.md file. */
5
7
  export interface AgentsMapEntry {
6
8
  /** POSIX path relative to repo root. Must not contain ".." or start with "/". */
@@ -9,11 +11,15 @@ export interface AgentsMapEntry {
9
11
  scope: string[];
10
12
  /** 1-3 sentences explaining why/when to use this file. */
11
13
  purpose: string;
14
+ /** How important this file is for agents. Default: "normal". */
15
+ priority?: Priority;
16
+ /** Date the AGENTS.md content was last meaningfully changed (YYYY-MM-DD). */
17
+ last_modified?: string;
12
18
  /** Optional team handles, CODEOWNERS aliases, etc. */
13
19
  owners?: string[];
14
20
  /** Optional categorical labels. */
15
21
  tags?: string[];
16
- /** Optional date in YYYY-MM-DD format. */
22
+ /** Date a human last verified the instructions are still correct (YYYY-MM-DD). */
17
23
  last_reviewed?: string;
18
24
  }
19
25
  /** The parsed AGENTS.map structure. */
package/dist/validator.js CHANGED
@@ -67,14 +67,39 @@ export function validate(map, rootDir) {
67
67
  }
68
68
  seenPaths.add(normalizedPath);
69
69
  // Check that the file actually exists
70
+ // Dependency paths (node_modules/) are ephemeral — warn instead of error
70
71
  const fullPath = path.join(rootDir, entry.path);
72
+ const isDep = entry.path.startsWith("node_modules/");
71
73
  if (!fs.existsSync(fullPath)) {
72
74
  diagnostics.push({
73
- severity: "error",
74
- message: `Entry "${entry.path}": file does not exist at ${fullPath}.`,
75
+ severity: isDep ? "warning" : "error",
76
+ message: isDep
77
+ ? `Entry "${entry.path}": dependency file not found. Run npm install to resolve.`
78
+ : `Entry "${entry.path}": file does not exist at ${fullPath}.`,
75
79
  entryPath: entry.path,
76
80
  });
77
81
  }
82
+ // Validate priority if present
83
+ if (entry.priority) {
84
+ const validPriorities = ["critical", "high", "normal", "low"];
85
+ if (!validPriorities.includes(entry.priority)) {
86
+ diagnostics.push({
87
+ severity: "warning",
88
+ message: `Entry "${entry.path}": priority "${entry.priority}" is not a valid value. Expected: critical, high, normal, or low.`,
89
+ entryPath: entry.path,
90
+ });
91
+ }
92
+ }
93
+ // Validate last_modified format if present
94
+ if (entry.last_modified) {
95
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(entry.last_modified)) {
96
+ diagnostics.push({
97
+ severity: "warning",
98
+ message: `Entry "${entry.path}": last_modified "${entry.last_modified}" is not in YYYY-MM-DD format.`,
99
+ entryPath: entry.path,
100
+ });
101
+ }
102
+ }
78
103
  // Validate last_reviewed format if present
79
104
  if (entry.last_reviewed) {
80
105
  if (!/^\d{4}-\d{2}-\d{2}$/.test(entry.last_reviewed)) {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "agentsmap",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "CLI tool for the AGENTS.map specification — discover, validate, and resolve AGENTS.md instruction files.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "agentsmap": "./dist/index.js"
7
+ "agentsmap": "dist/index.js"
8
8
  },
9
9
  "main": "./dist/index.js",
10
10
  "types": "./dist/index.d.ts",