nc1709 1.15.4__py3-none-any.whl

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 (86) hide show
  1. nc1709/__init__.py +13 -0
  2. nc1709/agent/__init__.py +36 -0
  3. nc1709/agent/core.py +505 -0
  4. nc1709/agent/mcp_bridge.py +245 -0
  5. nc1709/agent/permissions.py +298 -0
  6. nc1709/agent/tools/__init__.py +21 -0
  7. nc1709/agent/tools/base.py +440 -0
  8. nc1709/agent/tools/bash_tool.py +367 -0
  9. nc1709/agent/tools/file_tools.py +454 -0
  10. nc1709/agent/tools/notebook_tools.py +516 -0
  11. nc1709/agent/tools/search_tools.py +322 -0
  12. nc1709/agent/tools/task_tool.py +284 -0
  13. nc1709/agent/tools/web_tools.py +555 -0
  14. nc1709/agents/__init__.py +17 -0
  15. nc1709/agents/auto_fix.py +506 -0
  16. nc1709/agents/test_generator.py +507 -0
  17. nc1709/checkpoints.py +372 -0
  18. nc1709/cli.py +3380 -0
  19. nc1709/cli_ui.py +1080 -0
  20. nc1709/cognitive/__init__.py +149 -0
  21. nc1709/cognitive/anticipation.py +594 -0
  22. nc1709/cognitive/context_engine.py +1046 -0
  23. nc1709/cognitive/council.py +824 -0
  24. nc1709/cognitive/learning.py +761 -0
  25. nc1709/cognitive/router.py +583 -0
  26. nc1709/cognitive/system.py +519 -0
  27. nc1709/config.py +155 -0
  28. nc1709/custom_commands.py +300 -0
  29. nc1709/executor.py +333 -0
  30. nc1709/file_controller.py +354 -0
  31. nc1709/git_integration.py +308 -0
  32. nc1709/github_integration.py +477 -0
  33. nc1709/image_input.py +446 -0
  34. nc1709/linting.py +519 -0
  35. nc1709/llm_adapter.py +667 -0
  36. nc1709/logger.py +192 -0
  37. nc1709/mcp/__init__.py +18 -0
  38. nc1709/mcp/client.py +370 -0
  39. nc1709/mcp/manager.py +407 -0
  40. nc1709/mcp/protocol.py +210 -0
  41. nc1709/mcp/server.py +473 -0
  42. nc1709/memory/__init__.py +20 -0
  43. nc1709/memory/embeddings.py +325 -0
  44. nc1709/memory/indexer.py +474 -0
  45. nc1709/memory/sessions.py +432 -0
  46. nc1709/memory/vector_store.py +451 -0
  47. nc1709/models/__init__.py +86 -0
  48. nc1709/models/detector.py +377 -0
  49. nc1709/models/formats.py +315 -0
  50. nc1709/models/manager.py +438 -0
  51. nc1709/models/registry.py +497 -0
  52. nc1709/performance/__init__.py +343 -0
  53. nc1709/performance/cache.py +705 -0
  54. nc1709/performance/pipeline.py +611 -0
  55. nc1709/performance/tiering.py +543 -0
  56. nc1709/plan_mode.py +362 -0
  57. nc1709/plugins/__init__.py +17 -0
  58. nc1709/plugins/agents/__init__.py +18 -0
  59. nc1709/plugins/agents/django_agent.py +912 -0
  60. nc1709/plugins/agents/docker_agent.py +623 -0
  61. nc1709/plugins/agents/fastapi_agent.py +887 -0
  62. nc1709/plugins/agents/git_agent.py +731 -0
  63. nc1709/plugins/agents/nextjs_agent.py +867 -0
  64. nc1709/plugins/base.py +359 -0
  65. nc1709/plugins/manager.py +411 -0
  66. nc1709/plugins/registry.py +337 -0
  67. nc1709/progress.py +443 -0
  68. nc1709/prompts/__init__.py +22 -0
  69. nc1709/prompts/agent_system.py +180 -0
  70. nc1709/prompts/task_prompts.py +340 -0
  71. nc1709/prompts/unified_prompt.py +133 -0
  72. nc1709/reasoning_engine.py +541 -0
  73. nc1709/remote_client.py +266 -0
  74. nc1709/shell_completions.py +349 -0
  75. nc1709/slash_commands.py +649 -0
  76. nc1709/task_classifier.py +408 -0
  77. nc1709/version_check.py +177 -0
  78. nc1709/web/__init__.py +8 -0
  79. nc1709/web/server.py +950 -0
  80. nc1709/web/templates/index.html +1127 -0
  81. nc1709-1.15.4.dist-info/METADATA +858 -0
  82. nc1709-1.15.4.dist-info/RECORD +86 -0
  83. nc1709-1.15.4.dist-info/WHEEL +5 -0
  84. nc1709-1.15.4.dist-info/entry_points.txt +2 -0
  85. nc1709-1.15.4.dist-info/licenses/LICENSE +9 -0
  86. nc1709-1.15.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,507 @@
1
+ """
2
+ Test Generator Agent
3
+ Automatically generates unit tests for code
4
+ """
5
+ import os
6
+ import re
7
+ import ast
8
+ from typing import List, Dict, Any, Optional, Tuple
9
+ from dataclasses import dataclass
10
+ from enum import Enum
11
+
12
+
13
+ class TestFramework(Enum):
14
+ """Supported test frameworks"""
15
+ PYTEST = "pytest"
16
+ UNITTEST = "unittest"
17
+ JEST = "jest"
18
+ MOCHA = "mocha"
19
+ VITEST = "vitest"
20
+ GO_TEST = "go_test"
21
+ RUST_TEST = "rust_test"
22
+
23
+
24
+ @dataclass
25
+ class FunctionInfo:
26
+ """Information about a function to test"""
27
+ name: str
28
+ file_path: str
29
+ line_number: int
30
+ signature: str
31
+ docstring: Optional[str]
32
+ code: str
33
+ is_async: bool
34
+ is_method: bool
35
+ class_name: Optional[str]
36
+
37
+
38
+ @dataclass
39
+ class GeneratedTest:
40
+ """A generated test"""
41
+ function_name: str
42
+ test_name: str
43
+ test_code: str
44
+ description: str
45
+ framework: TestFramework
46
+
47
+
48
+ class TestGeneratorAgent:
49
+ """Agent that generates unit tests for code"""
50
+
51
+ def __init__(self, llm_adapter=None):
52
+ """Initialize the test generator agent
53
+
54
+ Args:
55
+ llm_adapter: LLMAdapter instance for generating tests
56
+ """
57
+ self.llm = llm_adapter
58
+
59
+ def analyze_file(self, file_path: str) -> List[FunctionInfo]:
60
+ """Analyze a file to find testable functions
61
+
62
+ Args:
63
+ file_path: Path to the file
64
+
65
+ Returns:
66
+ List of functions found
67
+ """
68
+ if not os.path.exists(file_path):
69
+ return []
70
+
71
+ ext = os.path.splitext(file_path)[1].lower()
72
+
73
+ with open(file_path, 'r') as f:
74
+ content = f.read()
75
+
76
+ if ext == '.py':
77
+ return self._analyze_python(file_path, content)
78
+ elif ext in ('.js', '.jsx', '.ts', '.tsx'):
79
+ return self._analyze_js_ts(file_path, content)
80
+ else:
81
+ return self._analyze_generic(file_path, content)
82
+
83
+ def generate_tests(
84
+ self,
85
+ functions: List[FunctionInfo],
86
+ framework: Optional[TestFramework] = None
87
+ ) -> List[GeneratedTest]:
88
+ """Generate tests for functions
89
+
90
+ Args:
91
+ functions: List of functions to test
92
+ framework: Test framework to use (auto-detect if None)
93
+
94
+ Returns:
95
+ List of generated tests
96
+ """
97
+ if not self.llm:
98
+ raise RuntimeError("LLM adapter required for generating tests")
99
+
100
+ if not framework:
101
+ # Auto-detect based on first function's language
102
+ if functions:
103
+ ext = os.path.splitext(functions[0].file_path)[1].lower()
104
+ framework = self._detect_framework(ext)
105
+ else:
106
+ framework = TestFramework.PYTEST
107
+
108
+ tests = []
109
+ for func in functions:
110
+ test = self._generate_test(func, framework)
111
+ if test:
112
+ tests.append(test)
113
+
114
+ return tests
115
+
116
+ def generate_test_file(
117
+ self,
118
+ source_file: str,
119
+ output_file: Optional[str] = None,
120
+ framework: Optional[TestFramework] = None
121
+ ) -> Tuple[str, List[GeneratedTest]]:
122
+ """Generate a complete test file for a source file
123
+
124
+ Args:
125
+ source_file: Path to source file
126
+ output_file: Path for output test file (auto-generate if None)
127
+ framework: Test framework to use
128
+
129
+ Returns:
130
+ Tuple of (output_file_path, generated_tests)
131
+ """
132
+ functions = self.analyze_file(source_file)
133
+
134
+ if not functions:
135
+ return "", []
136
+
137
+ # Auto-detect framework
138
+ ext = os.path.splitext(source_file)[1].lower()
139
+ if framework is None:
140
+ framework = self._detect_framework(ext)
141
+
142
+ # Generate output filename
143
+ if output_file is None:
144
+ output_file = self._generate_test_filename(source_file, framework)
145
+
146
+ # Generate tests
147
+ tests = self.generate_tests(functions, framework)
148
+
149
+ # Assemble test file
150
+ test_content = self._assemble_test_file(
151
+ source_file,
152
+ tests,
153
+ framework
154
+ )
155
+
156
+ # Write test file
157
+ os.makedirs(os.path.dirname(output_file) or '.', exist_ok=True)
158
+ with open(output_file, 'w') as f:
159
+ f.write(test_content)
160
+
161
+ return output_file, tests
162
+
163
+ def _analyze_python(self, file_path: str, content: str) -> List[FunctionInfo]:
164
+ """Analyze Python file"""
165
+ functions = []
166
+
167
+ try:
168
+ tree = ast.parse(content)
169
+ except SyntaxError:
170
+ return []
171
+
172
+ for node in ast.walk(tree):
173
+ if isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
174
+ # Skip private functions
175
+ if node.name.startswith('_') and not node.name.startswith('__'):
176
+ continue
177
+
178
+ # Get function signature
179
+ args = []
180
+ for arg in node.args.args:
181
+ args.append(arg.arg)
182
+
183
+ signature = f"{node.name}({', '.join(args)})"
184
+
185
+ # Get docstring
186
+ docstring = ast.get_docstring(node)
187
+
188
+ # Get code
189
+ start_line = node.lineno - 1
190
+ end_line = node.end_lineno
191
+ lines = content.split('\n')
192
+ code = '\n'.join(lines[start_line:end_line])
193
+
194
+ # Check if method
195
+ is_method = False
196
+ class_name = None
197
+ for parent in ast.walk(tree):
198
+ if isinstance(parent, ast.ClassDef):
199
+ if node in ast.walk(parent):
200
+ is_method = True
201
+ class_name = parent.name
202
+ break
203
+
204
+ functions.append(FunctionInfo(
205
+ name=node.name,
206
+ file_path=file_path,
207
+ line_number=node.lineno,
208
+ signature=signature,
209
+ docstring=docstring,
210
+ code=code,
211
+ is_async=isinstance(node, ast.AsyncFunctionDef),
212
+ is_method=is_method,
213
+ class_name=class_name
214
+ ))
215
+
216
+ return functions
217
+
218
+ def _analyze_js_ts(self, file_path: str, content: str) -> List[FunctionInfo]:
219
+ """Analyze JavaScript/TypeScript file"""
220
+ functions = []
221
+
222
+ # Regular function pattern
223
+ func_pattern = r'(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)'
224
+
225
+ for match in re.finditer(func_pattern, content):
226
+ name = match.group(1)
227
+ params = match.group(2)
228
+ start = match.start()
229
+
230
+ # Skip private functions
231
+ if name.startswith('_'):
232
+ continue
233
+
234
+ line_number = content[:start].count('\n') + 1
235
+
236
+ # Get function body (simplified)
237
+ brace_start = content.find('{', match.end())
238
+ if brace_start == -1:
239
+ continue
240
+
241
+ # Find matching closing brace
242
+ depth = 1
243
+ pos = brace_start + 1
244
+ while depth > 0 and pos < len(content):
245
+ if content[pos] == '{':
246
+ depth += 1
247
+ elif content[pos] == '}':
248
+ depth -= 1
249
+ pos += 1
250
+
251
+ code = content[match.start():pos]
252
+
253
+ functions.append(FunctionInfo(
254
+ name=name,
255
+ file_path=file_path,
256
+ line_number=line_number,
257
+ signature=f"{name}({params})",
258
+ docstring=None,
259
+ code=code,
260
+ is_async='async' in content[max(0, match.start()-10):match.start()],
261
+ is_method=False,
262
+ class_name=None
263
+ ))
264
+
265
+ # Arrow function pattern
266
+ arrow_pattern = r'(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>'
267
+
268
+ for match in re.finditer(arrow_pattern, content):
269
+ name = match.group(1)
270
+ start = match.start()
271
+
272
+ if name.startswith('_'):
273
+ continue
274
+
275
+ line_number = content[:start].count('\n') + 1
276
+
277
+ # Get function body
278
+ arrow_pos = content.find('=>', match.end() - 10)
279
+ if arrow_pos == -1:
280
+ continue
281
+
282
+ # Find end of arrow function
283
+ pos = arrow_pos + 2
284
+ while pos < len(content) and content[pos] in ' \t\n':
285
+ pos += 1
286
+
287
+ if pos < len(content) and content[pos] == '{':
288
+ # Block body
289
+ depth = 1
290
+ pos += 1
291
+ while depth > 0 and pos < len(content):
292
+ if content[pos] == '{':
293
+ depth += 1
294
+ elif content[pos] == '}':
295
+ depth -= 1
296
+ pos += 1
297
+ else:
298
+ # Expression body - find end
299
+ pos = content.find('\n', pos)
300
+ if pos == -1:
301
+ pos = len(content)
302
+
303
+ code = content[match.start():pos]
304
+
305
+ functions.append(FunctionInfo(
306
+ name=name,
307
+ file_path=file_path,
308
+ line_number=line_number,
309
+ signature=name,
310
+ docstring=None,
311
+ code=code,
312
+ is_async='async' in content[max(0, match.start()-10):match.start()],
313
+ is_method=False,
314
+ class_name=None
315
+ ))
316
+
317
+ return functions
318
+
319
+ def _analyze_generic(self, file_path: str, content: str) -> List[FunctionInfo]:
320
+ """Generic function analysis"""
321
+ # Basic pattern for functions
322
+ func_pattern = r'(?:func|def|fn|function)\s+(\w+)\s*\([^)]*\)'
323
+ functions = []
324
+
325
+ for match in re.finditer(func_pattern, content):
326
+ name = match.group(1)
327
+ start = match.start()
328
+ line_number = content[:start].count('\n') + 1
329
+
330
+ functions.append(FunctionInfo(
331
+ name=name,
332
+ file_path=file_path,
333
+ line_number=line_number,
334
+ signature=name,
335
+ docstring=None,
336
+ code=match.group(0),
337
+ is_async=False,
338
+ is_method=False,
339
+ class_name=None
340
+ ))
341
+
342
+ return functions
343
+
344
+ def _detect_framework(self, ext: str) -> TestFramework:
345
+ """Detect appropriate test framework"""
346
+ mapping = {
347
+ '.py': TestFramework.PYTEST,
348
+ '.js': TestFramework.JEST,
349
+ '.jsx': TestFramework.JEST,
350
+ '.ts': TestFramework.JEST,
351
+ '.tsx': TestFramework.JEST,
352
+ '.go': TestFramework.GO_TEST,
353
+ '.rs': TestFramework.RUST_TEST,
354
+ }
355
+ return mapping.get(ext, TestFramework.PYTEST)
356
+
357
+ def _generate_test_filename(self, source_file: str, framework: TestFramework) -> str:
358
+ """Generate test filename"""
359
+ base = os.path.basename(source_file)
360
+ name, ext = os.path.splitext(base)
361
+ dir_path = os.path.dirname(source_file)
362
+
363
+ if framework in (TestFramework.PYTEST, TestFramework.UNITTEST):
364
+ return os.path.join(dir_path, f"test_{name}{ext}")
365
+ elif framework in (TestFramework.JEST, TestFramework.VITEST, TestFramework.MOCHA):
366
+ return os.path.join(dir_path, f"{name}.test{ext}")
367
+ else:
368
+ return os.path.join(dir_path, f"{name}_test{ext}")
369
+
370
+ def _generate_test(self, func: FunctionInfo, framework: TestFramework) -> Optional[GeneratedTest]:
371
+ """Generate a test for a function"""
372
+ from ..llm_adapter import TaskType
373
+
374
+ prompt = f"""Generate a unit test for this function.
375
+
376
+ Function: {func.signature}
377
+ File: {func.file_path}
378
+ {'Docstring: ' + func.docstring if func.docstring else ''}
379
+
380
+ Code:
381
+ ```
382
+ {func.code}
383
+ ```
384
+
385
+ Test Framework: {framework.value}
386
+ {'This is a method of class ' + func.class_name if func.is_method else ''}
387
+ {'This is an async function' if func.is_async else ''}
388
+
389
+ Generate a comprehensive test that:
390
+ 1. Tests the main functionality
391
+ 2. Tests edge cases
392
+ 3. Tests error handling if applicable
393
+
394
+ Provide ONLY the test code, no explanations.
395
+ """
396
+
397
+ response = self.llm.complete(prompt, task_type=TaskType.CODING, max_tokens=1000)
398
+
399
+ # Extract code from response
400
+ code = self._extract_code(response)
401
+ if not code:
402
+ return None
403
+
404
+ test_name = f"test_{func.name}"
405
+
406
+ return GeneratedTest(
407
+ function_name=func.name,
408
+ test_name=test_name,
409
+ test_code=code,
410
+ description=f"Tests for {func.signature}",
411
+ framework=framework
412
+ )
413
+
414
+ def _extract_code(self, response: str) -> Optional[str]:
415
+ """Extract code from LLM response"""
416
+ code_match = re.search(r'```(?:\w+)?\n([\s\S]*?)```', response)
417
+ if code_match:
418
+ return code_match.group(1).strip()
419
+ return response.strip()
420
+
421
+ def _assemble_test_file(
422
+ self,
423
+ source_file: str,
424
+ tests: List[GeneratedTest],
425
+ framework: TestFramework
426
+ ) -> str:
427
+ """Assemble tests into a complete test file"""
428
+ module_name = os.path.splitext(os.path.basename(source_file))[0]
429
+
430
+ if framework == TestFramework.PYTEST:
431
+ header = f'''"""
432
+ Tests for {module_name}
433
+ Generated by NC1709
434
+ """
435
+ import pytest
436
+ from {module_name} import *
437
+
438
+ '''
439
+ elif framework == TestFramework.UNITTEST:
440
+ header = f'''"""
441
+ Tests for {module_name}
442
+ Generated by NC1709
443
+ """
444
+ import unittest
445
+ from {module_name} import *
446
+
447
+ '''
448
+ elif framework in (TestFramework.JEST, TestFramework.VITEST):
449
+ header = f'''/**
450
+ * Tests for {module_name}
451
+ * Generated by NC1709
452
+ */
453
+ import {{ describe, it, expect }} from '{framework.value}';
454
+ import * as module from './{module_name}';
455
+
456
+ '''
457
+ else:
458
+ header = f"// Tests for {module_name}\n// Generated by NC1709\n\n"
459
+
460
+ test_code = '\n\n'.join(test.test_code for test in tests)
461
+
462
+ return header + test_code
463
+
464
+
465
+ def generate_tests_command(file_path: str, output_path: Optional[str] = None) -> str:
466
+ """Command-line interface for test generation
467
+
468
+ Args:
469
+ file_path: Path to source file
470
+ output_path: Path for output test file
471
+
472
+ Returns:
473
+ Summary of results
474
+ """
475
+ from ..llm_adapter import LLMAdapter
476
+
477
+ agent = TestGeneratorAgent(LLMAdapter())
478
+
479
+ output = []
480
+ output.append(f"\n{'='*60}")
481
+ output.append(f"Test Generator: {file_path}")
482
+ output.append(f"{'='*60}\n")
483
+
484
+ # Analyze file
485
+ functions = agent.analyze_file(file_path)
486
+
487
+ if not functions:
488
+ output.append("No testable functions found!")
489
+ return '\n'.join(output)
490
+
491
+ output.append(f"Found {len(functions)} function(s):\n")
492
+ for func in functions:
493
+ output.append(f" - {func.signature} (line {func.line_number})")
494
+
495
+ # Generate tests
496
+ output_file, tests = agent.generate_test_file(file_path, output_path)
497
+
498
+ if tests:
499
+ output.append(f"\nGenerated {len(tests)} test(s):")
500
+ for test in tests:
501
+ output.append(f" - {test.test_name}")
502
+
503
+ output.append(f"\nTest file written to: {output_file}")
504
+ else:
505
+ output.append("\nNo tests could be generated.")
506
+
507
+ return '\n'.join(output)