@voodocs/cli 2.5.2 → 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,22 @@
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
+
3
20
  ## [2.5.2] - 2024-12-24
4
21
 
5
22
  ### 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.5.2"
19
+ __version__ = "2.5.3"
20
20
 
21
21
 
22
22
  @click.group()
@@ -90,9 +90,22 @@ def convert(paths, target_format, recursive, dry_run, verbose):
90
90
 
91
91
  # Convert
92
92
  if target_format == 'lite':
93
- converted = parser.standard_to_lite(content)
93
+ converted = parser.convert_file_standard_to_lite(content)
94
94
  else:
95
- converted = parser.lite_to_standard(content)
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
96
109
 
97
110
  # Check if anything changed
98
111
  if converted == content:
@@ -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.2",
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": {