@voodocs/cli 0.1.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.
Files changed (52) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +153 -0
  3. package/USAGE.md +314 -0
  4. package/cli.py +1340 -0
  5. package/examples/.cursorrules +437 -0
  6. package/examples/instructions/.claude/instructions.md +372 -0
  7. package/examples/instructions/.cursorrules +437 -0
  8. package/examples/instructions/.windsurfrules +437 -0
  9. package/examples/instructions/VOODOCS_INSTRUCTIONS.md +437 -0
  10. package/examples/math_example.py +41 -0
  11. package/examples/phase2_test.py +24 -0
  12. package/examples/test_compound_conditions.py +40 -0
  13. package/examples/test_math_example.py +186 -0
  14. package/lib/darkarts/README.md +115 -0
  15. package/lib/darkarts/__init__.py +16 -0
  16. package/lib/darkarts/annotations/__init__.py +34 -0
  17. package/lib/darkarts/annotations/parser.py +618 -0
  18. package/lib/darkarts/annotations/types.py +181 -0
  19. package/lib/darkarts/cli.py +128 -0
  20. package/lib/darkarts/core/__init__.py +32 -0
  21. package/lib/darkarts/core/interface.py +256 -0
  22. package/lib/darkarts/core/loader.py +231 -0
  23. package/lib/darkarts/core/plugin.py +215 -0
  24. package/lib/darkarts/core/registry.py +146 -0
  25. package/lib/darkarts/exceptions.py +51 -0
  26. package/lib/darkarts/parsers/typescript/dist/cli.d.ts +9 -0
  27. package/lib/darkarts/parsers/typescript/dist/cli.d.ts.map +1 -0
  28. package/lib/darkarts/parsers/typescript/dist/cli.js +69 -0
  29. package/lib/darkarts/parsers/typescript/dist/cli.js.map +1 -0
  30. package/lib/darkarts/parsers/typescript/dist/parser.d.ts +111 -0
  31. package/lib/darkarts/parsers/typescript/dist/parser.d.ts.map +1 -0
  32. package/lib/darkarts/parsers/typescript/dist/parser.js +365 -0
  33. package/lib/darkarts/parsers/typescript/dist/parser.js.map +1 -0
  34. package/lib/darkarts/parsers/typescript/package-lock.json +51 -0
  35. package/lib/darkarts/parsers/typescript/package.json +19 -0
  36. package/lib/darkarts/parsers/typescript/src/cli.ts +41 -0
  37. package/lib/darkarts/parsers/typescript/src/parser.ts +408 -0
  38. package/lib/darkarts/parsers/typescript/tsconfig.json +19 -0
  39. package/lib/darkarts/plugins/voodocs/__init__.py +379 -0
  40. package/lib/darkarts/plugins/voodocs/ai_native_plugin.py +151 -0
  41. package/lib/darkarts/plugins/voodocs/annotation_validator.py +280 -0
  42. package/lib/darkarts/plugins/voodocs/api_spec_generator.py +486 -0
  43. package/lib/darkarts/plugins/voodocs/documentation_generator.py +610 -0
  44. package/lib/darkarts/plugins/voodocs/html_exporter.py +260 -0
  45. package/lib/darkarts/plugins/voodocs/instruction_generator.py +706 -0
  46. package/lib/darkarts/plugins/voodocs/pdf_exporter.py +66 -0
  47. package/lib/darkarts/plugins/voodocs/test_generator.py +636 -0
  48. package/package.json +70 -0
  49. package/requirements.txt +13 -0
  50. package/templates/ci/github-actions.yml +73 -0
  51. package/templates/ci/gitlab-ci.yml +35 -0
  52. package/templates/ci/pre-commit-hook.sh +26 -0
@@ -0,0 +1,408 @@
1
+ /**
2
+ * TypeScript/JavaScript Parser for DarkArts Annotations
3
+ *
4
+ * This parser uses the TypeScript Compiler API to extract @voodocs
5
+ * annotations from TypeScript and JavaScript files.
6
+ */
7
+
8
+ import * as ts from 'typescript';
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+
12
+ interface Annotation {
13
+ [key: string]: any;
14
+ }
15
+
16
+ interface FunctionInfo {
17
+ name: string;
18
+ line: number;
19
+ parameters: string[];
20
+ return_type: string | null;
21
+ annotations: Annotation;
22
+ is_async: boolean;
23
+ is_exported: boolean;
24
+ }
25
+
26
+ interface ClassInfo {
27
+ name: string;
28
+ line: number;
29
+ annotations: Annotation;
30
+ methods: FunctionInfo[];
31
+ is_exported: boolean;
32
+ }
33
+
34
+ interface ParseResult {
35
+ file_path: string;
36
+ language: string;
37
+ module_annotations: Annotation;
38
+ classes: ClassInfo[];
39
+ functions: FunctionInfo[];
40
+ interfaces: InterfaceInfo[];
41
+ }
42
+
43
+ interface InterfaceInfo {
44
+ name: string;
45
+ line: number;
46
+ annotations: Annotation;
47
+ is_exported: boolean;
48
+ }
49
+
50
+ export class TypeScriptAnnotationParser {
51
+ private sourceFile: ts.SourceFile;
52
+ private sourceText: string;
53
+
54
+ constructor(private filePath: string) {
55
+ this.sourceText = fs.readFileSync(filePath, 'utf-8');
56
+ this.sourceFile = ts.createSourceFile(
57
+ filePath,
58
+ this.sourceText,
59
+ ts.ScriptTarget.Latest,
60
+ true
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Parse the TypeScript file and extract all @voodocs annotations
66
+ */
67
+ public parse(): ParseResult {
68
+ const result: ParseResult = {
69
+ file_path: this.filePath,
70
+ language: this.filePath.endsWith('.ts') ? 'typescript' : 'javascript',
71
+ module_annotations: this.extractModuleAnnotations(),
72
+ classes: [],
73
+ functions: [],
74
+ interfaces: []
75
+ };
76
+
77
+ // Visit all nodes in the AST
78
+ const visit = (node: ts.Node) => {
79
+ if (ts.isClassDeclaration(node)) {
80
+ const classInfo = this.parseClass(node);
81
+ if (classInfo) {
82
+ result.classes.push(classInfo);
83
+ }
84
+ } else if (ts.isFunctionDeclaration(node)) {
85
+ const funcInfo = this.parseFunction(node);
86
+ if (funcInfo) {
87
+ result.functions.push(funcInfo);
88
+ }
89
+ } else if (ts.isInterfaceDeclaration(node)) {
90
+ const interfaceInfo = this.parseInterface(node);
91
+ if (interfaceInfo) {
92
+ result.interfaces.push(interfaceInfo);
93
+ }
94
+ }
95
+
96
+ ts.forEachChild(node, visit);
97
+ };
98
+
99
+ visit(this.sourceFile);
100
+
101
+ return result;
102
+ }
103
+
104
+ /**
105
+ * Extract module-level annotations from the top of the file
106
+ */
107
+ private extractModuleAnnotations(): Annotation {
108
+ const leadingComments = this.getLeadingComments(this.sourceFile);
109
+ for (const comment of leadingComments) {
110
+ if (this.isDarkArtsComment(comment)) {
111
+ return this.parseAnnotationText(comment);
112
+ }
113
+ }
114
+ return {};
115
+ }
116
+
117
+ /**
118
+ * Parse a class declaration
119
+ */
120
+ private parseClass(node: ts.ClassDeclaration): ClassInfo | null {
121
+ const name = node.name?.text;
122
+ if (!name) return null;
123
+
124
+ const line = this.getLineNumber(node);
125
+ const annotations = this.extractAnnotations(node);
126
+ const methods: FunctionInfo[] = [];
127
+
128
+ // Extract methods
129
+ node.members.forEach(member => {
130
+ if (ts.isMethodDeclaration(member)) {
131
+ const methodInfo = this.parseMethod(member);
132
+ if (methodInfo) {
133
+ methods.push(methodInfo);
134
+ }
135
+ }
136
+ });
137
+
138
+ return {
139
+ name,
140
+ line,
141
+ annotations,
142
+ methods,
143
+ is_exported: this.isExported(node)
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Parse a function declaration
149
+ */
150
+ private parseFunction(node: ts.FunctionDeclaration): FunctionInfo | null {
151
+ const name = node.name?.text;
152
+ if (!name) return null;
153
+
154
+ return {
155
+ name,
156
+ line: this.getLineNumber(node),
157
+ parameters: this.extractParameters(node),
158
+ return_type: this.extractReturnType(node),
159
+ annotations: this.extractAnnotations(node),
160
+ is_async: this.isAsync(node),
161
+ is_exported: this.isExported(node)
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Parse a method declaration
167
+ */
168
+ private parseMethod(node: ts.MethodDeclaration): FunctionInfo | null {
169
+ const name = (node.name as ts.Identifier).text;
170
+ if (!name) return null;
171
+
172
+ return {
173
+ name,
174
+ line: this.getLineNumber(node),
175
+ parameters: this.extractParameters(node),
176
+ return_type: this.extractReturnType(node),
177
+ annotations: this.extractAnnotations(node),
178
+ is_async: this.isAsync(node),
179
+ is_exported: false // Methods are not directly exported
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Parse an interface declaration
185
+ */
186
+ private parseInterface(node: ts.InterfaceDeclaration): InterfaceInfo | null {
187
+ const name = node.name.text;
188
+ if (!name) return null;
189
+
190
+ return {
191
+ name,
192
+ line: this.getLineNumber(node),
193
+ annotations: this.extractAnnotations(node),
194
+ is_exported: this.isExported(node)
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Extract annotations from a node's JSDoc comments
200
+ */
201
+ private extractAnnotations(node: ts.Node): Annotation {
202
+ const comments = this.getLeadingComments(node);
203
+ // Find the LAST (closest) @voodocs comment
204
+ let lastDarkArtsComment: string | null = null;
205
+ for (const comment of comments) {
206
+ if (this.isDarkArtsComment(comment)) {
207
+ lastDarkArtsComment = comment;
208
+ }
209
+ }
210
+ if (lastDarkArtsComment) {
211
+ return this.parseAnnotationText(lastDarkArtsComment);
212
+ }
213
+ return {};
214
+ }
215
+
216
+ /**
217
+ * Get leading comments for a node
218
+ */
219
+ private getLeadingComments(node: ts.Node): string[] {
220
+ const comments: string[] = [];
221
+ const fullText = this.sourceText;
222
+ const nodePos = node.getFullStart();
223
+
224
+ const ranges = ts.getLeadingCommentRanges(fullText, nodePos);
225
+ if (ranges) {
226
+ for (const range of ranges) {
227
+ const commentText = fullText.substring(range.pos, range.end);
228
+ comments.push(commentText);
229
+ }
230
+ }
231
+
232
+ return comments;
233
+ }
234
+
235
+ /**
236
+ * Check if a comment contains @voodocs annotation
237
+ */
238
+ private isDarkArtsComment(comment: string): boolean {
239
+ return comment.includes('@voodocs');
240
+ }
241
+
242
+ /**
243
+ * Parse the annotation text and extract key-value pairs
244
+ */
245
+ private parseAnnotationText(comment: string): Annotation {
246
+ const annotation: Annotation = {};
247
+
248
+ // Remove comment markers
249
+ let text = comment.replace(/^\/\*\*?|\*\/$/g, '').trim();
250
+ text = text.replace(/^\s*\*\s?/gm, ''); // Remove leading asterisks
251
+
252
+ // Remove @voodocs marker
253
+ text = text.replace(/@voodocs\s*/i, '');
254
+
255
+ // Parse key-value pairs
256
+ const lines = text.split('\n');
257
+ let currentKey: string | null = null;
258
+ let currentValue: string[] = [];
259
+
260
+ for (const line of lines) {
261
+ const trimmed = line.trim();
262
+ if (!trimmed) continue;
263
+
264
+ // Check if this is a new key
265
+ const keyMatch = trimmed.match(/^(\w+):\s*(.*)$/);
266
+ if (keyMatch) {
267
+ // Save previous key-value pair
268
+ if (currentKey) {
269
+ annotation[currentKey] = this.parseValue(currentValue.join('\n'));
270
+ }
271
+
272
+ currentKey = keyMatch[1];
273
+ currentValue = [keyMatch[2]];
274
+ } else if (currentKey) {
275
+ // Continue current value
276
+ currentValue.push(trimmed);
277
+ }
278
+ }
279
+
280
+ // Save last key-value pair
281
+ if (currentKey) {
282
+ annotation[currentKey] = this.parseValue(currentValue.join('\n'));
283
+ }
284
+
285
+ return annotation;
286
+ }
287
+
288
+ /**
289
+ * Parse a value (string, array, or object)
290
+ */
291
+ private parseValue(value: string): any {
292
+ value = value.trim();
293
+
294
+ // Try to parse as JSON array
295
+ if (value.startsWith('[') && value.endsWith(']')) {
296
+ try {
297
+ // Extract array content
298
+ const content = value.slice(1, -1).trim();
299
+ if (!content) return [];
300
+
301
+ // Split by newlines first (each line is typically one item)
302
+ const items: string[] = [];
303
+ let currentItem = '';
304
+ let inQuotes = false;
305
+ let quoteChar = '';
306
+
307
+ for (let i = 0; i < content.length; i++) {
308
+ const char = content[i];
309
+
310
+ if ((char === '"' || char === "'") && (i === 0 || content[i-1] !== '\\')) {
311
+ if (!inQuotes) {
312
+ inQuotes = true;
313
+ quoteChar = char;
314
+ } else if (char === quoteChar) {
315
+ inQuotes = false;
316
+ }
317
+ }
318
+
319
+ if (char === ',' && !inQuotes) {
320
+ if (currentItem.trim()) {
321
+ items.push(currentItem.trim());
322
+ }
323
+ currentItem = '';
324
+ } else {
325
+ currentItem += char;
326
+ }
327
+ }
328
+
329
+ // Add the last item
330
+ if (currentItem.trim()) {
331
+ items.push(currentItem.trim());
332
+ }
333
+
334
+ // Clean up each item
335
+ return items.map(item => {
336
+ item = item.trim();
337
+ // Remove surrounding quotes
338
+ if ((item.startsWith('"') && item.endsWith('"')) ||
339
+ (item.startsWith("'") && item.endsWith("'"))) {
340
+ return item.slice(1, -1);
341
+ }
342
+ return item;
343
+ });
344
+ } catch (e) {
345
+ return value;
346
+ }
347
+ }
348
+
349
+ // Remove quotes if present
350
+ if ((value.startsWith('"') && value.endsWith('"')) ||
351
+ (value.startsWith("'") && value.endsWith("'"))) {
352
+ return value.slice(1, -1);
353
+ }
354
+
355
+ return value;
356
+ }
357
+
358
+ /**
359
+ * Extract parameter names from a function/method
360
+ */
361
+ private extractParameters(node: ts.FunctionDeclaration | ts.MethodDeclaration): string[] {
362
+ return node.parameters.map(param => {
363
+ const name = (param.name as ts.Identifier).text;
364
+ const type = param.type ? `: ${param.type.getText(this.sourceFile)}` : '';
365
+ return `${name}${type}`;
366
+ });
367
+ }
368
+
369
+ /**
370
+ * Extract return type from a function/method
371
+ */
372
+ private extractReturnType(node: ts.FunctionDeclaration | ts.MethodDeclaration): string | null {
373
+ if (node.type) {
374
+ return node.type.getText(this.sourceFile);
375
+ }
376
+ return null;
377
+ }
378
+
379
+ /**
380
+ * Check if a function/method is async
381
+ */
382
+ private isAsync(node: ts.FunctionDeclaration | ts.MethodDeclaration): boolean {
383
+ if (ts.canHaveModifiers(node)) {
384
+ const modifiers = ts.getModifiers(node);
385
+ return modifiers?.some((m: ts.Modifier) => m.kind === ts.SyntaxKind.AsyncKeyword) || false;
386
+ }
387
+ return false;
388
+ }
389
+
390
+ /**
391
+ * Check if a node is exported
392
+ */
393
+ private isExported(node: ts.Node): boolean {
394
+ if (ts.canHaveModifiers(node)) {
395
+ const modifiers = ts.getModifiers(node);
396
+ return modifiers?.some((m: ts.Modifier) => m.kind === ts.SyntaxKind.ExportKeyword) || false;
397
+ }
398
+ return false;
399
+ }
400
+
401
+ /**
402
+ * Get the line number of a node
403
+ */
404
+ private getLineNumber(node: ts.Node): number {
405
+ const { line } = this.sourceFile.getLineAndCharacterOfPosition(node.getStart());
406
+ return line + 1; // Convert to 1-indexed
407
+ }
408
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }