agents.yaml 0.2.0 → 0.2.2

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.
@@ -0,0 +1,219 @@
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"
2
+ import { tmpdir } from "node:os"
3
+ import path from "node:path"
4
+ import { performance } from "node:perf_hooks"
5
+ import { discoverAgentDocuments, type DiscoverOptions } from "./discover.ts"
6
+
7
+ type BenchmarkCase = {
8
+ name: string
9
+ options?: DiscoverOptions
10
+ }
11
+
12
+ type BenchmarkResult = {
13
+ name: string
14
+ docs: number
15
+ medianMs: number
16
+ minMs: number
17
+ maxMs: number
18
+ }
19
+
20
+ const hiddenDirectories = readPositiveInteger("AGENTS_BENCH_HIDDEN_DIRS", 250)
21
+ const filesPerHiddenDirectory = readPositiveInteger(
22
+ "AGENTS_BENCH_FILES_PER_HIDDEN_DIR",
23
+ 8,
24
+ )
25
+ const visiblePackages = readPositiveInteger("AGENTS_BENCH_VISIBLE_PACKAGES", 25)
26
+ const iterations = readPositiveInteger("AGENTS_BENCH_ITERATIONS", 7)
27
+ const warmups = readPositiveInteger("AGENTS_BENCH_WARMUPS", 1)
28
+
29
+ const cases: BenchmarkCase[] = [
30
+ { name: "default" },
31
+ {
32
+ name: "include dot directories",
33
+ options: { includeDotDirectories: true },
34
+ },
35
+ ]
36
+
37
+ const root = await mkdtemp(path.join(tmpdir(), "agents-yaml-bench-"))
38
+
39
+ try {
40
+ await createFixture(root)
41
+ const results: BenchmarkResult[] = []
42
+
43
+ for (const benchmarkCase of cases) {
44
+ for (let index = 0; index < warmups; index += 1) {
45
+ await discoverAgentDocuments(root, benchmarkCase.options)
46
+ }
47
+
48
+ results.push(await runCase(root, benchmarkCase))
49
+ }
50
+
51
+ printResults(results)
52
+ } finally {
53
+ await rm(root, { recursive: true, force: true })
54
+ }
55
+
56
+ async function createFixture(root: string): Promise<void> {
57
+ await writeFile(
58
+ path.join(root, "agents.yaml"),
59
+ "version: 1\n\ndocuments: []\n",
60
+ "utf8",
61
+ )
62
+ await writeFile(path.join(root, "AGENTS.md"), "# Root guidance\n", "utf8")
63
+
64
+ await createDirectDependency(root)
65
+ await createVisibleProjectDocuments(root)
66
+ await createHiddenCache(root)
67
+ }
68
+
69
+ async function createDirectDependency(root: string): Promise<void> {
70
+ const dependencyPath = path.join(root, "node_modules", "direct-lib")
71
+ await mkdir(dependencyPath, { recursive: true })
72
+ await writeFile(
73
+ path.join(dependencyPath, "AGENTS.md"),
74
+ "# Direct dependency guidance\n",
75
+ "utf8",
76
+ )
77
+ await writeFile(
78
+ path.join(dependencyPath, "package.json"),
79
+ JSON.stringify({
80
+ name: "direct-lib",
81
+ description: "Direct fixture dependency.",
82
+ }),
83
+ "utf8",
84
+ )
85
+ }
86
+
87
+ async function createVisibleProjectDocuments(root: string): Promise<void> {
88
+ for (let index = 0; index < visiblePackages; index += 1) {
89
+ const packagePath = path.join(root, "packages", `visible-${index}`)
90
+ await mkdir(packagePath, { recursive: true })
91
+ await writeFile(
92
+ path.join(packagePath, "AGENTS.md"),
93
+ "# Visible project guidance\n",
94
+ "utf8",
95
+ )
96
+ }
97
+ }
98
+
99
+ async function createHiddenCache(root: string): Promise<void> {
100
+ for (let index = 0; index < hiddenDirectories; index += 1) {
101
+ const cachePath = path.join(root, ".cache", `entry-${index}`, "nested")
102
+ await mkdir(cachePath, { recursive: true })
103
+ await writeFile(
104
+ path.join(cachePath, "AGENTS.md"),
105
+ "# Hidden cache guidance\n",
106
+ "utf8",
107
+ )
108
+
109
+ for (
110
+ let fileIndex = 0;
111
+ fileIndex < filesPerHiddenDirectory;
112
+ fileIndex += 1
113
+ ) {
114
+ await writeFile(
115
+ path.join(cachePath, `file-${fileIndex}.txt`),
116
+ "x".repeat(100),
117
+ "utf8",
118
+ )
119
+ }
120
+ }
121
+ }
122
+
123
+ async function runCase(
124
+ root: string,
125
+ benchmarkCase: BenchmarkCase,
126
+ ): Promise<BenchmarkResult> {
127
+ const durations: number[] = []
128
+ let docs = 0
129
+
130
+ for (let index = 0; index < iterations; index += 1) {
131
+ const start = performance.now()
132
+ const discovered = await discoverAgentDocuments(root, benchmarkCase.options)
133
+ const duration = performance.now() - start
134
+
135
+ docs = discovered.length
136
+ durations.push(duration)
137
+ }
138
+
139
+ const sorted = [...durations].sort((left, right) => left - right)
140
+ const medianMs = sorted[Math.floor(sorted.length / 2)]
141
+ const minMs = sorted[0]
142
+ const maxMs = sorted[sorted.length - 1]
143
+
144
+ if (medianMs === undefined || minMs === undefined || maxMs === undefined) {
145
+ throw new Error("Benchmark did not record any durations")
146
+ }
147
+
148
+ return {
149
+ name: benchmarkCase.name,
150
+ docs,
151
+ medianMs,
152
+ minMs,
153
+ maxMs,
154
+ }
155
+ }
156
+
157
+ function printResults(results: BenchmarkResult[]): void {
158
+ console.log("agents discover benchmark")
159
+ console.log(
160
+ [
161
+ `fixture: hiddenDirectories=${hiddenDirectories}`,
162
+ `filesPerHiddenDirectory=${filesPerHiddenDirectory}`,
163
+ `visiblePackages=${visiblePackages}`,
164
+ `iterations=${iterations}`,
165
+ `warmups=${warmups}`,
166
+ ].join(", "),
167
+ )
168
+ console.log("")
169
+ console.log(
170
+ [
171
+ pad("case", 24),
172
+ pad("docs", 8),
173
+ pad("median", 10),
174
+ pad("min", 10),
175
+ pad("max", 10),
176
+ ].join(""),
177
+ )
178
+ console.log("-".repeat(62))
179
+
180
+ for (const result of results) {
181
+ console.log(
182
+ [
183
+ pad(result.name, 24),
184
+ pad(String(result.docs), 8),
185
+ pad(formatMs(result.medianMs), 10),
186
+ pad(formatMs(result.minMs), 10),
187
+ pad(formatMs(result.maxMs), 10),
188
+ ].join(""),
189
+ )
190
+ }
191
+
192
+ const defaultResult = results.find((result) => result.name === "default")
193
+ const includeDotResult = results.find(
194
+ (result) => result.name === "include dot directories",
195
+ )
196
+ if (defaultResult && includeDotResult && defaultResult.medianMs > 0) {
197
+ const ratio = includeDotResult.medianMs / defaultResult.medianMs
198
+ console.log("")
199
+ console.log(`include dot directories median: ${ratio.toFixed(1)}x default`)
200
+ }
201
+ }
202
+
203
+ function readPositiveInteger(name: string, fallback: number): number {
204
+ const raw = process.env[name]
205
+ if (!raw) return fallback
206
+
207
+ const value = Number.parseInt(raw, 10)
208
+ if (Number.isInteger(value) && value > 0) return value
209
+
210
+ throw new Error(`${name} must be a positive integer`)
211
+ }
212
+
213
+ function formatMs(value: number): string {
214
+ return `${value.toFixed(1)}ms`
215
+ }
216
+
217
+ function pad(value: string, width: number): string {
218
+ return value.padEnd(width, " ")
219
+ }
@@ -1,114 +1,173 @@
1
- import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
2
- import { tmpdir } from 'node:os'
3
- import path from 'node:path'
4
- import { afterEach, describe, expect, it } from 'vitest'
5
- import { addDocuments, loadAgentsFile } from './agents-file.ts'
6
- import { discoverAgentDocuments } from './discover.ts'
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"
2
+ import { tmpdir } from "node:os"
3
+ import path from "node:path"
4
+ import { afterEach, describe, expect, it } from "vitest"
5
+ import { addDocuments, loadAgentsFile } from "./agents-file.ts"
6
+ import { discoverAgentDocuments } from "./discover.ts"
7
7
 
8
8
  const tempRoots: string[] = []
9
9
 
10
- describe('agents.yaml dependency discovery', () => {
11
- afterEach(async () => {
12
- await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })))
13
- })
14
-
15
- it('discovers a direct dependency with an AGENTS.md and adds it to agents.yaml', async () => {
16
- const root = await createTempProject()
17
- const dependencyAgentsPath = path.join(root, 'node_modules', 'direct-lib', 'AGENTS.md')
18
- await mkdir(path.dirname(dependencyAgentsPath), { recursive: true })
19
- await writeFile(dependencyAgentsPath, '# Direct dependency guidance\n', 'utf8')
20
- await writeFile(
21
- path.join(root, 'node_modules', 'direct-lib', 'package.json'),
22
- JSON.stringify({
23
- name: 'direct-lib',
24
- description: 'Direct fixtures for testing agent guidance.',
25
- }),
26
- 'utf8',
27
- )
28
-
29
- const discovered = await discoverAgentDocuments(root)
30
- expect(discovered).toEqual([
31
- {
32
- path: './node_modules/direct-lib/AGENTS.md',
33
- description: 'Direct fixtures for testing agent guidance.',
34
- },
35
- ])
36
-
37
- await addDocuments(root, [discovered[0]!])
38
-
39
- await expect(loadAgentsFile(root)).resolves.toEqual({
40
- version: 1,
41
- documents: [
42
- {
43
- path: './node_modules/direct-lib/AGENTS.md',
44
- description: 'Direct fixtures for testing agent guidance.',
45
- },
46
- ],
47
- })
48
-
49
- await addDocuments(root, [{ path: './node_modules/direct-lib/AGENTS.md' }])
50
- await expect(loadAgentsFile(root)).resolves.toEqual({
51
- version: 1,
52
- documents: [
53
- {
54
- path: './node_modules/direct-lib/AGENTS.md',
55
- description: 'Direct fixtures for testing agent guidance.',
56
- },
57
- ],
58
- })
59
- })
60
-
61
- it('does not discover an indirect dependency that has an AGENTS.md', async () => {
62
- const root = await createTempProject()
63
- const directAgentsPath = path.join(root, 'node_modules', 'direct-lib', 'AGENTS.md')
64
- const indirectAgentsPath = path.join(
65
- root,
66
- 'node_modules',
67
- 'direct-lib',
68
- 'node_modules',
69
- 'indirect-lib',
70
- 'AGENTS.md',
71
- )
72
-
73
- await mkdir(path.dirname(directAgentsPath), { recursive: true })
74
- await mkdir(path.dirname(indirectAgentsPath), { recursive: true })
75
- await writeFile(directAgentsPath, '# Direct dependency guidance\n', 'utf8')
76
- await writeFile(indirectAgentsPath, '# Indirect dependency guidance\n', 'utf8')
77
-
78
- await expect(discoverAgentDocuments(root)).resolves.toEqual([
79
- { path: './node_modules/direct-lib/AGENTS.md' },
80
- ])
81
- })
82
-
83
- it('includes scoped package descriptions when present', async () => {
84
- const root = await createTempProject()
85
- const dependencyPath = path.join(root, 'node_modules', '@scope', 'direct-lib')
86
- await mkdir(dependencyPath, { recursive: true })
87
- await writeFile(path.join(dependencyPath, 'AGENTS.md'), '# Scoped guidance\n', 'utf8')
88
- await writeFile(
89
- path.join(dependencyPath, 'package.json'),
90
- JSON.stringify({
91
- name: '@scope/direct-lib',
92
- description: 'Scoped package guidance breadcrumbs.',
93
- }),
94
- 'utf8',
95
- )
96
-
97
- await expect(discoverAgentDocuments(root)).resolves.toEqual([
98
- {
99
- path: './node_modules/@scope/direct-lib/AGENTS.md',
100
- description: 'Scoped package guidance breadcrumbs.',
101
- },
102
- ])
103
- })
10
+ describe("agents.yaml dependency discovery", () => {
11
+ afterEach(async () => {
12
+ await Promise.all(
13
+ tempRoots
14
+ .splice(0)
15
+ .map((root) => rm(root, { recursive: true, force: true })),
16
+ )
17
+ })
18
+
19
+ it("discovers a direct dependency with an AGENTS.md and adds it to agents.yaml", async () => {
20
+ const root = await createTempProject()
21
+ const dependencyAgentsPath = path.join(
22
+ root,
23
+ "node_modules",
24
+ "direct-lib",
25
+ "AGENTS.md",
26
+ )
27
+ await mkdir(path.dirname(dependencyAgentsPath), { recursive: true })
28
+ await writeFile(
29
+ dependencyAgentsPath,
30
+ "# Direct dependency guidance\n",
31
+ "utf8",
32
+ )
33
+ await writeFile(
34
+ path.join(root, "node_modules", "direct-lib", "package.json"),
35
+ JSON.stringify({
36
+ name: "direct-lib",
37
+ description: "Direct fixtures for testing agent guidance.",
38
+ }),
39
+ "utf8",
40
+ )
41
+
42
+ const discovered = await discoverAgentDocuments(root)
43
+ expect(discovered).toEqual([
44
+ {
45
+ path: "./node_modules/direct-lib/AGENTS.md",
46
+ description: "Direct fixtures for testing agent guidance.",
47
+ },
48
+ ])
49
+
50
+ await addDocuments(root, [discovered[0]!])
51
+
52
+ await expect(loadAgentsFile(root)).resolves.toEqual({
53
+ version: 1,
54
+ documents: [
55
+ {
56
+ path: "./node_modules/direct-lib/AGENTS.md",
57
+ description: "Direct fixtures for testing agent guidance.",
58
+ },
59
+ ],
60
+ })
61
+
62
+ await addDocuments(root, [{ path: "./node_modules/direct-lib/AGENTS.md" }])
63
+ await expect(loadAgentsFile(root)).resolves.toEqual({
64
+ version: 1,
65
+ documents: [
66
+ {
67
+ path: "./node_modules/direct-lib/AGENTS.md",
68
+ description: "Direct fixtures for testing agent guidance.",
69
+ },
70
+ ],
71
+ })
72
+ })
73
+
74
+ it("does not discover an indirect dependency that has an AGENTS.md", async () => {
75
+ const root = await createTempProject()
76
+ const directAgentsPath = path.join(
77
+ root,
78
+ "node_modules",
79
+ "direct-lib",
80
+ "AGENTS.md",
81
+ )
82
+ const indirectAgentsPath = path.join(
83
+ root,
84
+ "node_modules",
85
+ "direct-lib",
86
+ "node_modules",
87
+ "indirect-lib",
88
+ "AGENTS.md",
89
+ )
90
+
91
+ await mkdir(path.dirname(directAgentsPath), { recursive: true })
92
+ await mkdir(path.dirname(indirectAgentsPath), { recursive: true })
93
+ await writeFile(directAgentsPath, "# Direct dependency guidance\n", "utf8")
94
+ await writeFile(
95
+ indirectAgentsPath,
96
+ "# Indirect dependency guidance\n",
97
+ "utf8",
98
+ )
99
+
100
+ await expect(discoverAgentDocuments(root)).resolves.toEqual([
101
+ { path: "./node_modules/direct-lib/AGENTS.md" },
102
+ ])
103
+ })
104
+
105
+ it("includes scoped package descriptions when present", async () => {
106
+ const root = await createTempProject()
107
+ const dependencyPath = path.join(
108
+ root,
109
+ "node_modules",
110
+ "@scope",
111
+ "direct-lib",
112
+ )
113
+ await mkdir(dependencyPath, { recursive: true })
114
+ await writeFile(
115
+ path.join(dependencyPath, "AGENTS.md"),
116
+ "# Scoped guidance\n",
117
+ "utf8",
118
+ )
119
+ await writeFile(
120
+ path.join(dependencyPath, "package.json"),
121
+ JSON.stringify({
122
+ name: "@scope/direct-lib",
123
+ description: "Scoped package guidance breadcrumbs.",
124
+ }),
125
+ "utf8",
126
+ )
127
+
128
+ await expect(discoverAgentDocuments(root)).resolves.toEqual([
129
+ {
130
+ path: "./node_modules/@scope/direct-lib/AGENTS.md",
131
+ description: "Scoped package guidance breadcrumbs.",
132
+ },
133
+ ])
134
+ })
135
+
136
+ it("skips dot-prefixed directories by default", async () => {
137
+ const root = await createTempProject()
138
+ const hiddenAgentsPath = path.join(root, ".cache", "AGENTS.md")
139
+ await mkdir(path.dirname(hiddenAgentsPath), { recursive: true })
140
+ await writeFile(hiddenAgentsPath, "# Hidden cache guidance\n", "utf8")
141
+
142
+ await expect(discoverAgentDocuments(root)).resolves.toEqual([])
143
+ })
144
+
145
+ it("can include dot-prefixed directories when requested", async () => {
146
+ const root = await createTempProject()
147
+ const hiddenAgentsPath = path.join(root, ".cache", "AGENTS.md")
148
+ await mkdir(path.dirname(hiddenAgentsPath), { recursive: true })
149
+ await writeFile(hiddenAgentsPath, "# Hidden cache guidance\n", "utf8")
150
+
151
+ await expect(
152
+ discoverAgentDocuments(root, { includeDotDirectories: true }),
153
+ ).resolves.toEqual([{ path: "./.cache/AGENTS.md" }])
154
+ })
104
155
  })
105
156
 
106
157
  async function createTempProject(): Promise<string> {
107
- const root = await mkdtemp(path.join(tmpdir(), 'agents-yaml-'))
108
- tempRoots.push(root)
158
+ const root = await mkdtemp(path.join(tmpdir(), "agents-yaml-"))
159
+ tempRoots.push(root)
109
160
 
110
- await writeFile(path.join(root, 'agents.yaml'), 'version: 1\n\ndocuments: []\n', 'utf8')
111
- await writeFile(path.join(root, 'AGENTS.md'), 'Consult ./agents.yaml.\n', 'utf8')
161
+ await writeFile(
162
+ path.join(root, "agents.yaml"),
163
+ "version: 1\n\ndocuments: []\n",
164
+ "utf8",
165
+ )
166
+ await writeFile(
167
+ path.join(root, "AGENTS.md"),
168
+ "Consult ./agents.yaml.\n",
169
+ "utf8",
170
+ )
112
171
 
113
- return root
172
+ return root
114
173
  }