ctrlcode 0.1.0__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 (75) hide show
  1. ctrlcode/__init__.py +8 -0
  2. ctrlcode/agents/__init__.py +29 -0
  3. ctrlcode/agents/cleanup.py +388 -0
  4. ctrlcode/agents/communication.py +439 -0
  5. ctrlcode/agents/observability.py +421 -0
  6. ctrlcode/agents/react_loop.py +297 -0
  7. ctrlcode/agents/registry.py +211 -0
  8. ctrlcode/agents/result_parser.py +242 -0
  9. ctrlcode/agents/workflow.py +723 -0
  10. ctrlcode/analysis/__init__.py +28 -0
  11. ctrlcode/analysis/ast_diff.py +163 -0
  12. ctrlcode/analysis/bug_detector.py +149 -0
  13. ctrlcode/analysis/code_graphs.py +329 -0
  14. ctrlcode/analysis/semantic.py +205 -0
  15. ctrlcode/analysis/static.py +183 -0
  16. ctrlcode/analysis/synthesizer.py +281 -0
  17. ctrlcode/analysis/tests.py +189 -0
  18. ctrlcode/cleanup/__init__.py +16 -0
  19. ctrlcode/cleanup/auto_merge.py +350 -0
  20. ctrlcode/cleanup/doc_gardening.py +388 -0
  21. ctrlcode/cleanup/pr_automation.py +330 -0
  22. ctrlcode/cleanup/scheduler.py +356 -0
  23. ctrlcode/config.py +380 -0
  24. ctrlcode/embeddings/__init__.py +6 -0
  25. ctrlcode/embeddings/embedder.py +192 -0
  26. ctrlcode/embeddings/vector_store.py +213 -0
  27. ctrlcode/fuzzing/__init__.py +24 -0
  28. ctrlcode/fuzzing/analyzer.py +280 -0
  29. ctrlcode/fuzzing/budget.py +112 -0
  30. ctrlcode/fuzzing/context.py +665 -0
  31. ctrlcode/fuzzing/context_fuzzer.py +506 -0
  32. ctrlcode/fuzzing/derived_orchestrator.py +732 -0
  33. ctrlcode/fuzzing/oracle_adapter.py +135 -0
  34. ctrlcode/linters/__init__.py +11 -0
  35. ctrlcode/linters/hand_rolled_utils.py +221 -0
  36. ctrlcode/linters/yolo_parsing.py +217 -0
  37. ctrlcode/metrics/__init__.py +6 -0
  38. ctrlcode/metrics/dashboard.py +283 -0
  39. ctrlcode/metrics/tech_debt.py +663 -0
  40. ctrlcode/paths.py +68 -0
  41. ctrlcode/permissions.py +179 -0
  42. ctrlcode/providers/__init__.py +15 -0
  43. ctrlcode/providers/anthropic.py +138 -0
  44. ctrlcode/providers/base.py +77 -0
  45. ctrlcode/providers/openai.py +197 -0
  46. ctrlcode/providers/parallel.py +104 -0
  47. ctrlcode/server.py +871 -0
  48. ctrlcode/session/__init__.py +6 -0
  49. ctrlcode/session/baseline.py +57 -0
  50. ctrlcode/session/manager.py +967 -0
  51. ctrlcode/skills/__init__.py +10 -0
  52. ctrlcode/skills/builtin/commit.toml +29 -0
  53. ctrlcode/skills/builtin/docs.toml +25 -0
  54. ctrlcode/skills/builtin/refactor.toml +33 -0
  55. ctrlcode/skills/builtin/review.toml +28 -0
  56. ctrlcode/skills/builtin/test.toml +28 -0
  57. ctrlcode/skills/loader.py +111 -0
  58. ctrlcode/skills/registry.py +139 -0
  59. ctrlcode/storage/__init__.py +19 -0
  60. ctrlcode/storage/history_db.py +708 -0
  61. ctrlcode/tools/__init__.py +220 -0
  62. ctrlcode/tools/bash.py +112 -0
  63. ctrlcode/tools/browser.py +352 -0
  64. ctrlcode/tools/executor.py +153 -0
  65. ctrlcode/tools/explore.py +486 -0
  66. ctrlcode/tools/mcp.py +108 -0
  67. ctrlcode/tools/observability.py +561 -0
  68. ctrlcode/tools/registry.py +193 -0
  69. ctrlcode/tools/todo.py +291 -0
  70. ctrlcode/tools/update.py +266 -0
  71. ctrlcode/tools/webfetch.py +147 -0
  72. ctrlcode-0.1.0.dist-info/METADATA +93 -0
  73. ctrlcode-0.1.0.dist-info/RECORD +75 -0
  74. ctrlcode-0.1.0.dist-info/WHEEL +4 -0
  75. ctrlcode-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,10 @@
1
+ """Skills system for ctrl-code."""
2
+
3
+ from .loader import SkillLoader, Skill
4
+ from .registry import SkillRegistry
5
+
6
+ __all__ = [
7
+ "SkillLoader",
8
+ "Skill",
9
+ "SkillRegistry",
10
+ ]
@@ -0,0 +1,29 @@
1
+ name = "commit"
2
+ description = "Create a git commit with semantic commit message"
3
+ requires_args = false
4
+
5
+ prompt = """
6
+ Create a git commit following these steps:
7
+
8
+ 1. Run `git status` to see all changes (staged and unstaged)
9
+ 2. Run `git diff` to review the actual changes
10
+ 3. Run `git log --oneline -5` to see recent commit message style
11
+ 4. Analyze the changes and create a semantic commit message that:
12
+ - Summarizes the nature of changes (new feature, bug fix, refactor, etc.)
13
+ - Is concise (1-2 sentences)
14
+ - Focuses on the "why" rather than the "what"
15
+ - Follows the commit style of this repository
16
+ 5. Stage relevant files (avoid staging secrets like .env files)
17
+ 6. Create the commit with message ending with:
18
+ Co-Authored-By: Ctrl+Code <noreply@ctrl.code>
19
+
20
+ Important:
21
+ - Do NOT commit files that contain secrets
22
+ - Do NOT push unless explicitly requested
23
+ - Use semantic commit format (e.g., "feat:", "fix:", "refactor:")
24
+ """
25
+
26
+ examples = [
27
+ "/commit",
28
+ "skill:commit"
29
+ ]
@@ -0,0 +1,25 @@
1
+ name = "docs"
2
+ description = "Generate or improve documentation"
3
+ requires_args = true
4
+
5
+ prompt = """
6
+ Generate or improve documentation for: {args}
7
+
8
+ 1. Read the relevant code files
9
+ 2. Understand the purpose and functionality
10
+ 3. Create documentation that includes:
11
+ - Clear description of what it does
12
+ - Usage examples
13
+ - Parameter descriptions
14
+ - Return value descriptions
15
+ - Important notes or warnings
16
+ 4. Follow the existing documentation style in the project
17
+ 5. Include code examples where helpful
18
+
19
+ Make the documentation clear, concise, and helpful for users.
20
+ """
21
+
22
+ examples = [
23
+ "/docs FuzzingOrchestrator",
24
+ "skill:docs(MyClass)"
25
+ ]
@@ -0,0 +1,33 @@
1
+ name = "refactor"
2
+ description = "Refactor code for better quality"
3
+ requires_args = true
4
+
5
+ prompt = """
6
+ Refactor the code: {args}
7
+
8
+ 1. Read and understand the current implementation
9
+ 2. Identify improvement opportunities:
10
+ - Code duplication
11
+ - Complex functions (high cyclomatic complexity)
12
+ - Poor naming
13
+ - Missing abstractions
14
+ - Violation of SOLID principles
15
+ 3. Propose refactoring changes with:
16
+ - Clear explanation of the issue
17
+ - Proposed solution
18
+ - Benefits of the change
19
+ - Any trade-offs
20
+ 4. Make the changes incrementally
21
+ 5. Ensure tests still pass after each change
22
+
23
+ Focus on improving:
24
+ - Readability
25
+ - Maintainability
26
+ - Testability
27
+ - Performance (where applicable)
28
+ """
29
+
30
+ examples = [
31
+ "/refactor FuzzingOrchestrator.fuzz",
32
+ "skill:refactor(process_turn method)"
33
+ ]
@@ -0,0 +1,28 @@
1
+ name = "review"
2
+ description = "Review code changes for quality and potential issues"
3
+ requires_args = false
4
+
5
+ prompt = """
6
+ Review the current code changes:
7
+
8
+ 1. Run `git diff` to see all changes
9
+ 2. Analyze the changes for:
10
+ - Code quality and style
11
+ - Potential bugs or edge cases
12
+ - Security vulnerabilities
13
+ - Performance issues
14
+ - Missing error handling
15
+ - Test coverage gaps
16
+ 3. Provide constructive feedback with:
17
+ - Positive observations (what's good)
18
+ - Issues found (severity: critical, major, minor)
19
+ - Specific suggestions for improvement
20
+ - Code examples where helpful
21
+
22
+ Format your review clearly with sections for each category.
23
+ """
24
+
25
+ examples = [
26
+ "/review",
27
+ "skill:review"
28
+ ]
@@ -0,0 +1,28 @@
1
+ name = "test"
2
+ description = "Run tests and analyze results"
3
+ requires_args = false
4
+
5
+ prompt = """
6
+ Run tests and analyze results:
7
+
8
+ 1. Identify the test framework (pytest, unittest, jest, etc.)
9
+ 2. Run the test suite
10
+ 3. If tests fail:
11
+ - Show the failing tests
12
+ - Analyze the failure reasons
13
+ - Suggest fixes
14
+ 4. If tests pass:
15
+ - Report test coverage if available
16
+ - Suggest additional test cases for edge cases
17
+ 5. Check for:
18
+ - Missing tests for new functionality
19
+ - Flaky tests
20
+ - Test performance issues
21
+
22
+ Provide a summary with test counts, pass rate, and recommendations.
23
+ """
24
+
25
+ examples = [
26
+ "/test",
27
+ "skill:test"
28
+ ]
@@ -0,0 +1,111 @@
1
+ """Skill loading from TOML files."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional
7
+
8
+ try:
9
+ import tomllib
10
+ except ImportError:
11
+ import tomli as tomllib # type: ignore # Python < 3.11
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class Skill:
18
+ """A skill definition."""
19
+
20
+ name: str
21
+ prompt: str
22
+ description: str = ""
23
+ requires_args: bool = False
24
+ examples: list[str] = field(default_factory=list)
25
+
26
+
27
+ class SkillLoader:
28
+ """Loads skills from TOML files."""
29
+
30
+ def __init__(self, skills_dir: Optional[Path] = None):
31
+ """
32
+ Initialize skill loader.
33
+
34
+ Args:
35
+ skills_dir: Directory containing skill TOML files
36
+ """
37
+ self.skills_dir = skills_dir
38
+
39
+ def load_skill(self, skill_path: Path) -> Skill:
40
+ """
41
+ Load a skill from TOML file.
42
+
43
+ Args:
44
+ skill_path: Path to skill TOML file
45
+
46
+ Returns:
47
+ Loaded skill
48
+ """
49
+ with open(skill_path, "rb") as f:
50
+ data = tomllib.load(f)
51
+
52
+ return Skill(
53
+ name=data["name"],
54
+ prompt=data["prompt"],
55
+ description=data.get("description", ""),
56
+ requires_args=data.get("requires_args", False),
57
+ examples=data.get("examples", []),
58
+ )
59
+
60
+ def load_all(self) -> dict[str, Skill]:
61
+ """
62
+ Load all skills from directory.
63
+
64
+ Returns:
65
+ Dict mapping skill name to skill
66
+ """
67
+ skills: dict[str, Skill] = {}
68
+
69
+ if not self.skills_dir or not self.skills_dir.exists():
70
+ logger.warning(f"Skills directory not found: {self.skills_dir}")
71
+ return skills
72
+
73
+ for skill_file in self.skills_dir.glob("*.toml"):
74
+ try:
75
+ skill = self.load_skill(skill_file)
76
+ skills[skill.name] = skill
77
+ logger.info(f"Loaded skill: {skill.name}")
78
+ except Exception as e:
79
+ logger.error(f"Failed to load skill {skill_file}: {e}")
80
+
81
+ return skills
82
+
83
+ def load_from_paths(self, paths: list[Path]) -> dict[str, Skill]:
84
+ """
85
+ Load skills from multiple directories.
86
+
87
+ Args:
88
+ paths: List of directory paths
89
+
90
+ Returns:
91
+ Dict mapping skill name to skill
92
+ """
93
+ skills = {}
94
+
95
+ for path in paths:
96
+ if not path.exists():
97
+ logger.warning(f"Skill path not found: {path}")
98
+ continue
99
+
100
+ for skill_file in path.glob("*.toml"):
101
+ try:
102
+ skill = self.load_skill(skill_file)
103
+ if skill.name in skills:
104
+ logger.warning(f"Skill '{skill.name}' already loaded, skipping")
105
+ else:
106
+ skills[skill.name] = skill
107
+ logger.info(f"Loaded skill: {skill.name}")
108
+ except Exception as e:
109
+ logger.error(f"Failed to load skill {skill_file}: {e}")
110
+
111
+ return skills
@@ -0,0 +1,139 @@
1
+ """Skill registry for managing and expanding skills."""
2
+
3
+ import re
4
+ from typing import Optional
5
+
6
+ from .loader import Skill
7
+
8
+
9
+ class SkillRegistry:
10
+ """Registry for managing skills."""
11
+
12
+ def __init__(self):
13
+ """Initialize skill registry."""
14
+ self.skills: dict[str, Skill] = {}
15
+
16
+ def register(self, skill: Skill) -> None:
17
+ """
18
+ Register a skill.
19
+
20
+ Args:
21
+ skill: Skill to register
22
+ """
23
+ self.skills[skill.name] = skill
24
+
25
+ def register_all(self, skills: dict[str, Skill]) -> None:
26
+ """
27
+ Register multiple skills.
28
+
29
+ Args:
30
+ skills: Dict of skills to register
31
+ """
32
+ self.skills.update(skills)
33
+
34
+ def get(self, name: str) -> Optional[Skill]:
35
+ """
36
+ Get skill by name.
37
+
38
+ Args:
39
+ name: Skill name
40
+
41
+ Returns:
42
+ Skill or None if not found
43
+ """
44
+ return self.skills.get(name)
45
+
46
+ def list_skills(self) -> list[Skill]:
47
+ """
48
+ List all registered skills.
49
+
50
+ Returns:
51
+ List of all skills
52
+ """
53
+ return list(self.skills.values())
54
+
55
+ def expand(self, skill_name: str, args: str = "") -> str:
56
+ """
57
+ Expand skill to full prompt.
58
+
59
+ Args:
60
+ skill_name: Name of skill to expand
61
+ args: Optional arguments for skill
62
+
63
+ Returns:
64
+ Expanded prompt
65
+
66
+ Raises:
67
+ KeyError: If skill not found
68
+ """
69
+ skill = self.skills.get(skill_name)
70
+ if not skill:
71
+ raise KeyError(f"Skill not found: {skill_name}")
72
+
73
+ # Check if args required
74
+ if skill.requires_args and not args:
75
+ raise ValueError(f"Skill '{skill_name}' requires arguments")
76
+
77
+ # Expand prompt with args
78
+ prompt = skill.prompt
79
+
80
+ # Simple template expansion
81
+ # Supports {args} placeholder
82
+ prompt = prompt.replace("{args}", args)
83
+
84
+ return prompt
85
+
86
+ def parse_skill_command(self, user_input: str) -> tuple[Optional[str], Optional[str]]:
87
+ """
88
+ Parse skill command from user input.
89
+
90
+ Supports formats:
91
+ - /skill_name
92
+ - /skill_name arg1 arg2
93
+ - skill:skill_name
94
+ - skill:skill_name(arg1, arg2)
95
+
96
+ Args:
97
+ user_input: User input string
98
+
99
+ Returns:
100
+ Tuple of (skill_name, args) or (None, None) if not a skill command
101
+ """
102
+ # Check for /skill format
103
+ if user_input.startswith("/"):
104
+ parts = user_input[1:].split(None, 1)
105
+ skill_name = parts[0]
106
+ args = parts[1] if len(parts) > 1 else ""
107
+
108
+ if skill_name in self.skills:
109
+ return skill_name, args
110
+
111
+ # Check for skill:name format
112
+ skill_pattern = r"^skill:(\w+)(?:\((.*)\))?$"
113
+ match = re.match(skill_pattern, user_input.strip())
114
+ if match:
115
+ skill_name = match.group(1)
116
+ args = match.group(2) or ""
117
+
118
+ if skill_name in self.skills:
119
+ return skill_name, args
120
+
121
+ return None, None
122
+
123
+ def process_input(self, user_input: str) -> tuple[str, bool]:
124
+ """
125
+ Process user input and expand skills if found.
126
+
127
+ Args:
128
+ user_input: User input string
129
+
130
+ Returns:
131
+ Tuple of (processed_input, was_skill)
132
+ """
133
+ skill_name, args = self.parse_skill_command(user_input)
134
+
135
+ if skill_name:
136
+ expanded = self.expand(skill_name, args or "")
137
+ return expanded, True
138
+
139
+ return user_input, False
@@ -0,0 +1,19 @@
1
+ """Persistent storage for historical fuzzing data and knowledge base."""
2
+
3
+ from ctrlcode.storage.history_db import (
4
+ BugPattern,
5
+ CodeRecord,
6
+ FuzzingSession,
7
+ HistoryDB,
8
+ OracleRecord,
9
+ StoredTest,
10
+ )
11
+
12
+ __all__ = [
13
+ "HistoryDB",
14
+ "FuzzingSession",
15
+ "CodeRecord",
16
+ "OracleRecord",
17
+ "BugPattern",
18
+ "StoredTest",
19
+ ]