naive-knowledge-base 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.
- agents/__init__.py +7 -0
- agents/common/__init__.py +28 -0
- agents/common/base.py +187 -0
- agents/common/exceptions.py +62 -0
- agents/common/logging.py +60 -0
- agents/common/schema_converter.py +112 -0
- agents/common/tools.py +195 -0
- agents/common/utils.py +161 -0
- agents/dependency_graph/__init__.py +7 -0
- agents/dependency_graph/agent.py +205 -0
- agents/dependency_graph/model.py +38 -0
- api_models/__init__.py +3 -0
- api_models/flow_api_model.py +390 -0
- cli.py +203 -0
- main.py +68 -0
- naive_knowledge_base-0.1.0.dist-info/METADATA +215 -0
- naive_knowledge_base-0.1.0.dist-info/RECORD +24 -0
- naive_knowledge_base-0.1.0.dist-info/WHEEL +5 -0
- naive_knowledge_base-0.1.0.dist-info/entry_points.txt +2 -0
- naive_knowledge_base-0.1.0.dist-info/licenses/LICENSE +21 -0
- naive_knowledge_base-0.1.0.dist-info/top_level.txt +5 -0
- tools/__init__.py +11 -0
- tools/io.py +59 -0
- tools/tree.py +42 -0
agents/common/utils.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Optional, Any, Dict
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from smolagents import tool
|
|
8
|
+
|
|
9
|
+
from .exceptions import FileOperationError, wrap_exceptions
|
|
10
|
+
from .logging import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
@wrap_exceptions
|
|
15
|
+
def save_content_to_file(content: str, source_directory: str, file_name: str) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Saves content to a file, creating parent directories if needed.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
content: The content to save
|
|
21
|
+
source_directory: The directory where the file will be saved
|
|
22
|
+
file_name: The name of the file
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The absolute path to the saved file
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
FileOperationError: If there's an issue with the file operation
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
path = Path(file_path)
|
|
32
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
35
|
+
f.write(content)
|
|
36
|
+
|
|
37
|
+
logger.debug(f"Content saved to {file_path}")
|
|
38
|
+
return str(path.absolute())
|
|
39
|
+
except Exception as e:
|
|
40
|
+
raise FileOperationError(f"Failed to save content to {file_path}: {str(e)}")
|
|
41
|
+
|
|
42
|
+
@wrap_exceptions
|
|
43
|
+
def read_file_content(file_path: str) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Reads content from a file.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
file_path: The path to the file
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The file content as a string
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
FileOperationError: If there's an issue with the file operation
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
58
|
+
return f.read()
|
|
59
|
+
except Exception as e:
|
|
60
|
+
raise FileOperationError(f"Failed to read content from {file_path}: {str(e)}")
|
|
61
|
+
|
|
62
|
+
@wrap_exceptions
|
|
63
|
+
def delete_folder_or_file(path: str) -> bool:
|
|
64
|
+
"""
|
|
65
|
+
Deletes a folder or file.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
path: The path to the folder or file
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
True if the deletion was successful, False otherwise
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
FileOperationError: If there's an issue with the file operation
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
path_obj = Path(path)
|
|
78
|
+
|
|
79
|
+
if path_obj.is_file():
|
|
80
|
+
path_obj.unlink()
|
|
81
|
+
elif path_obj.is_dir():
|
|
82
|
+
# Import here to avoid circular imports
|
|
83
|
+
import shutil
|
|
84
|
+
shutil.rmtree(path)
|
|
85
|
+
else:
|
|
86
|
+
logger.warning(f"Path {path} does not exist")
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
logger.debug(f"Deleted {path}")
|
|
90
|
+
return True
|
|
91
|
+
except Exception as e:
|
|
92
|
+
raise FileOperationError(f"Failed to delete {path}: {str(e)}")
|
|
93
|
+
|
|
94
|
+
@wrap_exceptions
|
|
95
|
+
def extract_code_blocks(markdown_content: str, language: Optional[str] = None) -> List[str]:
|
|
96
|
+
"""
|
|
97
|
+
Extracts code blocks from markdown content.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
markdown_content: The markdown content
|
|
101
|
+
language: Optional language filter
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
List of extracted code blocks
|
|
105
|
+
"""
|
|
106
|
+
if language:
|
|
107
|
+
pattern = r'```(?:' + language + r')?\s*([\s\S]*?)\s*```(?:\n|$)'
|
|
108
|
+
else:
|
|
109
|
+
pattern = r'```(?:\w*)?\s*([\s\S]*?)\s*```(?:\n|$)'
|
|
110
|
+
|
|
111
|
+
return re.findall(pattern, markdown_content)
|
|
112
|
+
|
|
113
|
+
@wrap_exceptions
|
|
114
|
+
def load_json_file(file_path: str) -> Any:
|
|
115
|
+
"""
|
|
116
|
+
Loads a JSON file.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
file_path: The path to the JSON file
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
The parsed JSON content
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
FileOperationError: If there's an issue with the file operation
|
|
126
|
+
"""
|
|
127
|
+
try:
|
|
128
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
129
|
+
return json.load(f)
|
|
130
|
+
except json.JSONDecodeError as e:
|
|
131
|
+
raise FileOperationError(f"Invalid JSON in {file_path}: {str(e)}")
|
|
132
|
+
except Exception as e:
|
|
133
|
+
raise FileOperationError(f"Failed to read JSON from {file_path}: {str(e)}")
|
|
134
|
+
|
|
135
|
+
@wrap_exceptions
|
|
136
|
+
def save_json_file(content: Dict[str, Any], file_path: str, indent: int = 2) -> str:
|
|
137
|
+
"""
|
|
138
|
+
Saves content as a JSON file.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
content: The content to save
|
|
142
|
+
file_path: The path to the file
|
|
143
|
+
indent: JSON indentation level
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
The absolute path to the saved file
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
FileOperationError: If there's an issue with the file operation
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
path = Path(file_path)
|
|
153
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
154
|
+
|
|
155
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
156
|
+
json.dump(content, f, indent=indent)
|
|
157
|
+
|
|
158
|
+
logger.debug(f"JSON content saved to {file_path}")
|
|
159
|
+
return str(path.absolute())
|
|
160
|
+
except Exception as e:
|
|
161
|
+
raise FileOperationError(f"Failed to save JSON content to {file_path}: {str(e)}")
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
from smolagents import tool
|
|
2
|
+
from emerge.appear import Emerge
|
|
3
|
+
from .model import DependencyGraph
|
|
4
|
+
import os
|
|
5
|
+
import json
|
|
6
|
+
from toon_format import encode
|
|
7
|
+
from tools import save_content_to_file, delete_folder_or_file
|
|
8
|
+
|
|
9
|
+
@tool
|
|
10
|
+
def generate_dependency_graph(source_directory: str, permit_file_extensions: list[str], ignore_directories_containing: list[str]) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Generates a dependency graph for the given source directory.
|
|
13
|
+
Args:
|
|
14
|
+
source_directory: The source directory to analyze.
|
|
15
|
+
permit_file_extensions: The file extensions to permit in the analysis.
|
|
16
|
+
ignore_directories_containing: The directories to ignore in the analysis.
|
|
17
|
+
Returns:
|
|
18
|
+
str: The dependency graph file path.
|
|
19
|
+
Raises:
|
|
20
|
+
ValueError: If the source directory does not exist or is not a directory.
|
|
21
|
+
"""
|
|
22
|
+
# Check if the source directory exists
|
|
23
|
+
if not os.path.exists(source_directory):
|
|
24
|
+
raise ValueError(f"Source directory does not exist: {source_directory}")
|
|
25
|
+
|
|
26
|
+
# Check if the source directory is a directory
|
|
27
|
+
if not os.path.isdir(source_directory):
|
|
28
|
+
raise ValueError(f"Source path is not a directory: {source_directory}")
|
|
29
|
+
|
|
30
|
+
export_directory = os.path.join(source_directory, "export")
|
|
31
|
+
|
|
32
|
+
# Create the Emerge configuration YAML file
|
|
33
|
+
yaml_content = _create_emerge_config_yaml(source_directory, export_directory, permit_file_extensions, ignore_directories_containing)
|
|
34
|
+
|
|
35
|
+
# Write the YAML content to a file
|
|
36
|
+
yaml_file_path = _write_emerge_config_yaml(yaml_content)
|
|
37
|
+
|
|
38
|
+
# Run Emerge with the generated YAML file
|
|
39
|
+
try:
|
|
40
|
+
_run_emerge(yaml_file_path)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
raise ValueError(f"Failed to generate dependency graph: {e}")
|
|
43
|
+
|
|
44
|
+
dependency_graph = _transform_graph(source_directory)
|
|
45
|
+
|
|
46
|
+
# Delete the Emerge configuration YAML file
|
|
47
|
+
try:
|
|
48
|
+
delete_folder_or_file(yaml_file_path)
|
|
49
|
+
delete_folder_or_file(export_directory)
|
|
50
|
+
print(f"Deleting Emerge configuration file: {yaml_file_path}")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
raise ValueError(f"Failed to delete Emerge configuration file: {e}")
|
|
53
|
+
|
|
54
|
+
# Transform the dependency graph into a more readable format
|
|
55
|
+
content = encode(dependency_graph)
|
|
56
|
+
|
|
57
|
+
file_path = save_content_to_file(
|
|
58
|
+
content=content,
|
|
59
|
+
source_directory=source_directory,
|
|
60
|
+
file_name="dependency_graph.toon"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return f"Dependency graph saved to {file_path}"
|
|
64
|
+
|
|
65
|
+
def _create_emerge_config_yaml(source_directory: str, export_dir: str, permit_file_extensions: list[str], ignore_directories_containing: list[str]) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Generates a sample YAML configuration for Emerge to be used by write_emerge_config_yaml.
|
|
68
|
+
Args:
|
|
69
|
+
source_directory: The source directory to analyze.
|
|
70
|
+
permit_file_extensions: The file extensions to permit in the analysis.
|
|
71
|
+
ignore_directories_containing: The directories to ignore in the analysis.
|
|
72
|
+
Returns:
|
|
73
|
+
str: The YAML configuration as a string.
|
|
74
|
+
"""
|
|
75
|
+
export_directory = export_dir
|
|
76
|
+
if not os.path.exists(export_directory):
|
|
77
|
+
os.makedirs(export_directory)
|
|
78
|
+
return f"""
|
|
79
|
+
---
|
|
80
|
+
project_name: java_project_example
|
|
81
|
+
loglevel: info
|
|
82
|
+
analyses:
|
|
83
|
+
- analysis_name: full typescript check
|
|
84
|
+
source_directory: {source_directory}
|
|
85
|
+
only_permit_file_extensions: {permit_file_extensions}
|
|
86
|
+
ignore_dependencies_containing:
|
|
87
|
+
- java
|
|
88
|
+
- javax
|
|
89
|
+
- org
|
|
90
|
+
- och
|
|
91
|
+
- lambda
|
|
92
|
+
- javafx
|
|
93
|
+
- jakarta
|
|
94
|
+
- io.cloudevents
|
|
95
|
+
- fasterxml
|
|
96
|
+
- lombok
|
|
97
|
+
- dtcloyalty.lib.idempotentconsumer
|
|
98
|
+
- dtcloyalty.lib.outboxpattern
|
|
99
|
+
- feign
|
|
100
|
+
- io
|
|
101
|
+
- lib.programawaredatasource
|
|
102
|
+
- lib.programproperties
|
|
103
|
+
- com.algolia
|
|
104
|
+
ignore_directories_containing: {ignore_directories_containing}
|
|
105
|
+
file_scan:
|
|
106
|
+
- number_of_methods
|
|
107
|
+
- source_lines_of_code
|
|
108
|
+
- dependency_graph
|
|
109
|
+
- fan_in_out
|
|
110
|
+
- louvain_modularity
|
|
111
|
+
- tfidf
|
|
112
|
+
export:
|
|
113
|
+
- directory: {export_directory}
|
|
114
|
+
- graphml
|
|
115
|
+
- json
|
|
116
|
+
- tabular_file
|
|
117
|
+
- tabular_console_overall
|
|
118
|
+
- d3
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def _write_emerge_config_yaml(yaml_content: str) -> str:
|
|
122
|
+
"""
|
|
123
|
+
Creates a YAML configuration file for Emerge to be used by run_emerge.
|
|
124
|
+
Args:
|
|
125
|
+
yaml_content: The content of the YAML configuration.
|
|
126
|
+
Returns:
|
|
127
|
+
str: The path to the generated YAML configuration file.
|
|
128
|
+
"""
|
|
129
|
+
yaml_file_path = "/tmp/emerge_config.yaml"
|
|
130
|
+
with open(yaml_file_path, "w") as yaml_file:
|
|
131
|
+
yaml_file.write(yaml_content)
|
|
132
|
+
|
|
133
|
+
return yaml_file_path
|
|
134
|
+
|
|
135
|
+
def _run_emerge(yaml_file_path: str) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Runs Emerge on the given source path.
|
|
138
|
+
Args:
|
|
139
|
+
yaml_file_path: The yaml path to the YAML configuration file.
|
|
140
|
+
Returns:
|
|
141
|
+
str: The result of the Emerge analysis.
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
emerge = Emerge()
|
|
145
|
+
emerge.load_config(yaml_file_path)
|
|
146
|
+
emerge.start_analyzing()
|
|
147
|
+
except Exception as e:
|
|
148
|
+
raise ValueError(f"Failed to run Emerge: {e}")
|
|
149
|
+
|
|
150
|
+
def _transform_graph(source_directory: str) -> list[DependencyGraph]:
|
|
151
|
+
"""
|
|
152
|
+
Transforms the dependency graph into a more readable format.
|
|
153
|
+
Args:
|
|
154
|
+
source_directory: The source directory to transform the emerge-file_result_dependency_graph-data.json file.
|
|
155
|
+
Returns:
|
|
156
|
+
object: The transformed dependency graph.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
emerge_file_path = os.path.join(source_directory, "export", "emerge-file_result_dependency_graph-data.json")
|
|
160
|
+
if not os.path.exists(emerge_file_path):
|
|
161
|
+
raise ValueError(f"Emerge file not found: {emerge_file_path}")
|
|
162
|
+
with open(emerge_file_path, "r") as emerge_file:
|
|
163
|
+
emerge_data = emerge_file.read()
|
|
164
|
+
|
|
165
|
+
return _process_data(json.loads(emerge_data), source_directory)
|
|
166
|
+
|
|
167
|
+
def _process_data(json_data, source_directory) -> list[DependencyGraph]:
|
|
168
|
+
# Step 1: Filter nodes with absolute_name
|
|
169
|
+
relevant_nodes = [node for node in json_data['nodes'] if 'absolute_name' in node]
|
|
170
|
+
|
|
171
|
+
# Step 2: Map dependencies based on links
|
|
172
|
+
dependency_map = {}
|
|
173
|
+
|
|
174
|
+
for link in json_data['links']:
|
|
175
|
+
if link['source'] not in dependency_map:
|
|
176
|
+
dependency_map[link['source']] = []
|
|
177
|
+
target = os.path.join(source_directory, link['target'])
|
|
178
|
+
dependency_map[link['source']].append(target)
|
|
179
|
+
|
|
180
|
+
# Step 3: Create the final array
|
|
181
|
+
result = []
|
|
182
|
+
for node in relevant_nodes:
|
|
183
|
+
absolute_name = node['absolute_name']
|
|
184
|
+
file_path = os.path.join(source_directory, absolute_name.split('/', 1)[1])
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# Get metrics (with proper error handling for missing values)
|
|
188
|
+
fan_in = node.get('metric_fan-in-dependency-graph', 0)
|
|
189
|
+
fan_out = node.get('metric_fan-out-dependency-graph', 0)
|
|
190
|
+
modularity_level = node.get('metric_file_result_dependency_graph_louvain-modularity-in-file', 0)
|
|
191
|
+
number_of_methods = node.get('metric_number-of-methods-in-file', 0)
|
|
192
|
+
source_lines_of_code = node.get('metric_sloc-in-file', 0)
|
|
193
|
+
|
|
194
|
+
result.append({
|
|
195
|
+
"absolutePath": str(file_path),
|
|
196
|
+
"fanIn": fan_in,
|
|
197
|
+
"fanOut": fan_out,
|
|
198
|
+
"sourceLinesOfCode": source_lines_of_code,
|
|
199
|
+
"numberOfMethods": number_of_methods,
|
|
200
|
+
"louvainModularity": modularity_level,
|
|
201
|
+
"displayName": node.get('display_name', ''),
|
|
202
|
+
"dependencies":dependency_map.get(absolute_name, [])
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
return result
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import pydantic
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from pydantic.networks import AnyUrl
|
|
5
|
+
|
|
6
|
+
class DependencyGraph(BaseModel):
|
|
7
|
+
"""
|
|
8
|
+
A class representing a dependency graph for a software project.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
absolutePath: str = Field(
|
|
12
|
+
...,
|
|
13
|
+
description="The absolute path to the project directory.",
|
|
14
|
+
)
|
|
15
|
+
fanIn: int = Field(
|
|
16
|
+
...,
|
|
17
|
+
description="The number of incoming dependencies for the project.",
|
|
18
|
+
)
|
|
19
|
+
fanOut: int = Field(
|
|
20
|
+
...,
|
|
21
|
+
description="The number of outgoing dependencies for the project.",
|
|
22
|
+
)
|
|
23
|
+
numberOfMethods: int = Field(
|
|
24
|
+
...,
|
|
25
|
+
description="The number of methods in the project.",
|
|
26
|
+
)
|
|
27
|
+
sourceLinesOfCode: int = Field(
|
|
28
|
+
...,
|
|
29
|
+
description="The number of source lines of code in the project.",
|
|
30
|
+
)
|
|
31
|
+
louvainModularity: float = Field(
|
|
32
|
+
...,
|
|
33
|
+
description="The Louvain modularity score of the project.",
|
|
34
|
+
)
|
|
35
|
+
dependencies: List[str] = Field(
|
|
36
|
+
...,
|
|
37
|
+
description="A list of dependencies for the project.",
|
|
38
|
+
)
|
api_models/__init__.py
ADDED