@nerviq/cli 0.9.2 → 0.9.4

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.
@@ -1,33 +1,35 @@
1
1
  /**
2
2
  * Windsurf techniques module — CHECK CATALOG
3
3
  *
4
- * 82 checks across 16 categories:
4
+ * 84 checks across 16 categories:
5
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
6
  * v0.5 (55): G. Workflows & Steps(5), H. Memories(5), I. Enterprise(5)
7
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)
8
+ * CP-08 (84): M. Advisory(4), N. Pack(4), O. Repeat(3), P. Freshness(3)
9
9
  *
10
10
  * Each check: { id, name, check(ctx), impact, rating, category, fix, template, file(), line() }
11
11
  *
12
12
  * Windsurf key differences from Cursor:
13
13
  * - Instructions: .windsurf/rules/*.md (Markdown + YAML frontmatter, NOT MDC)
14
14
  * - Legacy: .windsurfrules (like .cursorrules)
15
- * - 4 activation modes: Always, Auto, Agent-Requested, Manual
15
+ * - 4 activation modes: always_on, glob, model_decision, manual
16
16
  * - Agent: Cascade (autonomous agent)
17
- * - Memories system (team-syncable)
17
+ * - Memories system (workspace-scoped, local to the current workspace)
18
18
  * - Workflows -> Slash commands
19
- * - 10K char rule limit
19
+ * - 12K char limit for modern rules/workflows; 6K for legacy/global rules
20
20
  * - MCP with team whitelist
21
21
  * - cascadeignore (gitignore for Cascade)
22
- * - No background agents
22
+ * - No background agents or supported CLI/headless mode
23
23
  * - Check ID prefix: WS-
24
24
  */
25
25
 
26
+ const fs = require('fs');
26
27
  const os = require('os');
27
28
  const path = require('path');
28
29
  const { WindsurfProjectContext } = require('./context');
29
30
  const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
30
- const { validateWindsurfFrontmatter, validateMcpEnvVars } = require('./config-parser');
31
+ const { attachSourceUrls } = require('../source-urls');
32
+ const { tryParseJson, validateMcpEnvVars } = require('./config-parser');
31
33
 
32
34
  // ─── Shared helpers ─────────────────────────────────────────────────────────
33
35
 
@@ -87,12 +89,71 @@ function coreRulesContent(ctx) {
87
89
  }
88
90
 
89
91
  function mcpJsonRaw(ctx) {
90
- return ctx.fileContent('.windsurf/mcp.json') || '';
92
+ const configPath = windsurfMcpConfigPath();
93
+ try {
94
+ return fs.readFileSync(configPath, 'utf8');
95
+ } catch {
96
+ return '';
97
+ }
91
98
  }
92
99
 
93
100
  function mcpJsonData(ctx) {
94
- const result = ctx.mcpConfig();
95
- return result && result.ok ? result.data : null;
101
+ const raw = mcpJsonRaw(ctx);
102
+ if (!raw) return null;
103
+ const result = tryParseJson(raw);
104
+ return result.ok ? result.data : null;
105
+ }
106
+
107
+ function windsurfMcpConfigPath() {
108
+ return path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
109
+ }
110
+
111
+ function normalizeWindsurfTrigger(frontmatter) {
112
+ if (!frontmatter) return 'always_on';
113
+
114
+ const trigger = String(frontmatter.trigger || '').trim().toLowerCase();
115
+ if (trigger === 'always_on' || trigger === 'always') return 'always_on';
116
+ if (trigger === 'glob' || trigger === 'auto' || trigger === 'auto_attached') return 'glob';
117
+ if (trigger === 'model_decision' || trigger === 'agent_requested' || trigger === 'agent-requested') return 'model_decision';
118
+ if (trigger === 'manual') return 'manual';
119
+
120
+ const hasGlob = Boolean(frontmatter.glob) ||
121
+ (Array.isArray(frontmatter.globs) ? frontmatter.globs.length > 0 : Boolean(frontmatter.globs));
122
+ const hasDescription = Boolean(frontmatter.description && String(frontmatter.description).trim());
123
+
124
+ if (hasGlob) return 'glob';
125
+ if (hasDescription) return 'model_decision';
126
+ return 'always_on';
127
+ }
128
+
129
+ function isValidWindsurfFrontmatter(frontmatter) {
130
+ if (!frontmatter || typeof frontmatter !== 'object') return false;
131
+
132
+ const validFields = new Set(['trigger', 'description', 'glob', 'globs', 'name']);
133
+ const validTriggers = new Set([
134
+ 'always_on', 'always',
135
+ 'glob', 'auto', 'auto_attached',
136
+ 'model_decision', 'agent_requested', 'agent-requested',
137
+ 'manual',
138
+ ]);
139
+
140
+ for (const key of Object.keys(frontmatter)) {
141
+ if (!validFields.has(key)) return false;
142
+ }
143
+
144
+ if (frontmatter.trigger && !validTriggers.has(String(frontmatter.trigger).trim().toLowerCase())) {
145
+ return false;
146
+ }
147
+
148
+ if (frontmatter.globs !== undefined && !Array.isArray(frontmatter.globs) && typeof frontmatter.globs !== 'string') {
149
+ return false;
150
+ }
151
+
152
+ if (frontmatter.glob !== undefined && typeof frontmatter.glob !== 'string') {
153
+ return false;
154
+ }
155
+
156
+ return true;
96
157
  }
97
158
 
98
159
  function docsBundle(ctx) {
@@ -154,11 +215,33 @@ function workflowContents(ctx) {
154
215
  return files.map(f => ctx.fileContent(f) || '').join('\n');
155
216
  }
156
217
 
218
+ function ciWorkflowContents(ctx) {
219
+ const files = ctx.ciWorkflowFiles ? ctx.ciWorkflowFiles() : [];
220
+ return files.map(f => ctx.fileContent(f) || '').join('\n');
221
+ }
222
+
157
223
  function wordCount(text) {
158
224
  if (!text) return 0;
159
225
  return text.split(/\s+/).filter(Boolean).length;
160
226
  }
161
227
 
228
+ function repoFilesOverLineThreshold(ctx, threshold = 300) {
229
+ const oversized = [];
230
+
231
+ for (const filePath of ctx.files || []) {
232
+ if (/^(node_modules|dist|build|coverage|\.git|\.next|vendor|out)\//i.test(filePath)) continue;
233
+ if (/\.(png|jpg|jpeg|gif|webp|ico|pdf|zip|gz|lock|woff2?)$/i.test(filePath)) continue;
234
+
235
+ const content = ctx.fileContent(filePath);
236
+ if (!content) continue;
237
+
238
+ const lineCount = content.split(/\r?\n/).length;
239
+ if (lineCount > threshold) oversized.push({ filePath, lineCount });
240
+ }
241
+
242
+ return oversized.sort((a, b) => b.lineCount - a.lineCount);
243
+ }
244
+
162
245
  // ─── WINDSURF_TECHNIQUES ──────────────────────────────────────────────────────
163
246
 
164
247
  const WINDSURF_TECHNIQUES = {
@@ -201,16 +284,16 @@ const WINDSURF_TECHNIQUES = {
201
284
 
202
285
  windsurfAlwaysRuleExists: {
203
286
  id: 'WS-A03',
204
- name: 'At least one rule has trigger: always for Cascade',
287
+ name: 'At least one rule is always_on for Cascade',
205
288
  check: (ctx) => {
206
289
  const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
207
290
  if (rules.length === 0) return null;
208
- return rules.some(r => r.ruleType === 'always');
291
+ return rules.some((rule) => normalizeWindsurfTrigger(rule.frontmatter) === 'always_on');
209
292
  },
210
293
  impact: 'high',
211
294
  rating: 5,
212
295
  category: 'rules',
213
- fix: 'Add trigger: always to your core rule file so Cascade always sees instructions.',
296
+ fix: 'Add a focused `trigger: always_on` rule for core guidance. Files without frontmatter also default to always_on, so make that choice explicit.',
214
297
  template: 'windsurf-rules',
215
298
  file: () => '.windsurf/rules/',
216
299
  line: () => null,
@@ -222,16 +305,12 @@ const WINDSURF_TECHNIQUES = {
222
305
  check: (ctx) => {
223
306
  const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
224
307
  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
- });
308
+ return rules.every((rule) => isValidWindsurfFrontmatter(rule.frontmatter));
230
309
  },
231
310
  impact: 'high',
232
311
  rating: 4,
233
312
  category: 'rules',
234
- fix: 'Fix YAML frontmatter in rule .md files. Use: trigger, description, globs, name fields.',
313
+ fix: 'Fix YAML frontmatter in rule files. Use current Windsurf triggers (`always_on`, `glob`, `model_decision`, `manual`) plus `glob`/`globs`, `description`, and `name` as needed.',
235
314
  template: null,
236
315
  file: () => '.windsurf/rules/',
237
316
  line: () => 1,
@@ -239,16 +318,16 @@ const WINDSURF_TECHNIQUES = {
239
318
 
240
319
  windsurfRulesUnder10kChars: {
241
320
  id: 'WS-A05',
242
- name: 'Rules under 10K character limit per file',
321
+ name: 'Modern rule files stay under the 12K character limit',
243
322
  check: (ctx) => {
244
323
  const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
245
324
  if (rules.length === 0) return null;
246
- return rules.every(r => !r.overLimit);
325
+ return rules.every((rule) => (rule.charCount || 0) <= 12000);
247
326
  },
248
327
  impact: 'high',
249
328
  rating: 4,
250
329
  category: 'rules',
251
- fix: 'Split rules over 10K characters into multiple focused files. Windsurf enforces a 10K char limit per rule.',
330
+ fix: 'Split rule files before they approach 12,000 characters. Windsurf silently truncates modern rules after 12K with no warning.',
252
331
  template: null,
253
332
  file: () => '.windsurf/rules/',
254
333
  line: () => null,
@@ -313,11 +392,12 @@ const WINDSURF_TECHNIQUES = {
313
392
 
314
393
  windsurfAgentRequestedDescriptions: {
315
394
  id: 'WS-A09',
316
- name: 'Agent-Requested rules have precise descriptions',
395
+ name: 'model_decision rules have precise descriptions',
317
396
  check: (ctx) => {
318
- const agentRules = ctx.agentRequestedRules ? ctx.agentRequestedRules() : [];
319
- if (agentRules.length === 0) return null;
320
- return agentRules.every(r => {
397
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
398
+ const modelDecisionRules = rules.filter((rule) => normalizeWindsurfTrigger(rule.frontmatter) === 'model_decision');
399
+ if (modelDecisionRules.length === 0) return null;
400
+ return modelDecisionRules.every((r) => {
321
401
  const desc = r.frontmatter && r.frontmatter.description;
322
402
  return desc && String(desc).trim().length >= 15;
323
403
  });
@@ -325,7 +405,7 @@ const WINDSURF_TECHNIQUES = {
325
405
  impact: 'medium',
326
406
  rating: 3,
327
407
  category: 'rules',
328
- fix: 'Add clear, specific descriptions (15+ chars) to Agent-Requested rules so Cascade can judge relevance.',
408
+ fix: 'Add clear, specific descriptions (15+ chars) to `trigger: model_decision` rules so Windsurf can judge when to load the full rule body.',
329
409
  template: null,
330
410
  file: () => '.windsurf/rules/',
331
411
  line: () => null,
@@ -337,20 +417,20 @@ const WINDSURF_TECHNIQUES = {
337
417
 
338
418
  windsurfMcpJsonExists: {
339
419
  id: 'WS-B01',
340
- name: '.windsurf/mcp.json exists if MCP is used',
420
+ name: 'Global Windsurf MCP config exists when MCP is used',
341
421
  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;
422
+ const raw = mcpJsonRaw(ctx);
423
+ if (raw) return true;
424
+ const docs = docsBundle(ctx);
425
+ if (!/\bmcp\b/i.test(docs)) return null;
346
426
  return false;
347
427
  },
348
428
  impact: 'high',
349
429
  rating: 4,
350
430
  category: 'config',
351
- fix: 'Create .windsurf/mcp.json with project-level MCP server configuration.',
431
+ fix: 'Create `%USERPROFILE%/.codeium/windsurf/mcp_config.json` for MCP. Windsurf MCP is global-only in current runtime; project `.windsurf/mcp.json` is not the validated surface.',
352
432
  template: 'windsurf-mcp',
353
- file: () => '.windsurf/mcp.json',
433
+ file: () => windsurfMcpConfigPath(),
354
434
  line: () => null,
355
435
  },
356
436
 
@@ -371,7 +451,7 @@ const WINDSURF_TECHNIQUES = {
371
451
  category: 'config',
372
452
  fix: 'Document MCP server team whitelist. Windsurf supports team-level MCP whitelisting.',
373
453
  template: null,
374
- file: () => '.windsurf/mcp.json',
454
+ file: () => windsurfMcpConfigPath(),
375
455
  line: () => null,
376
456
  },
377
457
 
@@ -421,7 +501,7 @@ const WINDSURF_TECHNIQUES = {
421
501
  impact: 'medium',
422
502
  rating: 3,
423
503
  category: 'config',
424
- fix: 'Create .windsurf/memories/ files for team-syncable persistent context.',
504
+ fix: 'Create `.windsurf/memories/` only for workspace-local persistent context. Do not rely on memories for cross-project or team-shared behavior.',
425
505
  template: 'windsurf-memories',
426
506
  file: () => '.windsurf/memories/',
427
507
  line: () => null,
@@ -433,17 +513,17 @@ const WINDSURF_TECHNIQUES = {
433
513
  check: (ctx) => {
434
514
  const raw = mcpJsonRaw(ctx);
435
515
  if (!raw) return null;
436
- const result = ctx.mcpConfig();
516
+ const result = tryParseJson(raw);
437
517
  return result && result.ok;
438
518
  },
439
519
  impact: 'critical',
440
520
  rating: 5,
441
521
  category: 'config',
442
- fix: 'Fix malformed JSON in .windsurf/mcp.json.',
522
+ fix: 'Fix malformed JSON in `%USERPROFILE%/.codeium/windsurf/mcp_config.json`.',
443
523
  template: null,
444
- file: () => '.windsurf/mcp.json',
524
+ file: () => windsurfMcpConfigPath(),
445
525
  line: (ctx) => {
446
- const result = ctx.mcpConfig();
526
+ const result = tryParseJson(mcpJsonRaw(ctx));
447
527
  if (result && result.ok) return null;
448
528
  if (result && result.error) {
449
529
  const match = result.error.match(/position (\d+)/i);
@@ -482,19 +562,20 @@ const WINDSURF_TECHNIQUES = {
482
562
 
483
563
  windsurfCascadeignoreSensitive: {
484
564
  id: 'WS-C01',
485
- name: 'Cascadeignore covers sensitive directories and files',
565
+ name: 'ZDR guidance does not overclaim local-only processing',
486
566
  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);
567
+ const docs = docsBundle(ctx);
568
+ if (!/\bzdr\b|zero.?data.?retention|privacy|retention/i.test(docs)) return null;
569
+ const claimsNoSend = /\b(no|never|without)\b.{0,40}\b(send|transmit|leave)\b.{0,40}\b(code|data)\b/i.test(docs);
570
+ const explainsTransmission = /\btransmi|sent to windsurf|server.?side processing|retention\b/i.test(docs);
571
+ return !claimsNoSend && explainsTransmission;
491
572
  },
492
573
  impact: 'high',
493
574
  rating: 5,
494
575
  category: 'trust',
495
- fix: 'Add sensitive file patterns (.env, secrets/, credentials, .aws/, .ssh/) to .cascadeignore.',
576
+ fix: 'Document ZDR accurately: it affects retention/training, not whether code is sent to Windsurf for processing. Keep `.cascadeignore` for local exclusion, but do not describe ZDR as a no-transmission mode.',
496
577
  template: null,
497
- file: () => '.cascadeignore',
578
+ file: () => '.windsurf/rules/',
498
579
  line: () => null,
499
580
  },
500
581
 
@@ -533,7 +614,7 @@ const WINDSURF_TECHNIQUES = {
533
614
  category: 'trust',
534
615
  fix: 'Verify MCP servers are from trusted sources. Check for known MCP CVEs.',
535
616
  template: null,
536
- file: () => '.windsurf/mcp.json',
617
+ file: () => windsurfMcpConfigPath(),
537
618
  line: () => null,
538
619
  },
539
620
 
@@ -553,7 +634,7 @@ const WINDSURF_TECHNIQUES = {
553
634
  category: 'trust',
554
635
  fix: 'Use ${env:VAR_NAME} syntax for MCP environment variables instead of hardcoded values.',
555
636
  template: null,
556
- file: () => '.windsurf/mcp.json',
637
+ file: () => windsurfMcpConfigPath(),
557
638
  line: () => null,
558
639
  },
559
640
 
@@ -577,7 +658,7 @@ const WINDSURF_TECHNIQUES = {
577
658
 
578
659
  windsurfMemoriesNoSecrets: {
579
660
  id: 'WS-C06',
580
- name: 'No secrets in memory files (team-synced!)',
661
+ name: 'No secrets in workspace-local memory files',
581
662
  check: (ctx) => {
582
663
  const content = memoryContents(ctx);
583
664
  if (!content.trim()) return null;
@@ -586,7 +667,7 @@ const WINDSURF_TECHNIQUES = {
586
667
  impact: 'critical',
587
668
  rating: 5,
588
669
  category: 'trust',
589
- fix: 'Remove secrets from .windsurf/memories/ these files sync across team members!',
670
+ fix: 'Remove secrets from `.windsurf/memories/`. Memories persist inside the current workspace and can resurface in later sessions even though they are not cross-project or team-shared.',
590
671
  template: null,
591
672
  file: () => '.windsurf/memories/',
592
673
  line: () => null,
@@ -613,16 +694,18 @@ const WINDSURF_TECHNIQUES = {
613
694
 
614
695
  windsurfTeamSyncAware: {
615
696
  id: 'WS-C08',
616
- name: 'Team sync implications documented',
697
+ name: 'Memory scope limitations are documented accurately',
617
698
  check: (ctx) => {
618
699
  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);
700
+ if (!/\bmemories?\b/i.test(docs)) return null;
701
+ const mentionsWorkspaceScope = /\bworkspace\b|\blocal only\b|\bnot cross-project\b|\bcurrent repo\b/i.test(docs);
702
+ const overclaimsSharing = /\bteam.?sync\b|\bshared across projects\b|\bcross-project memory\b/i.test(docs);
703
+ return mentionsWorkspaceScope && !overclaimsSharing;
621
704
  },
622
705
  impact: 'medium',
623
706
  rating: 4,
624
707
  category: 'trust',
625
- fix: 'Document team sync implications for memories and MCP whitelist.',
708
+ fix: 'Document memories as workspace-scoped and local to the current workspace. Do not rely on cross-project recall or team sync when writing guidance.',
626
709
  template: null,
627
710
  file: () => '.windsurf/rules/',
628
711
  line: () => null,
@@ -742,36 +825,35 @@ const WINDSURF_TECHNIQUES = {
742
825
 
743
826
  windsurfMcpPerSurface: {
744
827
  id: 'WS-E01',
745
- name: 'MCP servers configured per surface (project + global)',
828
+ name: 'MCP uses the validated global Windsurf config surface',
746
829
  check: (ctx) => {
747
- const project = ctx.mcpConfig();
748
- if (!project.ok) return null;
749
- return true;
830
+ const raw = mcpJsonRaw(ctx);
831
+ if (!raw) return null;
832
+ return !Boolean(ctx.fileContent('.windsurf/mcp.json'));
750
833
  },
751
834
  impact: 'medium',
752
835
  rating: 3,
753
836
  category: 'mcp',
754
- fix: 'Configure project-level MCP in .windsurf/mcp.json. Global config at ~/.windsurf/mcp.json.',
837
+ fix: 'Use `%USERPROFILE%/.codeium/windsurf/mcp_config.json` as the current MCP surface. Do not rely on project `.windsurf/mcp.json` or the old `~/.windsurf/mcp.json` path.',
755
838
  template: 'windsurf-mcp',
756
- file: () => '.windsurf/mcp.json',
839
+ file: () => windsurfMcpConfigPath(),
757
840
  line: () => null,
758
841
  },
759
842
 
760
843
  windsurfMcpProjectOverride: {
761
844
  id: 'WS-E02',
762
- name: 'Project mcp.json overrides global correctly',
845
+ name: 'Windsurf MCP guidance does not assume project-level overrides',
763
846
  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;
847
+ const docs = docsBundle(ctx);
848
+ if (!/\bmcp\b/i.test(docs)) return null;
849
+ return !/\.windsurf\/mcp\.json|~\/\.windsurf\/mcp\.json|project-level mcp/i.test(docs);
768
850
  },
769
851
  impact: 'medium',
770
852
  rating: 3,
771
853
  category: 'mcp',
772
- fix: 'Ensure project .windsurf/mcp.json and global ~/.windsurf/mcp.json are both valid JSON.',
854
+ fix: 'Remove docs that describe project-level MCP override behavior. Current Windsurf MCP runtime is validated only through the global `%USERPROFILE%/.codeium/windsurf/mcp_config.json` file.',
773
855
  template: null,
774
- file: () => '.windsurf/mcp.json',
856
+ file: () => '.windsurf/rules/',
775
857
  line: () => null,
776
858
  },
777
859
 
@@ -791,7 +873,7 @@ const WINDSURF_TECHNIQUES = {
791
873
  category: 'mcp',
792
874
  fix: 'Use ${env:VAR_NAME} syntax for MCP environment variables instead of hardcoded values.',
793
875
  template: null,
794
- file: () => '.windsurf/mcp.json',
876
+ file: () => windsurfMcpConfigPath(),
795
877
  line: () => null,
796
878
  },
797
879
 
@@ -809,7 +891,7 @@ const WINDSURF_TECHNIQUES = {
809
891
  category: 'mcp',
810
892
  fix: 'Use @latest for MCP packages or regularly update pinned versions.',
811
893
  template: null,
812
- file: () => '.windsurf/mcp.json',
894
+ file: () => windsurfMcpConfigPath(),
813
895
  line: () => null,
814
896
  },
815
897
 
@@ -828,7 +910,7 @@ const WINDSURF_TECHNIQUES = {
828
910
  category: 'mcp',
829
911
  fix: 'Enable MCP team whitelist for controlled environments.',
830
912
  template: null,
831
- file: () => '.windsurf/mcp.json',
913
+ file: () => windsurfMcpConfigPath(),
832
914
  line: () => null,
833
915
  },
834
916
 
@@ -1051,7 +1133,7 @@ const WINDSURF_TECHNIQUES = {
1051
1133
 
1052
1134
  windsurfMemoriesTeamSafe: {
1053
1135
  id: 'WS-H02',
1054
- name: 'Memories are safe for team sync (no personal data)',
1136
+ name: 'Memories are safe for workspace-local persistence (no personal data)',
1055
1137
  check: (ctx) => {
1056
1138
  const content = memoryContents(ctx);
1057
1139
  if (!content.trim()) return null;
@@ -1061,7 +1143,7 @@ const WINDSURF_TECHNIQUES = {
1061
1143
  impact: 'high',
1062
1144
  rating: 5,
1063
1145
  category: 'memories',
1064
- fix: 'Remove personal data and secrets from memories. These sync across team members.',
1146
+ fix: 'Remove personal data and secrets from memories. They are workspace-local, but they still persist and can influence future sessions in this repo.',
1065
1147
  template: null,
1066
1148
  file: () => '.windsurf/memories/',
1067
1149
  line: () => null,
@@ -1148,16 +1230,16 @@ const WINDSURF_TECHNIQUES = {
1148
1230
 
1149
1231
  windsurfEnterpriseTeamSync: {
1150
1232
  id: 'WS-I02',
1151
- name: 'Team sync policies configured',
1233
+ name: 'Enterprise deployment model is documented',
1152
1234
  check: (ctx) => {
1153
1235
  const docs = docsBundle(ctx);
1154
1236
  if (!/enterprise/i.test(docs)) return null;
1155
- return /team.*sync|sync.*policy|shared.*config|team.*memor/i.test(docs);
1237
+ return /self-host|self host|hybrid|cloud deployment|on-prem|on prem/i.test(docs);
1156
1238
  },
1157
1239
  impact: 'medium',
1158
1240
  rating: 3,
1159
1241
  category: 'enterprise',
1160
- fix: 'Configure team sync policies for memories and MCP whitelist.',
1242
+ fix: 'Document the actual Enterprise deployment posture (cloud, hybrid, or self-hosted). Do not describe memories as team-synced when runtime evidence shows they are workspace-scoped.',
1161
1243
  template: null,
1162
1244
  file: () => '.windsurf/rules/',
1163
1245
  line: () => null,
@@ -1182,16 +1264,20 @@ const WINDSURF_TECHNIQUES = {
1182
1264
 
1183
1265
  windsurfEnterpriseSecurityPolicy: {
1184
1266
  id: 'WS-I04',
1185
- name: 'Security policy documented',
1267
+ name: 'Security policy documents retention and transmission accurately',
1186
1268
  check: (ctx) => {
1187
1269
  const docs = docsBundle(ctx);
1188
1270
  if (!/enterprise/i.test(docs)) return null;
1189
- return /security.*policy|data.*retention|compliance|privacy/i.test(docs);
1271
+ const hasPolicy = /security.*policy|data.*retention|compliance|privacy/i.test(docs);
1272
+ const mentionsZdr = /\bzdr\b|zero.?data.?retention/i.test(docs);
1273
+ if (!hasPolicy) return false;
1274
+ if (!mentionsZdr) return true;
1275
+ return /\btransmi|sent to windsurf|processing\b/i.test(docs);
1190
1276
  },
1191
1277
  impact: 'high',
1192
1278
  rating: 4,
1193
1279
  category: 'enterprise',
1194
- fix: 'Document security and data retention policies for Enterprise deployments.',
1280
+ fix: 'Document security and data handling accurately. If you mention ZDR, also state that it controls retention/training posture, not whether code is transmitted for processing.',
1195
1281
  template: null,
1196
1282
  file: () => '.windsurf/rules/',
1197
1283
  line: () => null,
@@ -1318,18 +1404,18 @@ const WINDSURF_TECHNIQUES = {
1318
1404
 
1319
1405
  windsurfMcpConsistentSurfaces: {
1320
1406
  id: 'WS-K02',
1321
- name: 'MCP config consistent across project and global',
1407
+ name: 'MCP guidance matches the current global-only config surface',
1322
1408
  check: (ctx) => {
1323
- const project = ctx.mcpConfig();
1324
- if (!project.ok) return null;
1325
- return true;
1409
+ const raw = mcpJsonRaw(ctx);
1410
+ if (!raw) return null;
1411
+ return !Boolean(ctx.fileContent('.windsurf/mcp.json'));
1326
1412
  },
1327
1413
  impact: 'medium',
1328
1414
  rating: 3,
1329
1415
  category: 'cross-surface',
1330
- fix: 'Document which MCP servers are project-level vs global.',
1416
+ fix: 'Document Windsurf MCP as global-only in current runtime. Remove stale references to project `.windsurf/mcp.json` overrides.',
1331
1417
  template: null,
1332
- file: () => '.windsurf/mcp.json',
1418
+ file: () => windsurfMcpConfigPath(),
1333
1419
  line: () => null,
1334
1420
  },
1335
1421
 
@@ -1448,16 +1534,16 @@ const WINDSURF_TECHNIQUES = {
1448
1534
 
1449
1535
  windsurfRuleCharLimitAware: {
1450
1536
  id: 'WS-L05',
1451
- name: 'All rules within 10K char limit',
1537
+ name: 'All modern rules stay within the 12K char limit',
1452
1538
  check: (ctx) => {
1453
1539
  const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
1454
1540
  if (rules.length === 0) return null;
1455
- return rules.every(r => r.charCount <= 10000);
1541
+ return rules.every((rule) => (rule.charCount || 0) <= 12000);
1456
1542
  },
1457
1543
  impact: 'high',
1458
1544
  rating: 4,
1459
1545
  category: 'quality-deep',
1460
- fix: 'Ensure all rule files are under 10,000 characters. Windsurf enforces this limit.',
1546
+ fix: 'Keep modern rule files under 12,000 characters. Windsurf silently truncates content beyond 12K, and legacy/global rule surfaces still have a stricter 6K ceiling.',
1461
1547
  template: null,
1462
1548
  file: () => '.windsurf/rules/',
1463
1549
  line: () => null,
@@ -1503,51 +1589,36 @@ const WINDSURF_TECHNIQUES = {
1503
1589
 
1504
1590
  windsurfAdvisoryInstructionQuality: {
1505
1591
  id: 'WS-M01',
1506
- name: 'Instruction quality score meets advisory threshold',
1592
+ name: 'No high-risk large files beyond Windsurf’s ~300-line accuracy threshold',
1507
1593
  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;
1594
+ return repoFilesOverLineThreshold(ctx, 300).length === 0;
1519
1595
  },
1520
- impact: 'medium',
1596
+ impact: 'high',
1521
1597
  rating: 4,
1522
1598
  category: 'advisory',
1523
- fix: 'Improve rule quality: add more sections, architecture diagram, and verification commands.',
1524
- template: 'windsurf-rules',
1525
- file: () => '.windsurf/rules/',
1599
+ fix: 'Break files down before they exceed roughly 300 lines. Windsurf accuracy degrades in the 300-500 line range and becomes noticeably unreliable on 500+ line edits.',
1600
+ template: null,
1601
+ file: (ctx) => {
1602
+ const oversized = repoFilesOverLineThreshold(ctx, 300);
1603
+ return oversized[0] ? oversized[0].filePath : null;
1604
+ },
1526
1605
  line: () => null,
1527
1606
  },
1528
1607
 
1529
1608
  windsurfAdvisorySecurityPosture: {
1530
1609
  id: 'WS-M02',
1531
- name: 'Security posture meets advisory threshold',
1610
+ name: 'Long-running Cascade tasks are scoped to mitigate stall risk',
1532
1611
  check: (ctx) => {
1533
- let score = 0;
1534
1612
  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;
1613
+ const hasLargeFiles = repoFilesOverLineThreshold(ctx, 300).length > 0;
1614
+ const hasAutomation = (ctx.workflowFiles ? ctx.workflowFiles() : []).length > 0;
1615
+ if (!hasLargeFiles && !hasAutomation) return null;
1616
+ return /small.*task|chunk|restart.*session|new session|retry|focused task/i.test(docs);
1546
1617
  },
1547
- impact: 'high',
1548
- rating: 5,
1618
+ impact: 'medium',
1619
+ rating: 4,
1549
1620
  category: 'advisory',
1550
- fix: 'Improve security posture: add .cascadeignore, migrate .windsurfrules, secure MCP config.',
1621
+ fix: 'Document that long autonomous tasks can stall without auto-recovery. Prefer smaller, restartable chunks and tell users when to start a new session.',
1551
1622
  template: null,
1552
1623
  file: () => '.windsurf/rules/',
1553
1624
  line: () => null,
@@ -1555,36 +1626,38 @@ const WINDSURF_TECHNIQUES = {
1555
1626
 
1556
1627
  windsurfAdvisorySurfaceCoverage: {
1557
1628
  id: 'WS-M03',
1558
- name: 'Surface coverage meets advisory threshold',
1629
+ name: 'Repo does not assume an unsupported Windsurf CLI/headless surface',
1559
1630
  check: (ctx) => {
1560
- const surfaces = ctx.detectSurfaces ? ctx.detectSurfaces() : {};
1561
- return surfaces.foreground === true;
1631
+ const combined = `${docsBundle(ctx)}\n${ciWorkflowContents(ctx)}`;
1632
+ if (!combined.trim()) return null;
1633
+ const unsupportedAutomation = /\bwindsurf\b[\s\S]{0,80}\b(cli|headless|ci|pipeline|github action|automation|run)\b/i.test(combined) ||
1634
+ /\bcascade\b[\s\S]{0,80}\b(headless|ci|pipeline|automation)\b/i.test(combined);
1635
+ return !unsupportedAutomation;
1562
1636
  },
1563
- impact: 'medium',
1637
+ impact: 'high',
1564
1638
  rating: 4,
1565
1639
  category: 'advisory',
1566
- fix: 'Configure at least the foreground surface with .windsurf/rules/*.md files.',
1567
- template: 'windsurf-rules',
1568
- file: () => '.windsurf/rules/',
1640
+ fix: 'Do not rely on Windsurf for CI/CD or headless automation. Windsurf currently has no supported CLI/headless mode, so automation lanes should use another platform.',
1641
+ template: null,
1642
+ file: () => '.github/workflows/',
1569
1643
  line: () => null,
1570
1644
  },
1571
1645
 
1572
1646
  windsurfAdvisoryMcpHealth: {
1573
1647
  id: 'WS-M04',
1574
- name: 'MCP configuration health meets advisory threshold',
1648
+ name: 'Windows/WSL usage includes a Windsurf stability caveat',
1575
1649
  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;
1650
+ const docs = docsBundle(ctx);
1651
+ const relevant = os.platform() === 'win32' || /\bwsl\b|\bwindows\b/i.test(docs);
1652
+ if (!relevant) return null;
1653
+ return /\bwsl\b.{0,40}\b(crash|unstable|avoid|native windows)\b|\bnative windows\b|\bavoid wsl\b/i.test(docs);
1581
1654
  },
1582
1655
  impact: 'medium',
1583
1656
  rating: 3,
1584
1657
  category: 'advisory',
1585
- fix: 'Ensure MCP configuration is valid and servers are properly configured.',
1658
+ fix: 'Add a Windows note that Windsurf has known WSL crashes/path-resolution issues and is more stable in native Windows or native Linux than under WSL.',
1586
1659
  template: null,
1587
- file: () => '.windsurf/mcp.json',
1660
+ file: () => '.windsurf/rules/',
1588
1661
  line: () => null,
1589
1662
  },
1590
1663
 
@@ -1612,15 +1685,16 @@ const WINDSURF_TECHNIQUES = {
1612
1685
  id: 'WS-N02',
1613
1686
  name: 'MCP packs recommended based on project signals',
1614
1687
  check: (ctx) => {
1615
- const servers = ctx.mcpServers ? ctx.mcpServers() : {};
1688
+ const mcp = mcpJsonData(ctx);
1689
+ const servers = mcp && mcp.mcpServers ? mcp.mcpServers : {};
1616
1690
  return Object.keys(servers).length > 0;
1617
1691
  },
1618
1692
  impact: 'low',
1619
1693
  rating: 2,
1620
1694
  category: 'advisory',
1621
- fix: 'Add recommended MCP packs to .windsurf/mcp.json based on project domain.',
1695
+ fix: 'Add recommended MCP servers to `%USERPROFILE%/.codeium/windsurf/mcp_config.json` based on the project domain.',
1622
1696
  template: 'windsurf-mcp',
1623
- file: () => '.windsurf/mcp.json',
1697
+ file: () => windsurfMcpConfigPath(),
1624
1698
  line: () => null,
1625
1699
  },
1626
1700
 
@@ -1753,6 +1827,8 @@ const WINDSURF_TECHNIQUES = {
1753
1827
  },
1754
1828
  };
1755
1829
 
1830
+ attachSourceUrls('windsurf', WINDSURF_TECHNIQUES);
1831
+
1756
1832
  module.exports = {
1757
1833
  WINDSURF_TECHNIQUES,
1758
1834
  };