codespy-ai 1.2.1__tar.gz → 1.2.2__tar.gz
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.
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/PKG-INFO +1 -1
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/pyproject.toml +1 -1
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/__init__.py +1 -1
- codespy_ai-1.2.2/src/codespy/agents/review/__init__.py +6 -0
- codespy_ai-1.2.2/src/codespy/agents/review/audit/__init__.py +1 -0
- codespy_ai-1.2.1/src/codespy/agents/reviewer/modules/auditor.py → codespy_ai-1.2.2/src/codespy/agents/review/audit/agent.py +3 -4
- codespy_ai-1.2.2/src/codespy/agents/review/code_review/__init__.py +1 -0
- codespy_ai-1.2.1/src/codespy/agents/reviewer/modules/code_reviewer.py → codespy_ai-1.2.2/src/codespy/agents/review/code_review/agent.py +3 -2
- codespy_ai-1.2.2/src/codespy/agents/review/doc/__init__.py +1 -0
- codespy_ai-1.2.1/src/codespy/agents/reviewer/modules/doc_reviewer.py → codespy_ai-1.2.2/src/codespy/agents/review/doc/agent.py +4 -3
- {codespy_ai-1.2.1/src/codespy/agents/reviewer/modules → codespy_ai-1.2.2/src/codespy/agents/review}/helpers.py +28 -4
- codespy_ai-1.2.2/src/codespy/agents/review/models.py +138 -0
- codespy_ai-1.2.2/src/codespy/agents/review/scope/__init__.py +2 -0
- codespy_ai-1.2.1/src/codespy/agents/reviewer/modules/scope_resolver.py → codespy_ai-1.2.2/src/codespy/agents/review/scope/agent.py +4 -30
- codespy_ai-1.2.2/src/codespy/agents/review/scope/models.py +91 -0
- codespy_ai-1.2.2/src/codespy/agents/review/summary/__init__.py +1 -0
- codespy_ai-1.2.1/src/codespy/agents/reviewer/modules/summarizer.py → codespy_ai-1.2.2/src/codespy/agents/review/summary/agent.py +3 -2
- codespy_ai-1.2.2/src/codespy/agents/review/supply_chain/__init__.py +1 -0
- codespy_ai-1.2.1/src/codespy/agents/reviewer/modules/supply_chain_auditor.py → codespy_ai-1.2.2/src/codespy/agents/review/supply_chain/agent.py +4 -3
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/cli_local.py +6 -6
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/cli_mcp_server.py +1 -1
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/cli_remote.py +3 -3
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/patch_utils.py +1 -1
- codespy_ai-1.2.2/src/codespy/workflows/__init__.py +0 -0
- codespy_ai-1.2.2/src/codespy/workflows/review/__init__.py +7 -0
- {codespy_ai-1.2.1/src/codespy/agents/reviewer → codespy_ai-1.2.2/src/codespy/workflows/review}/models.py +32 -236
- codespy_ai-1.2.1/src/codespy/agents/reviewer/reviewer.py → codespy_ai-1.2.2/src/codespy/workflows/review/pipeline.py +11 -14
- codespy_ai-1.2.2/src/codespy/workflows/review/reporters/__init__.py +3 -0
- {codespy_ai-1.2.1/src/codespy/agents/reviewer → codespy_ai-1.2.2/src/codespy/workflows/review}/reporters/base.py +1 -1
- {codespy_ai-1.2.1/src/codespy/agents/reviewer → codespy_ai-1.2.2/src/codespy/workflows/review}/reporters/git.py +3 -2
- {codespy_ai-1.2.1/src/codespy/agents/reviewer → codespy_ai-1.2.2/src/codespy/workflows/review}/reporters/stdout.py +2 -2
- {codespy_ai-1.2.1/src/codespy/agents/reviewer → codespy_ai-1.2.2/src/codespy/workflows/review}/server.py +4 -4
- codespy_ai-1.2.1/src/codespy/agents/reviewer/__init__.py +0 -23
- codespy_ai-1.2.1/src/codespy/agents/reviewer/modules/__init__.py +0 -17
- codespy_ai-1.2.1/src/codespy/agents/reviewer/reporters/__init__.py +0 -11
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/LICENSE +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/README.md +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/context_safe.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/cost_tracker.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/dspy_config.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/budget.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/context_memory.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/episode.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/hippocampus.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/modules/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/modules/cartographer.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/modules/distiller.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/pg0_manager.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/postgres.py +0 -0
- {codespy_ai-1.2.1/src/codespy/agents/reviewer/modules → codespy_ai-1.2.2/src/codespy/agents/review/doc}/doc_extractor.py +0 -0
- {codespy_ai-1.2.1/src/codespy/agents/reviewer/modules → codespy_ai-1.2.2/src/codespy/agents/review/scope}/manifest_parser.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/cli.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_dspy.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_git.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_io.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_llm.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_memory.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_utils.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/cyber/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/cyber/osv/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/cyber/osv/client.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/cyber/osv/models.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/cyber/osv/server.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/base.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/client.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/github_client.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/gitlab_client.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/local_diff.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/models.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/server.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/mcp_utils.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/ripgrep/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/ripgrep/client.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/ripgrep/server.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/base_extractor.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/bash.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/cpp.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/csharp.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/go.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/java.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/javascript.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/kotlin.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/objc.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/php.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/python.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/ripgrep_fallback.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/ruby.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/rust.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/swift.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/terraform.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/models.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/parser.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/server.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/base.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/filesystem/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/filesystem/client.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/filesystem/server.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/models.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/s3/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/s3/client.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/s3/server.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/web/__init__.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/web/client.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/web/models.py +0 -0
- {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/web/server.py +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from codespy.agents.review.audit import Auditor
|
|
2
|
+
from codespy.agents.review.code_review import CodeReviewer
|
|
3
|
+
from codespy.agents.review.doc import DocReviewer
|
|
4
|
+
from codespy.agents.review.scope import ScopeResolver
|
|
5
|
+
from codespy.agents.review.summary import Summarizer
|
|
6
|
+
from codespy.agents.review.supply_chain import SupplyChainAuditor
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from codespy.agents.review.audit.agent import Auditor
|
|
@@ -8,14 +8,13 @@ import dspy
|
|
|
8
8
|
from codespy.agents import SignatureContext, get_cost_tracker
|
|
9
9
|
from codespy.agents.context_safe import ContextSafe
|
|
10
10
|
from codespy.agents.memory.hippocampus import ContextMemory, Hippocampus
|
|
11
|
-
from codespy.agents.
|
|
12
|
-
from codespy.agents.
|
|
13
|
-
from codespy.agents.reviewer.modules.scope_resolver import _deepest_common_folder
|
|
11
|
+
from codespy.agents.review.models import Issue, ReviewContext
|
|
12
|
+
from codespy.agents.review.helpers import deepest_common_folder
|
|
14
13
|
from codespy.config import get_settings
|
|
15
14
|
from codespy.config_memory import get_episode_store
|
|
16
15
|
|
|
17
16
|
if TYPE_CHECKING:
|
|
18
|
-
from codespy.agents.
|
|
17
|
+
from codespy.agents.review.scope.models import ScopeResult
|
|
19
18
|
|
|
20
19
|
logger = logging.getLogger(__name__)
|
|
21
20
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from codespy.agents.review.code_review.agent import CodeReviewer
|
|
@@ -13,8 +13,9 @@ from codespy.agents import SignatureContext, get_cost_tracker
|
|
|
13
13
|
from codespy.agents.context_safe import ContextSafe
|
|
14
14
|
from codespy.agents.memory.hippocampus import ContextMemory, Hippocampus
|
|
15
15
|
from codespy.agents.memory.hippocampus.episode import submit_episode_save
|
|
16
|
-
from codespy.agents.
|
|
17
|
-
from codespy.agents.
|
|
16
|
+
from codespy.agents.review.models import Issue, IssueCategory, ReviewContext
|
|
17
|
+
from codespy.agents.review.scope.models import ScopeResult
|
|
18
|
+
from codespy.agents.review.helpers import (
|
|
18
19
|
issues_to_markdown,
|
|
19
20
|
make_scope_relative,
|
|
20
21
|
resolve_scope_root,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from codespy.agents.review.doc.agent import DocReviewer
|
|
@@ -13,9 +13,10 @@ from codespy.agents import SignatureContext, get_cost_tracker
|
|
|
13
13
|
from codespy.agents.context_safe import ContextSafe
|
|
14
14
|
from codespy.agents.memory.hippocampus import ContextMemory, Hippocampus
|
|
15
15
|
from codespy.agents.memory.hippocampus.episode import submit_episode_save
|
|
16
|
-
from codespy.agents.
|
|
17
|
-
from codespy.agents.
|
|
18
|
-
from codespy.agents.
|
|
16
|
+
from codespy.agents.review.models import Issue, IssueCategory, ReviewContext
|
|
17
|
+
from codespy.agents.review.scope.models import ScopeResult
|
|
18
|
+
from codespy.agents.review.doc.doc_extractor import extract_documentation
|
|
19
|
+
from codespy.agents.review.helpers import (
|
|
19
20
|
build_patches,
|
|
20
21
|
issues_to_markdown,
|
|
21
22
|
make_scope_relative,
|
|
@@ -8,11 +8,11 @@ from collections.abc import Sequence
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
|
-
from codespy.agents.
|
|
11
|
+
from codespy.agents.review.models import Issue
|
|
12
12
|
from codespy.tools.git.models import ChangedFile
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
-
from codespy.agents.
|
|
15
|
+
from codespy.agents.review.scope.models import ScopeResult, PackageManifest
|
|
16
16
|
|
|
17
17
|
logger = logging.getLogger(__name__)
|
|
18
18
|
|
|
@@ -102,7 +102,29 @@ def resolve_scope_root(repo_path: Path, subroot: str) -> Path:
|
|
|
102
102
|
return repo_path if subroot == "." else repo_path / subroot
|
|
103
103
|
|
|
104
104
|
|
|
105
|
-
def
|
|
105
|
+
def deepest_common_folder(scopes: list["ScopeResult"], repo_slug: str) -> str:
|
|
106
|
+
"""Compute the deepest common ancestor directory across all scope subroots.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
scopes: List of scope results
|
|
110
|
+
repo_slug: Repository slug for fallback path
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Deepest common ancestor path (e.g., "/repo/scope/subroot/")
|
|
114
|
+
"""
|
|
115
|
+
subroots = [s.subroot for s in scopes]
|
|
116
|
+
if not subroots or any(sr in (".", "") for sr in subroots):
|
|
117
|
+
return f"/{repo_slug}/"
|
|
118
|
+
try:
|
|
119
|
+
common = os.path.commonpath(subroots)
|
|
120
|
+
except ValueError:
|
|
121
|
+
common = ""
|
|
122
|
+
if not common or common == ".":
|
|
123
|
+
return f"/{repo_slug}/"
|
|
124
|
+
return f"/{repo_slug}/{common.strip('/')}/"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def make_scope_relative(scope: "ScopeResult") -> "ScopeResult":
|
|
106
128
|
"""Create a copy of a ScopeResult with file paths relative to scope.subroot.
|
|
107
129
|
|
|
108
130
|
When MCP tools are rooted at repo_path/scope.subroot, the agent needs file
|
|
@@ -116,7 +138,7 @@ def make_scope_relative(scope: ScopeResult) -> ScopeResult:
|
|
|
116
138
|
New ScopeResult with scope-relative file paths in changed_files.
|
|
117
139
|
The subroot is set to "." since paths are now relative to it.
|
|
118
140
|
"""
|
|
119
|
-
from codespy.agents.
|
|
141
|
+
from codespy.agents.review.scope.models import ScopeResult, PackageManifest
|
|
120
142
|
|
|
121
143
|
if scope.subroot == ".":
|
|
122
144
|
return scope # Already at repo root, no transformation needed
|
|
@@ -145,6 +167,7 @@ def make_scope_relative(scope: ScopeResult) -> ScopeResult:
|
|
|
145
167
|
),
|
|
146
168
|
package_manager=scope.package_manifest.package_manager,
|
|
147
169
|
dependencies_changed=scope.package_manifest.dependencies_changed,
|
|
170
|
+
package_name=scope.package_manifest.package_name,
|
|
148
171
|
)
|
|
149
172
|
return ScopeResult(
|
|
150
173
|
repo=scope.repo,
|
|
@@ -156,6 +179,7 @@ def make_scope_relative(scope: ScopeResult) -> ScopeResult:
|
|
|
156
179
|
changed_files=relative_files,
|
|
157
180
|
reason=scope.reason,
|
|
158
181
|
skills=scope.skills,
|
|
182
|
+
description=scope.description,
|
|
159
183
|
)
|
|
160
184
|
|
|
161
185
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Cross-cutting review types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from codespy.agents.memory.hippocampus import ContextMemory
|
|
12
|
+
from codespy.tools.git.models import PullRequest
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from codespy.agents.memory.hippocampus.context_memory import Topic
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IssueSeverity(StrEnum):
|
|
19
|
+
"""Severity level of an issue."""
|
|
20
|
+
|
|
21
|
+
CRITICAL = "critical"
|
|
22
|
+
HIGH = "high"
|
|
23
|
+
MEDIUM = "medium"
|
|
24
|
+
LOW = "low"
|
|
25
|
+
INFO = "info"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class IssueCategory(StrEnum):
|
|
29
|
+
"""Category of an issue."""
|
|
30
|
+
|
|
31
|
+
SECURITY = "security"
|
|
32
|
+
BUG = "bug"
|
|
33
|
+
DOCUMENTATION = "documentation"
|
|
34
|
+
SMELL = "smell"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class PRContext(BaseModel):
|
|
38
|
+
"""Shared PR identity passed to all review modules after summarization.
|
|
39
|
+
|
|
40
|
+
Built by the pipeline orchestrator after the Summarizer runs, then
|
|
41
|
+
threaded through scope identification, review modules, and audit.
|
|
42
|
+
Each module constructs its own Hippocampus question from these fields.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
repo_slug: str = Field(
|
|
46
|
+
description="Host-qualified repo identifier (e.g. github.com/owner/repo)"
|
|
47
|
+
)
|
|
48
|
+
pr_number: int = Field(description="PR number")
|
|
49
|
+
pr_title: str = Field(description="PR title")
|
|
50
|
+
pr_url: str = Field(description="Full PR URL (e.g. https://github.com/owner/repo/pull/123)")
|
|
51
|
+
pr_description: str = Field(default="", description="PR body/description")
|
|
52
|
+
summary: str = Field(description="2-3 sentence PR summary produced by Summarizer")
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def repo_full_name(self) -> str:
|
|
56
|
+
"""Get owner/repo from host-qualified repo_slug."""
|
|
57
|
+
# repo_slug is "github.com/owner/repo" or just "owner/repo"
|
|
58
|
+
parts = self.repo_slug.split("/")
|
|
59
|
+
if len(parts) >= 3:
|
|
60
|
+
return "/".join(parts[1:]) # strip host
|
|
61
|
+
return self.repo_slug
|
|
62
|
+
|
|
63
|
+
def to_topic(self) -> "Topic":
|
|
64
|
+
"""Build a Topic representing this PR.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Topic object with id as PR URL, type as "pull_request", and description as "PR #N: Title"
|
|
68
|
+
"""
|
|
69
|
+
from codespy.agents.memory.hippocampus.context_memory import Topic
|
|
70
|
+
return Topic(
|
|
71
|
+
id=self.pr_url,
|
|
72
|
+
type="pull_request",
|
|
73
|
+
description=f"PR #{self.pr_number}: {self.pr_title}"[:500],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ReviewMetadata(BaseModel):
|
|
78
|
+
"""Runtime pipeline state, stable once constructed at pipeline start.
|
|
79
|
+
|
|
80
|
+
Groups repo_path, run_id, pr, and is_local to reduce parameter
|
|
81
|
+
proliferation across module method signatures.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
repo_path: Path
|
|
85
|
+
run_id: str | None = None
|
|
86
|
+
pr: PullRequest | None = None
|
|
87
|
+
is_local: bool = False
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ReviewContext(BaseModel):
|
|
91
|
+
"""Evolving pipeline state threaded through review stages.
|
|
92
|
+
|
|
93
|
+
Carries the immutable PR identity and runtime pipeline metadata.
|
|
94
|
+
Context memory is loaded independently by each module from its own prior episodes.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
pr_context: PRContext = Field(
|
|
98
|
+
description="Immutable PR identity (repo, number, title, summary)"
|
|
99
|
+
)
|
|
100
|
+
memory: ContextMemory | None = Field(
|
|
101
|
+
default=None, description="Unused — each module loads its own prior episodes. Kept for API compatibility."
|
|
102
|
+
)
|
|
103
|
+
metadata: ReviewMetadata | None = Field(default=None, description="Runtime pipeline state")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Issue(BaseModel):
|
|
107
|
+
"""Represents a single issue found during review."""
|
|
108
|
+
|
|
109
|
+
category: IssueCategory = Field(description="Issue category")
|
|
110
|
+
severity: IssueSeverity = Field(
|
|
111
|
+
default=IssueSeverity.MEDIUM,
|
|
112
|
+
description="Issue severity: critical, high, medium, low, or info",
|
|
113
|
+
)
|
|
114
|
+
title: str = Field(description="Brief title of the issue")
|
|
115
|
+
description: str = Field(description="≤25 word imperative description. No filler.")
|
|
116
|
+
filename: str = Field(description="File where the issue was found")
|
|
117
|
+
line_start: int | None = Field(default=None, description="Starting line number")
|
|
118
|
+
line_end: int | None = Field(default=None, description="Ending line number")
|
|
119
|
+
code_snippet: str | None = Field(
|
|
120
|
+
default=None, description="Deprecated—use line numbers. Leave None."
|
|
121
|
+
)
|
|
122
|
+
suggestion: str | None = Field(default=None, description="Suggested fix or improvement")
|
|
123
|
+
cwe_id: str | None = Field(
|
|
124
|
+
default=None, description="CWE ID for security issues (e.g., CWE-79)"
|
|
125
|
+
)
|
|
126
|
+
confidence: float = Field(default=0.8, ge=0.0, le=1.0, description="Confidence score (0-1)")
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def location(self) -> str:
|
|
130
|
+
"""Get a human-readable location string."""
|
|
131
|
+
if self.line_start:
|
|
132
|
+
if self.line_end and self.line_end != self.line_start:
|
|
133
|
+
return f"{self.filename}:{self.line_start}-{self.line_end}"
|
|
134
|
+
return f"{self.filename}:{self.line_start}"
|
|
135
|
+
return self.filename
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
@@ -21,13 +21,9 @@ from codespy.agents import SignatureContext, get_cost_tracker
|
|
|
21
21
|
from codespy.agents.context_safe import ContextSafe
|
|
22
22
|
from codespy.agents.memory.hippocampus import ContextMemory, Hippocampus
|
|
23
23
|
from codespy.agents.memory.hippocampus.episode import submit_episode_save
|
|
24
|
-
from codespy.agents.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
ScopeResult,
|
|
28
|
-
ScopeType,
|
|
29
|
-
)
|
|
30
|
-
from codespy.agents.reviewer.modules.manifest_parser import extract_package_name
|
|
24
|
+
from codespy.agents.review.models import ReviewContext
|
|
25
|
+
from codespy.agents.review.scope.manifest_parser import extract_package_name
|
|
26
|
+
from codespy.agents.review.scope.models import PackageManifest, ScopeResult, ScopeType
|
|
31
27
|
from codespy.config import get_settings
|
|
32
28
|
from codespy.config_memory import get_episode_store
|
|
33
29
|
from codespy.tools.git.client import get_client
|
|
@@ -87,28 +83,6 @@ def collect_skills(repo_path: Path, subroot: str) -> str | None:
|
|
|
87
83
|
return "\n\n".join(sections) if sections else None
|
|
88
84
|
|
|
89
85
|
|
|
90
|
-
def _deepest_common_folder(scopes: list[ScopeResult], repo_slug: str) -> str:
|
|
91
|
-
"""Compute the deepest common ancestor directory across all scope subroots.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
scopes: List of scope results
|
|
95
|
-
repo_slug: Repository slug for fallback path
|
|
96
|
-
|
|
97
|
-
Returns:
|
|
98
|
-
Deepest common ancestor path (e.g., "/repo/scope/subroot/")
|
|
99
|
-
"""
|
|
100
|
-
subroots = [s.subroot for s in scopes]
|
|
101
|
-
if not subroots or any(sr in (".", "") for sr in subroots):
|
|
102
|
-
return f"/{repo_slug}/"
|
|
103
|
-
try:
|
|
104
|
-
common = os.path.commonpath(subroots)
|
|
105
|
-
except ValueError:
|
|
106
|
-
common = ""
|
|
107
|
-
if not common or common == ".":
|
|
108
|
-
return f"/{repo_slug}/"
|
|
109
|
-
return f"/{repo_slug}/{common.strip('/')}/"
|
|
110
|
-
|
|
111
|
-
|
|
112
86
|
# Exact filename matches -> package manager
|
|
113
87
|
MANIFEST_FILES: dict[str, str] = {
|
|
114
88
|
# Go
|
|
@@ -556,7 +530,7 @@ class ScopeResolver(dspy.Module):
|
|
|
556
530
|
)
|
|
557
531
|
logger.info("Clone complete: %s", repo_path)
|
|
558
532
|
|
|
559
|
-
# Ensure manifest files
|
|
533
|
+
# Ensure manifest files at root and parent directories are checked out
|
|
560
534
|
await self._ensure_manifests(repo_path, changed_file_paths)
|
|
561
535
|
|
|
562
536
|
async def _ensure_manifests(self, repo_path: Path, changed_files: list[str]) -> None:
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Scope-agent-owned types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from codespy.tools.git.models import ChangedFile
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from codespy.agents.memory.hippocampus.context_memory import Topic
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ScopeType(StrEnum):
|
|
17
|
+
"""Type of code scope in a repository."""
|
|
18
|
+
|
|
19
|
+
LIBRARY = "library" # Shared code that others import
|
|
20
|
+
SERVICE = "service" # Isolated microservice with explicit APIs
|
|
21
|
+
APPLICATION = "application" # Standalone app or frontend
|
|
22
|
+
SCRIPT = "script" # Build/deployment scripts, tooling
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PackageManifest(BaseModel):
|
|
26
|
+
"""Package management file information for a scope."""
|
|
27
|
+
|
|
28
|
+
manifest_path: str = Field(description="Path to manifest file (e.g., package.json)")
|
|
29
|
+
lock_file_path: str | None = Field(
|
|
30
|
+
default=None, description="Path to lock file (e.g., package-lock.json)"
|
|
31
|
+
)
|
|
32
|
+
package_manager: str = Field(description="Package manager name (e.g., npm, go, pip)")
|
|
33
|
+
dependencies_changed: bool = Field(
|
|
34
|
+
default=False, description="Whether PR modified this manifest or lock file"
|
|
35
|
+
)
|
|
36
|
+
package_name: str | None = Field(default=None, description="Package identity from manifest")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ScopeResult(BaseModel):
|
|
40
|
+
"""A detected scope/subroot in the repository."""
|
|
41
|
+
|
|
42
|
+
repo: str = Field(
|
|
43
|
+
default="", description="Repo identifier: 'owner/repo' (remote) or local dir name"
|
|
44
|
+
)
|
|
45
|
+
subroot: str = Field(description="Path relative to repo root (e.g., packages/auth)")
|
|
46
|
+
scope_type: ScopeType = Field(description="Type of scope (library, service, etc.)")
|
|
47
|
+
has_changes: bool = Field(
|
|
48
|
+
default=False, description="Whether this scope has changed files from PR"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
language: str | None = Field(default=None, description="Primary language detected")
|
|
52
|
+
package_manifest: PackageManifest | None = Field(
|
|
53
|
+
default=None, description="Package manifest info if present"
|
|
54
|
+
)
|
|
55
|
+
changed_files: list[ChangedFile] = Field(
|
|
56
|
+
default_factory=list, description="Changed files belonging to this scope"
|
|
57
|
+
)
|
|
58
|
+
reason: str = Field(description="Explanation for why this scope was identified")
|
|
59
|
+
skills: str | None = Field(
|
|
60
|
+
default=None, description="Project/scope instructions inherited from ancestor directories"
|
|
61
|
+
)
|
|
62
|
+
description: str = Field(
|
|
63
|
+
default="", description="Description of scope's role in the project (max 500 chars)"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
67
|
+
|
|
68
|
+
def scope_path(self) -> str:
|
|
69
|
+
"""Return the storage-relative path for this scope.
|
|
70
|
+
|
|
71
|
+
Used by Hippocampus memory as the base directory for episode files.
|
|
72
|
+
``subroot == "."`` (repo root) results in ``/{repo}/``.
|
|
73
|
+
"""
|
|
74
|
+
if self.subroot in (".", ""):
|
|
75
|
+
return f"/{self.repo}/"
|
|
76
|
+
return f"/{self.repo}/{self.subroot.strip('/')}/"
|
|
77
|
+
|
|
78
|
+
def topic(self, repo_full_name: str) -> "Topic":
|
|
79
|
+
"""Build the Topic for this scope.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
repo_full_name: Repository full name (owner/repo)
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Topic object with id and description
|
|
86
|
+
"""
|
|
87
|
+
from codespy.agents.memory.hippocampus.context_memory import Topic, make_topic_id
|
|
88
|
+
|
|
89
|
+
package_name = self.package_manifest.package_name if self.package_manifest else None
|
|
90
|
+
topic_id = make_topic_id(repo_full_name, self.subroot, package_name)
|
|
91
|
+
return Topic(id=topic_id, type="project_scope", description=self.description)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from codespy.agents.review.summary.agent import Summarizer
|
|
@@ -10,12 +10,13 @@ from codespy.agents.context_safe import ContextSafe
|
|
|
10
10
|
from codespy.agents.memory.hippocampus import ContextMemory, Hippocampus
|
|
11
11
|
from codespy.agents.memory.hippocampus.episode import submit_episode_save
|
|
12
12
|
from codespy.agents.memory.hippocampus.context_memory import Topic
|
|
13
|
-
from codespy.agents.
|
|
13
|
+
from codespy.agents.review.helpers import deepest_common_folder
|
|
14
14
|
from codespy.config import get_settings
|
|
15
15
|
from codespy.config_memory import get_episode_store
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
|
-
from codespy.agents.
|
|
18
|
+
from codespy.agents.review.models import PRContext
|
|
19
|
+
from codespy.agents.review.scope.models import ScopeResult
|
|
19
20
|
|
|
20
21
|
logger = logging.getLogger(__name__)
|
|
21
22
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from codespy.agents.review.supply_chain.agent import SupplyChainAuditor
|
|
@@ -13,8 +13,9 @@ from codespy.agents import SignatureContext, get_cost_tracker
|
|
|
13
13
|
from codespy.agents.context_safe import ContextSafe
|
|
14
14
|
from codespy.agents.memory.hippocampus import ContextMemory, Hippocampus
|
|
15
15
|
from codespy.agents.memory.hippocampus.episode import submit_episode_save
|
|
16
|
-
from codespy.agents.
|
|
17
|
-
from codespy.agents.
|
|
16
|
+
from codespy.agents.review.models import Issue, IssueCategory, ReviewContext
|
|
17
|
+
from codespy.agents.review.scope.models import ScopeResult
|
|
18
|
+
from codespy.agents.review.helpers import (
|
|
18
19
|
issues_to_markdown,
|
|
19
20
|
resolve_scope_root,
|
|
20
21
|
restore_repo_paths,
|
|
@@ -67,7 +68,7 @@ class SupplyChainSecuritySignature(dspy.Signature):
|
|
|
67
68
|
- description: ≤25 words, imperative tone, no filler ("Fix X", "Pin Y").
|
|
68
69
|
- No polite or conversational language ("I suggest", "Please consider", "Great").
|
|
69
70
|
- Do not populate code_snippet—use line numbers instead.
|
|
70
|
-
- File paths in issues must be relative to
|
|
71
|
+
- File paths in issues must be relative to scope root.
|
|
71
72
|
|
|
72
73
|
## 2. DEPENDENCY SECURITY (package manifests)
|
|
73
74
|
|
|
@@ -107,8 +107,8 @@ def review_local(
|
|
|
107
107
|
)
|
|
108
108
|
|
|
109
109
|
try:
|
|
110
|
-
from codespy.
|
|
111
|
-
from codespy.
|
|
110
|
+
from codespy.workflows.review.models import LocalReviewConfig
|
|
111
|
+
from codespy.workflows.review.pipeline import ReviewPipeline
|
|
112
112
|
|
|
113
113
|
pipeline = ReviewPipeline(settings)
|
|
114
114
|
|
|
@@ -129,7 +129,7 @@ def review_local(
|
|
|
129
129
|
)
|
|
130
130
|
)
|
|
131
131
|
|
|
132
|
-
from codespy.
|
|
132
|
+
from codespy.workflows.review.reporters import StdoutReporter
|
|
133
133
|
|
|
134
134
|
stdout_reporter = StdoutReporter(format=settings.output_format, console=console)
|
|
135
135
|
stdout_reporter.report(result)
|
|
@@ -220,8 +220,8 @@ def review_uncommitted(
|
|
|
220
220
|
)
|
|
221
221
|
|
|
222
222
|
try:
|
|
223
|
-
from codespy.
|
|
224
|
-
from codespy.
|
|
223
|
+
from codespy.workflows.review.models import LocalReviewConfig
|
|
224
|
+
from codespy.workflows.review.pipeline import ReviewPipeline
|
|
225
225
|
|
|
226
226
|
pipeline = ReviewPipeline(settings)
|
|
227
227
|
|
|
@@ -242,7 +242,7 @@ def review_uncommitted(
|
|
|
242
242
|
)
|
|
243
243
|
)
|
|
244
244
|
|
|
245
|
-
from codespy.
|
|
245
|
+
from codespy.workflows.review.reporters import StdoutReporter
|
|
246
246
|
|
|
247
247
|
stdout_reporter = StdoutReporter(format=settings.output_format, console=console)
|
|
248
248
|
stdout_reporter.report(result)
|
|
@@ -159,8 +159,8 @@ def review(
|
|
|
159
159
|
)
|
|
160
160
|
|
|
161
161
|
try:
|
|
162
|
-
from codespy.
|
|
163
|
-
from codespy.
|
|
162
|
+
from codespy.workflows.review.models import RemoteReviewConfig
|
|
163
|
+
from codespy.workflows.review.pipeline import ReviewPipeline
|
|
164
164
|
|
|
165
165
|
pipeline = ReviewPipeline(settings)
|
|
166
166
|
|
|
@@ -183,7 +183,7 @@ def review(
|
|
|
183
183
|
)
|
|
184
184
|
|
|
185
185
|
# Output results using reporters
|
|
186
|
-
from codespy.
|
|
186
|
+
from codespy.workflows.review.reporters import GitReporter, StdoutReporter
|
|
187
187
|
|
|
188
188
|
if settings.output_stdout:
|
|
189
189
|
stdout_reporter = StdoutReporter(format=settings.output_format, console=console)
|
|
@@ -8,7 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
from typing import TYPE_CHECKING, Any
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
|
-
from codespy.agents.
|
|
11
|
+
from codespy.agents.review.scope.models import ScopeResult
|
|
12
12
|
|
|
13
13
|
from codespy.tools.git.models import ChangedFile, FileStatus
|
|
14
14
|
from codespy.tools.parsers.treesitter import FunctionInfo, TreeSitterParser
|
|
File without changes
|