Skip to content

feat: add retail product search skill and harden the Python recipe tooling skills - #2372

Merged
happyhuman merged 5 commits into
mainfrom
test-skill-delete
Jul 29, 2026
Merged

feat: add retail product search skill and harden the Python recipe tooling skills#2372
happyhuman merged 5 commits into
mainfrom
test-skill-delete

Conversation

@happyhuman

Copy link
Copy Markdown
Collaborator

Summary

Two related pieces of work, developed together because the second was discovered by running the first:

  1. New vertical skillskills/retail/retail-product-search, a semantic product search agent built on Vertex AI Vector Search and BigQuery.
  2. Bug fixes and new checks across four repo skills under .agents/skills/, found by running prepare-python-recipe against that recipe. Several were actively corrupting source files.

Note

AGENTS.md asks that .agents/skills/ changes not share a PR with recipe changes. They are combined here because the recipe is what surfaced the tooling bugs; happy to split if reviewers prefer.

Tooling fixes

extract-python-environment-variables (2.1.0 → 2.2.0)

Bug Impact
_post_header_index only recognised bare """ docstrings Any prefixed docstring (r""", u""") got import os injected above it, demoting it to a dead expression and losing __doc__. Now AST-based.
Trailing-comma insertion was comment-blind Wrote a stray comma inside a trailing comment; where the entry had no comma it produced unclosed-array TOML, which the round-trip guard then rejected — silently skipping the dependency. Now splits off the comment first, quote-aware.
Bare os.getenv("VAR") emitted even when no load_dotenv() bootstrap could be installed Nothing read .env, so the value resolved to None at runtime. run_step_load_dotenv now returns whether a bootstrap exists; without one the original literal is kept as the fallback.
Generic MODEL_NAME claimed too eagerly Misleading in recipes that already read a different model var. Names now derive from the assignment target (DEFAULT_EMBEDDING_MODELEMBEDDING_MODEL).

generate-python-runnability-test (1.0.0 → 1.1.0)

The generated test does import <module>, which only resolves if the recipe root is on sys.path — previously assumed, never checked. A recipe with no [build-system] got a test that always died with ModuleNotFoundError while py_compile still reported success.

New detect_import_support() classifies how the root is reached (installable / pythonpath-ini / existing-conftest / unresolved) and writes a tests/conftest.py shim when nothing else provides it. An existing conftest is never clobbered.

align-recipe-pyproject (1.0.0 → 1.1.0)

Two new report-only checks:

  • stale-python-version-refs — raising requires-python is not self-contained. A bootstrap script whose interpreter allowlist still accepts the old floor will build a venv the recipe then refuses to install into. The scan requires a Python-ish context, so gemini-3.5-flash is not a false positive.
  • runnability-test-in-testpaths — a testpaths of ["tests/unit", "tests/integration"] means a bare pytest never collects the required runnability test.

prepare-python-recipe (1.0.0 → 1.1.0)

  • Phase 0c — preflight for the required_dirs that .github/policy.yml mandates for skills/ recipes (tests/unit/ was missing and no phase created it).
  • Phase 7 — now compiles and runs the test. --collect-only was considered and rejected: the guarded test shape puts the import inside the test function, so collection proves nothing.
  • Phase 8 (new) — runs validate manifest and validate structure. The pipeline previously reported a clean run on a recipe CI then rejected.

Testing

  • 521 passed (pytest .agents/skills tools/tests), up from 204 at baseline.
  • New suites for align-recipe-pyproject and generate-python-runnability-test, which had none.
  • Every bug above was reproduced first, then fixed, then locked in with a regression test.
  • ruff format --check and ruff check clean across .agents/skills.

…ecipe-pyproject and add docstring regression tests to extract-python-environment-variables

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

ruff-formatter

[ruff-formatter] reported by reviewdog 🐶

logger.info("Set VECTOR_SEARCH_COLLECTION env var to this path in your agent.")


[ruff-formatter] reported by reviewdog 🐶

raw_fields = cfg.get("embedding_fields", "name, description, category, brand")


[ruff-formatter] reported by reviewdog 🐶

logger.error(" Validation produced %d errors. First 5:", len(errors))


[ruff-formatter] reported by reviewdog 🐶

parser.add_argument("--config", required=True, help="Path to design-spec.md")


[ruff-formatter] reported by reviewdog 🐶

raise ValueError(f"Unsupported file type: {suffix}. Use .csv, .json, or .jsonl")


[ruff-formatter] reported by reviewdog 🐶

row_errors.append(f"Row {row_num}: missing required field '{field}'")


[ruff-formatter] reported by reviewdog 🐶

row_errors.append(f"Row {row_num}: 'rating' should be 0-5, got {val}")


[ruff-formatter] reported by reviewdog 🐶

row_errors.append(f"Row {row_num}: unrecognized fields: {sorted(unknown_extras)}")


[ruff-formatter] reported by reviewdog 🐶

record, i, required, all_fields, higher_level_fields, seen_warned_fields


[ruff-formatter] reported by reviewdog 🐶

parser = argparse.ArgumentParser(description="Validate product catalog schema")


for field in required:
if field not in record or not record[field]:
row_errors.append(f"Row {row_num}: missing required field '{field}'")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using not record[field] will falsely report a missing field if the value is a numeric 0 or 0.0 (such as a valid free price). Check instead that the value is not None or an empty string.

"""
errors = []
for field in REQUIRED_FIELDS:
if field not in product or not product[field]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using not product[field] will falsely report a missing required field if the value is a numeric 0 or 0.0 (such as a valid free price). Check instead that the value is not None or an empty string.

converted = {}
converted["product_id"] = product.get("product_id", "")
converted["name"] = product.get("name", "")
converted["description"] = product.get("description", "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using and product[field] to check for numeric fields like price, rating, or stock will skip 0 or 0.0 (since they are falsy), causing them to be omitted. This will result in a BigQuery ingestion crash for the required 'price' field, and incorrect null values for rating and stock.

# Try PATH lookup first; fall back to absolute paths for sandboxed
# shells that launch with a stripped PATH.
PYTHON_BIN=""
for py in python3.13 python3.12 python3.11 python3.10 python3; do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the recipe's Python floor is >=3.11 (enforced by the repository's alignment rules), allowing Python 3.10 in the bootstrap script will cause pip install to crash inside the created virtual environment. The bootstrap should restrict the allowed interpreters to Python >=3.11.

@happyhuman
happyhuman merged commit 739bb34 into main Jul 29, 2026
20 checks passed
@happyhuman
happyhuman deleted the test-skill-delete branch July 31, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant