@a16njs/plugin-cursor 0.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,113 @@
1
+ # @a16njs/plugin-cursor
2
+
3
+ Cursor IDE plugin for a16n. Discovers and emits Cursor rules.
4
+
5
+ ## Installation
6
+
7
+ This plugin is bundled with the `a16n` CLI. For programmatic use:
8
+
9
+ ```bash
10
+ npm install @a16njs/plugin-cursor
11
+ ```
12
+
13
+ ## Supported Types
14
+
15
+ This plugin supports four customization types:
16
+
17
+ | Type | Format | Description |
18
+ |------|--------|-------------|
19
+ | **GlobalPrompt** | `alwaysApply: true` frontmatter | Always-active rules |
20
+ | **FileRule** | `globs: **/*.ts` frontmatter | Triggered by file patterns |
21
+ | **AgentSkill** | `description: "..."` frontmatter | Triggered by context matching |
22
+ | **AgentIgnore** | `.cursorignore` file | Files/patterns to exclude |
23
+
24
+ ## Supported Files
25
+
26
+ ### Discovery
27
+
28
+ - `.cursor/rules/**/*.mdc` - MDC format rules with frontmatter (recursive)
29
+ - `.cursorignore` - Gitignore-style patterns for files to exclude
30
+
31
+ > **Note:** Legacy `.cursorrules` files are not supported. Use `.cursor/rules/*.mdc` instead.
32
+
33
+ ### Classification Priority
34
+
35
+ Rules are classified based on frontmatter (first match wins):
36
+
37
+ 1. `alwaysApply: true` → GlobalPrompt
38
+ 2. `globs:` present → FileRule
39
+ 3. `description:` present → AgentSkill
40
+ 4. No frontmatter → GlobalPrompt (fallback)
41
+
42
+ ### Emission
43
+
44
+ - Creates `.cursor/rules/<name>.mdc` files with appropriate frontmatter
45
+ - Creates `.cursorignore` from AgentIgnore patterns
46
+
47
+ ## .cursorignore Format
48
+
49
+ The `.cursorignore` file uses gitignore-style patterns:
50
+
51
+ ```text
52
+ # Build output
53
+ dist/
54
+ build/
55
+
56
+ # Environment
57
+ .env
58
+ .env.local
59
+
60
+ # Logs
61
+ *.log
62
+
63
+ # Secrets
64
+ secrets/
65
+ ```
66
+
67
+ ## MDC Format
68
+
69
+ Cursor uses MDC (Markdown Configuration) format with YAML frontmatter:
70
+
71
+ ```markdown
72
+ ---
73
+ alwaysApply: true
74
+ ---
75
+
76
+ Always-applied rule content.
77
+ ```
78
+
79
+ ```markdown
80
+ ---
81
+ globs: **/*.tsx,**/*.jsx
82
+ ---
83
+
84
+ React-specific guidelines.
85
+ ```
86
+
87
+ ```markdown
88
+ ---
89
+ description: Authentication and authorization patterns
90
+ ---
91
+
92
+ Auth-related guidelines.
93
+ ```
94
+
95
+ ## Usage
96
+
97
+ ```typescript
98
+ import cursorPlugin from '@a16njs/plugin-cursor';
99
+ import { A16nEngine } from '@a16njs/engine';
100
+
101
+ const engine = new A16nEngine([cursorPlugin]);
102
+
103
+ // Discover Cursor rules
104
+ const result = await cursorPlugin.discover('./my-project');
105
+ console.log(`Found ${result.items.length} rules`);
106
+
107
+ // Emit to Cursor format
108
+ await cursorPlugin.emit(result.items, './my-project');
109
+ ```
110
+
111
+ ## License
112
+
113
+ MIT
@@ -0,0 +1,6 @@
1
+ import { type DiscoveryResult } from '@a16njs/models';
2
+ /**
3
+ * Discover all Cursor rules in a project directory.
4
+ */
5
+ export declare function discover(root: string): Promise<DiscoveryResult>;
6
+ //# sourceMappingURL=discover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,eAAe,EAOrB,MAAM,gBAAgB,CAAC;AAgJxB;;GAEG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA0BrE"}
@@ -0,0 +1,157 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { CustomizationType, createId, } from '@a16njs/models';
4
+ import { parseMdc } from './mdc.js';
5
+ /**
6
+ * Recursively find all .mdc files in a directory and its subdirectories.
7
+ * Returns paths relative to the rulesDir (e.g., "shared/core.mdc").
8
+ *
9
+ * NOTE: This only searches within the given rulesDir. Finding nested
10
+ * .cursor/rules/ directories elsewhere in the project is a future enhancement.
11
+ */
12
+ async function findMdcFiles(rulesDir, relativePath = '') {
13
+ const results = [];
14
+ try {
15
+ const entries = await fs.readdir(path.join(rulesDir, relativePath), { withFileTypes: true });
16
+ for (const entry of entries) {
17
+ const entryRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
18
+ if (entry.isFile() && entry.name.endsWith('.mdc')) {
19
+ results.push(entryRelativePath);
20
+ }
21
+ else if (entry.isDirectory()) {
22
+ // Recurse into subdirectories
23
+ const subFiles = await findMdcFiles(rulesDir, entryRelativePath);
24
+ results.push(...subFiles);
25
+ }
26
+ }
27
+ }
28
+ catch {
29
+ // Directory doesn't exist or can't be read
30
+ }
31
+ return results;
32
+ }
33
+ /**
34
+ * Parse comma-separated glob patterns into an array.
35
+ * Handles various formats: "*.ts,*.tsx" or "*.ts, *.tsx"
36
+ */
37
+ function parseGlobs(globsString) {
38
+ return globsString
39
+ .split(',')
40
+ .map(g => g.trim())
41
+ .filter(g => g.length > 0);
42
+ }
43
+ /**
44
+ * Classify a Cursor rule based on its frontmatter.
45
+ *
46
+ * Classification priority:
47
+ * 1. alwaysApply: true → GlobalPrompt
48
+ * 2. globs: present → FileRule
49
+ * 3. description: present → AgentSkill
50
+ * 4. None of above → GlobalPrompt (fallback)
51
+ */
52
+ function classifyRule(frontmatter, body, sourcePath) {
53
+ // Priority 1: alwaysApply: true → GlobalPrompt
54
+ if (frontmatter.alwaysApply === true) {
55
+ return {
56
+ id: createId(CustomizationType.GlobalPrompt, sourcePath),
57
+ type: CustomizationType.GlobalPrompt,
58
+ sourcePath,
59
+ content: body,
60
+ metadata: { ...frontmatter },
61
+ };
62
+ }
63
+ // Priority 2: globs present → FileRule
64
+ if (frontmatter.globs) {
65
+ const globs = parseGlobs(frontmatter.globs);
66
+ return {
67
+ id: createId(CustomizationType.FileRule, sourcePath),
68
+ type: CustomizationType.FileRule,
69
+ sourcePath,
70
+ content: body,
71
+ globs,
72
+ metadata: { ...frontmatter },
73
+ };
74
+ }
75
+ // Priority 3: description present → AgentSkill
76
+ if (frontmatter.description) {
77
+ return {
78
+ id: createId(CustomizationType.AgentSkill, sourcePath),
79
+ type: CustomizationType.AgentSkill,
80
+ sourcePath,
81
+ content: body,
82
+ description: frontmatter.description,
83
+ metadata: { ...frontmatter },
84
+ };
85
+ }
86
+ // Priority 4: Fallback → GlobalPrompt
87
+ return {
88
+ id: createId(CustomizationType.GlobalPrompt, sourcePath),
89
+ type: CustomizationType.GlobalPrompt,
90
+ sourcePath,
91
+ content: body,
92
+ metadata: { ...frontmatter },
93
+ };
94
+ }
95
+ /**
96
+ * Discover .cursorignore file and parse its patterns.
97
+ * Returns null if file doesn't exist or has no valid patterns.
98
+ */
99
+ async function discoverCursorIgnore(root) {
100
+ const ignorePath = path.join(root, '.cursorignore');
101
+ try {
102
+ const content = await fs.readFile(ignorePath, 'utf-8');
103
+ const patterns = content
104
+ .split(/\r?\n/)
105
+ .map(line => line.trimEnd())
106
+ .filter(line => {
107
+ const trimmedStart = line.trimStart();
108
+ return trimmedStart.length > 0 && !trimmedStart.startsWith('#');
109
+ })
110
+ .map(line => {
111
+ // Strip inline comments (unescaped ' #')
112
+ const inlineCommentIndex = line.indexOf(' #');
113
+ const cleaned = inlineCommentIndex >= 0 ? line.slice(0, inlineCommentIndex) : line;
114
+ return cleaned.trimEnd();
115
+ })
116
+ .filter(line => line.length > 0);
117
+ if (patterns.length === 0)
118
+ return null;
119
+ return {
120
+ id: createId(CustomizationType.AgentIgnore, '.cursorignore'),
121
+ type: CustomizationType.AgentIgnore,
122
+ sourcePath: '.cursorignore',
123
+ content,
124
+ patterns,
125
+ metadata: {},
126
+ };
127
+ }
128
+ catch {
129
+ return null;
130
+ }
131
+ }
132
+ /**
133
+ * Discover all Cursor rules in a project directory.
134
+ */
135
+ export async function discover(root) {
136
+ const items = [];
137
+ const warnings = [];
138
+ // Find .cursor/rules/*.mdc files
139
+ const rulesDir = path.join(root, '.cursor', 'rules');
140
+ const mdcFiles = await findMdcFiles(rulesDir);
141
+ for (const file of mdcFiles) {
142
+ const filePath = path.join(rulesDir, file);
143
+ const content = await fs.readFile(filePath, 'utf-8');
144
+ const { frontmatter, body } = parseMdc(content);
145
+ // Classify and add all rules (Phase 2: supports GlobalPrompt, FileRule, AgentSkill)
146
+ const sourcePath = `.cursor/rules/${file}`;
147
+ const item = classifyRule(frontmatter, body, sourcePath);
148
+ items.push(item);
149
+ }
150
+ // Discover .cursorignore (Phase 3)
151
+ const agentIgnore = await discoverCursorIgnore(root);
152
+ if (agentIgnore) {
153
+ items.push(agentIgnore);
154
+ }
155
+ return { items, warnings };
156
+ }
157
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAQL,iBAAiB,EACjB,QAAQ,GACT,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAuB,MAAM,UAAU,CAAC;AAEzD;;;;;;GAMG;AACH,KAAK,UAAU,YAAY,CAAC,QAAgB,EAAE,eAAuB,EAAE;IACrE,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAE7F,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,iBAAiB,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;YAEtF,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAClD,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAClC,CAAC;iBAAM,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBAC/B,8BAA8B;gBAC9B,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;gBACjE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,2CAA2C;IAC7C,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,WAAmB;IACrC,OAAO,WAAW;SACf,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SAClB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,YAAY,CACnB,WAA2B,EAC3B,IAAY,EACZ,UAAkB;IAElB,+CAA+C;IAC/C,IAAI,WAAW,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QACrC,OAAO;YACL,EAAE,EAAE,QAAQ,CAAC,iBAAiB,CAAC,YAAY,EAAE,UAAU,CAAC;YACxD,IAAI,EAAE,iBAAiB,CAAC,YAAY;YACpC,UAAU;YACV,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,EAAE,GAAG,WAAW,EAAE;SACb,CAAC;IACpB,CAAC;IAED,uCAAuC;IACvC,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5C,OAAO;YACL,EAAE,EAAE,QAAQ,CAAC,iBAAiB,CAAC,QAAQ,EAAE,UAAU,CAAC;YACpD,IAAI,EAAE,iBAAiB,CAAC,QAAQ;YAChC,UAAU;YACV,OAAO,EAAE,IAAI;YACb,KAAK;YACL,QAAQ,EAAE,EAAE,GAAG,WAAW,EAAE;SACjB,CAAC;IAChB,CAAC;IAED,+CAA+C;IAC/C,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC;QAC5B,OAAO;YACL,EAAE,EAAE,QAAQ,CAAC,iBAAiB,CAAC,UAAU,EAAE,UAAU,CAAC;YACtD,IAAI,EAAE,iBAAiB,CAAC,UAAU;YAClC,UAAU;YACV,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,WAAW,CAAC,WAAW;YACpC,QAAQ,EAAE,EAAE,GAAG,WAAW,EAAE;SACf,CAAC;IAClB,CAAC;IAED,sCAAsC;IACtC,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,iBAAiB,CAAC,YAAY,EAAE,UAAU,CAAC;QACxD,IAAI,EAAE,iBAAiB,CAAC,YAAY;QACpC,UAAU;QACV,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,EAAE,GAAG,WAAW,EAAE;KACb,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,oBAAoB,CAAC,IAAY;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;IAEpD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,OAAO;aACrB,KAAK,CAAC,OAAO,CAAC;aACd,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;aAC3B,MAAM,CAAC,IAAI,CAAC,EAAE;YACb,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,OAAO,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAClE,CAAC,CAAC;aACD,GAAG,CAAC,IAAI,CAAC,EAAE;YACV,yCAAyC;YACzC,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC9C,MAAM,OAAO,GAAG,kBAAkB,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACnF,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC,CAAC;aACD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAEnC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEvC,OAAO;YACL,EAAE,EAAE,QAAQ,CAAC,iBAAiB,CAAC,WAAW,EAAE,eAAe,CAAC;YAC5D,IAAI,EAAE,iBAAiB,CAAC,WAAW;YACnC,UAAU,EAAE,eAAe;YAC3B,OAAO;YACP,QAAQ;YACR,QAAQ,EAAE,EAAE;SACb,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY;IACzC,MAAM,KAAK,GAAyB,EAAE,CAAC;IACvC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,iCAAiC;IACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;IAE9C,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAEhD,oFAAoF;QACpF,MAAM,UAAU,GAAG,iBAAiB,IAAI,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,YAAY,CAAC,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QACzD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,mCAAmC;IACnC,MAAM,WAAW,GAAG,MAAM,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACrD,IAAI,WAAW,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC7B,CAAC"}
package/dist/emit.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { type AgentCustomization, type EmitResult } from '@a16njs/models';
2
+ /**
3
+ * Emit agent customizations to Cursor format.
4
+ * - GlobalPrompt → .mdc with alwaysApply: true
5
+ * - FileRule → .mdc with globs:
6
+ * - AgentSkill → .mdc with description:
7
+ */
8
+ export declare function emit(models: AgentCustomization[], root: string): Promise<EmitResult>;
9
+ //# 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,EAWhB,MAAM,gBAAgB,CAAC;AAwFxB;;;;;GAKG;AACH,wBAAsB,IAAI,CACxB,MAAM,EAAE,kBAAkB,EAAE,EAC5B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,CAAC,CAmIrB"}
package/dist/emit.js ADDED
@@ -0,0 +1,189 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { CustomizationType, WarningCode, isGlobalPrompt, isFileRule, isAgentSkill, isAgentIgnore, } from '@a16njs/models';
4
+ /**
5
+ * Sanitize a filename to be safe for the filesystem.
6
+ * Converts to lowercase, replaces spaces and special chars with hyphens.
7
+ * Returns 'rule' if sanitization produces an empty string.
8
+ */
9
+ function sanitizeFilename(sourcePath) {
10
+ // Get just the filename without directory
11
+ const basename = path.basename(sourcePath);
12
+ // Remove extension
13
+ const nameWithoutExt = basename.replace(/\.[^.]+$/, '');
14
+ // Convert to lowercase and replace unsafe characters
15
+ const sanitized = nameWithoutExt
16
+ .toLowerCase()
17
+ .replace(/[^a-z0-9]+/g, '-')
18
+ .replace(/^-+|-+$/g, ''); // Trim leading/trailing hyphens
19
+ // Return fallback if empty
20
+ return sanitized || 'rule';
21
+ }
22
+ /**
23
+ * Generate a unique filename by appending a counter if needed.
24
+ * Returns the unique filename and whether a collision occurred.
25
+ */
26
+ function getUniqueFilename(baseName, usedNames) {
27
+ if (!usedNames.has(baseName)) {
28
+ usedNames.add(baseName);
29
+ return { filename: baseName, collision: false };
30
+ }
31
+ // Collision detected - find unique name
32
+ let counter = 2;
33
+ let uniqueName = `${baseName.replace(/\.mdc$/, '')}-${counter}.mdc`;
34
+ while (usedNames.has(uniqueName)) {
35
+ counter++;
36
+ uniqueName = `${baseName.replace(/\.mdc$/, '')}-${counter}.mdc`;
37
+ }
38
+ usedNames.add(uniqueName);
39
+ return { filename: uniqueName, collision: true };
40
+ }
41
+ /**
42
+ * Format content as MDC with GlobalPrompt frontmatter.
43
+ */
44
+ function formatGlobalPromptMdc(content) {
45
+ return `---
46
+ alwaysApply: true
47
+ ---
48
+
49
+ ${content}
50
+ `;
51
+ }
52
+ /**
53
+ * Format content as MDC with FileRule frontmatter (globs).
54
+ */
55
+ function formatFileRuleMdc(content, globs) {
56
+ const globsLine = globs.join(',');
57
+ return `---
58
+ globs: ${globsLine}
59
+ ---
60
+
61
+ ${content}
62
+ `;
63
+ }
64
+ /**
65
+ * Format content as MDC with AgentSkill frontmatter (description).
66
+ */
67
+ function formatAgentSkillMdc(content, description) {
68
+ // Quote description if it contains special YAML characters
69
+ const needsQuotes = /[:&*#?|\-<>=!%@`]/.test(description);
70
+ const quotedDesc = needsQuotes ? `"${description.replace(/"/g, '\\"')}"` : description;
71
+ return `---
72
+ description: ${quotedDesc}
73
+ ---
74
+
75
+ ${content}
76
+ `;
77
+ }
78
+ /**
79
+ * Emit agent customizations to Cursor format.
80
+ * - GlobalPrompt → .mdc with alwaysApply: true
81
+ * - FileRule → .mdc with globs:
82
+ * - AgentSkill → .mdc with description:
83
+ */
84
+ export async function emit(models, root) {
85
+ const written = [];
86
+ const warnings = [];
87
+ const unsupported = [];
88
+ const usedFilenames = new Set();
89
+ // Separate by type
90
+ const globalPrompts = models.filter(isGlobalPrompt);
91
+ const fileRules = models.filter(isFileRule);
92
+ const agentSkills = models.filter(isAgentSkill);
93
+ const agentIgnores = models.filter(isAgentIgnore);
94
+ // Track unsupported types (future types)
95
+ for (const model of models) {
96
+ if (!isGlobalPrompt(model) && !isFileRule(model) && !isAgentSkill(model) && !isAgentIgnore(model)) {
97
+ unsupported.push(model);
98
+ }
99
+ }
100
+ const allItems = [...globalPrompts, ...fileRules, ...agentSkills];
101
+ // Early return only if no items at all (including agentIgnores)
102
+ if (allItems.length === 0 && agentIgnores.length === 0) {
103
+ return { written, warnings, unsupported };
104
+ }
105
+ // Track sources that had collisions for warning
106
+ const collisionSources = [];
107
+ // Ensure .cursor/rules directory exists (only if we have mdc items)
108
+ const rulesDir = path.join(root, '.cursor', 'rules');
109
+ if (allItems.length > 0) {
110
+ await fs.mkdir(rulesDir, { recursive: true });
111
+ }
112
+ // Emit each GlobalPrompt as a separate .mdc file
113
+ for (const gp of globalPrompts) {
114
+ const baseName = sanitizeFilename(gp.sourcePath) + '.mdc';
115
+ const { filename, collision } = getUniqueFilename(baseName, usedFilenames);
116
+ if (collision) {
117
+ collisionSources.push(gp.sourcePath);
118
+ }
119
+ const filepath = path.join(rulesDir, filename);
120
+ const content = formatGlobalPromptMdc(gp.content);
121
+ await fs.writeFile(filepath, content, 'utf-8');
122
+ written.push({
123
+ path: filepath,
124
+ type: CustomizationType.GlobalPrompt,
125
+ itemCount: 1,
126
+ });
127
+ }
128
+ // Emit each FileRule as a separate .mdc file with globs
129
+ for (const fr of fileRules) {
130
+ const baseName = sanitizeFilename(fr.sourcePath) + '.mdc';
131
+ const { filename, collision } = getUniqueFilename(baseName, usedFilenames);
132
+ if (collision) {
133
+ collisionSources.push(fr.sourcePath);
134
+ }
135
+ const filepath = path.join(rulesDir, filename);
136
+ const content = formatFileRuleMdc(fr.content, fr.globs);
137
+ await fs.writeFile(filepath, content, 'utf-8');
138
+ written.push({
139
+ path: filepath,
140
+ type: CustomizationType.FileRule,
141
+ itemCount: 1,
142
+ });
143
+ }
144
+ // Emit each AgentSkill as a separate .mdc file with description
145
+ for (const skill of agentSkills) {
146
+ const baseName = sanitizeFilename(skill.sourcePath) + '.mdc';
147
+ const { filename, collision } = getUniqueFilename(baseName, usedFilenames);
148
+ if (collision) {
149
+ collisionSources.push(skill.sourcePath);
150
+ }
151
+ const filepath = path.join(rulesDir, filename);
152
+ const content = formatAgentSkillMdc(skill.content, skill.description);
153
+ await fs.writeFile(filepath, content, 'utf-8');
154
+ written.push({
155
+ path: filepath,
156
+ type: CustomizationType.AgentSkill,
157
+ itemCount: 1,
158
+ });
159
+ }
160
+ // Emit warning if any collisions occurred
161
+ if (collisionSources.length > 0) {
162
+ warnings.push({
163
+ code: WarningCode.FileRenamed,
164
+ message: `Filename collision: ${collisionSources.length} file(s) renamed to avoid overwrite`,
165
+ sources: collisionSources,
166
+ });
167
+ }
168
+ // === Emit AgentIgnores as .cursorignore ===
169
+ if (agentIgnores.length > 0) {
170
+ const allPatterns = agentIgnores.flatMap(ai => ai.patterns);
171
+ const uniquePatterns = [...new Set(allPatterns)];
172
+ const filepath = path.join(root, '.cursorignore');
173
+ await fs.writeFile(filepath, uniquePatterns.join('\n') + '\n', 'utf-8');
174
+ written.push({
175
+ path: filepath,
176
+ type: CustomizationType.AgentIgnore,
177
+ itemCount: agentIgnores.length,
178
+ });
179
+ if (agentIgnores.length > 1) {
180
+ warnings.push({
181
+ code: WarningCode.Merged,
182
+ message: `Merged ${agentIgnores.length} ignore sources into .cursorignore`,
183
+ sources: agentIgnores.map(ai => ai.sourcePath),
184
+ });
185
+ }
186
+ }
187
+ return { written, warnings, unsupported };
188
+ }
189
+ //# sourceMappingURL=emit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit.js","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type A16nPlugin } from '@a16njs/models';
2
+ /**
3
+ * Cursor IDE plugin for a16n.
4
+ * Supports discovery and emission of Cursor rules.
5
+ */
6
+ declare const cursorPlugin: A16nPlugin;
7
+ export default cursorPlugin;
8
+ //# 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;;;GAGG;AACH,QAAA,MAAM,YAAY,EAAE,UAWnB,CAAC;AAEF,eAAe,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ import { CustomizationType } from '@a16njs/models';
2
+ import { discover } from './discover.js';
3
+ import { emit } from './emit.js';
4
+ /**
5
+ * Cursor IDE plugin for a16n.
6
+ * Supports discovery and emission of Cursor rules.
7
+ */
8
+ const cursorPlugin = {
9
+ id: 'cursor',
10
+ name: 'Cursor IDE',
11
+ supports: [
12
+ CustomizationType.GlobalPrompt,
13
+ CustomizationType.FileRule,
14
+ CustomizationType.AgentSkill,
15
+ ],
16
+ discover,
17
+ emit,
18
+ };
19
+ export default cursorPlugin;
20
+ //# 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;;;GAGG;AACH,MAAM,YAAY,GAAe;IAC/B,EAAE,EAAE,QAAQ;IACZ,IAAI,EAAE,YAAY;IAClB,QAAQ,EAAE;QACR,iBAAiB,CAAC,YAAY;QAC9B,iBAAiB,CAAC,QAAQ;QAC1B,iBAAiB,CAAC,UAAU;KAC7B;IAED,QAAQ;IACR,IAAI;CACL,CAAC;AAEF,eAAe,YAAY,CAAC"}
package/dist/mdc.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * MDC (Markdown Configuration) parsing utilities.
3
+ * Uses regex-based line parsing instead of YAML to avoid parsing issues
4
+ * with Cursor's non-standard frontmatter format.
5
+ */
6
+ export interface MdcFrontmatter {
7
+ alwaysApply?: boolean;
8
+ description?: string;
9
+ globs?: string;
10
+ }
11
+ export interface ParsedMdc {
12
+ frontmatter: MdcFrontmatter;
13
+ body: string;
14
+ }
15
+ /**
16
+ * Parse MDC file content into frontmatter and body.
17
+ * Uses line-by-line regex parsing for safety with Cursor's format.
18
+ */
19
+ export declare function parseMdc(content: string): ParsedMdc;
20
+ //# sourceMappingURL=mdc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdc.d.ts","sourceRoot":"","sources":["../src/mdc.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,cAAc,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CA8DnD"}
package/dist/mdc.js ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * MDC (Markdown Configuration) parsing utilities.
3
+ * Uses regex-based line parsing instead of YAML to avoid parsing issues
4
+ * with Cursor's non-standard frontmatter format.
5
+ */
6
+ /**
7
+ * Parse MDC file content into frontmatter and body.
8
+ * Uses line-by-line regex parsing for safety with Cursor's format.
9
+ */
10
+ export function parseMdc(content) {
11
+ const frontmatter = {};
12
+ // Check for frontmatter delimiters
13
+ const lines = content.split('\n');
14
+ // Find frontmatter boundaries
15
+ let frontmatterStart = -1;
16
+ let frontmatterEnd = -1;
17
+ for (let i = 0; i < lines.length; i++) {
18
+ const line = lines[i]?.trim();
19
+ if (line === '---') {
20
+ if (frontmatterStart === -1) {
21
+ frontmatterStart = i;
22
+ }
23
+ else {
24
+ frontmatterEnd = i;
25
+ break;
26
+ }
27
+ }
28
+ }
29
+ // No frontmatter found
30
+ if (frontmatterStart === -1 || frontmatterEnd === -1) {
31
+ return {
32
+ frontmatter: {},
33
+ body: content.trim(),
34
+ };
35
+ }
36
+ // Parse frontmatter lines with regex
37
+ for (let i = frontmatterStart + 1; i < frontmatterEnd; i++) {
38
+ const line = lines[i];
39
+ if (!line)
40
+ continue;
41
+ // Parse alwaysApply: true/false
42
+ const alwaysApplyMatch = line.match(/^alwaysApply:\s*(true|false)\s*$/);
43
+ if (alwaysApplyMatch) {
44
+ frontmatter.alwaysApply = alwaysApplyMatch[1] === 'true';
45
+ continue;
46
+ }
47
+ // Parse description: "..."
48
+ const descriptionMatch = line.match(/^description:\s*["']?(.+?)["']?\s*$/);
49
+ if (descriptionMatch) {
50
+ frontmatter.description = descriptionMatch[1];
51
+ continue;
52
+ }
53
+ // Parse globs: <pattern> (Cursor uses comma-separated string, not YAML array)
54
+ const globsMatch = line.match(/^globs:\s*(.+)\s*$/);
55
+ if (globsMatch) {
56
+ frontmatter.globs = globsMatch[1];
57
+ continue;
58
+ }
59
+ }
60
+ // Extract body (everything after second ---)
61
+ const bodyLines = lines.slice(frontmatterEnd + 1);
62
+ const body = bodyLines.join('\n').trim();
63
+ return { frontmatter, body };
64
+ }
65
+ //# sourceMappingURL=mdc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdc.js","sourceRoot":"","sources":["../src/mdc.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,MAAM,WAAW,GAAmB,EAAE,CAAC;IAEvC,mCAAmC;IACnC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAElC,8BAA8B;IAC9B,IAAI,gBAAgB,GAAG,CAAC,CAAC,CAAC;IAC1B,IAAI,cAAc,GAAG,CAAC,CAAC,CAAC;IAExB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QAC9B,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACnB,IAAI,gBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC5B,gBAAgB,GAAG,CAAC,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACN,cAAc,GAAG,CAAC,CAAC;gBACnB,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,IAAI,gBAAgB,KAAK,CAAC,CAAC,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;QACrD,OAAO;YACL,WAAW,EAAE,EAAE;YACf,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE;SACrB,CAAC;IACJ,CAAC;IAED,qCAAqC;IACrC,KAAK,IAAI,CAAC,GAAG,gBAAgB,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3D,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI;YAAE,SAAS;QAEpB,gCAAgC;QAChC,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACxE,IAAI,gBAAgB,EAAE,CAAC;YACrB,WAAW,CAAC,WAAW,GAAG,gBAAgB,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;YACzD,SAAS;QACX,CAAC;QAED,2BAA2B;QAC3B,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QAC3E,IAAI,gBAAgB,EAAE,CAAC;YACrB,WAAW,CAAC,WAAW,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;YAC9C,SAAS;QACX,CAAC;QAED,8EAA8E;QAC9E,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;QACpD,IAAI,UAAU,EAAE,CAAC;YACf,WAAW,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAClC,SAAS;QACX,CAAC;IACH,CAAC;IAED,6CAA6C;IAC7C,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IAEzC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;AAC/B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@a16njs/plugin-cursor",
3
+ "version": "0.0.1",
4
+ "description": "Cursor IDE plugin for a16n",
5
+ "license": "AGPL-3.0",
6
+ "author": "Texarkanine",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Texarkanine/a16n.git",
10
+ "directory": "packages/plugin-cursor"
11
+ },
12
+ "homepage": "https://github.com/Texarkanine/a16n#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/Texarkanine/a16n/issues"
15
+ },
16
+ "keywords": [
17
+ "a16n",
18
+ "plugin",
19
+ "cursor",
20
+ "ide",
21
+ "rules"
22
+ ],
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ }
31
+ },
32
+ "files": ["dist"],
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "clean": "rimraf dist *.tsbuildinfo",
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "vitest run",
38
+ "test:watch": "vitest"
39
+ },
40
+ "dependencies": {
41
+ "@a16njs/models": "workspace:*"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20.0.0",
45
+ "typescript": "^5.4.0",
46
+ "vitest": "^2.0.0"
47
+ }
48
+ }