@voodocs/cli 0.1.1 → 0.2.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.
@@ -0,0 +1,342 @@
1
+ """
2
+ YAML Utilities for Context Files
3
+
4
+ Handles reading, writing, and formatting of .voodocs.context YAML files.
5
+ """
6
+
7
+ import yaml
8
+ from pathlib import Path
9
+ from typing import Dict, Any, Optional
10
+ from .models import ContextFile, Versioning, Project, Architecture, Change
11
+
12
+
13
+ class ContextYAMLDumper(yaml.SafeDumper):
14
+ """Custom YAML dumper with better formatting for context files."""
15
+
16
+ def increase_indent(self, flow=False, indentless=False):
17
+ return super().increase_indent(flow, False)
18
+
19
+
20
+ def _represent_none(self, _):
21
+ """Represent None as empty string instead of 'null'."""
22
+ return self.represent_scalar('tag:yaml.org,2002:null', '')
23
+
24
+
25
+ # Register custom representers
26
+ ContextYAMLDumper.add_representer(type(None), _represent_none)
27
+
28
+
29
+ def write_context_yaml(context: ContextFile, file_path: Path) -> None:
30
+ """
31
+ Write a ContextFile to a YAML file with proper formatting.
32
+
33
+ Args:
34
+ context: The ContextFile object to write
35
+ file_path: Path to the .voodocs.context file
36
+ """
37
+ data = context.to_dict()
38
+
39
+ # Create file with header comment
40
+ with open(file_path, 'w', encoding='utf-8') as f:
41
+ # Write header
42
+ f.write("# VooDocs Context File\n")
43
+ f.write("# This file contains structured project context for AI assistants and developers.\n")
44
+ f.write("# Format: YAML (DSL for machines, use 'voodocs context view' for human-readable Markdown)\n")
45
+ f.write("# Version: {}\n".format(context.versioning.context_version))
46
+ f.write("# Last Updated: {}\n".format(context.versioning.last_updated))
47
+ f.write("\n")
48
+
49
+ # Write YAML content
50
+ yaml.dump(
51
+ data,
52
+ f,
53
+ Dumper=ContextYAMLDumper,
54
+ default_flow_style=False,
55
+ allow_unicode=True,
56
+ sort_keys=False,
57
+ width=100,
58
+ indent=2
59
+ )
60
+
61
+
62
+ def read_context_yaml(file_path: Path) -> Optional[Dict[str, Any]]:
63
+ """
64
+ Read a .voodocs.context YAML file.
65
+
66
+ Args:
67
+ file_path: Path to the .voodocs.context file
68
+
69
+ Returns:
70
+ Dictionary containing the context data, or None if file doesn't exist
71
+ """
72
+ if not file_path.exists():
73
+ return None
74
+
75
+ with open(file_path, 'r', encoding='utf-8') as f:
76
+ return yaml.safe_load(f)
77
+
78
+
79
+ def parse_context_file(data: Dict[str, Any]) -> ContextFile:
80
+ """
81
+ Parse a context dictionary into a ContextFile object.
82
+
83
+ Args:
84
+ data: Dictionary loaded from YAML
85
+
86
+ Returns:
87
+ ContextFile object
88
+ """
89
+ # Parse versioning
90
+ versioning_data = data.get('versioning', {})
91
+ versioning = Versioning(
92
+ code_version=versioning_data.get('code_version', '0.0.0'),
93
+ context_version=versioning_data.get('context_version', '0.0'),
94
+ last_updated=versioning_data.get('last_updated', '')
95
+ )
96
+
97
+ # Parse project
98
+ project_data = data.get('project', {})
99
+ project = Project(
100
+ name=project_data.get('name', ''),
101
+ purpose=project_data.get('purpose', ''),
102
+ repository=project_data.get('repository'),
103
+ homepage=project_data.get('homepage'),
104
+ license=project_data.get('license')
105
+ )
106
+
107
+ # Parse changes
108
+ changes_data = data.get('changes', [])
109
+ changes = []
110
+ for change_dict in changes_data:
111
+ from .models import Change
112
+ changes.append(Change(
113
+ type=change_dict.get('type', 'context'),
114
+ description=change_dict.get('description', ''),
115
+ date=change_dict.get('date', ''),
116
+ context_version=change_dict.get('context_version'),
117
+ code_version=change_dict.get('code_version'),
118
+ commit=change_dict.get('commit'),
119
+ author=change_dict.get('author')
120
+ ))
121
+
122
+ # Parse architecture
123
+ arch_data = data.get('architecture', {})
124
+ architecture = Architecture(
125
+ decisions=arch_data.get('decisions', []),
126
+ modules=arch_data.get('modules', {}),
127
+ tech_stack=arch_data.get('tech_stack', {})
128
+ )
129
+
130
+ # Parse assumptions
131
+ assumptions_data = data.get('assumptions', [])
132
+ assumptions = []
133
+ for assumption in assumptions_data:
134
+ if isinstance(assumption, dict):
135
+ from .models import Assumption
136
+ assumptions.append(Assumption(
137
+ assumption=assumption.get('description', assumption.get('assumption', '')),
138
+ rationale=assumption.get('rationale'),
139
+ risk_if_false=assumption.get('risk_if_false')
140
+ ))
141
+ elif isinstance(assumption, str):
142
+ from .models import Assumption
143
+ assumptions.append(Assumption(assumption=assumption))
144
+
145
+ # Parse known issues
146
+ issues_data = data.get('known_issues', [])
147
+ known_issues = []
148
+ for issue in issues_data:
149
+ if isinstance(issue, dict):
150
+ from .models import KnownIssue
151
+ known_issues.append(KnownIssue(
152
+ description=issue.get('description', ''),
153
+ severity=issue.get('severity'),
154
+ workaround=issue.get('workaround'),
155
+ tracking_id=issue.get('tracking_id')
156
+ ))
157
+ elif isinstance(issue, str):
158
+ from .models import KnownIssue
159
+ known_issues.append(KnownIssue(description=issue))
160
+
161
+ # Parse critical paths
162
+ paths_data = data.get('critical_paths', [])
163
+ critical_paths = []
164
+ for path in paths_data:
165
+ if isinstance(path, dict):
166
+ from .models import CriticalPath
167
+ critical_paths.append(CriticalPath(
168
+ name=path.get('name', ''),
169
+ description=path.get('description', ''),
170
+ entry_point=path.get('entry_point'),
171
+ steps=path.get('steps', [])
172
+ ))
173
+
174
+ # Parse roadmap
175
+ roadmap_data = data.get('roadmap', [])
176
+ roadmap = []
177
+ for item in roadmap_data:
178
+ if isinstance(item, dict):
179
+ from .models import RoadmapItem
180
+ roadmap.append(RoadmapItem(
181
+ version=item.get('version', ''),
182
+ description=item.get('description', ''),
183
+ status=item.get('status'),
184
+ target_date=item.get('target_date')
185
+ ))
186
+
187
+ # Create context file
188
+ context = ContextFile(
189
+ versioning=versioning,
190
+ project=project,
191
+ invariants=data.get('invariants', {}),
192
+ architecture=architecture,
193
+ critical_paths=critical_paths,
194
+ known_issues=known_issues,
195
+ assumptions=assumptions,
196
+ changes=changes,
197
+ roadmap=roadmap,
198
+ performance=data.get('performance', {}),
199
+ security=data.get('security', {}),
200
+ dependencies=data.get('dependencies', {}),
201
+ testing=data.get('testing', {}),
202
+ deployment=data.get('deployment', {}),
203
+ team=data.get('team', {}),
204
+ custom=data.get('custom', {})
205
+ )
206
+
207
+ return context
208
+
209
+
210
+ def add_to_gitignore(project_root: Path) -> bool:
211
+ """
212
+ Add .voodocs.context to .gitignore if not already present.
213
+
214
+ Args:
215
+ project_root: Root directory of the project
216
+
217
+ Returns:
218
+ True if added, False if already present or error
219
+ """
220
+ gitignore_path = project_root / '.gitignore'
221
+ context_entry = '.voodocs.context'
222
+
223
+ # Check if .gitignore exists
224
+ if gitignore_path.exists():
225
+ with open(gitignore_path, 'r', encoding='utf-8') as f:
226
+ content = f.read()
227
+
228
+ # Check if entry already exists
229
+ if context_entry in content:
230
+ return False
231
+
232
+ # Add entry
233
+ with open(gitignore_path, 'a', encoding='utf-8') as f:
234
+ # Ensure there's a newline before our section
235
+ if not content.endswith('\n'):
236
+ f.write('\n')
237
+ f.write('\n# VooDocs Context File (contains internal project details)\n')
238
+ f.write(f'{context_entry}\n')
239
+
240
+ return True
241
+ else:
242
+ # Create .gitignore with entry
243
+ with open(gitignore_path, 'w', encoding='utf-8') as f:
244
+ f.write('# VooDocs Context File (contains internal project details)\n')
245
+ f.write(f'{context_entry}\n')
246
+
247
+ return True
248
+
249
+
250
+ def format_context_as_markdown(context: ContextFile) -> str:
251
+ """
252
+ Convert a ContextFile to human-readable Markdown.
253
+
254
+ Args:
255
+ context: The ContextFile object
256
+
257
+ Returns:
258
+ Formatted Markdown string
259
+ """
260
+ lines = []
261
+
262
+ # Header
263
+ lines.append(f"# {context.project.name} Context")
264
+ lines.append("")
265
+ lines.append(f"**Version:** {context.versioning.context_version}")
266
+ lines.append(f"**Last Updated:** {context.versioning.last_updated}")
267
+ lines.append("")
268
+ lines.append("---")
269
+ lines.append("")
270
+
271
+ # Project Overview
272
+ lines.append("## 🎯 Project Overview")
273
+ lines.append("")
274
+ lines.append(f"**Purpose:** {context.project.purpose}")
275
+ lines.append("")
276
+ if context.project.repository:
277
+ lines.append(f"**Repository:** {context.project.repository}")
278
+ if context.project.license:
279
+ lines.append(f"**License:** {context.project.license}")
280
+ lines.append("")
281
+ lines.append("---")
282
+ lines.append("")
283
+
284
+ # Global Invariants
285
+ if context.invariants.get('global'):
286
+ lines.append("## ⚖️ Global Invariants")
287
+ lines.append("")
288
+ for invariant in context.invariants['global']:
289
+ lines.append(f"- `{invariant}`")
290
+ lines.append("")
291
+ lines.append("---")
292
+ lines.append("")
293
+
294
+ # Architecture
295
+ if context.architecture.decisions:
296
+ lines.append("## 🏛️ Architecture Decisions")
297
+ lines.append("")
298
+ for decision in context.architecture.decisions:
299
+ lines.append(f"### {decision.decision}")
300
+ lines.append("")
301
+ lines.append(f"**Rationale:** {decision.rationale}")
302
+ if decision.alternatives_considered:
303
+ lines.append("")
304
+ lines.append("**Alternatives Considered:**")
305
+ for alt in decision.alternatives_considered:
306
+ lines.append(f"- {alt}")
307
+ lines.append("")
308
+ lines.append("---")
309
+ lines.append("")
310
+
311
+ # Critical Paths
312
+ if context.critical_paths:
313
+ lines.append("## 🔄 Critical Paths")
314
+ lines.append("")
315
+ for path in context.critical_paths:
316
+ lines.append(f"### {path.name}")
317
+ lines.append("")
318
+ if path.description:
319
+ lines.append(path.description)
320
+ lines.append("")
321
+ for i, step in enumerate(path.steps, 1):
322
+ lines.append(f"{i}. {step}")
323
+ lines.append("")
324
+ lines.append("---")
325
+ lines.append("")
326
+
327
+ # Known Issues
328
+ if context.known_issues:
329
+ lines.append("## ⚠️ Known Issues")
330
+ lines.append("")
331
+ for issue in context.known_issues:
332
+ lines.append(f"### {issue.issue}")
333
+ lines.append("")
334
+ lines.append(f"**Impact:** {issue.impact}")
335
+ if issue.workaround:
336
+ lines.append(f"**Workaround:** {issue.workaround}")
337
+ lines.append(f"**Priority:** {issue.priority}")
338
+ lines.append("")
339
+ lines.append("---")
340
+ lines.append("")
341
+
342
+ return '\n'.join(lines)
@@ -567,7 +567,7 @@ class DocumentationGenerator:
567
567
 
568
568
  # Documentation coverage badge
569
569
  total_items = len(parsed.module.classes) + len(parsed.get_all_functions())
570
- annotated_items = sum(1 for _ in parsed.module.classes if _.invariants or _.state_transitions)
570
+ annotated_items = sum(1 for _ in parsed.module.classes if _.class_invariants or _.state_transitions)
571
571
  annotated_items += sum(1 for f in parsed.get_all_functions() if f.preconditions or f.postconditions)
572
572
 
573
573
  if total_items > 0:
@@ -20,7 +20,7 @@ SUPABASE_URL = os.getenv("VOODOCS_SUPABASE_URL", "https://sjatkayudkbkmipubhfy.s
20
20
  SUPABASE_ANON_KEY = os.getenv("VOODOCS_SUPABASE_ANON_KEY", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNqYXRrYXl1ZGtia21pcHViaGZ5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjYxNjE0MTMsImV4cCI6MjA4MTczNzQxM30.Wj3dbokjKPsmWTgbPw77aPnCoZCsE5hrFfIH-R_ErzI")
21
21
 
22
22
  # VooDocs version
23
- VERSION = "0.1.1"
23
+ VERSION = "0.1.2"
24
24
 
25
25
  class TelemetryClient:
26
26
  """Anonymous telemetry client for VooDocs CLI."""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voodocs/cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "AI-Native Documentation System - Generate docs, tests, and API specs from @voodocs annotations using the DarkArts language",
5
5
  "main": "cli.py",
6
6
  "bin": {
@@ -46,6 +46,7 @@
46
46
  "files": [
47
47
  "cli.py",
48
48
  "lib/darkarts/annotations/",
49
+ "lib/darkarts/context/",
49
50
  "lib/darkarts/core/",
50
51
  "lib/darkarts/exceptions.py",
51
52
  "lib/darkarts/telemetry.py",
@@ -0,0 +1,152 @@
1
+ # [Project Name] Context
2
+
3
+ **Version:** [current version]
4
+ **Last Updated:** [YYYY-MM-DD]
5
+
6
+ ---
7
+
8
+ ## 🎯 Project Overview
9
+
10
+ @voodocs
11
+ module_purpose: "[One-sentence description of what this project does and why it exists]"
12
+
13
+ ---
14
+
15
+ ## ⚖️ Global Invariants
16
+
17
+ These properties must hold true across the entire codebase.
18
+
19
+ @voodocs
20
+ global_invariants: [
21
+ "[Property that must always be true, e.g., 'user.balance >= 0']",
22
+ "[Another invariant, e.g., '∀ transaction: transaction.amount > 0']",
23
+ "[System-wide constraint, e.g., 'database connections <= max_pool_size']"
24
+ ]
25
+
26
+ ---
27
+
28
+ ## 🏛️ Architecture & Key Decisions
29
+
30
+ @voodocs
31
+ architecture_decisions: [
32
+ {
33
+ decision: "[What was decided]",
34
+ rationale: "[Why this decision was made]",
35
+ alternatives_considered: ["[Option A]", "[Option B]"],
36
+ tradeoffs: "[What was sacrificed or gained]"
37
+ }
38
+ ]
39
+
40
+ modules: {
41
+ "[module_name]": "[Brief description of what this module does]",
42
+ "[another_module]": "[Description]"
43
+ }
44
+
45
+ tech_stack: {
46
+ "languages": ["[Language 1]", "[Language 2]"],
47
+ "frameworks": ["[Framework 1]"],
48
+ "databases": ["[Database type]"],
49
+ "infrastructure": ["[Cloud provider or hosting]"]
50
+ }
51
+
52
+ ---
53
+
54
+ ## 🔄 Critical Paths & User Workflows
55
+
56
+ @voodocs
57
+ critical_paths: [
58
+ {
59
+ name: "[Workflow name, e.g., 'User Registration']",
60
+ steps: ["[Step 1]", "[Step 2]", "[Step 3]"]
61
+ },
62
+ {
63
+ name: "[Another workflow, e.g., 'Payment Processing']",
64
+ steps: ["[Step 1]", "[Step 2]", "[Step 3]"]
65
+ }
66
+ ]
67
+
68
+ ---
69
+
70
+ ## ⚠️ Known Issues & Assumptions
71
+
72
+ @voodocs
73
+ known_issues: [
74
+ {
75
+ issue: "[Description of the issue]",
76
+ impact: "[What breaks or degrades]",
77
+ workaround: "[How to work around it]",
78
+ priority: "[low|medium|high|critical]"
79
+ }
80
+ ]
81
+
82
+ assumptions: [
83
+ "[Assumption 1, e.g., 'Users have internet access']",
84
+ "[Assumption 2, e.g., 'Database is always available']",
85
+ "[Assumption 3, e.g., 'Input data is validated by frontend']"
86
+ ]
87
+
88
+ ---
89
+
90
+ ## 🗓️ Recent Changes
91
+
92
+ @voodocs
93
+ changes: [
94
+ {
95
+ type: "[fix|feat|docs|refactor|test]",
96
+ description: "[What changed]",
97
+ date: "[YYYY-MM-DD]",
98
+ commit: "[commit hash]"
99
+ }
100
+ ]
101
+
102
+ ---
103
+
104
+ ## 🔮 Future Vision
105
+
106
+ @voodocs
107
+ roadmap: [
108
+ "[Feature or improvement planned for next version]",
109
+ "[Another planned feature]",
110
+ "[Long-term goal]"
111
+ ]
112
+
113
+ ---
114
+
115
+ ## 📚 Additional Context
116
+
117
+ ### Performance Characteristics
118
+
119
+ @voodocs
120
+ performance: {
121
+ "typical_response_time": "[e.g., '< 100ms']",
122
+ "max_throughput": "[e.g., '1000 requests/sec']",
123
+ "bottlenecks": ["[Known bottleneck 1]", "[Known bottleneck 2]"]
124
+ }
125
+
126
+ ### Security Considerations
127
+
128
+ @voodocs
129
+ security: {
130
+ "authentication": "[How users are authenticated]",
131
+ "authorization": "[How permissions are enforced]",
132
+ "data_protection": "[How sensitive data is protected]",
133
+ "known_vulnerabilities": ["[CVE or issue description]"]
134
+ }
135
+
136
+ ### Dependencies & Integrations
137
+
138
+ @voodocs
139
+ dependencies: {
140
+ "external_services": ["[Service 1]", "[Service 2]"],
141
+ "third_party_apis": ["[API 1]", "[API 2]"],
142
+ "critical_libraries": ["[Library 1]", "[Library 2]"]
143
+ }
144
+
145
+ ---
146
+
147
+ **Note:** This context file should be updated whenever:
148
+ - Architecture decisions are made
149
+ - Global invariants change
150
+ - New modules are added
151
+ - Critical issues are discovered
152
+ - Major features are shipped