Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The call chain follows four conceptual layers:

1. **Receive** — `commands/webhook/main.py` (webhooks), `commands/` (CLI/CI).
2. **Prepare** — `workspace/setup.py::prepare_job_event()` resolves clone URLs and branches; `commands/webhook/jobs/runner/process.py` wraps with error handling and queue management.
3. **Orchestrate** — `review/reviewer.py` (business logic: diff fetching, PR metadata fetching, prompt building, sub-agent configuration, output parsing).
3. **Orchestrate** — `review/reviewer.py` (business logic: diff fetching, PR metadata fetching, prompt building, sub-agent configuration, output parsing). `ReviewScope.CODEBASE` skips all platform API calls (diff/comments/metadata), skips diff-line filtering, and uses a codebase-review prompt header — intended for whole-repo audit passes that have no associated PR.
4. **Invoke** — `agent/invoke.py` provides agent execution with explicit conversation lifecycle (`prepare_conversation`, `invoke_agent`, `save_conversation`).

## Agent runner selection
Expand Down
9 changes: 9 additions & 0 deletions app/nominal_code/commands/webhook/jobs/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from nominal_code.commands.webhook.jobs.handler import JobHandler
from nominal_code.commands.webhook.jobs.payload import JobPayload
from nominal_code.platforms.base import CommentEvent
from nominal_code.review.reviewer import ReviewScope
from nominal_code.workspace.setup import prepare_job_event

if TYPE_CHECKING:
Expand Down Expand Up @@ -60,6 +61,8 @@ async def execute_job(
conversation_store: ConversationStore | None = None,
pre_cloned: bool = False,
context: str = "",
scope: ReviewScope = ReviewScope.PR,
workspace_path: str | None = None,
) -> JobResult:
"""
Execute a review job.
Expand All @@ -77,6 +80,10 @@ async def execute_job(
pre_cloned (bool): When True, the repository was pre-cloned by
an external process and clone URL resolution is skipped.
context (str): Pre-review context to include in the user message.
scope (ReviewScope): Whether this is a PR diff review or a
whole-repository codebase review.
workspace_path (str): Pre-existing workspace path. Required when
``scope`` is ``ReviewScope.CODEBASE``.

Returns:
JobResult: The execution result.
Expand All @@ -100,6 +107,8 @@ async def execute_job(
conversation_store=conversation_store,
namespace=job.namespace,
context=context,
scope=scope,
workspace_path=workspace_path,
)

return JobResult(review_result=review_result)
18 changes: 16 additions & 2 deletions app/nominal_code/commands/webhook/jobs/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from typing import TYPE_CHECKING, Protocol

from nominal_code.review.reviewer import ReviewScope, run_and_post_review

if TYPE_CHECKING:
from nominal_code.config import Config
from nominal_code.conversation.base import ConversationStore
Expand Down Expand Up @@ -31,6 +33,8 @@ async def handle_review(
conversation_store: ConversationStore | None = None,
namespace: str = "",
context: str = "",
scope: ReviewScope = ReviewScope.PR,
workspace_path: str | None = None,
) -> ReviewResult:
"""
Execute a code review and post results.
Expand All @@ -44,6 +48,10 @@ async def handle_review(
conversation continuity.
namespace (str): Logical namespace for conversation key isolation.
context (str): Pre-review context to include in the user message.
scope (ReviewScope): Whether this is a PR diff review or a
whole-repository codebase review.
workspace_path (str): Pre-existing workspace path. Required when
``scope`` is ``ReviewScope.CODEBASE``.

Returns:
ReviewResult: The review result with findings and summary.
Expand All @@ -66,6 +74,8 @@ async def handle_review(
conversation_store: ConversationStore | None = None,
namespace: str = "",
context: str = "",
scope: ReviewScope = ReviewScope.PR,
workspace_path: str | None = None,
) -> ReviewResult:
"""
Delegate to ``run_and_post_review``.
Expand All @@ -79,13 +89,15 @@ async def handle_review(
conversation continuity.
namespace (str): Logical namespace for conversation key isolation.
context (str): Pre-review context to include in the user message.
scope (ReviewScope): Whether this is a PR diff review or a
whole-repository codebase review.
workspace_path (str): Pre-existing workspace path. Required when
``scope`` is ``ReviewScope.CODEBASE``.

Returns:
ReviewResult: The review result with findings and summary.
"""

from nominal_code.review.reviewer import run_and_post_review

return await run_and_post_review(
event=event,
prompt=prompt,
Expand All @@ -94,4 +106,6 @@ async def handle_review(
conversation_store=conversation_store,
namespace=namespace,
context=context,
scope=scope,
workspace_path=workspace_path,
)
17 changes: 17 additions & 0 deletions app/nominal_code/commands/webhook/jobs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from nominal_code.models import ProviderName
from nominal_code.platforms import build_platform
from nominal_code.platforms.base import Platform, PlatformName
from nominal_code.review.reviewer import ReviewScope

_env: Env = Env()

Expand All @@ -27,6 +28,8 @@
async def run_job_main(
pre_cloned: bool = False,
context: str = "",
scope: ReviewScope = ReviewScope.PR,
workspace_path: str | None = None,
) -> JobResult | None:
"""
Entry point for the ``run-job`` CLI subcommand.
Expand All @@ -39,6 +42,10 @@ async def run_job_main(
pre_cloned (bool): When True, the repository was pre-cloned by
an external process and clone URL resolution is skipped.
context (str): Pre-review context to include in the user message.
scope (ReviewScope): Whether this is a PR diff review or a
whole-repository codebase review.
workspace_path (str): Pre-existing workspace path. Required when
``scope`` is ``ReviewScope.CODEBASE``.

Returns:
JobResult | None: The job result on success, or ``None`` on
Expand Down Expand Up @@ -85,6 +92,8 @@ async def run_job_main(
conversation_store=conversation_store,
pre_cloned=pre_cloned,
context=context,
scope=scope,
workspace_path=workspace_path,
)

succeeded: bool = result is not None
Expand All @@ -101,6 +110,8 @@ async def _run_job(
conversation_store: ConversationStore | None = None,
pre_cloned: bool = False,
context: str = "",
scope: ReviewScope = ReviewScope.PR,
workspace_path: str | None = None,
) -> JobResult | None:
"""
Execute a job via the unified dispatch pipeline.
Expand All @@ -114,6 +125,10 @@ async def _run_job(
for conversation continuity.
pre_cloned (bool): When True, skip clone URL resolution.
context (str): Pre-review context to include in the user message.
scope (ReviewScope): Whether this is a PR diff review or a
whole-repository codebase review.
workspace_path (str): Pre-existing workspace path. Required when
``scope`` is ``ReviewScope.CODEBASE``.

Returns:
JobResult | None: The job result on success, or ``None`` on
Expand All @@ -136,6 +151,8 @@ async def _run_job(
conversation_store=conversation_store,
pre_cloned=pre_cloned,
context=context,
scope=scope,
workspace_path=workspace_path,
)
except Exception:
logger.exception(
Expand Down
41 changes: 39 additions & 2 deletions app/nominal_code/review/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,44 @@
MAX_COMMIT_MESSAGES: int = 20


def build_codebase_reviewer_prompt(
event: PullRequestEvent,
user_prompt: str,
context: str = "",
) -> str:
"""
Build a prompt for a whole-repository review (no diff context).

Used when ``scope`` is ``ReviewScope.CODEBASE``. Produces a header
that identifies the repo and branch without referencing a PR number
or diff. The LLM is expected to explore the workspace via its tools.

Args:
event (PullRequestEvent): Event carrying the repo name and branch.
user_prompt (str): Optional caller-supplied instructions.
context (str): Pre-review exploration notes to insert before
the review instruction.

Returns:
str: The full prompt to send to the codebase reviewer.
"""

parts: list[str] = [
f"## Codebase review: {event.repo_full_name}\n\n"
f"**Branch**: <{TAG_BRANCH_NAME}>{event.pr_branch}</{TAG_BRANCH_NAME}>"
]

if user_prompt:
parts.append(
f"Additional instructions:\n{wrap_tag(TAG_UNTRUSTED_REQUEST, user_prompt)}"
)

if context:
parts.append(context)

return "\n\n".join(parts)


def build_reviewer_prompt(
event: PullRequestEvent,
user_prompt: str,
Expand Down Expand Up @@ -87,8 +125,7 @@ def build_reviewer_prompt(

review_instruction: str = (
"Review the above changes. Each diff line is annotated with its "
"actual line number — use these directly. Call the submit_review "
"tool with your complete review.\n\n"
"actual line number — use these directly.\n\n"
"For comments on deleted lines (prefixed with `-` in the diff), "
'set `"side": "LEFT"`. For additions (`+`) and context lines '
'omit `side` or use `"RIGHT"`.'
Expand Down
Loading
Loading