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
5 changes: 3 additions & 2 deletions scripts/ci_impact/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import sys
from pathlib import Path

from .core import STAGES, ImpactPlan, build_plan, write_plan
from .core import STAGES, ImpactPlan, build_plan, is_hard_full_path, write_plan
from .llm import (
LLMSelection,
LLMStageError,
Expand Down Expand Up @@ -78,7 +78,8 @@ def _plan(args: argparse.Namespace) -> int:
catalog = load_catalog(args.catalog, plan.head)
plan = expand_plan_with_catalog(plan, catalog)
write_plan(plan, args.deterministic_output)
if plan.mode == "full":
hard_full_change = any(is_hard_full_path(path) for path in plan.changed_files)
if plan.mode == "full" and (args.force_full or hard_full_change):
selection = _skipped_llm_selection()
plan.llm = selection.to_dict()
write_llm_artifact(selection, args.llm_output)
Expand Down
13 changes: 7 additions & 6 deletions scripts/ci_impact/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,7 @@
"weight_free_qaic",
)

HARD_FULL_FILES = {
"pyproject.toml",
"scripts/Jenkinsfile",
"scripts/JenkinsFileFullCi",
}
HARD_FULL_FILES = {"pyproject.toml", "scripts/Jenkinsfile", "scripts/JenkinsFileFullCi"}
IGNORED_FILES = {
".gitignore",
".pre-commit-config.yaml",
Expand Down Expand Up @@ -626,6 +622,11 @@ def _empty_plan(
)


def is_hard_full_path(path: str) -> bool:
"""Return whether a changed path must bypass LLM test selection."""
return path in HARD_FULL_FILES or path.startswith("scripts/ci_impact/")


def build_plan(repo: Path, base: str, head: str = "HEAD", force_full: bool = False) -> ImpactPlan:
repo = repo.resolve()
merge_base, head_sha, changes = resolve_changes(repo, base, head)
Expand All @@ -635,7 +636,7 @@ def build_plan(repo: Path, base: str, head: str = "HEAD", force_full: bool = Fal
return _empty_plan("no_tests", merge_base, head_sha, changes, ["no changed files"], [])

paths = {change.path for change in changes}
hard = sorted(path for path in paths if path in HARD_FULL_FILES or path.startswith("scripts/ci_impact/"))
hard = sorted(path for path in paths if is_hard_full_path(path))
if hard:
reasons = [f"unconditional full-CI path: {path}" for path in hard]
return _empty_plan("full", merge_base, head_sha, changes, reasons, [])
Expand Down
17 changes: 3 additions & 14 deletions scripts/ci_impact/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
from dataclasses import asdict, dataclass
from pathlib import Path

from .core import SCHEMA_VERSION, STAGES, ImpactPlan, TestCase
from .core import SCHEMA_VERSION, STAGES, ImpactPlan, TestCase, is_hard_full_path

DEFAULT_MODEL = "azure::gpt-5.5"
DEFAULT_MODEL = "gpt5.6-terra"
DEFAULT_REASONING_EFFORT = "high"
MAX_PROMPT_BYTES = 400_000
MAX_RESPONSE_BYTES = 1_000_000
Expand All @@ -36,13 +36,6 @@
QUERY_TOOL_PATH = Path(__file__).with_name("query.py")
TOOL_POLICY_PATH = Path(__file__).with_name("tool_policy.py")
HOOK_AUDIT_NAME = ".ci-impact-qgenie-audit.jsonl"
LLM_REFINABLE_FULL_REASONS = (
"global pytest behavior changed:",
"source snapshot generation failed",
"unsafe static analysis for ",
"unclassified production/configuration changes",
"unparsable model inventory:",
)


class LLMStageError(RuntimeError):
Expand Down Expand Up @@ -700,11 +693,7 @@ def merge_selection(
plan.stages[stage]["enabled"] = True
return plan

if (
plan.mode == "full"
and plan.reasons
and all(reason.startswith(LLM_REFINABLE_FULL_REASONS) for reason in plan.reasons)
):
if plan.mode == "full" and not any(is_hard_full_path(path) for path in plan.changed_files):
for stage in STAGES:
plan.stages[stage]["enabled"] = False
plan.reasons.append("LLM bounded a deterministic static-analysis full-CI fallback")
Expand Down
14 changes: 12 additions & 2 deletions tests/ci_impact/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,13 +564,23 @@ def test_llm_can_escalate_to_full_ci() -> None:
assert all(stage["enabled"] for stage in merged.stages.values())


def test_llm_can_refine_static_analysis_full_plan() -> None:
@pytest.mark.parametrize(
"reason",
[
"unsafe static analysis for QEfficient/base/modeling_qeff.py",
"unclassified production/configuration changes",
"unparsable model inventory: tests/configs/models.json",
"production changes produced no confident deterministic test matches",
"dependency closure reaches every Jenkins stage",
],
)
def test_llm_can_refine_deterministic_full_plan(reason: str) -> None:
deterministic = ImpactPlan(
mode="full",
base="base",
head="head",
changed_files=["QEfficient/base/modeling_qeff.py"],
reasons=["unsafe static analysis for QEfficient/base/modeling_qeff.py"],
reasons=[reason],
unresolved=["QEFFBaseModel: dynamic reflection"],
stages=_stage_plans(enabled=True),
)
Expand Down
Loading