@nerviq/cli 0.9.1 → 0.9.3

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