@nerviq/cli 0.0.1 → 0.9.0-beta.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.
Files changed (148) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/LICENSE +21 -0
  3. package/README.md +447 -0
  4. package/bin/cli.js +749 -0
  5. package/content/case-study-template.md +91 -0
  6. package/content/claims-governance.md +37 -0
  7. package/content/claude-code/audit-repo/SKILL.md +20 -0
  8. package/content/claude-native-integration.md +60 -0
  9. package/content/devto-article.json +9 -0
  10. package/content/launch-posts.md +226 -0
  11. package/content/pilot-rollout-kit.md +30 -0
  12. package/content/release-checklist.md +31 -0
  13. package/package.json +53 -4
  14. package/src/activity.js +529 -0
  15. package/src/aider/activity.js +226 -0
  16. package/src/aider/config-parser.js +166 -0
  17. package/src/aider/context.js +158 -0
  18. package/src/aider/deep-review.js +316 -0
  19. package/src/aider/domain-packs.js +278 -0
  20. package/src/aider/freshness.js +168 -0
  21. package/src/aider/governance.js +253 -0
  22. package/src/aider/interactive.js +334 -0
  23. package/src/aider/mcp-packs.js +98 -0
  24. package/src/aider/patch.js +214 -0
  25. package/src/aider/plans.js +186 -0
  26. package/src/aider/premium.js +360 -0
  27. package/src/aider/setup.js +404 -0
  28. package/src/aider/techniques.js +1323 -0
  29. package/src/analyze.js +821 -0
  30. package/src/audit.js +1003 -0
  31. package/src/badge.js +13 -0
  32. package/src/benchmark.js +339 -0
  33. package/src/claudex-sync.json +7 -0
  34. package/src/codex/activity.js +324 -0
  35. package/src/codex/config-parser.js +183 -0
  36. package/src/codex/context.js +221 -0
  37. package/src/codex/deep-review.js +493 -0
  38. package/src/codex/domain-packs.js +372 -0
  39. package/src/codex/freshness.js +167 -0
  40. package/src/codex/governance.js +192 -0
  41. package/src/codex/interactive.js +618 -0
  42. package/src/codex/mcp-packs.js +660 -0
  43. package/src/codex/patch.js +209 -0
  44. package/src/codex/plans.js +251 -0
  45. package/src/codex/premium.js +614 -0
  46. package/src/codex/setup.js +603 -0
  47. package/src/codex/techniques.js +2649 -0
  48. package/src/context.js +272 -0
  49. package/src/copilot/activity.js +309 -0
  50. package/src/copilot/config-parser.js +226 -0
  51. package/src/copilot/context.js +197 -0
  52. package/src/copilot/deep-review.js +346 -0
  53. package/src/copilot/domain-packs.js +350 -0
  54. package/src/copilot/freshness.js +197 -0
  55. package/src/copilot/governance.js +222 -0
  56. package/src/copilot/interactive.js +406 -0
  57. package/src/copilot/mcp-packs.js +572 -0
  58. package/src/copilot/patch.js +238 -0
  59. package/src/copilot/plans.js +253 -0
  60. package/src/copilot/premium.js +450 -0
  61. package/src/copilot/setup.js +488 -0
  62. package/src/copilot/techniques.js +1822 -0
  63. package/src/cursor/activity.js +301 -0
  64. package/src/cursor/config-parser.js +265 -0
  65. package/src/cursor/context.js +236 -0
  66. package/src/cursor/deep-review.js +334 -0
  67. package/src/cursor/domain-packs.js +346 -0
  68. package/src/cursor/freshness.js +214 -0
  69. package/src/cursor/governance.js +229 -0
  70. package/src/cursor/interactive.js +391 -0
  71. package/src/cursor/mcp-packs.js +571 -0
  72. package/src/cursor/patch.js +243 -0
  73. package/src/cursor/plans.js +254 -0
  74. package/src/cursor/premium.js +468 -0
  75. package/src/cursor/setup.js +488 -0
  76. package/src/cursor/techniques.js +1786 -0
  77. package/src/deep-review.js +345 -0
  78. package/src/domain-packs.js +364 -0
  79. package/src/formatters/sarif.js +115 -0
  80. package/src/gemini/activity.js +402 -0
  81. package/src/gemini/config-parser.js +275 -0
  82. package/src/gemini/context.js +221 -0
  83. package/src/gemini/deep-review.js +559 -0
  84. package/src/gemini/domain-packs.js +371 -0
  85. package/src/gemini/freshness.js +204 -0
  86. package/src/gemini/governance.js +201 -0
  87. package/src/gemini/interactive.js +860 -0
  88. package/src/gemini/mcp-packs.js +658 -0
  89. package/src/gemini/patch.js +229 -0
  90. package/src/gemini/plans.js +269 -0
  91. package/src/gemini/premium.js +759 -0
  92. package/src/gemini/setup.js +692 -0
  93. package/src/gemini/techniques.js +2084 -0
  94. package/src/governance.js +523 -0
  95. package/src/harmony/advisor.js +383 -0
  96. package/src/harmony/audit.js +303 -0
  97. package/src/harmony/canon.js +444 -0
  98. package/src/harmony/cli.js +331 -0
  99. package/src/harmony/drift.js +401 -0
  100. package/src/harmony/governance.js +313 -0
  101. package/src/harmony/memory.js +238 -0
  102. package/src/harmony/sync.js +458 -0
  103. package/src/harmony/watch.js +336 -0
  104. package/src/index.js +256 -0
  105. package/src/insights.js +119 -0
  106. package/src/interactive.js +118 -0
  107. package/src/mcp-packs.js +597 -0
  108. package/src/opencode/activity.js +286 -0
  109. package/src/opencode/config-parser.js +109 -0
  110. package/src/opencode/context.js +247 -0
  111. package/src/opencode/deep-review.js +313 -0
  112. package/src/opencode/domain-packs.js +240 -0
  113. package/src/opencode/freshness.js +158 -0
  114. package/src/opencode/governance.js +159 -0
  115. package/src/opencode/interactive.js +392 -0
  116. package/src/opencode/mcp-packs.js +474 -0
  117. package/src/opencode/patch.js +184 -0
  118. package/src/opencode/plans.js +231 -0
  119. package/src/opencode/premium.js +413 -0
  120. package/src/opencode/setup.js +449 -0
  121. package/src/opencode/techniques.js +1713 -0
  122. package/src/plans.js +655 -0
  123. package/src/secret-patterns.js +30 -0
  124. package/src/setup.js +1274 -0
  125. package/src/synergy/adaptive.js +261 -0
  126. package/src/synergy/compensation.js +156 -0
  127. package/src/synergy/evidence.js +193 -0
  128. package/src/synergy/learning.js +184 -0
  129. package/src/synergy/patterns.js +227 -0
  130. package/src/synergy/ranking.js +83 -0
  131. package/src/synergy/report.js +163 -0
  132. package/src/synergy/routing.js +152 -0
  133. package/src/techniques.js +1354 -0
  134. package/src/watch.js +229 -0
  135. package/src/windsurf/activity.js +302 -0
  136. package/src/windsurf/config-parser.js +267 -0
  137. package/src/windsurf/context.js +249 -0
  138. package/src/windsurf/deep-review.js +337 -0
  139. package/src/windsurf/domain-packs.js +348 -0
  140. package/src/windsurf/freshness.js +215 -0
  141. package/src/windsurf/governance.js +231 -0
  142. package/src/windsurf/interactive.js +388 -0
  143. package/src/windsurf/mcp-packs.js +535 -0
  144. package/src/windsurf/patch.js +231 -0
  145. package/src/windsurf/plans.js +247 -0
  146. package/src/windsurf/premium.js +467 -0
  147. package/src/windsurf/setup.js +471 -0
  148. package/src/windsurf/techniques.js +1758 -0
@@ -0,0 +1,1786 @@
1
+ /**
2
+ * Cursor techniques module — CHECK CATALOG
3
+ *
4
+ * 82 checks across 16 categories:
5
+ * v0.1 (40): A. Rules(9), B. Config(7), C. Trust & Safety(9), D. Agent Mode(5), E. MCP(5), F. Instructions Quality(5)
6
+ * v0.5 (55): G. Background Agents(5), H. Automations(5), I. Enterprise(5)
7
+ * v1.0 (70): J. BugBot & Code Review(4), K. Cross-Surface(4), L. Quality Deep(7)
8
+ * CP-08 (82): M. Advisory(4), N. Pack(4), O. Repeat(3), P. Freshness(3)
9
+ *
10
+ * Each check: { id, name, check(ctx), impact, rating, category, fix, template, file(), line() }
11
+ */
12
+
13
+ const os = require('os');
14
+ const path = require('path');
15
+ const { CursorProjectContext } = require('./context');
16
+ const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
17
+ const { validateMdcFrontmatter, validateMcpEnvVars } = require('./config-parser');
18
+
19
+ // ─── Shared helpers ─────────────────────────────────────────────────────────
20
+
21
+ const FILLER_PATTERNS = [
22
+ /\bbe helpful\b/i,
23
+ /\bbe accurate\b/i,
24
+ /\bbe concise\b/i,
25
+ /\balways do your best\b/i,
26
+ /\bmaintain high quality\b/i,
27
+ /\bwrite clean code\b/i,
28
+ /\bfollow best practices\b/i,
29
+ ];
30
+
31
+ function countSections(markdown) {
32
+ return (markdown.match(/^##\s+/gm) || []).length;
33
+ }
34
+
35
+ function firstLineMatching(text, matcher) {
36
+ if (!text) return null;
37
+ const lines = text.split(/\r?\n/);
38
+ for (let index = 0; index < lines.length; index++) {
39
+ const line = lines[index];
40
+ if (typeof matcher === 'string' && line.includes(matcher)) return index + 1;
41
+ if (matcher instanceof RegExp && matcher.test(line)) {
42
+ matcher.lastIndex = 0;
43
+ return index + 1;
44
+ }
45
+ if (typeof matcher === 'function' && matcher(line, index + 1)) return index + 1;
46
+ }
47
+ return null;
48
+ }
49
+
50
+ function findFillerLine(content) {
51
+ return firstLineMatching(content, (line) => FILLER_PATTERNS.some((p) => p.test(line)));
52
+ }
53
+
54
+ function findSecretLine(content) {
55
+ const lines = content.split(/\r?\n/);
56
+ for (let index = 0; index < lines.length; index++) {
57
+ const matched = EMBEDDED_SECRET_PATTERNS.some((pattern) => {
58
+ pattern.lastIndex = 0;
59
+ return pattern.test(lines[index]);
60
+ });
61
+ if (matched) return index + 1;
62
+ }
63
+ return null;
64
+ }
65
+
66
+ function allRulesContent(ctx) {
67
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
68
+ return rules.map(r => r.body || '').join('\n');
69
+ }
70
+
71
+ function coreRulesContent(ctx) {
72
+ const always = ctx.alwaysApplyRules ? ctx.alwaysApplyRules() : [];
73
+ return always.map(r => r.body || '').join('\n');
74
+ }
75
+
76
+ function mcpJsonRaw(ctx) {
77
+ return ctx.fileContent('.cursor/mcp.json') || '';
78
+ }
79
+
80
+ function mcpJsonData(ctx) {
81
+ const result = ctx.mcpConfig();
82
+ return result && result.ok ? result.data : null;
83
+ }
84
+
85
+ function envJsonData(ctx) {
86
+ const result = ctx.environmentJson();
87
+ return result && result.ok ? result.data : null;
88
+ }
89
+
90
+ function docsBundle(ctx) {
91
+ const rules = allRulesContent(ctx) || '';
92
+ const readme = ctx.fileContent('README.md') || '';
93
+ const legacy = ctx.legacyCursorrules ? (ctx.legacyCursorrules() || '') : '';
94
+ return `${rules}\n${readme}\n${legacy}`;
95
+ }
96
+
97
+ function expectedVerificationCategories(ctx) {
98
+ const categories = new Set();
99
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
100
+ const scripts = pkg && pkg.scripts ? pkg.scripts : {};
101
+ if (scripts.test) categories.add('test');
102
+ if (scripts.lint) categories.add('lint');
103
+ if (scripts.build) categories.add('build');
104
+ if (ctx.fileContent('Cargo.toml')) { categories.add('test'); categories.add('build'); }
105
+ if (ctx.fileContent('go.mod')) { categories.add('test'); categories.add('build'); }
106
+ if (ctx.fileContent('pyproject.toml') || ctx.fileContent('requirements.txt')) categories.add('test');
107
+ if (ctx.fileContent('Makefile') || ctx.fileContent('justfile')) categories.add('build');
108
+ return [...categories];
109
+ }
110
+
111
+ function hasCommandMention(content, category) {
112
+ if (category === 'test') {
113
+ return /\bnpm test\b|\bnpm run test\b|\bpnpm test\b|\byarn test\b|\bvitest\b|\bjest\b|\bpytest\b|\bgo test\b|\bcargo test\b|\bmake test\b/i.test(content);
114
+ }
115
+ if (category === 'lint') {
116
+ return /\bnpm run lint\b|\bpnpm lint\b|\byarn lint\b|\beslint\b|\bprettier\b|\bruff\b|\bclippy\b|\bgolangci-lint\b|\bmake lint\b/i.test(content);
117
+ }
118
+ if (category === 'build') {
119
+ return /\bnpm run build\b|\bpnpm build\b|\byarn build\b|\btsc\b|\bvite build\b|\bnext build\b|\bcargo build\b|\bgo build\b|\bmake\b/i.test(content);
120
+ }
121
+ return false;
122
+ }
123
+
124
+ function hasArchitecture(content) {
125
+ return /```mermaid|flowchart\b|graph\s+(TD|LR|RL|BT)\b|##\s+Architecture\b|##\s+Project Map\b|##\s+Structure\b/i.test(content);
126
+ }
127
+
128
+ function repoLooksRegulated(ctx) {
129
+ const filenames = (ctx.files || []).join('\n');
130
+ const pkg = ctx.fileContent('package.json') || '';
131
+ const readme = ctx.fileContent('README.md') || '';
132
+ const combined = `${filenames}\n${pkg}\n${readme}`;
133
+ const strong = /\bhipaa\b|\bphi\b|\bpci\b|\bsoc2\b|\biso[- ]?27001\b|\bcompliance\b|\bhealth(?:care)?\b|\bmedical\b|\bbank(?:ing)?\b|\bpayments?\b|\bfintech\b/i;
134
+ if (strong.test(combined)) return true;
135
+ const weakMatches = combined.match(/\bgdpr\b|\bpii\b/gi) || [];
136
+ return weakMatches.length >= 2;
137
+ }
138
+
139
+ function automationContents(ctx) {
140
+ const configs = ctx.automationsConfig ? ctx.automationsConfig() : [];
141
+ return configs.map(c => c.content || '').join('\n');
142
+ }
143
+
144
+ function wordCount(text) {
145
+ if (!text) return 0;
146
+ return text.split(/\s+/).filter(Boolean).length;
147
+ }
148
+
149
+ // ─── CURSOR_TECHNIQUES ──────────────────────────────────────────────────────
150
+
151
+ const CURSOR_TECHNIQUES = {
152
+
153
+ // =============================================
154
+ // A. Rules (9 checks) — CU-A01..CU-A09
155
+ // =============================================
156
+
157
+ cursorRulesExist: {
158
+ id: 'CU-A01',
159
+ name: '.cursor/rules/ directory exists with .mdc files',
160
+ check: (ctx) => {
161
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
162
+ return rules.length > 0;
163
+ },
164
+ impact: 'critical',
165
+ rating: 5,
166
+ category: 'rules',
167
+ fix: 'Create .cursor/rules/ directory with at least one .mdc rule file.',
168
+ template: 'cursor-rules',
169
+ file: () => '.cursor/rules/',
170
+ line: () => null,
171
+ },
172
+
173
+ cursorNoLegacyCursorrules: {
174
+ id: 'CU-A02',
175
+ name: 'No .cursorrules without migration warning',
176
+ check: (ctx) => {
177
+ const hasLegacy = ctx.hasLegacyRules ? ctx.hasLegacyRules() : Boolean(ctx.fileContent('.cursorrules'));
178
+ return !hasLegacy;
179
+ },
180
+ impact: 'critical',
181
+ rating: 5,
182
+ category: 'rules',
183
+ fix: 'Migrate .cursorrules to .cursor/rules/*.mdc with proper frontmatter. AGENT MODE IGNORES .cursorrules completely!',
184
+ template: 'cursor-legacy-migration',
185
+ file: () => '.cursorrules',
186
+ line: () => 1,
187
+ },
188
+
189
+ cursorAlwaysApplyExists: {
190
+ id: 'CU-A03',
191
+ name: 'At least one rule has alwaysApply: true for agent mode',
192
+ check: (ctx) => {
193
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
194
+ if (rules.length === 0) return null;
195
+ return rules.some(r => r.ruleType === 'always');
196
+ },
197
+ impact: 'high',
198
+ rating: 5,
199
+ category: 'rules',
200
+ fix: 'Add alwaysApply: true to your core rule file so agents always see instructions.',
201
+ template: 'cursor-rules',
202
+ file: () => '.cursor/rules/',
203
+ line: () => null,
204
+ },
205
+
206
+ cursorRulesValidFrontmatter: {
207
+ id: 'CU-A04',
208
+ name: 'Rules have valid MDC frontmatter (YAML parseable)',
209
+ check: (ctx) => {
210
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
211
+ if (rules.length === 0) return null;
212
+ return rules.every(r => {
213
+ if (!r.frontmatter) return false;
214
+ const validation = validateMdcFrontmatter(r.frontmatter);
215
+ return validation.valid;
216
+ });
217
+ },
218
+ impact: 'high',
219
+ rating: 4,
220
+ category: 'rules',
221
+ fix: 'Fix YAML frontmatter in .mdc files. Use only: description, globs, alwaysApply fields.',
222
+ template: null,
223
+ file: () => '.cursor/rules/',
224
+ line: () => 1,
225
+ },
226
+
227
+ cursorNoGlobsDescriptionCombo: {
228
+ id: 'CU-A05',
229
+ name: 'No rules combine globs + description (creates ambiguity)',
230
+ check: (ctx) => {
231
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
232
+ if (rules.length === 0) return null;
233
+ return !rules.some(r => {
234
+ if (!r.frontmatter || r.frontmatter.alwaysApply === true) return false;
235
+ const hasGlobs = Array.isArray(r.frontmatter.globs)
236
+ ? r.frontmatter.globs.length > 0
237
+ : Boolean(r.frontmatter.globs);
238
+ const hasDesc = Boolean(r.frontmatter.description && String(r.frontmatter.description).trim());
239
+ return hasGlobs && hasDesc;
240
+ });
241
+ },
242
+ impact: 'medium',
243
+ rating: 3,
244
+ category: 'rules',
245
+ fix: 'Do not combine globs + description in the same rule. Use one or the other for clear rule activation.',
246
+ template: null,
247
+ file: () => '.cursor/rules/',
248
+ line: () => null,
249
+ },
250
+
251
+ cursorRulesUnder500Words: {
252
+ id: 'CU-A06',
253
+ name: 'Rules are under ~500 words each (longer = less reliably followed)',
254
+ check: (ctx) => {
255
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
256
+ if (rules.length === 0) return null;
257
+ return rules.every(r => wordCount(r.body) <= 500);
258
+ },
259
+ impact: 'medium',
260
+ rating: 3,
261
+ category: 'rules',
262
+ fix: 'Split long rules into focused, shorter files. Rules over ~500 words are less reliably followed.',
263
+ template: null,
264
+ file: () => '.cursor/rules/',
265
+ line: () => null,
266
+ },
267
+
268
+ cursorRulesNoFiller: {
269
+ id: 'CU-A07',
270
+ name: 'No generic filler instructions in rules',
271
+ check: (ctx) => {
272
+ const content = allRulesContent(ctx);
273
+ if (!content.trim()) return null;
274
+ return !FILLER_PATTERNS.some(p => p.test(content));
275
+ },
276
+ impact: 'low',
277
+ rating: 3,
278
+ category: 'rules',
279
+ fix: 'Replace generic filler like "be helpful" with concrete, repo-specific guidance.',
280
+ template: null,
281
+ file: () => '.cursor/rules/',
282
+ line: () => {
283
+ const content = allRulesContent({ cursorRules: () => [] });
284
+ return content ? findFillerLine(content) : null;
285
+ },
286
+ },
287
+
288
+ cursorRulesNoSecrets: {
289
+ id: 'CU-A08',
290
+ name: 'No secrets/API keys in rule files',
291
+ check: (ctx) => {
292
+ const rulesContent = allRulesContent(ctx);
293
+ const commandContent = (ctx.commandFiles ? ctx.commandFiles() : [])
294
+ .map(f => ctx.fileContent(f) || '').join('\n');
295
+ const combined = `${rulesContent}\n${commandContent}`;
296
+ if (!combined.trim()) return null;
297
+ return !containsEmbeddedSecret(combined);
298
+ },
299
+ impact: 'critical',
300
+ rating: 5,
301
+ category: 'rules',
302
+ fix: 'Remove API keys and secrets from rule and command files. Use environment variables instead.',
303
+ template: null,
304
+ file: () => '.cursor/rules/',
305
+ line: () => null,
306
+ },
307
+
308
+ cursorAgentRequestedDescriptions: {
309
+ id: 'CU-A09',
310
+ name: 'Agent Requested rules have precise descriptions',
311
+ check: (ctx) => {
312
+ const agentRules = ctx.agentRequestedRules ? ctx.agentRequestedRules() : [];
313
+ if (agentRules.length === 0) return null;
314
+ return agentRules.every(r => {
315
+ const desc = r.frontmatter && r.frontmatter.description;
316
+ return desc && String(desc).trim().length >= 15;
317
+ });
318
+ },
319
+ impact: 'medium',
320
+ rating: 3,
321
+ category: 'rules',
322
+ fix: 'Add clear, specific descriptions (15+ chars) to Agent Requested rules so AI can judge relevance.',
323
+ template: null,
324
+ file: () => '.cursor/rules/',
325
+ line: () => null,
326
+ },
327
+
328
+ // =============================================
329
+ // B. Config (7 checks) — CU-B01..CU-B07
330
+ // =============================================
331
+
332
+ cursorMcpJsonExists: {
333
+ id: 'CU-B01',
334
+ name: '.cursor/mcp.json exists if MCP is used',
335
+ check: (ctx) => {
336
+ const result = ctx.mcpConfig();
337
+ if (result.ok) return true;
338
+ // N/A if no MCP signals at all
339
+ const globalResult = ctx.globalMcpConfig ? ctx.globalMcpConfig() : { ok: false };
340
+ if (!globalResult.ok) return null;
341
+ return false;
342
+ },
343
+ impact: 'high',
344
+ rating: 4,
345
+ category: 'config',
346
+ fix: 'Create .cursor/mcp.json with project-level MCP server configuration.',
347
+ template: 'cursor-mcp',
348
+ file: () => '.cursor/mcp.json',
349
+ line: () => null,
350
+ },
351
+
352
+ cursorMcpToolLimit: {
353
+ id: 'CU-B02',
354
+ name: 'MCP total tools < 40 (silent drop limit)',
355
+ check: (ctx) => {
356
+ const servers = ctx.mcpServers ? ctx.mcpServers() : {};
357
+ const count = Object.keys(servers).length;
358
+ if (count === 0) return null;
359
+ const estimated = count * 5; // ~5 tools per server estimate
360
+ return estimated < 40;
361
+ },
362
+ impact: 'high',
363
+ rating: 4,
364
+ category: 'config',
365
+ fix: 'Reduce MCP servers to stay under ~40 total tools. Cursor silently drops tools beyond this limit.',
366
+ template: null,
367
+ file: () => '.cursor/mcp.json',
368
+ line: () => null,
369
+ },
370
+
371
+ cursorCommandsExist: {
372
+ id: 'CU-B03',
373
+ name: 'Custom commands exist in .cursor/commands/',
374
+ check: (ctx) => {
375
+ const files = ctx.commandFiles ? ctx.commandFiles() : [];
376
+ return files.length > 0;
377
+ },
378
+ impact: 'medium',
379
+ rating: 3,
380
+ category: 'config',
381
+ fix: 'Create .cursor/commands/*.md files for reusable slash command prompts.',
382
+ template: 'cursor-commands',
383
+ file: () => '.cursor/commands/',
384
+ line: () => null,
385
+ },
386
+
387
+ cursorEnvironmentJsonExists: {
388
+ id: 'CU-B04',
389
+ name: '.cursor/environment.json exists if background agents used',
390
+ check: (ctx) => {
391
+ const env = envJsonData(ctx);
392
+ if (env) return true;
393
+ // N/A if no background agent signals
394
+ const rules = allRulesContent(ctx);
395
+ const hasBackgroundSignals = /background agent|background.*agent/i.test(rules);
396
+ if (!hasBackgroundSignals) return null;
397
+ return false;
398
+ },
399
+ impact: 'high',
400
+ rating: 4,
401
+ category: 'config',
402
+ fix: 'Create .cursor/environment.json to configure background agent VM (baseImage, env, persistedDirectories).',
403
+ template: 'cursor-environment',
404
+ file: () => '.cursor/environment.json',
405
+ line: () => null,
406
+ },
407
+
408
+ cursorNoDeprecatedVscodeKeys: {
409
+ id: 'CU-B05',
410
+ name: 'No deprecated .vscode/settings.json Cursor keys',
411
+ check: (ctx) => {
412
+ const raw = ctx.fileContent('.vscode/settings.json') || '';
413
+ if (!raw) return null;
414
+ // Check for deprecated Cursor-specific keys
415
+ const deprecatedPatterns = [
416
+ /cursor\.general\.enableStickyScroll/,
417
+ /cursor\.aicompletion/i,
418
+ ];
419
+ return !deprecatedPatterns.some(p => p.test(raw));
420
+ },
421
+ impact: 'medium',
422
+ rating: 3,
423
+ category: 'config',
424
+ fix: 'Remove deprecated Cursor keys from .vscode/settings.json.',
425
+ template: null,
426
+ file: () => '.vscode/settings.json',
427
+ line: () => null,
428
+ },
429
+
430
+ cursorMcpValidJson: {
431
+ id: 'CU-B06',
432
+ name: 'MCP config is valid JSON',
433
+ check: (ctx) => {
434
+ const raw = mcpJsonRaw(ctx);
435
+ if (!raw) return null;
436
+ const result = ctx.mcpConfig();
437
+ return result && result.ok;
438
+ },
439
+ impact: 'critical',
440
+ rating: 5,
441
+ category: 'config',
442
+ fix: 'Fix malformed JSON in .cursor/mcp.json.',
443
+ template: null,
444
+ file: () => '.cursor/mcp.json',
445
+ line: (ctx) => {
446
+ const result = ctx.mcpConfig();
447
+ if (result && result.ok) return null;
448
+ if (result && result.error) {
449
+ const match = result.error.match(/position (\d+)/i);
450
+ if (match) {
451
+ const raw = mcpJsonRaw(ctx);
452
+ return raw ? raw.slice(0, Number(match[1])).split('\n').length : 1;
453
+ }
454
+ }
455
+ return 1;
456
+ },
457
+ },
458
+
459
+ cursorCommandsClear: {
460
+ id: 'CU-B07',
461
+ name: 'Custom command .md files have clear prompts',
462
+ check: (ctx) => {
463
+ const files = ctx.commandFiles ? ctx.commandFiles() : [];
464
+ if (files.length === 0) return null;
465
+ return files.every(f => {
466
+ const content = ctx.fileContent(f);
467
+ return content && content.trim().length >= 20;
468
+ });
469
+ },
470
+ impact: 'low',
471
+ rating: 2,
472
+ category: 'config',
473
+ fix: 'Ensure custom command files have clear, actionable prompt content (20+ chars).',
474
+ template: null,
475
+ file: () => '.cursor/commands/',
476
+ line: () => null,
477
+ },
478
+
479
+ // =============================================
480
+ // C. Trust & Safety (9 checks) — CU-C01..CU-C09
481
+ // =============================================
482
+
483
+ cursorPrivacyMode: {
484
+ id: 'CU-C01',
485
+ name: 'Privacy Mode enabled (or explicitly documented as off)',
486
+ check: (ctx) => {
487
+ // Cannot detect Privacy Mode from files (stored in SQLite state.vscdb)
488
+ // Check if rules/docs document the privacy mode status
489
+ const docs = docsBundle(ctx);
490
+ return /privacy mode|zero.?retention|data retention|privacy.*enabled/i.test(docs);
491
+ },
492
+ impact: 'high',
493
+ rating: 5,
494
+ category: 'trust',
495
+ fix: 'Enable Privacy Mode in Cursor Settings for zero data retention, or document why it is disabled.',
496
+ template: null,
497
+ file: () => '.cursor/rules/',
498
+ line: () => null,
499
+ },
500
+
501
+ cursorNoAutoRunUntrusted: {
502
+ id: 'CU-C02',
503
+ name: 'No auto-run terminal without review for untrusted repos',
504
+ check: (ctx) => {
505
+ // Check if rules mention auto-run/YOLO mode awareness
506
+ const docs = docsBundle(ctx);
507
+ if (!docs.trim()) return null;
508
+ // Pass if rules mention caution about auto-run
509
+ const mentionsAutoRun = /auto.?run|yolo|terminal.*approv|command.*confirm/i.test(docs);
510
+ // If no mention at all, it's not necessarily a fail — only fail if auto-run is explicitly enabled
511
+ return mentionsAutoRun || !(/auto.?run.*enable|yolo.*mode/i.test(docs));
512
+ },
513
+ impact: 'high',
514
+ rating: 4,
515
+ category: 'trust',
516
+ fix: 'Document terminal auto-run policy. Consider disabling for untrusted repos.',
517
+ template: null,
518
+ file: () => '.cursor/rules/',
519
+ line: () => null,
520
+ },
521
+
522
+ cursorAutomationTriggersScoped: {
523
+ id: 'CU-C03',
524
+ name: 'Automation triggers are intentional and scoped',
525
+ check: (ctx) => {
526
+ const configs = ctx.automationsConfig ? ctx.automationsConfig() : [];
527
+ if (configs.length === 0) return null;
528
+ // Check for overly broad triggers
529
+ const combined = configs.map(c => c.content).join('\n');
530
+ const hasBroadTrigger = /trigger:.*any|on.*any.*push|trigger:.*\*/i.test(combined);
531
+ return !hasBroadTrigger;
532
+ },
533
+ impact: 'high',
534
+ rating: 5,
535
+ category: 'trust',
536
+ fix: 'Scope automation triggers to specific branches and events. Avoid wildcard triggers.',
537
+ template: null,
538
+ file: () => '.cursor/automations/',
539
+ line: () => null,
540
+ },
541
+
542
+ cursorNoSecretsInEnvJson: {
543
+ id: 'CU-C04',
544
+ name: 'No secrets in environment.json or command files',
545
+ check: (ctx) => {
546
+ const envContent = ctx.fileContent('.cursor/environment.json') || '';
547
+ const cmdContent = (ctx.commandFiles ? ctx.commandFiles() : [])
548
+ .map(f => ctx.fileContent(f) || '').join('\n');
549
+ const combined = `${envContent}\n${cmdContent}`;
550
+ if (!combined.trim()) return null;
551
+ return !containsEmbeddedSecret(combined);
552
+ },
553
+ impact: 'critical',
554
+ rating: 5,
555
+ category: 'trust',
556
+ fix: 'Remove secrets from environment.json and command files. Use KMS/vault for background agent secrets.',
557
+ template: null,
558
+ file: () => '.cursor/environment.json',
559
+ line: () => null,
560
+ },
561
+
562
+ cursorMcpTrustedSources: {
563
+ id: 'CU-C05',
564
+ name: 'MCP servers from trusted sources (no known CVEs)',
565
+ check: (ctx) => {
566
+ const raw = mcpJsonRaw(ctx);
567
+ if (!raw) return null;
568
+ // Check for known vulnerable patterns
569
+ const knownVulnerable = /mcp-poisoned|cve-2025/i.test(raw);
570
+ // Check for non-npm/trusted sources
571
+ const hasUntrusted = /curl.*\|.*sh|wget.*\|.*sh/i.test(raw);
572
+ return !knownVulnerable && !hasUntrusted;
573
+ },
574
+ impact: 'high',
575
+ rating: 5,
576
+ category: 'trust',
577
+ fix: 'Verify MCP servers are from trusted sources. Check for CVE-2025-54136 (MCPoison) and CVE-2025-54135 (CurXecute).',
578
+ template: null,
579
+ file: () => '.cursor/mcp.json',
580
+ line: () => null,
581
+ },
582
+
583
+ cursorBackgroundAgentBranch: {
584
+ id: 'CU-C06',
585
+ name: 'Background agent creates branch (not commits to main)',
586
+ check: (ctx) => {
587
+ const env = envJsonData(ctx);
588
+ if (!env) return null;
589
+ // Background agents always create PRs by default — check for override
590
+ const rules = allRulesContent(ctx);
591
+ const hasPushToMain = /push.*main|commit.*main.*direct|direct.*push/i.test(rules);
592
+ return !hasPushToMain;
593
+ },
594
+ impact: 'high',
595
+ rating: 5,
596
+ category: 'trust',
597
+ fix: 'Ensure background agents create branches and PRs, never push directly to main.',
598
+ template: null,
599
+ file: () => '.cursor/rules/',
600
+ line: () => null,
601
+ },
602
+
603
+ cursorCodeReversionRisk: {
604
+ id: 'CU-C07',
605
+ name: 'Code reversion risk mitigated (format-on-save + agent review conflict)',
606
+ check: (ctx) => {
607
+ const vscodeRaw = ctx.fileContent('.vscode/settings.json') || '';
608
+ const hasFormatOnSave = /formatOnSave.*true/i.test(vscodeRaw);
609
+ if (!hasFormatOnSave) return null; // No risk if format-on-save is off
610
+ // Check if rules document the risk
611
+ const rules = allRulesContent(ctx);
612
+ return /code reversion|format.*save.*conflict|revert|format.*save.*warning/i.test(rules);
613
+ },
614
+ impact: 'critical',
615
+ rating: 5,
616
+ category: 'trust',
617
+ fix: 'Document code reversion risk: format-on-save + agent review tab + cloud sync can cause silent code loss.',
618
+ template: null,
619
+ file: () => '.cursor/rules/',
620
+ line: () => null,
621
+ },
622
+
623
+ cursorEnterprisePrivacyMode: {
624
+ id: 'CU-C08',
625
+ name: 'Enterprise: org-wide Privacy Mode enforced',
626
+ check: (ctx) => {
627
+ const docs = docsBundle(ctx);
628
+ if (!/enterprise|org.*policy/i.test(docs)) return null;
629
+ return /privacy mode.*enforc|org.*privacy|enterprise.*privacy/i.test(docs);
630
+ },
631
+ impact: 'medium',
632
+ rating: 4,
633
+ category: 'trust',
634
+ fix: 'Document whether org-wide Privacy Mode enforcement is active for Enterprise tier.',
635
+ template: null,
636
+ file: () => '.cursor/rules/',
637
+ line: () => null,
638
+ },
639
+
640
+ cursorNoWildcardAutomation: {
641
+ id: 'CU-C09',
642
+ name: 'No wildcard automation triggers (e.g., "on any push")',
643
+ check: (ctx) => {
644
+ const configs = ctx.automationsConfig ? ctx.automationsConfig() : [];
645
+ if (configs.length === 0) return null;
646
+ const combined = configs.map(c => c.content).join('\n');
647
+ const hasWildcard = /branches:.*\*|on:.*\*|trigger:.*all/i.test(combined);
648
+ return !hasWildcard;
649
+ },
650
+ impact: 'high',
651
+ rating: 5,
652
+ category: 'trust',
653
+ fix: 'Replace wildcard automation triggers with specific branch/event patterns.',
654
+ template: null,
655
+ file: () => '.cursor/automations/',
656
+ line: () => null,
657
+ },
658
+
659
+ // =============================================
660
+ // D. Agent Mode (5 checks) — CU-D01..CU-D05
661
+ // =============================================
662
+
663
+ cursorRulesReachAgent: {
664
+ id: 'CU-D01',
665
+ name: 'Rules properly reach agent (not just .cursorrules)',
666
+ check: (ctx) => {
667
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
668
+ const hasLegacy = ctx.hasLegacyRules ? ctx.hasLegacyRules() : false;
669
+ if (rules.length === 0 && !hasLegacy) return null;
670
+ // Must have .mdc rules, not just legacy
671
+ return rules.length > 0;
672
+ },
673
+ impact: 'critical',
674
+ rating: 5,
675
+ category: 'agent-mode',
676
+ fix: 'Create .cursor/rules/*.mdc files with proper frontmatter. .cursorrules is ignored by agent mode!',
677
+ template: 'cursor-rules',
678
+ file: () => '.cursor/rules/',
679
+ line: () => null,
680
+ },
681
+
682
+ cursorCodebaseIndexed: {
683
+ id: 'CU-D02',
684
+ name: 'Agent has appropriate context scope (@Codebase indexed)',
685
+ check: (ctx) => {
686
+ const rules = allRulesContent(ctx);
687
+ if (!rules.trim()) return null;
688
+ return /@Codebase|@codebase|semantic search|codebase.*index/i.test(rules);
689
+ },
690
+ impact: 'medium',
691
+ rating: 3,
692
+ category: 'agent-mode',
693
+ fix: 'Mention @Codebase in rules to guide agents to use project-wide semantic search.',
694
+ template: null,
695
+ file: () => '.cursor/rules/',
696
+ line: () => null,
697
+ },
698
+
699
+ cursorMultiAgentWorktree: {
700
+ id: 'CU-D03',
701
+ name: 'Multi-agent workflows use Git worktree isolation',
702
+ check: (ctx) => {
703
+ const rules = allRulesContent(ctx);
704
+ if (!rules.trim()) return null;
705
+ // Only relevant if multi-agent mentioned
706
+ if (!/multi.?agent|parallel agent|agent.*window/i.test(rules)) return null;
707
+ return /worktree|git worktree|isolat/i.test(rules);
708
+ },
709
+ impact: 'medium',
710
+ rating: 3,
711
+ category: 'agent-mode',
712
+ fix: 'For multi-agent workflows (Agents Window), use Git worktree isolation to prevent conflicts.',
713
+ template: null,
714
+ file: () => '.cursor/rules/',
715
+ line: () => null,
716
+ },
717
+
718
+ cursorSessionLengthAwareness: {
719
+ id: 'CU-D04',
720
+ name: 'Agent session length awareness (<2h recommended)',
721
+ check: (ctx) => {
722
+ const rules = allRulesContent(ctx);
723
+ if (!rules.trim()) return null;
724
+ return /session.*length|session.*limit|2.*hour|context.*drift|long.*session/i.test(rules);
725
+ },
726
+ impact: 'low',
727
+ rating: 2,
728
+ category: 'agent-mode',
729
+ fix: 'Document session length recommendations. Sessions >2h may lose agent context.',
730
+ template: null,
731
+ file: () => '.cursor/rules/',
732
+ line: () => null,
733
+ },
734
+
735
+ cursorDocsConfigured: {
736
+ id: 'CU-D05',
737
+ name: '@Docs configured for project key libraries',
738
+ check: (ctx) => {
739
+ const rules = allRulesContent(ctx);
740
+ if (!rules.trim()) return null;
741
+ return /@Docs|@docs|documentation.*index|library.*doc/i.test(rules);
742
+ },
743
+ impact: 'medium',
744
+ rating: 3,
745
+ category: 'agent-mode',
746
+ fix: 'Configure @Docs for key project libraries to give agents access to current documentation.',
747
+ template: null,
748
+ file: () => '.cursor/rules/',
749
+ line: () => null,
750
+ },
751
+
752
+ // =============================================
753
+ // E. MCP (5 checks) — CU-E01..CU-E05
754
+ // =============================================
755
+
756
+ cursorMcpPerSurface: {
757
+ id: 'CU-E01',
758
+ name: 'MCP servers configured per surface (project + global)',
759
+ check: (ctx) => {
760
+ const project = ctx.mcpConfig();
761
+ if (!project.ok) return null;
762
+ return true;
763
+ },
764
+ impact: 'medium',
765
+ rating: 3,
766
+ category: 'mcp',
767
+ fix: 'Configure project-level MCP in .cursor/mcp.json. Global config at ~/.cursor/mcp.json.',
768
+ template: 'cursor-mcp',
769
+ file: () => '.cursor/mcp.json',
770
+ line: () => null,
771
+ },
772
+
773
+ cursorMcpProjectOverride: {
774
+ id: 'CU-E02',
775
+ name: 'Project mcp.json overrides global correctly',
776
+ check: (ctx) => {
777
+ const project = ctx.mcpConfig();
778
+ const global = ctx.globalMcpConfig ? ctx.globalMcpConfig() : { ok: false };
779
+ if (!project.ok || !global.ok) return null;
780
+ // Just verify both parse correctly — Cursor handles override automatically
781
+ return true;
782
+ },
783
+ impact: 'medium',
784
+ rating: 3,
785
+ category: 'mcp',
786
+ fix: 'Ensure project .cursor/mcp.json and global ~/.cursor/mcp.json are both valid JSON.',
787
+ template: null,
788
+ file: () => '.cursor/mcp.json',
789
+ line: () => null,
790
+ },
791
+
792
+ cursorMcpEnvVarSyntax: {
793
+ id: 'CU-E03',
794
+ name: 'MCP env vars use ${env:VAR} syntax (not hardcoded)',
795
+ check: (ctx) => {
796
+ const raw = mcpJsonRaw(ctx);
797
+ if (!raw) return null;
798
+ const data = mcpJsonData(ctx);
799
+ if (!data) return null;
800
+ const validation = validateMcpEnvVars(data);
801
+ return validation.valid;
802
+ },
803
+ impact: 'high',
804
+ rating: 5,
805
+ category: 'mcp',
806
+ fix: 'Use ${env:VAR_NAME} syntax for MCP environment variables instead of hardcoded values.',
807
+ template: null,
808
+ file: () => '.cursor/mcp.json',
809
+ line: () => null,
810
+ },
811
+
812
+ cursorMcpCurrentVersion: {
813
+ id: 'CU-E04',
814
+ name: 'MCP marketplace servers are current version',
815
+ check: (ctx) => {
816
+ const raw = mcpJsonRaw(ctx);
817
+ if (!raw) return null;
818
+ // Check for @latest or explicit version
819
+ const hasStale = /\b\d+\.\d+\.\d+\b/.test(raw) && !/@latest\b/.test(raw);
820
+ return !hasStale;
821
+ },
822
+ impact: 'low',
823
+ rating: 2,
824
+ category: 'mcp',
825
+ fix: 'Use @latest for MCP packages or regularly update pinned versions.',
826
+ template: null,
827
+ file: () => '.cursor/mcp.json',
828
+ line: () => null,
829
+ },
830
+
831
+ cursorMcpBackgroundAccess: {
832
+ id: 'CU-E05',
833
+ name: 'Background agent -p mode has MCP access (known bug)',
834
+ check: (ctx) => {
835
+ const env = envJsonData(ctx);
836
+ const mcp = mcpJsonData(ctx);
837
+ if (!env || !mcp) return null;
838
+ // Document awareness of the known bug
839
+ const rules = allRulesContent(ctx);
840
+ return /background.*mcp|mcp.*background|-p.*mode|programmatic.*mode/i.test(rules);
841
+ },
842
+ impact: 'medium',
843
+ rating: 3,
844
+ category: 'mcp',
845
+ fix: 'Document MCP access limitations in background agent -p mode (known bug).',
846
+ template: null,
847
+ file: () => '.cursor/rules/',
848
+ line: () => null,
849
+ },
850
+
851
+ // =============================================
852
+ // F. Instructions Quality (5 checks) — CU-F01..CU-F05
853
+ // =============================================
854
+
855
+ cursorRulesIncludeCommands: {
856
+ id: 'CU-F01',
857
+ name: 'Rules include build/test/lint commands',
858
+ check: (ctx) => {
859
+ const content = coreRulesContent(ctx) || allRulesContent(ctx);
860
+ if (!content.trim()) return null;
861
+ const expected = expectedVerificationCategories(ctx);
862
+ if (expected.length === 0) return /\bverify\b|\btest\b|\blint\b|\bbuild\b/i.test(content);
863
+ return expected.every(cat => hasCommandMention(content, cat));
864
+ },
865
+ impact: 'high',
866
+ rating: 5,
867
+ category: 'instructions-quality',
868
+ fix: 'Add actual build/test/lint commands to your core rules so agents can verify their changes.',
869
+ template: 'cursor-rules',
870
+ file: () => '.cursor/rules/',
871
+ line: () => null,
872
+ },
873
+
874
+ cursorRulesArchitecture: {
875
+ id: 'CU-F02',
876
+ name: 'Rules include architecture section or Mermaid diagram',
877
+ check: (ctx) => {
878
+ const content = allRulesContent(ctx);
879
+ if (!content.trim()) return null;
880
+ return hasArchitecture(content);
881
+ },
882
+ impact: 'medium',
883
+ rating: 4,
884
+ category: 'instructions-quality',
885
+ fix: 'Add an architecture section or Mermaid diagram to your core rule to orient agents.',
886
+ template: 'cursor-rules',
887
+ file: () => '.cursor/rules/',
888
+ line: () => null,
889
+ },
890
+
891
+ cursorRulesVerification: {
892
+ id: 'CU-F03',
893
+ name: 'Rules mention verification/testing expectations',
894
+ check: (ctx) => {
895
+ const content = allRulesContent(ctx);
896
+ if (!content.trim()) return null;
897
+ return /\bverif|\btest.*before|\bbefore.*commit|\brun test|\bensure test/i.test(content);
898
+ },
899
+ impact: 'high',
900
+ rating: 5,
901
+ category: 'instructions-quality',
902
+ fix: 'Add verification expectations: agents should run tests before declaring a task complete.',
903
+ template: 'cursor-rules',
904
+ file: () => '.cursor/rules/',
905
+ line: () => null,
906
+ },
907
+
908
+ cursorRulesNoContradictions: {
909
+ id: 'CU-F04',
910
+ name: 'No contradictions between rules',
911
+ check: (ctx) => {
912
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
913
+ if (rules.length < 2) return null;
914
+ // Simple heuristic: check for opposing instructions
915
+ const combined = rules.map(r => r.body || '').join('\n');
916
+ const hasContradiction = /\bnever use.*\balways use|\balways.*\bnever/i.test(combined);
917
+ return !hasContradiction;
918
+ },
919
+ impact: 'medium',
920
+ rating: 3,
921
+ category: 'instructions-quality',
922
+ fix: 'Review rules for contradictions. Cursor concatenates all matching rules without conflict resolution.',
923
+ template: null,
924
+ file: () => '.cursor/rules/',
925
+ line: () => null,
926
+ },
927
+
928
+ cursorRulesProjectSpecific: {
929
+ id: 'CU-F05',
930
+ name: 'Rules reference project-specific patterns (not generic)',
931
+ check: (ctx) => {
932
+ const content = allRulesContent(ctx);
933
+ if (!content.trim()) return null;
934
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
935
+ const projectName = (pkg && pkg.name) || path.basename(ctx.dir);
936
+ // Check for project-specific references
937
+ const hasSpecific = content.includes(projectName) ||
938
+ /src\/|app\/|api\/|routes\/|services\/|components\/|lib\/|cmd\//i.test(content);
939
+ return hasSpecific;
940
+ },
941
+ impact: 'medium',
942
+ rating: 3,
943
+ category: 'instructions-quality',
944
+ fix: 'Reference actual project directories and patterns in rules instead of generic instructions.',
945
+ template: null,
946
+ file: () => '.cursor/rules/',
947
+ line: () => null,
948
+ },
949
+
950
+ // =============================================
951
+ // G. Background Agents (5 checks) — CU-G01..CU-G05
952
+ // =============================================
953
+
954
+ cursorEnvBaseImage: {
955
+ id: 'CU-G01',
956
+ name: 'environment.json has appropriate baseImage',
957
+ check: (ctx) => {
958
+ const env = envJsonData(ctx);
959
+ if (!env) return null;
960
+ return Boolean(env.baseImage);
961
+ },
962
+ impact: 'high',
963
+ rating: 4,
964
+ category: 'background-agents',
965
+ fix: 'Set baseImage in .cursor/environment.json (e.g., "node:20", "python:3.12").',
966
+ template: 'cursor-environment',
967
+ file: () => '.cursor/environment.json',
968
+ line: () => 1,
969
+ },
970
+
971
+ cursorEnvPersistedDirs: {
972
+ id: 'CU-G02',
973
+ name: 'persistedDirectories includes node_modules/venv',
974
+ check: (ctx) => {
975
+ const env = envJsonData(ctx);
976
+ if (!env) return null;
977
+ const persisted = env.persistedDirectories || [];
978
+ if (persisted.length === 0) return false;
979
+ return true;
980
+ },
981
+ impact: 'medium',
982
+ rating: 3,
983
+ category: 'background-agents',
984
+ fix: 'Add persistedDirectories to environment.json to cache dependencies between runs.',
985
+ template: 'cursor-environment',
986
+ file: () => '.cursor/environment.json',
987
+ line: () => null,
988
+ },
989
+
990
+ cursorEnvProcesses: {
991
+ id: 'CU-G03',
992
+ name: 'Processes defined for dev servers if needed',
993
+ check: (ctx) => {
994
+ const env = envJsonData(ctx);
995
+ if (!env) return null;
996
+ // Only relevant if project has dev server
997
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
998
+ const hasDevServer = pkg && pkg.scripts && (pkg.scripts.dev || pkg.scripts.start);
999
+ if (!hasDevServer) return null;
1000
+ return Boolean(env.processes && Object.keys(env.processes).length > 0);
1001
+ },
1002
+ impact: 'medium',
1003
+ rating: 3,
1004
+ category: 'background-agents',
1005
+ fix: 'Define processes in environment.json for dev servers that background agents need.',
1006
+ template: 'cursor-environment',
1007
+ file: () => '.cursor/environment.json',
1008
+ line: () => null,
1009
+ },
1010
+
1011
+ cursorEnvSecretsKms: {
1012
+ id: 'CU-G04',
1013
+ name: 'Secrets use KMS (not plaintext env vars)',
1014
+ check: (ctx) => {
1015
+ const envContent = ctx.fileContent('.cursor/environment.json') || '';
1016
+ if (!envContent.trim()) return null;
1017
+ // Check for hardcoded secret-looking values
1018
+ return !containsEmbeddedSecret(envContent);
1019
+ },
1020
+ impact: 'critical',
1021
+ rating: 5,
1022
+ category: 'background-agents',
1023
+ fix: 'Use KMS-encrypted secrets vault for background agents. Never put secrets in environment.json.',
1024
+ template: null,
1025
+ file: () => '.cursor/environment.json',
1026
+ line: () => null,
1027
+ },
1028
+
1029
+ cursorEnvCreatesPr: {
1030
+ id: 'CU-G05',
1031
+ name: 'Agent output creates PR (not direct push)',
1032
+ check: (ctx) => {
1033
+ const env = envJsonData(ctx);
1034
+ if (!env) return null;
1035
+ // Background agents create PRs by default, check for override
1036
+ const rules = allRulesContent(ctx);
1037
+ const hasBadPattern = /push.*directly|commit.*main|--force push/i.test(rules);
1038
+ return !hasBadPattern;
1039
+ },
1040
+ impact: 'high',
1041
+ rating: 5,
1042
+ category: 'background-agents',
1043
+ fix: 'Ensure background agents create PRs for review, never push directly to main.',
1044
+ template: null,
1045
+ file: () => '.cursor/rules/',
1046
+ line: () => null,
1047
+ },
1048
+
1049
+ // =============================================
1050
+ // H. Automations (5 checks) — CU-H01..CU-H05
1051
+ // =============================================
1052
+
1053
+ cursorAutomationDocumented: {
1054
+ id: 'CU-H01',
1055
+ name: 'Automation triggers are documented',
1056
+ check: (ctx) => {
1057
+ const configs = ctx.automationsConfig ? ctx.automationsConfig() : [];
1058
+ if (configs.length === 0) return null;
1059
+ // Check that each automation has clear name/description
1060
+ return configs.every(c => {
1061
+ const content = c.content || '';
1062
+ return /name:|description:|instructions:/i.test(content);
1063
+ });
1064
+ },
1065
+ impact: 'high',
1066
+ rating: 4,
1067
+ category: 'automations',
1068
+ fix: 'Document each automation with name, description, and clear instructions.',
1069
+ template: null,
1070
+ file: () => '.cursor/automations/',
1071
+ line: () => null,
1072
+ },
1073
+
1074
+ cursorAutomationNoBroadTrigger: {
1075
+ id: 'CU-H02',
1076
+ name: 'No overly broad triggers (e.g., "any push to any branch")',
1077
+ check: (ctx) => {
1078
+ const combined = automationContents(ctx);
1079
+ if (!combined.trim()) return null;
1080
+ const hasBroad = /branches:.*\*|on:.*\*.*push|any.*branch|all.*events/i.test(combined);
1081
+ return !hasBroad;
1082
+ },
1083
+ impact: 'high',
1084
+ rating: 5,
1085
+ category: 'automations',
1086
+ fix: 'Scope automation triggers to specific branches. Avoid wildcards that trigger on every push.',
1087
+ template: null,
1088
+ file: () => '.cursor/automations/',
1089
+ line: () => null,
1090
+ },
1091
+
1092
+ cursorAutomationErrorHandling: {
1093
+ id: 'CU-H03',
1094
+ name: 'Automation has error handling / failure notification',
1095
+ check: (ctx) => {
1096
+ const combined = automationContents(ctx);
1097
+ if (!combined.trim()) return null;
1098
+ return /error|fail|notification|alert|on_failure|on_error/i.test(combined);
1099
+ },
1100
+ impact: 'medium',
1101
+ rating: 3,
1102
+ category: 'automations',
1103
+ fix: 'Add error handling and failure notification to automations.',
1104
+ template: null,
1105
+ file: () => '.cursor/automations/',
1106
+ line: () => null,
1107
+ },
1108
+
1109
+ cursorAutomationRateLimits: {
1110
+ id: 'CU-H04',
1111
+ name: 'Rate limits considered (hundreds/hour possible)',
1112
+ check: (ctx) => {
1113
+ const combined = automationContents(ctx);
1114
+ if (!combined.trim()) return null;
1115
+ return /debounce|rate.?limit|throttle|cool.?down|max.*per/i.test(combined);
1116
+ },
1117
+ impact: 'medium',
1118
+ rating: 3,
1119
+ category: 'automations',
1120
+ fix: 'Add debounce_ms or rate limiting to automations that could fire frequently.',
1121
+ template: null,
1122
+ file: () => '.cursor/automations/',
1123
+ line: () => null,
1124
+ },
1125
+
1126
+ cursorAutomationScopedPerms: {
1127
+ id: 'CU-H05',
1128
+ name: 'Automation agents have scoped permissions',
1129
+ check: (ctx) => {
1130
+ const combined = automationContents(ctx);
1131
+ if (!combined.trim()) return null;
1132
+ return /sandbox|permission|scope|restrict|limited/i.test(combined);
1133
+ },
1134
+ impact: 'high',
1135
+ rating: 4,
1136
+ category: 'automations',
1137
+ fix: 'Define scoped permissions and sandbox config for each automation agent.',
1138
+ template: null,
1139
+ file: () => '.cursor/automations/',
1140
+ line: () => null,
1141
+ },
1142
+
1143
+ // =============================================
1144
+ // I. Enterprise (5 checks) — CU-I01..CU-I05
1145
+ // =============================================
1146
+
1147
+ cursorEnterpriseSso: {
1148
+ id: 'CU-I01',
1149
+ name: 'SSO configured (SAML/OIDC) if Enterprise',
1150
+ check: (ctx) => {
1151
+ const docs = docsBundle(ctx);
1152
+ if (!/enterprise/i.test(docs)) return null;
1153
+ return /sso|saml|oidc|single sign/i.test(docs);
1154
+ },
1155
+ impact: 'medium',
1156
+ rating: 3,
1157
+ category: 'enterprise',
1158
+ fix: 'Configure SSO (SAML/OIDC) for Enterprise tier deployments.',
1159
+ template: null,
1160
+ file: () => '.cursor/rules/',
1161
+ line: () => null,
1162
+ },
1163
+
1164
+ cursorEnterpriseScim: {
1165
+ id: 'CU-I02',
1166
+ name: 'SCIM 2.0 provisioning active',
1167
+ check: (ctx) => {
1168
+ const docs = docsBundle(ctx);
1169
+ if (!/enterprise/i.test(docs)) return null;
1170
+ return /scim|provisioning|directory sync/i.test(docs);
1171
+ },
1172
+ impact: 'low',
1173
+ rating: 2,
1174
+ category: 'enterprise',
1175
+ fix: 'Enable SCIM 2.0 provisioning for automated user management.',
1176
+ template: null,
1177
+ file: () => '.cursor/rules/',
1178
+ line: () => null,
1179
+ },
1180
+
1181
+ cursorEnterpriseAuditLogs: {
1182
+ id: 'CU-I03',
1183
+ name: 'Audit logs enabled',
1184
+ check: (ctx) => {
1185
+ const docs = docsBundle(ctx);
1186
+ if (!/enterprise/i.test(docs)) return null;
1187
+ return /audit log|audit trail|tracking/i.test(docs);
1188
+ },
1189
+ impact: 'medium',
1190
+ rating: 3,
1191
+ category: 'enterprise',
1192
+ fix: 'Enable audit logs for Enterprise tier to track AI code generation and tool usage.',
1193
+ template: null,
1194
+ file: () => '.cursor/rules/',
1195
+ line: () => null,
1196
+ },
1197
+
1198
+ cursorEnterpriseMcpAllowlist: {
1199
+ id: 'CU-I04',
1200
+ name: 'MCP server allowlist maintained',
1201
+ check: (ctx) => {
1202
+ const docs = docsBundle(ctx);
1203
+ if (!/enterprise/i.test(docs)) return null;
1204
+ return /mcp.*allowlist|allowlist.*mcp|approved.*server|server.*approval/i.test(docs);
1205
+ },
1206
+ impact: 'high',
1207
+ rating: 4,
1208
+ category: 'enterprise',
1209
+ fix: 'Maintain an MCP server allowlist for Enterprise deployments to control tool access.',
1210
+ template: null,
1211
+ file: () => '.cursor/rules/',
1212
+ line: () => null,
1213
+ },
1214
+
1215
+ cursorEnterpriseModelPolicy: {
1216
+ id: 'CU-I05',
1217
+ name: 'Model access policy matches team needs',
1218
+ check: (ctx) => {
1219
+ const docs = docsBundle(ctx);
1220
+ if (!/enterprise/i.test(docs)) return null;
1221
+ return /model.*policy|model.*access|allowed.*model|model.*restriction/i.test(docs);
1222
+ },
1223
+ impact: 'medium',
1224
+ rating: 3,
1225
+ category: 'enterprise',
1226
+ fix: 'Define model access policy for Enterprise — which models are available to team members.',
1227
+ template: null,
1228
+ file: () => '.cursor/rules/',
1229
+ line: () => null,
1230
+ },
1231
+
1232
+ // =============================================
1233
+ // J. BugBot & Code Review (4 checks) — CU-J01..CU-J04
1234
+ // =============================================
1235
+
1236
+ cursorBugbotEnabled: {
1237
+ id: 'CU-J01',
1238
+ name: 'BugBot enabled for critical repos',
1239
+ check: (ctx) => {
1240
+ const docs = docsBundle(ctx);
1241
+ if (!docs.trim()) return null;
1242
+ return /bugbot|bug.?bot|automated.*pr.*review/i.test(docs);
1243
+ },
1244
+ impact: 'medium',
1245
+ rating: 3,
1246
+ category: 'bugbot',
1247
+ fix: 'Enable BugBot for automated PR code review on critical repos.',
1248
+ template: null,
1249
+ file: () => '.cursor/rules/',
1250
+ line: () => null,
1251
+ },
1252
+
1253
+ cursorBugbotAutofix: {
1254
+ id: 'CU-J02',
1255
+ name: 'BugBot autofix configured appropriately',
1256
+ check: (ctx) => {
1257
+ const docs = docsBundle(ctx);
1258
+ if (!/bugbot|bug.?bot/i.test(docs)) return null;
1259
+ return /autofix|auto.?fix|fix.*mode|resolution/i.test(docs);
1260
+ },
1261
+ impact: 'medium',
1262
+ rating: 3,
1263
+ category: 'bugbot',
1264
+ fix: 'Configure BugBot autofix settings — decide which issue types should be auto-fixed vs flagged.',
1265
+ template: null,
1266
+ file: () => '.cursor/rules/',
1267
+ line: () => null,
1268
+ },
1269
+
1270
+ cursorReviewInstructionsLength: {
1271
+ id: 'CU-J03',
1272
+ name: 'Code review instructions within effective length',
1273
+ check: (ctx) => {
1274
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
1275
+ const reviewRules = rules.filter(r =>
1276
+ /review|code.*review/i.test(r.name || '') ||
1277
+ (r.frontmatter && r.frontmatter.description && /review/i.test(r.frontmatter.description))
1278
+ );
1279
+ if (reviewRules.length === 0) return null;
1280
+ return reviewRules.every(r => wordCount(r.body) <= 400);
1281
+ },
1282
+ impact: 'medium',
1283
+ rating: 3,
1284
+ category: 'bugbot',
1285
+ fix: 'Keep code review instruction rules under ~400 words for reliable agent adherence.',
1286
+ template: null,
1287
+ file: () => '.cursor/rules/',
1288
+ line: () => null,
1289
+ },
1290
+
1291
+ cursorReviewNoConflict: {
1292
+ id: 'CU-J04',
1293
+ name: 'Review automation does not conflict with human review',
1294
+ check: (ctx) => {
1295
+ const configs = ctx.automationsConfig ? ctx.automationsConfig() : [];
1296
+ if (configs.length === 0) return null;
1297
+ const combined = configs.map(c => c.content).join('\n');
1298
+ const hasReviewAuto = /auto.*review|review.*auto|merge.*auto/i.test(combined);
1299
+ if (!hasReviewAuto) return null;
1300
+ // If auto-review exists, check it doesn't auto-merge
1301
+ return !/auto.*merge|merge.*without.*review/i.test(combined);
1302
+ },
1303
+ impact: 'low',
1304
+ rating: 2,
1305
+ category: 'bugbot',
1306
+ fix: 'Ensure automated review does not bypass human review requirements.',
1307
+ template: null,
1308
+ file: () => '.cursor/automations/',
1309
+ line: () => null,
1310
+ },
1311
+
1312
+ // =============================================
1313
+ // K. Cross-Surface Consistency (4 checks) — CU-K01..CU-K04
1314
+ // =============================================
1315
+
1316
+ cursorRulesConsistentSurfaces: {
1317
+ id: 'CU-K01',
1318
+ name: 'Rules consistent between foreground and background agents',
1319
+ check: (ctx) => {
1320
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
1321
+ const env = envJsonData(ctx);
1322
+ if (rules.length === 0 || !env) return null;
1323
+ // If we have both surfaces, rules should exist for both
1324
+ return rules.length > 0;
1325
+ },
1326
+ impact: 'high',
1327
+ rating: 4,
1328
+ category: 'cross-surface',
1329
+ fix: 'Ensure .cursor/rules/ are accessible to both foreground and background agents.',
1330
+ template: null,
1331
+ file: () => '.cursor/rules/',
1332
+ line: () => null,
1333
+ },
1334
+
1335
+ cursorMcpConsistentSurfaces: {
1336
+ id: 'CU-K02',
1337
+ name: 'MCP config consistent across surfaces',
1338
+ check: (ctx) => {
1339
+ const project = ctx.mcpConfig();
1340
+ if (!project.ok) return null;
1341
+ // MCP config applies to foreground; background has separate access
1342
+ return true;
1343
+ },
1344
+ impact: 'medium',
1345
+ rating: 3,
1346
+ category: 'cross-surface',
1347
+ fix: 'Document which MCP servers are available to foreground vs background agents.',
1348
+ template: null,
1349
+ file: () => '.cursor/mcp.json',
1350
+ line: () => null,
1351
+ },
1352
+
1353
+ cursorAutomationRulesConsistent: {
1354
+ id: 'CU-K03',
1355
+ name: 'Automation agents have same rules as interactive agents',
1356
+ check: (ctx) => {
1357
+ const configs = ctx.automationsConfig ? ctx.automationsConfig() : [];
1358
+ if (configs.length === 0) return null;
1359
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
1360
+ return rules.length > 0;
1361
+ },
1362
+ impact: 'high',
1363
+ rating: 4,
1364
+ category: 'cross-surface',
1365
+ fix: 'Ensure automation agents can access the same .cursor/rules/ as interactive agents.',
1366
+ template: null,
1367
+ file: () => '.cursor/rules/',
1368
+ line: () => null,
1369
+ },
1370
+
1371
+ cursorEnvMatchesLocal: {
1372
+ id: 'CU-K04',
1373
+ name: 'environment.json matches local dev environment',
1374
+ check: (ctx) => {
1375
+ const env = envJsonData(ctx);
1376
+ if (!env) return null;
1377
+ // Check that baseImage matches project stack
1378
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
1379
+ if (pkg && env.baseImage) {
1380
+ const isNode = /node/i.test(env.baseImage);
1381
+ const projectIsNode = Boolean(pkg.dependencies || pkg.devDependencies);
1382
+ return isNode === projectIsNode || true; // relaxed check
1383
+ }
1384
+ return true;
1385
+ },
1386
+ impact: 'medium',
1387
+ rating: 3,
1388
+ category: 'cross-surface',
1389
+ fix: 'Ensure environment.json baseImage matches the local development environment stack.',
1390
+ template: null,
1391
+ file: () => '.cursor/environment.json',
1392
+ line: () => null,
1393
+ },
1394
+
1395
+ // =============================================
1396
+ // L. Quality Deep (7 checks) — CU-L01..CU-L07
1397
+ // =============================================
1398
+
1399
+ cursorModernFeatures: {
1400
+ id: 'CU-L01',
1401
+ name: 'Rules mention modern Cursor features (automations, background agents, BugBot)',
1402
+ check: (ctx) => {
1403
+ const content = allRulesContent(ctx);
1404
+ if (!content.trim()) return null;
1405
+ return /automation|background agent|bugbot|bug.?bot|design mode|agent.*window/i.test(content);
1406
+ },
1407
+ impact: 'medium',
1408
+ rating: 3,
1409
+ category: 'quality-deep',
1410
+ fix: 'Document awareness of modern Cursor features: automations, background agents, BugBot, Design Mode.',
1411
+ template: null,
1412
+ file: () => '.cursor/rules/',
1413
+ line: () => null,
1414
+ },
1415
+
1416
+ cursorNoDeprecatedPatterns: {
1417
+ id: 'CU-L02',
1418
+ name: 'No deprecated patterns (Notepads, .cursorrules for agent)',
1419
+ check: (ctx) => {
1420
+ const content = allRulesContent(ctx);
1421
+ const legacy = ctx.legacyCursorrules ? ctx.legacyCursorrules() : null;
1422
+ const combined = `${content}\n${legacy || ''}`;
1423
+ if (!combined.trim()) return null;
1424
+ const hasDeprecated = /@Notepads|notepad/i.test(combined);
1425
+ return !hasDeprecated && !legacy;
1426
+ },
1427
+ impact: 'high',
1428
+ rating: 4,
1429
+ category: 'quality-deep',
1430
+ fix: 'Remove @Notepads references (deprecated Oct 2025). Migrate .cursorrules to .cursor/rules/*.mdc.',
1431
+ template: 'cursor-legacy-migration',
1432
+ file: () => '.cursor/rules/',
1433
+ line: () => null,
1434
+ },
1435
+
1436
+ cursorRuleCountManageable: {
1437
+ id: 'CU-L03',
1438
+ name: 'Rule file count is manageable (<20 files, avoid context bloat)',
1439
+ check: (ctx) => {
1440
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
1441
+ if (rules.length === 0) return null;
1442
+ return rules.length < 20;
1443
+ },
1444
+ impact: 'medium',
1445
+ rating: 3,
1446
+ category: 'quality-deep',
1447
+ fix: 'Keep rule files under 20. Consolidate related rules to avoid context bloat.',
1448
+ template: null,
1449
+ file: () => '.cursor/rules/',
1450
+ line: () => null,
1451
+ },
1452
+
1453
+ cursorAlwaysApplyMinimized: {
1454
+ id: 'CU-L04',
1455
+ name: 'Always Apply rules minimized (token cost per message)',
1456
+ check: (ctx) => {
1457
+ const always = ctx.alwaysApplyRules ? ctx.alwaysApplyRules() : [];
1458
+ if (always.length === 0) return null;
1459
+ // More than 3 always-apply rules is excessive
1460
+ return always.length <= 3;
1461
+ },
1462
+ impact: 'medium',
1463
+ rating: 3,
1464
+ category: 'quality-deep',
1465
+ fix: 'Minimize Always Apply rules (keep to 1-3). Each one adds token cost to every message.',
1466
+ template: null,
1467
+ file: () => '.cursor/rules/',
1468
+ line: () => null,
1469
+ },
1470
+
1471
+ cursorNoNestedRulesDirs: {
1472
+ id: 'CU-L05',
1473
+ name: 'No nested rules directories (silently ignored by Cursor)',
1474
+ check: (ctx) => {
1475
+ const rulesDir = path.join(ctx.dir, '.cursor', 'rules');
1476
+ try {
1477
+ const entries = require('fs').readdirSync(rulesDir, { withFileTypes: true });
1478
+ const hasDirs = entries.some(e => e.isDirectory());
1479
+ return !hasDirs;
1480
+ } catch {
1481
+ return null;
1482
+ }
1483
+ },
1484
+ impact: 'low',
1485
+ rating: 2,
1486
+ category: 'quality-deep',
1487
+ fix: 'Remove subdirectories from .cursor/rules/ — Cursor silently ignores nested rule directories.',
1488
+ template: null,
1489
+ file: () => '.cursor/rules/',
1490
+ line: () => null,
1491
+ },
1492
+
1493
+ cursorDocsIndexed: {
1494
+ id: 'CU-L06',
1495
+ name: '@Docs indexed for project framework documentation',
1496
+ check: (ctx) => {
1497
+ const content = allRulesContent(ctx);
1498
+ if (!content.trim()) return null;
1499
+ return /@Docs|documentation.*crawl|docs.*index|library.*reference/i.test(content);
1500
+ },
1501
+ impact: 'medium',
1502
+ rating: 3,
1503
+ category: 'quality-deep',
1504
+ fix: 'Configure @Docs to index key framework documentation for better agent suggestions.',
1505
+ template: null,
1506
+ file: () => '.cursor/rules/',
1507
+ line: () => null,
1508
+ },
1509
+
1510
+ cursorSessionDriftAwareness: {
1511
+ id: 'CU-L07',
1512
+ name: 'Agent session drift awareness documented',
1513
+ check: (ctx) => {
1514
+ const content = allRulesContent(ctx);
1515
+ if (!content.trim()) return null;
1516
+ return /session.*drift|context.*window|long.*session|session.*length|refresh.*context/i.test(content);
1517
+ },
1518
+ impact: 'low',
1519
+ rating: 2,
1520
+ category: 'quality-deep',
1521
+ fix: 'Document session drift awareness. Long sessions (>2h) may lose agent context.',
1522
+ template: null,
1523
+ file: () => '.cursor/rules/',
1524
+ line: () => null,
1525
+ },
1526
+
1527
+ // =============================================
1528
+ // M. Advisory (4 checks) — CU-M01..CU-M04
1529
+ // =============================================
1530
+
1531
+ cursorAdvisoryInstructionQuality: {
1532
+ id: 'CU-M01',
1533
+ name: 'Instruction quality score meets advisory threshold',
1534
+ check: (ctx) => {
1535
+ const content = coreRulesContent(ctx) || allRulesContent(ctx);
1536
+ if (!content.trim()) return null;
1537
+ const lines = content.split(/\r?\n/).filter(l => l.trim()).length;
1538
+ const sections = countSections(content);
1539
+ const hasArch = hasArchitecture(content);
1540
+ const hasVerify = /\bverif|\btest|\blint|\bbuild/i.test(content);
1541
+ const score = (lines >= 30 ? 2 : lines >= 15 ? 1 : 0) +
1542
+ (sections >= 4 ? 2 : sections >= 2 ? 1 : 0) +
1543
+ (hasArch ? 1 : 0) +
1544
+ (hasVerify ? 1 : 0);
1545
+ return score >= 4;
1546
+ },
1547
+ impact: 'medium',
1548
+ rating: 4,
1549
+ category: 'advisory',
1550
+ fix: 'Improve rule quality: add more sections, architecture diagram, and verification commands.',
1551
+ template: 'cursor-rules',
1552
+ file: () => '.cursor/rules/',
1553
+ line: () => null,
1554
+ },
1555
+
1556
+ cursorAdvisorySecurityPosture: {
1557
+ id: 'CU-M02',
1558
+ name: 'Security posture meets advisory threshold',
1559
+ check: (ctx) => {
1560
+ let score = 0;
1561
+ const docs = docsBundle(ctx);
1562
+ if (/privacy mode/i.test(docs)) score++;
1563
+ if (!ctx.hasLegacyRules || !ctx.hasLegacyRules()) score++;
1564
+ const mcpResult = ctx.mcpConfig();
1565
+ if (mcpResult.ok) {
1566
+ const validation = validateMcpEnvVars(mcpResult.data);
1567
+ if (validation.valid) score++;
1568
+ } else {
1569
+ score++; // No MCP = no MCP risk
1570
+ }
1571
+ if (/security|secret|credential/i.test(docs)) score++;
1572
+ return score >= 2;
1573
+ },
1574
+ impact: 'high',
1575
+ rating: 5,
1576
+ category: 'advisory',
1577
+ fix: 'Improve security posture: enable Privacy Mode, migrate .cursorrules, secure MCP config.',
1578
+ template: null,
1579
+ file: () => '.cursor/rules/',
1580
+ line: () => null,
1581
+ },
1582
+
1583
+ cursorAdvisorySurfaceCoverage: {
1584
+ id: 'CU-M03',
1585
+ name: 'Surface coverage meets advisory threshold',
1586
+ check: (ctx) => {
1587
+ const surfaces = ctx.detectSurfaces ? ctx.detectSurfaces() : {};
1588
+ return surfaces.foreground === true;
1589
+ },
1590
+ impact: 'medium',
1591
+ rating: 4,
1592
+ category: 'advisory',
1593
+ fix: 'Configure at least the foreground agent surface with .cursor/rules/*.mdc files.',
1594
+ template: 'cursor-rules',
1595
+ file: () => '.cursor/rules/',
1596
+ line: () => null,
1597
+ },
1598
+
1599
+ cursorAdvisoryMcpHealth: {
1600
+ id: 'CU-M04',
1601
+ name: 'MCP configuration health meets advisory threshold',
1602
+ check: (ctx) => {
1603
+ const servers = ctx.mcpServers ? ctx.mcpServers() : {};
1604
+ const count = Object.keys(servers).length;
1605
+ if (count === 0) return null;
1606
+ const mcpResult = ctx.mcpConfig();
1607
+ return mcpResult && mcpResult.ok;
1608
+ },
1609
+ impact: 'medium',
1610
+ rating: 3,
1611
+ category: 'advisory',
1612
+ fix: 'Ensure MCP configuration is valid and servers are properly configured.',
1613
+ template: null,
1614
+ file: () => '.cursor/mcp.json',
1615
+ line: () => null,
1616
+ },
1617
+
1618
+ // =============================================
1619
+ // N. Pack (4 checks) — CU-N01..CU-N04
1620
+ // =============================================
1621
+
1622
+ cursorPackDomainDetected: {
1623
+ id: 'CU-N01',
1624
+ name: 'Domain pack detection returns relevant results',
1625
+ check: (ctx) => {
1626
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
1627
+ return Boolean(pkg || ctx.fileContent('go.mod') || ctx.fileContent('Cargo.toml') || ctx.fileContent('pyproject.toml'));
1628
+ },
1629
+ impact: 'low',
1630
+ rating: 2,
1631
+ category: 'advisory',
1632
+ fix: 'Ensure project has identifiable stack markers for domain pack detection.',
1633
+ template: null,
1634
+ file: () => 'package.json',
1635
+ line: () => null,
1636
+ },
1637
+
1638
+ cursorPackMcpRecommended: {
1639
+ id: 'CU-N02',
1640
+ name: 'MCP packs recommended based on project signals',
1641
+ check: (ctx) => {
1642
+ const servers = ctx.mcpServers ? ctx.mcpServers() : {};
1643
+ return Object.keys(servers).length > 0;
1644
+ },
1645
+ impact: 'low',
1646
+ rating: 2,
1647
+ category: 'advisory',
1648
+ fix: 'Add recommended MCP packs to .cursor/mcp.json based on project domain.',
1649
+ template: 'cursor-mcp',
1650
+ file: () => '.cursor/mcp.json',
1651
+ line: () => null,
1652
+ },
1653
+
1654
+ cursorPackGovernanceApplied: {
1655
+ id: 'CU-N03',
1656
+ name: 'Governance pack applied if enterprise signals detected',
1657
+ check: (ctx) => {
1658
+ const docs = docsBundle(ctx);
1659
+ if (!/enterprise|business/i.test(docs)) return null;
1660
+ return /governance|policy|audit/i.test(docs);
1661
+ },
1662
+ impact: 'medium',
1663
+ rating: 3,
1664
+ category: 'advisory',
1665
+ fix: 'Apply governance pack for enterprise repos.',
1666
+ template: null,
1667
+ file: () => '.cursor/rules/',
1668
+ line: () => null,
1669
+ },
1670
+
1671
+ cursorPackConsistency: {
1672
+ id: 'CU-N04',
1673
+ name: 'All applied packs are consistent with each other',
1674
+ check: (ctx) => {
1675
+ const rules = allRulesContent(ctx);
1676
+ const mcp = mcpJsonRaw(ctx);
1677
+ if (!rules && !mcp) return null;
1678
+ // No contradiction: if rules say "strict" and config says "yolo"
1679
+ const rulesStrict = /\bstrict\b|\blocked.?down\b|\bno auto/i.test(rules);
1680
+ const configPermissive = /yolo|auto.*run.*all/i.test(rules);
1681
+ return !(rulesStrict && configPermissive);
1682
+ },
1683
+ impact: 'medium',
1684
+ rating: 3,
1685
+ category: 'advisory',
1686
+ fix: 'Resolve contradictions between rule guidance and configuration.',
1687
+ template: null,
1688
+ file: () => '.cursor/rules/',
1689
+ line: () => null,
1690
+ },
1691
+
1692
+ // =============================================
1693
+ // O. Repeat (3 checks) — CU-O01..CU-O03
1694
+ // =============================================
1695
+
1696
+ cursorRepeatScoreImproved: {
1697
+ id: 'CU-O01',
1698
+ name: 'Audit score improved since last run',
1699
+ check: () => null, // Requires snapshot history — always N/A in static check
1700
+ impact: 'low',
1701
+ rating: 2,
1702
+ category: 'freshness',
1703
+ fix: 'Run audits regularly and track score improvement over time.',
1704
+ template: null,
1705
+ file: () => null,
1706
+ line: () => null,
1707
+ },
1708
+
1709
+ cursorRepeatNoRegressions: {
1710
+ id: 'CU-O02',
1711
+ name: 'No regressions since last audit',
1712
+ check: () => null, // Requires snapshot history
1713
+ impact: 'medium',
1714
+ rating: 3,
1715
+ category: 'freshness',
1716
+ fix: 'Review and fix any regressions detected since the last audit.',
1717
+ template: null,
1718
+ file: () => null,
1719
+ line: () => null,
1720
+ },
1721
+
1722
+ cursorRepeatFeedbackLoop: {
1723
+ id: 'CU-O03',
1724
+ name: 'Feedback loop active for recommendations',
1725
+ check: () => null, // Requires feedback data
1726
+ impact: 'low',
1727
+ rating: 2,
1728
+ category: 'freshness',
1729
+ fix: 'Use `npx nerviq --platform cursor feedback` to rate recommendations.',
1730
+ template: null,
1731
+ file: () => null,
1732
+ line: () => null,
1733
+ },
1734
+
1735
+ // =============================================
1736
+ // P. Freshness (3 checks) — CU-P01..CU-P03
1737
+ // =============================================
1738
+
1739
+ cursorFreshnessSourcesVerified: {
1740
+ id: 'CU-P01',
1741
+ name: 'P0 freshness sources verified within threshold',
1742
+ check: () => null, // Requires freshness verification data
1743
+ impact: 'medium',
1744
+ rating: 3,
1745
+ category: 'freshness',
1746
+ fix: 'Verify P0 Cursor documentation sources are current before claiming freshness.',
1747
+ template: null,
1748
+ file: () => null,
1749
+ line: () => null,
1750
+ },
1751
+
1752
+ cursorFreshnessPropagation: {
1753
+ id: 'CU-P02',
1754
+ name: 'Freshness propagation checklist is current',
1755
+ check: () => null, // Requires propagation tracking
1756
+ impact: 'low',
1757
+ rating: 2,
1758
+ category: 'freshness',
1759
+ fix: 'Review propagation checklist when Cursor releases new features or changes.',
1760
+ template: null,
1761
+ file: () => null,
1762
+ line: () => null,
1763
+ },
1764
+
1765
+ cursorFreshnessRuleFormat: {
1766
+ id: 'CU-P03',
1767
+ name: 'Rule format matches current Cursor version expectations',
1768
+ check: (ctx) => {
1769
+ const rules = ctx.cursorRules ? ctx.cursorRules() : [];
1770
+ if (rules.length === 0) return null;
1771
+ // All rules should use MDC format (not plain markdown)
1772
+ return rules.every(r => r.frontmatter !== null);
1773
+ },
1774
+ impact: 'medium',
1775
+ rating: 3,
1776
+ category: 'freshness',
1777
+ fix: 'Ensure all rules use current MDC format with YAML frontmatter.',
1778
+ template: null,
1779
+ file: () => '.cursor/rules/',
1780
+ line: () => null,
1781
+ },
1782
+ };
1783
+
1784
+ module.exports = {
1785
+ CURSOR_TECHNIQUES,
1786
+ };