feat: refocus on drawings — code/ADA gated off, drawing-integrity wave, Brain-directed clarification
Docker Release / build-and-push (push) Successful in 1m8s
Docker Release / release (push) Skipped

- ENABLE_CODE_REVIEW flag (default off): skips code/ADA/jurisdiction review
  path in both pipelines; nothing deleted, one env flag to restore.
- Per-sheet Drawing Integrity QA wave (agent + classic, default on):
  dangling refs, on-sheet contradictions, dimension sanity, missing sheet
  essentials, tag hygiene. New DrawingIntegrityAgent + classic stage.
- Broadened conflict critic: intra-sheet + same-discipline contradictions,
  not just cross-discipline.
- Wave 6.5 Brain-directed clarification (bounded hub-and-spoke): Brain names
  uncertain findings, verify_evidence requests route through the wave-5b
  verifier; refuted findings suppressed. One planning call + capped verifies,
  single iteration. Shared _build_verify_scopes across 5b and 6.5.
- Config knobs, .env.example, frontend copy, tests (182 passing).
This commit is contained in:
2026-08-20 15:10:32 -05:00
parent bae608a505
commit d37ac8c1c7
22 changed files with 1917 additions and 63 deletions
+72 -1
View File
@@ -6,7 +6,12 @@ from typing import Dict, List, Tuple
from backend import config
from backend.agents.base import AgentUsage
from backend.agents.prompts import BRAIN_SYSTEM_PROMPT, BRAIN_USER_PROMPT
from backend.agents.prompts import (
BRAIN_CLARIFY_SYSTEM_PROMPT,
BRAIN_CLARIFY_USER_PROMPT,
BRAIN_SYSTEM_PROMPT,
BRAIN_USER_PROMPT,
)
from backend.llm import call_json
from backend.pipeline._stage import collect_list, validate_issue
@@ -127,3 +132,69 @@ class BrainAgent:
issues.sort(key=lambda item: -int(item.get("risk_score") or 0))
decisions = parsed.get("decisions") or []
return issues, [item for item in decisions if isinstance(item, dict)]
def plan_clarifications(self, prioritized: List[Dict]) -> List[Dict]:
"""Wave 6.5 planning call: name kept findings the Brain wants to
double-check before publishing, as typed clarification requests.
Returns a capped list of {issue_id, request_type, reason}. Only
findings that carry an issue_id and do NOT already have a verification
result are offered to the model; anything the model names outside that
set, or with an unknown request_type, is dropped by the caller/router.
Never raises — a failed/empty plan just yields no requests.
"""
max_requests = config.BRAIN_CLARIFY_MAX_REQUESTS
if not prioritized or max_requests <= 0:
return []
candidates = [
{
"issue_id": f.get("issue_id"),
"severity": f.get("severity"),
"confidence": f.get("confidence"),
"source_stage": f.get("source_stage"),
"description": (f.get("description") or "")[:400],
"evidence": f.get("evidence") or [],
"already_verified": bool(f.get("verification")),
}
for f in prioritized
if f.get("issue_id") and not f.get("verification")
]
if not candidates:
return []
instruction = (
BRAIN_CLARIFY_USER_PROMPT
.replace("{max_requests}", str(max_requests))
.replace("{findings}", json.dumps(candidates, ensure_ascii=True))
)
try:
parsed = call_json(
system_prompt=BRAIN_CLARIFY_SYSTEM_PROMPT,
user_text=instruction,
max_tokens=config.BRAIN_CLARIFY_MAX_TOKENS,
model=config.AGENT_BRAIN_MODEL,
usage_tracker=self.usage,
usage_stage="agent.brain_clarify",
)
except Exception:
return []
raw = parsed.get("requests") if isinstance(parsed, dict) else parsed
if not isinstance(raw, list):
return []
valid_ids = {c["issue_id"] for c in candidates}
requests: List[Dict] = []
seen: set = set()
for item in raw:
if not isinstance(item, dict):
continue
issue_id = item.get("issue_id")
if issue_id not in valid_ids or issue_id in seen:
continue
requests.append({
"issue_id": issue_id,
"request_type": (item.get("request_type") or "verify_evidence").strip(),
"reason": (item.get("reason") or "").strip(),
})
seen.add(issue_id)
if len(requests) >= max_requests:
break
return requests