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 +28 -17
- package/dist/discoverer.d.ts +5 -0
- package/dist/discoverer.js +47 -0
- package/dist/generator.d.ts +2 -0
- package/dist/generator.js +10 -0
- package/dist/index.js +62 -6
- package/dist/parser.js +25 -1
- package/dist/resolver.d.ts +5 -0
- package/dist/resolver.js +49 -2
- package/dist/types.d.ts +7 -1
- package/dist/validator.js +27 -2
- package/package.json +2 -2
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
|
-
|
|
64
|
+
Use `--tag` to find entries by domain instead of path:
|
|
59
65
|
|
|
60
|
-
```
|
|
61
|
-
|
|
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.
|
package/dist/discoverer.d.ts
CHANGED
|
@@ -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.
|
package/dist/discoverer.js
CHANGED
|
@@ -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.
|
package/dist/generator.d.ts
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
-
.
|
|
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)
|
package/dist/resolver.d.ts
CHANGED
|
@@ -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) =>
|
|
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
|
-
/**
|
|
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:
|
|
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.
|
|
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": "
|
|
7
|
+
"agentsmap": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|