Skip to content
Draft
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
170 commits
Select commit Hold shift + click to select a range
7632b4f
test(ci): require semantic CodeGraph smoke evidence
seonghobae Sep 3, 2026
caf58ab
fix(ci): fail closed on empty CodeGraph smoke retrieval
seonghobae Sep 3, 2026
fc26787
test(reviewer): reject empty CodeGraph semantic evidence
seonghobae Sep 3, 2026
4bdbd86
fix(reviewer): fail closed on empty CodeGraph semantics
seonghobae Sep 3, 2026
1f842a6
docs(reviewer): define empty CodeGraph semantics as missing evidence
seonghobae Sep 3, 2026
bc56698
test(reviewer): reject initialization-only CodeGraph evidence
seonghobae Sep 3, 2026
4284c4c
test(reviewer): reject empty CodeGraph explore payload
seonghobae Sep 3, 2026
6f7ba0c
fix(reviewer): require non-empty CodeGraph explore context
seonghobae Sep 3, 2026
f4f6379
test(reviewer): require labelled CodeGraph explore evidence
seonghobae Sep 3, 2026
957dc55
test(reviewer): preserve current CodeGraph collector compatibility
seonghobae Sep 3, 2026
148cea6
fix(reviewer): keep semantic gate compatible with live collector
seonghobae Sep 3, 2026
eeafc5f
test(reviewer): align semantic evidence with live collector shape
seonghobae Sep 3, 2026
1a74404
test(reviewer): use semantic CodeGraph evidence in full manifest fixture
seonghobae Sep 3, 2026
964cadc
test(reviewer): keep truncated-diff fixture otherwise complete
seonghobae Sep 3, 2026
d9e4bba
test(reviewer): label semantic output at production CLI boundary
seonghobae Sep 3, 2026
2cef887
fix(reviewer): label CodeGraph explore output at CLI boundary
seonghobae Sep 3, 2026
ad64bef
test(reviewer): require provenance-labelled semantic evidence
seonghobae Sep 3, 2026
d658949
fix(reviewer): require provenance-labelled CodeGraph semantics
seonghobae Sep 3, 2026
a901e2e
test(reviewer): fail closed on unlabelled collector output
seonghobae Sep 3, 2026
acf6c9f
docs(reviewer): align semantic evidence and blocked exit code
seonghobae Sep 3, 2026
e9bfc79
test(reviewer): require collector-owned CodeGraph provenance
seonghobae Sep 3, 2026
3c710ef
test(reviewer): reproduce unlabelled sandbox semantic evidence
seonghobae Sep 3, 2026
d47f35a
fix(reviewer): label sandbox CodeGraph semantic evidence
seonghobae Sep 3, 2026
fa0e9e9
test(reviewer): align sandbox protocol with semantic provenance
seonghobae Sep 3, 2026
a1246fc
test(reviewer): bind semantic provenance to runner contract
seonghobae Sep 3, 2026
5a63f85
fix(reviewer): preserve sandbox section provenance
seonghobae Sep 3, 2026
8c657b0
test(reviewer): restore unlabelled collector rejection contract
seonghobae Sep 3, 2026
d28619e
fix(reviewer): restore sandbox-owned section provenance
seonghobae Sep 3, 2026
745c0d4
test(reviewer): reject prelude CodeGraph marker spoof
seonghobae Sep 3, 2026
e2a066e
fix(reviewer): bind semantics to final explore section
seonghobae Sep 3, 2026
58d5876
test(reviewer): reject truncation-only semantic evidence
seonghobae Sep 3, 2026
fb3510e
fix(reviewer): require retained semantic bytes
seonghobae Sep 3, 2026
6ecaef7
test(reviewer): fail closed on malformed truncation-only evidence
seonghobae Sep 4, 2026
836447e
test(reviewer): close semantic runner coverage edges
seonghobae Sep 4, 2026
24c59f4
fix(reviewer): reject annotation-shaped empty semantics
seonghobae Sep 4, 2026
9cbe181
test(reviewer): reject labelled status-only CodeGraph output
seonghobae Sep 4, 2026
04dcb83
fix(reviewer): reject status-only CodeGraph explore evidence
seonghobae Sep 4, 2026
a17108a
fix(reviewer): reject annotation-only CodeGraph evidence
seonghobae Sep 4, 2026
2ef8c3f
test(reviewer): bind empty result to final CodeGraph section
seonghobae Sep 4, 2026
4295c7f
fix(reviewer): bind empty result to final CodeGraph section
seonghobae Sep 4, 2026
db27981
docs(reviewer): document final CodeGraph evidence authority
seonghobae Sep 4, 2026
2f4561b
fix(reviewer): reject section-split empty evidence
seonghobae Sep 4, 2026
d339b2a
test(reviewer): reject self-labelled CodeGraph provenance
seonghobae Sep 4, 2026
2b697d7
test(reviewer): require unambiguous CodeGraph provenance
seonghobae Sep 4, 2026
83d17f3
fix(reviewer): bind CodeGraph provenance to wrapper output
seonghobae Sep 4, 2026
e77099b
fix(reviewer): fail closed on ambiguous CodeGraph provenance
seonghobae Sep 4, 2026
d022de8
docs(reviewer): document wrapper-owned CodeGraph provenance
seonghobae Sep 4, 2026
e064e07
Merge remote-tracking branch 'origin/main' into pr546
seonghobae Sep 4, 2026
356db1f
test(reviewer): reject sanitized raw marker as semantic evidence
seonghobae Sep 4, 2026
b146781
fix(reviewer): drop sanitized raw-only provenance markers
seonghobae Sep 4, 2026
669a7af
docs(reviewer): define raw-marker-only evidence as empty
seonghobae Sep 4, 2026
3d79848
docs(reviewer): fix exit-code formatting
seonghobae Sep 4, 2026
895b4fc
test(reviewer): reject embedded CodeGraph provenance marker
seonghobae Sep 4, 2026
401c3ab
fix(reviewer): require line-exact CodeGraph provenance marker
seonghobae Sep 4, 2026
966c618
test(reviewer): reject raw marker annotation as semantics
seonghobae Sep 4, 2026
40f2edb
fix(reviewer): reject raw marker annotations as context
seonghobae Sep 4, 2026
5a83559
test(reviewer): reject whitespace-obscured empty CodeGraph result
seonghobae Sep 4, 2026
628c29d
fix(reviewer): normalize empty-result whitespace before admission
seonghobae Sep 4, 2026
2f89f4b
docs(reviewer): document whitespace-normalized empty evidence
seonghobae Sep 4, 2026
401ffe1
test(reviewer): reject embedded raw CodeGraph marker evidence
seonghobae Sep 4, 2026
0b5c4f9
fix(reviewer): discard raw marker-contaminated CodeGraph lines
seonghobae Sep 4, 2026
2678085
docs(reviewer): document raw marker-contaminated line rejection
seonghobae Sep 4, 2026
24ef486
docs(reviewer): fix exit-code contract formatting
seonghobae Sep 4, 2026
95c4418
test(reviewer): preserve semantic context beside empty-result text
seonghobae Sep 4, 2026
3b3e4cd
fix(reviewer): scope empty-result classification to response prefix
seonghobae Sep 4, 2026
b36c4a2
docs(reviewer): distinguish empty-result response from source text
seonghobae Sep 4, 2026
4335707
test(reviewer): reject lifecycle-prefixed empty CodeGraph result
seonghobae Sep 4, 2026
91a32e5
fix(reviewer): ignore lifecycle banners before empty CodeGraph result
seonghobae Sep 4, 2026
4f734b2
docs(reviewer): define semantic empty-result prefix
seonghobae Sep 4, 2026
a20ea30
test(reviewer): align self-labelled CodeGraph expectation
seonghobae Sep 4, 2026
04896a8
test(reviewer): require indexed-symbol recovery for path-only CodeGra…
seonghobae Sep 4, 2026
7e69ae8
fix(reviewer): seed empty CodeGraph explore from indexed changed-file…
seonghobae Sep 4, 2026
55ef2b5
test(reviewer): preserve exact changed-file boundaries for CodeGraph …
seonghobae Sep 4, 2026
fdb13ca
test(reviewer): reject non-current paths as CodeGraph symbol seeds
seonghobae Sep 4, 2026
5ec6df1
fix(reviewer): bind CodeGraph symbol recovery to exact current-head p…
seonghobae Sep 4, 2026
6a10701
docs(reviewer): document symbol-seeded empty-explore recovery
seonghobae Sep 4, 2026
9e31052
test(reviewer): reject ambiguous changed-path recovery
seonghobae Sep 4, 2026
50a62ac
fix(reviewer): fail closed on ambiguous CodeGraph path boundaries
seonghobae Sep 4, 2026
05edafa
docs(reviewer): record ambiguous path fail-closed boundary
seonghobae Sep 4, 2026
3874a09
test(reviewer): cover bounded CodeGraph path recovery
seonghobae Sep 4, 2026
0180991
test(reviewer): expose lifecycle-prefixed CodeGraph recovery miss
seonghobae Sep 4, 2026
08283d4
fix(reviewer): recover CodeGraph misses behind lifecycle banners
seonghobae Sep 4, 2026
29fa384
docs(reviewer): define lifecycle-prefixed empty-result recovery
seonghobae Sep 4, 2026
364591e
test(reviewer): preserve exact whitespace in CodeGraph path recovery
seonghobae Sep 4, 2026
1e66b05
fix(reviewer): preserve exact changed-path whitespace during CodeGrap…
seonghobae Sep 4, 2026
ca71e88
docs(reviewer): document exact-whitespace CodeGraph path recovery
seonghobae Sep 4, 2026
0705e7d
test(reviewer): preserve long CodeGraph changed-path identity
seonghobae Sep 4, 2026
45c161c
fix(reviewer): preserve exact CodeGraph changed-path scope
seonghobae Sep 4, 2026
b84a819
docs(reviewer): document exact CodeGraph path scope budget
seonghobae Sep 4, 2026
8c6d63a
test(reviewer): cover exact CodeGraph aggregate scope guard
seonghobae Sep 4, 2026
531418c
test(reviewer): prove long CodeGraph paths remain recoverable
seonghobae Sep 4, 2026
444164e
fix(reviewer): recover exact long CodeGraph paths within bounded probes
seonghobae Sep 4, 2026
9a115ca
test(reviewer): cover bounded long-path segmentation probes
seonghobae Sep 4, 2026
9606032
docs(reviewer): document exact long-path recovery probe budget
seonghobae Sep 4, 2026
2e30a18
test(reviewer): reject ambient CodeGraph process authority
seonghobae Sep 5, 2026
64f9bd6
fix(reviewer): isolate CodeGraph subprocess environment
seonghobae Sep 5, 2026
1a39aca
test(reviewer): align CodeGraph least-authority fixture
seonghobae Sep 5, 2026
63dba0f
docs(reviewer): record CodeGraph ambient-authority boundary
seonghobae Sep 5, 2026
94638b1
test(reviewer): isolate CodeGraph home authority
seonghobae Sep 5, 2026
1e5ac9c
test(reviewer): expose self-check deadlock
seonghobae Sep 5, 2026
7205f34
fix(reviewer): exclude in-flight Noema self-check
seonghobae Sep 5, 2026
421cc0b
fix(reviewer): isolate CodeGraph home authority
seonghobae Sep 5, 2026
756217f
docs(reviewer): record self-check and isolated-home boundaries
seonghobae Sep 5, 2026
0141ae9
test(reviewer): reject partial CodeGraph symbol recovery
seonghobae Sep 5, 2026
9dba467
fix(reviewer): fail closed on partial CodeGraph seed scopes
seonghobae Sep 5, 2026
e651ef3
docs(reviewer): make symbol recovery completeness explicit
seonghobae Sep 5, 2026
71d8f95
test(reviewer): require independent current-head check evidence
seonghobae Sep 5, 2026
4d64e14
fix(reviewer): require independent current-head check evidence
seonghobae Sep 5, 2026
0e7ab93
docs(reviewer): bind cycle exception to independent evidence
seonghobae Sep 5, 2026
0b5628b
test(reviewer): retain distinct deterministic finding evidence
seonghobae Sep 5, 2026
1b314bd
fix(reviewer): preserve distinct deterministic findings
seonghobae Sep 5, 2026
6fcb58f
test(reviewer): align CodeGraph probe-budget boundary
seonghobae Sep 5, 2026
36630f2
test(reviewer): require complete symbol seed coverage
seonghobae Sep 5, 2026
3cddc10
fix(reviewer): fail closed on incomplete symbol seeds
seonghobae Sep 5, 2026
7495a71
test(reviewer): align symbol probe recovery contract
seonghobae Sep 5, 2026
2a6bfdd
test(reviewer): fail closed above CodeGraph file scope budget
seonghobae Sep 5, 2026
2b41e76
fix(reviewer): fail closed above CodeGraph file scope budget
seonghobae Sep 5, 2026
f5bee97
docs(reviewer): document exact CodeGraph file scope budget
seonghobae Sep 5, 2026
551b0d7
test(reviewer): reject symlinked parent CodeGraph seeds
seonghobae Sep 5, 2026
a199f9a
fix(reviewer): reject symlinked CodeGraph path traversal
seonghobae Sep 5, 2026
7f716f9
docs(reviewer): define symlink-free CodeGraph seed boundary
seonghobae Sep 5, 2026
d46e653
test(reviewer): expose self-dependent Noema wait
seonghobae Sep 5, 2026
8222133
fix(reviewer): exclude self-dependent Noema check from evidence wait
seonghobae Sep 5, 2026
fe4386a
test(reviewer): fail closed on truncated symbol-map recovery
seonghobae Sep 5, 2026
e0811e9
fix(reviewer): reject partial symbol-map recovery
seonghobae Sep 5, 2026
74ca6f4
test(reviewer): reject symlinked checkout root for CodeGraph seed
seonghobae Sep 5, 2026
b90c21b
fix(reviewer): reject symlinked CodeGraph checkout roots
seonghobae Sep 5, 2026
be9f83c
test(reviewer): cover symlinked checkout ancestor provenance
seonghobae Sep 5, 2026
1741178
docs(reviewer): bind CodeGraph recovery to physical checkout root
seonghobae Sep 5, 2026
664b9e7
test(codegraph): preserve exact sandbox changed-path identity
seonghobae Sep 5, 2026
ebbc748
fix(codegraph): preserve exact sandbox changed-path identity
seonghobae Sep 5, 2026
4b96b40
test(codegraph): expose Unicode scope budget mismatch
seonghobae Sep 5, 2026
5eee256
fix(codegraph): align Unicode scope budget with reviewer
seonghobae Sep 5, 2026
cfacdba
test(reviewer): prove CodeGraph retry prompt needs fresh sandbox
seonghobae Sep 5, 2026
4fcacd1
fix(reviewer): execute distinct CodeGraph retry prompts
seonghobae Sep 5, 2026
9e9db27
chore(reviewer): preserve sandbox source newline
seonghobae Sep 5, 2026
742f363
test(reviewer): prove production sandbox can symbol-seed retry
seonghobae Sep 5, 2026
51c9440
fix(reviewer): inject CodeGraph runner through semantic recovery
seonghobae Sep 5, 2026
387b0d1
fix(reviewer): add isolated CodeGraph symbol probe
seonghobae Sep 5, 2026
c441571
fix(reviewer): keep semantic recovery inside Docker boundary
seonghobae Sep 5, 2026
1651705
test(reviewer): bind central Docker runner to semantic retry
seonghobae Sep 5, 2026
9696bea
test(reviewer): align retry cache with semantic Docker output
seonghobae Sep 5, 2026
286ac31
test(reviewer): align Docker sandbox fixtures with semantic envelope
seonghobae Sep 5, 2026
d34c2f0
test(reviewer): cover isolated symbol probe path boundary
seonghobae Sep 5, 2026
9f205b8
test(reviewer): cover semantic Docker recovery boundary
seonghobae Sep 5, 2026
3739ea7
docs(reviewer): bind symbol recovery to production Docker runner
seonghobae Sep 5, 2026
fc0585c
test(reviewer): reject redirected Docker source roots
seonghobae Sep 5, 2026
9f93d99
fix(reviewer): bind Docker sandbox to physical checkout root
seonghobae Sep 5, 2026
b2f91c8
test(reviewer): reject ambient CodeGraph temp capability
seonghobae Sep 5, 2026
15ff51c
fix(reviewer): isolate CodeGraph temp capability
seonghobae Sep 5, 2026
377f237
test(reviewer): require complete context through canonical scope
seonghobae Sep 5, 2026
406c2f9
fix(reviewer): retain full canonical changed-file context
seonghobae Sep 5, 2026
4a7f140
docs(reviewer): align manifest scope and temp isolation
seonghobae Sep 5, 2026
f12c9ab
docs(changelog): record reviewer context liveness repair
seonghobae Sep 5, 2026
f18b665
test(reviewer): fail closed before oversized CodeGraph execution
seonghobae Sep 5, 2026
fed98d0
fix(reviewer): reject oversized CodeGraph scope before execution
seonghobae Sep 5, 2026
af0e59c
test(reviewer): reject filename prompt injection in CodeGraph scope
seonghobae Sep 5, 2026
d439058
fix(reviewer): isolate changed filenames as prompt data
seonghobae Sep 5, 2026
1dbe078
test(reviewer): preserve symbol recovery for JSON changed scope
seonghobae Sep 5, 2026
a785cd4
fix(reviewer): recover symbols from canonical JSON scope
seonghobae Sep 5, 2026
244a029
test(reviewer): keep recovery seeds as untrusted prompt data
seonghobae Sep 5, 2026
b6c3702
fix(reviewer): encode empty-recovery seeds as untrusted JSON
seonghobae Sep 5, 2026
1a407c1
docs(reviewer): document JSON-safe recovery seeds
seonghobae Sep 5, 2026
1d9e8e5
docs(reviewer): fix exit-code markup
seonghobae Sep 5, 2026
3328f7b
test(codegraph): preserve leading backslash Git path identity
seonghobae Sep 5, 2026
04376e2
fix(codegraph): keep Linux backslash filenames byte-exact
seonghobae Sep 5, 2026
b84f0e5
test(reviewer): align deterministic finding identity contract
seonghobae Sep 5, 2026
6e5df50
test(reviewer): cover fail-closed CodeGraph admission edges
seonghobae Sep 5, 2026
95144d5
fix(reviewer): remove unreachable legacy path branch
seonghobae Sep 5, 2026
7d3de5a
fix(reviewer): document bound semantic runner
seonghobae Sep 5, 2026
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
15 changes: 8 additions & 7 deletions .github/workflows/reviewer-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,15 @@ jobs:
assert runner(["codegraph", "sync"], source_root) == ""
assert runner(["codegraph", "status"], source_root) == ""
output = runner(
[
"codegraph",
"explore",
"Review blast radius and focused tests for example.ts",
],
["codegraph", "explore", "commercialReadiness"],
source_root,
)
if "Sandbox copied 1 files" not in output or "## codegraph explore" not in output:
raise SystemExit("CodeGraph sandbox smoke output was incomplete")
if (
"Sandbox copied 1 files" not in output
or "## codegraph explore" not in output
or "No relevant code found" in output
or "export const commercialReadiness = true;" not in output
):
raise SystemExit("CodeGraph sandbox smoke did not retrieve the indexed fixture")
print(output[:2000])
PY
17 changes: 14 additions & 3 deletions reviewer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,18 @@ they hold regardless of what the model says:
1. **Strict runs never pass silently.** With `--strict`, a manifest missing its
diff, changed-file context, current check conclusions, CodeGraph evidence,
or any requested GitHub evidence source returns a `blocked` verdict that
names every gap.
names every gap. Production collection emits exactly one wrapper-owned
`## codegraph explore` provenance marker and neutralizes any matching marker
text returned by raw CodeGraph stdout before retaining it. If raw stdout
contains only copies of that marker, collection retains an empty labelled
explore section rather than letting the neutralization annotation become
semantic evidence. A strict manifest with more than one explore marker is
therefore ambiguous and fails closed. Initialization/status banners, an
empty labelled explore section, unlabelled concatenated output,
`No relevant code found`, truncation/workflow-command annotations without
retained semantic bytes, and control/punctuation-only output are not
semantic review evidence. Setup/status bytes cannot redefine the
wrapper-owned explore boundary.
2. **MEDIUM-or-higher dependency findings can't ride out on an approve.** An
unresolved OSV/Trivy/dependency-review finding at MEDIUM+ downgrades an
approval to `request_changes` with the finding attached — the org rule is
Expand Down Expand Up @@ -74,7 +85,7 @@ python -m noema_reviewer --repo ContextualWisdomLab/naruon --pr-number 1039 \
python -m noema_reviewer --manifest-file manifest.json
```

Exit code: `0` for approve/blocked, `2` for request_changes.
Exit code: `0` for approve, `2` for request_changes, `3` for blocked.

## Configuration

Expand Down Expand Up @@ -112,4 +123,4 @@ python -m interrogate -c pyproject.toml noema_reviewer # 100% docstring gate
```

Tests drive the agent with PydanticAI's offline `TestModel`/`FunctionModel` and
a stub `gh` runner — no network, no secret, no real model.
a stub `gh` runner — no network, no secret, no real model.
41 changes: 37 additions & 4 deletions reviewer/noema_reviewer/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
from __future__ import annotations

import argparse
import re
import sys
from collections.abc import Callable
from collections.abc import Callable, Sequence

from .agent import ReviewAgent, build_agent
from .github_io import fetch_manifest, publish_verdict
from .github_io import default_codegraph_runner, fetch_manifest, publish_verdict
from .manifest import ReviewManifest
from .models import ReviewVerdict, Verdict

Expand All @@ -21,13 +22,45 @@
ManifestLoader = Callable[[argparse.Namespace], ReviewManifest]
Publisher = Callable[[str, int, ReviewVerdict, str, str], str]

CODEGRAPH_EXPLORE_MARKER = "## codegraph explore"
RAW_CODEGRAPH_EXPLORE_MARKER = "[raw CodeGraph explore marker]"


def _semantic_codegraph_runner(args: Sequence[str], source_root: str) -> str:
"""Attach wrapper-owned explore provenance without trusting raw CodeGraph labels."""
output = default_codegraph_runner(args, source_root)
if len(args) < 2 or args[1] != "explore":
return output
stripped = output.strip()
if stripped:
sanitized = re.sub(
re.escape(CODEGRAPH_EXPLORE_MARKER),
RAW_CODEGRAPH_EXPLORE_MARKER,
output,
flags=re.IGNORECASE,
)
retained_non_marker = "\n".join(
line
for line in sanitized.splitlines()
if line.strip().lower() != RAW_CODEGRAPH_EXPLORE_MARKER.lower()
).strip()
if retained_non_marker:
return f"{CODEGRAPH_EXPLORE_MARKER}\n{sanitized}"
return CODEGRAPH_EXPLORE_MARKER
return CODEGRAPH_EXPLORE_MARKER


def _load_manifest(args: argparse.Namespace) -> ReviewManifest:
"""Load a manifest from a file when given, else fetch it from GitHub."""
if args.manifest_file:
with open(args.manifest_file, encoding="utf-8") as handle:
return ReviewManifest.model_validate_json(handle.read())
return fetch_manifest(args.repo, args.pr_number, source_root=args.source_root)
return fetch_manifest(
args.repo,
args.pr_number,
source_root=args.source_root,
codegraph_runner=_semantic_codegraph_runner,
)


def _publish(repo: str, pr_number: int, verdict: ReviewVerdict, head_sha: str, token_source: str) -> str:
Expand All @@ -36,7 +69,7 @@ def _publish(repo: str, pr_number: int, verdict: ReviewVerdict, head_sha: str, t


def parse_args(argv: list[str]) -> argparse.Namespace:
"""Parse the reviewer CLI arguments."""
"""Parse CLI arguments."""
parser = argparse.ArgumentParser(prog="noema_reviewer", description="Noema independent PR reviewer.")
parser.add_argument("--repo", default="", help="Target repository in owner/name form.")
parser.add_argument("--pr-number", type=int, default=0, help="Pull request number.")
Expand Down
75 changes: 74 additions & 1 deletion reviewer/noema_reviewer/gating.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,58 @@
{"opencode-review", "metadata-only gate evaluation"}
)

CODEGRAPH_EXPLORE_MARKER = "## codegraph explore"

# These are lifecycle/status banners emitted by CodeGraph collection paths, not
# semantic review context. The explore provenance wrapper must not promote them
# merely because they were returned on the explore stdout channel.
NON_SEMANTIC_CODEGRAPH_EXPLORE_OUTPUTS = frozenset(
{
"initialized",
"synced",
"index is up to date",
"codegraph initialized; status produced no output.",
}
)


def _codegraph_explore_section(codegraph_status: str) -> tuple[str, int, str]:
"""Return normalized status, marker count, and the sole trusted explore section."""
status_lower = codegraph_status.strip().lower()
status_lines = status_lower.splitlines()
marker_indexes = [
index
for index, raw_line in enumerate(status_lines)
if raw_line.strip() == CODEGRAPH_EXPLORE_MARKER
]
marker_count = len(marker_indexes)
if marker_count != 1:
return status_lower, marker_count, ""
return (
status_lower,
marker_count,
"\n".join(status_lines[marker_indexes[0] + 1 :]),
)


def _has_semantic_codegraph_context(manifest: ReviewManifest) -> bool:
"""Require retained semantic bytes after exactly one wrapper-owned explore marker."""
_, marker_count, explore_section = _codegraph_explore_section(manifest.codegraph_status)
if marker_count != 1:
return False
semantic_lines = explore_section.splitlines()
return any(
line
and line not in NON_SEMANTIC_CODEGRAPH_EXPLORE_OUTPUTS
and not line.startswith("[truncated ")
and not line.startswith("## codegraph ")
and not line.startswith("::")
and line.isprintable()
and any(character.isalnum() for character in line)
for raw_line in semantic_lines
if (line := raw_line.strip())
)


def missing_evidence(manifest: ReviewManifest) -> list[str]:
"""Return human-readable reasons the manifest lacks review-grade evidence."""
Expand All @@ -46,13 +98,34 @@ def missing_evidence(manifest: ReviewManifest) -> list[str]:
if not manifest.check_conclusions:
reasons.append("missing current GitHub check conclusions")
codegraph_status = manifest.codegraph_status.strip()
codegraph_status_lower, explore_marker_count, final_explore_section = _codegraph_explore_section(
codegraph_status
)
normalized_final_explore = " ".join(
line
for raw_line in final_explore_section.splitlines()
if (line := raw_line.strip())
and not line.startswith(("## codegraph ", "::", "[truncated "))
)
if not codegraph_status:
# A blank/whitespace status is not evidence; treat it as missing so a
# malformed artifact cannot pass strict mode silently (mirrors the diff
# check above and the field's own "not supplied" default semantics).
reasons.append("missing CodeGraph evidence")
elif codegraph_status.lower().startswith("unavailable"):
elif codegraph_status_lower.startswith("unavailable"):
reasons.append(manifest.codegraph_status)
elif explore_marker_count > 1:
# The production wrapper emits exactly one provenance marker. A second
# marker can only come from untrusted output or a malformed prepared
# manifest, so strict review cannot choose which section is authoritative.
reasons.append("CodeGraph semantic query has ambiguous provenance")
elif "no relevant code found" in normalized_final_explore:
# CodeGraph can initialize and index successfully while returning no
# semantic context. The sole provenance-labelled explore section owns
# that classification; setup/status output cannot override it.
reasons.append("CodeGraph semantic query returned no relevant code")
elif not _has_semantic_codegraph_context(manifest):
reasons.append("CodeGraph semantic query produced no review context")
reasons.extend(f"evidence collection failure: {failure}" for failure in manifest.evidence_failures)
return reasons

Expand Down
43 changes: 41 additions & 2 deletions reviewer/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,56 @@ def test_load_manifest_from_file(tmp_path) -> None:
assert loaded.repo == "o/r"


def test_semantic_codegraph_runner_labels_explore_output(monkeypatch) -> None:
"""Production collection labels explore stdout at the command boundary."""
monkeypatch.setattr(
cli,
"default_codegraph_runner",
lambda args, source_root: "x.py -> token boundary" if "explore" in args else "initialized",
)

assert cli._semantic_codegraph_runner(
["codegraph", "explore", "review x.py"],
"/target",
) == "## codegraph explore\nx.py -> token boundary"
assert cli._semantic_codegraph_runner(["codegraph", "status"], "/target") == "initialized"


def test_semantic_codegraph_runner_does_not_trust_self_labelled_output(monkeypatch) -> None:
"""Raw CodeGraph stdout cannot supply the provenance marker trusted by strict review."""
labelled = "## codegraph explore\nx.py -> token boundary\n"
monkeypatch.setattr(cli, "default_codegraph_runner", lambda args, source_root: labelled)

assert cli._semantic_codegraph_runner(
["codegraph", "explore", "review x.py"],
"/target",
) == (
"## codegraph explore\n"
"[raw CodeGraph explore marker]\n"
"x.py -> token boundary\n"
)


def test_semantic_codegraph_runner_labels_empty_explore_output(monkeypatch) -> None:
"""An empty explore result still receives the provenance marker and no synthetic payload."""
monkeypatch.setattr(cli, "default_codegraph_runner", lambda args, source_root: " \n")

assert cli._semantic_codegraph_runner(["codegraph", "explore", "review x.py"], "/target") == "## codegraph explore"


def test_load_manifest_fetches_when_no_file(monkeypatch) -> None:
"""The default loader fetches from GitHub when no file is given."""
captured = {}

def fake_fetch(repo, pr_number, *, source_root):
def fake_fetch(repo, pr_number, *, source_root, codegraph_runner):
captured["source_root"] = source_root
captured["codegraph_runner"] = codegraph_runner
return _manifest()

monkeypatch.setattr(cli, "fetch_manifest", fake_fetch)
assert cli._load_manifest(_args(source_root="/target")).pr_number == 9
assert captured["source_root"] == "/target"
assert captured["codegraph_runner"] is cli._semantic_codegraph_runner


def test_publish_adapter_calls_github(monkeypatch) -> None:
Expand Down Expand Up @@ -178,4 +217,4 @@ def test_main_runs_with_manifest_file(tmp_path, monkeypatch) -> None:
manifest_file.write_text(_manifest().model_dump_json())
monkeypatch.setattr(cli, "build_agent", lambda: FixedAgent(ReviewVerdict(verdict=Verdict.APPROVE, summary="ok")))
code = cli.main(["--manifest-file", str(manifest_file)])
assert code == 0
assert code == 0
25 changes: 25 additions & 0 deletions reviewer/tests/test_codegraph_marker_line_authority.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Regression coverage for line-exact CodeGraph provenance markers."""

from noema_reviewer.gating import missing_evidence
from noema_reviewer.manifest import ChangedFile, CheckConclusion, ReviewManifest


def _manifest(codegraph_status: str) -> ReviewManifest:
"""Build an otherwise-complete manifest for provenance parsing tests."""
return ReviewManifest(
repo="ContextualWisdomLab/noema",
pr_number=546,
diff="diff --git a/x.py b/x.py",
changed_files=[ChangedFile(path="x.py", content="value = 1")],
check_conclusions=[CheckConclusion(name="ci", conclusion="success")],
codegraph_status=codegraph_status,
)


def test_embedded_explore_marker_is_not_wrapper_provenance() -> None:
"""Only a dedicated marker line may authorize the following semantic payload."""
reasons = missing_evidence(
_manifest("notice: ## codegraph explore\nx.py -> sensitive_call")
)

assert reasons == ["CodeGraph semantic query produced no review context"]
36 changes: 36 additions & 0 deletions reviewer/tests/test_codegraph_raw_marker_authority.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Regression coverage for wrapper-owned CodeGraph provenance authority."""

from noema_reviewer import cli
from noema_reviewer.gating import missing_evidence
from noema_reviewer.manifest import ChangedFile, CheckConclusion, ReviewManifest


def _manifest(codegraph_status: str) -> ReviewManifest:
"""Build an otherwise-complete manifest for the raw-marker authority regression."""
return ReviewManifest(
repo="ContextualWisdomLab/noema",
pr_number=546,
diff="diff --git a/x.py b/x.py",
changed_files=[ChangedFile(path="x.py", content="value = 1")],
check_conclusions=[CheckConclusion(name="ci", conclusion="success")],
codegraph_status=codegraph_status,
)


def test_raw_explore_marker_alone_cannot_become_semantic_context(monkeypatch) -> None:
"""A sanitized copy of the trust delimiter must not itself satisfy strict evidence."""
monkeypatch.setattr(
cli,
"default_codegraph_runner",
lambda args, source_root: "## codegraph explore",
)

retained = cli._semantic_codegraph_runner(
["codegraph", "explore", "review x.py"],
"/target",
)

assert retained == "## codegraph explore"
assert missing_evidence(_manifest(retained)) == [
"CodeGraph semantic query produced no review context"
]
Loading
Loading