@bossmissing/agent-meta 0.1.0

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -0
  3. package/dist/analyzers/duplicate-rule-analyzer.d.ts +31 -0
  4. package/dist/analyzers/duplicate-rule-analyzer.js +80 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +177 -0
  7. package/dist/commands/audit.d.ts +7 -0
  8. package/dist/commands/audit.js +187 -0
  9. package/dist/commands/check.d.ts +35 -0
  10. package/dist/commands/check.js +109 -0
  11. package/dist/commands/config.d.ts +2 -0
  12. package/dist/commands/config.js +59 -0
  13. package/dist/commands/init.d.ts +8 -0
  14. package/dist/commands/init.js +179 -0
  15. package/dist/commands/new-adr.d.ts +8 -0
  16. package/dist/commands/new-adr.js +65 -0
  17. package/dist/commands/new-domain.d.ts +13 -0
  18. package/dist/commands/new-domain.js +106 -0
  19. package/dist/commands/sync.d.ts +7 -0
  20. package/dist/commands/sync.js +46 -0
  21. package/dist/core/config.d.ts +15 -0
  22. package/dist/core/config.js +40 -0
  23. package/dist/core/errors.d.ts +12 -0
  24. package/dist/core/errors.js +16 -0
  25. package/dist/core/filesystem.d.ts +33 -0
  26. package/dist/core/filesystem.js +92 -0
  27. package/dist/core/frontmatter.d.ts +19 -0
  28. package/dist/core/frontmatter.js +84 -0
  29. package/dist/core/logger.d.ts +8 -0
  30. package/dist/core/logger.js +22 -0
  31. package/dist/core/paths.d.ts +8 -0
  32. package/dist/core/paths.js +39 -0
  33. package/dist/core/risk.d.ts +3 -0
  34. package/dist/core/risk.js +8 -0
  35. package/dist/core/templates.d.ts +10 -0
  36. package/dist/core/templates.js +31 -0
  37. package/dist/core/types.d.ts +21 -0
  38. package/dist/core/types.js +7 -0
  39. package/dist/generators/adr-generator.d.ts +11 -0
  40. package/dist/generators/adr-generator.js +25 -0
  41. package/dist/generators/index-generator.d.ts +6 -0
  42. package/dist/generators/index-generator.js +87 -0
  43. package/dist/index.d.ts +11 -0
  44. package/dist/index.js +10 -0
  45. package/dist/schemas/config.schema.d.ts +208 -0
  46. package/dist/schemas/config.schema.js +83 -0
  47. package/dist/templates/index.d.ts +23 -0
  48. package/dist/templates/index.js +445 -0
  49. package/dist/validators/adr-validator.d.ts +3 -0
  50. package/dist/validators/adr-validator.js +182 -0
  51. package/dist/validators/agents-validator.d.ts +3 -0
  52. package/dist/validators/agents-validator.js +82 -0
  53. package/dist/validators/context.d.ts +12 -0
  54. package/dist/validators/context.js +1 -0
  55. package/dist/validators/docs-validator.d.ts +3 -0
  56. package/dist/validators/docs-validator.js +129 -0
  57. package/dist/validators/domain-validator.d.ts +3 -0
  58. package/dist/validators/domain-validator.js +132 -0
  59. package/dist/validators/openspec-validator.d.ts +2 -0
  60. package/dist/validators/openspec-validator.js +76 -0
  61. package/dist/validators/project-validator.d.ts +2 -0
  62. package/dist/validators/project-validator.js +41 -0
  63. package/package.json +64 -0
@@ -0,0 +1,445 @@
1
+ /**
2
+ * Built-in templates. A project can override any of them by placing a file
3
+ * named `<key>.tmpl` under `templates/agent-meta/` (configurable via
4
+ * paths.templates). Only {{variable}} substitution is supported.
5
+ */
6
+ const AGENTS_MD = `# AGENTS.md
7
+
8
+ ## Project Overview
9
+
10
+ - Project type: {{project_type}}
11
+ - Package manager: {{package_manager}}
12
+ - Main application paths: {{main_paths}}
13
+
14
+ ## Important Documentation
15
+
16
+ - Architecture: \`docs/architecture/\`
17
+ - Domain rules: \`docs/domain/\`
18
+ - API conventions: \`docs/api/\`
19
+ - Engineering rules: \`docs/engineering/\`
20
+ - Architecture decisions: \`docs/adr/\`
21
+ - Product capability specs: \`openspec/specs/\`
22
+ - Active changes: \`openspec/changes/\`
23
+
24
+ ## Working Rules
25
+
26
+ - Read the nearest applicable \`AGENTS.md\` before modifying files.
27
+ - Read related domain documentation before changing business behavior.
28
+ - Do not duplicate domain rules in this file.
29
+ - Update OpenSpec specs when externally observable behavior changes.
30
+ - Add or update tests for behavior changes.
31
+ - Do not mix unrelated refactors into a feature change.
32
+
33
+ ## High-Risk Areas
34
+
35
+ Before changing payment, order, commission, settlement, withdrawal, permissions, webhooks, inventory, or audit logging, read the applicable documentation under \`docs/domain/\`.
36
+
37
+ ## Definition of Done
38
+
39
+ - Relevant tests pass.
40
+ - Type checking and lint pass where configured.
41
+ - Behavior changes include documentation or specification updates where applicable.
42
+ - High-risk changes include rollback and failure-path consideration.
43
+ `;
44
+ const DOMAIN_README = `---
45
+ title: {{domain_title}}
46
+ status: active
47
+ owner: {{owner}}
48
+ last_reviewed: {{date}}
49
+ source_of_truth: true
50
+ applies_to: []
51
+ ---
52
+
53
+ # {{domain_title}}
54
+
55
+ ## 职责
56
+
57
+ <!-- 描述该领域负责的业务能力。 -->
58
+
59
+ ## 非职责
60
+
61
+ <!-- 明确该领域不负责什么,避免模块边界扩散。 -->
62
+
63
+ ## 核心对象
64
+
65
+ | 对象 | 说明 |
66
+ |---|---|
67
+ | | |
68
+
69
+ ## 上游依赖
70
+
71
+ <!-- 哪些模块、事件、服务会驱动该领域。 -->
72
+
73
+ ## 下游影响
74
+
75
+ <!-- 该领域变化会影响哪些模块、数据、接口或用户角色。 -->
76
+
77
+ ## 关键规则入口
78
+
79
+ - [业务规则](./rules.md)
80
+ {{state_machine_link}}{{glossary_link}}
81
+
82
+ ## 关联 ADR
83
+
84
+ <!-- 例如:../adr/0004-withdrawal-idempotency.md -->
85
+ `;
86
+ const DOMAIN_RULES = `---
87
+ title: {{domain_title}}业务规则
88
+ status: active
89
+ owner: {{owner}}
90
+ last_reviewed: {{date}}
91
+ source_of_truth: true
92
+ applies_to: []
93
+ ---
94
+
95
+ # {{domain_title}}业务规则
96
+
97
+ ## 术语
98
+
99
+ <!-- 仅列出该领域特有术语;通用术语应放到 docs/domain/glossary.md。 -->
100
+
101
+ ## 不变量
102
+
103
+ <!-- 无论何时都必须成立的规则。 -->
104
+
105
+ -
106
+ -
107
+ -
108
+
109
+ ## 权限规则
110
+
111
+ | 角色 | 可执行操作 | 限制 |
112
+ |---|---|---|
113
+ | | | |
114
+
115
+ ## 边界条件
116
+
117
+ <!-- 写清重复请求、失败重试、并发、撤销、退款、异常状态等行为。 -->
118
+
119
+ ## 审计与追踪
120
+
121
+ <!-- 是否需要审计日志、操作人、时间、状态变更原因等。 -->
122
+
123
+ ## 禁止行为
124
+
125
+ -
126
+ -
127
+ `;
128
+ const DOMAIN_STATE_MACHINE = `---
129
+ title: {{domain_title}}状态机
130
+ status: active
131
+ owner: {{owner}}
132
+ last_reviewed: {{date}}
133
+ source_of_truth: true
134
+ applies_to: []
135
+ ---
136
+
137
+ # {{domain_title}}状态机
138
+
139
+ ## 状态列表
140
+
141
+ | 状态 | 含义 | 是否终态 |
142
+ |---|---|---|
143
+ | | | 否 |
144
+
145
+ ## 状态流转
146
+
147
+ \`\`\`mermaid
148
+ stateDiagram-v2
149
+ [*] --> pending
150
+ pending --> completed
151
+ pending --> cancelled
152
+ \`\`\`
153
+
154
+ ## 流转规则
155
+
156
+ | 当前状态 | 触发事件 | 下一个状态 | 前置条件 | 副作用 |
157
+ | ---- | ---- | ----- | ---- | --- |
158
+ | | | | | |
159
+
160
+ ## 幂等规则
161
+
162
+ <!-- 对重复请求、重复事件、重复回调的处理规则。 -->
163
+ `;
164
+ const DOMAIN_GLOSSARY = `---
165
+ title: {{domain_title}}术语表
166
+ status: active
167
+ owner: {{owner}}
168
+ last_reviewed: {{date}}
169
+ source_of_truth: true
170
+ applies_to: []
171
+ ---
172
+
173
+ # {{domain_title}}术语表
174
+
175
+ | 术语 | 定义 | 备注 |
176
+ |---|---|---|
177
+ | | | |
178
+ `;
179
+ const DOMAIN_AGENTS = `# {{domain_title}} Module Rules
180
+
181
+ ## Scope
182
+
183
+ This directory contains the {{domain_title}} domain.
184
+
185
+ ## Required Reading
186
+
187
+ - \`README.md\`
188
+ - \`rules.md\`
189
+ {{state_machine_reading}}
190
+
191
+ ## Rules for Agents
192
+
193
+ - Do not change domain behavior without updating the relevant domain documentation.
194
+ - Do not introduce new states without updating \`state-machine.md\`.
195
+ - Preserve existing audit and idempotency behavior.
196
+ - Keep business rules out of controllers and UI components.
197
+ - Add tests for normal, boundary, and retry scenarios.
198
+
199
+ ## Review Checklist
200
+
201
+ - Are permissions affected?
202
+ - Are money, inventory, or externally triggered callbacks affected?
203
+ - Is retry or duplicate handling required?
204
+ - Does this change require an ADR?
205
+ `;
206
+ const ADR = `---
207
+ title: ADR-{{adr_number}}:{{title}}
208
+ status: {{status}}
209
+ date: {{date}}
210
+ owners:
211
+ - {{owner}}
212
+ supersedes: []
213
+ superseded_by: []
214
+ tags: []
215
+ ---
216
+
217
+ # ADR-{{adr_number}}:{{title}}
218
+
219
+ ## 背景
220
+
221
+ <!-- 当前问题是什么?为什么需要做决策? -->
222
+
223
+ ## 决策
224
+
225
+ <!-- 明确采用什么方案。必须能被以后的人理解和执行。 -->
226
+
227
+ ## 不采用的方案
228
+
229
+ ### 方案 A
230
+
231
+ <!-- 为什么不选。 -->
232
+
233
+ ### 方案 B
234
+
235
+ <!-- 为什么不选。 -->
236
+
237
+ ## 后果
238
+
239
+ ### 正向影响
240
+
241
+ -
242
+
243
+ ### 代价与限制
244
+
245
+ -
246
+
247
+ ### 后续约束
248
+
249
+ -
250
+
251
+ ## 关联内容
252
+
253
+ - Domain docs:
254
+ - OpenSpec changes:
255
+ - Related ADRs:
256
+ - Related APIs:
257
+ `;
258
+ const ADR_0001 = `---
259
+ title: ADR-{{adr_number}}:Repository conventions
260
+ status: accepted
261
+ date: {{date}}
262
+ owners:
263
+ - {{owner}}
264
+ supersedes: []
265
+ superseded_by: []
266
+ tags: [conventions]
267
+ ---
268
+
269
+ # ADR-{{adr_number}}:Repository conventions
270
+
271
+ ## 背景
272
+
273
+ 项目接入 AI Agent 协作,需要统一仓库元数据结构:AGENTS.md、领域文档、ADR、frontmatter 与校验规则。
274
+
275
+ ## 决策
276
+
277
+ 采用 Agent Meta 约定:
278
+
279
+ - 根 \`AGENTS.md\` 只保留操作性规则与文档入口,不复制领域规则。
280
+ - 领域规则统一放在 \`docs/domain/<domain>/\`。
281
+ - 长期架构决策记录在 \`docs/adr/\`,四位递增编号,不复用编号。
282
+ - 受约束文档必须包含 frontmatter:title、status、owner、last_reviewed。
283
+ - 通过 \`meta check\` 在本地与 CI 校验元数据完整性。
284
+
285
+ ## 不采用的方案
286
+
287
+ ### 单一大 AGENTS.md
288
+
289
+ 规则集中在单文件会不可维护,且 Agent 无法按目录感知局部规则。
290
+
291
+ ## 后果
292
+
293
+ ### 正向影响
294
+
295
+ - 规则只写一次,可被机器校验。
296
+
297
+ ### 代价与限制
298
+
299
+ - 需要维护文档 frontmatter 与审核时间。
300
+
301
+ ### 后续约束
302
+
303
+ - 新领域必须通过 \`meta new-domain\` 创建。
304
+
305
+ ## 关联内容
306
+
307
+ - Domain docs: docs/domain/
308
+ - Related ADRs:
309
+ `;
310
+ const DOMAIN_INDEX_README = `---
311
+ title: Domain Index
312
+ status: active
313
+ owner: {{owner}}
314
+ last_reviewed: {{date}}
315
+ source_of_truth: false
316
+ applies_to: []
317
+ ---
318
+
319
+ # Domain Index
320
+
321
+ <!-- meta:domain-index:start -->
322
+ <!-- Generated by \`meta sync --indexes\`. Do not edit between markers. -->
323
+ <!-- meta:domain-index:end -->
324
+ `;
325
+ const ADR_INDEX_README = `---
326
+ title: ADR Index
327
+ status: active
328
+ owner: {{owner}}
329
+ last_reviewed: {{date}}
330
+ source_of_truth: false
331
+ applies_to: []
332
+ ---
333
+
334
+ # ADR Index
335
+
336
+ <!-- meta:adr-index:start -->
337
+ <!-- Generated by \`meta sync --indexes\`. Do not edit between markers. -->
338
+ <!-- meta:adr-index:end -->
339
+ `;
340
+ const GLOSSARY = `---
341
+ title: 术语表
342
+ status: active
343
+ owner: {{owner}}
344
+ last_reviewed: {{date}}
345
+ source_of_truth: true
346
+ applies_to: []
347
+ ---
348
+
349
+ # 术语表
350
+
351
+ <!-- 跨领域通用术语。领域特有术语放在各领域目录下。 -->
352
+
353
+ | 术语 | 定义 | 备注 |
354
+ |---|---|---|
355
+ | | | |
356
+ `;
357
+ const SYSTEM_OVERVIEW = `---
358
+ title: 系统架构总览
359
+ status: draft
360
+ owner: {{owner}}
361
+ last_reviewed: {{date}}
362
+ source_of_truth: true
363
+ applies_to: []
364
+ ---
365
+
366
+ # 系统架构总览
367
+
368
+ ## 系统边界
369
+
370
+ <!-- 系统包含哪些应用 / 服务。 -->
371
+
372
+ ## 模块划分
373
+
374
+ <!-- 主要模块与职责。 -->
375
+
376
+ ## 关键依赖
377
+
378
+ <!-- 数据库、消息队列、第三方服务。 -->
379
+ `;
380
+ const API_CONVENTIONS = `---
381
+ title: API 约定
382
+ status: draft
383
+ owner: {{owner}}
384
+ last_reviewed: {{date}}
385
+ source_of_truth: true
386
+ applies_to: []
387
+ ---
388
+
389
+ # API 约定
390
+
391
+ ## 命名与版本
392
+
393
+ ## 错误码规范
394
+
395
+ ## 鉴权约定
396
+ `;
397
+ const ENGINEERING_TESTING = `---
398
+ title: 测试规范
399
+ status: draft
400
+ owner: {{owner}}
401
+ last_reviewed: {{date}}
402
+ source_of_truth: true
403
+ applies_to: []
404
+ ---
405
+
406
+ # 测试规范
407
+
408
+ ## 必须覆盖的场景
409
+
410
+ - 正常路径
411
+ - 边界条件
412
+ - 重试与幂等
413
+
414
+ ## 工具与命令
415
+ `;
416
+ const RUNBOOKS_README = `---
417
+ title: Runbooks
418
+ status: draft
419
+ owner: {{owner}}
420
+ last_reviewed: {{date}}
421
+ source_of_truth: true
422
+ applies_to: []
423
+ ---
424
+
425
+ # Runbooks
426
+
427
+ <!-- 运维手册与故障处理流程。 -->
428
+ `;
429
+ export const BUILTIN_TEMPLATES = {
430
+ "agents.md": AGENTS_MD,
431
+ "domain-readme.md": DOMAIN_README,
432
+ "domain-rules.md": DOMAIN_RULES,
433
+ "domain-state-machine.md": DOMAIN_STATE_MACHINE,
434
+ "domain-glossary.md": DOMAIN_GLOSSARY,
435
+ "domain-agents.md": DOMAIN_AGENTS,
436
+ "adr.md": ADR,
437
+ "adr-0001.md": ADR_0001,
438
+ "domain-index-readme.md": DOMAIN_INDEX_README,
439
+ "adr-index-readme.md": ADR_INDEX_README,
440
+ "glossary.md": GLOSSARY,
441
+ "system-overview.md": SYSTEM_OVERVIEW,
442
+ "api-conventions.md": API_CONVENTIONS,
443
+ "engineering-testing.md": ENGINEERING_TESTING,
444
+ "runbooks-readme.md": RUNBOOKS_README,
445
+ };
@@ -0,0 +1,3 @@
1
+ import type { ValidatorContext, ValidatorResult } from "./context.js";
2
+ export declare const VALID_ADR_STATUSES: string[];
3
+ export declare function validateAdrs(ctx: ValidatorContext): ValidatorResult;
@@ -0,0 +1,182 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parseDocFile, sectionHasContent, hasSection } from "../core/frontmatter.js";
4
+ import { dateValueToString } from "../core/paths.js";
5
+ import { listAdrFiles, ADR_FILE_RE } from "../generators/adr-generator.js";
6
+ export const VALID_ADR_STATUSES = ["proposed", "accepted", "deprecated", "superseded", "rejected"];
7
+ const SECTION_ALIASES = {
8
+ background: ["背景", "background", "context"],
9
+ decision: ["决策", "decision"],
10
+ consequences: ["后果", "consequences"],
11
+ };
12
+ export function validateAdrs(ctx) {
13
+ const issues = [];
14
+ const { root, config } = ctx;
15
+ const adrDirAbs = path.join(root, config.paths.adr_docs);
16
+ if (!fs.existsSync(adrDirAbs))
17
+ return { issues, checkedFiles: 0 };
18
+ const entries = fs.readdirSync(adrDirAbs).filter((f) => f.endsWith(".md"));
19
+ const adrFiles = listAdrFiles(adrDirAbs);
20
+ const relDir = config.paths.adr_docs;
21
+ // Filename format (README.md and index files are exempt).
22
+ for (const f of entries) {
23
+ if (f.toLowerCase() === "readme.md")
24
+ continue;
25
+ if (!ADR_FILE_RE.test(f)) {
26
+ issues.push({
27
+ code: "ADR_FILENAME_INVALID",
28
+ severity: "error",
29
+ file: path.join(relDir, f),
30
+ message: `ADR filename must match NNNN-slug.md (padding: ${config.defaults.adr_padding})`,
31
+ suggestion: "Rename the file, e.g. 0002-my-decision.md.",
32
+ });
33
+ }
34
+ }
35
+ // Duplicate numbers / slugs.
36
+ const byNumber = new Map();
37
+ const bySlug = new Map();
38
+ for (const info of adrFiles) {
39
+ byNumber.set(info.number, [...(byNumber.get(info.number) ?? []), path.basename(info.file)]);
40
+ bySlug.set(info.slug, [...(bySlug.get(info.slug) ?? []), path.basename(info.file)]);
41
+ }
42
+ for (const [num, files] of byNumber) {
43
+ if (files.length > 1) {
44
+ issues.push({
45
+ code: "ADR_DUPLICATE_NUMBER",
46
+ severity: "error",
47
+ file: path.join(relDir, files[1]),
48
+ message: `Duplicate ADR number ${String(num).padStart(config.defaults.adr_padding, "0")}: ${files.join(", ")}`,
49
+ suggestion: "Renumber one of the ADRs; numbers must be unique and never reused.",
50
+ });
51
+ }
52
+ }
53
+ for (const [slug, files] of bySlug) {
54
+ if (files.length > 1) {
55
+ issues.push({
56
+ code: "ADR_DUPLICATE_SLUG",
57
+ severity: "error",
58
+ file: path.join(relDir, files[1]),
59
+ message: `Duplicate ADR slug '${slug}': ${files.join(", ")}`,
60
+ });
61
+ }
62
+ }
63
+ const existingBasenames = new Set(entries);
64
+ for (const info of adrFiles) {
65
+ const rel = path.join(relDir, path.basename(info.file));
66
+ const doc = parseDocFile(info.file);
67
+ if (doc.parseError || !doc.hasFrontmatter) {
68
+ issues.push({
69
+ code: "DOC_FRONTMATTER_INVALID",
70
+ severity: "error",
71
+ file: rel,
72
+ message: doc.parseError
73
+ ? `Frontmatter YAML cannot be parsed: ${doc.parseError}`
74
+ : "ADR is missing YAML frontmatter",
75
+ });
76
+ continue;
77
+ }
78
+ const data = doc.data;
79
+ // Title must contain the same number as the filename.
80
+ const title = typeof data["title"] === "string" ? data["title"] : "";
81
+ if (!title) {
82
+ issues.push({
83
+ code: "DOC_REQUIRED_FIELD_MISSING",
84
+ severity: "error",
85
+ file: rel,
86
+ message: "ADR frontmatter is missing 'title'",
87
+ });
88
+ }
89
+ else {
90
+ const numMatch = /(\d{4,})/.exec(title);
91
+ if (!numMatch || parseInt(numMatch[1], 10) !== info.number) {
92
+ issues.push({
93
+ code: "ADR_TITLE_NUMBER_MISMATCH",
94
+ severity: "error",
95
+ file: rel,
96
+ message: `Title number does not match filename number ${info.numberStr}`,
97
+ suggestion: `Use a title like "ADR-${info.numberStr}:...".`,
98
+ });
99
+ }
100
+ }
101
+ const status = data["status"];
102
+ if (typeof status !== "string" || !VALID_ADR_STATUSES.includes(status)) {
103
+ issues.push({
104
+ code: "ADR_STATUS_INVALID",
105
+ severity: "error",
106
+ file: rel,
107
+ message: `Invalid ADR status '${String(status)}'`,
108
+ suggestion: `Use one of: ${VALID_ADR_STATUSES.join(", ")}.`,
109
+ });
110
+ }
111
+ if (!dateValueToString(data["date"])) {
112
+ issues.push({
113
+ code: "ADR_DATE_INVALID",
114
+ severity: "error",
115
+ file: rel,
116
+ message: "'date' is missing or not a valid YYYY-MM-DD date",
117
+ });
118
+ }
119
+ const owners = data["owners"];
120
+ if (!Array.isArray(owners) || owners.length === 0) {
121
+ issues.push({
122
+ code: "ADR_OWNERS_EMPTY",
123
+ severity: "error",
124
+ file: rel,
125
+ message: "'owners' must be a non-empty array",
126
+ });
127
+ }
128
+ // Required sections with non-empty content.
129
+ for (const [key, aliases] of Object.entries(SECTION_ALIASES)) {
130
+ if (!hasSection(doc.content, aliases)) {
131
+ issues.push({
132
+ code: `ADR_MISSING_${key.toUpperCase()}`,
133
+ severity: "error",
134
+ file: rel,
135
+ message: `Missing required section: ${aliases[0]}`,
136
+ suggestion: `Add a "## ${aliases[0]}" section.`,
137
+ });
138
+ }
139
+ else if (!sectionHasContent(doc.content, aliases)) {
140
+ issues.push({
141
+ code: `ADR_MISSING_${key.toUpperCase()}`,
142
+ severity: "error",
143
+ file: rel,
144
+ message: `Section "${aliases[0]}" is empty`,
145
+ suggestion: `Fill in the "## ${aliases[0]}" section.`,
146
+ });
147
+ }
148
+ }
149
+ // supersedes / superseded_by references must exist.
150
+ for (const field of ["supersedes", "superseded_by"]) {
151
+ const refs = data[field];
152
+ if (!Array.isArray(refs))
153
+ continue;
154
+ for (const ref of refs) {
155
+ if (typeof ref !== "string" || ref.trim() === "")
156
+ continue;
157
+ const base = path.basename(ref);
158
+ if (!existingBasenames.has(base)) {
159
+ issues.push({
160
+ code: "ADR_REFERENCE_NOT_FOUND",
161
+ severity: "error",
162
+ file: rel,
163
+ message: `'${field}' references a file that does not exist: ${ref}`,
164
+ });
165
+ }
166
+ }
167
+ }
168
+ if (status === "superseded") {
169
+ const sb = data["superseded_by"];
170
+ if (!Array.isArray(sb) || sb.filter((s) => typeof s === "string" && s.trim()).length === 0) {
171
+ issues.push({
172
+ code: "ADR_SUPERSEDED_WITHOUT_LINK",
173
+ severity: "warning",
174
+ file: rel,
175
+ message: "ADR is marked 'superseded' but 'superseded_by' is empty",
176
+ suggestion: "Point 'superseded_by' at the replacing ADR file.",
177
+ });
178
+ }
179
+ }
180
+ }
181
+ return { issues, checkedFiles: adrFiles.length };
182
+ }
@@ -0,0 +1,3 @@
1
+ import type { ValidatorContext, ValidatorResult } from "./context.js";
2
+ export declare function findLocalAgentsFiles(root: string, rootAgents: string): string[];
3
+ export declare function validateAgents(ctx: ValidatorContext): ValidatorResult;
@@ -0,0 +1,82 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import fg from "fast-glob";
4
+ import { compareAgentsRules } from "../analyzers/duplicate-rule-analyzer.js";
5
+ export function findLocalAgentsFiles(root, rootAgents) {
6
+ return fg
7
+ .sync("**/AGENTS.md", {
8
+ cwd: root,
9
+ ignore: ["node_modules/**", ".git/**", "dist/**", "templates/**"],
10
+ })
11
+ .filter((rel) => rel !== rootAgents)
12
+ .sort();
13
+ }
14
+ export function validateAgents(ctx) {
15
+ const issues = [];
16
+ const { root, config } = ctx;
17
+ const rootAgentsRel = config.paths.root_agents;
18
+ const rootAgentsAbs = path.join(root, rootAgentsRel);
19
+ let rootContent = null;
20
+ let checkedFiles = 0;
21
+ if (!fs.existsSync(rootAgentsAbs)) {
22
+ issues.push({
23
+ code: "ROOT_AGENTS_MISSING",
24
+ severity: "error",
25
+ file: rootAgentsRel,
26
+ message: `Root ${rootAgentsRel} is missing`,
27
+ suggestion: "Run `meta init` to generate it.",
28
+ });
29
+ }
30
+ else {
31
+ checkedFiles++;
32
+ rootContent = fs.readFileSync(rootAgentsAbs, "utf8");
33
+ const lines = rootContent.split(/\r?\n/).length;
34
+ if (lines > config.defaults.agents_max_lines) {
35
+ issues.push({
36
+ code: "AGENTS_TOO_LONG",
37
+ severity: "warning",
38
+ file: rootAgentsRel,
39
+ message: `Root AGENTS.md is ${lines} lines (recommended max: ${config.defaults.agents_max_lines})`,
40
+ suggestion: "Move detailed rules into docs/domain/, docs/architecture/ or docs/engineering/ and keep only references here.",
41
+ });
42
+ }
43
+ }
44
+ for (const rel of findLocalAgentsFiles(root, rootAgentsRel)) {
45
+ checkedFiles++;
46
+ const content = fs.readFileSync(path.join(root, rel), "utf8");
47
+ const lines = content.split(/\r?\n/).length;
48
+ if (lines > config.defaults.local_agents_max_lines) {
49
+ issues.push({
50
+ code: "AGENTS_TOO_LONG",
51
+ severity: "warning",
52
+ file: rel,
53
+ message: `Local AGENTS.md is ${lines} lines (recommended max: ${config.defaults.local_agents_max_lines})`,
54
+ suggestion: "Move detailed rules into the domain documentation.",
55
+ });
56
+ }
57
+ if (rootContent) {
58
+ const { duplicates, conflicts } = compareAgentsRules(rootContent, content);
59
+ for (const dup of duplicates) {
60
+ issues.push({
61
+ code: "AGENTS_DUPLICATE_RULE",
62
+ severity: "warning",
63
+ file: rel,
64
+ line: dup.localLine,
65
+ message: `Rule duplicates root AGENTS.md (line ${dup.rootLine}): "${dup.rule}"`,
66
+ suggestion: "Keep the detailed rule in domain docs and retain only a reference in AGENTS.md.",
67
+ });
68
+ }
69
+ for (const c of conflicts) {
70
+ issues.push({
71
+ code: "AGENTS_POTENTIAL_CONFLICT",
72
+ severity: "warning",
73
+ file: rel,
74
+ line: c.localLine,
75
+ message: `Rule may conflict with root AGENTS.md (line ${c.rootLine}): local "${c.localRule}" vs root "${c.rootRule}"`,
76
+ suggestion: "Resolve the contradiction or scope the local rule explicitly.",
77
+ });
78
+ }
79
+ }
80
+ }
81
+ return { issues, checkedFiles };
82
+ }