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
4 changes: 2 additions & 2 deletions config/template-drift-allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ reason = Intentional divergence re-baselined 2026-06-30: root and consumer guard
main = .github/workflows/agents-issue-optimizer.yml
template = templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml
main_sha256 = 77eecd57e1a7637628bc16faedb6ef50f8cb5a810f94e286eaffd8dc50e02409
template_sha256 = a93f1a86ee6396740647fc4e923297aa3d6224bcec3f791592258d2710776d6b
reason = Intentional divergence re-baselined 2026-07-07: root and consumer issue-optimizer workflows keep different auth plumbing/action pin surfaces, and the consumer template installs LLM deps from checked-out workflows-scripts/tools/requirements-llm.txt with python -m pip. Do not align wholesale because that would strip consumer action pins/token setup.
template_sha256 = a5f02fc1c8909466a254e4ec4ab85819644dd8a9008522e4a232f426c67e6435
reason = Intentional divergence re-baselined 2026-07-31: root and consumer issue-optimizer workflows keep different auth plumbing/action pin surfaces, and the consumer template installs LLM deps from checked-out workflows-scripts/tools/requirements-llm.txt with python -m pip. The template additionally sparse-checks out `config` alongside `scripts/langchain` and `tools`, which the root workflow does not need because it runs in-tree where config/ is already present; without it tools/llm_registry.py resolves no model registry in the vendored tree (see #2850, #2861). Do not align wholesale because that would strip consumer action pins/token setup.

[pair.12]
main = .github/workflows/agents-keepalive-loop-reporter.yml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ jobs:
repository: stranske/Workflows
token: ${{ secrets.SERVICE_BOT_PAT || github.token }}
sparse-checkout: |
config
Comment thread
coderabbitai[bot] marked this conversation as resolved.
scripts/langchain
tools
.github/scripts/error_classifier.js
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ jobs:
repository: stranske/Workflows
path: workflows-scripts
sparse-checkout: |
config
scripts/langchain
tools
sparse-checkout-cone-mode: false
Expand Down
35 changes: 27 additions & 8 deletions tests/workflows/test_workflow_llm_installs.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,19 @@
LLM_CONFIG_PATHS = (Path("config/llm_slots.json"), Path("config/model_registry.json"))
LANGCHAIN_ENTRYPOINT_DIR = "scripts/langchain"
VERIFY_TO_NEW_PR = WORKFLOWS_DIR / "agents-verify-to-new-pr.yml"
KNOWN_LLM_CLIENT_WORKFLOWS = (VERIFIER, VERIFY_TO_ISSUE, VERIFY_TO_NEW_PR)
CONSUMER_WORKFLOWS_DIR = Path("templates/consumer-repo/.github/workflows")
CONSUMER_VERIFY_TO_NEW_PR = CONSUMER_WORKFLOWS_DIR / "agents-verify-to-new-pr.yml"
CONSUMER_ISSUE_OPTIMIZER = CONSUMER_WORKFLOWS_DIR / "agents-issue-optimizer.yml"
CONSUMER_PR_EVENT_HUB = CONSUMER_WORKFLOWS_DIR / "agents-80-pr-event-hub.yml"
LLM_CLIENT_WORKFLOW_DIRS = (WORKFLOWS_DIR, CONSUMER_WORKFLOWS_DIR)
KNOWN_LLM_CLIENT_WORKFLOWS = (
VERIFIER,
VERIFY_TO_ISSUE,
VERIFY_TO_NEW_PR,
CONSUMER_VERIFY_TO_NEW_PR,
CONSUMER_ISSUE_OPTIMIZER,
CONSUMER_PR_EVENT_HUB,
)
Comment thread
stranske marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.


def _load_text(path: Path) -> str:
Expand Down Expand Up @@ -114,10 +126,17 @@ def _discover_llm_client_workflows() -> list[Path]:
"""Workflows that vendor `tools` and then run the LangChain client from it.

Discovery rather than an explicit list: any future workflow that vendors the
client inherits the config-vendoring requirement automatically.
client inherits the config-vendoring requirement automatically. The consumer
template directory is scanned too, because those workflows are synced into
the consumer repos and vendor the same client from the same remote tree.
"""
candidates: list[Path] = []
for directory in LLM_CLIENT_WORKFLOW_DIRS:
candidates.extend(sorted(directory.glob("*.yml")))
candidates.extend(sorted(directory.glob("*.yaml")))

matches = []
for path in sorted(WORKFLOWS_DIR.glob("*.yml")) + sorted(WORKFLOWS_DIR.glob("*.yaml")):
for path in candidates:
text = path.read_text(encoding="utf-8")
if LANGCHAIN_ENTRYPOINT_DIR not in text:
continue
Expand Down Expand Up @@ -285,20 +304,20 @@ def test_reusable_agents_verifier_pip_cache_is_configured() -> None:

def test_llm_client_workflow_discovery_covers_the_known_verifier_surfaces() -> None:
discovered = set(_discover_llm_client_workflows())
missing = [path.name for path in KNOWN_LLM_CLIENT_WORKFLOWS if path not in discovered]
missing = [path.as_posix() for path in KNOWN_LLM_CLIENT_WORKFLOWS if path not in discovered]
assert not missing, (
f"Discovery no longer sees {missing}; the config-vendoring guard below would "
"silently stop covering them."
)


@pytest.mark.parametrize(
"workflow_path", _discover_llm_client_workflows(), ids=lambda path: path.name
"workflow_path", _discover_llm_client_workflows(), ids=lambda path: path.as_posix()
)
def test_llm_workflows_vendor_the_model_registry_config(workflow_path: Path) -> None:
workflow = _load_workflow(workflow_path)
checkouts = _workflows_library_checkout_steps(workflow)
assert checkouts, f"{workflow_path.name} must sparse-checkout stranske/Workflows"
assert checkouts, f"{workflow_path.as_posix()} must sparse-checkout stranske/Workflows"

vendors_tools = False
for step in checkouts:
Expand All @@ -308,13 +327,13 @@ def test_llm_workflows_vendor_the_model_registry_config(workflow_path: Path) ->
vendors_tools = True
missing = [entry for entry in ("config",) if entry not in paths]
assert not missing, (
f"{workflow_path.name} vendors `tools` but not {missing}; "
f"{workflow_path.as_posix()} vendors `tools` but not {missing}; "
f"{LLM_REGISTRY_MODULE} resolves {', '.join(str(p) for p in LLM_CONFIG_PATHS)} "
"relative to the vendored tree, so every judge slot resolves to no model and "
'compare mode reports "available families: none".'
)

assert vendors_tools, f"{workflow_path.name} must vendor `tools` for the LLM client"
assert vendors_tools, f"{workflow_path.as_posix()} must vendor `tools` for the LLM client"


def test_bundled_llm_config_resolves_two_cross_family_judges(
Expand Down
Loading