1313the circularity concern is empirically defused.
1414
1515Environment variables:
16- ANTHROPIC_API_KEY Required. Claude API key.
16+ CLAUDE_CODE_OAUTH_TOKEN Preferred. OAuth token for subscription billing.
17+ ANTHROPIC_API_KEY Fallback. Claude API key.
1718 SOURCEGRAPH_ACCESS_TOKEN Required for deepsearch/hybrid backends.
1819 SOURCEGRAPH_URL SG instance (default: https://sourcegraph.sourcegraph.com)
1920 CCB_REPO_CACHE Repo clone cache dir (default: ~/.cache/ccb_repos)
6667# Constants
6768# ---------------------------------------------------------------------------
6869
69- DEFAULT_MODEL = "claude-sonnet -4-6" # Good balance of cost and capability
70+ DEFAULT_MODEL = "claude-opus -4-6" # Strongest model for oracle generation
7071MAX_TOKENS = 16384
7172MAX_TOOL_CALLS = 40
7273TOOL_TIMEOUT_SEC = 30
@@ -1294,6 +1295,81 @@ def _extract_json_from_messages(messages: List[Dict]) -> Dict[str, Any]:
12941295 return {"files" : [], "text" : "Agent did not produce valid JSON output." }
12951296
12961297
1298+ # ---------------------------------------------------------------------------
1299+ # Dual-Retrieval Verification
1300+ # ---------------------------------------------------------------------------
1301+
1302+
1303+ def verify_dual_retrieval (
1304+ oracle : Dict [str , Any ],
1305+ repo_paths : Dict [str , Path ],
1306+ sg_client : Optional ["SourcegraphClient" ] = None ,
1307+ ) -> Dict [str , Any ]:
1308+ """Verify each oracle file is discoverable via local FS and Sourcegraph.
1309+
1310+ Returns dict with per-file verification results and summary stats.
1311+ Files that fail either check are flagged but NOT removed from the oracle.
1312+ """
1313+ files = oracle .get ("files" , [])
1314+ verification = []
1315+
1316+ for entry in files :
1317+ repo = entry .get ("repo" , "" )
1318+ path = entry .get ("path" , "" )
1319+
1320+ # --- Local verification: file exists on disk in any repo_paths ---
1321+ local_ok = False
1322+ if repo_paths :
1323+ # Try exact repo match first, then fall back to any repo dir
1324+ for rname , rdir in repo_paths .items ():
1325+ candidate = rdir / path
1326+ if candidate .is_file ():
1327+ local_ok = True
1328+ break
1329+
1330+ # --- Sourcegraph verification: keyword search returns results ---
1331+ sg_ok = False
1332+ if sg_client and sg_client .token :
1333+ try :
1334+ # Construct a precise file search query
1335+ sg_query = f"file:^{ re .escape (path )} $ count:1"
1336+ if repo :
1337+ sg_query = f"repo:{ repo } { sg_query } "
1338+ result = sg_client .keyword_search (sg_query , max_results = 1 )
1339+ sg_ok = result and "No results found" not in result and "error" not in result .lower ()
1340+ except Exception as e :
1341+ log .debug ("SG verify failed for %s:%s: %s" , repo , path , e )
1342+
1343+ verification .append ({
1344+ "repo" : repo ,
1345+ "path" : path ,
1346+ "local_verified" : local_ok ,
1347+ "sg_verified" : sg_ok ,
1348+ })
1349+
1350+ # Summary stats
1351+ n_total = len (verification )
1352+ n_dual = sum (1 for v in verification if v ["local_verified" ] and v ["sg_verified" ])
1353+ n_local_only = sum (1 for v in verification if v ["local_verified" ] and not v ["sg_verified" ])
1354+ n_sg_only = sum (1 for v in verification if not v ["local_verified" ] and v ["sg_verified" ])
1355+ n_unverified = sum (1 for v in verification if not v ["local_verified" ] and not v ["sg_verified" ])
1356+
1357+ summary = {
1358+ "n_total" : n_total ,
1359+ "n_dual_verified" : n_dual ,
1360+ "n_local_only" : n_local_only ,
1361+ "n_sg_only" : n_sg_only ,
1362+ "n_unverified" : n_unverified ,
1363+ }
1364+
1365+ log .info (
1366+ " Verification: %d/%d dual, %d local-only, %d sg-only, %d unverified" ,
1367+ n_dual , n_total , n_local_only , n_sg_only , n_unverified ,
1368+ )
1369+
1370+ return {"files" : verification , "summary" : summary }
1371+
1372+
12971373# ---------------------------------------------------------------------------
12981374# Output
12991375# ---------------------------------------------------------------------------
@@ -1394,6 +1470,10 @@ def main() -> int:
13941470 "--missing-only" , action = "store_true" ,
13951471 help = "Only process tasks that have NO ground truth at all (no oracle_answer.json, no ground_truth.json)" ,
13961472 )
1473+ parser .add_argument (
1474+ "--no-verify" , action = "store_true" ,
1475+ help = "Skip dual-retrieval verification pass" ,
1476+ )
13971477 parser .add_argument (
13981478 "--dry-run" , action = "store_true" ,
13991479 help = "Show tasks without running agent" ,
@@ -1413,9 +1493,16 @@ def main() -> int:
14131493 log .error ("anthropic package not installed. pip install anthropic" )
14141494 return 1
14151495
1416- api_key = os .environ .get ("ANTHROPIC_API_KEY" , "" )
1496+ # OAuth token preferred (subscription billing), API key fallback
1497+ api_key = os .environ .get ("CLAUDE_CODE_OAUTH_TOKEN" , "" )
1498+ if api_key :
1499+ log .info ("Using OAuth token (CLAUDE_CODE_OAUTH_TOKEN)" )
1500+ else :
1501+ api_key = os .environ .get ("ANTHROPIC_API_KEY" , "" )
1502+ if api_key :
1503+ log .info ("Using API key (ANTHROPIC_API_KEY)" )
14171504 if not api_key and not args .dry_run :
1418- log .error ("ANTHROPIC_API_KEY not set " )
1505+ log .error ("Set CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY " )
14191506 return 1
14201507
14211508 # Discover tasks
@@ -1557,6 +1644,18 @@ def main() -> int:
15571644 else :
15581645 output_path = str (out_dir / f"{ task_dir .name } _gt_agent.json" )
15591646
1647+ # Dual-retrieval verification (unless --no-verify)
1648+ if not args .no_verify :
1649+ vr = verify_dual_retrieval (oracle , repo_paths , sg_client = sg )
1650+ metadata ["dual_verification" ] = vr ["summary" ]
1651+ # Annotate oracle file entries with verification flags
1652+ for v_entry in vr ["files" ]:
1653+ for f_entry in oracle .get ("files" , []):
1654+ if f_entry .get ("path" ) == v_entry ["path" ]:
1655+ f_entry ["local_verified" ] = v_entry ["local_verified" ]
1656+ f_entry ["sg_verified" ] = v_entry ["sg_verified" ]
1657+ break
1658+
15601659 out_file = write_oracle (task_dir , oracle , metadata , output_path )
15611660
15621661 n_files = len (oracle .get ("files" , []))
0 commit comments