@a16njs/plugin-agentsmd 1.0.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 ADDED
@@ -0,0 +1,73 @@
1
+ # @a16njs/plugin-agentsmd
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@a16njs/plugin-agentsmd.svg)](https://www.npmjs.com/package/@a16njs/plugin-agentsmd)
4
+ [![codecov](https://codecov.io/gh/Texarkanine/a16n/graph/badge.svg?flag=plugin-agentsmd)](https://codecov.io/gh/Texarkanine/a16n)
5
+
6
+ [AGENTS.md](https://agents.md/) plugin for a16n. Discovers and emits AGENTS.md files at any directory depth.
7
+
8
+ ## Installation
9
+
10
+ This plugin is bundled with the `a16n` CLI. For programmatic use:
11
+
12
+ ```bash
13
+ npm install @a16njs/plugin-agentsmd
14
+ ```
15
+
16
+ ## Supported Types
17
+
18
+ | Type | AGENTS.md Format | Description |
19
+ |------|------------------|-------------|
20
+ | **GlobalPrompt** | root `AGENTS.md` | Always-active instructions |
21
+ | **FileRule** | `<dir>/AGENTS.md` | Directory-scoped instructions (directory-shaped globs only) |
22
+
23
+ AGENTS.md is plain markdown with no frontmatter, globs, skills, commands, or ignore rules, so **converting into AGENTS.md is lossy** for everything else:
24
+
25
+ - FileRules whose globs are not directory-shaped (e.g. `*.ts`) are skipped with a warning
26
+ - Skills, manual prompts, and agent-ignore rules are reported as unsupported
27
+
28
+ Converting *out of* AGENTS.md is lossless.
29
+
30
+ ## Supported Files
31
+
32
+ ### Discovery
33
+
34
+ - `AGENTS.md` — root instructions (GlobalPrompt)
35
+ - `<dir>/AGENTS.md` — nested instructions at any depth (FileRule with `globs: ['<dir>/**']`)
36
+
37
+ Per the [AGENTS.md standard](https://agents.md/), a nested AGENTS.md provides instructions scoped to its subtree. a16n encodes that scoping as a directory-shaped glob so it converts into native path-scoped rules:
38
+
39
+ - Cursor: `.cursor/rules/<dir>/AGENTSMD.mdc` with `globs: <dir>/**`
40
+ - Claude Code: `.claude/rules/<dir>/AGENTSMD.md` with `paths:` frontmatter
41
+
42
+ Discovery skips dot-directories and `node_modules`.
43
+
44
+ ### Emission
45
+
46
+ - **GlobalPrompt** → root `AGENTS.md` (multiple prompts are concatenated, with a `merged` warning)
47
+ - **GlobalPrompt** discovered from a nested `CLAUDE.md` → `<same-dir>/AGENTS.md`
48
+ - **FileRule** with a single directory-shaped glob (`<dir>/**` or `<dir>/**/*`) → `<dir>/AGENTS.md`
49
+ - Everything else → warning or unsupported (see above)
50
+
51
+ Emission deterministically overwrites target files (output depends only on the converted items, so repeated conversions converge). Replacing a pre-existing `AGENTS.md` whose content differs produces an `overwritten` warning.
52
+
53
+ When converting AGENTS.md into Cursor/Claude rules, a16n emits `AGENTSMD.*` rule filenames. See the plugin docs for rationale and collision behavior details: <https://texarkanine.github.io/a16n/plugin-agentsmd>.
54
+
55
+ ## Usage
56
+
57
+ ```typescript
58
+ import agentsmdPlugin from '@a16njs/plugin-agentsmd';
59
+ import { A16nEngine } from '@a16njs/engine';
60
+
61
+ const engine = new A16nEngine([agentsmdPlugin]);
62
+
63
+ // Discover AGENTS.md files
64
+ const result = await agentsmdPlugin.discover('./my-project');
65
+ console.log(`Found ${result.items.length} items`);
66
+
67
+ // Emit to AGENTS.md format
68
+ await agentsmdPlugin.emit(result.items, './my-project');
69
+ ```
70
+
71
+ ## Documentation
72
+
73
+ Full documentation available at <https://texarkanine.github.io/a16n/plugin-agentsmd>.
@@ -0,0 +1,17 @@
1
+ import { type DiscoveryResult, type Workspace } from '@a16njs/models';
2
+ /**
3
+ * Discover AGENTS.md files in a project directory tree.
4
+ *
5
+ * Mapping (see Issue #50 and the a16n IR model):
6
+ * - Root `AGENTS.md` → GlobalPrompt (always-applied instructions)
7
+ * - Nested `<dir>/AGENTS.md` → FileRule with `globs: ['<dir>/**']` and
8
+ * `relativeDir: '<dir>'` — the AGENTS.md standard scopes nested files to
9
+ * their subtree ("the closest AGENTS.md wins"), which the IR expresses as
10
+ * a directory-shaped glob. This is what lets nested AGENTS.md files
11
+ * convert into Cursor `globs:` rules and Claude `paths:` rules.
12
+ *
13
+ * @param rootOrWorkspace - Root directory path or Workspace instance
14
+ * @returns All customizations found and any warnings
15
+ */
16
+ export declare function discover(rootOrWorkspace: string | Workspace): Promise<DiscoveryResult>;
17
+ //# sourceMappingURL=discover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,eAAe,EAIpB,KAAK,SAAS,EAOf,MAAM,gBAAgB,CAAC;AA0CxB;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,eAAe,CAAC,CAkD5F"}
@@ -0,0 +1,98 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { CustomizationType, WarningCode, createId, inferGlobalPromptName, resolveRoot, CURRENT_IR_VERSION, } from '@a16njs/models';
4
+ /**
5
+ * Recursively find all AGENTS.md files in a directory tree.
6
+ * Returns POSIX-style paths relative to root (e.g., "AGENTS.md", "web/AGENTS.md").
7
+ *
8
+ * Skips dot-directories (e.g. `.git`, `.cursor`) and `node_modules`,
9
+ * matching the traversal rules of the other bundled plugins.
10
+ */
11
+ async function findAgentsFiles(root, currentDir = '') {
12
+ const results = [];
13
+ const fullPath = currentDir ? path.join(root, currentDir) : root;
14
+ try {
15
+ const entries = await fs.readdir(fullPath, { withFileTypes: true });
16
+ for (const entry of entries) {
17
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') {
18
+ continue;
19
+ }
20
+ const relativePath = currentDir
21
+ ? `${currentDir}/${entry.name}`
22
+ : entry.name;
23
+ if (entry.isFile() && entry.name === 'AGENTS.md') {
24
+ results.push(relativePath);
25
+ }
26
+ else if (entry.isDirectory()) {
27
+ const nested = await findAgentsFiles(root, relativePath);
28
+ results.push(...nested);
29
+ }
30
+ }
31
+ }
32
+ catch {
33
+ // Directory doesn't exist or can't be read
34
+ }
35
+ return results;
36
+ }
37
+ /**
38
+ * Discover AGENTS.md files in a project directory tree.
39
+ *
40
+ * Mapping (see Issue #50 and the a16n IR model):
41
+ * - Root `AGENTS.md` → GlobalPrompt (always-applied instructions)
42
+ * - Nested `<dir>/AGENTS.md` → FileRule with `globs: ['<dir>/**']` and
43
+ * `relativeDir: '<dir>'` — the AGENTS.md standard scopes nested files to
44
+ * their subtree ("the closest AGENTS.md wins"), which the IR expresses as
45
+ * a directory-shaped glob. This is what lets nested AGENTS.md files
46
+ * convert into Cursor `globs:` rules and Claude `paths:` rules.
47
+ *
48
+ * @param rootOrWorkspace - Root directory path or Workspace instance
49
+ * @returns All customizations found and any warnings
50
+ */
51
+ export async function discover(rootOrWorkspace) {
52
+ const root = resolveRoot(rootOrWorkspace);
53
+ const items = [];
54
+ const warnings = [];
55
+ const agentsFiles = await findAgentsFiles(root);
56
+ for (const file of agentsFiles) {
57
+ const fullPath = path.join(root, ...file.split('/'));
58
+ try {
59
+ const content = await fs.readFile(fullPath, 'utf-8');
60
+ const dir = path.posix.dirname(file);
61
+ const depth = dir === '.' ? 0 : dir.split('/').length;
62
+ if (dir === '.') {
63
+ // Root AGENTS.md → always-applied GlobalPrompt
64
+ items.push({
65
+ id: createId(CustomizationType.GlobalPrompt, file),
66
+ type: CustomizationType.GlobalPrompt,
67
+ version: CURRENT_IR_VERSION,
68
+ sourcePath: file,
69
+ name: inferGlobalPromptName(file),
70
+ content,
71
+ metadata: { nested: false, depth },
72
+ });
73
+ }
74
+ else {
75
+ // Nested AGENTS.md → subtree-scoped FileRule
76
+ items.push({
77
+ id: createId(CustomizationType.FileRule, file),
78
+ type: CustomizationType.FileRule,
79
+ version: CURRENT_IR_VERSION,
80
+ sourcePath: file,
81
+ relativeDir: dir,
82
+ content,
83
+ globs: [`${dir}/**`],
84
+ metadata: { nested: true, depth },
85
+ });
86
+ }
87
+ }
88
+ catch (error) {
89
+ warnings.push({
90
+ code: WarningCode.Skipped,
91
+ message: `Could not read ${file}: ${error.message}`,
92
+ sources: [file],
93
+ });
94
+ }
95
+ }
96
+ return { items, warnings };
97
+ }
98
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"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"}
package/dist/emit.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { type AgentCustomization, type EmitResult, type EmitOptions, type Workspace } from '@a16njs/models';
2
+ /**
3
+ * Emit agent customizations as AGENTS.md files.
4
+ *
5
+ * Placement:
6
+ * - GlobalPrompt → root `AGENTS.md`. `relativeDir` is deliberately ignored:
7
+ * it describes file organization inside a rules directory, not scoping —
8
+ * an always-applied prompt's only faithful AGENTS.md location is the root.
9
+ * - GlobalPrompt with `metadata.nested === true` and a `sourcePath` (nested
10
+ * CLAUDE.md discovered by the claude plugin) → `dirname(sourcePath)/AGENTS.md`,
11
+ * preserving the directory scoping those files carry. Falls back to the
12
+ * root when the source directory is unusable (always-applied content is
13
+ * never dropped).
14
+ * - FileRule whose globs are a single directory-shaped pattern (`<dir>/**`
15
+ * or `<dir>/**\/*`) → `<dir>/AGENTS.md`. Other FileRules cannot be
16
+ * represented in AGENTS.md and are skipped with a warning.
17
+ * - All other types are returned in `unsupported`.
18
+ *
19
+ * Write semantics: deterministic overwrite — output is a pure function of
20
+ * the input items, so repeated emission is idempotent. Multiple items
21
+ * targeting the same file are concatenated (`\n\n` joined, input order) with
22
+ * a `Merged` warning. Replacing a pre-existing file whose content differs
23
+ * produces an `Overwritten` warning; byte-identical re-writes stay silent.
24
+ *
25
+ * @param models - The customizations to emit
26
+ * @param rootOrWorkspace - Root directory path or Workspace instance to write to
27
+ * @param options - Optional emit options (e.g., dryRun)
28
+ * @returns Info about what was written (or would be written) and any issues
29
+ */
30
+ export declare function emit(models: AgentCustomization[], rootOrWorkspace: string | Workspace, options?: EmitOptions): Promise<EmitResult>;
31
+ //# sourceMappingURL=emit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,WAAW,EAIhB,KAAK,SAAS,EAKf,MAAM,gBAAgB,CAAC;AAkDxB;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,IAAI,CACxB,MAAM,EAAE,kBAAkB,EAAE,EAC5B,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,UAAU,CAAC,CAyGrB"}
package/dist/emit.js ADDED
@@ -0,0 +1,174 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { WarningCode, isGlobalPrompt, isFileRule, resolveRoot, } from '@a16njs/models';
4
+ /**
5
+ * Matches a directory-shaped glob: `<dir>/**` or `<dir>/**\/*`.
6
+ * Group 1 captures the directory part.
7
+ */
8
+ const DIR_SHAPED_GLOB = /^(.+?)\/\*\*(\/\*)?$/;
9
+ /** Glob metacharacters that disqualify a captured directory part. */
10
+ const GLOB_METACHARS = /[*?[\]{}]/;
11
+ /**
12
+ * Validate a POSIX-style relative directory and confirm it stays inside root.
13
+ * Returns the normalized relative dir ('' for root), or null when the input
14
+ * is absolute, contains empty/'..' segments, or escapes the root.
15
+ */
16
+ function resolveSafeDir(root, dir) {
17
+ if (!dir || dir === '.')
18
+ return '';
19
+ if (dir.startsWith('/') || /^[A-Za-z]:/.test(dir))
20
+ return null;
21
+ const segments = dir.split('/');
22
+ if (segments.some(s => s === '' || s === '.' || s === '..'))
23
+ return null;
24
+ const resolved = path.resolve(root, ...segments);
25
+ const resolvedRoot = path.resolve(root);
26
+ if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + path.sep)) {
27
+ return null;
28
+ }
29
+ return segments.join('/');
30
+ }
31
+ /**
32
+ * Extract the target directory for a FileRule, when its globs are exactly one
33
+ * directory-shaped pattern with a clean (metacharacter-free) directory part.
34
+ * Returns the POSIX relative dir, or null when the rule cannot be represented
35
+ * as a directory-scoped AGENTS.md.
36
+ */
37
+ function fileRuleTargetDir(root, rule) {
38
+ if (rule.globs.length !== 1)
39
+ return null;
40
+ const glob = rule.globs[0].replace(/^\.\//, '');
41
+ const match = DIR_SHAPED_GLOB.exec(glob);
42
+ if (!match)
43
+ return null;
44
+ const dir = match[1];
45
+ if (GLOB_METACHARS.test(dir))
46
+ return null;
47
+ return resolveSafeDir(root, dir);
48
+ }
49
+ /**
50
+ * Emit agent customizations as AGENTS.md files.
51
+ *
52
+ * Placement:
53
+ * - GlobalPrompt → root `AGENTS.md`. `relativeDir` is deliberately ignored:
54
+ * it describes file organization inside a rules directory, not scoping —
55
+ * an always-applied prompt's only faithful AGENTS.md location is the root.
56
+ * - GlobalPrompt with `metadata.nested === true` and a `sourcePath` (nested
57
+ * CLAUDE.md discovered by the claude plugin) → `dirname(sourcePath)/AGENTS.md`,
58
+ * preserving the directory scoping those files carry. Falls back to the
59
+ * root when the source directory is unusable (always-applied content is
60
+ * never dropped).
61
+ * - FileRule whose globs are a single directory-shaped pattern (`<dir>/**`
62
+ * or `<dir>/**\/*`) → `<dir>/AGENTS.md`. Other FileRules cannot be
63
+ * represented in AGENTS.md and are skipped with a warning.
64
+ * - All other types are returned in `unsupported`.
65
+ *
66
+ * Write semantics: deterministic overwrite — output is a pure function of
67
+ * the input items, so repeated emission is idempotent. Multiple items
68
+ * targeting the same file are concatenated (`\n\n` joined, input order) with
69
+ * a `Merged` warning. Replacing a pre-existing file whose content differs
70
+ * produces an `Overwritten` warning; byte-identical re-writes stay silent.
71
+ *
72
+ * @param models - The customizations to emit
73
+ * @param rootOrWorkspace - Root directory path or Workspace instance to write to
74
+ * @param options - Optional emit options (e.g., dryRun)
75
+ * @returns Info about what was written (or would be written) and any issues
76
+ */
77
+ export async function emit(models, rootOrWorkspace, options) {
78
+ const root = resolveRoot(rootOrWorkspace);
79
+ const dryRun = options?.dryRun ?? false;
80
+ const written = [];
81
+ const warnings = [];
82
+ const unsupported = [];
83
+ // Bucket items by target directory ('' = repo root), preserving input order.
84
+ const buckets = new Map();
85
+ const addToBucket = (dir, item) => {
86
+ const bucket = buckets.get(dir);
87
+ if (bucket) {
88
+ bucket.push(item);
89
+ }
90
+ else {
91
+ buckets.set(dir, [item]);
92
+ }
93
+ };
94
+ for (const model of models) {
95
+ if (isGlobalPrompt(model)) {
96
+ let dir = '';
97
+ if (model.metadata?.nested === true && model.sourcePath) {
98
+ const sourceDir = path.posix.dirname(model.sourcePath.split(path.sep).join('/'));
99
+ dir = resolveSafeDir(root, sourceDir) ?? '';
100
+ }
101
+ addToBucket(dir, model);
102
+ }
103
+ else if (isFileRule(model)) {
104
+ const dir = fileRuleTargetDir(root, model);
105
+ if (dir === null || dir === '') {
106
+ // '' means the glob resolved to the root itself, which a dir-scoped
107
+ // rule cannot legitimately do — treat it like any other unrepresentable glob.
108
+ warnings.push({
109
+ code: WarningCode.Skipped,
110
+ message: `FileRule skipped: globs cannot be represented as a directory-scoped AGENTS.md (${model.globs.join(', ')})`,
111
+ sources: model.sourcePath ? [model.sourcePath] : [],
112
+ });
113
+ continue;
114
+ }
115
+ addToBucket(dir, model);
116
+ }
117
+ else {
118
+ unsupported.push(model);
119
+ }
120
+ }
121
+ // Deterministic output order: root first, then directories sorted.
122
+ const dirs = [...buckets.keys()].sort((a, b) => a === '' ? -1 : b === '' ? 1 : a.localeCompare(b));
123
+ for (const dir of dirs) {
124
+ const items = buckets.get(dir);
125
+ const targetDir = dir ? path.join(root, ...dir.split('/')) : root;
126
+ const targetPath = path.join(targetDir, 'AGENTS.md');
127
+ const relPath = dir ? `${dir}/AGENTS.md` : 'AGENTS.md';
128
+ const sources = items
129
+ .map(i => i.sourcePath)
130
+ .filter((s) => s !== undefined);
131
+ const content = items.map(i => i.content.trim()).join('\n\n') + '\n';
132
+ let existing = null;
133
+ try {
134
+ existing = await fs.readFile(targetPath, 'utf-8');
135
+ }
136
+ catch (error) {
137
+ const err = error;
138
+ if (err.code === 'ENOENT') {
139
+ existing = null;
140
+ }
141
+ else {
142
+ throw error;
143
+ }
144
+ }
145
+ const isNewFile = existing === null;
146
+ if (!dryRun) {
147
+ await fs.mkdir(targetDir, { recursive: true });
148
+ await fs.writeFile(targetPath, content, 'utf-8');
149
+ }
150
+ written.push({
151
+ path: targetPath,
152
+ type: items[0].type,
153
+ itemCount: items.length,
154
+ isNewFile,
155
+ sourceItems: items,
156
+ });
157
+ if (items.length > 1) {
158
+ warnings.push({
159
+ code: WarningCode.Merged,
160
+ message: `Merged ${items.length} items into ${relPath}`,
161
+ sources,
162
+ });
163
+ }
164
+ if (!isNewFile && existing !== content) {
165
+ warnings.push({
166
+ code: WarningCode.Overwritten,
167
+ message: `Replaced existing ${relPath}`,
168
+ sources,
169
+ });
170
+ }
171
+ }
172
+ return { written, warnings, unsupported };
173
+ }
174
+ //# sourceMappingURL=emit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit.js","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAQL,WAAW,EACX,cAAc,EACd,UAAU,EACV,WAAW,GACZ,MAAM,gBAAgB,CAAC;AAExB;;;GAGG;AACH,MAAM,eAAe,GAAG,sBAAsB,CAAC;AAE/C,qEAAqE;AACrE,MAAM,cAAc,GAAG,WAAW,CAAC;AAEnC;;;;GAIG;AACH,SAAS,cAAc,CAAC,IAAY,EAAE,GAAW;IAC/C,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,GAAG;QAAE,OAAO,EAAE,CAAC;IACnC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAE/D,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,QAAQ,CAAC,CAAC;IACjD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,QAAQ,KAAK,YAAY,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,IAAY,EAAE,IAAc;IACrD,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IACtB,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAE1C,OAAO,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,MAA4B,EAC5B,eAAmC,EACnC,OAAqB;IAErB,MAAM,IAAI,GAAG,WAAW,CAAC,eAAe,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,KAAK,CAAC;IACxC,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,WAAW,GAAyB,EAAE,CAAC;IAE7C,6EAA6E;IAC7E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAgC,CAAC;IACxD,MAAM,WAAW,GAAG,CAAC,GAAW,EAAE,IAAwB,EAAQ,EAAE;QAClE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,GAAG,GAAG,EAAE,CAAC;YACb,IAAI,KAAK,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;gBACxD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;gBACjF,GAAG,GAAG,cAAc,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC;YAC9C,CAAC;YACD,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;aAAM,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC3C,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;gBAC/B,oEAAoE;gBACpE,8EAA8E;gBAC9E,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,WAAW,CAAC,OAAO;oBACzB,OAAO,EAAE,kFAAkF,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;oBACpH,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE;iBACpD,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YACD,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,mEAAmE;IACnE,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC7C,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAClD,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;QAChC,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAClE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;QACvD,MAAM,OAAO,GAAG,KAAK;aAClB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;aACtB,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;QAE/C,MAAM,OAAO,GACX,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;QAEvD,IAAI,QAAQ,GAAkB,IAAI,CAAC;QACnC,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,KAA8B,CAAC;YAC3C,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC1B,QAAQ,GAAG,IAAI,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACN,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QACD,MAAM,SAAS,GAAG,QAAQ,KAAK,IAAI,CAAC;QAEpC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/C,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACnD,CAAC;QAED,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI;YACpB,SAAS,EAAE,KAAK,CAAC,MAAM;YACvB,SAAS;YACT,WAAW,EAAE,KAAK;SACnB,CAAC,CAAC;QAEH,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,WAAW,CAAC,MAAM;gBACxB,OAAO,EAAE,UAAU,KAAK,CAAC,MAAM,eAAe,OAAO,EAAE;gBACvD,OAAO;aACR,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,SAAS,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YACvC,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,WAAW,CAAC,WAAW;gBAC7B,OAAO,EAAE,qBAAqB,OAAO,EAAE;gBACvC,OAAO;aACR,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type A16nPlugin } from '@a16njs/models';
2
+ /**
3
+ * AGENTS.md plugin for a16n.
4
+ *
5
+ * Discovers AGENTS.md files at any directory depth (root → GlobalPrompt,
6
+ * nested → directory-scoped FileRule) and emits GlobalPrompts and
7
+ * directory-shaped FileRules back to AGENTS.md files.
8
+ *
9
+ * AGENTS.md is plain markdown with no frontmatter, globs, or skills, so
10
+ * conversion into this format is lossy for most customization types; the
11
+ * standard warning channels (`skipped`, `merged`, `overwritten`) and the
12
+ * `unsupported` result surface exactly what could not be represented.
13
+ *
14
+ * No `pathPatterns`: AGENTS.md files have no fixed directory prefix (they
15
+ * live at any depth), so this plugin opts out of the engine's path-reference
16
+ * scanning rather than misreporting it.
17
+ */
18
+ declare const agentsmdPlugin: A16nPlugin;
19
+ export default agentsmdPlugin;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,gBAAgB,CAAC;AAIpE;;;;;;;;;;;;;;;GAeG;AACH,QAAA,MAAM,cAAc,EAAE,UAUrB,CAAC;AAEF,eAAe,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ import { CustomizationType } from '@a16njs/models';
2
+ import { discover } from './discover.js';
3
+ import { emit } from './emit.js';
4
+ /**
5
+ * AGENTS.md plugin for a16n.
6
+ *
7
+ * Discovers AGENTS.md files at any directory depth (root → GlobalPrompt,
8
+ * nested → directory-scoped FileRule) and emits GlobalPrompts and
9
+ * directory-shaped FileRules back to AGENTS.md files.
10
+ *
11
+ * AGENTS.md is plain markdown with no frontmatter, globs, or skills, so
12
+ * conversion into this format is lossy for most customization types; the
13
+ * standard warning channels (`skipped`, `merged`, `overwritten`) and the
14
+ * `unsupported` result surface exactly what could not be represented.
15
+ *
16
+ * No `pathPatterns`: AGENTS.md files have no fixed directory prefix (they
17
+ * live at any depth), so this plugin opts out of the engine's path-reference
18
+ * scanning rather than misreporting it.
19
+ */
20
+ const agentsmdPlugin = {
21
+ id: 'agentsmd',
22
+ name: 'AGENTS.md',
23
+ supports: [
24
+ CustomizationType.GlobalPrompt,
25
+ CustomizationType.FileRule,
26
+ ],
27
+ discover,
28
+ emit,
29
+ };
30
+ export default agentsmdPlugin;
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,cAAc,GAAe;IACjC,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,WAAW;IACjB,QAAQ,EAAE;QACR,iBAAiB,CAAC,YAAY;QAC9B,iBAAiB,CAAC,QAAQ;KAC3B;IAED,QAAQ;IACR,IAAI;CACL,CAAC;AAEF,eAAe,cAAc,CAAC"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@a16njs/plugin-agentsmd",
3
+ "version": "1.0.1",
4
+ "description": "AGENTS.md plugin for a16n",
5
+ "license": "AGPL-3.0",
6
+ "author": "Texarkanine",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/Texarkanine/a16n.git",
13
+ "directory": "packages/plugin-agentsmd"
14
+ },
15
+ "homepage": "https://texarkanine.github.io/a16n/plugin-agentsmd",
16
+ "type": "module",
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "scripts": {
29
+ "build": "tsc",
30
+ "clean": "rimraf dist *.tsbuildinfo",
31
+ "typecheck": "tsc --noEmit",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest",
34
+ "test:coverage": "vitest run --coverage"
35
+ },
36
+ "dependencies": {
37
+ "@a16njs/models": "workspace:*"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^25.9.3",
41
+ "typescript": "^5.4.0",
42
+ "vitest": "^2.0.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=22.0.0"
46
+ }
47
+ }