@nerviq/cli 0.0.1 → 0.9.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/LICENSE +21 -0
  3. package/README.md +447 -0
  4. package/bin/cli.js +749 -0
  5. package/content/case-study-template.md +91 -0
  6. package/content/claims-governance.md +37 -0
  7. package/content/claude-code/audit-repo/SKILL.md +20 -0
  8. package/content/claude-native-integration.md +60 -0
  9. package/content/devto-article.json +9 -0
  10. package/content/launch-posts.md +226 -0
  11. package/content/pilot-rollout-kit.md +30 -0
  12. package/content/release-checklist.md +31 -0
  13. package/package.json +53 -4
  14. package/src/activity.js +529 -0
  15. package/src/aider/activity.js +226 -0
  16. package/src/aider/config-parser.js +166 -0
  17. package/src/aider/context.js +158 -0
  18. package/src/aider/deep-review.js +316 -0
  19. package/src/aider/domain-packs.js +278 -0
  20. package/src/aider/freshness.js +168 -0
  21. package/src/aider/governance.js +253 -0
  22. package/src/aider/interactive.js +334 -0
  23. package/src/aider/mcp-packs.js +98 -0
  24. package/src/aider/patch.js +214 -0
  25. package/src/aider/plans.js +186 -0
  26. package/src/aider/premium.js +360 -0
  27. package/src/aider/setup.js +404 -0
  28. package/src/aider/techniques.js +1323 -0
  29. package/src/analyze.js +821 -0
  30. package/src/audit.js +1003 -0
  31. package/src/badge.js +13 -0
  32. package/src/benchmark.js +339 -0
  33. package/src/claudex-sync.json +7 -0
  34. package/src/codex/activity.js +324 -0
  35. package/src/codex/config-parser.js +183 -0
  36. package/src/codex/context.js +221 -0
  37. package/src/codex/deep-review.js +493 -0
  38. package/src/codex/domain-packs.js +372 -0
  39. package/src/codex/freshness.js +167 -0
  40. package/src/codex/governance.js +192 -0
  41. package/src/codex/interactive.js +618 -0
  42. package/src/codex/mcp-packs.js +660 -0
  43. package/src/codex/patch.js +209 -0
  44. package/src/codex/plans.js +251 -0
  45. package/src/codex/premium.js +614 -0
  46. package/src/codex/setup.js +603 -0
  47. package/src/codex/techniques.js +2649 -0
  48. package/src/context.js +272 -0
  49. package/src/copilot/activity.js +309 -0
  50. package/src/copilot/config-parser.js +226 -0
  51. package/src/copilot/context.js +197 -0
  52. package/src/copilot/deep-review.js +346 -0
  53. package/src/copilot/domain-packs.js +350 -0
  54. package/src/copilot/freshness.js +197 -0
  55. package/src/copilot/governance.js +222 -0
  56. package/src/copilot/interactive.js +406 -0
  57. package/src/copilot/mcp-packs.js +572 -0
  58. package/src/copilot/patch.js +238 -0
  59. package/src/copilot/plans.js +253 -0
  60. package/src/copilot/premium.js +450 -0
  61. package/src/copilot/setup.js +488 -0
  62. package/src/copilot/techniques.js +1822 -0
  63. package/src/cursor/activity.js +301 -0
  64. package/src/cursor/config-parser.js +265 -0
  65. package/src/cursor/context.js +236 -0
  66. package/src/cursor/deep-review.js +334 -0
  67. package/src/cursor/domain-packs.js +346 -0
  68. package/src/cursor/freshness.js +214 -0
  69. package/src/cursor/governance.js +229 -0
  70. package/src/cursor/interactive.js +391 -0
  71. package/src/cursor/mcp-packs.js +571 -0
  72. package/src/cursor/patch.js +243 -0
  73. package/src/cursor/plans.js +254 -0
  74. package/src/cursor/premium.js +468 -0
  75. package/src/cursor/setup.js +488 -0
  76. package/src/cursor/techniques.js +1786 -0
  77. package/src/deep-review.js +345 -0
  78. package/src/domain-packs.js +364 -0
  79. package/src/formatters/sarif.js +115 -0
  80. package/src/gemini/activity.js +402 -0
  81. package/src/gemini/config-parser.js +275 -0
  82. package/src/gemini/context.js +221 -0
  83. package/src/gemini/deep-review.js +559 -0
  84. package/src/gemini/domain-packs.js +371 -0
  85. package/src/gemini/freshness.js +204 -0
  86. package/src/gemini/governance.js +201 -0
  87. package/src/gemini/interactive.js +860 -0
  88. package/src/gemini/mcp-packs.js +658 -0
  89. package/src/gemini/patch.js +229 -0
  90. package/src/gemini/plans.js +269 -0
  91. package/src/gemini/premium.js +759 -0
  92. package/src/gemini/setup.js +692 -0
  93. package/src/gemini/techniques.js +2084 -0
  94. package/src/governance.js +523 -0
  95. package/src/harmony/advisor.js +383 -0
  96. package/src/harmony/audit.js +303 -0
  97. package/src/harmony/canon.js +444 -0
  98. package/src/harmony/cli.js +331 -0
  99. package/src/harmony/drift.js +401 -0
  100. package/src/harmony/governance.js +313 -0
  101. package/src/harmony/memory.js +238 -0
  102. package/src/harmony/sync.js +458 -0
  103. package/src/harmony/watch.js +336 -0
  104. package/src/index.js +256 -0
  105. package/src/insights.js +119 -0
  106. package/src/interactive.js +118 -0
  107. package/src/mcp-packs.js +597 -0
  108. package/src/opencode/activity.js +286 -0
  109. package/src/opencode/config-parser.js +109 -0
  110. package/src/opencode/context.js +247 -0
  111. package/src/opencode/deep-review.js +313 -0
  112. package/src/opencode/domain-packs.js +240 -0
  113. package/src/opencode/freshness.js +158 -0
  114. package/src/opencode/governance.js +159 -0
  115. package/src/opencode/interactive.js +392 -0
  116. package/src/opencode/mcp-packs.js +474 -0
  117. package/src/opencode/patch.js +184 -0
  118. package/src/opencode/plans.js +231 -0
  119. package/src/opencode/premium.js +413 -0
  120. package/src/opencode/setup.js +449 -0
  121. package/src/opencode/techniques.js +1713 -0
  122. package/src/plans.js +655 -0
  123. package/src/secret-patterns.js +30 -0
  124. package/src/setup.js +1274 -0
  125. package/src/synergy/adaptive.js +261 -0
  126. package/src/synergy/compensation.js +156 -0
  127. package/src/synergy/evidence.js +193 -0
  128. package/src/synergy/learning.js +184 -0
  129. package/src/synergy/patterns.js +227 -0
  130. package/src/synergy/ranking.js +83 -0
  131. package/src/synergy/report.js +163 -0
  132. package/src/synergy/routing.js +152 -0
  133. package/src/techniques.js +1354 -0
  134. package/src/watch.js +229 -0
  135. package/src/windsurf/activity.js +302 -0
  136. package/src/windsurf/config-parser.js +267 -0
  137. package/src/windsurf/context.js +249 -0
  138. package/src/windsurf/deep-review.js +337 -0
  139. package/src/windsurf/domain-packs.js +348 -0
  140. package/src/windsurf/freshness.js +215 -0
  141. package/src/windsurf/governance.js +231 -0
  142. package/src/windsurf/interactive.js +388 -0
  143. package/src/windsurf/mcp-packs.js +535 -0
  144. package/src/windsurf/patch.js +231 -0
  145. package/src/windsurf/plans.js +247 -0
  146. package/src/windsurf/premium.js +467 -0
  147. package/src/windsurf/setup.js +471 -0
  148. package/src/windsurf/techniques.js +1758 -0
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Windsurf Governance Module
3
+ *
4
+ * 6 permission profiles, 7 hook equivalents, 5 policy packs.
5
+ *
6
+ * Windsurf-specific differences from Cursor:
7
+ * - NO background agents (Cascade runs in foreground only)
8
+ * - Cascade: autonomous agent with multi-file editing
9
+ * - Memories: team-syncable persistent context
10
+ * - Workflows: slash commands
11
+ * - Steps: automation sequences
12
+ * - .cascadeignore: gitignore-like for Cascade
13
+ * - MCP with team whitelist
14
+ * - 10K char rule limit per file
15
+ * - 4 activation modes: Always, Auto, Agent-Requested, Manual
16
+ */
17
+
18
+ const { WINDSURF_DOMAIN_PACKS } = require('./domain-packs');
19
+ const { WINDSURF_MCP_PACKS } = require('./mcp-packs');
20
+
21
+ const WINDSURF_PERMISSION_PROFILES = [
22
+ {
23
+ key: 'read-only',
24
+ label: 'Read Only',
25
+ risk: 'low',
26
+ defaultSandbox: 'no-writes',
27
+ approvalPolicy: 'always-confirm',
28
+ useWhen: 'First contact with a repo, security review, or auditing.',
29
+ behavior: 'Cascade can read and suggest, but all edits and terminal commands require explicit confirmation.',
30
+ surfaces: ['foreground'],
31
+ },
32
+ {
33
+ key: 'standard',
34
+ label: 'Standard',
35
+ risk: 'medium',
36
+ defaultSandbox: 'user-approval',
37
+ approvalPolicy: 'selective-approval',
38
+ useWhen: 'Default product work where Cascade edits locally but risky commands need review.',
39
+ behavior: 'Cascade proposes edits. Terminal commands require per-command approval.',
40
+ surfaces: ['foreground'],
41
+ },
42
+ {
43
+ key: 'cascade-agent',
44
+ label: 'Cascade Agent',
45
+ risk: 'medium',
46
+ defaultSandbox: 'auto-run-trusted',
47
+ approvalPolicy: 'auto-approve-safe',
48
+ useWhen: 'Trusted repos where full Cascade agent mode is the primary workflow.',
49
+ behavior: 'Full Cascade agent mode. Multi-file edits with auto-approval for safe operations.',
50
+ surfaces: ['foreground'],
51
+ },
52
+ {
53
+ key: 'steps-automation',
54
+ label: 'Steps Automation',
55
+ risk: 'medium',
56
+ defaultSandbox: 'step-scoped',
57
+ approvalPolicy: 'step-level-approval',
58
+ useWhen: 'Complex multi-step tasks using Steps automation.',
59
+ behavior: 'Cascade runs multi-step workflows. Each step can be reviewed before proceeding.',
60
+ surfaces: ['foreground'],
61
+ },
62
+ {
63
+ key: 'team-managed',
64
+ label: 'Team Managed',
65
+ risk: 'medium',
66
+ defaultSandbox: 'team-policy',
67
+ approvalPolicy: 'team-controlled',
68
+ useWhen: 'Team environments with shared memories and MCP whitelist.',
69
+ behavior: 'Team-level policies for MCP whitelist, memories sync, and workflow access.',
70
+ surfaces: ['foreground'],
71
+ },
72
+ {
73
+ key: 'enterprise',
74
+ label: 'Enterprise',
75
+ risk: 'low',
76
+ defaultSandbox: 'org-policy-enforced',
77
+ approvalPolicy: 'org-admin-controlled',
78
+ useWhen: 'Enterprise tier with team sync, MCP whitelist, audit logs.',
79
+ behavior: 'Admin-managed policies. MCP whitelist enforced. Audit logs and team sync policies.',
80
+ surfaces: ['foreground'],
81
+ },
82
+ ];
83
+
84
+ const WINDSURF_HOOK_REGISTRY = [
85
+ {
86
+ key: 'always-rules',
87
+ file: '.windsurf/rules/*.md',
88
+ triggerPoint: 'trigger: always',
89
+ matcher: 'every Cascade interaction',
90
+ purpose: 'Inject core instructions into every Cascade interaction.',
91
+ risk: 'low',
92
+ },
93
+ {
94
+ key: 'auto-rules',
95
+ file: '.windsurf/rules/*.md',
96
+ triggerPoint: 'trigger: auto, globs match',
97
+ matcher: 'file glob patterns',
98
+ purpose: 'Inject context-specific rules when matching files are referenced.',
99
+ risk: 'low',
100
+ },
101
+ {
102
+ key: 'agent-requested-rules',
103
+ file: '.windsurf/rules/*.md',
104
+ triggerPoint: 'trigger: agent_requested',
105
+ matcher: 'Cascade agent decision',
106
+ purpose: 'Rules that Cascade can choose to apply based on description relevance.',
107
+ risk: 'low',
108
+ },
109
+ {
110
+ key: 'workflow-trigger',
111
+ file: '.windsurf/workflows/*.md',
112
+ triggerPoint: 'slash command invocation',
113
+ matcher: 'user-triggered slash command',
114
+ purpose: 'Execute predefined workflows via slash commands.',
115
+ risk: 'medium',
116
+ },
117
+ {
118
+ key: 'memory-load',
119
+ file: '.windsurf/memories/',
120
+ triggerPoint: 'session start',
121
+ matcher: 'persistent context',
122
+ purpose: 'Load team-syncable memories into Cascade context.',
123
+ risk: 'low',
124
+ },
125
+ {
126
+ key: 'cascadeignore-filter',
127
+ file: '.cascadeignore',
128
+ triggerPoint: 'file access',
129
+ matcher: 'gitignore-style patterns',
130
+ purpose: 'Prevent Cascade from accessing sensitive files.',
131
+ risk: 'low',
132
+ },
133
+ {
134
+ key: 'mcp-tool-access',
135
+ file: '.windsurf/mcp.json',
136
+ triggerPoint: 'MCP tool invocation',
137
+ matcher: 'tool name/server + team whitelist',
138
+ purpose: 'Control which MCP tools are available. Team whitelist for controlled environments.',
139
+ risk: 'medium',
140
+ },
141
+ ];
142
+
143
+ const WINDSURF_POLICY_PACKS = [
144
+ {
145
+ key: 'baseline-safe',
146
+ label: 'Baseline Safe',
147
+ modules: ['.windsurf/rules/ with trigger: always', 'no .windsurfrules', '.cascadeignore configured', 'no secrets in rules'],
148
+ useWhen: 'Default local Windsurf rollout.',
149
+ },
150
+ {
151
+ key: 'cascade-safe',
152
+ label: 'Cascade Safe',
153
+ modules: ['cascadeignore for secrets', 'PR review gate', 'multi-file review before commit', 'Steps scoped'],
154
+ useWhen: 'Repos using Cascade for autonomous multi-file editing.',
155
+ },
156
+ {
157
+ key: 'team-safe',
158
+ label: 'Team Safe',
159
+ modules: ['MCP team whitelist', 'memories no secrets', 'shared workflows reviewed', 'sync policies documented'],
160
+ useWhen: 'Team environments with shared Windsurf configuration.',
161
+ },
162
+ {
163
+ key: 'enterprise-governed',
164
+ label: 'Enterprise Governed',
165
+ modules: ['MCP whitelist enforced', 'audit logs enabled', 'team sync policies', 'model access policy'],
166
+ useWhen: 'Enterprise tier repos with strict governance requirements.',
167
+ },
168
+ {
169
+ key: 'security-first',
170
+ label: 'Security First',
171
+ modules: ['.cascadeignore comprehensive', 'no secrets in any Windsurf config', 'MCP env vars secured', 'memories reviewed for PII'],
172
+ useWhen: 'Repos handling sensitive data where security is paramount.',
173
+ },
174
+ ];
175
+
176
+ const WINDSURF_PILOT_ROLLOUT_KIT = {
177
+ recommendedScope: [
178
+ 'Start with audit and setup on one trusted repo.',
179
+ 'Keep .windsurf/rules/ and .windsurf/mcp.json in version control.',
180
+ 'Configure .cascadeignore before enabling Cascade on sensitive repos.',
181
+ 'Migrate .windsurfrules to .windsurf/rules/*.md before relying on Cascade.',
182
+ 'Review team-synced memories for secrets or PII before sharing.',
183
+ 'Test workflows on non-critical repos first.',
184
+ ],
185
+ approvals: [
186
+ 'Engineering owner approves Cascade usage scope and MCP whitelist.',
187
+ 'Security owner approves .cascadeignore and memory sync policies.',
188
+ 'Pilot owner records before/after audit deltas and rollback expectations.',
189
+ 'Team lead approves shared workflow definitions.',
190
+ ],
191
+ successMetrics: [
192
+ 'Audit score delta',
193
+ 'Surface coverage (rules + workflows + memories)',
194
+ 'Time to first useful Cascade task',
195
+ 'No-overwrite rate on existing repo files',
196
+ 'Legacy .windsurfrules migration completion',
197
+ 'MCP server whitelist compliance',
198
+ ],
199
+ rollbackExpectations: [
200
+ 'Every Windsurf setup/apply write path should emit a rollback artifact.',
201
+ 'Re-run audit after rollback to confirm the repo returned to expected state.',
202
+ 'Cascade can be limited by removing .windsurf/rules/ or configuring .cascadeignore.',
203
+ 'Team sync can be disabled by removing .windsurf/memories/.',
204
+ ],
205
+ };
206
+
207
+ function getWindsurfGovernanceSummary() {
208
+ return {
209
+ platform: 'windsurf',
210
+ platformLabel: 'Windsurf (Cascade)',
211
+ permissionProfiles: WINDSURF_PERMISSION_PROFILES,
212
+ hookRegistry: WINDSURF_HOOK_REGISTRY,
213
+ policyPacks: WINDSURF_POLICY_PACKS,
214
+ domainPacks: WINDSURF_DOMAIN_PACKS,
215
+ mcpPacks: WINDSURF_MCP_PACKS,
216
+ pilotRolloutKit: WINDSURF_PILOT_ROLLOUT_KIT,
217
+ platformCaveats: [
218
+ { id: 'windsurfrules-legacy', severity: 'high', message: '.windsurfrules is legacy format — migrate to .windsurf/rules/*.md with YAML frontmatter.' },
219
+ { id: 'no-background-agents', severity: 'info', message: 'Windsurf has NO background agents (unlike Cursor). All Cascade runs are foreground.' },
220
+ { id: 'rule-char-limit', severity: 'medium', message: 'Windsurf enforces a 10K character limit per rule file.' },
221
+ { id: 'memories-team-sync', severity: 'high', message: 'Memories sync across team members — never put secrets or PII in memory files.' },
222
+ { id: 'mcp-team-whitelist', severity: 'medium', message: 'MCP servers can be whitelisted at team level. Ensure only approved servers are listed.' },
223
+ { id: 'cascadeignore-important', severity: 'high', message: 'Use .cascadeignore to prevent Cascade from accessing sensitive files (similar to .gitignore).' },
224
+ { id: 'cascade-multi-file', severity: 'medium', message: 'Cascade performs multi-file edits. Review all changed files before committing.' },
225
+ ],
226
+ };
227
+ }
228
+
229
+ module.exports = {
230
+ getWindsurfGovernanceSummary,
231
+ };
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Windsurf Interactive Wizard — 10-stage guided setup.
3
+ *
4
+ * Adapted for Windsurf's architecture (foreground Cascade, memories, workflows).
5
+ *
6
+ * Stages:
7
+ * 1. Project detection (stacks, existing .windsurf/, .windsurfrules)
8
+ * 2. Rule type selection (Always, Auto, Agent-Requested, Manual)
9
+ * 3. Legacy migration (.windsurfrules -> .windsurf/rules/)
10
+ * 4. Cascadeignore setup
11
+ * 5. Domain pack selection
12
+ * 6. MCP pack selection
13
+ * 7. Workflow config
14
+ * 8. Memories config
15
+ * 9. Team & security
16
+ * 10. Review & generate
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const os = require('os');
21
+ const path = require('path');
22
+ const readline = require('readline');
23
+ const { STACKS } = require('../techniques');
24
+ const { WindsurfProjectContext } = require('./context');
25
+ const { WINDSURF_TECHNIQUES } = require('./techniques');
26
+ const { WINDSURF_DOMAIN_PACKS, detectWindsurfDomainPacks } = require('./domain-packs');
27
+ const { recommendWindsurfMcpPacks, WINDSURF_MCP_PACKS } = require('./mcp-packs');
28
+
29
+ const COLORS = {
30
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
31
+ red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m',
32
+ blue: '\x1b[36m', magenta: '\x1b[35m',
33
+ };
34
+ const c = (text, color) => `${COLORS[color] || ''}${text}${COLORS.reset}`;
35
+
36
+ function ask(rl, question) {
37
+ return new Promise(resolve => rl.question(question, resolve));
38
+ }
39
+
40
+ function isTTY() {
41
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
42
+ }
43
+
44
+ const STAGE_NAMES = [
45
+ 'Project Detection',
46
+ 'Rule Type Selection',
47
+ 'Legacy Migration',
48
+ 'Cascadeignore Setup',
49
+ 'Domain Pack Selection',
50
+ 'MCP Pack Selection',
51
+ 'Workflow Config',
52
+ 'Memories Config',
53
+ 'Team & Security',
54
+ 'Review & Generate',
55
+ ];
56
+
57
+ function printStageHeader(index) {
58
+ console.log('');
59
+ console.log(c(` ── Stage ${index + 1}/${STAGE_NAMES.length}: ${STAGE_NAMES[index]} ──`, 'magenta'));
60
+ console.log('');
61
+ }
62
+
63
+ // --- Stage 1: Project Detection ---
64
+
65
+ function runProjectDetection(ctx) {
66
+ const stacks = ctx.detectStacks(STACKS);
67
+ const rules = ctx.windsurfRules ? ctx.windsurfRules() : [];
68
+ const hasLegacy = ctx.hasLegacyRules ? ctx.hasLegacyRules() : false;
69
+ const hasMcpJson = ctx.mcpConfig().ok;
70
+ const workflows = ctx.workflowFiles ? ctx.workflowFiles() : [];
71
+ const memories = ctx.memoryFiles ? ctx.memoryFiles() : [];
72
+ const hasCascadeignore = ctx.hasCascadeignore ? ctx.hasCascadeignore() : false;
73
+ const surfaces = ctx.detectSurfaces();
74
+
75
+ const auditResults = [];
76
+ for (const [key, technique] of Object.entries(WINDSURF_TECHNIQUES)) {
77
+ auditResults.push({ key, ...technique, passed: technique.check(ctx) });
78
+ }
79
+ const passed = auditResults.filter(r => r.passed === true);
80
+ const failed = auditResults.filter(r => r.passed === false);
81
+
82
+ return { stacks, rules, hasLegacy, hasMcpJson, workflows, memories, hasCascadeignore, surfaces, auditResults, passed, failed };
83
+ }
84
+
85
+ async function stageProjectDetection(rl, ctx, state) {
86
+ printStageHeader(0);
87
+ const det = runProjectDetection(ctx);
88
+ Object.assign(state, det);
89
+
90
+ if (det.stacks.length > 0) console.log(c(` Detected stacks: ${det.stacks.map(s => s.label).join(', ')}`, 'blue'));
91
+ else console.log(c(' No specific stack detected — will use baseline defaults.', 'dim'));
92
+
93
+ console.log(c(` Working directory: ${ctx.dir}`, 'dim'));
94
+ console.log('');
95
+ console.log(` ${c(`${det.passed.length}/${det.auditResults.length}`, 'bold')} Windsurf checks passing.`);
96
+ console.log(` ${c(String(det.failed.length), 'yellow')} improvements available.`);
97
+ console.log('');
98
+ console.log(` Surfaces: FG ${det.surfaces.foreground ? c('Y', 'green') : c('N', 'red')} | WF ${det.surfaces.workflows ? c('Y', 'green') : c('N', 'red')} | Mem ${det.surfaces.memories ? c('Y', 'green') : c('N', 'red')} | CI ${det.hasCascadeignore ? c('Y', 'green') : c('N', 'red')}`);
99
+ console.log(` Rules: ${det.rules.length} .md files${det.hasLegacy ? c(' + .windsurfrules (legacy!)', 'red') : ''}`);
100
+
101
+ if (det.rules.length > 0 || det.hasLegacy) {
102
+ console.log('');
103
+ if (det.hasLegacy) console.log(c(' WARNING: .windsurfrules found — this is the legacy format!', 'red'));
104
+ if (det.rules.length > 0) console.log(c(` ${det.rules.length} .md rule files in .windsurf/rules/`, 'dim'));
105
+ if (det.hasMcpJson) console.log(c(' .windsurf/mcp.json found', 'dim'));
106
+ console.log('');
107
+
108
+ const answer = await ask(rl, c(' Merge with existing config or replace? (merge/replace/quit) ', 'magenta'));
109
+ const choice = answer.trim().toLowerCase();
110
+ if (choice === 'quit' || choice === 'q') return 'quit';
111
+ state.migrationMode = choice === 'replace' ? 'replace' : 'merge';
112
+ } else {
113
+ state.migrationMode = 'create';
114
+ console.log('');
115
+ console.log(c(' No existing Windsurf rules — will create fresh setup.', 'dim'));
116
+ }
117
+
118
+ return 'next';
119
+ }
120
+
121
+ // --- Stage 2: Rule Type Selection ---
122
+
123
+ async function stageRuleTypeSelection(rl, _ctx, state) {
124
+ printStageHeader(1);
125
+
126
+ console.log(' Windsurf supports 4 rule activation modes. Which do you want to generate?');
127
+ console.log('');
128
+ console.log(` ${c('1', 'bold')}. ${c('Always only', 'blue')} — Core rules for every Cascade interaction.`);
129
+ console.log(` ${c('2', 'bold')}. ${c('Always + Auto', 'blue')} — Core + file-type-specific rules.`);
130
+ console.log(` ${c('3', 'bold')}. ${c('Full set', 'blue')} — Always + Auto + Agent-Requested.`);
131
+ console.log('');
132
+
133
+ const answer = await ask(rl, c(' Choice (1-3, or b=back, q=quit) [2]: ', 'magenta'));
134
+ const trimmed = answer.trim().toLowerCase();
135
+ if (trimmed === 'b') return 'back';
136
+ if (trimmed === 'q') return 'quit';
137
+
138
+ const choice = parseInt(trimmed, 10);
139
+ state.ruleTypes = ['always'];
140
+ if (choice >= 2 || trimmed === '') state.ruleTypes.push('auto');
141
+ if (choice >= 3) state.ruleTypes.push('agent-requested');
142
+
143
+ console.log(c(` → Rule types: ${state.ruleTypes.join(', ')}`, 'green'));
144
+ return 'next';
145
+ }
146
+
147
+ // --- Stage 3: Legacy Migration ---
148
+
149
+ async function stageLegacyMigration(rl, ctx, state) {
150
+ if (!state.hasLegacy) return 'next';
151
+
152
+ printStageHeader(2);
153
+
154
+ console.log(c(' WARNING: .windsurfrules file detected!', 'red'));
155
+ console.log('');
156
+ console.log(' The .windsurfrules file is the legacy format.');
157
+ console.log(' Migrate to .windsurf/rules/*.md with YAML frontmatter for best results.');
158
+ console.log('');
159
+ console.log(` ${c('1', 'bold')}. ${c('Auto-migrate', 'blue')} — Convert .windsurfrules content to .md format.`);
160
+ console.log(` ${c('2', 'bold')}. ${c('Keep both', 'yellow')} — Generate new .md rules alongside .windsurfrules.`);
161
+ console.log(` ${c('3', 'bold')}. ${c('Skip', 'dim')} — Handle migration manually later.`);
162
+ console.log('');
163
+
164
+ const answer = await ask(rl, c(' Choice (1-3, or b=back, q=quit) [1]: ', 'magenta'));
165
+ const trimmed = answer.trim().toLowerCase();
166
+ if (trimmed === 'b') return 'back';
167
+ if (trimmed === 'q') return 'quit';
168
+
169
+ const choice = parseInt(trimmed, 10);
170
+ state.legacyMigration = choice === 3 ? 'skip' : choice === 2 ? 'keep-both' : 'auto-migrate';
171
+ console.log(c(` → Legacy migration: ${state.legacyMigration}`, 'green'));
172
+ return 'next';
173
+ }
174
+
175
+ // --- Stage 4: Cascadeignore Setup ---
176
+
177
+ async function stageCascadeignore(rl, _ctx, state) {
178
+ printStageHeader(3);
179
+
180
+ console.log(' Configure .cascadeignore for sensitive file exclusion?');
181
+ console.log('');
182
+ console.log(c(' .cascadeignore uses gitignore syntax to prevent Cascade from accessing files.', 'dim'));
183
+ console.log(c(' Recommended patterns: .env, secrets/, credentials/, .aws/, .ssh/', 'dim'));
184
+ console.log('');
185
+
186
+ const answer = await ask(rl, c(' Generate .cascadeignore? (y/n, or b=back, q=quit) [y]: ', 'magenta'));
187
+ const trimmed = answer.trim().toLowerCase();
188
+ if (trimmed === 'b') return 'back';
189
+ if (trimmed === 'q') return 'quit';
190
+
191
+ state.generateCascadeignore = trimmed !== 'n';
192
+ console.log(c(` → Cascadeignore: ${state.generateCascadeignore ? 'will configure' : 'skip'}`, 'green'));
193
+ return 'next';
194
+ }
195
+
196
+ // --- Stage 5: Domain Pack Selection ---
197
+
198
+ async function stageDomainPacks(rl, ctx, state) {
199
+ printStageHeader(4);
200
+
201
+ const detected = detectWindsurfDomainPacks(ctx, state.stacks || []);
202
+ state.domainPacks = detected;
203
+
204
+ console.log(` Detected ${detected.length} domain pack(s):`);
205
+ console.log('');
206
+ detected.forEach((pack, i) => {
207
+ console.log(` ${c(`${i + 1}`, 'bold')}. ${c(pack.label, 'blue')} — ${pack.matchReasons[0] || pack.useWhen}`);
208
+ });
209
+ console.log('');
210
+
211
+ const answer = await ask(rl, c(' Accept detected packs? (y/n, or b=back, q=quit) [y]: ', 'magenta'));
212
+ const trimmed = answer.trim().toLowerCase();
213
+ if (trimmed === 'b') return 'back';
214
+ if (trimmed === 'q') return 'quit';
215
+ if (trimmed === 'n') state.domainPacks = [];
216
+
217
+ console.log(c(` → Domain packs: ${state.domainPacks.map(p => p.label).join(', ') || 'none'}`, 'green'));
218
+ return 'next';
219
+ }
220
+
221
+ // --- Stage 6: MCP Pack Selection ---
222
+
223
+ async function stageMcpPacks(rl, ctx, state) {
224
+ printStageHeader(5);
225
+
226
+ const recommended = recommendWindsurfMcpPacks(state.stacks || [], state.domainPacks || [], { ctx });
227
+ state.mcpPacks = recommended;
228
+
229
+ console.log(` Recommended ${recommended.length} MCP pack(s):`);
230
+ console.log('');
231
+ recommended.forEach((pack, i) => {
232
+ console.log(` ${c(`${i + 1}`, 'bold')}. ${c(pack.label, 'blue')} — ${pack.description}`);
233
+ });
234
+ console.log('');
235
+
236
+ const answer = await ask(rl, c(' Accept recommended packs? (y/n, or b=back, q=quit) [y]: ', 'magenta'));
237
+ const trimmed = answer.trim().toLowerCase();
238
+ if (trimmed === 'b') return 'back';
239
+ if (trimmed === 'q') return 'quit';
240
+ if (trimmed === 'n') state.mcpPacks = [];
241
+
242
+ console.log(c(` → MCP packs: ${state.mcpPacks.map(p => p.label).join(', ') || 'none'}`, 'green'));
243
+ return 'next';
244
+ }
245
+
246
+ // --- Stage 7: Workflow Config ---
247
+
248
+ async function stageWorkflowConfig(rl, _ctx, state) {
249
+ printStageHeader(6);
250
+
251
+ console.log(' Configure Windsurf workflows (slash commands)?');
252
+ console.log('');
253
+ console.log(c(' Workflows define reusable slash commands for Cascade.', 'dim'));
254
+ console.log(c(' Stored in .windsurf/workflows/*.md', 'dim'));
255
+ console.log('');
256
+
257
+ const answer = await ask(rl, c(' Set up starter workflows? (y/n, or b=back, q=quit) [n]: ', 'magenta'));
258
+ const trimmed = answer.trim().toLowerCase();
259
+ if (trimmed === 'b') return 'back';
260
+ if (trimmed === 'q') return 'quit';
261
+
262
+ state.generateWorkflows = trimmed === 'y';
263
+ console.log(c(` → Workflows: ${state.generateWorkflows ? 'will configure' : 'skip'}`, 'green'));
264
+ return 'next';
265
+ }
266
+
267
+ // --- Stage 8: Memories Config ---
268
+
269
+ async function stageMemoriesConfig(rl, _ctx, state) {
270
+ printStageHeader(7);
271
+
272
+ console.log(' Configure Windsurf memories (persistent context)?');
273
+ console.log('');
274
+ console.log(c(' Memories provide persistent context that syncs across team members.', 'dim'));
275
+ console.log(c(' IMPORTANT: Never put secrets or PII in memories — they sync to all team members!', 'yellow'));
276
+ console.log('');
277
+
278
+ const answer = await ask(rl, c(' Generate starter memories? (y/n, or b=back, q=quit) [n]: ', 'magenta'));
279
+ const trimmed = answer.trim().toLowerCase();
280
+ if (trimmed === 'b') return 'back';
281
+ if (trimmed === 'q') return 'quit';
282
+
283
+ state.generateMemories = trimmed === 'y';
284
+ console.log(c(` → Memories: ${state.generateMemories ? 'will configure' : 'skip'}`, 'green'));
285
+ return 'next';
286
+ }
287
+
288
+ // --- Stage 9: Team & Security ---
289
+
290
+ async function stageTeamSecurity(rl, _ctx, state) {
291
+ printStageHeader(8);
292
+
293
+ console.log(' Review team and security considerations:');
294
+ console.log('');
295
+ console.log(c(' 1. Memories sync across team — check for secrets/PII', 'yellow'));
296
+ console.log(c(' 2. MCP servers can be whitelisted at team level', 'dim'));
297
+ console.log(c(' 3. .cascadeignore protects sensitive files from Cascade', 'dim'));
298
+ console.log(c(' 4. Each rule file has a 10K character limit', 'dim'));
299
+ console.log('');
300
+
301
+ const answer = await ask(rl, c(' Acknowledge team/security considerations? (y/n, or b=back, q=quit) [y]: ', 'magenta'));
302
+ const trimmed = answer.trim().toLowerCase();
303
+ if (trimmed === 'b') return 'back';
304
+ if (trimmed === 'q') return 'quit';
305
+ if (trimmed === 'n') return 'quit';
306
+
307
+ state.securityAcknowledged = true;
308
+ console.log(c(' → Team/security considerations acknowledged.', 'yellow'));
309
+ return 'next';
310
+ }
311
+
312
+ // --- Stage 10: Review & Generate ---
313
+
314
+ async function stageReviewGenerate(rl, _ctx, state) {
315
+ printStageHeader(9);
316
+
317
+ console.log(' Summary of your Windsurf setup:');
318
+ console.log('');
319
+ console.log(` Rule types: ${(state.ruleTypes || []).join(', ')}`);
320
+ console.log(` Legacy migration: ${state.legacyMigration || 'N/A'}`);
321
+ console.log(` Cascadeignore: ${state.generateCascadeignore ? 'yes' : 'no'}`);
322
+ console.log(` Domain packs: ${(state.domainPacks || []).map(p => p.label).join(', ') || 'none'}`);
323
+ console.log(` MCP packs: ${(state.mcpPacks || []).map(p => p.label).join(', ') || 'none'}`);
324
+ console.log(` Workflows: ${state.generateWorkflows ? 'yes' : 'no'}`);
325
+ console.log(` Memories: ${state.generateMemories ? 'yes' : 'no'}`);
326
+ console.log('');
327
+
328
+ const answer = await ask(rl, c(' Generate files? (y/n, or b=back, q=quit) [y]: ', 'magenta'));
329
+ const trimmed = answer.trim().toLowerCase();
330
+ if (trimmed === 'b') return 'back';
331
+ if (trimmed === 'q') return 'quit';
332
+ if (trimmed === 'n') return 'quit';
333
+
334
+ state.confirmed = true;
335
+ return 'done';
336
+ }
337
+
338
+ // --- Main Wizard ---
339
+
340
+ const STAGES = [
341
+ stageProjectDetection,
342
+ stageRuleTypeSelection,
343
+ stageLegacyMigration,
344
+ stageCascadeignore,
345
+ stageDomainPacks,
346
+ stageMcpPacks,
347
+ stageWorkflowConfig,
348
+ stageMemoriesConfig,
349
+ stageTeamSecurity,
350
+ stageReviewGenerate,
351
+ ];
352
+
353
+ async function runWindsurfWizard(options) {
354
+ if (!isTTY()) {
355
+ console.log('Interactive wizard requires a TTY. Use --non-interactive or pipe mode instead.');
356
+ return null;
357
+ }
358
+
359
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
360
+ const ctx = new WindsurfProjectContext(options.dir);
361
+ const state = {};
362
+
363
+ console.log('');
364
+ console.log(c(' nerviq windsurf interactive wizard', 'bold'));
365
+ console.log(c(' ═══════════════════════════════════════', 'dim'));
366
+
367
+ let stageIndex = 0;
368
+
369
+ try {
370
+ while (stageIndex < STAGES.length) {
371
+ const result = await STAGES[stageIndex](rl, ctx, state);
372
+ if (result === 'quit') { rl.close(); return null; }
373
+ if (result === 'back') { stageIndex = Math.max(0, stageIndex - 1); continue; }
374
+ if (result === 'done') break;
375
+ stageIndex++;
376
+ }
377
+ } finally {
378
+ rl.close();
379
+ }
380
+
381
+ if (!state.confirmed) return null;
382
+ return state;
383
+ }
384
+
385
+ module.exports = {
386
+ runWindsurfWizard,
387
+ runProjectDetection,
388
+ };