@voodocs/cli 2.5.2 → 3.0.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.
@@ -148,20 +148,42 @@ class ImportExtractor(ast.NodeVisitor):
148
148
 
149
149
  def extract_imports(file_path: Path) -> Set[str]:
150
150
  """
151
- Extract all imports from a Python file using AST parsing.
151
+ Extract all imports from a source file.
152
152
 
153
- Handles both absolute and relative imports.
153
+ Supports:
154
+ - Python: AST-based parsing
155
+ - TypeScript/JavaScript: Regex-based extraction
156
+ - Other languages: Best-effort regex extraction
154
157
 
155
158
  Args:
156
- file_path: Path to the Python file
159
+ file_path: Path to the source file
157
160
 
158
161
  Returns:
159
- Set of top-level module names imported in the file
162
+ Set of top-level module/package names imported in the file
160
163
 
161
164
  Examples:
162
165
  >>> extract_imports(Path("myfile.py"))
163
166
  {'os', 'sys', 'pathlib', 'typing', 'darkarts'}
167
+ >>> extract_imports(Path("myfile.ts"))
168
+ {'react', 'axios', './utils'}
164
169
  """
170
+ suffix = file_path.suffix.lower()
171
+
172
+ # Python files - use AST parsing
173
+ if suffix == '.py':
174
+ return _extract_python_imports(file_path)
175
+
176
+ # TypeScript/JavaScript files - use regex
177
+ elif suffix in {'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'}:
178
+ return _extract_js_imports(file_path)
179
+
180
+ # Other languages - best effort
181
+ else:
182
+ return _extract_generic_imports(file_path)
183
+
184
+
185
+ def _extract_python_imports(file_path: Path) -> Set[str]:
186
+ """Extract imports from Python files using AST."""
165
187
  try:
166
188
  content = file_path.read_text(encoding='utf-8')
167
189
  tree = ast.parse(content, filename=str(file_path))
@@ -171,15 +193,13 @@ def extract_imports(file_path: Path) -> Set[str]:
171
193
 
172
194
  imports = extractor.get_all_imports()
173
195
 
174
- # Filter out standard library modules that are commonly omitted
175
- # from annotations (optional - can be configured)
196
+ # Filter out standard library modules
176
197
  stdlib_to_keep = {
177
198
  'typing', 'dataclasses', 'enum', 'abc', 'pathlib',
178
199
  'datetime', 'json', 'yaml', 're', 'os', 'sys',
179
200
  'time', 'uuid', 'collections', 'itertools', 'functools'
180
201
  }
181
202
 
182
- # Keep all non-stdlib and explicitly listed stdlib modules
183
203
  filtered_imports = {
184
204
  imp for imp in imports
185
205
  if not _is_stdlib_module(imp) or imp in stdlib_to_keep
@@ -193,6 +213,69 @@ def extract_imports(file_path: Path) -> Set[str]:
193
213
  raise ValueError(f"Error parsing {file_path}: {e}")
194
214
 
195
215
 
216
+ def _extract_js_imports(file_path: Path) -> Set[str]:
217
+ """Extract imports from TypeScript/JavaScript files using regex."""
218
+ try:
219
+ content = file_path.read_text(encoding='utf-8')
220
+ imports = set()
221
+
222
+ # Match: import ... from 'module'
223
+ # Match: import ... from "module"
224
+ import_pattern = r"import\s+(?:.*?\s+from\s+)?['\"]([^'\"]+)['\"]"
225
+ for match in re.finditer(import_pattern, content):
226
+ module = match.group(1)
227
+ # Extract package name (ignore relative paths for now)
228
+ if not module.startswith('.'):
229
+ # Get first part of scoped package (@org/pkg) or regular package
230
+ if module.startswith('@'):
231
+ parts = module.split('/')
232
+ if len(parts) >= 2:
233
+ imports.add(f"{parts[0]}/{parts[1]}")
234
+ else:
235
+ imports.add(module.split('/')[0])
236
+
237
+ # Match: require('module')
238
+ require_pattern = r"require\(['\"]([^'\"]+)['\"]\)"
239
+ for match in re.finditer(require_pattern, content):
240
+ module = match.group(1)
241
+ if not module.startswith('.'):
242
+ if module.startswith('@'):
243
+ parts = module.split('/')
244
+ if len(parts) >= 2:
245
+ imports.add(f"{parts[0]}/{parts[1]}")
246
+ else:
247
+ imports.add(module.split('/')[0])
248
+
249
+ return imports
250
+
251
+ except Exception as e:
252
+ raise ValueError(f"Error parsing {file_path}: {e}")
253
+
254
+
255
+ def _extract_generic_imports(file_path: Path) -> Set[str]:
256
+ """Best-effort import extraction for other languages."""
257
+ try:
258
+ content = file_path.read_text(encoding='utf-8')
259
+ imports = set()
260
+
261
+ # Generic patterns for various languages
262
+ patterns = [
263
+ r"import\s+['\"]([^'\"]+)['\"]", # Generic import
264
+ r"require\(['\"]([^'\"]+)['\"]\)", # Require
265
+ r"use\s+([\w:]+)", # Rust/Perl
266
+ r"#include\s+[<\"]([^>\"]+)[>\"]" # C/C++
267
+ ]
268
+
269
+ for pattern in patterns:
270
+ for match in re.finditer(pattern, content):
271
+ imports.add(match.group(1))
272
+
273
+ return imports
274
+
275
+ except Exception as e:
276
+ return set() # Return empty set on error for generic extraction
277
+
278
+
196
279
  def _is_stdlib_module(module_name: str) -> bool:
197
280
  """
198
281
  Check if a module is part of Python's standard library.
@@ -258,10 +341,15 @@ def parse_dependencies(annotation: str) -> Set[str]:
258
341
 
259
342
  def extract_annotation(file_path: Path) -> Optional[str]:
260
343
  """
261
- Extract the @darkarts annotation from a Python file.
344
+ Extract the @darkarts annotation from any source file.
345
+
346
+ Supports:
347
+ - Python: triple-quote @darkarts docstrings
348
+ - TypeScript/JavaScript: /**@darkarts ... */ block comments
349
+ - Other languages with similar comment syntax
262
350
 
263
351
  Args:
264
- file_path: Path to the Python file
352
+ file_path: Path to the source file
265
353
 
266
354
  Returns:
267
355
  The annotation string, or None if not found
@@ -269,17 +357,46 @@ def extract_annotation(file_path: Path) -> Optional[str]:
269
357
  try:
270
358
  content = file_path.read_text(encoding='utf-8')
271
359
 
272
- # Check if file starts with @darkarts annotation
273
- if not content.startswith('"""@darkarts'):
274
- return None
360
+ # Python-style docstring
361
+ if '"""@darkarts' in content:
362
+ start_idx = content.find('"""@darkarts')
363
+ end_idx = content.find('"""', start_idx + 3)
364
+ if end_idx != -1:
365
+ return content[start_idx + 3:end_idx]
366
+
367
+ # JavaScript/TypeScript/C-style block comment
368
+ if '/**@darkarts' in content or '/*@darkarts' in content:
369
+ # Find the @darkarts comment
370
+ start_marker = '/**@darkarts' if '/**@darkarts' in content else '/*@darkarts'
371
+ start_idx = content.find(start_marker)
372
+ end_idx = content.find('*/', start_idx)
373
+ if end_idx != -1:
374
+ annotation = content[start_idx + len(start_marker):end_idx]
375
+ return annotation.strip()
275
376
 
276
- # Find the end of the docstring
277
- end_idx = content.find('"""', 3)
278
- if end_idx == -1:
279
- return None
377
+ # Single-line comment style (less common but supported)
378
+ if '//@darkarts' in content or '#@darkarts' in content:
379
+ lines = content.split('\n')
380
+ annotation_lines = []
381
+ in_annotation = False
382
+ for line in lines:
383
+ if '//@darkarts' in line or '#@darkarts' in line:
384
+ in_annotation = True
385
+ # Extract content after @darkarts
386
+ if '//@darkarts' in line:
387
+ annotation_lines.append(line.split('//@darkarts', 1)[1].strip())
388
+ else:
389
+ annotation_lines.append(line.split('#@darkarts', 1)[1].strip())
390
+ elif in_annotation and (line.strip().startswith('//') or line.strip().startswith('#')):
391
+ # Continue multi-line annotation
392
+ annotation_lines.append(line.strip().lstrip('/#').strip())
393
+ elif in_annotation:
394
+ # End of annotation
395
+ break
396
+ if annotation_lines:
397
+ return '\n'.join(annotation_lines)
280
398
 
281
- annotation = content[3:end_idx] # Skip opening """
282
- return annotation
399
+ return None
283
400
 
284
401
  except Exception as e:
285
402
  raise ValueError(f"Error reading {file_path}: {e}")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voodocs/cli",
3
- "version": "2.5.2",
3
+ "version": "3.0.0",
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": {
@@ -64,9 +64,10 @@
64
64
  "lib/darkarts/exceptions.py",
65
65
  "lib/darkarts/telemetry.py",
66
66
  "lib/darkarts/companion_files.py",
67
- "lib/darkarts/voodocs_lite_dict.py",
68
- "lib/darkarts/voodocs_lite_dict_v2.py",
69
- "lib/darkarts/voodocs_lite_parser.py",
67
+ "lib/darkarts/darkarts_abbreviations.py",
68
+ "lib/darkarts/darkarts_patterns.py",
69
+ "lib/darkarts/darkarts_symbols.py",
70
+ "lib/darkarts/darkarts_parser_v3.py",
70
71
  "lib/darkarts/priority_analyzer/",
71
72
  "lib/darkarts/parsers/typescript/dist/",
72
73
  "lib/darkarts/parsers/typescript/src/",
@@ -1,131 +0,0 @@
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)
@@ -1,216 +0,0 @@
1
- """
2
- VooDocs Lite - Abbreviation Dictionary
3
-
4
- Provides bidirectional mapping between full words and abbreviations
5
- for ultra-compact symbolic notation.
6
- """
7
-
8
- # Abbreviation dictionary: abbr -> full
9
- ABBREVIATIONS = {
10
- # Storage & Data
11
- 'db': 'database',
12
- 'cfg': 'configuration',
13
- 'var': 'variable',
14
- 'const': 'constant',
15
- 'param': 'parameter',
16
- 'arg': 'argument',
17
- 'id': 'identifier',
18
-
19
- # Common verbs
20
- 'must': 'must',
21
- 'be': 'be',
22
- 'is': 'is',
23
- 'are': 'are',
24
- 'has': 'has',
25
- 'have': 'have',
26
- 'does': 'does',
27
- 'do': 'do',
28
- 'will': 'will',
29
- 'can': 'can',
30
- 'should': 'should',
31
- 'may': 'may',
32
- 'contains': 'contains',
33
- 'returns': 'returns',
34
- 'expire': 'expire',
35
- 'expires': 'expires',
36
- 'stored': 'stored',
37
- 'signed': 'signed',
38
- 'hashed': 'hashed',
39
- 'valid': 'valid',
40
- 'logged': 'logged',
41
-
42
- # Actions
43
- 'init': 'initialize',
44
- 'val': 'validate',
45
- 'ver': 'verify',
46
- 'gen': 'generate',
47
- 'cr': 'create',
48
- 'upd': 'update',
49
- 'del': 'delete',
50
- 'mod': 'modify',
51
- 'get': 'retrieve',
52
- 'qry': 'query',
53
- 'chk': 'check',
54
-
55
- # Security
56
- 'auth': 'authentication',
57
- 'authz': 'authorization',
58
- 'pwd': 'password',
59
- 'tok': 'token',
60
-
61
- # Entities
62
- 'usr': 'user',
63
- 'usrs': 'users',
64
-
65
- # Time
66
- 'ts': 'timestamp',
67
- 'exp': 'expiration',
68
-
69
- # Communication
70
- 'resp': 'response',
71
- 'req': 'request',
72
- 'msg': 'message',
73
-
74
- # Status & Errors
75
- 'err': 'error',
76
- 'exc': 'exception',
77
- 'ok': 'success',
78
- 'fail': 'failure',
79
-
80
- # Boolean & Values
81
- 'T': 'true',
82
- 'F': 'false',
83
- 'N': 'null',
84
- 'U': 'undefined',
85
- 'E': 'empty',
86
-
87
- # Types
88
- 'str': 'string',
89
- 'num': 'number',
90
- 'int': 'integer',
91
- 'bool': 'boolean',
92
- 'arr': 'array',
93
- 'obj': 'object',
94
- 'fn': 'function',
95
-
96
- # Blockchain
97
- 'addr': 'address',
98
- 'ctr': 'contract',
99
- 'tx': 'transaction',
100
- 'blk': 'block',
101
- 'bal': 'balance',
102
- 'amt': 'amount',
103
-
104
- # Domain-specific
105
- 'sub': 'subdomain',
106
- 'subs': 'subdomains',
107
- 'reg': 'registry',
108
- 'own': 'owner',
109
- 'mgmt': 'management',
110
-
111
- # Common words
112
- 'w/': 'with',
113
- 'wo/': 'without',
114
- 'svc': 'service',
115
- 'sys': 'system',
116
- 'ops': 'operations',
117
- 'ret': 'returns',
118
- 'res': 'result',
119
- 'val': 'value',
120
- 'vals': 'values',
121
- 'len': 'length',
122
- 'cnt': 'count',
123
- 'max': 'maximum',
124
- 'min': 'minimum',
125
- 'avg': 'average',
126
- 'sum': 'summary',
127
- 'desc': 'description',
128
- 'info': 'information',
129
- 'spec': 'specification',
130
- 'impl': 'implementation',
131
- 'ref': 'reference',
132
- 'def': 'definition',
133
- 'decl': 'declaration',
134
- }
135
-
136
- # Reverse mapping: full -> abbr
137
- EXPANSIONS = {v: k for k, v in ABBREVIATIONS.items()}
138
-
139
- # Symbol mappings
140
- LITE_TO_STANDARD = {
141
- '>': '⊢', # Purpose/Postcondition (context-dependent)
142
- '@': '∂', # Dependencies
143
- '!': '⚠', # Assumptions
144
- '<': '⊳', # Preconditions
145
- '=': '⊨', # Invariants
146
- '~': '⚡', # Complexity
147
- '#': '🔒', # Security
148
- }
149
-
150
- STANDARD_TO_LITE = {v: k for k, v in LITE_TO_STANDARD.items()}
151
-
152
-
153
- def expand_abbreviation(abbr: str) -> str:
154
- """Expand an abbreviation to its full form."""
155
- return ABBREVIATIONS.get(abbr, abbr)
156
-
157
-
158
- def compress_word(word: str) -> str:
159
- """Compress a word to its abbreviation."""
160
- return EXPANSIONS.get(word.lower(), word)
161
-
162
-
163
- def expand_text(text: str) -> str:
164
- """
165
- Expand abbreviated text to full form.
166
-
167
- Example:
168
- "usr auth svc w/ JWT gen" -> "user authentication service with JWT generation"
169
- """
170
- words = text.split()
171
- expanded = []
172
-
173
- for word in words:
174
- # Check if word has punctuation
175
- if word[-1] in '.,;:!?':
176
- punct = word[-1]
177
- word_part = word[:-1]
178
- expanded_word = expand_abbreviation(word_part)
179
- expanded.append(expanded_word + punct)
180
- else:
181
- expanded.append(expand_abbreviation(word))
182
-
183
- return ' '.join(expanded)
184
-
185
-
186
- def compress_text(text: str) -> str:
187
- """
188
- Compress text using abbreviations.
189
-
190
- Example:
191
- "user authentication service with JWT generation" -> "usr auth svc w/ JWT gen"
192
- """
193
- words = text.split()
194
- compressed = []
195
-
196
- for word in words:
197
- # Check if word has punctuation
198
- if word[-1] in '.,;:!?':
199
- punct = word[-1]
200
- word_part = word[:-1]
201
- compressed_word = compress_word(word_part)
202
- compressed.append(compressed_word + punct)
203
- else:
204
- compressed.append(compress_word(word))
205
-
206
- return ' '.join(compressed)
207
-
208
-
209
- def get_lite_symbol(standard_symbol: str) -> str:
210
- """Convert standard VooDocs symbol to Lite symbol."""
211
- return STANDARD_TO_LITE.get(standard_symbol, standard_symbol)
212
-
213
-
214
- def get_standard_symbol(lite_symbol: str) -> str:
215
- """Convert Lite symbol to standard VooDocs symbol."""
216
- return LITE_TO_STANDARD.get(lite_symbol, lite_symbol)