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.
- package/AGENTS.md +2 -0
- package/README.md +18 -0
- package/dist/index.mjs +54 -20
- package/package.json +3 -1
- package/src/agents-file.ts +185 -170
- package/src/discover.bench.ts +219 -0
- package/src/discover.test.ts +164 -105
- package/src/discover.ts +163 -123
- package/src/index.ts +4 -4
- package/src/paths.ts +13 -8
- package/src/run.ts +296 -198
package/src/discover.ts
CHANGED
|
@@ -1,158 +1,198 @@
|
|
|
1
|
-
import { access, opendir, readFile } from
|
|
2
|
-
import path from
|
|
3
|
-
import { formatProjectPath, resolveFromRoot } from
|
|
1
|
+
import { access, opendir, readFile } from "node:fs/promises"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import { formatProjectPath, resolveFromRoot } from "./paths.ts"
|
|
4
4
|
|
|
5
5
|
export type DiscoveredDocument = {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
path: string
|
|
7
|
+
description?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type DiscoverOptions = {
|
|
11
|
+
includeDotDirectories?: boolean
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
const skippedDirectories = new Set([
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
".git",
|
|
16
|
+
".hg",
|
|
17
|
+
".svn",
|
|
18
|
+
".turbo",
|
|
19
|
+
".next",
|
|
20
|
+
"coverage",
|
|
21
|
+
"dist",
|
|
22
|
+
"build",
|
|
19
23
|
])
|
|
20
24
|
|
|
21
|
-
export async function discoverAgentDocuments(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
export async function discoverAgentDocuments(
|
|
26
|
+
root: string,
|
|
27
|
+
options: DiscoverOptions = {},
|
|
28
|
+
): Promise<DiscoveredDocument[]> {
|
|
29
|
+
const found: DiscoveredDocument[] = []
|
|
30
|
+
await walk(root, root, found, options)
|
|
31
|
+
return found
|
|
32
|
+
.filter((document) => document.path !== "./AGENTS.md")
|
|
33
|
+
.sort((left, right) => left.path.localeCompare(right.path))
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
export async function describeAgentDocument(
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
root: string,
|
|
38
|
+
agentsDocumentPath: string,
|
|
32
39
|
): Promise<DiscoveredDocument> {
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
const absolutePath = resolveFromRoot(root, agentsDocumentPath)
|
|
41
|
+
return documentEntry(
|
|
42
|
+
root,
|
|
43
|
+
absolutePath,
|
|
44
|
+
await readPackageDescription(path.dirname(absolutePath)),
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function walk(
|
|
49
|
+
root: string,
|
|
50
|
+
directory: string,
|
|
51
|
+
found: DiscoveredDocument[],
|
|
52
|
+
options: DiscoverOptions,
|
|
53
|
+
): Promise<void> {
|
|
54
|
+
let handle
|
|
55
|
+
try {
|
|
56
|
+
handle = await opendir(directory)
|
|
57
|
+
} catch {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for await (const entry of handle) {
|
|
62
|
+
const absolutePath = path.join(directory, entry.name)
|
|
63
|
+
|
|
64
|
+
if (entry.isDirectory()) {
|
|
65
|
+
if (entry.name === "node_modules") {
|
|
66
|
+
await scanDirectNodeModules(root, absolutePath, found)
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!shouldSkipDirectory(entry.name, options)) {
|
|
71
|
+
await walk(root, absolutePath, found, options)
|
|
72
|
+
}
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (entry.isFile() && entry.name === "AGENTS.md") {
|
|
77
|
+
found.push(
|
|
78
|
+
await documentEntry(
|
|
79
|
+
root,
|
|
80
|
+
absolutePath,
|
|
81
|
+
await readPackageDescription(path.dirname(absolutePath)),
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
35
86
|
}
|
|
36
87
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
handle = await opendir(directory)
|
|
41
|
-
} catch {
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for await (const entry of handle) {
|
|
46
|
-
const absolutePath = path.join(directory, entry.name)
|
|
47
|
-
|
|
48
|
-
if (entry.isDirectory()) {
|
|
49
|
-
if (entry.name === 'node_modules') {
|
|
50
|
-
await scanDirectNodeModules(root, absolutePath, found)
|
|
51
|
-
continue
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!skippedDirectories.has(entry.name)) {
|
|
55
|
-
await walk(root, absolutePath, found)
|
|
56
|
-
}
|
|
57
|
-
continue
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (entry.isFile() && entry.name === 'AGENTS.md') {
|
|
61
|
-
found.push(
|
|
62
|
-
await documentEntry(
|
|
63
|
-
root,
|
|
64
|
-
absolutePath,
|
|
65
|
-
await readPackageDescription(path.dirname(absolutePath)),
|
|
66
|
-
),
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
88
|
+
function shouldSkipDirectory(name: string, options: DiscoverOptions): boolean {
|
|
89
|
+
if (skippedDirectories.has(name)) return true
|
|
90
|
+
return !options.includeDotDirectories && name.startsWith(".")
|
|
70
91
|
}
|
|
71
92
|
|
|
72
93
|
async function scanDirectNodeModules(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
94
|
+
root: string,
|
|
95
|
+
nodeModulesPath: string,
|
|
96
|
+
found: DiscoveredDocument[],
|
|
76
97
|
): Promise<void> {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
let handle
|
|
99
|
+
try {
|
|
100
|
+
handle = await opendir(nodeModulesPath)
|
|
101
|
+
} catch {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for await (const entry of handle) {
|
|
106
|
+
if (
|
|
107
|
+
(!entry.isDirectory() && !entry.isSymbolicLink()) ||
|
|
108
|
+
entry.name.startsWith(".")
|
|
109
|
+
) {
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const packagePath = path.join(nodeModulesPath, entry.name)
|
|
114
|
+
if (entry.name.startsWith("@")) {
|
|
115
|
+
await scanScopedPackages(root, packagePath, found)
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await addPackageAgentsDocument(root, packagePath, found)
|
|
120
|
+
}
|
|
97
121
|
}
|
|
98
122
|
|
|
99
123
|
async function scanScopedPackages(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
124
|
+
root: string,
|
|
125
|
+
scopePath: string,
|
|
126
|
+
found: DiscoveredDocument[],
|
|
103
127
|
): Promise<void> {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
let handle
|
|
129
|
+
try {
|
|
130
|
+
handle = await opendir(scopePath)
|
|
131
|
+
} catch {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for await (const entry of handle) {
|
|
136
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
137
|
+
await addPackageAgentsDocument(
|
|
138
|
+
root,
|
|
139
|
+
path.join(scopePath, entry.name),
|
|
140
|
+
found,
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
116
144
|
}
|
|
117
145
|
|
|
118
146
|
async function addPackageAgentsDocument(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
147
|
+
root: string,
|
|
148
|
+
packagePath: string,
|
|
149
|
+
found: DiscoveredDocument[],
|
|
122
150
|
): Promise<void> {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
151
|
+
const agentsPath = path.join(packagePath, "AGENTS.md")
|
|
152
|
+
try {
|
|
153
|
+
await access(agentsPath)
|
|
154
|
+
found.push(
|
|
155
|
+
await documentEntry(
|
|
156
|
+
root,
|
|
157
|
+
agentsPath,
|
|
158
|
+
await readPackageDescription(packagePath),
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
} catch {
|
|
162
|
+
// Packages without AGENTS.md are simply not candidates.
|
|
163
|
+
}
|
|
130
164
|
}
|
|
131
165
|
|
|
132
166
|
async function documentEntry(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
167
|
+
root: string,
|
|
168
|
+
agentsPath: string,
|
|
169
|
+
description: string | undefined,
|
|
136
170
|
): Promise<DiscoveredDocument> {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
171
|
+
return {
|
|
172
|
+
path: formatProjectPath(root, agentsPath),
|
|
173
|
+
...(description ? { description } : {}),
|
|
174
|
+
}
|
|
141
175
|
}
|
|
142
176
|
|
|
143
|
-
async function readPackageDescription(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
177
|
+
async function readPackageDescription(
|
|
178
|
+
packagePath: string,
|
|
179
|
+
): Promise<string | undefined> {
|
|
180
|
+
try {
|
|
181
|
+
const source = await readFile(
|
|
182
|
+
path.join(packagePath, "package.json"),
|
|
183
|
+
"utf8",
|
|
184
|
+
)
|
|
185
|
+
const parsed = JSON.parse(source) as unknown
|
|
186
|
+
if (!isPackageJson(parsed) || typeof parsed.description !== "string")
|
|
187
|
+
return undefined
|
|
188
|
+
|
|
189
|
+
const description = parsed.description.trim()
|
|
190
|
+
return description && description.length > 0 ? description : undefined
|
|
191
|
+
} catch {
|
|
192
|
+
return undefined
|
|
193
|
+
}
|
|
154
194
|
}
|
|
155
195
|
|
|
156
196
|
function isPackageJson(value: unknown): value is Record<string, unknown> {
|
|
157
|
-
|
|
197
|
+
return typeof value === "object" && value !== null
|
|
158
198
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { run } from
|
|
3
|
+
import { run } from "./run.ts"
|
|
4
4
|
|
|
5
5
|
run(process.argv.slice(2)).catch((error: unknown) => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
7
|
+
console.error(`agents: ${message}`)
|
|
8
|
+
process.exitCode = 1
|
|
9
9
|
})
|
package/src/paths.ts
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import path from
|
|
1
|
+
import path from "node:path"
|
|
2
2
|
|
|
3
3
|
export function cwd(): string {
|
|
4
|
-
|
|
4
|
+
return process.cwd()
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function resolveFromRoot(root: string, input: string): string {
|
|
8
|
-
|
|
8
|
+
return path.isAbsolute(input)
|
|
9
|
+
? path.normalize(input)
|
|
10
|
+
: path.resolve(root, input)
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export function formatProjectPath(root: string, target: string): string {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const relative = path
|
|
15
|
+
.relative(root, target)
|
|
16
|
+
.split(path.sep)
|
|
17
|
+
.join(path.posix.sep)
|
|
18
|
+
if (relative === ".." || relative.startsWith("../")) {
|
|
19
|
+
return target
|
|
20
|
+
}
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
return relative.startsWith("./") ? relative : `./${relative}`
|
|
18
23
|
}
|