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.
Files changed (115) hide show
  1. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/PKG-INFO +1 -1
  2. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/pyproject.toml +1 -1
  3. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/__init__.py +1 -1
  4. codespy_ai-1.2.2/src/codespy/agents/review/__init__.py +6 -0
  5. codespy_ai-1.2.2/src/codespy/agents/review/audit/__init__.py +1 -0
  6. 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
  7. codespy_ai-1.2.2/src/codespy/agents/review/code_review/__init__.py +1 -0
  8. 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
  9. codespy_ai-1.2.2/src/codespy/agents/review/doc/__init__.py +1 -0
  10. 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
  11. {codespy_ai-1.2.1/src/codespy/agents/reviewer/modules → codespy_ai-1.2.2/src/codespy/agents/review}/helpers.py +28 -4
  12. codespy_ai-1.2.2/src/codespy/agents/review/models.py +138 -0
  13. codespy_ai-1.2.2/src/codespy/agents/review/scope/__init__.py +2 -0
  14. 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
  15. codespy_ai-1.2.2/src/codespy/agents/review/scope/models.py +91 -0
  16. codespy_ai-1.2.2/src/codespy/agents/review/summary/__init__.py +1 -0
  17. 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
  18. codespy_ai-1.2.2/src/codespy/agents/review/supply_chain/__init__.py +1 -0
  19. 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
  20. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/cli_local.py +6 -6
  21. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/cli_mcp_server.py +1 -1
  22. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/cli_remote.py +3 -3
  23. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/patch_utils.py +1 -1
  24. codespy_ai-1.2.2/src/codespy/workflows/__init__.py +0 -0
  25. codespy_ai-1.2.2/src/codespy/workflows/review/__init__.py +7 -0
  26. {codespy_ai-1.2.1/src/codespy/agents/reviewer → codespy_ai-1.2.2/src/codespy/workflows/review}/models.py +32 -236
  27. codespy_ai-1.2.1/src/codespy/agents/reviewer/reviewer.py → codespy_ai-1.2.2/src/codespy/workflows/review/pipeline.py +11 -14
  28. codespy_ai-1.2.2/src/codespy/workflows/review/reporters/__init__.py +3 -0
  29. {codespy_ai-1.2.1/src/codespy/agents/reviewer → codespy_ai-1.2.2/src/codespy/workflows/review}/reporters/base.py +1 -1
  30. {codespy_ai-1.2.1/src/codespy/agents/reviewer → codespy_ai-1.2.2/src/codespy/workflows/review}/reporters/git.py +3 -2
  31. {codespy_ai-1.2.1/src/codespy/agents/reviewer → codespy_ai-1.2.2/src/codespy/workflows/review}/reporters/stdout.py +2 -2
  32. {codespy_ai-1.2.1/src/codespy/agents/reviewer → codespy_ai-1.2.2/src/codespy/workflows/review}/server.py +4 -4
  33. codespy_ai-1.2.1/src/codespy/agents/reviewer/__init__.py +0 -23
  34. codespy_ai-1.2.1/src/codespy/agents/reviewer/modules/__init__.py +0 -17
  35. codespy_ai-1.2.1/src/codespy/agents/reviewer/reporters/__init__.py +0 -11
  36. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/LICENSE +0 -0
  37. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/README.md +0 -0
  38. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/__init__.py +0 -0
  39. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/context_safe.py +0 -0
  40. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/cost_tracker.py +0 -0
  41. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/dspy_config.py +0 -0
  42. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/__init__.py +0 -0
  43. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/__init__.py +0 -0
  44. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/budget.py +0 -0
  45. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/context_memory.py +0 -0
  46. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/episode.py +0 -0
  47. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/hippocampus.py +0 -0
  48. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/modules/__init__.py +0 -0
  49. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/modules/cartographer.py +0 -0
  50. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/hippocampus/modules/distiller.py +0 -0
  51. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/pg0_manager.py +0 -0
  52. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/agents/memory/postgres.py +0 -0
  53. {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
  54. {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
  55. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/cli.py +0 -0
  56. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config.py +0 -0
  57. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_dspy.py +0 -0
  58. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_git.py +0 -0
  59. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_io.py +0 -0
  60. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_llm.py +0 -0
  61. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_memory.py +0 -0
  62. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/config_utils.py +0 -0
  63. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/__init__.py +0 -0
  64. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/cyber/__init__.py +0 -0
  65. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/cyber/osv/__init__.py +0 -0
  66. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/cyber/osv/client.py +0 -0
  67. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/cyber/osv/models.py +0 -0
  68. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/cyber/osv/server.py +0 -0
  69. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/__init__.py +0 -0
  70. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/base.py +0 -0
  71. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/client.py +0 -0
  72. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/github_client.py +0 -0
  73. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/gitlab_client.py +0 -0
  74. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/local_diff.py +0 -0
  75. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/models.py +0 -0
  76. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/git/server.py +0 -0
  77. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/mcp_utils.py +0 -0
  78. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/__init__.py +0 -0
  79. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/ripgrep/__init__.py +0 -0
  80. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/ripgrep/client.py +0 -0
  81. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/ripgrep/server.py +0 -0
  82. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/__init__.py +0 -0
  83. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/base_extractor.py +0 -0
  84. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/__init__.py +0 -0
  85. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/bash.py +0 -0
  86. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/cpp.py +0 -0
  87. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/csharp.py +0 -0
  88. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/go.py +0 -0
  89. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/java.py +0 -0
  90. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/javascript.py +0 -0
  91. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/kotlin.py +0 -0
  92. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/objc.py +0 -0
  93. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/php.py +0 -0
  94. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/python.py +0 -0
  95. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/ripgrep_fallback.py +0 -0
  96. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/ruby.py +0 -0
  97. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/rust.py +0 -0
  98. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/swift.py +0 -0
  99. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/extractors/terraform.py +0 -0
  100. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/models.py +0 -0
  101. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/parser.py +0 -0
  102. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/parsers/treesitter/server.py +0 -0
  103. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/__init__.py +0 -0
  104. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/base.py +0 -0
  105. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/filesystem/__init__.py +0 -0
  106. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/filesystem/client.py +0 -0
  107. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/filesystem/server.py +0 -0
  108. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/models.py +0 -0
  109. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/s3/__init__.py +0 -0
  110. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/s3/client.py +0 -0
  111. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/storage/s3/server.py +0 -0
  112. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/web/__init__.py +0 -0
  113. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/web/client.py +0 -0
  114. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/web/models.py +0 -0
  115. {codespy_ai-1.2.1 → codespy_ai-1.2.2}/src/codespy/tools/web/server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codespy-ai
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: Code review agent powered by DSPy
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "codespy-ai"
3
- version = "1.2.1"
3
+ version = "1.2.2"
4
4
  description = "Code review agent powered by DSPy"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """codespy - Code review agent powered by DSPy."""
2
2
 
3
- __version__ = "1.2.1"
3
+ __version__ = "1.2.2"
@@ -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.memory.hippocampus.context_memory import Topic
12
- from codespy.agents.reviewer.models import Issue, ReviewContext
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.reviewer.models import ScopeResult
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.reviewer.models import Issue, IssueCategory, ReviewContext, ScopeResult
17
- from codespy.agents.reviewer.modules.helpers import (
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.reviewer.models import Issue, IssueCategory, ReviewContext, ScopeResult
17
- from codespy.agents.reviewer.modules.doc_extractor import extract_documentation
18
- from codespy.agents.reviewer.modules.helpers import (
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.reviewer.models import Issue
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.reviewer.models import ScopeResult
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 make_scope_relative(scope: ScopeResult) -> ScopeResult:
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.reviewer.models import PackageManifest, ScopeResult
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
+
@@ -0,0 +1,2 @@
1
+ from codespy.agents.review.scope.agent import MANIFEST_FILES, MANIFEST_GLOBS, ScopeResolver, derive_sparse_paths
2
+ from codespy.agents.review.scope.models import PackageManifest, ScopeResult, ScopeType
@@ -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.reviewer.models import (
25
- PackageManifest,
26
- ReviewContext,
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 are present at root and parent directories
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.reviewer.modules.scope_resolver import _deepest_common_folder
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.reviewer.models import PRContext, ScopeResult
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.reviewer.models import Issue, IssueCategory, ReviewContext, ScopeResult
17
- from codespy.agents.reviewer.modules.helpers import (
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 the scope root.
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.agents.reviewer.models import LocalReviewConfig
111
- from codespy.agents.reviewer.reviewer import ReviewPipeline
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.agents.reviewer.reporters import StdoutReporter
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.agents.reviewer.models import LocalReviewConfig
224
- from codespy.agents.reviewer.reviewer import ReviewPipeline
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.agents.reviewer.reporters import StdoutReporter
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)
@@ -52,6 +52,6 @@ def serve(
52
52
  console.print(f"[red]Error:[/red] {e}")
53
53
  raise typer.Exit(1) from None
54
54
 
55
- from codespy.agents.reviewer.server import run_server
55
+ from codespy.workflows.review.server import run_server
56
56
 
57
57
  run_server(settings=settings)
@@ -159,8 +159,8 @@ def review(
159
159
  )
160
160
 
161
161
  try:
162
- from codespy.agents.reviewer.models import RemoteReviewConfig
163
- from codespy.agents.reviewer.reviewer import ReviewPipeline
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.agents.reviewer.reporters import GitReporter, StdoutReporter
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.reviewer.models import ScopeResult
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
@@ -0,0 +1,7 @@
1
+ from codespy.workflows.review.models import (
2
+ LocalReviewConfig,
3
+ RemoteReviewConfig,
4
+ ReviewConfig,
5
+ ReviewResult,
6
+ )
7
+ from codespy.workflows.review.pipeline import ReviewPipeline