@voodocs/cli 2.5.1 → 2.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [2.5.3] - 2024-12-24
4
+
5
+ ### Fixed - CRITICAL BUG
6
+ - **Convert Command File Corruption**: Fixed critical bug where `voodocs convert` emptied all files
7
+ - Root cause: `standard_to_lite()` and `lite_to_standard()` only converted annotation blocks, not entire files
8
+ - Added `convert_file_standard_to_lite()` and `convert_file_lite_to_standard()` methods
9
+ - These new methods find and replace annotations while preserving all other file content
10
+ - Updated `convert.py` to use file-level conversion methods
11
+ - **Safety Checks**: Added validation to prevent file corruption
12
+ - Reject conversions that result in nearly empty files (<10 chars)
13
+ - Reject conversions that shrink file size by >90%
14
+ - Display warnings when safety checks trigger
15
+
16
+ **IMPORTANT**: If you used `voodocs convert` from v2.5.2, restore your files from git immediately!
17
+
18
+ ---
19
+
20
+ ## [2.5.2] - 2024-12-24
21
+
22
+ ### Fixed
23
+ - **CLI Registration**: Fixed analyze, convert, and companion commands not being registered
24
+ - Converted `analyze.py` from argparse to Click command
25
+ - Created `convert.py` as Click command for VooDocs Lite conversion
26
+ - Updated `lib/cli/__init__.py` to import and register all three commands
27
+ - Updated `__version__` from "2.1.3" to "2.5.2" in Python code
28
+ - v2.5.0 and v2.5.1 had files in package but commands weren't accessible via CLI
29
+ - All Priority Analyzer and VooDocs Lite features now fully functional
30
+
31
+ ---
32
+
3
33
  ## [2.5.1] - 2024-12-24
4
34
 
5
35
  ### Fixed
@@ -16,7 +16,7 @@ This module provides the command-line interface for VooDocs.
16
16
  import click
17
17
  from typing import Optional
18
18
 
19
- __version__ = "2.1.3"
19
+ __version__ = "2.5.3"
20
20
 
21
21
 
22
22
  @click.group()
@@ -41,6 +41,9 @@ from .generate import generate
41
41
  from .benchmark import benchmark
42
42
  from .fix import fix
43
43
  from .context import context
44
+ from .analyze import analyze
45
+ from .convert import convert
46
+ from .companion import companion
44
47
 
45
48
  # Register commands
46
49
  cli.add_command(init)
@@ -50,6 +53,9 @@ cli.add_command(generate)
50
53
  cli.add_command(benchmark)
51
54
  cli.add_command(fix)
52
55
  cli.add_command(context)
56
+ cli.add_command(analyze)
57
+ cli.add_command(convert)
58
+ cli.add_command(companion)
53
59
 
54
60
 
55
61
  def main():
@@ -1,29 +1,96 @@
1
+ """@darkarts
2
+ ⊢cli:analyze
3
+ ∂{click,pathlib,darkarts.priority_analyzer}
4
+ ⚠{python≥3.7,click≥8.0}
5
+ ⊨{∀file→analyzed,∀score→calculated}
6
+ 🔒{read-only:source-files}
7
+ ⚡{O(n):files}
1
8
  """
2
- CLI command for VooDocs Priority Analyzer
3
9
 
4
- Usage: voodocs analyze [path] [options]
5
10
  """
11
+ VooDocs Analyze Command
6
12
 
13
+ Analyze files to identify which need VooDocs annotations most urgently.
14
+ """
15
+
16
+ import click
7
17
  import os
8
18
  import sys
19
+ import json
20
+ import csv
9
21
  from pathlib import Path
10
22
 
11
23
  # Add parent directory to path for imports
12
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
24
+ sys.path.insert(0, str(Path(__file__).parent.parent))
25
+ from darkarts.priority_analyzer.analyzer import PriorityAnalyzer
13
26
 
14
- from lib.darkarts.priority_analyzer.analyzer import PriorityAnalyzer
15
27
 
16
-
17
- def cmd_analyze(args):
18
- """Execute the analyze command."""
19
- # Get path (default to current directory)
20
- path = args.path if hasattr(args, 'path') and args.path else '.'
28
+ @click.command()
29
+ @click.argument(
30
+ 'path',
31
+ type=click.Path(exists=True),
32
+ default='.',
33
+ required=False
34
+ )
35
+ @click.option(
36
+ '-r', '--recursive',
37
+ is_flag=True,
38
+ default=True,
39
+ help='Recursively analyze directories (default: true)'
40
+ )
41
+ @click.option(
42
+ '--top',
43
+ type=int,
44
+ default=10,
45
+ help='Show top N files (default: 10, 0 for all)'
46
+ )
47
+ @click.option(
48
+ '--priority',
49
+ type=click.Choice(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'MINIMAL'], case_sensitive=False),
50
+ help='Filter by priority level'
51
+ )
52
+ @click.option(
53
+ '--min-score',
54
+ type=int,
55
+ help='Minimum priority score to show'
56
+ )
57
+ @click.option(
58
+ '--format',
59
+ 'output_format',
60
+ type=click.Choice(['text', 'json', 'csv', 'markdown']),
61
+ default='text',
62
+ help='Output format (default: text)'
63
+ )
64
+ @click.option(
65
+ '--include-annotated',
66
+ is_flag=True,
67
+ help='Include fully annotated files'
68
+ )
69
+ @click.option(
70
+ '--exclude',
71
+ help='Comma-separated list of patterns to exclude'
72
+ )
73
+ def analyze(path, recursive, top, priority, min_score, output_format, include_annotated, exclude):
74
+ """
75
+ Analyze files to identify which need VooDocs annotations most urgently.
76
+
77
+ Uses heuristics based on:
78
+ - File complexity (LOC, cyclomatic complexity, function count)
79
+ - Security keywords (auth, password, token, etc.)
80
+ - Import/dependency count
81
+
82
+ Examples:
83
+ voodocs analyze src/
84
+ voodocs analyze src/ --top 20
85
+ voodocs analyze src/ --priority critical
86
+ voodocs analyze src/ --format json
87
+ """
21
88
  path = os.path.abspath(path)
22
89
 
23
90
  # Check if path exists
24
91
  if not os.path.exists(path):
25
- print(f"❌ Error: Path not found: {path}")
26
- return 1
92
+ click.echo(f"❌ Error: Path not found: {path}", err=True)
93
+ sys.exit(1)
27
94
 
28
95
  # Determine if path is file or directory
29
96
  is_file = os.path.isfile(path)
@@ -36,38 +103,34 @@ def cmd_analyze(args):
36
103
  if is_file:
37
104
  scores = [analyzer.analyze_file(path)]
38
105
  else:
39
- recursive = getattr(args, 'recursive', True)
40
- exclude = getattr(args, 'exclude', None)
41
106
  exclude_patterns = exclude.split(',') if exclude else None
42
107
 
43
- print(f"🔍 Analyzing files in: {path}")
44
- if recursive:
45
- print(" (recursive mode)")
46
- print()
108
+ if output_format == 'text':
109
+ click.echo(f"🔍 Analyzing files in: {path}")
110
+ if recursive:
111
+ click.echo(" (recursive mode)")
112
+ click.echo()
47
113
 
48
114
  scores = analyzer.analyze_directory(path, recursive, exclude_patterns)
49
115
 
50
116
  # Filter by priority level if specified
51
- if hasattr(args, 'priority') and args.priority:
52
- priority_filter = args.priority.upper()
117
+ if priority:
118
+ priority_filter = priority.upper()
53
119
  scores = [s for s in scores if s.priority_level == priority_filter]
54
120
 
55
121
  # Filter by minimum score
56
- if hasattr(args, 'min_score') and args.min_score:
57
- scores = [s for s in scores if s.priority_score >= args.min_score]
122
+ if min_score:
123
+ scores = [s for s in scores if s.priority_score >= min_score]
58
124
 
59
125
  # Filter out fully annotated files (unless --include-annotated)
60
- if not getattr(args, 'include_annotated', False):
126
+ if not include_annotated:
61
127
  scores = [s for s in scores if s.annotation_coverage < 1.0]
62
128
 
63
129
  # Limit to top N
64
- top_n = getattr(args, 'top', 10)
65
- if top_n and top_n > 0:
66
- scores = scores[:top_n]
67
-
68
- # Output format
69
- output_format = getattr(args, 'format', 'text')
130
+ if top and top > 0:
131
+ scores = scores[:top]
70
132
 
133
+ # Output
71
134
  if output_format == 'json':
72
135
  _output_json(scores)
73
136
  elif output_format == 'csv':
@@ -77,14 +140,14 @@ def cmd_analyze(args):
77
140
  else:
78
141
  _output_text(scores, path)
79
142
 
80
- return 0
143
+ sys.exit(0)
81
144
 
82
145
 
83
146
  def _output_text(scores, path):
84
147
  """Output results in human-readable text format."""
85
148
  if not scores:
86
- print("✅ No files need annotations!")
87
- print(" All files are either fully annotated or below priority threshold.")
149
+ click.echo("✅ No files need annotations!")
150
+ click.echo(" All files are either fully annotated or below priority threshold.")
88
151
  return
89
152
 
90
153
  # Calculate statistics
@@ -101,34 +164,34 @@ def _output_text(scores, path):
101
164
  coverage_pct = (annotated_count / total_files * 100) if total_files > 0 else 0
102
165
 
103
166
  # Header
104
- print("━" * 70)
105
- print("VooDocs Priority Analysis")
106
- print("━" * 70)
107
- print()
108
- print(f"Project: {path}")
109
- print(f"Files analyzed: {total_files}")
110
- print(f"Annotations found: {annotated_count} ({coverage_pct:.0f}% coverage)")
111
- print()
167
+ click.echo("━" * 70)
168
+ click.echo("VooDocs Priority Analysis")
169
+ click.echo("━" * 70)
170
+ click.echo()
171
+ click.echo(f"Project: {path}")
172
+ click.echo(f"Files analyzed: {total_files}")
173
+ click.echo(f"Annotations found: {annotated_count} ({coverage_pct:.0f}% coverage)")
174
+ click.echo()
112
175
 
113
176
  # Priority distribution
114
- print("Priority Distribution:")
177
+ click.echo("Priority Distribution:")
115
178
  if priority_dist['CRITICAL'] > 0:
116
- print(f"🔴 CRITICAL (80-100): {priority_dist['CRITICAL']} files")
179
+ click.echo(f"🔴 CRITICAL (80-100): {priority_dist['CRITICAL']} files")
117
180
  if priority_dist['HIGH'] > 0:
118
- print(f"🟠 HIGH (60-79): {priority_dist['HIGH']} files")
181
+ click.echo(f"🟠 HIGH (60-79): {priority_dist['HIGH']} files")
119
182
  if priority_dist['MEDIUM'] > 0:
120
- print(f"🟡 MEDIUM (40-59): {priority_dist['MEDIUM']} files")
183
+ click.echo(f"🟡 MEDIUM (40-59): {priority_dist['MEDIUM']} files")
121
184
  if priority_dist['LOW'] > 0:
122
- print(f"🟢 LOW (20-39): {priority_dist['LOW']} files")
185
+ click.echo(f"🟢 LOW (20-39): {priority_dist['LOW']} files")
123
186
  if priority_dist['MINIMAL'] > 0:
124
- print(f"⚪ MINIMAL (0-19): {priority_dist['MINIMAL']} files")
125
- print()
187
+ click.echo(f"⚪ MINIMAL (0-19): {priority_dist['MINIMAL']} files")
188
+ click.echo()
126
189
 
127
190
  # Top files
128
- print("━" * 70)
129
- print(f"Top {len(scores)} Files Needing Annotations")
130
- print("━" * 70)
131
- print()
191
+ click.echo("━" * 70)
192
+ click.echo(f"Top {len(scores)} Files Needing Annotations")
193
+ click.echo("━" * 70)
194
+ click.echo()
132
195
 
133
196
  for i, score in enumerate(scores, 1):
134
197
  # Priority emoji
@@ -143,41 +206,39 @@ def _output_text(scores, path):
143
206
  # Relative path
144
207
  rel_path = os.path.relpath(score.filepath, path) if not os.path.isfile(path) else score.filepath
145
208
 
146
- print(f"{i}. {emoji} {rel_path} (Score: {score.priority_score:.0f})")
147
- print(f" Complexity: {score.complexity_score} Security: {score.security_score} Dependencies: {score.dependency_score} Annotations: {score.annotation_penalty}")
148
- print()
209
+ click.echo(f"{i}. {emoji} {rel_path} (Score: {score.priority_score:.0f})")
210
+ click.echo(f" Complexity: {score.complexity_score} Security: {score.security_score} Dependencies: {score.dependency_score} Annotations: {score.annotation_penalty}")
211
+ click.echo()
149
212
 
150
213
  # Reasons
151
214
  if score.reasons:
152
- print(" Reasons:")
215
+ click.echo(" Reasons:")
153
216
  for reason in score.reasons:
154
- print(f" • {reason}")
155
- print()
217
+ click.echo(f" • {reason}")
218
+ click.echo()
156
219
 
157
220
  # Suggestions
158
221
  if score.suggestions:
159
- print(" Suggestions:")
222
+ click.echo(" Suggestions:")
160
223
  for suggestion in score.suggestions:
161
- print(f" ✓ {suggestion}")
162
- print()
224
+ click.echo(f" ✓ {suggestion}")
225
+ click.echo()
163
226
 
164
227
  # Footer
165
- print("━" * 70)
166
- print()
167
- print("💡 Tip: Start with CRITICAL files first. Use:")
228
+ click.echo("━" * 70)
229
+ click.echo()
230
+ click.echo("💡 Tip: Start with CRITICAL files first. Use:")
168
231
  if scores:
169
232
  first_file = scores[0].filepath
170
- print(f" voodocs companion {first_file}")
171
- print()
172
- print(" Or convert to VooDocs Lite for faster annotation:")
173
- print(f" voodocs convert {path} --to lite --in-place")
174
- print()
233
+ click.echo(f" voodocs companion {first_file}")
234
+ click.echo()
235
+ click.echo(" Or convert to VooDocs Lite for faster annotation:")
236
+ click.echo(f" voodocs convert {path} --to lite")
237
+ click.echo()
175
238
 
176
239
 
177
240
  def _output_json(scores):
178
241
  """Output results in JSON format."""
179
- import json
180
-
181
242
  data = []
182
243
  for score in scores:
183
244
  data.append({
@@ -197,14 +258,11 @@ def _output_json(scores):
197
258
  'suggestions': score.suggestions
198
259
  })
199
260
 
200
- print(json.dumps(data, indent=2))
261
+ click.echo(json.dumps(data, indent=2))
201
262
 
202
263
 
203
264
  def _output_csv(scores):
204
265
  """Output results in CSV format."""
205
- import csv
206
- import sys
207
-
208
266
  writer = csv.writer(sys.stdout)
209
267
 
210
268
  # Header
@@ -234,14 +292,14 @@ def _output_csv(scores):
234
292
 
235
293
  def _output_markdown(scores):
236
294
  """Output results in Markdown format."""
237
- print("# VooDocs Priority Analysis")
238
- print()
239
- print("## Summary")
240
- print()
241
- print(f"- **Files analyzed:** {len(scores)}")
242
- print()
243
- print("## Top Files")
244
- print()
295
+ click.echo("# VooDocs Priority Analysis")
296
+ click.echo()
297
+ click.echo("## Summary")
298
+ click.echo()
299
+ click.echo(f"- **Files analyzed:** {len(scores)}")
300
+ click.echo()
301
+ click.echo("## Top Files")
302
+ click.echo()
245
303
 
246
304
  for i, score in enumerate(scores, 1):
247
305
  emoji = {
@@ -252,26 +310,26 @@ def _output_markdown(scores):
252
310
  'MINIMAL': '⚪'
253
311
  }.get(score.priority_level, '⚪')
254
312
 
255
- print(f"### {i}. {emoji} {score.filepath}")
256
- print()
257
- print(f"**Score:** {score.priority_score:.0f} ({score.priority_level})")
258
- print()
259
- print(f"- Complexity: {score.complexity_score}")
260
- print(f"- Security: {score.security_score}")
261
- print(f"- Dependencies: {score.dependency_score}")
262
- print(f"- Annotation Coverage: {score.annotation_coverage * 100:.0f}%")
263
- print()
313
+ click.echo(f"### {i}. {emoji} {score.filepath}")
314
+ click.echo()
315
+ click.echo(f"**Score:** {score.priority_score:.0f} ({score.priority_level})")
316
+ click.echo()
317
+ click.echo(f"- Complexity: {score.complexity_score}")
318
+ click.echo(f"- Security: {score.security_score}")
319
+ click.echo(f"- Dependencies: {score.dependency_score}")
320
+ click.echo(f"- Annotation Coverage: {score.annotation_coverage * 100:.0f}%")
321
+ click.echo()
264
322
 
265
323
  if score.reasons:
266
- print("**Reasons:**")
267
- print()
324
+ click.echo("**Reasons:**")
325
+ click.echo()
268
326
  for reason in score.reasons:
269
- print(f"- {reason}")
270
- print()
327
+ click.echo(f"- {reason}")
328
+ click.echo()
271
329
 
272
330
  if score.suggestions:
273
- print("**Suggestions:**")
274
- print()
331
+ click.echo("**Suggestions:**")
332
+ click.echo()
275
333
  for suggestion in score.suggestions:
276
- print(f"- {suggestion}")
277
- print()
334
+ click.echo(f"- {suggestion}")
335
+ click.echo()
@@ -0,0 +1,144 @@
1
+ """@darkarts
2
+ ⊢cli:convert
3
+ ∂{click,pathlib,darkarts.voodocs_lite_parser}
4
+ ⚠{python≥3.7,click≥8.0}
5
+ ⊨{∀file→converted|skipped,∀error→reported}
6
+ 🔒{read-only:source-files}
7
+ ⚡{O(n):files}
8
+ """
9
+
10
+ """
11
+ VooDocs Convert Command
12
+
13
+ Convert VooDocs annotations between Standard and Lite formats.
14
+ """
15
+
16
+ import click
17
+ from pathlib import Path
18
+ import sys
19
+
20
+ # Import VooDocs Lite parser
21
+ sys.path.insert(0, str(Path(__file__).parent.parent))
22
+ from darkarts.voodocs_lite_parser import VooDocsLiteParser
23
+
24
+
25
+ @click.command()
26
+ @click.argument(
27
+ 'paths',
28
+ nargs=-1,
29
+ type=click.Path(exists=True),
30
+ required=True
31
+ )
32
+ @click.option(
33
+ '--to',
34
+ 'target_format',
35
+ type=click.Choice(['lite', 'standard']),
36
+ required=True,
37
+ help='Target format to convert to'
38
+ )
39
+ @click.option(
40
+ '-r', '--recursive',
41
+ is_flag=True,
42
+ help='Recursively process directories'
43
+ )
44
+ @click.option(
45
+ '--dry-run',
46
+ is_flag=True,
47
+ help='Show what would be converted without modifying files'
48
+ )
49
+ @click.option(
50
+ '-v', '--verbose',
51
+ is_flag=True,
52
+ help='Show detailed conversion information'
53
+ )
54
+ def convert(paths, target_format, recursive, dry_run, verbose):
55
+ """
56
+ Convert VooDocs annotations between Standard and Lite formats.
57
+
58
+ Examples:
59
+ voodocs convert src/ --to lite
60
+ voodocs convert src/ --to standard --recursive
61
+ voodocs convert file.ts --to lite --dry-run
62
+ """
63
+ parser = VooDocsLiteParser()
64
+
65
+ converted_count = 0
66
+ skipped_count = 0
67
+ error_count = 0
68
+
69
+ for path_str in paths:
70
+ path = Path(path_str)
71
+
72
+ if path.is_file():
73
+ files = [path]
74
+ elif path.is_dir():
75
+ if recursive:
76
+ files = list(path.rglob('*.ts')) + list(path.rglob('*.js')) + \
77
+ list(path.rglob('*.py')) + list(path.rglob('*.sol'))
78
+ else:
79
+ files = list(path.glob('*.ts')) + list(path.glob('*.js')) + \
80
+ list(path.glob('*.py')) + list(path.glob('*.sol'))
81
+ else:
82
+ click.echo(f"❌ Invalid path: {path}", err=True)
83
+ error_count += 1
84
+ continue
85
+
86
+ for file_path in files:
87
+ try:
88
+ # Read file
89
+ content = file_path.read_text()
90
+
91
+ # Convert
92
+ if target_format == 'lite':
93
+ converted = parser.convert_file_standard_to_lite(content)
94
+ else:
95
+ converted = parser.convert_file_lite_to_standard(content)
96
+
97
+ # Safety check: Ensure conversion didn't empty the file
98
+ if not converted or len(converted.strip()) < 10:
99
+ click.echo(f"⚠️ Warning: Conversion resulted in nearly empty file, skipping: {file_path}", err=True)
100
+ error_count += 1
101
+ continue
102
+
103
+ # Safety check: Ensure file size didn't shrink by more than 90%
104
+ if len(converted) < len(content) * 0.1:
105
+ click.echo(f"⚠️ Warning: Conversion would reduce file size by >90%, skipping: {file_path}", err=True)
106
+ click.echo(f" Original: {len(content)} chars, Converted: {len(converted)} chars", err=True)
107
+ error_count += 1
108
+ continue
109
+
110
+ # Check if anything changed
111
+ if converted == content:
112
+ if verbose:
113
+ click.echo(f"⏭️ Skipped (no changes): {file_path}")
114
+ skipped_count += 1
115
+ continue
116
+
117
+ # Write or show
118
+ if dry_run:
119
+ click.echo(f"Would convert: {file_path}")
120
+ if verbose:
121
+ click.echo(f" {len(content)} → {len(converted)} chars")
122
+ else:
123
+ file_path.write_text(converted)
124
+ click.echo(f"✅ Converted: {file_path}")
125
+
126
+ converted_count += 1
127
+
128
+ except Exception as e:
129
+ click.echo(f"❌ Error converting {file_path}: {e}", err=True)
130
+ error_count += 1
131
+
132
+ # Summary
133
+ click.echo()
134
+ click.echo(f"Summary:")
135
+ click.echo(f" ✅ Converted: {converted_count}")
136
+ click.echo(f" ⏭️ Skipped: {skipped_count}")
137
+ if error_count > 0:
138
+ click.echo(f" ❌ Errors: {error_count}")
139
+
140
+ if dry_run:
141
+ click.echo()
142
+ click.echo("(Dry run - no files were modified)")
143
+
144
+ sys.exit(0 if error_count == 0 else 1)
@@ -44,6 +44,64 @@ class VooDocsLiteParser:
44
44
  'security': r'🔒\{([^}]+)\}',
45
45
  }
46
46
 
47
+ @classmethod
48
+ def convert_file_standard_to_lite(cls, file_content: str) -> str:
49
+ """
50
+ Convert entire file from Standard to Lite format.
51
+
52
+ Finds @darkarts annotations and converts them to @darkarts-lite.
53
+ Preserves all other content unchanged.
54
+
55
+ Args:
56
+ file_content: Full file content
57
+
58
+ Returns:
59
+ File content with annotations converted to Lite format
60
+ """
61
+ # Pattern to match @darkarts comment blocks
62
+ pattern = r'/\*\*@darkarts\n(.*?)\*/'
63
+
64
+ def replace_annotation(match):
65
+ annotation_content = match.group(1)
66
+ # Convert the annotation
67
+ lite_content = cls.standard_to_lite(annotation_content, compress_abbreviations=True)
68
+ # Return as @darkarts-lite
69
+ return f'/**@darkarts-lite\n{lite_content}\n*/'
70
+
71
+ # Replace all @darkarts annotations
72
+ result = re.sub(pattern, replace_annotation, file_content, flags=re.DOTALL)
73
+
74
+ return result
75
+
76
+ @classmethod
77
+ def convert_file_lite_to_standard(cls, file_content: str) -> str:
78
+ """
79
+ Convert entire file from Lite to Standard format.
80
+
81
+ Finds @darkarts-lite annotations and converts them to @darkarts.
82
+ Preserves all other content unchanged.
83
+
84
+ Args:
85
+ file_content: Full file content
86
+
87
+ Returns:
88
+ File content with annotations converted to Standard format
89
+ """
90
+ # Pattern to match @darkarts-lite comment blocks
91
+ pattern = r'/\*\*@darkarts-lite\n(.*?)\*/'
92
+
93
+ def replace_annotation(match):
94
+ annotation_content = match.group(1)
95
+ # Convert the annotation
96
+ standard_content = cls.lite_to_standard(annotation_content)
97
+ # Return as @darkarts
98
+ return f'/**@darkarts\n{standard_content}\n*/'
99
+
100
+ # Replace all @darkarts-lite annotations
101
+ result = re.sub(pattern, replace_annotation, file_content, flags=re.DOTALL)
102
+
103
+ return result
104
+
47
105
  @classmethod
48
106
  def parse_lite(cls, content: str) -> Dict[str, any]:
49
107
  """
@@ -122,7 +180,7 @@ class VooDocsLiteParser:
122
180
  @classmethod
123
181
  def parse_standard(cls, content: str) -> Dict[str, any]:
124
182
  """
125
- Parse standard VooDocs format annotation.
183
+ Parse VooDocs Standard format annotation.
126
184
 
127
185
  Args:
128
186
  content: Standard format annotation text
@@ -141,33 +199,50 @@ class VooDocsLiteParser:
141
199
  'security': [],
142
200
  }
143
201
 
144
- # Extract each field
145
- for field, pattern in cls.STANDARD_PATTERNS.items():
146
- matches = re.findall(pattern, content)
147
-
148
- if field in ['purpose', 'complexity']:
149
- # Single value fields
150
- if matches:
151
- result[field] = matches[0].strip()
152
- elif field == 'dependencies':
153
- # Comma-separated list
154
- if matches:
155
- deps = matches[0].strip()
156
- result[field] = [d.strip() for d in deps.split(',')]
157
- else:
158
- # Multiple value fields
159
- result[field] = [m.strip() for m in matches]
202
+ # Purpose
203
+ match = re.search(cls.STANDARD_PATTERNS['purpose'], content)
204
+ if match:
205
+ result['purpose'] = match.group(1).strip()
206
+
207
+ # Dependencies
208
+ for match in re.finditer(cls.STANDARD_PATTERNS['dependencies'], content):
209
+ deps = match.group(1).strip()
210
+ result['dependencies'].extend([d.strip() for d in deps.split(',')])
211
+
212
+ # Assumptions
213
+ for match in re.finditer(cls.STANDARD_PATTERNS['assumptions'], content):
214
+ result['assumptions'].append(match.group(1).strip())
215
+
216
+ # Preconditions
217
+ for match in re.finditer(cls.STANDARD_PATTERNS['preconditions'], content):
218
+ result['preconditions'].append(match.group(1).strip())
219
+
220
+ # Postconditions
221
+ for match in re.finditer(cls.STANDARD_PATTERNS['postconditions'], content):
222
+ result['postconditions'].append(match.group(1).strip())
223
+
224
+ # Invariants
225
+ for match in re.finditer(cls.STANDARD_PATTERNS['invariants'], content):
226
+ result['invariants'].append(match.group(1).strip())
227
+
228
+ # Complexity
229
+ match = re.search(cls.STANDARD_PATTERNS['complexity'], content)
230
+ if match:
231
+ result['complexity'] = match.group(1).strip()
232
+
233
+ # Security
234
+ for match in re.finditer(cls.STANDARD_PATTERNS['security'], content):
235
+ result['security'].append(match.group(1).strip())
160
236
 
161
237
  return result
162
238
 
163
239
  @classmethod
164
- def lite_to_standard(cls, lite_content: str, expand_abbreviations: bool = True) -> str:
240
+ def lite_to_standard(cls, lite_content: str) -> str:
165
241
  """
166
242
  Convert Lite format to Standard format.
167
243
 
168
244
  Args:
169
245
  lite_content: Lite format annotation
170
- expand_abbreviations: Whether to expand abbreviations
171
246
 
172
247
  Returns:
173
248
  Standard format annotation
@@ -178,62 +253,46 @@ class VooDocsLiteParser:
178
253
 
179
254
  # Purpose
180
255
  if parsed['purpose']:
181
- text = parsed['purpose']
182
- if expand_abbreviations:
183
- text = expand_text(text)
256
+ text = ultra_expand(parsed['purpose'])
184
257
  lines.append(f"⊢{{{text}}}")
185
258
 
186
259
  # Dependencies
187
260
  if parsed['dependencies']:
188
- deps = ', '.join(parsed['dependencies'])
189
- if expand_abbreviations:
190
- deps = expand_text(deps)
261
+ deps = ','.join([ultra_expand(d) for d in parsed['dependencies']])
191
262
  lines.append(f"∂{{{deps}}}")
192
263
 
193
264
  # Assumptions
194
265
  if parsed['assumptions']:
195
266
  for assumption in parsed['assumptions']:
196
- text = assumption
197
- if expand_abbreviations:
198
- text = expand_text(text)
267
+ text = ultra_expand(assumption)
199
268
  lines.append(f"⚠{{{text}}}")
200
269
 
201
270
  # Preconditions
202
271
  if parsed['preconditions']:
203
272
  for precond in parsed['preconditions']:
204
- text = precond
205
- if expand_abbreviations:
206
- text = expand_text(text)
273
+ text = ultra_expand(precond)
207
274
  lines.append(f"⊳{{{text}}}")
208
275
 
209
276
  # Postconditions
210
277
  if parsed['postconditions']:
211
278
  for postcond in parsed['postconditions']:
212
- text = postcond
213
- if expand_abbreviations:
214
- text = expand_text(text)
279
+ text = ultra_expand(postcond)
215
280
  lines.append(f"⊲{{{text}}}")
216
281
 
217
282
  # Invariants
218
283
  if parsed['invariants']:
219
284
  for invariant in parsed['invariants']:
220
- text = invariant
221
- if expand_abbreviations:
222
- text = expand_text(text)
285
+ text = ultra_expand(invariant)
223
286
  lines.append(f"⊨{{{text}}}")
224
287
 
225
288
  # Complexity
226
289
  if parsed['complexity']:
227
- text = parsed['complexity']
228
- # Don't expand complexity (O(n) should stay as-is)
229
- lines.append(f"⚡{{{text}}}")
290
+ lines.append(f"⚡{{{parsed['complexity']}}}")
230
291
 
231
292
  # Security
232
293
  if parsed['security']:
233
294
  for security in parsed['security']:
234
- text = security
235
- if expand_abbreviations:
236
- text = expand_text(text)
295
+ text = ultra_expand(security)
237
296
  lines.append(f"🔒{{{text}}}")
238
297
 
239
298
  return '\n'.join(lines)
@@ -319,25 +378,27 @@ class VooDocsLiteParser:
319
378
  @classmethod
320
379
  def detect_format(cls, content: str) -> str:
321
380
  """
322
- Detect whether content is Lite or Standard format.
381
+ Detect if content is Standard or Lite format.
323
382
 
383
+ Args:
384
+ content: Annotation content
385
+
324
386
  Returns:
325
- 'lite', 'standard', or 'unknown'
387
+ 'standard', 'lite', or 'unknown'
326
388
  """
327
- # Check for Lite symbols at start of lines
328
- lite_indicators = ['>', '@', '!', '<', '=', '~', '#']
329
- has_lite = any(line.strip().startswith(sym) for sym in lite_indicators for line in content.split('\n'))
389
+ # Check for standard symbols
390
+ if any(symbol in content for symbol in ['', '', '', '', '', '', '', '🔒']):
391
+ return 'standard'
330
392
 
331
- # Check for Standard symbols
332
- standard_indicators = ['', '∂', '⚠', '⊳', '⊲', '⊨', '⚡', '🔒']
333
- has_standard = any(sym in content for sym in standard_indicators)
393
+ # Check for lite symbols at start of lines
394
+ lines = content.strip().split('\n')
395
+ lite_symbols = 0
396
+ for line in lines:
397
+ line = line.strip()
398
+ if line and line[0] in ['>', '@', '!', '<', '=', '~', '#']:
399
+ lite_symbols += 1
334
400
 
335
- if has_lite and not has_standard:
401
+ if lite_symbols > 0:
336
402
  return 'lite'
337
- elif has_standard and not has_lite:
338
- return 'standard'
339
- elif has_lite and has_standard:
340
- # Mixed format, prefer standard
341
- return 'standard'
342
- else:
343
- return 'unknown'
403
+
404
+ return 'unknown'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voodocs/cli",
3
- "version": "2.5.1",
3
+ "version": "2.5.3",
4
4
  "description": "AI-Native Symbolic Documentation System - The world's first documentation tool using mathematical notation with semantic validation",
5
5
  "main": "voodocs_cli.py",
6
6
  "bin": {