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/AGENTS.md
CHANGED
|
@@ -19,3 +19,5 @@ Agents should treat paths listed in `documents` as promoted supplemental guidanc
|
|
|
19
19
|
The CLI can help discover package and local `AGENTS.md` files, add selected paths to `agents.yaml`, remove paths, initialize the root breadcrumb, and validate that referenced files still exist.
|
|
20
20
|
|
|
21
21
|
Discovery only considers direct dependencies under a project's `node_modules`; nested dependency `AGENTS.md` files are not automatically activated.
|
|
22
|
+
|
|
23
|
+
Discovery skips dot-prefixed directories by default. Use `agents discover --include-dot-directories` when hidden project directories should be scanned too.
|
package/README.md
CHANGED
|
@@ -17,12 +17,30 @@ pnpm run build
|
|
|
17
17
|
```sh
|
|
18
18
|
agents init
|
|
19
19
|
agents discover
|
|
20
|
+
agents discover --include-dot-directories
|
|
20
21
|
agents add ./node_modules/react/AGENTS.md
|
|
21
22
|
agents validate
|
|
22
23
|
```
|
|
23
24
|
|
|
24
25
|
Run `agents` with no command for the interactive flow.
|
|
25
26
|
|
|
27
|
+
Discovery skips dot-prefixed directories by default so local caches and tool
|
|
28
|
+
state do not dominate scan time. Use `--include-dot-directories` when you need
|
|
29
|
+
to search those directories too.
|
|
30
|
+
|
|
31
|
+
## Benchmark
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pnpm --filter agents.yaml bench
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The benchmark creates a temporary discovery fixture, compares default discovery
|
|
38
|
+
against `--include-dot-directories`, prints median/min/max timings, and removes
|
|
39
|
+
the fixture when it exits. Fixture size can be tuned with
|
|
40
|
+
`AGENTS_BENCH_HIDDEN_DIRS`, `AGENTS_BENCH_FILES_PER_HIDDEN_DIR`,
|
|
41
|
+
`AGENTS_BENCH_VISIBLE_PACKAGES`, `AGENTS_BENCH_ITERATIONS`, and
|
|
42
|
+
`AGENTS_BENCH_WARMUPS`.
|
|
43
|
+
|
|
26
44
|
## File Format
|
|
27
45
|
|
|
28
46
|
```yaml
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { MultiSelectPrompt } from "@clack/core";
|
|
2
3
|
import * as clack from "@clack/prompts";
|
|
4
|
+
import { styleText } from "node:util";
|
|
3
5
|
import { access, opendir, readFile, writeFile } from "node:fs/promises";
|
|
4
6
|
import path from "node:path";
|
|
5
7
|
import * as YAML from "yaml";
|
|
@@ -13,8 +15,8 @@ function resolveFromRoot(root, input) {
|
|
|
13
15
|
}
|
|
14
16
|
function formatProjectPath(root, target) {
|
|
15
17
|
const relative = path.relative(root, target).split(path.sep).join(path.posix.sep);
|
|
16
|
-
if (relative.startsWith("
|
|
17
|
-
return relative.startsWith("
|
|
18
|
+
if (relative === ".." || relative.startsWith("../")) return target;
|
|
19
|
+
return relative.startsWith("./") ? relative : `./${relative}`;
|
|
18
20
|
}
|
|
19
21
|
//#endregion
|
|
20
22
|
//#region src/agents-file.ts
|
|
@@ -172,16 +174,16 @@ const skippedDirectories = new Set([
|
|
|
172
174
|
"dist",
|
|
173
175
|
"build"
|
|
174
176
|
]);
|
|
175
|
-
async function discoverAgentDocuments(root) {
|
|
177
|
+
async function discoverAgentDocuments(root, options = {}) {
|
|
176
178
|
const found = [];
|
|
177
|
-
await walk(root, root, found);
|
|
179
|
+
await walk(root, root, found, options);
|
|
178
180
|
return found.filter((document) => document.path !== "./AGENTS.md").sort((left, right) => left.path.localeCompare(right.path));
|
|
179
181
|
}
|
|
180
182
|
async function describeAgentDocument(root, agentsDocumentPath) {
|
|
181
183
|
const absolutePath = resolveFromRoot(root, agentsDocumentPath);
|
|
182
184
|
return documentEntry(root, absolutePath, await readPackageDescription(path.dirname(absolutePath)));
|
|
183
185
|
}
|
|
184
|
-
async function walk(root, directory, found) {
|
|
186
|
+
async function walk(root, directory, found, options) {
|
|
185
187
|
let handle;
|
|
186
188
|
try {
|
|
187
189
|
handle = await opendir(directory);
|
|
@@ -195,12 +197,16 @@ async function walk(root, directory, found) {
|
|
|
195
197
|
await scanDirectNodeModules(root, absolutePath, found);
|
|
196
198
|
continue;
|
|
197
199
|
}
|
|
198
|
-
if (!
|
|
200
|
+
if (!shouldSkipDirectory(entry.name, options)) await walk(root, absolutePath, found, options);
|
|
199
201
|
continue;
|
|
200
202
|
}
|
|
201
203
|
if (entry.isFile() && entry.name === "AGENTS.md") found.push(await documentEntry(root, absolutePath, await readPackageDescription(path.dirname(absolutePath))));
|
|
202
204
|
}
|
|
203
205
|
}
|
|
206
|
+
function shouldSkipDirectory(name, options) {
|
|
207
|
+
if (skippedDirectories.has(name)) return true;
|
|
208
|
+
return !options.includeDotDirectories && name.startsWith(".");
|
|
209
|
+
}
|
|
204
210
|
async function scanDirectNodeModules(root, nodeModulesPath, found) {
|
|
205
211
|
let handle;
|
|
206
212
|
try {
|
|
@@ -261,7 +267,7 @@ const helpText = `agents
|
|
|
261
267
|
Usage:
|
|
262
268
|
agents
|
|
263
269
|
agents init [--force]
|
|
264
|
-
agents discover [--json]
|
|
270
|
+
agents discover [--json] [--include-dot-directories]
|
|
265
271
|
agents add <path...>
|
|
266
272
|
agents remove <path...>
|
|
267
273
|
agents validate [--json]
|
|
@@ -284,7 +290,10 @@ async function run(argv) {
|
|
|
284
290
|
await commandInit(root, parsed.flags.get("force") === true);
|
|
285
291
|
return;
|
|
286
292
|
case "discover":
|
|
287
|
-
await commandDiscover(root,
|
|
293
|
+
await commandDiscover(root, {
|
|
294
|
+
json: parsed.flags.get("json") === true,
|
|
295
|
+
includeDotDirectories: parsed.flags.get("include-dot-directories") === true
|
|
296
|
+
});
|
|
288
297
|
return;
|
|
289
298
|
case "add":
|
|
290
299
|
await commandAdd(root, parsed.values);
|
|
@@ -351,9 +360,9 @@ async function commandInit(root, force) {
|
|
|
351
360
|
clack.note(result.messages.join("\n"), "Updated");
|
|
352
361
|
clack.outro("Project breadcrumb is ready.");
|
|
353
362
|
}
|
|
354
|
-
async function commandDiscover(root,
|
|
355
|
-
const documents = await discoverAgentDocuments(root);
|
|
356
|
-
if (json) {
|
|
363
|
+
async function commandDiscover(root, options) {
|
|
364
|
+
const documents = await discoverAgentDocuments(root, { includeDotDirectories: options.includeDotDirectories });
|
|
365
|
+
if (options.json) {
|
|
357
366
|
console.log(JSON.stringify(documents, null, 2));
|
|
358
367
|
return;
|
|
359
368
|
}
|
|
@@ -432,20 +441,45 @@ async function interactive(root) {
|
|
|
432
441
|
clack.outro("No unlisted supplemental AGENTS.md files found.");
|
|
433
442
|
return;
|
|
434
443
|
}
|
|
435
|
-
const selected = await
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
})),
|
|
441
|
-
required: false
|
|
442
|
-
});
|
|
443
|
-
if (clack.isCancel(selected) || selected.length === 0) {
|
|
444
|
+
const selected = await chooseDocumentsToEnable(candidates.map((doc) => ({
|
|
445
|
+
value: doc.path,
|
|
446
|
+
label: doc.path
|
|
447
|
+
})));
|
|
448
|
+
if (clack.isCancel(selected) || selected === void 0 || selected.length === 0) {
|
|
444
449
|
clack.cancel("No documents selected.");
|
|
445
450
|
return;
|
|
446
451
|
}
|
|
447
452
|
await commandAdd(root, selected);
|
|
448
453
|
}
|
|
454
|
+
function chooseDocumentsToEnable(options) {
|
|
455
|
+
return new MultiSelectPrompt({
|
|
456
|
+
options,
|
|
457
|
+
required: false,
|
|
458
|
+
render() {
|
|
459
|
+
const prefix = `${styleText("cyan", clack.S_BAR)} `;
|
|
460
|
+
const selected = this.value ?? [];
|
|
461
|
+
return `${styleText("gray", clack.S_BAR)}
|
|
462
|
+
${clack.symbol(this.state)} Choose documents to enable
|
|
463
|
+
${prefix}${clack.limitOptions({
|
|
464
|
+
options: this.options,
|
|
465
|
+
cursor: this.cursor,
|
|
466
|
+
columnPadding: prefix.length,
|
|
467
|
+
style: (option, active) => styleDocumentOption(option, {
|
|
468
|
+
active,
|
|
469
|
+
selected: selected.includes(option.value)
|
|
470
|
+
})
|
|
471
|
+
}).join(`
|
|
472
|
+
${prefix}`)}
|
|
473
|
+
${styleText("cyan", clack.S_BAR_END)}
|
|
474
|
+
`;
|
|
475
|
+
}
|
|
476
|
+
}).prompt();
|
|
477
|
+
}
|
|
478
|
+
function styleDocumentOption(option, state) {
|
|
479
|
+
if (option.disabled) return `${styleText("gray", clack.S_CHECKBOX_INACTIVE)} ${styleText(["strikethrough", "gray"], option.label)}`;
|
|
480
|
+
const checkbox = state.selected ? styleText("green", clack.S_CHECKBOX_SELECTED) : state.active ? styleText("cyan", clack.S_CHECKBOX_ACTIVE) : styleText("dim", clack.S_CHECKBOX_INACTIVE);
|
|
481
|
+
return `${state.active ? styleText("cyan", ">") : " "} ${checkbox} ${state.active ? option.label : styleText("dim", option.label)}`;
|
|
482
|
+
}
|
|
449
483
|
//#endregion
|
|
450
484
|
//#region src/index.ts
|
|
451
485
|
run(process.argv.slice(2)).catch((error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents.yaml",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "A CLI for discovering and curating agent-readable documentation in agents.yaml.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"type": "module",
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"@clack/core": "1.4.1",
|
|
20
21
|
"@clack/prompts": "1.5.1",
|
|
21
22
|
"yaml": "2.9.0",
|
|
22
23
|
"zod": "4.4.3"
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
"pnpm": "11.5.2"
|
|
31
32
|
},
|
|
32
33
|
"scripts": {
|
|
34
|
+
"bench": "node src/discover.bench.ts",
|
|
33
35
|
"build": "vp pack",
|
|
34
36
|
"dev": "src/index.ts",
|
|
35
37
|
"test": "vp test"
|
package/src/agents-file.ts
CHANGED
|
@@ -1,209 +1,224 @@
|
|
|
1
|
-
import { access, readFile, writeFile } from
|
|
2
|
-
import path from
|
|
3
|
-
import * as YAML from
|
|
4
|
-
import { z } from
|
|
5
|
-
import { resolveFromRoot } from
|
|
1
|
+
import { access, readFile, writeFile } from "node:fs/promises"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import * as YAML from "yaml"
|
|
4
|
+
import { z } from "zod"
|
|
5
|
+
import { resolveFromRoot } from "./paths.ts"
|
|
6
6
|
|
|
7
7
|
export type AgentsDocumentEntry = {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
path: string
|
|
9
|
+
description?: string | undefined
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export type AgentsFile = {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
version: 1
|
|
14
|
+
documents: AgentsDocumentEntry[]
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export type ValidationResult = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
ok: boolean
|
|
19
|
+
errors: string[]
|
|
20
|
+
warnings: string[]
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const fileSchema = z.object({
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
version: z.literal(1),
|
|
25
|
+
documents: z.array(
|
|
26
|
+
z.object({
|
|
27
|
+
path: z.string().min(1),
|
|
28
|
+
description: z.string().min(1).optional(),
|
|
29
|
+
}),
|
|
30
|
+
),
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
const breadcrumb = `Consult \`./agents.yaml\` when working with outside dependencies.`
|
|
34
34
|
|
|
35
35
|
export async function loadAgentsFile(root: string): Promise<AgentsFile> {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
const filePath = agentsPath(root)
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const source = await readFile(filePath, "utf8")
|
|
40
|
+
const parsed = YAML.parse(source) as unknown
|
|
41
|
+
return fileSchema.parse(parsed)
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (isNotFound(error)) {
|
|
44
|
+
return { version: 1, documents: [] }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (error instanceof z.ZodError) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Invalid agents.yaml: ${error.issues.map((issue) => issue.message).join(", ")}`,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export async function saveAgentsFile(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
export async function saveAgentsFile(
|
|
58
|
+
root: string,
|
|
59
|
+
file: AgentsFile,
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
const normalized = {
|
|
62
|
+
version: file.version,
|
|
63
|
+
documents: file.documents.map((doc) => ({
|
|
64
|
+
path: doc.path,
|
|
65
|
+
...(doc.description ? { description: doc.description } : {}),
|
|
66
|
+
})),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await writeFile(
|
|
70
|
+
agentsPath(root),
|
|
71
|
+
YAML.stringify(normalized, { lineWidth: 0 }),
|
|
72
|
+
"utf8",
|
|
73
|
+
)
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
export async function addDocuments(
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
root: string,
|
|
78
|
+
documents: AgentsDocumentEntry[],
|
|
72
79
|
): Promise<AgentsFile> {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
const file = await loadAgentsFile(root)
|
|
81
|
+
const byPath = new Map(file.documents.map((doc) => [doc.path, doc]))
|
|
82
|
+
|
|
83
|
+
for (const document of documents) {
|
|
84
|
+
const existing = byPath.get(document.path)
|
|
85
|
+
byPath.set(document.path, {
|
|
86
|
+
path: document.path,
|
|
87
|
+
...(existing?.description ? { description: existing.description } : {}),
|
|
88
|
+
...(document.description ? { description: document.description } : {}),
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const next = {
|
|
93
|
+
version: 1 as const,
|
|
94
|
+
documents: [...byPath.values()].sort((left, right) =>
|
|
95
|
+
left.path.localeCompare(right.path),
|
|
96
|
+
),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await saveAgentsFile(root, next)
|
|
100
|
+
return next
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
export async function removeDocuments(
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
root: string,
|
|
105
|
+
paths: string[],
|
|
97
106
|
): Promise<{ file: AgentsFile; removed: string[] }> {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
const file = await loadAgentsFile(root)
|
|
108
|
+
const pathSet = new Set(paths)
|
|
109
|
+
const removed: string[] = []
|
|
110
|
+
const documents = file.documents.filter((doc) => {
|
|
111
|
+
if (pathSet.has(doc.path)) {
|
|
112
|
+
removed.push(doc.path)
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return true
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const next = { version: 1 as const, documents }
|
|
120
|
+
await saveAgentsFile(root, next)
|
|
121
|
+
return { file: next, removed }
|
|
113
122
|
}
|
|
114
123
|
|
|
115
|
-
export async function validateAgentsFile(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
124
|
+
export async function validateAgentsFile(
|
|
125
|
+
root: string,
|
|
126
|
+
): Promise<ValidationResult> {
|
|
127
|
+
const errors: string[] = []
|
|
128
|
+
const warnings: string[] = []
|
|
129
|
+
let file: AgentsFile
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
file = await loadAgentsFile(root)
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
errors: [error instanceof Error ? error.message : String(error)],
|
|
137
|
+
warnings,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const seen = new Set<string>()
|
|
142
|
+
for (const [index, document] of file.documents.entries()) {
|
|
143
|
+
const label = `documents[${index}] ${document.path}`
|
|
144
|
+
|
|
145
|
+
if (seen.has(document.path)) {
|
|
146
|
+
errors.push(`${label}: duplicate path`)
|
|
147
|
+
}
|
|
148
|
+
seen.add(document.path)
|
|
149
|
+
|
|
150
|
+
if (path.basename(document.path) !== "AGENTS.md") {
|
|
151
|
+
warnings.push(`${label}: path does not end with AGENTS.md`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
await access(resolveFromRoot(root, document.path))
|
|
156
|
+
} catch {
|
|
157
|
+
errors.push(`${label}: file does not exist`)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const source = await readFile(path.join(root, "AGENTS.md"), "utf8")
|
|
163
|
+
if (!source.includes("./agents.yaml") && !source.includes("agents.yaml")) {
|
|
164
|
+
warnings.push("AGENTS.md does not mention agents.yaml")
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
warnings.push("AGENTS.md is missing the agents.yaml breadcrumb")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
ok: errors.length === 0,
|
|
172
|
+
errors,
|
|
173
|
+
warnings,
|
|
174
|
+
}
|
|
164
175
|
}
|
|
165
176
|
|
|
166
177
|
export async function initProject(
|
|
167
|
-
|
|
168
|
-
|
|
178
|
+
root: string,
|
|
179
|
+
options: { force: boolean },
|
|
169
180
|
): Promise<{ messages: string[] }> {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
181
|
+
const messages: string[] = []
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
await access(agentsPath(root))
|
|
185
|
+
messages.push("agents.yaml already exists")
|
|
186
|
+
} catch {
|
|
187
|
+
await saveAgentsFile(root, { version: 1, documents: [] })
|
|
188
|
+
messages.push("created agents.yaml")
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const projectAgentsPath = path.join(root, "AGENTS.md")
|
|
192
|
+
try {
|
|
193
|
+
const source = await readFile(projectAgentsPath, "utf8")
|
|
194
|
+
if (source.includes("agents.yaml") && !options.force) {
|
|
195
|
+
messages.push("AGENTS.md already mentions agents.yaml")
|
|
196
|
+
return { messages }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const next =
|
|
200
|
+
source.trimEnd().length === 0
|
|
201
|
+
? `# Project Instructions\n\n${breadcrumb}\n`
|
|
202
|
+
: `${source.trimEnd()}\n\n${breadcrumb}\n`
|
|
203
|
+
await writeFile(projectAgentsPath, next, "utf8")
|
|
204
|
+
messages.push("updated AGENTS.md")
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (!isNotFound(error)) throw error
|
|
207
|
+
await writeFile(
|
|
208
|
+
projectAgentsPath,
|
|
209
|
+
`# Project Instructions\n\n${breadcrumb}\n`,
|
|
210
|
+
"utf8",
|
|
211
|
+
)
|
|
212
|
+
messages.push("created AGENTS.md")
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { messages }
|
|
201
216
|
}
|
|
202
217
|
|
|
203
218
|
function agentsPath(root: string): string {
|
|
204
|
-
|
|
219
|
+
return path.join(root, "agents.yaml")
|
|
205
220
|
}
|
|
206
221
|
|
|
207
222
|
function isNotFound(error: unknown): boolean {
|
|
208
|
-
|
|
223
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT"
|
|
209
224
|
}
|