@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/CHANGELOG.md +76 -1693
- package/README.md +92 -0
- package/USAGE.md +138 -2
- package/lib/cli/companion.py +137 -0
- package/lib/cli/generate.py +53 -3
- package/lib/cli/init.py +38 -9
- package/lib/darkarts/companion_files.py +299 -0
- package/package.json +2 -1
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
|
-
|
|
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")
|
package/lib/cli/generate.py
CHANGED
|
@@ -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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
|