@nerviq/cli 0.0.1 → 0.9.0-beta.2

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,1758 @@
1
+ /**
2
+ * Windsurf 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. Cascade Agent(5), E. MCP(5), F. Instructions Quality(5)
6
+ * v0.5 (55): G. Workflows & Steps(5), H. Memories(5), I. Enterprise(5)
7
+ * v1.0 (70): J. Cascadeignore & 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
+ * Windsurf key differences from Cursor:
13
+ * - Instructions: .windsurf/rules/*.md (Markdown + YAML frontmatter, NOT MDC)
14
+ * - Legacy: .windsurfrules (like .cursorrules)
15
+ * - 4 activation modes: Always, Auto, Agent-Requested, Manual
16
+ * - Agent: Cascade (autonomous agent)
17
+ * - Memories system (team-syncable)
18
+ * - Workflows -> Slash commands
19
+ * - 10K char rule limit
20
+ * - MCP with team whitelist
21
+ * - cascadeignore (gitignore for Cascade)
22
+ * - No background agents
23
+ * - Check ID prefix: WS-
24
+ */
25
+
26
+ const os = require('os');
27
+ const path = require('path');
28
+ const { WindsurfProjectContext } = require('./context');
29
+ const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
30
+ const { validateWindsurfFrontmatter, validateMcpEnvVars } = require('./config-parser');
31
+
32
+ // ─── Shared helpers ─────────────────────────────────────────────────────────
33
+
34
+ const FILLER_PATTERNS = [
35
+ /\bbe helpful\b/i,
36
+ /\bbe accurate\b/i,
37
+ /\bbe concise\b/i,
38
+ /\balways do your best\b/i,
39
+ /\bmaintain high quality\b/i,
40
+ /\bwrite clean code\b/i,
41
+ /\bfollow best practices\b/i,
42
+ ];
43
+
44
+ function countSections(markdown) {
45
+ return (markdown.match(/^##\s+/gm) || []).length;
46
+ }
47
+
48
+ function firstLineMatching(text, matcher) {
49
+ if (!text) return null;
50
+ const lines = text.split(/\r?\n/);
51
+ for (let index = 0; index < lines.length; index++) {
52
+ const line = lines[index];
53
+ if (typeof matcher === 'string' && line.includes(matcher)) return index + 1;
54
+ if (matcher instanceof RegExp && matcher.test(line)) {
55
+ matcher.lastIndex = 0;
56
+ return index + 1;
57
+ }
58
+ if (typeof matcher === 'function' && matcher(line, index + 1)) return index + 1;
59
+ }
60
+ return null;
61
+ }
62
+
63
+ function findFillerLine(content) {
64
+ return firstLineMatching(content, (line) => FILLER_PATTERNS.some((p) => p.test(line)));
65
+ }
66
+
67
+ function findSecretLine(content) {
68
+ const lines = content.split(/\r?\n/);
69
+ for (let index = 0; index < lines.length; index++) {
70
+ const matched = EMBEDDED_SECRET_PATTERNS.some((pattern) => {
71
+ pattern.lastIndex = 0;
72
+ return pattern.test(lines[index]);
73
+ });
74
+ if (matched) return index + 1;
75
+ }
76
+ return null;
77
+ }
78
+
79
+ function allRulesContent(ctx) {
80
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
81
+ return rules.map(r => r.body || '').join('\n');
82
+ }
83
+
84
+ function coreRulesContent(ctx) {
85
+ const always = ctx.alwaysRules ? ctx.alwaysRules() : [];
86
+ return always.map(r => r.body || '').join('\n');
87
+ }
88
+
89
+ function mcpJsonRaw(ctx) {
90
+ return ctx.fileContent('.windsurf/mcp.json') || '';
91
+ }
92
+
93
+ function mcpJsonData(ctx) {
94
+ const result = ctx.mcpConfig();
95
+ return result && result.ok ? result.data : null;
96
+ }
97
+
98
+ function docsBundle(ctx) {
99
+ const rules = allRulesContent(ctx) || '';
100
+ const readme = ctx.fileContent('README.md') || '';
101
+ const legacy = ctx.legacyWindsurfrules ? (ctx.legacyWindsurfrules() || '') : '';
102
+ return `${rules}\n${readme}\n${legacy}`;
103
+ }
104
+
105
+ function expectedVerificationCategories(ctx) {
106
+ const categories = new Set();
107
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
108
+ const scripts = pkg && pkg.scripts ? pkg.scripts : {};
109
+ if (scripts.test) categories.add('test');
110
+ if (scripts.lint) categories.add('lint');
111
+ if (scripts.build) categories.add('build');
112
+ if (ctx.fileContent('Cargo.toml')) { categories.add('test'); categories.add('build'); }
113
+ if (ctx.fileContent('go.mod')) { categories.add('test'); categories.add('build'); }
114
+ if (ctx.fileContent('pyproject.toml') || ctx.fileContent('requirements.txt')) categories.add('test');
115
+ if (ctx.fileContent('Makefile') || ctx.fileContent('justfile')) categories.add('build');
116
+ return [...categories];
117
+ }
118
+
119
+ function hasCommandMention(content, category) {
120
+ if (category === 'test') {
121
+ 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);
122
+ }
123
+ if (category === 'lint') {
124
+ 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);
125
+ }
126
+ if (category === 'build') {
127
+ 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);
128
+ }
129
+ return false;
130
+ }
131
+
132
+ function hasArchitecture(content) {
133
+ return /```mermaid|flowchart\b|graph\s+(TD|LR|RL|BT)\b|##\s+Architecture\b|##\s+Project Map\b|##\s+Structure\b/i.test(content);
134
+ }
135
+
136
+ function repoLooksRegulated(ctx) {
137
+ const filenames = (ctx.files || []).join('\n');
138
+ const pkg = ctx.fileContent('package.json') || '';
139
+ const readme = ctx.fileContent('README.md') || '';
140
+ const combined = `${filenames}\n${pkg}\n${readme}`;
141
+ 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;
142
+ if (strong.test(combined)) return true;
143
+ const weakMatches = combined.match(/\bgdpr\b|\bpii\b/gi) || [];
144
+ return weakMatches.length >= 2;
145
+ }
146
+
147
+ function memoryContents(ctx) {
148
+ const memories = ctx.memoryContents ? ctx.memoryContents() : [];
149
+ return memories.map(m => m.content || '').join('\n');
150
+ }
151
+
152
+ function workflowContents(ctx) {
153
+ const files = ctx.workflowFiles ? ctx.workflowFiles() : [];
154
+ return files.map(f => ctx.fileContent(f) || '').join('\n');
155
+ }
156
+
157
+ function wordCount(text) {
158
+ if (!text) return 0;
159
+ return text.split(/\s+/).filter(Boolean).length;
160
+ }
161
+
162
+ // ─── WINDSURF_TECHNIQUES ──────────────────────────────────────────────────────
163
+
164
+ const WINDSURF_TECHNIQUES = {
165
+
166
+ // =============================================
167
+ // A. Rules (9 checks) — WS-A01..WS-A09
168
+ // =============================================
169
+
170
+ windsurfRulesExist: {
171
+ id: 'WS-A01',
172
+ name: '.windsurf/rules/ directory exists with .md files',
173
+ check: (ctx) => {
174
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
175
+ return rules.length > 0;
176
+ },
177
+ impact: 'critical',
178
+ rating: 5,
179
+ category: 'rules',
180
+ fix: 'Create .windsurf/rules/ directory with at least one .md rule file with YAML frontmatter.',
181
+ template: 'windsurf-rules',
182
+ file: () => '.windsurf/rules/',
183
+ line: () => null,
184
+ },
185
+
186
+ windsurfNoLegacyWindsurfrules: {
187
+ id: 'WS-A02',
188
+ name: 'No .windsurfrules without migration to .windsurf/rules/',
189
+ check: (ctx) => {
190
+ const hasLegacy = ctx.hasLegacyRules ? ctx.hasLegacyRules() : Boolean(ctx.fileContent('.windsurfrules'));
191
+ return !hasLegacy;
192
+ },
193
+ impact: 'critical',
194
+ rating: 5,
195
+ category: 'rules',
196
+ fix: 'Migrate .windsurfrules to .windsurf/rules/*.md with proper YAML frontmatter.',
197
+ template: 'windsurf-legacy-migration',
198
+ file: () => '.windsurfrules',
199
+ line: () => 1,
200
+ },
201
+
202
+ windsurfAlwaysRuleExists: {
203
+ id: 'WS-A03',
204
+ name: 'At least one rule has trigger: always for Cascade',
205
+ check: (ctx) => {
206
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
207
+ if (rules.length === 0) return null;
208
+ return rules.some(r => r.ruleType === 'always');
209
+ },
210
+ impact: 'high',
211
+ rating: 5,
212
+ category: 'rules',
213
+ fix: 'Add trigger: always to your core rule file so Cascade always sees instructions.',
214
+ template: 'windsurf-rules',
215
+ file: () => '.windsurf/rules/',
216
+ line: () => null,
217
+ },
218
+
219
+ windsurfRulesValidFrontmatter: {
220
+ id: 'WS-A04',
221
+ name: 'Rules have valid YAML frontmatter',
222
+ check: (ctx) => {
223
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
224
+ if (rules.length === 0) return null;
225
+ return rules.every(r => {
226
+ if (!r.frontmatter) return false;
227
+ const validation = validateWindsurfFrontmatter(r.frontmatter);
228
+ return validation.valid;
229
+ });
230
+ },
231
+ impact: 'high',
232
+ rating: 4,
233
+ category: 'rules',
234
+ fix: 'Fix YAML frontmatter in rule .md files. Use: trigger, description, globs, name fields.',
235
+ template: null,
236
+ file: () => '.windsurf/rules/',
237
+ line: () => 1,
238
+ },
239
+
240
+ windsurfRulesUnder10kChars: {
241
+ id: 'WS-A05',
242
+ name: 'Rules under 10K character limit per file',
243
+ check: (ctx) => {
244
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
245
+ if (rules.length === 0) return null;
246
+ return rules.every(r => !r.overLimit);
247
+ },
248
+ impact: 'high',
249
+ rating: 4,
250
+ category: 'rules',
251
+ fix: 'Split rules over 10K characters into multiple focused files. Windsurf enforces a 10K char limit per rule.',
252
+ template: null,
253
+ file: () => '.windsurf/rules/',
254
+ line: () => null,
255
+ },
256
+
257
+ windsurfRulesUnder500Words: {
258
+ id: 'WS-A06',
259
+ name: 'Rules are under ~500 words each (longer = less reliably followed)',
260
+ check: (ctx) => {
261
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
262
+ if (rules.length === 0) return null;
263
+ return rules.every(r => wordCount(r.body) <= 500);
264
+ },
265
+ impact: 'medium',
266
+ rating: 3,
267
+ category: 'rules',
268
+ fix: 'Split long rules into focused, shorter files. Rules over ~500 words are less reliably followed.',
269
+ template: null,
270
+ file: () => '.windsurf/rules/',
271
+ line: () => null,
272
+ },
273
+
274
+ windsurfRulesNoFiller: {
275
+ id: 'WS-A07',
276
+ name: 'No generic filler instructions in rules',
277
+ check: (ctx) => {
278
+ const content = allRulesContent(ctx);
279
+ if (!content.trim()) return null;
280
+ return !FILLER_PATTERNS.some(p => p.test(content));
281
+ },
282
+ impact: 'low',
283
+ rating: 3,
284
+ category: 'rules',
285
+ fix: 'Replace generic filler like "be helpful" with concrete, repo-specific guidance.',
286
+ template: null,
287
+ file: () => '.windsurf/rules/',
288
+ line: () => {
289
+ const content = allRulesContent({ windsurfRules: () => [] });
290
+ return content ? findFillerLine(content) : null;
291
+ },
292
+ },
293
+
294
+ windsurfRulesNoSecrets: {
295
+ id: 'WS-A08',
296
+ name: 'No secrets/API keys in rule files',
297
+ check: (ctx) => {
298
+ const rulesContent = allRulesContent(ctx);
299
+ const workflowContent = (ctx.workflowFiles ? ctx.workflowFiles() : [])
300
+ .map(f => ctx.fileContent(f) || '').join('\n');
301
+ const combined = `${rulesContent}\n${workflowContent}`;
302
+ if (!combined.trim()) return null;
303
+ return !containsEmbeddedSecret(combined);
304
+ },
305
+ impact: 'critical',
306
+ rating: 5,
307
+ category: 'rules',
308
+ fix: 'Remove API keys and secrets from rule and workflow files. Use environment variables instead.',
309
+ template: null,
310
+ file: () => '.windsurf/rules/',
311
+ line: () => null,
312
+ },
313
+
314
+ windsurfAgentRequestedDescriptions: {
315
+ id: 'WS-A09',
316
+ name: 'Agent-Requested rules have precise descriptions',
317
+ check: (ctx) => {
318
+ const agentRules = ctx.agentRequestedRules ? ctx.agentRequestedRules() : [];
319
+ if (agentRules.length === 0) return null;
320
+ return agentRules.every(r => {
321
+ const desc = r.frontmatter && r.frontmatter.description;
322
+ return desc && String(desc).trim().length >= 15;
323
+ });
324
+ },
325
+ impact: 'medium',
326
+ rating: 3,
327
+ category: 'rules',
328
+ fix: 'Add clear, specific descriptions (15+ chars) to Agent-Requested rules so Cascade can judge relevance.',
329
+ template: null,
330
+ file: () => '.windsurf/rules/',
331
+ line: () => null,
332
+ },
333
+
334
+ // =============================================
335
+ // B. Config (7 checks) — WS-B01..WS-B07
336
+ // =============================================
337
+
338
+ windsurfMcpJsonExists: {
339
+ id: 'WS-B01',
340
+ name: '.windsurf/mcp.json exists if MCP is used',
341
+ check: (ctx) => {
342
+ const result = ctx.mcpConfig();
343
+ if (result.ok) return true;
344
+ const globalResult = ctx.globalMcpConfig ? ctx.globalMcpConfig() : { ok: false };
345
+ if (!globalResult.ok) return null;
346
+ return false;
347
+ },
348
+ impact: 'high',
349
+ rating: 4,
350
+ category: 'config',
351
+ fix: 'Create .windsurf/mcp.json with project-level MCP server configuration.',
352
+ template: 'windsurf-mcp',
353
+ file: () => '.windsurf/mcp.json',
354
+ line: () => null,
355
+ },
356
+
357
+ windsurfMcpTeamWhitelist: {
358
+ id: 'WS-B02',
359
+ name: 'MCP servers on team whitelist (if team)',
360
+ check: (ctx) => {
361
+ const servers = ctx.mcpServers ? ctx.mcpServers() : {};
362
+ const count = Object.keys(servers).length;
363
+ if (count === 0) return null;
364
+ // Check if rules mention team whitelist / approved servers
365
+ const docs = docsBundle(ctx);
366
+ if (!/team|org|enterprise/i.test(docs)) return null;
367
+ return /whitelist|allowlist|approved.*server|mcp.*approv/i.test(docs);
368
+ },
369
+ impact: 'high',
370
+ rating: 4,
371
+ category: 'config',
372
+ fix: 'Document MCP server team whitelist. Windsurf supports team-level MCP whitelisting.',
373
+ template: null,
374
+ file: () => '.windsurf/mcp.json',
375
+ line: () => null,
376
+ },
377
+
378
+ windsurfWorkflowsExist: {
379
+ id: 'WS-B03',
380
+ name: 'Workflow slash commands exist in .windsurf/workflows/',
381
+ check: (ctx) => {
382
+ const files = ctx.workflowFiles ? ctx.workflowFiles() : [];
383
+ return files.length > 0;
384
+ },
385
+ impact: 'medium',
386
+ rating: 3,
387
+ category: 'config',
388
+ fix: 'Create .windsurf/workflows/*.md files for reusable slash command workflows.',
389
+ template: 'windsurf-workflows',
390
+ file: () => '.windsurf/workflows/',
391
+ line: () => null,
392
+ },
393
+
394
+ windsurfCascadeignoreExists: {
395
+ id: 'WS-B04',
396
+ name: '.cascadeignore exists for sensitive file exclusion',
397
+ check: (ctx) => {
398
+ const hasCascadeignore = ctx.hasCascadeignore ? ctx.hasCascadeignore() : Boolean(ctx.fileContent('.cascadeignore'));
399
+ if (hasCascadeignore) return true;
400
+ // N/A if no sensitive file signals
401
+ const hasSecrets = ctx.fileContent('.env') || ctx.fileContent('.env.local') || ctx.hasDir('secrets');
402
+ if (!hasSecrets) return null;
403
+ return false;
404
+ },
405
+ impact: 'high',
406
+ rating: 4,
407
+ category: 'config',
408
+ fix: 'Create .cascadeignore to exclude sensitive files from Cascade agent access (similar to .gitignore syntax).',
409
+ template: 'windsurf-cascadeignore',
410
+ file: () => '.cascadeignore',
411
+ line: () => null,
412
+ },
413
+
414
+ windsurfMemoriesConfigured: {
415
+ id: 'WS-B05',
416
+ name: 'Memories configured for persistent context',
417
+ check: (ctx) => {
418
+ const memories = ctx.memoryFiles ? ctx.memoryFiles() : [];
419
+ return memories.length > 0;
420
+ },
421
+ impact: 'medium',
422
+ rating: 3,
423
+ category: 'config',
424
+ fix: 'Create .windsurf/memories/ files for team-syncable persistent context.',
425
+ template: 'windsurf-memories',
426
+ file: () => '.windsurf/memories/',
427
+ line: () => null,
428
+ },
429
+
430
+ windsurfMcpValidJson: {
431
+ id: 'WS-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 .windsurf/mcp.json.',
443
+ template: null,
444
+ file: () => '.windsurf/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
+ windsurfWorkflowsClear: {
460
+ id: 'WS-B07',
461
+ name: 'Workflow .md files have clear prompts',
462
+ check: (ctx) => {
463
+ const files = ctx.workflowFiles ? ctx.workflowFiles() : [];
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 workflow files have clear, actionable prompt content (20+ chars).',
474
+ template: null,
475
+ file: () => '.windsurf/workflows/',
476
+ line: () => null,
477
+ },
478
+
479
+ // =============================================
480
+ // C. Trust & Safety (9 checks) — WS-C01..WS-C09
481
+ // =============================================
482
+
483
+ windsurfCascadeignoreSensitive: {
484
+ id: 'WS-C01',
485
+ name: 'Cascadeignore covers sensitive directories and files',
486
+ check: (ctx) => {
487
+ const content = ctx.cascadeignoreContent ? ctx.cascadeignoreContent() : ctx.fileContent('.cascadeignore');
488
+ if (!content) return null;
489
+ // Check for common sensitive patterns
490
+ return /\.env|secrets|credentials|\.aws|\.ssh|private/i.test(content);
491
+ },
492
+ impact: 'high',
493
+ rating: 5,
494
+ category: 'trust',
495
+ fix: 'Add sensitive file patterns (.env, secrets/, credentials, .aws/, .ssh/) to .cascadeignore.',
496
+ template: null,
497
+ file: () => '.cascadeignore',
498
+ line: () => null,
499
+ },
500
+
501
+ windsurfNoSecretsInConfig: {
502
+ id: 'WS-C02',
503
+ name: 'No secrets in any Windsurf config files',
504
+ check: (ctx) => {
505
+ const rulesContent = allRulesContent(ctx);
506
+ const mcpContent = mcpJsonRaw(ctx);
507
+ const memContent = memoryContents(ctx);
508
+ const combined = `${rulesContent}\n${mcpContent}\n${memContent}`;
509
+ if (!combined.trim()) return null;
510
+ return !containsEmbeddedSecret(combined);
511
+ },
512
+ impact: 'critical',
513
+ rating: 5,
514
+ category: 'trust',
515
+ fix: 'Remove secrets from all Windsurf config files. Use environment variables instead.',
516
+ template: null,
517
+ file: () => '.windsurf/',
518
+ line: () => null,
519
+ },
520
+
521
+ windsurfMcpTrustedSources: {
522
+ id: 'WS-C03',
523
+ name: 'MCP servers from trusted sources',
524
+ check: (ctx) => {
525
+ const raw = mcpJsonRaw(ctx);
526
+ if (!raw) return null;
527
+ const knownVulnerable = /mcp-poisoned|cve-2025/i.test(raw);
528
+ const hasUntrusted = /curl.*\|.*sh|wget.*\|.*sh/i.test(raw);
529
+ return !knownVulnerable && !hasUntrusted;
530
+ },
531
+ impact: 'high',
532
+ rating: 5,
533
+ category: 'trust',
534
+ fix: 'Verify MCP servers are from trusted sources. Check for known MCP CVEs.',
535
+ template: null,
536
+ file: () => '.windsurf/mcp.json',
537
+ line: () => null,
538
+ },
539
+
540
+ windsurfMcpEnvVarSyntax: {
541
+ id: 'WS-C04',
542
+ name: 'MCP env vars use proper syntax (not hardcoded)',
543
+ check: (ctx) => {
544
+ const raw = mcpJsonRaw(ctx);
545
+ if (!raw) return null;
546
+ const data = mcpJsonData(ctx);
547
+ if (!data) return null;
548
+ const validation = validateMcpEnvVars(data);
549
+ return validation.valid;
550
+ },
551
+ impact: 'high',
552
+ rating: 5,
553
+ category: 'trust',
554
+ fix: 'Use ${env:VAR_NAME} syntax for MCP environment variables instead of hardcoded values.',
555
+ template: null,
556
+ file: () => '.windsurf/mcp.json',
557
+ line: () => null,
558
+ },
559
+
560
+ windsurfNoDirectPushMain: {
561
+ id: 'WS-C05',
562
+ name: 'Rules discourage direct push to main',
563
+ check: (ctx) => {
564
+ const rules = allRulesContent(ctx);
565
+ if (!rules.trim()) return null;
566
+ const hasPushToMain = /push.*main|commit.*main.*direct|direct.*push/i.test(rules);
567
+ return !hasPushToMain;
568
+ },
569
+ impact: 'high',
570
+ rating: 5,
571
+ category: 'trust',
572
+ fix: 'Ensure rules guide Cascade to create branches and PRs, not push directly to main.',
573
+ template: null,
574
+ file: () => '.windsurf/rules/',
575
+ line: () => null,
576
+ },
577
+
578
+ windsurfMemoriesNoSecrets: {
579
+ id: 'WS-C06',
580
+ name: 'No secrets in memory files (team-synced!)',
581
+ check: (ctx) => {
582
+ const content = memoryContents(ctx);
583
+ if (!content.trim()) return null;
584
+ return !containsEmbeddedSecret(content);
585
+ },
586
+ impact: 'critical',
587
+ rating: 5,
588
+ category: 'trust',
589
+ fix: 'Remove secrets from .windsurf/memories/ — these files sync across team members!',
590
+ template: null,
591
+ file: () => '.windsurf/memories/',
592
+ line: () => null,
593
+ },
594
+
595
+ windsurfCodeReversionRisk: {
596
+ id: 'WS-C07',
597
+ name: 'Code reversion risk mitigated',
598
+ check: (ctx) => {
599
+ const vscodeRaw = ctx.fileContent('.vscode/settings.json') || '';
600
+ const hasFormatOnSave = /formatOnSave.*true/i.test(vscodeRaw);
601
+ if (!hasFormatOnSave) return null;
602
+ const rules = allRulesContent(ctx);
603
+ return /code reversion|format.*save.*conflict|revert|format.*save.*warning/i.test(rules);
604
+ },
605
+ impact: 'critical',
606
+ rating: 5,
607
+ category: 'trust',
608
+ fix: 'Document code reversion risk: format-on-save + agent edits can cause silent code loss.',
609
+ template: null,
610
+ file: () => '.windsurf/rules/',
611
+ line: () => null,
612
+ },
613
+
614
+ windsurfTeamSyncAware: {
615
+ id: 'WS-C08',
616
+ name: 'Team sync implications documented',
617
+ check: (ctx) => {
618
+ const docs = docsBundle(ctx);
619
+ if (!/team|org/i.test(docs)) return null;
620
+ return /team.*sync|shared.*memor|team.*whitelist|sync.*across/i.test(docs);
621
+ },
622
+ impact: 'medium',
623
+ rating: 4,
624
+ category: 'trust',
625
+ fix: 'Document team sync implications for memories and MCP whitelist.',
626
+ template: null,
627
+ file: () => '.windsurf/rules/',
628
+ line: () => null,
629
+ },
630
+
631
+ windsurfNoWildcardWorkflows: {
632
+ id: 'WS-C09',
633
+ name: 'No overly broad workflow triggers',
634
+ check: (ctx) => {
635
+ const content = workflowContents(ctx);
636
+ if (!content.trim()) return null;
637
+ const hasBroad = /trigger:.*\*|on:.*\*|all.*files/i.test(content);
638
+ return !hasBroad;
639
+ },
640
+ impact: 'high',
641
+ rating: 5,
642
+ category: 'trust',
643
+ fix: 'Scope workflow triggers to specific patterns. Avoid wildcards.',
644
+ template: null,
645
+ file: () => '.windsurf/workflows/',
646
+ line: () => null,
647
+ },
648
+
649
+ // =============================================
650
+ // D. Cascade Agent (5 checks) — WS-D01..WS-D05
651
+ // =============================================
652
+
653
+ windsurfRulesReachCascade: {
654
+ id: 'WS-D01',
655
+ name: 'Rules properly reach Cascade (not just .windsurfrules)',
656
+ check: (ctx) => {
657
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
658
+ const hasLegacy = ctx.hasLegacyRules ? ctx.hasLegacyRules() : false;
659
+ if (rules.length === 0 && !hasLegacy) return null;
660
+ return rules.length > 0;
661
+ },
662
+ impact: 'critical',
663
+ rating: 5,
664
+ category: 'cascade-agent',
665
+ fix: 'Create .windsurf/rules/*.md files with proper frontmatter. .windsurfrules may be deprecated.',
666
+ template: 'windsurf-rules',
667
+ file: () => '.windsurf/rules/',
668
+ line: () => null,
669
+ },
670
+
671
+ windsurfCascadeMultiFile: {
672
+ id: 'WS-D02',
673
+ name: 'Cascade multi-file editing awareness documented',
674
+ check: (ctx) => {
675
+ const rules = allRulesContent(ctx);
676
+ if (!rules.trim()) return null;
677
+ return /multi.?file|cross.?file|cascade.*edit|multiple.*file/i.test(rules);
678
+ },
679
+ impact: 'medium',
680
+ rating: 3,
681
+ category: 'cascade-agent',
682
+ fix: 'Document Cascade multi-file editing capabilities and any project-specific constraints.',
683
+ template: null,
684
+ file: () => '.windsurf/rules/',
685
+ line: () => null,
686
+ },
687
+
688
+ windsurfCascadeStepsAwareness: {
689
+ id: 'WS-D03',
690
+ name: 'Steps automation awareness documented',
691
+ check: (ctx) => {
692
+ const rules = allRulesContent(ctx);
693
+ if (!rules.trim()) return null;
694
+ return /steps|automation|step.?by.?step|cascade.*step/i.test(rules);
695
+ },
696
+ impact: 'medium',
697
+ rating: 3,
698
+ category: 'cascade-agent',
699
+ fix: 'Document Cascade Steps automation capabilities for complex multi-step tasks.',
700
+ template: null,
701
+ file: () => '.windsurf/rules/',
702
+ line: () => null,
703
+ },
704
+
705
+ windsurfSessionLengthAwareness: {
706
+ id: 'WS-D04',
707
+ name: 'Agent session length awareness',
708
+ check: (ctx) => {
709
+ const rules = allRulesContent(ctx);
710
+ if (!rules.trim()) return null;
711
+ return /session.*length|session.*limit|context.*drift|long.*session/i.test(rules);
712
+ },
713
+ impact: 'low',
714
+ rating: 2,
715
+ category: 'cascade-agent',
716
+ fix: 'Document session length recommendations. Long sessions may lose Cascade context.',
717
+ template: null,
718
+ file: () => '.windsurf/rules/',
719
+ line: () => null,
720
+ },
721
+
722
+ windsurfSkillsConfigured: {
723
+ id: 'WS-D05',
724
+ name: 'Cascade skills configured for project needs',
725
+ check: (ctx) => {
726
+ const rules = allRulesContent(ctx);
727
+ if (!rules.trim()) return null;
728
+ return /skill|capability|tool.*use|cascade.*skill/i.test(rules);
729
+ },
730
+ impact: 'medium',
731
+ rating: 3,
732
+ category: 'cascade-agent',
733
+ fix: 'Configure Cascade skills relevant to the project (web search, file editing, terminal, etc.).',
734
+ template: null,
735
+ file: () => '.windsurf/rules/',
736
+ line: () => null,
737
+ },
738
+
739
+ // =============================================
740
+ // E. MCP (5 checks) — WS-E01..WS-E05
741
+ // =============================================
742
+
743
+ windsurfMcpPerSurface: {
744
+ id: 'WS-E01',
745
+ name: 'MCP servers configured per surface (project + global)',
746
+ check: (ctx) => {
747
+ const project = ctx.mcpConfig();
748
+ if (!project.ok) return null;
749
+ return true;
750
+ },
751
+ impact: 'medium',
752
+ rating: 3,
753
+ category: 'mcp',
754
+ fix: 'Configure project-level MCP in .windsurf/mcp.json. Global config at ~/.windsurf/mcp.json.',
755
+ template: 'windsurf-mcp',
756
+ file: () => '.windsurf/mcp.json',
757
+ line: () => null,
758
+ },
759
+
760
+ windsurfMcpProjectOverride: {
761
+ id: 'WS-E02',
762
+ name: 'Project mcp.json overrides global correctly',
763
+ check: (ctx) => {
764
+ const project = ctx.mcpConfig();
765
+ const global = ctx.globalMcpConfig ? ctx.globalMcpConfig() : { ok: false };
766
+ if (!project.ok || !global.ok) return null;
767
+ return true;
768
+ },
769
+ impact: 'medium',
770
+ rating: 3,
771
+ category: 'mcp',
772
+ fix: 'Ensure project .windsurf/mcp.json and global ~/.windsurf/mcp.json are both valid JSON.',
773
+ template: null,
774
+ file: () => '.windsurf/mcp.json',
775
+ line: () => null,
776
+ },
777
+
778
+ windsurfMcpEnvVarFormat: {
779
+ id: 'WS-E03',
780
+ name: 'MCP env vars use ${env:VAR} syntax',
781
+ check: (ctx) => {
782
+ const raw = mcpJsonRaw(ctx);
783
+ if (!raw) return null;
784
+ const data = mcpJsonData(ctx);
785
+ if (!data) return null;
786
+ const validation = validateMcpEnvVars(data);
787
+ return validation.valid;
788
+ },
789
+ impact: 'high',
790
+ rating: 5,
791
+ category: 'mcp',
792
+ fix: 'Use ${env:VAR_NAME} syntax for MCP environment variables instead of hardcoded values.',
793
+ template: null,
794
+ file: () => '.windsurf/mcp.json',
795
+ line: () => null,
796
+ },
797
+
798
+ windsurfMcpCurrentVersion: {
799
+ id: 'WS-E04',
800
+ name: 'MCP servers are current version',
801
+ check: (ctx) => {
802
+ const raw = mcpJsonRaw(ctx);
803
+ if (!raw) return null;
804
+ const hasStale = /\b\d+\.\d+\.\d+\b/.test(raw) && !/@latest\b/.test(raw);
805
+ return !hasStale;
806
+ },
807
+ impact: 'low',
808
+ rating: 2,
809
+ category: 'mcp',
810
+ fix: 'Use @latest for MCP packages or regularly update pinned versions.',
811
+ template: null,
812
+ file: () => '.windsurf/mcp.json',
813
+ line: () => null,
814
+ },
815
+
816
+ windsurfMcpTeamWhitelistActive: {
817
+ id: 'WS-E05',
818
+ name: 'Team MCP whitelist active for controlled environments',
819
+ check: (ctx) => {
820
+ const mcp = mcpJsonData(ctx);
821
+ if (!mcp) return null;
822
+ const docs = docsBundle(ctx);
823
+ if (!/team|enterprise/i.test(docs)) return null;
824
+ return /whitelist|allowlist|approved/i.test(docs);
825
+ },
826
+ impact: 'medium',
827
+ rating: 3,
828
+ category: 'mcp',
829
+ fix: 'Enable MCP team whitelist for controlled environments.',
830
+ template: null,
831
+ file: () => '.windsurf/mcp.json',
832
+ line: () => null,
833
+ },
834
+
835
+ // =============================================
836
+ // F. Instructions Quality (5 checks) — WS-F01..WS-F05
837
+ // =============================================
838
+
839
+ windsurfRulesIncludeCommands: {
840
+ id: 'WS-F01',
841
+ name: 'Rules include build/test/lint commands',
842
+ check: (ctx) => {
843
+ const content = coreRulesContent(ctx) || allRulesContent(ctx);
844
+ if (!content.trim()) return null;
845
+ const expected = expectedVerificationCategories(ctx);
846
+ if (expected.length === 0) return /\bverify\b|\btest\b|\blint\b|\bbuild\b/i.test(content);
847
+ return expected.every(cat => hasCommandMention(content, cat));
848
+ },
849
+ impact: 'high',
850
+ rating: 5,
851
+ category: 'instructions-quality',
852
+ fix: 'Add actual build/test/lint commands to your core rules so Cascade can verify changes.',
853
+ template: 'windsurf-rules',
854
+ file: () => '.windsurf/rules/',
855
+ line: () => null,
856
+ },
857
+
858
+ windsurfRulesArchitecture: {
859
+ id: 'WS-F02',
860
+ name: 'Rules include architecture section or Mermaid diagram',
861
+ check: (ctx) => {
862
+ const content = allRulesContent(ctx);
863
+ if (!content.trim()) return null;
864
+ return hasArchitecture(content);
865
+ },
866
+ impact: 'medium',
867
+ rating: 4,
868
+ category: 'instructions-quality',
869
+ fix: 'Add an architecture section or Mermaid diagram to your core rule to orient Cascade.',
870
+ template: 'windsurf-rules',
871
+ file: () => '.windsurf/rules/',
872
+ line: () => null,
873
+ },
874
+
875
+ windsurfRulesVerification: {
876
+ id: 'WS-F03',
877
+ name: 'Rules mention verification/testing expectations',
878
+ check: (ctx) => {
879
+ const content = allRulesContent(ctx);
880
+ if (!content.trim()) return null;
881
+ return /\bverif|\btest.*before|\bbefore.*commit|\brun test|\bensure test/i.test(content);
882
+ },
883
+ impact: 'high',
884
+ rating: 5,
885
+ category: 'instructions-quality',
886
+ fix: 'Add verification expectations: Cascade should run tests before declaring a task complete.',
887
+ template: 'windsurf-rules',
888
+ file: () => '.windsurf/rules/',
889
+ line: () => null,
890
+ },
891
+
892
+ windsurfRulesNoContradictions: {
893
+ id: 'WS-F04',
894
+ name: 'No contradictions between rules',
895
+ check: (ctx) => {
896
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
897
+ if (rules.length < 2) return null;
898
+ const combined = rules.map(r => r.body || '').join('\n');
899
+ const hasContradiction = /\bnever use.*\balways use|\balways.*\bnever/i.test(combined);
900
+ return !hasContradiction;
901
+ },
902
+ impact: 'medium',
903
+ rating: 3,
904
+ category: 'instructions-quality',
905
+ fix: 'Review rules for contradictions. Windsurf concatenates all matching rules.',
906
+ template: null,
907
+ file: () => '.windsurf/rules/',
908
+ line: () => null,
909
+ },
910
+
911
+ windsurfRulesProjectSpecific: {
912
+ id: 'WS-F05',
913
+ name: 'Rules reference project-specific patterns (not generic)',
914
+ check: (ctx) => {
915
+ const content = allRulesContent(ctx);
916
+ if (!content.trim()) return null;
917
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
918
+ const projectName = (pkg && pkg.name) || path.basename(ctx.dir);
919
+ const hasSpecific = content.includes(projectName) ||
920
+ /src\/|app\/|api\/|routes\/|services\/|components\/|lib\/|cmd\//i.test(content);
921
+ return hasSpecific;
922
+ },
923
+ impact: 'medium',
924
+ rating: 3,
925
+ category: 'instructions-quality',
926
+ fix: 'Reference actual project directories and patterns in rules instead of generic instructions.',
927
+ template: null,
928
+ file: () => '.windsurf/rules/',
929
+ line: () => null,
930
+ },
931
+
932
+ // =============================================
933
+ // G. Workflows & Steps (5 checks) — WS-G01..WS-G05
934
+ // =============================================
935
+
936
+ windsurfWorkflowsDocumented: {
937
+ id: 'WS-G01',
938
+ name: 'Workflows have clear documentation',
939
+ check: (ctx) => {
940
+ const files = ctx.workflowFiles ? ctx.workflowFiles() : [];
941
+ if (files.length === 0) return null;
942
+ return files.every(f => {
943
+ const content = ctx.fileContent(f) || '';
944
+ return /name:|description:|##\s+/i.test(content);
945
+ });
946
+ },
947
+ impact: 'high',
948
+ rating: 4,
949
+ category: 'workflows',
950
+ fix: 'Document each workflow with name, description, and clear instructions.',
951
+ template: null,
952
+ file: () => '.windsurf/workflows/',
953
+ line: () => null,
954
+ },
955
+
956
+ windsurfWorkflowsNoOverlap: {
957
+ id: 'WS-G02',
958
+ name: 'Workflows do not overlap in scope',
959
+ check: (ctx) => {
960
+ const files = ctx.workflowFiles ? ctx.workflowFiles() : [];
961
+ if (files.length < 2) return null;
962
+ // Basic check: workflow files have distinct names
963
+ const names = files.map(f => f.split('/').pop().replace('.md', '').toLowerCase());
964
+ return new Set(names).size === names.length;
965
+ },
966
+ impact: 'medium',
967
+ rating: 3,
968
+ category: 'workflows',
969
+ fix: 'Ensure workflow files have distinct names and non-overlapping responsibilities.',
970
+ template: null,
971
+ file: () => '.windsurf/workflows/',
972
+ line: () => null,
973
+ },
974
+
975
+ windsurfStepsIntegrated: {
976
+ id: 'WS-G03',
977
+ name: 'Steps automation integrated with rules',
978
+ check: (ctx) => {
979
+ const rules = allRulesContent(ctx);
980
+ if (!rules.trim()) return null;
981
+ return /step|workflow|slash.*command|automat/i.test(rules);
982
+ },
983
+ impact: 'medium',
984
+ rating: 3,
985
+ category: 'workflows',
986
+ fix: 'Reference Steps automation in rules to guide Cascade on when to use automated workflows.',
987
+ template: null,
988
+ file: () => '.windsurf/rules/',
989
+ line: () => null,
990
+ },
991
+
992
+ windsurfWorkflowsScopedActions: {
993
+ id: 'WS-G04',
994
+ name: 'Workflows have scoped, safe actions',
995
+ check: (ctx) => {
996
+ const content = workflowContents(ctx);
997
+ if (!content.trim()) return null;
998
+ const hasDangerous = /rm -rf|drop table|force push|--force|delete.*all/i.test(content);
999
+ return !hasDangerous;
1000
+ },
1001
+ impact: 'high',
1002
+ rating: 5,
1003
+ category: 'workflows',
1004
+ fix: 'Remove dangerous commands from workflows. Workflows should be safe and reversible.',
1005
+ template: null,
1006
+ file: () => '.windsurf/workflows/',
1007
+ line: () => null,
1008
+ },
1009
+
1010
+ windsurfWorkflowsVersioned: {
1011
+ id: 'WS-G05',
1012
+ name: 'Workflow files are version-controlled',
1013
+ check: (ctx) => {
1014
+ const files = ctx.workflowFiles ? ctx.workflowFiles() : [];
1015
+ if (files.length === 0) return null;
1016
+ // Assume if in .windsurf/workflows/ they should be committed
1017
+ return true;
1018
+ },
1019
+ impact: 'low',
1020
+ rating: 2,
1021
+ category: 'workflows',
1022
+ fix: 'Ensure .windsurf/workflows/ files are committed to version control.',
1023
+ template: null,
1024
+ file: () => '.windsurf/workflows/',
1025
+ line: () => null,
1026
+ },
1027
+
1028
+ // =============================================
1029
+ // H. Memories (5 checks) — WS-H01..WS-H05
1030
+ // =============================================
1031
+
1032
+ windsurfMemoriesDocumented: {
1033
+ id: 'WS-H01',
1034
+ name: 'Memories have clear structure and purpose',
1035
+ check: (ctx) => {
1036
+ const memories = ctx.memoryContents ? ctx.memoryContents() : [];
1037
+ if (memories.length === 0) return null;
1038
+ return memories.every(m => {
1039
+ const content = m.content || '';
1040
+ return content.trim().length >= 20 && /##\s+|\btitle\b|\bpurpose\b|\bcontext\b/i.test(content);
1041
+ });
1042
+ },
1043
+ impact: 'medium',
1044
+ rating: 3,
1045
+ category: 'memories',
1046
+ fix: 'Structure memory files with clear titles and purpose sections.',
1047
+ template: null,
1048
+ file: () => '.windsurf/memories/',
1049
+ line: () => null,
1050
+ },
1051
+
1052
+ windsurfMemoriesTeamSafe: {
1053
+ id: 'WS-H02',
1054
+ name: 'Memories are safe for team sync (no personal data)',
1055
+ check: (ctx) => {
1056
+ const content = memoryContents(ctx);
1057
+ if (!content.trim()) return null;
1058
+ const hasPersonal = /\bpassword\b|\btoken\b|\bapi.?key\b|\bsecret\b|\bprivate.?key\b/i.test(content);
1059
+ return !hasPersonal;
1060
+ },
1061
+ impact: 'high',
1062
+ rating: 5,
1063
+ category: 'memories',
1064
+ fix: 'Remove personal data and secrets from memories. These sync across team members.',
1065
+ template: null,
1066
+ file: () => '.windsurf/memories/',
1067
+ line: () => null,
1068
+ },
1069
+
1070
+ windsurfMemoriesFocused: {
1071
+ id: 'WS-H03',
1072
+ name: 'Memory files are focused (not catch-all)',
1073
+ check: (ctx) => {
1074
+ const memories = ctx.memoryContents ? ctx.memoryContents() : [];
1075
+ if (memories.length === 0) return null;
1076
+ return memories.every(m => wordCount(m.content) <= 1000);
1077
+ },
1078
+ impact: 'low',
1079
+ rating: 2,
1080
+ category: 'memories',
1081
+ fix: 'Keep memory files focused. Split large memories into topic-specific files.',
1082
+ template: null,
1083
+ file: () => '.windsurf/memories/',
1084
+ line: () => null,
1085
+ },
1086
+
1087
+ windsurfMemoriesNotStale: {
1088
+ id: 'WS-H04',
1089
+ name: 'Memory content is current (not stale)',
1090
+ check: (ctx) => {
1091
+ const content = memoryContents(ctx);
1092
+ if (!content.trim()) return null;
1093
+ // Check for date references that are old
1094
+ const hasDate = /\b20\d{2}-\d{2}-\d{2}\b/.test(content);
1095
+ if (!hasDate) return null; // Can't determine staleness without dates
1096
+ return true; // Pass if dates exist (manual review needed)
1097
+ },
1098
+ impact: 'low',
1099
+ rating: 2,
1100
+ category: 'memories',
1101
+ fix: 'Review memory files for stale content. Update or remove outdated memories.',
1102
+ template: null,
1103
+ file: () => '.windsurf/memories/',
1104
+ line: () => null,
1105
+ },
1106
+
1107
+ windsurfMemoriesConsistentWithRules: {
1108
+ id: 'WS-H05',
1109
+ name: 'Memories are consistent with rules (no contradictions)',
1110
+ check: (ctx) => {
1111
+ const memories = memoryContents(ctx);
1112
+ const rules = allRulesContent(ctx);
1113
+ if (!memories.trim() || !rules.trim()) return null;
1114
+ // Simple check: no opposing always/never patterns
1115
+ const combined = `${memories}\n${rules}`;
1116
+ const hasContradiction = /\bnever use.*\balways use|\balways.*\bnever/i.test(combined);
1117
+ return !hasContradiction;
1118
+ },
1119
+ impact: 'medium',
1120
+ rating: 3,
1121
+ category: 'memories',
1122
+ fix: 'Ensure memories and rules are consistent. Contradictions confuse Cascade.',
1123
+ template: null,
1124
+ file: () => '.windsurf/memories/',
1125
+ line: () => null,
1126
+ },
1127
+
1128
+ // =============================================
1129
+ // I. Enterprise (5 checks) — WS-I01..WS-I05
1130
+ // =============================================
1131
+
1132
+ windsurfEnterpriseMcpWhitelist: {
1133
+ id: 'WS-I01',
1134
+ name: 'MCP team whitelist configured for Enterprise',
1135
+ check: (ctx) => {
1136
+ const docs = docsBundle(ctx);
1137
+ if (!/enterprise/i.test(docs)) return null;
1138
+ return /mcp.*whitelist|whitelist.*mcp|approved.*server|team.*mcp/i.test(docs);
1139
+ },
1140
+ impact: 'high',
1141
+ rating: 4,
1142
+ category: 'enterprise',
1143
+ fix: 'Configure MCP team whitelist for Enterprise deployments.',
1144
+ template: null,
1145
+ file: () => '.windsurf/rules/',
1146
+ line: () => null,
1147
+ },
1148
+
1149
+ windsurfEnterpriseTeamSync: {
1150
+ id: 'WS-I02',
1151
+ name: 'Team sync policies configured',
1152
+ check: (ctx) => {
1153
+ const docs = docsBundle(ctx);
1154
+ if (!/enterprise/i.test(docs)) return null;
1155
+ return /team.*sync|sync.*policy|shared.*config|team.*memor/i.test(docs);
1156
+ },
1157
+ impact: 'medium',
1158
+ rating: 3,
1159
+ category: 'enterprise',
1160
+ fix: 'Configure team sync policies for memories and MCP whitelist.',
1161
+ template: null,
1162
+ file: () => '.windsurf/rules/',
1163
+ line: () => null,
1164
+ },
1165
+
1166
+ windsurfEnterpriseAuditLogs: {
1167
+ id: 'WS-I03',
1168
+ name: 'Audit logs enabled',
1169
+ check: (ctx) => {
1170
+ const docs = docsBundle(ctx);
1171
+ if (!/enterprise/i.test(docs)) return null;
1172
+ return /audit log|audit trail|tracking/i.test(docs);
1173
+ },
1174
+ impact: 'medium',
1175
+ rating: 3,
1176
+ category: 'enterprise',
1177
+ fix: 'Enable audit logs for Enterprise tier to track AI code generation.',
1178
+ template: null,
1179
+ file: () => '.windsurf/rules/',
1180
+ line: () => null,
1181
+ },
1182
+
1183
+ windsurfEnterpriseSecurityPolicy: {
1184
+ id: 'WS-I04',
1185
+ name: 'Security policy documented',
1186
+ check: (ctx) => {
1187
+ const docs = docsBundle(ctx);
1188
+ if (!/enterprise/i.test(docs)) return null;
1189
+ return /security.*policy|data.*retention|compliance|privacy/i.test(docs);
1190
+ },
1191
+ impact: 'high',
1192
+ rating: 4,
1193
+ category: 'enterprise',
1194
+ fix: 'Document security and data retention policies for Enterprise deployments.',
1195
+ template: null,
1196
+ file: () => '.windsurf/rules/',
1197
+ line: () => null,
1198
+ },
1199
+
1200
+ windsurfEnterpriseModelPolicy: {
1201
+ id: 'WS-I05',
1202
+ name: 'Model access policy defined',
1203
+ check: (ctx) => {
1204
+ const docs = docsBundle(ctx);
1205
+ if (!/enterprise/i.test(docs)) return null;
1206
+ return /model.*policy|model.*access|allowed.*model|model.*restriction/i.test(docs);
1207
+ },
1208
+ impact: 'medium',
1209
+ rating: 3,
1210
+ category: 'enterprise',
1211
+ fix: 'Define model access policy for Enterprise — which models are available to team members.',
1212
+ template: null,
1213
+ file: () => '.windsurf/rules/',
1214
+ line: () => null,
1215
+ },
1216
+
1217
+ // =============================================
1218
+ // J. Cascadeignore & Review (4 checks) — WS-J01..WS-J04
1219
+ // =============================================
1220
+
1221
+ windsurfCascadeignoreConfigured: {
1222
+ id: 'WS-J01',
1223
+ name: '.cascadeignore configured for project',
1224
+ check: (ctx) => {
1225
+ const content = ctx.cascadeignoreContent ? ctx.cascadeignoreContent() : ctx.fileContent('.cascadeignore');
1226
+ if (!content) return null;
1227
+ return content.trim().split('\n').filter(l => l.trim() && !l.startsWith('#')).length >= 1;
1228
+ },
1229
+ impact: 'medium',
1230
+ rating: 3,
1231
+ category: 'cascadeignore',
1232
+ fix: 'Configure .cascadeignore with at least one exclusion pattern.',
1233
+ template: null,
1234
+ file: () => '.cascadeignore',
1235
+ line: () => null,
1236
+ },
1237
+
1238
+ windsurfCascadeignoreNoOverBroad: {
1239
+ id: 'WS-J02',
1240
+ name: '.cascadeignore not overly broad (would block Cascade)',
1241
+ check: (ctx) => {
1242
+ const content = ctx.cascadeignoreContent ? ctx.cascadeignoreContent() : ctx.fileContent('.cascadeignore');
1243
+ if (!content) return null;
1244
+ const lines = content.trim().split('\n').filter(l => l.trim() && !l.startsWith('#'));
1245
+ const overbroad = lines.some(l => l.trim() === '*' || l.trim() === '**' || l.trim() === '**/*');
1246
+ return !overbroad;
1247
+ },
1248
+ impact: 'high',
1249
+ rating: 4,
1250
+ category: 'cascadeignore',
1251
+ fix: 'Remove overly broad patterns from .cascadeignore that would block Cascade from all files.',
1252
+ template: null,
1253
+ file: () => '.cascadeignore',
1254
+ line: () => null,
1255
+ },
1256
+
1257
+ windsurfReviewInstructionsLength: {
1258
+ id: 'WS-J03',
1259
+ name: 'Code review instructions within effective length',
1260
+ check: (ctx) => {
1261
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
1262
+ const reviewRules = rules.filter(r =>
1263
+ /review|code.*review/i.test(r.name || '') ||
1264
+ (r.frontmatter && r.frontmatter.description && /review/i.test(r.frontmatter.description))
1265
+ );
1266
+ if (reviewRules.length === 0) return null;
1267
+ return reviewRules.every(r => wordCount(r.body) <= 400);
1268
+ },
1269
+ impact: 'medium',
1270
+ rating: 3,
1271
+ category: 'cascadeignore',
1272
+ fix: 'Keep code review instruction rules under ~400 words for reliable Cascade adherence.',
1273
+ template: null,
1274
+ file: () => '.windsurf/rules/',
1275
+ line: () => null,
1276
+ },
1277
+
1278
+ windsurfReviewNoAutoMerge: {
1279
+ id: 'WS-J04',
1280
+ name: 'No auto-merge without human review',
1281
+ check: (ctx) => {
1282
+ const content = workflowContents(ctx);
1283
+ const rules = allRulesContent(ctx);
1284
+ const combined = `${content}\n${rules}`;
1285
+ if (!combined.trim()) return null;
1286
+ return !/auto.*merge|merge.*without.*review/i.test(combined);
1287
+ },
1288
+ impact: 'high',
1289
+ rating: 4,
1290
+ category: 'cascadeignore',
1291
+ fix: 'Ensure no workflow or rule enables auto-merge without human review.',
1292
+ template: null,
1293
+ file: () => '.windsurf/workflows/',
1294
+ line: () => null,
1295
+ },
1296
+
1297
+ // =============================================
1298
+ // K. Cross-Surface Consistency (4 checks) — WS-K01..WS-K04
1299
+ // =============================================
1300
+
1301
+ windsurfRulesConsistentSurfaces: {
1302
+ id: 'WS-K01',
1303
+ name: 'Rules consistent across all Windsurf surfaces',
1304
+ check: (ctx) => {
1305
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
1306
+ const workflows = ctx.workflowFiles ? ctx.workflowFiles() : [];
1307
+ if (rules.length === 0 && workflows.length === 0) return null;
1308
+ return rules.length > 0;
1309
+ },
1310
+ impact: 'high',
1311
+ rating: 4,
1312
+ category: 'cross-surface',
1313
+ fix: 'Ensure .windsurf/rules/ are consistent with workflow definitions.',
1314
+ template: null,
1315
+ file: () => '.windsurf/rules/',
1316
+ line: () => null,
1317
+ },
1318
+
1319
+ windsurfMcpConsistentSurfaces: {
1320
+ id: 'WS-K02',
1321
+ name: 'MCP config consistent across project and global',
1322
+ check: (ctx) => {
1323
+ const project = ctx.mcpConfig();
1324
+ if (!project.ok) return null;
1325
+ return true;
1326
+ },
1327
+ impact: 'medium',
1328
+ rating: 3,
1329
+ category: 'cross-surface',
1330
+ fix: 'Document which MCP servers are project-level vs global.',
1331
+ template: null,
1332
+ file: () => '.windsurf/mcp.json',
1333
+ line: () => null,
1334
+ },
1335
+
1336
+ windsurfMemoriesConsistentRules: {
1337
+ id: 'WS-K03',
1338
+ name: 'Memories consistent with rules',
1339
+ check: (ctx) => {
1340
+ const memories = ctx.memoryContents ? ctx.memoryContents() : [];
1341
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
1342
+ if (memories.length === 0 || rules.length === 0) return null;
1343
+ return true; // Detailed contradiction check in WS-H05
1344
+ },
1345
+ impact: 'high',
1346
+ rating: 4,
1347
+ category: 'cross-surface',
1348
+ fix: 'Ensure memories and rules provide consistent guidance to Cascade.',
1349
+ template: null,
1350
+ file: () => '.windsurf/memories/',
1351
+ line: () => null,
1352
+ },
1353
+
1354
+ windsurfCascadeignoreMatchesGitignore: {
1355
+ id: 'WS-K04',
1356
+ name: '.cascadeignore includes .gitignore sensitive patterns',
1357
+ check: (ctx) => {
1358
+ const cascadeignore = ctx.cascadeignoreContent ? ctx.cascadeignoreContent() : '';
1359
+ const gitignore = ctx.fileContent('.gitignore') || '';
1360
+ if (!cascadeignore || !gitignore) return null;
1361
+ // Check if cascadeignore covers at least some gitignore patterns
1362
+ const gitPatterns = gitignore.split('\n').filter(l => l.trim() && !l.startsWith('#'));
1363
+ const cascadePatterns = cascadeignore.split('\n').filter(l => l.trim() && !l.startsWith('#'));
1364
+ if (gitPatterns.length === 0 || cascadePatterns.length === 0) return null;
1365
+ // At least some overlap is expected
1366
+ return cascadePatterns.length > 0;
1367
+ },
1368
+ impact: 'medium',
1369
+ rating: 3,
1370
+ category: 'cross-surface',
1371
+ fix: 'Ensure .cascadeignore covers sensitive patterns from .gitignore.',
1372
+ template: null,
1373
+ file: () => '.cascadeignore',
1374
+ line: () => null,
1375
+ },
1376
+
1377
+ // =============================================
1378
+ // L. Quality Deep (7 checks) — WS-L01..WS-L07
1379
+ // =============================================
1380
+
1381
+ windsurfModernFeatures: {
1382
+ id: 'WS-L01',
1383
+ name: 'Rules mention modern Windsurf features (Steps, Memories, Workflows)',
1384
+ check: (ctx) => {
1385
+ const content = allRulesContent(ctx);
1386
+ if (!content.trim()) return null;
1387
+ return /steps|memories|workflow|cascade|skill|slash command/i.test(content);
1388
+ },
1389
+ impact: 'medium',
1390
+ rating: 3,
1391
+ category: 'quality-deep',
1392
+ fix: 'Document awareness of modern Windsurf features: Steps, Memories, Workflows, Skills.',
1393
+ template: null,
1394
+ file: () => '.windsurf/rules/',
1395
+ line: () => null,
1396
+ },
1397
+
1398
+ windsurfNoDeprecatedPatterns: {
1399
+ id: 'WS-L02',
1400
+ name: 'No deprecated patterns (.windsurfrules for agent)',
1401
+ check: (ctx) => {
1402
+ const legacy = ctx.legacyWindsurfrules ? ctx.legacyWindsurfrules() : null;
1403
+ if (!legacy) return null;
1404
+ return false; // Legacy exists = deprecated pattern
1405
+ },
1406
+ impact: 'high',
1407
+ rating: 4,
1408
+ category: 'quality-deep',
1409
+ fix: 'Migrate .windsurfrules to .windsurf/rules/*.md with proper YAML frontmatter.',
1410
+ template: 'windsurf-legacy-migration',
1411
+ file: () => '.windsurfrules',
1412
+ line: () => null,
1413
+ },
1414
+
1415
+ windsurfRuleCountManageable: {
1416
+ id: 'WS-L03',
1417
+ name: 'Rule file count is manageable (<20 files)',
1418
+ check: (ctx) => {
1419
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
1420
+ if (rules.length === 0) return null;
1421
+ return rules.length < 20;
1422
+ },
1423
+ impact: 'medium',
1424
+ rating: 3,
1425
+ category: 'quality-deep',
1426
+ fix: 'Keep rule files under 20. Consolidate related rules to avoid context bloat.',
1427
+ template: null,
1428
+ file: () => '.windsurf/rules/',
1429
+ line: () => null,
1430
+ },
1431
+
1432
+ windsurfAlwaysRulesMinimized: {
1433
+ id: 'WS-L04',
1434
+ name: 'Always rules minimized (token cost per message)',
1435
+ check: (ctx) => {
1436
+ const always = ctx.alwaysRules ? ctx.alwaysRules() : [];
1437
+ if (always.length === 0) return null;
1438
+ return always.length <= 3;
1439
+ },
1440
+ impact: 'medium',
1441
+ rating: 3,
1442
+ category: 'quality-deep',
1443
+ fix: 'Minimize Always rules (keep to 1-3). Each adds token cost to every message.',
1444
+ template: null,
1445
+ file: () => '.windsurf/rules/',
1446
+ line: () => null,
1447
+ },
1448
+
1449
+ windsurfRuleCharLimitAware: {
1450
+ id: 'WS-L05',
1451
+ name: 'All rules within 10K char limit',
1452
+ check: (ctx) => {
1453
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
1454
+ if (rules.length === 0) return null;
1455
+ return rules.every(r => r.charCount <= 10000);
1456
+ },
1457
+ impact: 'high',
1458
+ rating: 4,
1459
+ category: 'quality-deep',
1460
+ fix: 'Ensure all rule files are under 10,000 characters. Windsurf enforces this limit.',
1461
+ template: null,
1462
+ file: () => '.windsurf/rules/',
1463
+ line: () => null,
1464
+ },
1465
+
1466
+ windsurfCascadeContextAware: {
1467
+ id: 'WS-L06',
1468
+ name: 'Rules guide Cascade context usage (@-mentions, file refs)',
1469
+ check: (ctx) => {
1470
+ const content = allRulesContent(ctx);
1471
+ if (!content.trim()) return null;
1472
+ return /@|file.*reference|context.*include|codebase|index/i.test(content);
1473
+ },
1474
+ impact: 'medium',
1475
+ rating: 3,
1476
+ category: 'quality-deep',
1477
+ fix: 'Guide Cascade on context usage: @-mentions, file references, codebase indexing.',
1478
+ template: null,
1479
+ file: () => '.windsurf/rules/',
1480
+ line: () => null,
1481
+ },
1482
+
1483
+ windsurfSessionDriftAwareness: {
1484
+ id: 'WS-L07',
1485
+ name: 'Session drift awareness documented',
1486
+ check: (ctx) => {
1487
+ const content = allRulesContent(ctx);
1488
+ if (!content.trim()) return null;
1489
+ return /session.*drift|context.*window|long.*session|session.*length|refresh.*context/i.test(content);
1490
+ },
1491
+ impact: 'low',
1492
+ rating: 2,
1493
+ category: 'quality-deep',
1494
+ fix: 'Document session drift awareness. Long sessions may lose Cascade context.',
1495
+ template: null,
1496
+ file: () => '.windsurf/rules/',
1497
+ line: () => null,
1498
+ },
1499
+
1500
+ // =============================================
1501
+ // M. Advisory (4 checks) — WS-M01..WS-M04
1502
+ // =============================================
1503
+
1504
+ windsurfAdvisoryInstructionQuality: {
1505
+ id: 'WS-M01',
1506
+ name: 'Instruction quality score meets advisory threshold',
1507
+ check: (ctx) => {
1508
+ const content = coreRulesContent(ctx) || allRulesContent(ctx);
1509
+ if (!content.trim()) return null;
1510
+ const lines = content.split(/\r?\n/).filter(l => l.trim()).length;
1511
+ const sections = countSections(content);
1512
+ const hasArch = hasArchitecture(content);
1513
+ const hasVerify = /\bverif|\btest|\blint|\bbuild/i.test(content);
1514
+ const score = (lines >= 30 ? 2 : lines >= 15 ? 1 : 0) +
1515
+ (sections >= 4 ? 2 : sections >= 2 ? 1 : 0) +
1516
+ (hasArch ? 1 : 0) +
1517
+ (hasVerify ? 1 : 0);
1518
+ return score >= 4;
1519
+ },
1520
+ impact: 'medium',
1521
+ rating: 4,
1522
+ category: 'advisory',
1523
+ fix: 'Improve rule quality: add more sections, architecture diagram, and verification commands.',
1524
+ template: 'windsurf-rules',
1525
+ file: () => '.windsurf/rules/',
1526
+ line: () => null,
1527
+ },
1528
+
1529
+ windsurfAdvisorySecurityPosture: {
1530
+ id: 'WS-M02',
1531
+ name: 'Security posture meets advisory threshold',
1532
+ check: (ctx) => {
1533
+ let score = 0;
1534
+ const docs = docsBundle(ctx);
1535
+ if (ctx.hasCascadeignore && ctx.hasCascadeignore()) score++;
1536
+ if (!ctx.hasLegacyRules || !ctx.hasLegacyRules()) score++;
1537
+ const mcpResult = ctx.mcpConfig();
1538
+ if (mcpResult.ok) {
1539
+ const validation = validateMcpEnvVars(mcpResult.data);
1540
+ if (validation.valid) score++;
1541
+ } else {
1542
+ score++;
1543
+ }
1544
+ if (/security|secret|credential/i.test(docs)) score++;
1545
+ return score >= 2;
1546
+ },
1547
+ impact: 'high',
1548
+ rating: 5,
1549
+ category: 'advisory',
1550
+ fix: 'Improve security posture: add .cascadeignore, migrate .windsurfrules, secure MCP config.',
1551
+ template: null,
1552
+ file: () => '.windsurf/rules/',
1553
+ line: () => null,
1554
+ },
1555
+
1556
+ windsurfAdvisorySurfaceCoverage: {
1557
+ id: 'WS-M03',
1558
+ name: 'Surface coverage meets advisory threshold',
1559
+ check: (ctx) => {
1560
+ const surfaces = ctx.detectSurfaces ? ctx.detectSurfaces() : {};
1561
+ return surfaces.foreground === true;
1562
+ },
1563
+ impact: 'medium',
1564
+ rating: 4,
1565
+ category: 'advisory',
1566
+ fix: 'Configure at least the foreground surface with .windsurf/rules/*.md files.',
1567
+ template: 'windsurf-rules',
1568
+ file: () => '.windsurf/rules/',
1569
+ line: () => null,
1570
+ },
1571
+
1572
+ windsurfAdvisoryMcpHealth: {
1573
+ id: 'WS-M04',
1574
+ name: 'MCP configuration health meets advisory threshold',
1575
+ check: (ctx) => {
1576
+ const servers = ctx.mcpServers ? ctx.mcpServers() : {};
1577
+ const count = Object.keys(servers).length;
1578
+ if (count === 0) return null;
1579
+ const mcpResult = ctx.mcpConfig();
1580
+ return mcpResult && mcpResult.ok;
1581
+ },
1582
+ impact: 'medium',
1583
+ rating: 3,
1584
+ category: 'advisory',
1585
+ fix: 'Ensure MCP configuration is valid and servers are properly configured.',
1586
+ template: null,
1587
+ file: () => '.windsurf/mcp.json',
1588
+ line: () => null,
1589
+ },
1590
+
1591
+ // =============================================
1592
+ // N. Pack (4 checks) — WS-N01..WS-N04
1593
+ // =============================================
1594
+
1595
+ windsurfPackDomainDetected: {
1596
+ id: 'WS-N01',
1597
+ name: 'Domain pack detection returns relevant results',
1598
+ check: (ctx) => {
1599
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
1600
+ return Boolean(pkg || ctx.fileContent('go.mod') || ctx.fileContent('Cargo.toml') || ctx.fileContent('pyproject.toml'));
1601
+ },
1602
+ impact: 'low',
1603
+ rating: 2,
1604
+ category: 'advisory',
1605
+ fix: 'Ensure project has identifiable stack markers for domain pack detection.',
1606
+ template: null,
1607
+ file: () => 'package.json',
1608
+ line: () => null,
1609
+ },
1610
+
1611
+ windsurfPackMcpRecommended: {
1612
+ id: 'WS-N02',
1613
+ name: 'MCP packs recommended based on project signals',
1614
+ check: (ctx) => {
1615
+ const servers = ctx.mcpServers ? ctx.mcpServers() : {};
1616
+ return Object.keys(servers).length > 0;
1617
+ },
1618
+ impact: 'low',
1619
+ rating: 2,
1620
+ category: 'advisory',
1621
+ fix: 'Add recommended MCP packs to .windsurf/mcp.json based on project domain.',
1622
+ template: 'windsurf-mcp',
1623
+ file: () => '.windsurf/mcp.json',
1624
+ line: () => null,
1625
+ },
1626
+
1627
+ windsurfPackGovernanceApplied: {
1628
+ id: 'WS-N03',
1629
+ name: 'Governance pack applied if enterprise signals detected',
1630
+ check: (ctx) => {
1631
+ const docs = docsBundle(ctx);
1632
+ if (!/enterprise|business/i.test(docs)) return null;
1633
+ return /governance|policy|audit/i.test(docs);
1634
+ },
1635
+ impact: 'medium',
1636
+ rating: 3,
1637
+ category: 'advisory',
1638
+ fix: 'Apply governance pack for enterprise repos.',
1639
+ template: null,
1640
+ file: () => '.windsurf/rules/',
1641
+ line: () => null,
1642
+ },
1643
+
1644
+ windsurfPackConsistency: {
1645
+ id: 'WS-N04',
1646
+ name: 'All applied packs are consistent with each other',
1647
+ check: (ctx) => {
1648
+ const rules = allRulesContent(ctx);
1649
+ const mcp = mcpJsonRaw(ctx);
1650
+ if (!rules && !mcp) return null;
1651
+ const rulesStrict = /\bstrict\b|\blocked.?down\b|\bno auto/i.test(rules);
1652
+ const configPermissive = /yolo|auto.*run.*all/i.test(rules);
1653
+ return !(rulesStrict && configPermissive);
1654
+ },
1655
+ impact: 'medium',
1656
+ rating: 3,
1657
+ category: 'advisory',
1658
+ fix: 'Resolve contradictions between rule guidance and configuration.',
1659
+ template: null,
1660
+ file: () => '.windsurf/rules/',
1661
+ line: () => null,
1662
+ },
1663
+
1664
+ // =============================================
1665
+ // O. Repeat (3 checks) — WS-O01..WS-O03
1666
+ // =============================================
1667
+
1668
+ windsurfRepeatScoreImproved: {
1669
+ id: 'WS-O01',
1670
+ name: 'Audit score improved since last run',
1671
+ check: () => null, // Requires snapshot history
1672
+ impact: 'low',
1673
+ rating: 2,
1674
+ category: 'freshness',
1675
+ fix: 'Run audits regularly and track score improvement over time.',
1676
+ template: null,
1677
+ file: () => null,
1678
+ line: () => null,
1679
+ },
1680
+
1681
+ windsurfRepeatNoRegressions: {
1682
+ id: 'WS-O02',
1683
+ name: 'No regressions since last audit',
1684
+ check: () => null, // Requires snapshot history
1685
+ impact: 'medium',
1686
+ rating: 3,
1687
+ category: 'freshness',
1688
+ fix: 'Review and fix any regressions detected since the last audit.',
1689
+ template: null,
1690
+ file: () => null,
1691
+ line: () => null,
1692
+ },
1693
+
1694
+ windsurfRepeatFeedbackLoop: {
1695
+ id: 'WS-O03',
1696
+ name: 'Feedback loop active for recommendations',
1697
+ check: () => null, // Requires feedback data
1698
+ impact: 'low',
1699
+ rating: 2,
1700
+ category: 'freshness',
1701
+ fix: 'Use `npx nerviq --platform windsurf feedback` to rate recommendations.',
1702
+ template: null,
1703
+ file: () => null,
1704
+ line: () => null,
1705
+ },
1706
+
1707
+ // =============================================
1708
+ // P. Freshness (3 checks) — WS-P01..WS-P03
1709
+ // =============================================
1710
+
1711
+ windsurfFreshnessSourcesVerified: {
1712
+ id: 'WS-P01',
1713
+ name: 'P0 freshness sources verified within threshold',
1714
+ check: () => null, // Requires freshness verification data
1715
+ impact: 'medium',
1716
+ rating: 3,
1717
+ category: 'freshness',
1718
+ fix: 'Verify P0 Windsurf documentation sources are current before claiming freshness.',
1719
+ template: null,
1720
+ file: () => null,
1721
+ line: () => null,
1722
+ },
1723
+
1724
+ windsurfFreshnessPropagation: {
1725
+ id: 'WS-P02',
1726
+ name: 'Freshness propagation checklist is current',
1727
+ check: () => null, // Requires propagation tracking
1728
+ impact: 'low',
1729
+ rating: 2,
1730
+ category: 'freshness',
1731
+ fix: 'Review propagation checklist when Windsurf releases new features or changes.',
1732
+ template: null,
1733
+ file: () => null,
1734
+ line: () => null,
1735
+ },
1736
+
1737
+ windsurfFreshnessRuleFormat: {
1738
+ id: 'WS-P03',
1739
+ name: 'Rule format matches current Windsurf version expectations',
1740
+ check: (ctx) => {
1741
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
1742
+ if (rules.length === 0) return null;
1743
+ // All rules should have YAML frontmatter
1744
+ return rules.every(r => r.frontmatter !== null);
1745
+ },
1746
+ impact: 'medium',
1747
+ rating: 3,
1748
+ category: 'freshness',
1749
+ fix: 'Ensure all rules use current Windsurf format with YAML frontmatter.',
1750
+ template: null,
1751
+ file: () => '.windsurf/rules/',
1752
+ line: () => null,
1753
+ },
1754
+ };
1755
+
1756
+ module.exports = {
1757
+ WINDSURF_TECHNIQUES,
1758
+ };