@voodocs/cli 2.5.1 → 2.5.2

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,18 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [2.5.2] - 2024-12-24
4
+
5
+ ### Fixed
6
+ - **CLI Registration**: Fixed analyze, convert, and companion commands not being registered
7
+ - Converted `analyze.py` from argparse to Click command
8
+ - Created `convert.py` as Click command for VooDocs Lite conversion
9
+ - Updated `lib/cli/__init__.py` to import and register all three commands
10
+ - Updated `__version__` from "2.1.3" to "2.5.2" in Python code
11
+ - v2.5.0 and v2.5.1 had files in package but commands weren't accessible via CLI
12
+ - All Priority Analyzer and VooDocs Lite features now fully functional
13
+
14
+ ---
15
+
3
16
  ## [2.5.1] - 2024-12-24
4
17
 
5
18
  ### 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.2"
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,131 @@
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.standard_to_lite(content)
94
+ else:
95
+ converted = parser.lite_to_standard(content)
96
+
97
+ # Check if anything changed
98
+ if converted == content:
99
+ if verbose:
100
+ click.echo(f"⏭️ Skipped (no changes): {file_path}")
101
+ skipped_count += 1
102
+ continue
103
+
104
+ # Write or show
105
+ if dry_run:
106
+ click.echo(f"Would convert: {file_path}")
107
+ if verbose:
108
+ click.echo(f" {len(content)} → {len(converted)} chars")
109
+ else:
110
+ file_path.write_text(converted)
111
+ click.echo(f"✅ Converted: {file_path}")
112
+
113
+ converted_count += 1
114
+
115
+ except Exception as e:
116
+ click.echo(f"❌ Error converting {file_path}: {e}", err=True)
117
+ error_count += 1
118
+
119
+ # Summary
120
+ click.echo()
121
+ click.echo(f"Summary:")
122
+ click.echo(f" ✅ Converted: {converted_count}")
123
+ click.echo(f" ⏭️ Skipped: {skipped_count}")
124
+ if error_count > 0:
125
+ click.echo(f" ❌ Errors: {error_count}")
126
+
127
+ if dry_run:
128
+ click.echo()
129
+ click.echo("(Dry run - no files were modified)")
130
+
131
+ sys.exit(0 if error_count == 0 else 1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voodocs/cli",
3
- "version": "2.5.1",
3
+ "version": "2.5.2",
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": {