@nerviq/cli 1.18.0 → 1.19.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.
- package/LICENSE +23 -23
- package/README.md +2 -2
- package/bin/cli.js +130 -130
- package/package.json +1 -1
- package/src/activity.js +1039 -1039
- package/src/adoption-advisor.js +299 -299
- package/src/aider/config-parser.js +166 -166
- package/src/aider/context.js +158 -158
- package/src/aider/deep-review.js +316 -316
- package/src/aider/domain-packs.js +303 -303
- package/src/aider/freshness.js +93 -93
- package/src/aider/governance.js +253 -253
- package/src/aider/interactive.js +334 -334
- package/src/aider/mcp-packs.js +329 -329
- package/src/aider/patch.js +214 -214
- package/src/aider/plans.js +186 -186
- package/src/aider/premium.js +360 -360
- package/src/aider/setup.js +404 -404
- package/src/aider/techniques.js +16 -16
- package/src/analyze.js +951 -951
- package/src/anti-patterns.js +485 -485
- package/src/audit/instruction-files.js +180 -180
- package/src/audit/recommendations.js +577 -577
- package/src/auto-suggest.js +154 -154
- package/src/badge.js +13 -13
- package/src/behavioral-drift.js +801 -801
- package/src/benchmark.js +67 -67
- package/src/catalog.js +103 -103
- package/src/certification.js +128 -128
- package/src/codex/config-parser.js +183 -183
- package/src/codex/context.js +223 -223
- package/src/codex/deep-review.js +493 -493
- package/src/codex/domain-packs.js +394 -394
- package/src/codex/freshness.js +84 -84
- package/src/codex/governance.js +192 -192
- package/src/codex/interactive.js +618 -618
- package/src/codex/mcp-packs.js +914 -914
- package/src/codex/patch.js +209 -209
- package/src/codex/plans.js +251 -251
- package/src/codex/premium.js +614 -614
- package/src/codex/setup.js +591 -591
- package/src/context.js +320 -320
- package/src/continuous-ops.js +681 -681
- package/src/copilot/activity.js +309 -309
- package/src/copilot/deep-review.js +346 -346
- package/src/copilot/domain-packs.js +372 -372
- package/src/copilot/freshness.js +57 -57
- package/src/copilot/governance.js +222 -222
- package/src/copilot/interactive.js +406 -406
- package/src/copilot/mcp-packs.js +826 -826
- package/src/copilot/plans.js +253 -253
- package/src/copilot/premium.js +451 -451
- package/src/copilot/setup.js +488 -488
- package/src/cost-tracking.js +61 -61
- package/src/cursor/activity.js +301 -301
- package/src/cursor/config-parser.js +265 -265
- package/src/cursor/context.js +256 -256
- package/src/cursor/deep-review.js +334 -334
- package/src/cursor/domain-packs.js +368 -368
- package/src/cursor/freshness.js +65 -65
- package/src/cursor/governance.js +229 -229
- package/src/cursor/interactive.js +391 -391
- package/src/cursor/mcp-packs.js +828 -828
- package/src/cursor/plans.js +254 -254
- package/src/cursor/premium.js +469 -469
- package/src/cursor/setup.js +488 -488
- package/src/dashboard.js +493 -493
- package/src/deep-review.js +428 -428
- package/src/deprecation.js +98 -98
- package/src/diff-only.js +280 -280
- package/src/doctor.js +119 -119
- package/src/domain-pack-expansion.js +1033 -1033
- package/src/domain-packs.js +387 -387
- package/src/feedback.js +178 -178
- package/src/fix-engine.js +783 -783
- package/src/fix-prompts.js +122 -122
- package/src/formatters/sarif.js +115 -115
- package/src/freshness.js +74 -74
- package/src/gemini/config-parser.js +275 -275
- package/src/gemini/context.js +221 -221
- package/src/gemini/deep-review.js +559 -559
- package/src/gemini/domain-packs.js +393 -393
- package/src/gemini/freshness.js +66 -66
- package/src/gemini/governance.js +201 -201
- package/src/gemini/interactive.js +860 -860
- package/src/gemini/mcp-packs.js +915 -915
- package/src/gemini/plans.js +269 -269
- package/src/gemini/premium.js +760 -760
- package/src/gemini/setup.js +692 -692
- package/src/gemini/techniques.js +14 -14
- package/src/governance.js +72 -72
- package/src/harmony/add.js +68 -68
- package/src/harmony/advisor.js +333 -333
- package/src/harmony/canon.js +565 -565
- package/src/harmony/cli.js +591 -591
- package/src/harmony/drift.js +401 -401
- package/src/harmony/governance.js +313 -313
- package/src/harmony/memory.js +239 -239
- package/src/harmony/sync.js +475 -475
- package/src/harmony/watch.js +370 -370
- package/src/hook-validation.js +342 -342
- package/src/index.js +271 -271
- package/src/init.js +184 -184
- package/src/instruction-surfaces.js +185 -185
- package/src/integrations.js +144 -144
- package/src/interactive.js +118 -118
- package/src/locales/en.json +1 -1
- package/src/locales/es.json +1 -1
- package/src/mcp-packs.js +830 -830
- package/src/mcp-server.js +726 -726
- package/src/mcp-validation.js +337 -337
- package/src/nerviq-sync.json +7 -7
- package/src/opencode/config-parser.js +109 -109
- package/src/opencode/context.js +247 -247
- package/src/opencode/deep-review.js +313 -313
- package/src/opencode/domain-packs.js +262 -262
- package/src/opencode/freshness.js +66 -66
- package/src/opencode/governance.js +159 -159
- package/src/opencode/interactive.js +392 -392
- package/src/opencode/mcp-packs.js +705 -705
- package/src/opencode/patch.js +184 -184
- package/src/opencode/plans.js +231 -231
- package/src/opencode/premium.js +413 -413
- package/src/opencode/setup.js +449 -449
- package/src/opencode/techniques.js +27 -27
- package/src/operating-profile.js +574 -574
- package/src/org.js +152 -152
- package/src/permission-rules.js +218 -218
- package/src/plans.js +839 -839
- package/src/platform-change-manifest.js +86 -86
- package/src/plugins.js +110 -110
- package/src/policy-layers.js +210 -210
- package/src/profiles.js +124 -124
- package/src/prompt-injection.js +74 -74
- package/src/public-api.js +173 -173
- package/src/recommendation-rules.js +84 -84
- package/src/repo-archetype.js +386 -386
- package/src/secret-patterns.js +39 -39
- package/src/server.js +527 -527
- package/src/setup/analysis.js +607 -607
- package/src/setup/runtime.js +172 -172
- package/src/setup.js +677 -677
- package/src/shared/capabilities.js +194 -194
- package/src/source-urls.js +132 -132
- package/src/stack-checks.js +565 -565
- package/src/supplemental-checks.js +13 -13
- package/src/synergy/adaptive.js +261 -261
- package/src/synergy/compensation.js +137 -137
- package/src/synergy/evidence.js +193 -193
- package/src/synergy/learning.js +199 -199
- package/src/synergy/patterns.js +227 -227
- package/src/synergy/ranking.js +83 -83
- package/src/synergy/report.js +165 -165
- package/src/synergy/routing.js +146 -146
- package/src/techniques/api.js +407 -407
- package/src/techniques/automation.js +316 -316
- package/src/techniques/compliance.js +257 -257
- package/src/techniques/hygiene.js +294 -294
- package/src/techniques/instructions.js +243 -243
- package/src/techniques/observability.js +226 -226
- package/src/techniques/optimization.js +142 -142
- package/src/techniques/quality.js +318 -318
- package/src/techniques/security.js +237 -237
- package/src/techniques/shared.js +443 -443
- package/src/techniques/stacks.js +2294 -2294
- package/src/techniques/tools.js +106 -106
- package/src/techniques/workflow.js +413 -413
- package/src/techniques.js +81 -81
- package/src/terminology.js +73 -73
- package/src/token-estimate.js +35 -35
- package/src/usage-patterns.js +99 -99
- package/src/verification-metadata.js +145 -145
- package/src/watch.js +247 -247
- package/src/windsurf/activity.js +302 -302
- package/src/windsurf/config-parser.js +267 -267
- package/src/windsurf/context.js +249 -249
- package/src/windsurf/deep-review.js +337 -337
- package/src/windsurf/domain-packs.js +370 -370
- package/src/windsurf/freshness.js +36 -36
- package/src/windsurf/governance.js +231 -231
- package/src/windsurf/interactive.js +388 -388
- package/src/windsurf/mcp-packs.js +792 -792
- package/src/windsurf/plans.js +247 -247
- package/src/windsurf/premium.js +468 -468
- package/src/windsurf/setup.js +471 -471
- package/src/windsurf/techniques.js +17 -17
- package/src/workspace.js +375 -375
package/src/cursor/context.js
CHANGED
|
@@ -1,256 +1,256 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cursor project context.
|
|
3
|
-
*
|
|
4
|
-
* Extends the shared ProjectContext with Cursor-specific file lookups:
|
|
5
|
-
* - .cursor/rules/*.mdc (MDC format: YAML frontmatter + Markdown body)
|
|
6
|
-
* - .cursorrules (legacy, ignored by agent mode)
|
|
7
|
-
* - .cursor/mcp.json (MCP server config)
|
|
8
|
-
* - .cursor/environment.json (background agent VM config)
|
|
9
|
-
* - .cursor/commands/*.md (custom slash commands)
|
|
10
|
-
* - .cursor/automations/ (event-driven triggers)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const os = require('os');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
const { ProjectContext } = require('../context');
|
|
17
|
-
const { tryParseJson, parseMdc, detectRuleType, getValueByPath } = require('./config-parser');
|
|
18
|
-
|
|
19
|
-
function listFiles(fullPath, filter) {
|
|
20
|
-
try {
|
|
21
|
-
const entries = fs.readdirSync(fullPath).filter(f => !f.startsWith('.'));
|
|
22
|
-
return filter ? entries.filter(filter) : entries;
|
|
23
|
-
} catch {
|
|
24
|
-
return [];
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class CursorProjectContext extends ProjectContext {
|
|
29
|
-
|
|
30
|
-
// ─── Rules (.cursor/rules/*.mdc) ──────────────────────────────────────
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* List all .mdc rule files in .cursor/rules/.
|
|
34
|
-
* Returns array of { name, path, frontmatter, body, ruleType }.
|
|
35
|
-
*
|
|
36
|
-
* NOTE: Subdirectories inside .cursor/rules/ are silently ignored by Cursor.
|
|
37
|
-
*/
|
|
38
|
-
cursorRules() {
|
|
39
|
-
const fs = require('fs');
|
|
40
|
-
const rulesPath = path.join(this.dir, '.cursor', 'rules');
|
|
41
|
-
let dir = rulesPath;
|
|
42
|
-
let basePath = '.cursor/rules';
|
|
43
|
-
|
|
44
|
-
// File-redirect pattern: .cursor/rules is a file pointing to another path
|
|
45
|
-
// (e.g., cal.com uses agents/rules/ with .cursor/rules as a text pointer).
|
|
46
|
-
try {
|
|
47
|
-
const stat = fs.statSync(rulesPath);
|
|
48
|
-
if (stat.isFile()) {
|
|
49
|
-
const redirect = fs.readFileSync(rulesPath, 'utf8').trim();
|
|
50
|
-
if (redirect && redirect.length < 500) {
|
|
51
|
-
const resolved = path.resolve(path.dirname(rulesPath), redirect);
|
|
52
|
-
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
53
|
-
dir = resolved;
|
|
54
|
-
basePath = path.relative(this.dir, resolved).replace(/\\/g, '/');
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
} catch { /* .cursor/rules may not exist — fall through to empty list */ }
|
|
59
|
-
|
|
60
|
-
const files = listFiles(dir, f => f.endsWith('.mdc') || f.endsWith('.md'));
|
|
61
|
-
return files.map(f => {
|
|
62
|
-
const relPath = `${basePath}/${f}`;
|
|
63
|
-
const content = this.fileContent(relPath);
|
|
64
|
-
if (!content) return null;
|
|
65
|
-
const parsed = parseMdc(content);
|
|
66
|
-
const ruleType = detectRuleType(parsed.frontmatter);
|
|
67
|
-
return {
|
|
68
|
-
name: f.replace(/\.(mdc|md)$/, ''),
|
|
69
|
-
path: relPath,
|
|
70
|
-
frontmatter: parsed.frontmatter,
|
|
71
|
-
body: parsed.body,
|
|
72
|
-
ruleType,
|
|
73
|
-
};
|
|
74
|
-
}).filter(Boolean);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get rules filtered by type.
|
|
79
|
-
*/
|
|
80
|
-
alwaysApplyRules() {
|
|
81
|
-
return this.cursorRules().filter(r => r.ruleType === 'always');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
autoAttachedRules() {
|
|
85
|
-
return this.cursorRules().filter(r => r.ruleType === 'auto-attached');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
agentRequestedRules() {
|
|
89
|
-
return this.cursorRules().filter(r => r.ruleType === 'agent-requested');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
manualRules() {
|
|
93
|
-
return this.cursorRules().filter(r => r.ruleType === 'manual');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ─── Legacy .cursorrules ──────────────────────────────────────────────
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* .cursorrules content (deprecated — AGENT MODE IGNORES THIS).
|
|
100
|
-
*/
|
|
101
|
-
legacyCursorrules() {
|
|
102
|
-
return this.fileContent('.cursorrules');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
hasLegacyRules() {
|
|
106
|
-
return Boolean(this.legacyCursorrules());
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ─── MCP config (.cursor/mcp.json) ────────────────────────────────────
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* .cursor/mcp.json parsed.
|
|
113
|
-
* Cursor MCP format: { mcpServers: { name: { command, args, env } } }
|
|
114
|
-
*/
|
|
115
|
-
mcpConfig() {
|
|
116
|
-
const content = this.fileContent('.cursor/mcp.json');
|
|
117
|
-
if (!content) {
|
|
118
|
-
return { ok: false, data: null, error: 'missing .cursor/mcp.json', source: '.cursor/mcp.json' };
|
|
119
|
-
}
|
|
120
|
-
const parsed = tryParseJson(content);
|
|
121
|
-
return { ...parsed, source: '.cursor/mcp.json' };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Global MCP config (~/.cursor/mcp.json).
|
|
126
|
-
*/
|
|
127
|
-
globalMcpConfig() {
|
|
128
|
-
const homeDir = os.homedir();
|
|
129
|
-
const globalPath = path.join(homeDir, '.cursor', 'mcp.json');
|
|
130
|
-
try {
|
|
131
|
-
const content = fs.readFileSync(globalPath, 'utf8');
|
|
132
|
-
const parsed = tryParseJson(content);
|
|
133
|
-
return { ...parsed, source: globalPath };
|
|
134
|
-
} catch {
|
|
135
|
-
return { ok: false, data: null, error: 'missing global mcp.json', source: globalPath };
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* MCP servers from .cursor/mcp.json.
|
|
141
|
-
*/
|
|
142
|
-
mcpServers() {
|
|
143
|
-
const result = this.mcpConfig();
|
|
144
|
-
if (!result.ok || !result.data) return {};
|
|
145
|
-
return result.data.mcpServers || {};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Count total MCP tools across all servers.
|
|
150
|
-
* Cursor has a hard limit of ~40 tools.
|
|
151
|
-
*/
|
|
152
|
-
totalMcpTools() {
|
|
153
|
-
const servers = this.mcpServers();
|
|
154
|
-
let total = 0;
|
|
155
|
-
for (const server of Object.values(servers)) {
|
|
156
|
-
// Each server exposes tools; estimate ~5 per server if no explicit count
|
|
157
|
-
const toolCount = server.tools ? Object.keys(server.tools).length : 5;
|
|
158
|
-
total += toolCount;
|
|
159
|
-
}
|
|
160
|
-
return total;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ─── Environment config (.cursor/environment.json) ────────────────────
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* .cursor/environment.json parsed (background agent VM config).
|
|
167
|
-
*/
|
|
168
|
-
environmentJson() {
|
|
169
|
-
const content = this.fileContent('.cursor/environment.json');
|
|
170
|
-
if (!content) {
|
|
171
|
-
return { ok: false, data: null, error: 'missing .cursor/environment.json', source: '.cursor/environment.json' };
|
|
172
|
-
}
|
|
173
|
-
const parsed = tryParseJson(content);
|
|
174
|
-
return { ...parsed, source: '.cursor/environment.json' };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ─── Custom commands (.cursor/commands/*.md) ──────────────────────────
|
|
178
|
-
|
|
179
|
-
commandFiles() {
|
|
180
|
-
const commandsDir = path.join(this.dir, '.cursor', 'commands');
|
|
181
|
-
return listFiles(commandsDir, f => f.endsWith('.md'))
|
|
182
|
-
.map(f => `.cursor/commands/${f}`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ─── Automations (.cursor/automations/) ───────────────────────────────
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Automation config files (.cursor/automations/*.yaml).
|
|
189
|
-
*/
|
|
190
|
-
automationsConfig() {
|
|
191
|
-
const dir = path.join(this.dir, '.cursor', 'automations');
|
|
192
|
-
const files = listFiles(dir, f => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
193
|
-
return files.map(f => {
|
|
194
|
-
const relPath = `.cursor/automations/${f}`;
|
|
195
|
-
const content = this.fileContent(relPath);
|
|
196
|
-
return { name: f, path: relPath, content };
|
|
197
|
-
}).filter(item => item.content);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// ─── VS Code compat (.vscode/settings.json) ──────────────────────────
|
|
201
|
-
|
|
202
|
-
vscodeSettings() {
|
|
203
|
-
const content = this.fileContent('.vscode/settings.json');
|
|
204
|
-
if (!content) {
|
|
205
|
-
return { ok: false, data: null, error: 'missing .vscode/settings.json', source: '.vscode/settings.json' };
|
|
206
|
-
}
|
|
207
|
-
const parsed = tryParseJson(content);
|
|
208
|
-
return { ...parsed, source: '.vscode/settings.json' };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// ─── Workflow files ───────────────────────────────────────────────────
|
|
212
|
-
|
|
213
|
-
workflowFiles() {
|
|
214
|
-
const dir = path.join(this.dir, '.github', 'workflows');
|
|
215
|
-
return listFiles(dir, f => f.endsWith('.yml') || f.endsWith('.yaml'))
|
|
216
|
-
.map(f => `.github/workflows/${f}`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ─── Surface detection ────────────────────────────────────────────────
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Detect which Cursor surfaces are configured.
|
|
223
|
-
*/
|
|
224
|
-
detectSurfaces() {
|
|
225
|
-
const foreground = Boolean(
|
|
226
|
-
this.cursorRules().length > 0 ||
|
|
227
|
-
this.legacyCursorrules() ||
|
|
228
|
-
this.mcpConfig().ok
|
|
229
|
-
);
|
|
230
|
-
const background = Boolean(this.environmentJson().ok);
|
|
231
|
-
const automations = this.automationsConfig().length > 0;
|
|
232
|
-
|
|
233
|
-
return { foreground, background, automations };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// ─── Static detection ─────────────────────────────────────────────────
|
|
237
|
-
|
|
238
|
-
static isCursorRepo(dir) {
|
|
239
|
-
try {
|
|
240
|
-
return fs.existsSync(path.join(dir, '.cursor')) ||
|
|
241
|
-
fs.existsSync(path.join(dir, '.cursorrules'));
|
|
242
|
-
} catch {
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// ─── Stack detection (reuse shared) ───────────────────────────────────
|
|
248
|
-
|
|
249
|
-
detectStacks(STACKS) {
|
|
250
|
-
return super.detectStacks(STACKS);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
module.exports = {
|
|
255
|
-
CursorProjectContext,
|
|
256
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Cursor project context.
|
|
3
|
+
*
|
|
4
|
+
* Extends the shared ProjectContext with Cursor-specific file lookups:
|
|
5
|
+
* - .cursor/rules/*.mdc (MDC format: YAML frontmatter + Markdown body)
|
|
6
|
+
* - .cursorrules (legacy, ignored by agent mode)
|
|
7
|
+
* - .cursor/mcp.json (MCP server config)
|
|
8
|
+
* - .cursor/environment.json (background agent VM config)
|
|
9
|
+
* - .cursor/commands/*.md (custom slash commands)
|
|
10
|
+
* - .cursor/automations/ (event-driven triggers)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { ProjectContext } = require('../context');
|
|
17
|
+
const { tryParseJson, parseMdc, detectRuleType, getValueByPath } = require('./config-parser');
|
|
18
|
+
|
|
19
|
+
function listFiles(fullPath, filter) {
|
|
20
|
+
try {
|
|
21
|
+
const entries = fs.readdirSync(fullPath).filter(f => !f.startsWith('.'));
|
|
22
|
+
return filter ? entries.filter(filter) : entries;
|
|
23
|
+
} catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class CursorProjectContext extends ProjectContext {
|
|
29
|
+
|
|
30
|
+
// ─── Rules (.cursor/rules/*.mdc) ──────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* List all .mdc rule files in .cursor/rules/.
|
|
34
|
+
* Returns array of { name, path, frontmatter, body, ruleType }.
|
|
35
|
+
*
|
|
36
|
+
* NOTE: Subdirectories inside .cursor/rules/ are silently ignored by Cursor.
|
|
37
|
+
*/
|
|
38
|
+
cursorRules() {
|
|
39
|
+
const fs = require('fs');
|
|
40
|
+
const rulesPath = path.join(this.dir, '.cursor', 'rules');
|
|
41
|
+
let dir = rulesPath;
|
|
42
|
+
let basePath = '.cursor/rules';
|
|
43
|
+
|
|
44
|
+
// File-redirect pattern: .cursor/rules is a file pointing to another path
|
|
45
|
+
// (e.g., cal.com uses agents/rules/ with .cursor/rules as a text pointer).
|
|
46
|
+
try {
|
|
47
|
+
const stat = fs.statSync(rulesPath);
|
|
48
|
+
if (stat.isFile()) {
|
|
49
|
+
const redirect = fs.readFileSync(rulesPath, 'utf8').trim();
|
|
50
|
+
if (redirect && redirect.length < 500) {
|
|
51
|
+
const resolved = path.resolve(path.dirname(rulesPath), redirect);
|
|
52
|
+
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
53
|
+
dir = resolved;
|
|
54
|
+
basePath = path.relative(this.dir, resolved).replace(/\\/g, '/');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch { /* .cursor/rules may not exist — fall through to empty list */ }
|
|
59
|
+
|
|
60
|
+
const files = listFiles(dir, f => f.endsWith('.mdc') || f.endsWith('.md'));
|
|
61
|
+
return files.map(f => {
|
|
62
|
+
const relPath = `${basePath}/${f}`;
|
|
63
|
+
const content = this.fileContent(relPath);
|
|
64
|
+
if (!content) return null;
|
|
65
|
+
const parsed = parseMdc(content);
|
|
66
|
+
const ruleType = detectRuleType(parsed.frontmatter);
|
|
67
|
+
return {
|
|
68
|
+
name: f.replace(/\.(mdc|md)$/, ''),
|
|
69
|
+
path: relPath,
|
|
70
|
+
frontmatter: parsed.frontmatter,
|
|
71
|
+
body: parsed.body,
|
|
72
|
+
ruleType,
|
|
73
|
+
};
|
|
74
|
+
}).filter(Boolean);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get rules filtered by type.
|
|
79
|
+
*/
|
|
80
|
+
alwaysApplyRules() {
|
|
81
|
+
return this.cursorRules().filter(r => r.ruleType === 'always');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
autoAttachedRules() {
|
|
85
|
+
return this.cursorRules().filter(r => r.ruleType === 'auto-attached');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
agentRequestedRules() {
|
|
89
|
+
return this.cursorRules().filter(r => r.ruleType === 'agent-requested');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
manualRules() {
|
|
93
|
+
return this.cursorRules().filter(r => r.ruleType === 'manual');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── Legacy .cursorrules ──────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* .cursorrules content (deprecated — AGENT MODE IGNORES THIS).
|
|
100
|
+
*/
|
|
101
|
+
legacyCursorrules() {
|
|
102
|
+
return this.fileContent('.cursorrules');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
hasLegacyRules() {
|
|
106
|
+
return Boolean(this.legacyCursorrules());
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ─── MCP config (.cursor/mcp.json) ────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* .cursor/mcp.json parsed.
|
|
113
|
+
* Cursor MCP format: { mcpServers: { name: { command, args, env } } }
|
|
114
|
+
*/
|
|
115
|
+
mcpConfig() {
|
|
116
|
+
const content = this.fileContent('.cursor/mcp.json');
|
|
117
|
+
if (!content) {
|
|
118
|
+
return { ok: false, data: null, error: 'missing .cursor/mcp.json', source: '.cursor/mcp.json' };
|
|
119
|
+
}
|
|
120
|
+
const parsed = tryParseJson(content);
|
|
121
|
+
return { ...parsed, source: '.cursor/mcp.json' };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Global MCP config (~/.cursor/mcp.json).
|
|
126
|
+
*/
|
|
127
|
+
globalMcpConfig() {
|
|
128
|
+
const homeDir = os.homedir();
|
|
129
|
+
const globalPath = path.join(homeDir, '.cursor', 'mcp.json');
|
|
130
|
+
try {
|
|
131
|
+
const content = fs.readFileSync(globalPath, 'utf8');
|
|
132
|
+
const parsed = tryParseJson(content);
|
|
133
|
+
return { ...parsed, source: globalPath };
|
|
134
|
+
} catch {
|
|
135
|
+
return { ok: false, data: null, error: 'missing global mcp.json', source: globalPath };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* MCP servers from .cursor/mcp.json.
|
|
141
|
+
*/
|
|
142
|
+
mcpServers() {
|
|
143
|
+
const result = this.mcpConfig();
|
|
144
|
+
if (!result.ok || !result.data) return {};
|
|
145
|
+
return result.data.mcpServers || {};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Count total MCP tools across all servers.
|
|
150
|
+
* Cursor has a hard limit of ~40 tools.
|
|
151
|
+
*/
|
|
152
|
+
totalMcpTools() {
|
|
153
|
+
const servers = this.mcpServers();
|
|
154
|
+
let total = 0;
|
|
155
|
+
for (const server of Object.values(servers)) {
|
|
156
|
+
// Each server exposes tools; estimate ~5 per server if no explicit count
|
|
157
|
+
const toolCount = server.tools ? Object.keys(server.tools).length : 5;
|
|
158
|
+
total += toolCount;
|
|
159
|
+
}
|
|
160
|
+
return total;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── Environment config (.cursor/environment.json) ────────────────────
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* .cursor/environment.json parsed (background agent VM config).
|
|
167
|
+
*/
|
|
168
|
+
environmentJson() {
|
|
169
|
+
const content = this.fileContent('.cursor/environment.json');
|
|
170
|
+
if (!content) {
|
|
171
|
+
return { ok: false, data: null, error: 'missing .cursor/environment.json', source: '.cursor/environment.json' };
|
|
172
|
+
}
|
|
173
|
+
const parsed = tryParseJson(content);
|
|
174
|
+
return { ...parsed, source: '.cursor/environment.json' };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ─── Custom commands (.cursor/commands/*.md) ──────────────────────────
|
|
178
|
+
|
|
179
|
+
commandFiles() {
|
|
180
|
+
const commandsDir = path.join(this.dir, '.cursor', 'commands');
|
|
181
|
+
return listFiles(commandsDir, f => f.endsWith('.md'))
|
|
182
|
+
.map(f => `.cursor/commands/${f}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ─── Automations (.cursor/automations/) ───────────────────────────────
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Automation config files (.cursor/automations/*.yaml).
|
|
189
|
+
*/
|
|
190
|
+
automationsConfig() {
|
|
191
|
+
const dir = path.join(this.dir, '.cursor', 'automations');
|
|
192
|
+
const files = listFiles(dir, f => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
193
|
+
return files.map(f => {
|
|
194
|
+
const relPath = `.cursor/automations/${f}`;
|
|
195
|
+
const content = this.fileContent(relPath);
|
|
196
|
+
return { name: f, path: relPath, content };
|
|
197
|
+
}).filter(item => item.content);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── VS Code compat (.vscode/settings.json) ──────────────────────────
|
|
201
|
+
|
|
202
|
+
vscodeSettings() {
|
|
203
|
+
const content = this.fileContent('.vscode/settings.json');
|
|
204
|
+
if (!content) {
|
|
205
|
+
return { ok: false, data: null, error: 'missing .vscode/settings.json', source: '.vscode/settings.json' };
|
|
206
|
+
}
|
|
207
|
+
const parsed = tryParseJson(content);
|
|
208
|
+
return { ...parsed, source: '.vscode/settings.json' };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ─── Workflow files ───────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
workflowFiles() {
|
|
214
|
+
const dir = path.join(this.dir, '.github', 'workflows');
|
|
215
|
+
return listFiles(dir, f => f.endsWith('.yml') || f.endsWith('.yaml'))
|
|
216
|
+
.map(f => `.github/workflows/${f}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ─── Surface detection ────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Detect which Cursor surfaces are configured.
|
|
223
|
+
*/
|
|
224
|
+
detectSurfaces() {
|
|
225
|
+
const foreground = Boolean(
|
|
226
|
+
this.cursorRules().length > 0 ||
|
|
227
|
+
this.legacyCursorrules() ||
|
|
228
|
+
this.mcpConfig().ok
|
|
229
|
+
);
|
|
230
|
+
const background = Boolean(this.environmentJson().ok);
|
|
231
|
+
const automations = this.automationsConfig().length > 0;
|
|
232
|
+
|
|
233
|
+
return { foreground, background, automations };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ─── Static detection ─────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
static isCursorRepo(dir) {
|
|
239
|
+
try {
|
|
240
|
+
return fs.existsSync(path.join(dir, '.cursor')) ||
|
|
241
|
+
fs.existsSync(path.join(dir, '.cursorrules'));
|
|
242
|
+
} catch {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ─── Stack detection (reuse shared) ───────────────────────────────────
|
|
248
|
+
|
|
249
|
+
detectStacks(STACKS) {
|
|
250
|
+
return super.detectStacks(STACKS);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = {
|
|
255
|
+
CursorProjectContext,
|
|
256
|
+
};
|