@contextstream/mcp-server 0.4.66 → 0.4.67

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.
package/dist/index.js CHANGED
@@ -1563,10 +1563,12 @@ var init_files = __esm({
1563
1563
  // src/hooks-config.ts
1564
1564
  var hooks_config_exports = {};
1565
1565
  __export(hooks_config_exports, {
1566
+ CLAUDE_ENFORCEMENT_CRITICAL_HOOKS: () => CLAUDE_ENFORCEMENT_CRITICAL_HOOKS,
1566
1567
  CLINE_POSTTOOLUSE_HOOK_SCRIPT: () => CLINE_POSTTOOLUSE_HOOK_SCRIPT,
1567
1568
  CLINE_PRETOOLUSE_HOOK_SCRIPT: () => CLINE_PRETOOLUSE_HOOK_SCRIPT,
1568
1569
  CLINE_USER_PROMPT_HOOK_SCRIPT: () => CLINE_USER_PROMPT_HOOK_SCRIPT,
1569
1570
  CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT: () => CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT,
1571
+ CURSOR_ENFORCEMENT_CRITICAL_HOOKS: () => CURSOR_ENFORCEMENT_CRITICAL_HOOKS,
1570
1572
  CURSOR_PRETOOLUSE_HOOK_SCRIPT: () => CURSOR_PRETOOLUSE_HOOK_SCRIPT,
1571
1573
  MEDIA_AWARE_HOOK_SCRIPT: () => MEDIA_AWARE_HOOK_SCRIPT,
1572
1574
  PRECOMPACT_HOOK_SCRIPT: () => PRECOMPACT_HOOK_SCRIPT,
@@ -1585,6 +1587,7 @@ __export(hooks_config_exports, {
1585
1587
  getIndexStatusPath: () => getIndexStatusPath,
1586
1588
  getKiloCodeHooksDir: () => getKiloCodeHooksDir,
1587
1589
  getRooCodeHooksDir: () => getRooCodeHooksDir,
1590
+ getWindsurfHooksConfigPath: () => getWindsurfHooksConfigPath,
1588
1591
  installAllEditorHooks: () => installAllEditorHooks,
1589
1592
  installClaudeCodeHooks: () => installClaudeCodeHooks,
1590
1593
  installClineHookScripts: () => installClineHookScripts,
@@ -1593,15 +1596,18 @@ __export(hooks_config_exports, {
1593
1596
  installHookScripts: () => installHookScripts,
1594
1597
  installKiloCodeHookScripts: () => installKiloCodeHookScripts,
1595
1598
  installRooCodeHookScripts: () => installRooCodeHookScripts,
1599
+ installWindsurfHookScripts: () => installWindsurfHookScripts,
1596
1600
  markProjectIndexed: () => markProjectIndexed,
1597
1601
  mergeHooksIntoSettings: () => mergeHooksIntoSettings,
1598
1602
  readClaudeSettings: () => readClaudeSettings,
1599
1603
  readCursorHooksConfig: () => readCursorHooksConfig,
1600
1604
  readIndexStatus: () => readIndexStatus,
1605
+ readWindsurfHooksConfig: () => readWindsurfHooksConfig,
1601
1606
  unmarkProjectIndexed: () => unmarkProjectIndexed,
1602
1607
  writeClaudeSettings: () => writeClaudeSettings,
1603
1608
  writeCursorHooksConfig: () => writeCursorHooksConfig,
1604
- writeIndexStatus: () => writeIndexStatus
1609
+ writeIndexStatus: () => writeIndexStatus,
1610
+ writeWindsurfHooksConfig: () => writeWindsurfHooksConfig
1605
1611
  });
1606
1612
  import * as fs4 from "node:fs/promises";
1607
1613
  import * as fsSync from "node:fs";
@@ -1711,6 +1717,18 @@ function buildHooksConfig(options) {
1711
1717
  ]
1712
1718
  }
1713
1719
  ];
1720
+ config.PostCompact = [
1721
+ {
1722
+ matcher: "*",
1723
+ hooks: [
1724
+ {
1725
+ type: "command",
1726
+ command: getHookCommand("post-compact"),
1727
+ timeout: 15
1728
+ }
1729
+ ]
1730
+ }
1731
+ ];
1714
1732
  }
1715
1733
  if (options?.includeSessionInit !== false) {
1716
1734
  config.SessionStart = [
@@ -1725,6 +1743,102 @@ function buildHooksConfig(options) {
1725
1743
  ]
1726
1744
  }
1727
1745
  ];
1746
+ config.InstructionsLoaded = [
1747
+ {
1748
+ matcher: "*",
1749
+ hooks: [
1750
+ {
1751
+ type: "command",
1752
+ command: getHookCommand("instructions-loaded"),
1753
+ timeout: 10
1754
+ }
1755
+ ]
1756
+ }
1757
+ ];
1758
+ config.ConfigChange = [
1759
+ {
1760
+ matcher: "*",
1761
+ hooks: [
1762
+ {
1763
+ type: "command",
1764
+ command: getHookCommand("config-change"),
1765
+ timeout: 10
1766
+ }
1767
+ ]
1768
+ }
1769
+ ];
1770
+ config.CwdChanged = [
1771
+ {
1772
+ matcher: "*",
1773
+ hooks: [
1774
+ {
1775
+ type: "command",
1776
+ command: getHookCommand("cwd-changed"),
1777
+ timeout: 10
1778
+ }
1779
+ ]
1780
+ }
1781
+ ];
1782
+ config.FileChanged = [
1783
+ {
1784
+ matcher: ".*",
1785
+ hooks: [
1786
+ {
1787
+ type: "command",
1788
+ command: getHookCommand("file-changed"),
1789
+ timeout: 10
1790
+ }
1791
+ ]
1792
+ }
1793
+ ];
1794
+ config.WorktreeCreate = [
1795
+ {
1796
+ matcher: "*",
1797
+ hooks: [
1798
+ {
1799
+ type: "command",
1800
+ command: getHookCommand("worktree-create"),
1801
+ timeout: 15
1802
+ }
1803
+ ]
1804
+ }
1805
+ ];
1806
+ config.WorktreeRemove = [
1807
+ {
1808
+ matcher: "*",
1809
+ hooks: [
1810
+ {
1811
+ type: "command",
1812
+ command: getHookCommand("worktree-remove"),
1813
+ timeout: 15
1814
+ }
1815
+ ]
1816
+ }
1817
+ ];
1818
+ config.Elicitation = [
1819
+ {
1820
+ matcher: ".*",
1821
+ hooks: [
1822
+ {
1823
+ type: "command",
1824
+ command: getHookCommand("elicitation"),
1825
+ timeout: 10
1826
+ }
1827
+ ]
1828
+ }
1829
+ ];
1830
+ config.ElicitationResult = [
1831
+ {
1832
+ matcher: ".*",
1833
+ hooks: [
1834
+ {
1835
+ type: "command",
1836
+ command: getHookCommand("elicitation-result"),
1837
+ timeout: 10
1838
+ }
1839
+ ]
1840
+ }
1841
+ ];
1728
1842
  }
1729
1843
  if (options?.includeSessionEnd !== false) {
1730
1844
  config.Stop = [
@@ -1751,6 +1865,18 @@ function buildHooksConfig(options) {
1751
1865
  ]
1752
1866
  }
1753
1867
  ];
1868
+ config.StopFailure = [
1869
+ {
1870
+ matcher: "*",
1871
+ hooks: [
1872
+ {
1873
+ type: "command",
1874
+ command: getHookCommand("stop-failure"),
1875
+ timeout: 10
1876
+ }
1877
+ ]
1878
+ }
1879
+ ];
1754
1880
  }
1755
1881
  const postToolUseHooks = [];
1756
1882
  if (options?.includePostWrite !== false) {
@@ -1846,6 +1972,12 @@ function buildHooksConfig(options) {
1846
1972
  hooks: [{ type: "command", command: getHookCommand("subagent-stop"), timeout: 15 }]
1847
1973
  }
1848
1974
  ];
1975
+ config.TaskCreated = [
1976
+ {
1977
+ matcher: "*",
1978
+ hooks: [{ type: "command", command: getHookCommand("task-created"), timeout: 10 }]
1979
+ }
1980
+ ];
1849
1981
  config.TaskCompleted = [
1850
1982
  {
1851
1983
  matcher: "*",
@@ -1926,11 +2058,21 @@ async function installClaudeCodeHooks(options) {
1926
2058
  getHookCommand("user-prompt-submit"),
1927
2059
  getHookCommand("on-save-intent"),
1928
2060
  getHookCommand("session-start"),
2061
+ getHookCommand("instructions-loaded"),
2062
+ getHookCommand("config-change"),
2063
+ getHookCommand("cwd-changed"),
2064
+ getHookCommand("file-changed"),
2065
+ getHookCommand("worktree-create"),
2066
+ getHookCommand("worktree-remove"),
2067
+ getHookCommand("elicitation"),
2068
+ getHookCommand("elicitation-result"),
1929
2069
  getHookCommand("stop"),
2070
+ getHookCommand("stop-failure"),
1930
2071
  getHookCommand("session-end"),
1931
2072
  getHookCommand("post-tool-use-failure"),
1932
2073
  getHookCommand("subagent-start"),
1933
2074
  getHookCommand("subagent-stop"),
2075
+ getHookCommand("task-created"),
1934
2076
  getHookCommand("task-completed"),
1935
2077
  getHookCommand("teammate-idle"),
1936
2078
  getHookCommand("notification"),
@@ -1938,6 +2080,7 @@ async function installClaudeCodeHooks(options) {
1938
2080
  );
1939
2081
  if (options.includePreCompact !== false) {
1940
2082
  result.scripts.push(getHookCommand("pre-compact"));
2083
+ result.scripts.push(getHookCommand("post-compact"));
1941
2084
  }
1942
2085
  if (options.includeMediaAware === true) {
1943
2086
  result.scripts.push(getHookCommand("media-aware"));
@@ -2232,6 +2375,15 @@ function getCursorHooksDir(scope, projectPath) {
2232
2375
  }
2233
2376
  return path4.join(projectPath, ".cursor", "hooks");
2234
2377
  }
2378
+ function getWindsurfHooksConfigPath(scope, projectPath) {
2379
+ if (scope === "global") {
2380
+ return path4.join(homedir3(), ".codeium", "windsurf", "hooks.json");
2381
+ }
2382
+ if (!projectPath) {
2383
+ throw new Error("projectPath required for project scope");
2384
+ }
2385
+ return path4.join(projectPath, ".windsurf", "hooks.json");
2386
+ }
2235
2387
  async function readCursorHooksConfig(scope, projectPath) {
2236
2388
  const configPath = getCursorHooksConfigPath(scope, projectPath);
2237
2389
  try {
@@ -2258,17 +2410,31 @@ async function installCursorHookScripts(options) {
2258
2410
  return !hook.command?.includes("contextstream");
2259
2411
  });
2260
2412
  };
2261
- const filteredPreToolUse = filterContextStreamHooks(existingConfig.hooks.preToolUse);
2262
- const filteredBeforeSubmit = filterContextStreamHooks(existingConfig.hooks.beforeSubmitPrompt);
2413
+ const cleanedHooks = {};
2414
+ for (const [eventName, entries] of Object.entries(existingConfig.hooks || {})) {
2415
+ cleanedHooks[eventName] = filterContextStreamHooks(entries);
2416
+ }
2263
2417
  const preToolUseCommand = getHookCommand("pre-tool-use");
2264
2418
  const userPromptCommand = getHookCommand("user-prompt-submit");
2265
2419
  const saveIntentCommand = getHookCommand("on-save-intent");
2420
+ const postToolUseCommand = getHookCommand("post-tool-use");
2421
+ const postToolUseFailureCommand = getHookCommand("post-tool-use-failure");
2422
+ const preCompactCommand = getHookCommand("pre-compact");
2423
+ const sessionStartCommand = getHookCommand("session-start");
2424
+ const sessionEndCommand = getHookCommand("session-end");
2425
+ const stopCommand = getHookCommand("stop");
2426
+ const notificationCommand = getHookCommand("notification");
2427
+ const permissionRequestCommand = getHookCommand("permission-request");
2428
+ const subagentStartCommand = getHookCommand("subagent-start");
2429
+ const subagentStopCommand = getHookCommand("subagent-stop");
2430
+ const taskCompletedCommand = getHookCommand("task-completed");
2431
+ const teammateIdleCommand = getHookCommand("teammate-idle");
2266
2432
  const config = {
2267
2433
  version: 1,
2268
2434
  hooks: {
2269
- ...existingConfig.hooks,
2435
+ ...cleanedHooks,
2270
2436
  preToolUse: [
2271
- ...filteredPreToolUse,
2437
+ ...cleanedHooks.preToolUse || [],
2272
2438
  {
2273
2439
  command: preToolUseCommand,
2274
2440
  type: "command",
@@ -2276,7 +2442,7 @@ async function installCursorHookScripts(options) {
2276
2442
  }
2277
2443
  ],
2278
2444
  beforeSubmitPrompt: [
2279
- ...filteredBeforeSubmit,
2445
+ ...cleanedHooks.beforeSubmitPrompt || [],
2280
2446
  {
2281
2447
  command: userPromptCommand,
2282
2448
  type: "command",
@@ -2287,6 +2453,70 @@ async function installCursorHookScripts(options) {
2287
2453
  type: "command",
2288
2454
  timeout: 5
2289
2455
  }
2456
+ ],
2457
+ beforeMCPExecution: [
2458
+ ...cleanedHooks.beforeMCPExecution || [],
2459
+ { command: preToolUseCommand, type: "command", timeout: 5 }
2460
+ ],
2461
+ afterMCPExecution: [
2462
+ ...cleanedHooks.afterMCPExecution || [],
2463
+ { command: postToolUseCommand, type: "command", timeout: 10 }
2464
+ ],
2465
+ beforeShellExecution: [
2466
+ ...cleanedHooks.beforeShellExecution || [],
2467
+ { command: preToolUseCommand, type: "command", timeout: 5 }
2468
+ ],
2469
+ afterShellExecution: [
2470
+ ...cleanedHooks.afterShellExecution || [],
2471
+ { command: postToolUseFailureCommand, type: "command", timeout: 10 }
2472
+ ],
2473
+ beforeReadFile: [
2474
+ ...cleanedHooks.beforeReadFile || [],
2475
+ { command: preToolUseCommand, type: "command", timeout: 5 }
2476
+ ],
2477
+ afterFileEdit: [
2478
+ ...cleanedHooks.afterFileEdit || [],
2479
+ { command: postToolUseCommand, type: "command", timeout: 10 }
2480
+ ],
2481
+ preCompact: [
2482
+ ...cleanedHooks.preCompact || [],
2483
+ { command: preCompactCommand, type: "command", timeout: 15 }
2484
+ ],
2485
+ sessionStart: [
2486
+ ...cleanedHooks.sessionStart || [],
2487
+ { command: sessionStartCommand, type: "command", timeout: 15 }
2488
+ ],
2489
+ sessionEnd: [
2490
+ ...cleanedHooks.sessionEnd || [],
2491
+ { command: sessionEndCommand, type: "command", timeout: 10 }
2492
+ ],
2493
+ stop: [
2494
+ ...cleanedHooks.stop || [],
2495
+ { command: stopCommand, type: "command", timeout: 15 }
2496
+ ],
2497
+ subagentStart: [
2498
+ ...cleanedHooks.subagentStart || [],
2499
+ { command: subagentStartCommand, type: "command", timeout: 10 }
2500
+ ],
2501
+ subagentStop: [
2502
+ ...cleanedHooks.subagentStop || [],
2503
+ { command: subagentStopCommand, type: "command", timeout: 10 }
2504
+ ],
2505
+ taskCompleted: [
2506
+ ...cleanedHooks.taskCompleted || [],
2507
+ { command: taskCompletedCommand, type: "command", timeout: 10 }
2508
+ ],
2509
+ teammateIdle: [
2510
+ ...cleanedHooks.teammateIdle || [],
2511
+ { command: teammateIdleCommand, type: "command", timeout: 10 }
2512
+ ],
2513
+ notification: [
2514
+ ...cleanedHooks.notification || [],
2515
+ { command: notificationCommand, type: "command", timeout: 10 }
2516
+ ],
2517
+ permissionRequest: [
2518
+ ...cleanedHooks.permissionRequest || [],
2519
+ { command: permissionRequestCommand, type: "command", timeout: 10 }
2290
2520
  ]
2291
2521
  }
2292
2522
  };
@@ -2298,6 +2528,83 @@ async function installCursorHookScripts(options) {
2298
2528
  config: configPath
2299
2529
  };
2300
2530
  }
2531
+ async function readWindsurfHooksConfig(scope, projectPath) {
2532
+ const configPath = getWindsurfHooksConfigPath(scope, projectPath);
2533
+ try {
2534
+ const content = await fs4.readFile(configPath, "utf-8");
2535
+ return JSON.parse(content);
2536
+ } catch {
2537
+ return { hooks: {} };
2538
+ }
2539
+ }
2540
+ async function writeWindsurfHooksConfig(config, scope, projectPath) {
2541
+ const configPath = getWindsurfHooksConfigPath(scope, projectPath);
2542
+ const dir = path4.dirname(configPath);
2543
+ await fs4.mkdir(dir, { recursive: true });
2544
+ await fs4.writeFile(configPath, JSON.stringify(config, null, 2));
2545
+ }
2546
+ async function installWindsurfHookScripts(options) {
2547
+ const existingConfig = await readWindsurfHooksConfig(options.scope, options.projectPath);
2548
+ const filterContextStreamHooks = (hooks) => {
2549
+ if (!hooks) return [];
2550
+ return hooks.filter((h) => {
2551
+ const hook = h;
2552
+ return !hook.command?.toLowerCase().includes("contextstream");
2553
+ });
2554
+ };
2555
+ const cleanedHooks = {};
2556
+ for (const [eventName, entries] of Object.entries(existingConfig.hooks || {})) {
2557
+ cleanedHooks[eventName] = filterContextStreamHooks(entries);
2558
+ }
2559
+ const preToolUseCommand = getHookCommand("pre-tool-use");
2560
+ const userPromptCommand = getHookCommand("user-prompt-submit");
2561
+ const postToolUseCommand = getHookCommand("post-tool-use");
2562
+ const sessionEndCommand = getHookCommand("session-end");
2563
+ const config = {
2564
+ hooks: {
2565
+ ...cleanedHooks,
2566
+ pre_mcp_tool_use: [
2567
+ ...cleanedHooks.pre_mcp_tool_use || [],
2568
+ { command: preToolUseCommand, show_output: true }
2569
+ ],
2570
+ pre_user_prompt: [
2571
+ ...cleanedHooks.pre_user_prompt || [],
2572
+ { command: userPromptCommand }
2573
+ ],
2574
+ pre_read_code: [
2575
+ ...cleanedHooks.pre_read_code || [],
2576
+ { command: preToolUseCommand, show_output: true }
2577
+ ],
2578
+ pre_write_code: [
2579
+ ...cleanedHooks.pre_write_code || [],
2580
+ { command: preToolUseCommand, show_output: true }
2581
+ ],
2582
+ pre_run_command: [
2583
+ ...cleanedHooks.pre_run_command || [],
2584
+ { command: preToolUseCommand, show_output: true }
2585
+ ],
2586
+ post_write_code: [
2587
+ ...cleanedHooks.post_write_code || [],
2588
+ { command: postToolUseCommand, show_output: false }
2589
+ ],
2590
+ post_mcp_tool_use: [
2591
+ ...cleanedHooks.post_mcp_tool_use || [],
2592
+ { command: postToolUseCommand, show_output: false }
2593
+ ],
2594
+ post_cascade_response_with_transcript: [
2595
+ ...cleanedHooks.post_cascade_response_with_transcript || [],
2596
+ { command: sessionEndCommand }
2597
+ ]
2598
+ }
2599
+ };
2600
+ await writeWindsurfHooksConfig(config, options.scope, options.projectPath);
2601
+ const configPath = getWindsurfHooksConfigPath(options.scope, options.projectPath);
2602
+ return {
2603
+ preMcpToolUse: preToolUseCommand,
2604
+ preUserPrompt: userPromptCommand,
2605
+ config: configPath
2606
+ };
2607
+ }
2301
2608
  async function installEditorHooks(options) {
2302
2609
  const { editor, scope, projectPath, includePreCompact, includePostWrite } = options;
2303
2610
  switch (editor) {
@@ -2357,12 +2664,20 @@ async function installEditorHooks(options) {
2357
2664
  hooksDir: getCursorHooksDir(scope, projectPath)
2358
2665
  };
2359
2666
  }
2667
+ case "windsurf": {
2668
+ const scripts = await installWindsurfHookScripts({ scope, projectPath });
2669
+ return {
2670
+ editor: "windsurf",
2671
+ installed: [scripts.preMcpToolUse, scripts.preUserPrompt, scripts.config],
2672
+ hooksDir: path4.dirname(scripts.config)
2673
+ };
2674
+ }
2360
2675
  default:
2361
2676
  throw new Error(`Unsupported editor: ${editor}`);
2362
2677
  }
2363
2678
  }
2364
2679
  async function installAllEditorHooks(options) {
2365
- const editors = options.editors || ["claude", "cline", "roo", "kilo", "cursor"];
2680
+ const editors = options.editors || ["claude", "cline", "roo", "kilo", "cursor", "windsurf"];
2366
2681
  const results = [];
2367
2682
  for (const editor of editors) {
2368
2683
  try {
@@ -2390,8 +2705,9 @@ ContextStream can install hooks for multiple AI code editors to enforce ContextS
2390
2705
 
2391
2706
  | Editor | Hooks Location | Hook Types |
2392
2707
  |--------|---------------|------------|
2393
- | **Claude Code** | \`~/.claude/hooks/\` | PreToolUse, UserPromptSubmit, PreCompact |
2394
- | **Cursor** | \`~/.cursor/hooks/\` | preToolUse, beforeSubmit |
2708
+ | **Claude Code** | \`~/.claude/hooks/\` | PreToolUse, UserPromptSubmit, SessionStart, Pre/PostCompact, PostToolUse |
2709
+ | **Cursor** | \`~/.cursor/hooks/\` | preToolUse, beforeSubmitPrompt, before/after MCP+Shell, beforeReadFile, afterFileEdit |
2710
+ | **Windsurf** | \`~/.codeium/windsurf/hooks.json\` | pre_mcp_tool_use, pre_user_prompt, pre/post code + command hooks |
2395
2711
  | **Cline** | \`~/Documents/Cline/Rules/Hooks/\` | PreToolUse, UserPromptSubmit |
2396
2712
  | **Roo Code** | \`~/.roo/hooks/\` | PreToolUse, UserPromptSubmit |
2397
2713
  | **Kilo Code** | \`~/.kilocode/hooks/\` | PreToolUse, UserPromptSubmit |
@@ -2402,9 +2718,19 @@ ${generateHooksDocumentation()}
2402
2718
 
2403
2719
  ### Cursor Hooks
2404
2720
 
2405
- Cursor uses a \`hooks.json\` configuration file:
2406
- - **preToolUse**: Blocks discovery tools before execution
2407
- - **beforeSubmitPrompt**: Injects ContextStream rules reminder
2721
+ Cursor uses a \`hooks.json\` configuration file with enforcement + lifecycle hooks:
2722
+ - **preToolUse / beforeMCPExecution / beforeShellExecution / beforeReadFile**: blocks or redirects non-ContextStream-first flows
2723
+ - **beforeSubmitPrompt**: injects ContextStream rules reminder + save-intent capture
2724
+ - **afterMCPExecution / afterShellExecution / afterFileEdit**: post-action bookkeeping/index updates
2725
+ - **sessionStart / preCompact / stop / sessionEnd**: lifecycle persistence and reliability
2726
+
2727
+ ### Windsurf Hooks
2728
+
2729
+ Windsurf uses a \`hooks.json\` configuration file:
2730
+ - **pre_mcp_tool_use / pre_read_code / pre_write_code / pre_run_command**: pre-action gating and enforcement
2731
+ - **pre_user_prompt**: injects ContextStream-first guidance each prompt
2732
+ - **post_write_code / post_mcp_tool_use**: post-action indexing bookkeeping
2733
+ - **post_cascade_response_with_transcript**: end-of-response/session capture
2408
2734
 
2409
2735
  #### Output Format
2410
2736
  \`\`\`json
@@ -2434,7 +2760,7 @@ Hooks are executable scripts named after the hook type (no extension).
2434
2760
 
2435
2761
  ### Installation
2436
2762
 
2437
- Use \`generate_rules(install_hooks=true, editors=["claude", "cursor", "cline", "roo", "kilo"])\` to install hooks for specific editors, or omit \`editors\` to install for all.
2763
+ Use \`generate_rules(install_hooks=true, editors=["claude", "cursor", "windsurf", "cline", "roo", "kilo"])\` to install hooks for specific editors, or omit \`editors\` to install for all.
2438
2764
 
2439
2765
  ### Disabling Hooks
2440
2766
 
@@ -2443,7 +2769,7 @@ Set environment variables:
2443
2769
  - \`CONTEXTSTREAM_REMINDER_ENABLED=false\` - Disable UserPromptSubmit reminders
2444
2770
  `.trim();
2445
2771
  }
2446
- var PRETOOLUSE_HOOK_SCRIPT, USER_PROMPT_HOOK_SCRIPT, MEDIA_AWARE_HOOK_SCRIPT, PRECOMPACT_HOOK_SCRIPT, CLINE_PRETOOLUSE_HOOK_SCRIPT, CLINE_USER_PROMPT_HOOK_SCRIPT, CLINE_POSTTOOLUSE_HOOK_SCRIPT, CURSOR_PRETOOLUSE_HOOK_SCRIPT, CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT;
2772
+ var PRETOOLUSE_HOOK_SCRIPT, USER_PROMPT_HOOK_SCRIPT, MEDIA_AWARE_HOOK_SCRIPT, PRECOMPACT_HOOK_SCRIPT, CLAUDE_ENFORCEMENT_CRITICAL_HOOKS, CURSOR_ENFORCEMENT_CRITICAL_HOOKS, CLINE_PRETOOLUSE_HOOK_SCRIPT, CLINE_USER_PROMPT_HOOK_SCRIPT, CLINE_POSTTOOLUSE_HOOK_SCRIPT, CURSOR_PRETOOLUSE_HOOK_SCRIPT, CURSOR_BEFORE_SUBMIT_HOOK_SCRIPT;
2447
2773
  var init_hooks_config = __esm({
2448
2774
  "src/hooks-config.ts"() {
2449
2775
  "use strict";
@@ -2924,6 +3250,23 @@ After compaction, call session_init(is_post_compact=true) to restore context.
2924
3250
  if __name__ == "__main__":
2925
3251
  main()
2926
3252
  `;
3253
+ CLAUDE_ENFORCEMENT_CRITICAL_HOOKS = [
3254
+ "PreToolUse",
3255
+ "UserPromptSubmit",
3256
+ "SessionStart",
3257
+ "PreCompact",
3258
+ "PostToolUse"
3259
+ ];
3260
+ CURSOR_ENFORCEMENT_CRITICAL_HOOKS = [
3261
+ "preToolUse",
3262
+ "beforeSubmitPrompt",
3263
+ "beforeMCPExecution",
3264
+ "beforeShellExecution",
3265
+ "beforeReadFile",
3266
+ "afterFileEdit",
3267
+ "sessionStart",
3268
+ "preCompact"
3269
+ ];
2927
3270
  CLINE_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
2928
3271
  """
2929
3272
  ContextStream PreToolUse Hook for Cline
@@ -3427,6 +3770,7 @@ memory(
3427
3770
  | Persistent plan | \`session(action="capture_plan")\` |
3428
3771
  | Task status | \`memory(action="update_task")\` |
3429
3772
  | Decision capture | \`session(action="capture", event_type="decision")\` |
3773
+ | Update skill by name | \`skill(action="update", name="...", instruction_body="...", change_summary="...")\` |
3430
3774
  | Past context | \`session(action="recall", query="...")\` |
3431
3775
  | Lessons | \`session(action="get_lessons", query="...")\` |
3432
3776
 
@@ -3462,6 +3806,9 @@ ${options.workspaceId ? `# Workspace ID: ${options.workspaceId}` : ""}
3462
3806
  if (NO_HOOKS_EDITORS.includes(editor.toLowerCase())) {
3463
3807
  content += NO_HOOKS_SUPPLEMENT;
3464
3808
  }
3809
+ if (editor.toLowerCase() === "antigravity") {
3810
+ content += ANTIGRAVITY_SUPPLEMENT;
3811
+ }
3465
3812
  if (options?.additionalRules) {
3466
3813
  content += "\n\n## Project-Specific Rules\n\n" + options.additionalRules;
3467
3814
  }
@@ -3497,7 +3844,7 @@ function generateAllRuleFiles(options) {
3497
3844
  }))
3498
3845
  ).filter((r) => r !== null);
3499
3846
  }
3500
- var DEFAULT_CLAUDE_MCP_SERVER_NAME, RULES_VERSION, CONTEXTSTREAM_TOOL_NAMES, CONTEXTSTREAM_RULES_BOOTSTRAP, CONTEXTSTREAM_RULES_DYNAMIC, CONTEXTSTREAM_RULES_FULL, CONTEXTSTREAM_RULES_MINIMAL, NO_HOOKS_SUPPLEMENT, NO_HOOKS_EDITORS, TEMPLATES;
3847
+ var DEFAULT_CLAUDE_MCP_SERVER_NAME, RULES_VERSION, CONTEXTSTREAM_TOOL_NAMES, CONTEXTSTREAM_RULES_BOOTSTRAP, CONTEXTSTREAM_RULES_DYNAMIC, CONTEXTSTREAM_RULES_FULL, CONTEXTSTREAM_RULES_MINIMAL, NO_HOOKS_SUPPLEMENT, ANTIGRAVITY_SUPPLEMENT, NO_HOOKS_EDITORS, TEMPLATES;
3501
3848
  var init_rules_templates = __esm({
3502
3849
  "src/rules-templates.ts"() {
3503
3850
  "use strict";
@@ -4429,13 +4776,32 @@ After updating, user should restart their AI tool.
4429
4776
 
4430
4777
  ---
4431
4778
  `;
4432
- NO_HOOKS_EDITORS = ["codex", "aider", "antigravity"];
4779
+ ANTIGRAVITY_SUPPLEMENT = `
4780
+ ---
4781
+ ## Antigravity-Specific Reliability Notes
4782
+
4783
+ - Antigravity currently has no documented lifecycle hooks for ContextStream enforcement.
4784
+ - Treat ContextStream-first behavior as mandatory policy: run \`context(...)\` first, then \`search(mode="auto", ...)\` before local discovery.
4785
+ - Keep \`mcp_config.json\` valid and minimal: preserve non-ContextStream servers and only update the \`contextstream\` server block.
4786
+ - If ContextStream appears skipped, verify:
4787
+ 1. MCP server status is healthy in Antigravity settings
4788
+ 2. Project is indexed and \`search(mode="auto", ...)\` is retried before local fallbacks
4789
+ 3. Instructions file contains the current ContextStream managed block
4790
+ `;
4791
+ NO_HOOKS_EDITORS = ["copilot", "codex", "opencode", "aider", "antigravity"];
4433
4792
  TEMPLATES = {
4434
4793
  codex: {
4435
4794
  filename: "AGENTS.md",
4436
4795
  description: "Codex CLI agent instructions",
4437
4796
  build: (rules) => `# Codex CLI Instructions
4438
4797
  ${rules}
4798
+ `
4799
+ },
4800
+ opencode: {
4801
+ filename: "AGENTS.md",
4802
+ description: "OpenCode CLI agent instructions",
4803
+ build: (rules) => `# OpenCode CLI Instructions
4804
+ ${rules}
4439
4805
  `
4440
4806
  },
4441
4807
  cursor: {
@@ -4443,6 +4809,17 @@ ${rules}
4443
4809
  description: "Cursor AI rules",
4444
4810
  build: (rules) => `# Cursor Rules
4445
4811
  ${rules}
4812
+ `
4813
+ },
4814
+ windsurf: {
4815
+ filename: ".windsurf/rules/contextstream.md",
4816
+ description: "Windsurf AI rules",
4817
+ build: (rules) => `---
4818
+ trigger: always_on
4819
+ ---
4820
+
4821
+ # Windsurf Rules
4822
+ ${rules}
4446
4823
  `
4447
4824
  },
4448
4825
  cline: {
@@ -19536,6 +19913,68 @@ function formatRunResult(result) {
19536
19913
  ${result.output}`;
19537
19914
  return JSON.stringify(result, null, 2);
19538
19915
  }
19916
+ function computeSkillMatchScore(query, name, title) {
19917
+ const normalizedQuery = query.trim().toLowerCase();
19918
+ if (!normalizedQuery) return null;
19919
+ const normalizedName = String(name || "").trim().toLowerCase();
19920
+ const normalizedTitle = String(title || "").trim().toLowerCase();
19921
+ if (normalizedName === normalizedQuery) return 0;
19922
+ if (normalizedTitle === normalizedQuery) return 1;
19923
+ if (normalizedName.includes(normalizedQuery)) return 2;
19924
+ if (normalizedTitle.includes(normalizedQuery)) return 3;
19925
+ return null;
19926
+ }
19927
+ function rankSkillCandidates(query, items) {
19928
+ return items.map((item) => {
19929
+ const id = String(item?.id || "");
19930
+ const name = String(item?.name || "?");
19931
+ const title = String(item?.title || name);
19932
+ const score = computeSkillMatchScore(query, name, title);
19933
+ if (!id || score == null) return null;
19934
+ return {
19935
+ id,
19936
+ name,
19937
+ title,
19938
+ scope: String(item?.scope || "?"),
19939
+ status: String(item?.status || "?"),
19940
+ score
19941
+ };
19942
+ }).filter((item) => item !== null).sort((a, b) => {
19943
+ if (a.score !== b.score) return a.score - b.score;
19944
+ if (a.name !== b.name) return a.name.localeCompare(b.name);
19945
+ if (a.title !== b.title) return a.title.localeCompare(b.title);
19946
+ return a.id.localeCompare(b.id);
19947
+ });
19948
+ }
19949
+ async function resolveSkillIdForAction(client, input, action) {
19950
+ if (input.skill_id) return input.skill_id;
19951
+ const name = typeof input.name === "string" ? input.name.trim() : "";
19952
+ if (!name) {
19953
+ throw new Error(`Either skill_id or name is required for '${action}'`);
19954
+ }
19955
+ const result = await client.listSkills({
19956
+ workspace_id: input.workspace_id,
19957
+ project_id: input.project_id,
19958
+ query: name,
19959
+ limit: 25
19960
+ });
19961
+ const ranked = rankSkillCandidates(name, result.items || []);
19962
+ if (ranked.length === 0) {
19963
+ throw new Error(`Skill '${name}' not found. Provide a different name or skill_id.`);
19964
+ }
19965
+ const bestScore = ranked[0].score;
19966
+ const bestCandidates = ranked.filter((candidate) => candidate.score === bestScore);
19967
+ if (bestCandidates.length > 1) {
19968
+ const candidates = bestCandidates.slice(0, 5).map(
19969
+ (candidate) => `- ${candidate.title} (${candidate.name}) [${candidate.scope}|${candidate.status}] id=${candidate.id}`
19970
+ ).join("\n");
19971
+ throw new Error(
19972
+ `Skill name '${name}' is ambiguous. Provide skill_id or use a more specific name. Candidates:
19973
+ ${candidates}`
19974
+ );
19975
+ }
19976
+ return ranked[0].id;
19977
+ }
19539
19978
  function readStatNumber(payload, key) {
19540
19979
  if (!payload || typeof payload !== "object") return void 0;
19541
19980
  const direct = payload[key];
@@ -21935,20 +22374,8 @@ Actions:
21935
22374
  return { content: [{ type: "text", text }] };
21936
22375
  }
21937
22376
  case "get": {
21938
- let skillData;
21939
- if (input.skill_id) {
21940
- skillData = await client.getSkill(input.skill_id);
21941
- } else if (input.name) {
21942
- const result = await client.listSkills({
21943
- workspace_id: input.workspace_id,
21944
- query: input.name,
21945
- limit: 1
21946
- });
21947
- skillData = result.items?.find((s) => s.name === input.name) || result.items?.[0];
21948
- if (!skillData) throw new Error(`Skill '${input.name}' not found`);
21949
- } else {
21950
- throw new Error("Either skill_id or name is required for 'get'");
21951
- }
22377
+ const resolvedId = await resolveSkillIdForAction(client, input, "get");
22378
+ const skillData = await client.getSkill(resolvedId);
21952
22379
  const detail = formatSkillDetail(skillData);
21953
22380
  return { content: [{ type: "text", text: detail }] };
21954
22381
  }
@@ -21976,8 +22403,8 @@ Actions:
21976
22403
  return { content: [{ type: "text", text: `Skill '${input.name}' created (id=${result.id || "?"}).` }] };
21977
22404
  }
21978
22405
  case "update": {
21979
- if (!input.skill_id) throw new Error("'skill_id' is required for update");
21980
- const result = await client.updateSkill(input.skill_id, {
22406
+ const resolvedId = await resolveSkillIdForAction(client, input, "update");
22407
+ const result = await client.updateSkill(resolvedId, {
21981
22408
  title: input.title,
21982
22409
  description: input.description,
21983
22410
  instruction_body: input.instruction_body,
@@ -21991,21 +22418,10 @@ Actions:
21991
22418
  priority: input.priority,
21992
22419
  change_summary: input.change_summary
21993
22420
  });
21994
- return { content: [{ type: "text", text: `Skill ${input.skill_id} updated (version=${result.version || 0}).` }] };
22421
+ return { content: [{ type: "text", text: `Skill ${resolvedId} updated (version=${result.version || 0}).` }] };
21995
22422
  }
21996
22423
  case "run": {
21997
- let resolvedId = input.skill_id;
21998
- if (!resolvedId && input.name) {
21999
- const result2 = await client.listSkills({
22000
- workspace_id: input.workspace_id,
22001
- query: input.name,
22002
- limit: 1
22003
- });
22004
- const found = result2.items?.find((s) => s.name === input.name) || result2.items?.[0];
22005
- if (!found?.id) throw new Error(`Skill '${input.name}' not found`);
22006
- resolvedId = found.id;
22007
- }
22008
- if (!resolvedId) throw new Error("Either skill_id or name is required for 'run'");
22424
+ const resolvedId = await resolveSkillIdForAction(client, input, "run");
22009
22425
  const result = await client.runSkill(resolvedId, {
22010
22426
  params: input.params,
22011
22427
  dry_run: input.dry_run
@@ -22013,9 +22429,9 @@ Actions:
22013
22429
  return { content: [{ type: "text", text: formatRunResult(result) }] };
22014
22430
  }
22015
22431
  case "delete": {
22016
- if (!input.skill_id) throw new Error("'skill_id' is required for delete");
22017
- await client.deleteSkill(input.skill_id);
22018
- return { content: [{ type: "text", text: `Skill ${input.skill_id} deleted.` }] };
22432
+ const resolvedId = await resolveSkillIdForAction(client, input, "delete");
22433
+ await client.deleteSkill(resolvedId);
22434
+ return { content: [{ type: "text", text: `Skill ${resolvedId} deleted.` }] };
22019
22435
  }
22020
22436
  case "import": {
22021
22437
  let importContent = input.content;
@@ -22044,10 +22460,10 @@ Actions:
22044
22460
  return { content: [{ type: "text", text: result.content || JSON.stringify(result, null, 2) }] };
22045
22461
  }
22046
22462
  case "share": {
22047
- if (!input.skill_id) throw new Error("'skill_id' is required for share");
22048
22463
  if (!input.scope) throw new Error("'scope' is required for share");
22049
- const result = await client.shareSkill(input.skill_id, input.scope);
22050
- return { content: [{ type: "text", text: `Skill ${input.skill_id} shared with scope=${result.scope || input.scope}.` }] };
22464
+ const resolvedId = await resolveSkillIdForAction(client, input, "share");
22465
+ const result = await client.shareSkill(resolvedId, input.scope);
22466
+ return { content: [{ type: "text", text: `Skill ${resolvedId} shared with scope=${result.scope || input.scope}.` }] };
22051
22467
  }
22052
22468
  default:
22053
22469
  throw new Error(`Invalid skill action: '${action}'. Valid: list, get, create, update, run, delete, import, export, share`);
@@ -24223,6 +24639,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
24223
24639
  ).length;
24224
24640
  const skippedCount = results.filter((r) => r.status.startsWith("skipped")).length;
24225
24641
  const baseMessage = input.dry_run ? "Dry run complete. Use dry_run: false to write files." : skippedCount > 0 ? `Generated ${createdCount} rule files. ${skippedCount} skipped (existing files). Re-run with overwrite_existing: true to replace ContextStream blocks.` : `Generated ${createdCount} rule files.`;
24642
+ const copilotSetupNote = editors.includes("copilot") ? "Select Copilot in setup; setup handles both ~/.copilot/mcp-config.json and project .vscode/mcp.json automatically." : void 0;
24226
24643
  const globalPrompt = input.apply_global ? "Global rule update complete." : globalTargets.length > 0 ? "Apply rules globally too? Re-run with apply_global: true." : "No global rule locations are known for these editors.";
24227
24644
  let hooksResults;
24228
24645
  let hooksPrompt;
@@ -24231,7 +24648,8 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
24231
24648
  cline: "cline",
24232
24649
  roo: "roo",
24233
24650
  kilo: "kilo",
24234
- cursor: "cursor"
24651
+ cursor: "cursor",
24652
+ windsurf: "windsurf"
24235
24653
  };
24236
24654
  const hookSupportedEditors = editors.filter((e) => e in editorHookMap);
24237
24655
  const shouldInstallHooks = hookSupportedEditors.length > 0 && input.install_hooks !== false;
@@ -24270,6 +24688,10 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
24270
24688
  { editor, file: "~/.cursor/hooks/contextstream-beforesubmit.py", status: "dry run - would create" },
24271
24689
  { editor, file: "~/.cursor/hooks.json", status: "dry run - would update" }
24272
24690
  );
24691
+ } else if (editor === "windsurf") {
24692
+ hooksResults.push(
24693
+ { editor, file: "~/.codeium/windsurf/hooks.json", status: "dry run - would update" }
24694
+ );
24273
24695
  }
24274
24696
  }
24275
24697
  } else {
@@ -24298,6 +24720,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
24298
24720
  ...globalTargets.length > 0 ? { global_targets: globalTargets } : {},
24299
24721
  ...hooksResults ? { hooks_results: hooksResults } : {},
24300
24722
  message: baseMessage,
24723
+ ...copilotSetupNote ? { copilot_setup_note: copilotSetupNote } : {},
24301
24724
  global_prompt: globalPrompt,
24302
24725
  ...hooksPrompt ? { hooks_prompt: hooksPrompt } : {}
24303
24726
  };
@@ -24388,7 +24811,10 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
24388
24811
  const summary = {
24389
24812
  folder: folderPath,
24390
24813
  results,
24391
- message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : skippedCount > 0 ? `Generated ${createdCount} rule files. ${skippedCount} skipped (existing files). Re-run with overwrite_existing: true to replace ContextStream blocks.` : `Generated ${createdCount} rule files.`
24814
+ message: input.dry_run ? "Dry run complete. Use dry_run: false to write files." : skippedCount > 0 ? `Generated ${createdCount} rule files. ${skippedCount} skipped (existing files). Re-run with overwrite_existing: true to replace ContextStream blocks.` : `Generated ${createdCount} rule files.`,
24815
+ ...editors.includes("copilot") ? {
24816
+ copilot_setup_note: "Select Copilot in setup; setup handles both ~/.copilot/mcp-config.json and project .vscode/mcp.json automatically."
24817
+ } : {}
24392
24818
  };
24393
24819
  return {
24394
24820
  content: [{ type: "text", text: formatContent(summary) }]
@@ -30546,6 +30972,7 @@ This will:
30546
30972
  - Start a 5-day Pro trial
30547
30973
  - Auto-configure your editor's MCP settings
30548
30974
  - Write rules files for better AI assistance
30975
+ - If you select Copilot, setup automatically writes both ~/.copilot/mcp-config.json and project .vscode/mcp.json
30549
30976
 
30550
30977
  Preview first:
30551
30978
  npx --prefer-online -y @contextstream/mcp-server@latest setup --dry-run
@@ -32387,9 +32814,11 @@ init_hooks_config();
32387
32814
  init_files();
32388
32815
  var EDITOR_LABELS = {
32389
32816
  codex: "Codex CLI",
32817
+ copilot: "GitHub Copilot (VS Code)",
32390
32818
  opencode: "OpenCode",
32391
32819
  claude: "Claude Code",
32392
32820
  cursor: "Cursor / VS Code",
32821
+ windsurf: "Windsurf",
32393
32822
  cline: "Cline",
32394
32823
  kilo: "Kilo Code",
32395
32824
  roo: "Roo Code",
@@ -32397,7 +32826,7 @@ var EDITOR_LABELS = {
32397
32826
  antigravity: "Antigravity (Google)"
32398
32827
  };
32399
32828
  function supportsProjectMcpConfig(editor) {
32400
- return editor === "opencode" || editor === "cursor" || editor === "claude" || editor === "kilo" || editor === "roo" || editor === "antigravity";
32829
+ return editor === "opencode" || editor === "copilot" || editor === "cursor" || editor === "claude" || editor === "kilo" || editor === "roo";
32401
32830
  }
32402
32831
  function normalizeInput(value) {
32403
32832
  return value.trim();
@@ -32468,6 +32897,7 @@ var CONTEXTSTREAM_PREAMBLE_PATTERNS2 = [
32468
32897
  /^#\s+codex cli instructions$/i,
32469
32898
  /^#\s+claude code instructions$/i,
32470
32899
  /^#\s+cursor rules$/i,
32900
+ /^#\s+windsurf rules$/i,
32471
32901
  /^#\s+cline rules$/i,
32472
32902
  /^#\s+kilo code rules$/i,
32473
32903
  /^#\s+roo code rules$/i,
@@ -32608,6 +33038,10 @@ function globalRulesPathForEditor(editor) {
32608
33038
  switch (editor) {
32609
33039
  case "codex":
32610
33040
  return path8.join(home, ".codex", "AGENTS.md");
33041
+ case "copilot":
33042
+ return null;
33043
+ case "opencode":
33044
+ return path8.join(home, ".opencode", "AGENTS.md");
32611
33045
  case "claude":
32612
33046
  return path8.join(home, ".claude", "CLAUDE.md");
32613
33047
  case "cline":
@@ -32622,6 +33056,8 @@ function globalRulesPathForEditor(editor) {
32622
33056
  return path8.join(home, ".gemini", "GEMINI.md");
32623
33057
  case "cursor":
32624
33058
  return null;
33059
+ case "windsurf":
33060
+ return path8.join(home, ".codeium", "windsurf", "memories", "global_rules.md");
32625
33061
  default:
32626
33062
  return null;
32627
33063
  }
@@ -32687,6 +33123,16 @@ async function isClineInstalled() {
32687
33123
  ];
32688
33124
  return anyPathExists(candidates);
32689
33125
  }
33126
+ async function isCopilotInstalled() {
33127
+ const home = homedir6();
33128
+ const candidates = [
33129
+ path8.join(home, ".copilot"),
33130
+ path8.join(home, ".config", "github-copilot"),
33131
+ path8.join(home, ".vscode", "extensions", "github.copilot-chat"),
33132
+ path8.join(home, ".vscode", "extensions", "github.copilot")
33133
+ ];
33134
+ return anyPathExists(candidates);
33135
+ }
32690
33136
  async function isKiloInstalled() {
32691
33137
  const home = homedir6();
32692
33138
  const candidates = [path8.join(home, ".kilocode"), path8.join(home, ".config", "kilocode")];
@@ -32725,6 +33171,29 @@ async function isCursorInstalled() {
32725
33171
  }
32726
33172
  return anyPathExists(candidates);
32727
33173
  }
33174
+ async function isWindsurfInstalled() {
33175
+ const home = homedir6();
33176
+ const candidates = [path8.join(home, ".codeium", "windsurf")];
33177
+ if (process.platform === "darwin") {
33178
+ candidates.push("/Applications/Windsurf.app");
33179
+ candidates.push(path8.join(home, "Applications", "Windsurf.app"));
33180
+ candidates.push(path8.join(home, "Library", "Application Support", "Windsurf"));
33181
+ } else if (process.platform === "win32") {
33182
+ const localApp = process.env.LOCALAPPDATA;
33183
+ const programFiles = process.env.ProgramFiles;
33184
+ const programFilesX86 = process.env["ProgramFiles(x86)"];
33185
+ if (localApp) candidates.push(path8.join(localApp, "Programs", "Windsurf", "Windsurf.exe"));
33186
+ if (localApp) candidates.push(path8.join(localApp, "Windsurf", "Windsurf.exe"));
33187
+ if (programFiles) candidates.push(path8.join(programFiles, "Windsurf", "Windsurf.exe"));
33188
+ if (programFilesX86) candidates.push(path8.join(programFilesX86, "Windsurf", "Windsurf.exe"));
33189
+ } else {
33190
+ candidates.push("/usr/bin/windsurf");
33191
+ candidates.push("/usr/local/bin/windsurf");
33192
+ candidates.push("/opt/Windsurf");
33193
+ candidates.push("/opt/windsurf");
33194
+ }
33195
+ return anyPathExists(candidates);
33196
+ }
32728
33197
  async function isAntigravityInstalled() {
32729
33198
  const home = homedir6();
32730
33199
  const candidates = [path8.join(home, ".gemini")];
@@ -32754,12 +33223,16 @@ async function isEditorInstalled(editor) {
32754
33223
  switch (editor) {
32755
33224
  case "codex":
32756
33225
  return isCodexInstalled();
33226
+ case "copilot":
33227
+ return isCopilotInstalled();
32757
33228
  case "opencode":
32758
33229
  return isOpenCodeInstalled();
32759
33230
  case "claude":
32760
33231
  return isClaudeInstalled();
32761
33232
  case "cursor":
32762
33233
  return isCursorInstalled();
33234
+ case "windsurf":
33235
+ return isWindsurfInstalled();
32763
33236
  case "cline":
32764
33237
  return isClineInstalled();
32765
33238
  case "kilo":
@@ -33559,7 +34032,7 @@ Code: ${device.user_code}`);
33559
34032
  );
33560
34033
  }
33561
34034
  }
33562
- const NO_HOOKS_EDITORS2 = ["codex", "opencode", "aider", "antigravity"];
34035
+ const NO_HOOKS_EDITORS2 = ["codex", "copilot", "opencode", "aider", "antigravity"];
33563
34036
  const getModeForEditor = (editor) => NO_HOOKS_EDITORS2.includes(editor) ? "full" : "bootstrap";
33564
34037
  const detectedPlanName = await client.getPlanName();
33565
34038
  const detectedGraphTier = await client.getGraphTier();
@@ -33586,9 +34059,11 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
33586
34059
  }
33587
34060
  const editors = [
33588
34061
  "codex",
34062
+ "copilot",
33589
34063
  "opencode",
33590
34064
  "claude",
33591
34065
  "cursor",
34066
+ "windsurf",
33592
34067
  "cline",
33593
34068
  "kilo",
33594
34069
  "roo",
@@ -33658,6 +34133,28 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
33658
34133
  )
33659
34134
  ) || mcpChoiceDefault;
33660
34135
  const mcpScope = mcpChoice === "2" && hasCodex && !hasProjectMcpEditors ? "skip" : mcpChoice === "4" ? "skip" : mcpChoice === "1" ? "global" : mcpChoice === "2" ? "project" : "both";
34136
+ const copilotSelected = configuredEditors.includes("copilot");
34137
+ let enforceCopilotCanonicalPair = copilotSelected && mcpScope !== "skip";
34138
+ if (enforceCopilotCanonicalPair) {
34139
+ const confirmCopilotPair = normalizeInput(
34140
+ await rl.question(
34141
+ "Configure Copilot canonical MCP files (~/.copilot/mcp-config.json + .vscode/mcp.json)? [Y/n]: "
34142
+ )
34143
+ ).toLowerCase();
34144
+ enforceCopilotCanonicalPair = confirmCopilotPair !== "n" && confirmCopilotPair !== "no";
34145
+ }
34146
+ if (enforceCopilotCanonicalPair) {
34147
+ console.log(
34148
+ "\nCopilot automation enabled: setup will write both ~/.copilot/mcp-config.json and project .vscode/mcp.json (no separate Copilot command needed)."
34149
+ );
34150
+ console.log(
34151
+ "If you installed the Rust runtime, replace the generated command with `contextstream-mcp` in both files."
34152
+ );
34153
+ } else if (copilotSelected && mcpScope !== "skip") {
34154
+ console.log(
34155
+ "\nCopilot canonical pair disabled for this run. You can rerun setup anytime to generate both files together."
34156
+ );
34157
+ }
33661
34158
  const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, contextPackEnabled });
33662
34159
  const mcpServerClaude = buildContextStreamMcpServer({ apiUrl, apiKey, contextPackEnabled });
33663
34160
  const mcpServerOpenCode = buildContextStreamOpenCodeLocalServer({
@@ -33674,7 +34171,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
33674
34171
  " OpenCode reads CONTEXTSTREAM_API_KEY from your environment. Export it before launching OpenCode."
33675
34172
  );
33676
34173
  };
33677
- const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex;
34174
+ const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex || enforceCopilotCanonicalPair;
33678
34175
  if (needsGlobalMcpConfig) {
33679
34176
  console.log("\nInstalling global MCP config...");
33680
34177
  for (const editor of configuredEditors) {
@@ -33696,6 +34193,18 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
33696
34193
  console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
33697
34194
  continue;
33698
34195
  }
34196
+ if (editor === "copilot") {
34197
+ const filePath = path8.join(homedir6(), ".copilot", "mcp-config.json");
34198
+ if (dryRun) {
34199
+ writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
34200
+ console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
34201
+ continue;
34202
+ }
34203
+ const status = await upsertJsonMcpConfig(filePath, mcpServer);
34204
+ writeActions.push({ kind: "mcp-config", target: filePath, status });
34205
+ console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
34206
+ continue;
34207
+ }
33699
34208
  if (editor === "claude") {
33700
34209
  const desktopPath = claudeDesktopConfigPath();
33701
34210
  if (desktopPath) {
@@ -33751,6 +34260,18 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
33751
34260
  console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
33752
34261
  continue;
33753
34262
  }
34263
+ if (editor === "windsurf") {
34264
+ const filePath = path8.join(homedir6(), ".codeium", "windsurf", "mcp_config.json");
34265
+ if (dryRun) {
34266
+ writeActions.push({ kind: "mcp-config", target: filePath, status: "dry-run" });
34267
+ console.log(`- ${EDITOR_LABELS[editor]}: would update ${filePath}`);
34268
+ continue;
34269
+ }
34270
+ const status = await upsertJsonMcpConfig(filePath, mcpServer);
34271
+ writeActions.push({ kind: "mcp-config", target: filePath, status });
34272
+ console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
34273
+ continue;
34274
+ }
33754
34275
  if (editor === "cline") {
33755
34276
  console.log(
33756
34277
  `- ${EDITOR_LABELS[editor]}: MCP config is managed via the extension UI (skipping global).`
@@ -33776,11 +34297,14 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
33776
34297
  const HOOKS_SUPPORTED_EDITORS = {
33777
34298
  claude: "claude",
33778
34299
  cursor: "cursor",
34300
+ windsurf: "windsurf",
33779
34301
  cline: "cline",
33780
34302
  roo: "roo",
33781
34303
  kilo: "kilo",
33782
34304
  codex: null,
33783
34305
  // No hooks API
34306
+ copilot: null,
34307
+ // No hooks API
33784
34308
  opencode: null,
33785
34309
  // No hooks API
33786
34310
  aider: null,
@@ -33809,6 +34333,16 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
33809
34333
  writeActions.push({ kind: "hooks", target: script, status: "created" });
33810
34334
  console.log(`- ${EDITOR_LABELS[editor]}: installed ${path8.basename(script)}`);
33811
34335
  }
34336
+ if (editor === "cursor") {
34337
+ console.log(
34338
+ " Cursor hooks enabled: preToolUse, beforeSubmitPrompt, before/after MCP+shell, beforeReadFile, afterFileEdit, session lifecycle"
34339
+ );
34340
+ }
34341
+ if (editor === "windsurf") {
34342
+ console.log(
34343
+ " Windsurf hooks enabled: pre_mcp_tool_use, pre_user_prompt, pre/post code+command hooks, post_cascade_response_with_transcript"
34344
+ );
34345
+ }
33812
34346
  } catch (err) {
33813
34347
  const message = err instanceof Error ? err.message : String(err);
33814
34348
  console.log(`- ${EDITOR_LABELS[editor]}: failed to install hooks: ${message}`);
@@ -33816,6 +34350,19 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
33816
34350
  }
33817
34351
  console.log(" Disable hooks anytime with CONTEXTSTREAM_HOOK_ENABLED=false");
33818
34352
  }
34353
+ const noHookEditors = configuredEditors.filter((e) => HOOKS_SUPPORTED_EDITORS[e] === null);
34354
+ if (noHookEditors.length > 0) {
34355
+ console.log("\nEditors without lifecycle hooks (rules-only enforcement):");
34356
+ for (const editor of noHookEditors) {
34357
+ if (editor === "antigravity") {
34358
+ console.log(
34359
+ `- ${EDITOR_LABELS[editor]}: no hooks API available; relying on strict ContextStream rules + index/search discipline.`
34360
+ );
34361
+ } else {
34362
+ console.log(`- ${EDITOR_LABELS[editor]}: no hooks API available; using rules-only enforcement.`);
34363
+ }
34364
+ }
34365
+ }
33819
34366
  console.log("\nCode Privacy:");
33820
34367
  console.log(" \u2713 Encrypted in transit (TLS 1.3) and at rest (AES-256)");
33821
34368
  console.log(" \u2713 Isolated per workspace \u2014 no cross-tenant access");
@@ -33823,12 +34370,6 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
33823
34370
  if (scope === "global" || scope === "both") {
33824
34371
  console.log("\nInstalling global rules...");
33825
34372
  for (const editor of configuredEditors) {
33826
- if (editor === "opencode") {
33827
- console.log(
33828
- `- ${EDITOR_LABELS[editor]}: rules are not auto-generated yet (MCP config only).`
33829
- );
33830
- continue;
33831
- }
33832
34373
  const filePath = globalRulesPathForEditor(editor);
33833
34374
  if (!filePath) {
33834
34375
  console.log(
@@ -33859,7 +34400,7 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
33859
34400
  }
33860
34401
  }
33861
34402
  const projectPaths = /* @__PURE__ */ new Set();
33862
- const needsProjects = scope === "project" || scope === "both" || (mcpScope === "project" || mcpScope === "both") && hasProjectMcpEditors;
34403
+ const needsProjects = scope === "project" || scope === "both" || (mcpScope === "project" || mcpScope === "both") && hasProjectMcpEditors || enforceCopilotCanonicalPair;
33863
34404
  if (needsProjects) {
33864
34405
  console.log("\nProject setup...");
33865
34406
  const addCwd = normalizeInput(
@@ -33942,8 +34483,10 @@ Applying to ${projects.length} project(s)...`);
33942
34483
  status: "dry-run"
33943
34484
  });
33944
34485
  }
33945
- if (mcpScope === "project" || mcpScope === "both") {
34486
+ if (mcpScope === "project" || mcpScope === "both" || enforceCopilotCanonicalPair) {
33946
34487
  for (const editor of configuredEditors) {
34488
+ const shouldWriteProjectMcp = mcpScope === "project" || mcpScope === "both" || enforceCopilotCanonicalPair && editor === "copilot";
34489
+ if (!shouldWriteProjectMcp) continue;
33947
34490
  try {
33948
34491
  if (editor === "cursor") {
33949
34492
  const cursorPath = path8.join(projectPath, ".cursor", "mcp.json");
@@ -33959,6 +34502,12 @@ Applying to ${projects.length} project(s)...`);
33959
34502
  }
33960
34503
  continue;
33961
34504
  }
34505
+ if (editor === "windsurf") {
34506
+ console.log(
34507
+ `- ${EDITOR_LABELS[editor]}: uses global MCP config only (${path8.join(homedir6(), ".codeium", "windsurf", "mcp_config.json")}).`
34508
+ );
34509
+ continue;
34510
+ }
33962
34511
  if (editor === "claude") {
33963
34512
  const mcpPath = path8.join(projectPath, ".mcp.json");
33964
34513
  if (dryRun) {
@@ -33984,6 +34533,16 @@ Applying to ${projects.length} project(s)...`);
33984
34533
  printOpenCodeEnvNote();
33985
34534
  continue;
33986
34535
  }
34536
+ if (editor === "copilot") {
34537
+ const vsCodePath = path8.join(projectPath, ".vscode", "mcp.json");
34538
+ if (dryRun) {
34539
+ writeActions.push({ kind: "mcp-config", target: vsCodePath, status: "dry-run" });
34540
+ } else {
34541
+ const status = await upsertJsonVsCodeMcpConfig(vsCodePath, vsCodeServer);
34542
+ writeActions.push({ kind: "mcp-config", target: vsCodePath, status });
34543
+ }
34544
+ continue;
34545
+ }
33987
34546
  if (editor === "kilo") {
33988
34547
  const kiloPath = path8.join(projectPath, ".kilocode", "mcp.json");
33989
34548
  if (dryRun) {
@@ -34015,7 +34574,6 @@ Applying to ${projects.length} project(s)...`);
34015
34574
  for (const editor of selectedEditors) {
34016
34575
  if (scope !== "project" && scope !== "both") continue;
34017
34576
  if (!configuredEditors.includes(editor)) continue;
34018
- if (editor === "opencode") continue;
34019
34577
  const rule = generateRuleContent(editor, {
34020
34578
  workspaceName,
34021
34579
  workspaceId: workspaceId && workspaceId !== "dry-run" ? workspaceId : void 0,
@@ -34139,6 +34697,16 @@ Applying to ${projects.length} project(s)...`);
34139
34697
  console.log(
34140
34698
  "- For UI-based MCP setup (Cline/Kilo/Roo global), see https://contextstream.io/docs/mcp"
34141
34699
  );
34700
+ if (copilotSelected) {
34701
+ const nodeCommand = IS_WINDOWS ? "cmd /c npx --prefer-online -y @contextstream/mcp-server@latest" : "npx --prefer-online -y @contextstream/mcp-server@latest";
34702
+ console.log("\nGitHub Copilot + VS Code checklist:");
34703
+ console.log("- Confirm ~/.copilot/mcp-config.json has a contextstream server entry.");
34704
+ console.log("- Confirm .vscode/mcp.json has a contextstream server entry.");
34705
+ console.log(`- Node runtime command: ${nodeCommand}`);
34706
+ console.log(
34707
+ "- If Rust MCP is installed, replace the command in both files with `contextstream-mcp` and set args to []."
34708
+ );
34709
+ }
34142
34710
  console.log("");
34143
34711
  console.log(
34144
34712
  "You're all set! ContextStream gives your AI persistent memory, semantic code search, and cross-session context."