@voodocs/cli 2.2.2 → 2.3.0

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/README.md CHANGED
@@ -91,6 +91,86 @@ def authenticate_user(user_id: str, password: str) -> Optional[str]:
91
91
 
92
92
  ---
93
93
 
94
+ ## Companion Files for Compiled Languages
95
+
96
+ **NEW in v2.3.0!** For compiled languages like Solidity, Rust, or C++ where inline annotations may interfere with compilation, VooDocs supports **companion documentation files**.
97
+
98
+ ### What are Companion Files?
99
+
100
+ Companion files are `.voodocs.md` files that live alongside your source files, containing rich documentation without modifying the source code.
101
+
102
+ ```
103
+ contracts/
104
+ ├── SubdomainRegistry.sol ← Source code
105
+ ├── SubdomainRegistry.voodocs.md ← Companion documentation
106
+ ├── DomainMarketplace.sol ← Source code
107
+ ├── DomainMarketplace.voodocs.md ← Companion documentation
108
+ ```
109
+
110
+ ### Creating Companion Files
111
+
112
+ Use the `companion` command to generate templates:
113
+
114
+ ```bash
115
+ # Create companion file for a single file
116
+ voodocs companion contracts/Registry.sol
117
+
118
+ # Create companion files for all Solidity files
119
+ voodocs companion contracts/ -r
120
+
121
+ # Preview what would be created
122
+ voodocs companion contracts/ -r --dry-run
123
+ ```
124
+
125
+ ### Companion File Format
126
+
127
+ Companion files use Markdown with VooDocs symbolic notation:
128
+
129
+ ```markdown
130
+ # SubdomainRegistry.voodocs.md
131
+
132
+ ## Purpose
133
+ ⊢{4-level naming hierarchy with tiered subdomain allocation}
134
+
135
+ ## Architecture
136
+ - **Depends On**: IPricingEngine, IERC721
137
+ - **Depended By**: ThreeNSFacet, DomainMarketplace
138
+ - **Storage**: PostgreSQL (cache), L1 (source of truth)
139
+
140
+ ## Invariants
141
+ ⊨{L1 is always the source of truth}
142
+ ⊨{Personal names destroyed on subdomain transfer}
143
+ ⊨{resolve() must check expiration before returning owner}
144
+
145
+ ## Assumptions
146
+ ⊲{Block timestamp is reliable for expiration checks}
147
+ ⊲{Gas costs are acceptable for array operations}
148
+
149
+ ## Critical Sections
150
+ - `_burnPersonalNames()` - Must be called before any transfer
151
+ - `resolve()` - Must check expiration before returning owner
152
+
153
+ ## Security Considerations
154
+ ⊨{Reentrancy protection on all external calls}
155
+ ⊨{Owner validation before subdomain operations}
156
+ ```
157
+
158
+ ### Generating Documentation with Companion Files
159
+
160
+ ```bash
161
+ voodocs generate contracts/ docs/ --companion-files
162
+ ```
163
+
164
+ ### Benefits
165
+
166
+ ✅ **No Compilation Issues** - Documentation lives in separate files
167
+ ✅ **Rich Markdown** - Use tables, diagrams, code examples, links
168
+ ✅ **Version Control** - Track documentation changes alongside code
169
+ ✅ **IDE Friendly** - View source and docs side-by-side
170
+ ✅ **AI-Ready** - Same context quality as inline annotations
171
+
172
+ ---
173
+
94
174
  ## Symbolic Format Reference
95
175
 
96
176
  ### Module-Level Annotations
@@ -171,6 +251,17 @@ voodocs fix ./src --dry-run # Preview changes
171
251
  voodocs fix ./src --backup # Create backups
172
252
  ```
173
253
 
254
+ ### `voodocs companion`
255
+
256
+ Create companion documentation files for source files:
257
+
258
+ ```bash
259
+ voodocs companion contracts/ # Create for single file
260
+ voodocs companion contracts/ -r # Recursive
261
+ voodocs companion contracts/ --dry-run # Preview changes
262
+ voodocs companion contracts/ --force # Overwrite existing
263
+ ```
264
+
174
265
  ### `voodocs generate`
175
266
 
176
267
  Generate documentation from annotations:
@@ -179,6 +270,7 @@ Generate documentation from annotations:
179
270
  voodocs generate ./src ./docs # Generate docs
180
271
  voodocs generate ./src ./docs -r # Recursive
181
272
  voodocs generate ./src ./docs --validate # Validate first
273
+ voodocs generate ./src ./docs --companion-files # Use companion files
182
274
  voodocs generate ./src ./docs --format json # JSON output
183
275
  ```
184
276
 
package/USAGE.md CHANGED
@@ -13,6 +13,7 @@ This guide provides a comprehensive overview of VooDocs, its commands, and best
13
13
  - [CLI Commands](#cli-commands)
14
14
  - [init](#init)
15
15
  - [instruct](#instruct)
16
+ - [companion](#companion)
16
17
  - [generate](#generate)
17
18
  - [status](#status)
18
19
  - [watch](#watch)
@@ -98,13 +99,81 @@ voodocs instruct [--assistant <name>] [--output <file>]
98
99
  - `--assistant`: The AI assistant to generate instructions for (`cursor`, `claude`, `copilot`, `windsurf`, `generic`).
99
100
  - `--output`: The output file path (defaults to `.cursorrules`, `.claude/instructions.md`, etc.).
100
101
 
102
+ ### `companion`
103
+
104
+ **NEW in v2.3.0!** Create companion documentation files (`.voodocs.md`) for source files. Useful for compiled languages like Solidity, Rust, or C++ where inline annotations may interfere with compilation.
105
+
106
+ **Usage**:
107
+ ```bash
108
+ voodocs companion <source> [options]
109
+ ```
110
+
111
+ **Arguments**:
112
+ - `<source>`: Source file or directory to create companion files for.
113
+
114
+ **Options**:
115
+ - `-r, --recursive`: Recursively process all files in subdirectories.
116
+ - `--force`: Overwrite existing companion files.
117
+ - `--dry-run`: Show what would be created without creating files.
118
+
119
+ **Examples**:
120
+ ```bash
121
+ # Create companion file for a single Solidity contract
122
+ voodocs companion contracts/Registry.sol
123
+
124
+ # Create companion files for all Solidity contracts
125
+ voodocs companion contracts/ -r
126
+
127
+ # Preview what would be created
128
+ voodocs companion contracts/ -r --dry-run
129
+
130
+ # Force overwrite existing companion files
131
+ voodocs companion contracts/ -r --force
132
+ ```
133
+
134
+ **Companion File Format**:
135
+
136
+ Companion files use Markdown with structured sections:
137
+
138
+ ```markdown
139
+ # FileName.voodocs.md
140
+
141
+ ## Purpose
142
+ ⊢{Module purpose description}
143
+
144
+ ## Architecture
145
+ - **Depends On**: Dependency1, Dependency2
146
+ - **Depended By**: Parent1, Parent2
147
+ - **Storage**: Storage information
148
+
149
+ ## Invariants
150
+ ⊨{Invariant 1}
151
+ ⊨{Invariant 2}
152
+
153
+ ## Assumptions
154
+ ⊲{Assumption 1}
155
+
156
+ ## Critical Sections
157
+ - `functionName()` - Description
158
+
159
+ ## Security Considerations
160
+ ⊨{Security requirement}
161
+ ```
162
+
163
+ **Benefits**:
164
+ - ✅ No compilation interference
165
+ - ✅ Rich Markdown formatting (tables, diagrams, links)
166
+ - ✅ Version control alongside source files
167
+ - ✅ IDE-friendly (side-by-side viewing)
168
+ - ✅ Same AI context quality as inline annotations
169
+
101
170
  ### `generate`
102
171
 
103
172
  Generate docs, tests, and API specs from annotations.
104
173
 
105
174
  **Usage**:
106
175
  ```bash
107
- voodocs generate <path> [--output <dir>] [--docs-only] [--tests-only] [--api-only] [--verbose]
176
+ voodocs generate <path> [--output <dir>] [--docs-only] [--tests-only] [--api-only] [--companion-files] [--verbose]
108
177
  ```
109
178
 
110
179
  **Arguments**:
@@ -113,8 +182,21 @@ voodocs generate <path> [--output <dir>] [--docs-only] [--tests-only] [--api-onl
113
182
  - `--docs-only`: Generate only documentation.
114
183
  - `--tests-only`: Generate only tests.
115
184
  - `--api-only`: Generate only API specs.
185
+ - `--companion-files`: **NEW in v2.3.0!** Scan for and use `.voodocs.md` companion files alongside source files.
116
186
  - `--verbose`: Show detailed output.
117
187
 
188
+ **Examples**:
189
+ ```bash
190
+ # Generate documentation from inline annotations
191
+ voodocs generate src/ docs/
192
+
193
+ # Generate documentation using companion files (for Solidity, Rust, etc.)
194
+ voodocs generate contracts/ docs/ --companion-files
195
+
196
+ # Generate with validation
197
+ voodocs generate src/ docs/ --validate
198
+ ```
199
+
118
200
  ### `status`
119
201
 
120
202
  Show project status and statistics.
@@ -318,7 +400,61 @@ class BankAccount:
318
400
  self.balance += amount
319
401
  ```
320
402
 
321
- See the `examples/` directory for more complete examples in Python and TypeScript.
403
+ ### Solidity Contract with Companion File
404
+
405
+ **NEW in v2.3.0!** For compiled languages, use companion files:
406
+
407
+ **contracts/SubdomainRegistry.sol**:
408
+ ```solidity
409
+ // SPDX-License-Identifier: MIT
410
+ pragma solidity ^0.8.0;
411
+
412
+ contract SubdomainRegistry {
413
+ mapping(uint256 => address) public owners;
414
+ mapping(uint256 => uint256) public expirations;
415
+
416
+ function register(uint256 nameId, address owner, uint256 duration) public {
417
+ require(owners[nameId] == address(0), "Already registered");
418
+ owners[nameId] = owner;
419
+ expirations[nameId] = block.timestamp + duration;
420
+ }
421
+
422
+ function resolve(uint256 nameId) public view returns (address) {
423
+ require(block.timestamp < expirations[nameId], "Expired");
424
+ return owners[nameId];
425
+ }
426
+ }
427
+ ```
428
+
429
+ **contracts/SubdomainRegistry.voodocs.md**:
430
+ ```markdown
431
+ # SubdomainRegistry.voodocs.md
432
+
433
+ ## Purpose
434
+ ⊢{Subdomain registry with expiration management}
435
+
436
+ ## Invariants
437
+ ⊨{Only one owner per subdomain}
438
+ ⊨{resolve() must check expiration before returning owner}
439
+ ⊨{Expired subdomains cannot be resolved}
440
+
441
+ ## Assumptions
442
+ ⊲{Block timestamp is reliable for expiration checks}
443
+ ⊲{nameId is unique and generated off-chain}
444
+
445
+ ## Security Considerations
446
+ ⊨{Owner validation before subdomain operations}
447
+ ⊨{Zero address checks for all owner parameters}
448
+ ⊨{Expiration checks prevent stale data}
449
+ ```
450
+
451
+ **Generate documentation**:
452
+ ```bash
453
+ voodocs companion contracts/ -r
454
+ voodocs generate contracts/ docs/ --companion-files
455
+ ```
456
+
457
+ See the `examples/` directory for more complete examples in Python, TypeScript, and Solidity.
322
458
 
323
459
  ---
324
460
 
@@ -0,0 +1,137 @@
1
+ """
2
+ ⊢cli:companion
3
+ ∂{click,pathlib,typing}
4
+ ⚠{python≥3.7,click≥8.0}
5
+ ⊨{∀template→valid_markdown}
6
+ 🔒{write:files}
7
+ ⚡{O(n)|n=files}
8
+ """
9
+
10
+ """
11
+ VooDocs CLI - Companion Command
12
+
13
+ Creates .voodocs.md companion files for source files.
14
+ """
15
+
16
+ import sys
17
+ import click
18
+ from pathlib import Path
19
+ from typing import List
20
+
21
+ sys.path.insert(0, str(Path(__file__).parent.parent))
22
+ from darkarts.companion_files import CompanionFileScanner
23
+
24
+
25
+ @click.command()
26
+ @click.argument('source', type=click.Path(exists=True), required=True)
27
+ @click.option('-r', '--recursive', is_flag=True, help='Recursively process all files')
28
+ @click.option('--force', is_flag=True, help='Overwrite existing companion files')
29
+ @click.option('--dry-run', is_flag=True, help='Show what would be created without creating files')
30
+ def companion(
31
+ source: str,
32
+ recursive: bool,
33
+ force: bool,
34
+ dry_run: bool
35
+ ):
36
+ """
37
+ Create .voodocs.md companion files for source files.
38
+
39
+ Companion files allow you to add rich documentation alongside source files
40
+ without modifying the source code. This is especially useful for compiled
41
+ languages like Solidity where inline annotations may interfere with compilation.
42
+
43
+ Examples:
44
+
45
+ # Create companion file for a single file
46
+ voodocs companion contracts/Registry.sol
47
+
48
+ # Create companion files for all Solidity files
49
+ voodocs companion contracts/ -r
50
+
51
+ # Preview what would be created
52
+ voodocs companion contracts/ -r --dry-run
53
+
54
+ # Overwrite existing companion files
55
+ voodocs companion contracts/ -r --force
56
+ """
57
+
58
+ source_path = Path(source)
59
+
60
+ # Collect source files
61
+ files_to_process: List[Path] = []
62
+
63
+ if source_path.is_file():
64
+ files_to_process = [source_path]
65
+ elif source_path.is_dir():
66
+ extensions = ['*.py', '*.ts', '*.js', '*.jsx', '*.tsx', '*.sol']
67
+
68
+ if recursive:
69
+ for ext in extensions:
70
+ files_to_process.extend([f for f in source_path.glob(f"**/{ext}") if f.is_file()])
71
+ else:
72
+ for ext in extensions:
73
+ files_to_process.extend([f for f in source_path.glob(ext) if f.is_file()])
74
+
75
+ if not files_to_process:
76
+ click.secho("No source files found.", fg='yellow')
77
+ sys.exit(1)
78
+
79
+ click.echo(f"Found {len(files_to_process)} source file(s)")
80
+ click.echo()
81
+
82
+ if dry_run:
83
+ click.secho("DRY RUN MODE - No files will be created", fg='cyan')
84
+ click.echo()
85
+
86
+ created_count = 0
87
+ skipped_count = 0
88
+
89
+ for file_path in files_to_process:
90
+ companion_path = file_path.parent / f"{file_path.stem}{CompanionFileScanner.COMPANION_EXTENSION}"
91
+
92
+ if companion_path.exists() and not force:
93
+ click.echo(f"⏭️ {file_path.name} (companion already exists)")
94
+ skipped_count += 1
95
+ continue
96
+
97
+ if dry_run:
98
+ if companion_path.exists():
99
+ click.echo(f"🔄 {file_path.name} → {companion_path.name} (would overwrite)")
100
+ else:
101
+ click.echo(f"✨ {file_path.name} → {companion_path.name} (would create)")
102
+ created_count += 1
103
+ else:
104
+ template = CompanionFileScanner.create_template(file_path)
105
+ companion_path.write_text(template, encoding='utf-8')
106
+
107
+ if force and companion_path.exists():
108
+ click.echo(f"🔄 {file_path.name} → {companion_path.name} (overwritten)")
109
+ else:
110
+ click.echo(f"✨ {file_path.name} → {companion_path.name}")
111
+
112
+ created_count += 1
113
+
114
+ click.echo()
115
+ click.echo("━" * 60)
116
+
117
+ if dry_run:
118
+ click.echo(f"Would create: {created_count}")
119
+ click.echo(f"Would skip: {skipped_count}")
120
+ else:
121
+ click.secho(f"Created: {created_count}", fg='green')
122
+ click.echo(f"Skipped: {skipped_count}")
123
+
124
+ click.echo("━" * 60)
125
+
126
+ if not dry_run and created_count > 0:
127
+ click.echo()
128
+ click.secho("✅ Companion files created!", fg='green')
129
+ click.echo()
130
+ click.echo("Next steps:")
131
+ click.echo(" 1. Edit the .voodocs.md files to add documentation")
132
+ click.echo(" 2. Run: voodocs generate --companion-files")
133
+ click.echo()
134
+ click.echo("Tip: Companion files support:")
135
+ click.echo(" • Rich Markdown formatting (tables, diagrams, links)")
136
+ click.echo(" • VooDocs symbolic notation (⊢{}, ⊨{}, ⊲{})")
137
+ click.echo(" • Version control alongside source files")
@@ -33,6 +33,7 @@ from darkarts.validation.performance_wrapper import PerformanceTracker
33
33
  @click.option('--strict', is_flag=True, help='Fail if validation fails')
34
34
  @click.option('--format', type=click.Choice(['markdown', 'html', 'json']), default='markdown', help='Output format')
35
35
  @click.option('--include-private', is_flag=True, help='Include private members in documentation')
36
+ @click.option('--companion-files', is_flag=True, help='Scan for .voodocs.md companion files alongside source files')
36
37
  def generate(
37
38
  source: str,
38
39
  output: str,
@@ -40,7 +41,8 @@ def generate(
40
41
  validate: bool,
41
42
  strict: bool,
42
43
  format: str,
43
- include_private: bool
44
+ include_private: bool,
45
+ companion_files: bool
44
46
  ):
45
47
  """
46
48
  Generate documentation from @darkarts annotations.
@@ -132,6 +134,29 @@ def generate(
132
134
 
133
135
  click.echo(f"Found {len(files_to_process)} files to process")
134
136
 
137
+ # Scan for companion files if requested
138
+ companion_mapping = {}
139
+ if companion_files:
140
+ click.echo()
141
+ click.secho("Scanning for companion files (.voodocs.md)...", fg='cyan')
142
+ from darkarts.companion_files import CompanionFileScanner
143
+
144
+ companion_count = 0
145
+ for file_path in files_to_process:
146
+ companion = CompanionFileScanner.find_companion_file(file_path)
147
+ if companion:
148
+ companion_mapping[file_path] = companion
149
+ companion_count += 1
150
+ click.echo(f" 📄 {file_path.name} → {companion.name}")
151
+
152
+ click.echo()
153
+ if companion_count > 0:
154
+ click.secho(f"✅ Found {companion_count} companion file(s)", fg='green')
155
+ else:
156
+ click.secho("⚠️ No companion files found", fg='yellow')
157
+ click.echo(" Tip: Create .voodocs.md files alongside your source files")
158
+ click.echo(" Example: contracts/Registry.sol → contracts/Registry.voodocs.md")
159
+
135
160
  # Validate if requested
136
161
  if validate:
137
162
  click.echo()
@@ -179,8 +204,14 @@ def generate(
179
204
  generated_files = []
180
205
  for file_path in files_to_process:
181
206
  try:
207
+ # Parse companion file if available
208
+ companion_data = None
209
+ if companion_files and file_path in companion_mapping:
210
+ from darkarts.companion_files import CompanionFileScanner
211
+ companion_data = CompanionFileScanner.parse_companion_file(companion_mapping[file_path])
212
+
182
213
  # Generate documentation for this file
183
- doc_content = _generate_doc_for_file(file_path, format, include_private)
214
+ doc_content = _generate_doc_for_file(file_path, format, include_private, companion_data)
184
215
 
185
216
  # Determine output filename
186
217
  if source_path.is_dir():
@@ -291,7 +322,7 @@ def generate(
291
322
  sys.exit(1)
292
323
 
293
324
 
294
- def _generate_doc_for_file(file_path: Path, format: str, include_private: bool) -> str:
325
+ def _generate_doc_for_file(file_path: Path, format: str, include_private: bool, companion_data: dict = None) -> str:
295
326
  """Generate documentation for a single file."""
296
327
  content = file_path.read_text(encoding='utf-8')
297
328
 
@@ -309,6 +340,25 @@ def _generate_doc_for_file(file_path: Path, format: str, include_private: bool)
309
340
  security = _extract_section(annotation, '🔒')
310
341
  performance = _extract_section(annotation, '⚡')
311
342
 
343
+ # Merge with companion file data if available
344
+ if companion_data:
345
+ if companion_data.get('purpose'):
346
+ module_id = companion_data['purpose']
347
+ if companion_data.get('dependencies'):
348
+ dependencies = ', '.join(companion_data['dependencies'])
349
+ if companion_data.get('invariants'):
350
+ inv_list = invariants.split('\n') if invariants else []
351
+ inv_list.extend(companion_data['invariants'])
352
+ invariants = '\n'.join(filter(None, inv_list))
353
+ if companion_data.get('assumptions'):
354
+ assume_list = assumptions.split('\n') if assumptions else []
355
+ assume_list.extend(companion_data['assumptions'])
356
+ assumptions = '\n'.join(filter(None, assume_list))
357
+ if companion_data.get('security'):
358
+ sec_list = security.split('\n') if security else []
359
+ sec_list.extend(companion_data['security'])
360
+ security = '\n'.join(filter(None, sec_list))
361
+
312
362
  # Generate documentation based on format
313
363
  if format == 'markdown':
314
364
  return _generate_markdown(file_path, module_id, dependencies, assumptions, invariants, security, performance)
package/lib/cli/init.py CHANGED
@@ -153,10 +153,10 @@ def init(non_interactive, upgrade):
153
153
  setup_ai = click.confirm('Update AI instructions?', default=False)
154
154
  if setup_ai:
155
155
  ai_choice = click.prompt(
156
- 'Which AI assistant?',
157
- type=click.Choice(['cursor', 'claude', 'default'], case_sensitive=False),
158
- default=detected_ai or 'cursor'
159
- )
156
+ 'Which AI assistant?',
157
+ type=click.Choice(['cursor', 'claude', 'copilot', 'windsurf', 'cline', 'all'], case_sensitive=False),
158
+ default=detected_ai or 'cursor'
159
+ )
160
160
  else:
161
161
  ai_choice = None
162
162
  else:
@@ -173,10 +173,10 @@ def init(non_interactive, upgrade):
173
173
  setup_ai = click.confirm('Generate AI instructions for your coding assistant?', default=True)
174
174
  if setup_ai:
175
175
  ai_choice = click.prompt(
176
- 'Which AI assistant?',
177
- type=click.Choice(['cursor', 'claude', 'default'], case_sensitive=False),
178
- default=detected_ai or 'cursor'
179
- )
176
+ 'Which AI assistant?',
177
+ type=click.Choice(['cursor', 'claude', 'copilot', 'windsurf', 'cline', 'all'], case_sensitive=False),
178
+ default=detected_ai or 'cursor'
179
+ )
180
180
  else:
181
181
  ai_choice = None
182
182
 
@@ -411,7 +411,12 @@ def _check_ai_instructions(cwd: Path) -> bool:
411
411
  """Check if AI instructions exist."""
412
412
  return (
413
413
  (cwd / '.cursorrules').exists() or
414
+ (cwd / '.cursor' / 'rules').exists() or
414
415
  (cwd / '.claude' / 'instructions.md').exists() or
416
+ (cwd / '.claude' / 'skills').exists() or
417
+ (cwd / '.github' / 'copilot-instructions.md').exists() or
418
+ (cwd / '.windsurfrules').exists() or
419
+ (cwd / '.clinerules').exists() or
415
420
  (cwd / 'AI_INSTRUCTIONS.md').exists()
416
421
  )
417
422
 
@@ -421,8 +426,18 @@ def _get_ai_file_status(cwd: Path) -> str:
421
426
  files = []
422
427
  if (cwd / '.cursorrules').exists():
423
428
  files.append('.cursorrules')
429
+ if (cwd / '.cursor' / 'rules').exists():
430
+ files.append('.cursor/rules/')
424
431
  if (cwd / '.claude' / 'instructions.md').exists():
425
432
  files.append('.claude/instructions.md')
433
+ if (cwd / '.claude' / 'skills').exists():
434
+ files.append('.claude/skills/')
435
+ if (cwd / '.github' / 'copilot-instructions.md').exists():
436
+ files.append('.github/copilot-instructions.md')
437
+ if (cwd / '.windsurfrules').exists():
438
+ files.append('.windsurfrules')
439
+ if (cwd / '.clinerules').exists():
440
+ files.append('.clinerules')
426
441
  if (cwd / 'AI_INSTRUCTIONS.md').exists():
427
442
  files.append('AI_INSTRUCTIONS.md')
428
443
 
@@ -510,12 +525,26 @@ def _detect_repository(cwd: Path) -> Optional[str]:
510
525
 
511
526
  def _detect_ai_environment() -> Optional[str]:
512
527
  """Detect which AI environment is being used."""
513
- if os.environ.get('CURSOR_USER') or Path('.cursorrules').exists():
528
+ # Check for Cursor
529
+ if os.environ.get('CURSOR_USER') or Path('.cursorrules').exists() or Path('.cursor').exists():
514
530
  return 'cursor'
515
531
 
532
+ # Check for Claude Code
516
533
  if os.environ.get('CLAUDE_API_KEY') or Path('.claude').exists():
517
534
  return 'claude'
518
535
 
536
+ # Check for Windsurf
537
+ if Path('.windsurfrules').exists():
538
+ return 'windsurf'
539
+
540
+ # Check for Cline
541
+ if Path('.clinerules').exists():
542
+ return 'cline'
543
+
544
+ # Check for GitHub Copilot (less reliable, check last)
545
+ if Path('.github/copilot-instructions.md').exists():
546
+ return 'copilot'
547
+
519
548
  return None
520
549
 
521
550