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 .agents/skills/align-recipe-pyproject/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Runs `scripts/align_pyproject.py` against a recipe directory. Six rules:

1. **Always use the script — never hand-edit `pyproject.toml` or `manifest.yaml` to perform these changes.** The script exists specifically so edits are style-preserving and reviewable via one report.

2. **Ask for the recipe directory** if the user has not provided one. Do not guess. Recipe roots live under `core/python/<name>/`, `contrib/<name>/`, or `skills/python/<name>/`.
2. **Ask for the recipe directory** if the user has not provided one. Do not guess. Recipe roots live under `core/python/<name>/`, `contrib/python/<name>/`, or `skills/<vertical>/<solution>/`.

3. **Always start with `--dry-run`** unless the user has explicitly said "apply", "fix it", "just do it", or equivalent. Show them what would change before doing it.

Expand All @@ -76,7 +76,7 @@ Runs `scripts/align_pyproject.py` against a recipe directory. Six rules:

| Field | Required | Description |
|---|---|---|
| `--recipe-dir` | Yes | Path to the recipe root (e.g. `core/python/cross-session-memory`, `contrib/my-recipe`, `skills/python/my-skill`). |
| `--recipe-dir` | Yes | Path to the recipe root (e.g. `core/python/cross-session-memory`, `contrib/python/my-recipe`, `skills/retail/store-ops`). |
| `--dry-run` | No | Report what would change without modifying any files. |
| `--description-source` | Only when resolving a `description-matches-manifest` mismatch. Values: `pyproject`, `manifest`, `delete`. See below. | Chooses how to reconcile a description mismatch. |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1015,7 +1015,8 @@ def run(
ERROR,
f"{recipe_dir} looks like a git repository root (it contains "
f"a .git entry), not a recipe. Point --recipe-dir at a recipe "
f"root under core/python/<name>/, contrib/<name>/, or skills/python/<name>/.",
f"root under core/python/<name>/, contrib/python/<name>/, or "
f"skills/<vertical>/<solution>/.",
)
)
return report
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ Python source (unchanged behaviour) and WRITES only to `.env.example`.

| Field | Required | Description |
|-------|----------|-------------|
| Recipe directory | Yes | Path to the recipe root (e.g. `contrib/my-recipe`, `core/python/my-recipe`, or `skills/python/my-skill`) |
| Recipe directory | Yes | Path to the recipe root (e.g. `contrib/python/my-recipe`, `core/python/my-recipe`, or `skills/retail/store-ops`) |

If the user has not specified the recipe directory, ask for it before proceeding.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2176,7 +2176,7 @@ def main() -> None:
parser.add_argument(
"--recipe-dir",
required=True,
help="Path to the root of the Python recipe (e.g. contrib/my-recipe)",
help="Path to the root of the Python recipe (e.g. contrib/python/my-recipe)",
)
parser.add_argument(
"--dry-run",
Expand Down
4 changes: 2 additions & 2 deletions .agents/skills/generate-manifest/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Scan a recipe directory and produce a valid `manifest.yaml` for it, then validat

### 1. Identify the target recipe

The user will provide a path to a recipe directory (e.g. `core/rag-agent-search`, `contrib/my-recipe`, `skills/python/my-skill`). If they don't, ask for it before proceeding.
The user will provide a path to a recipe directory (e.g. `core/python/rag-agent-search`, `contrib/python/my-recipe`, `skills/retail/store-ops`). If they don't, ask for it before proceeding.

### 2. Read the schema

Expand Down Expand Up @@ -101,7 +101,7 @@ After writing the manifest, run the validator **from the repo root** (do not `cd
uv run validate manifest <recipe-path>
```

`<recipe-path>` must be **relative to the repo root** (e.g. `core/python/my-recipe`, `contrib/my-recipe`, `skills/python/my-skill`), not an absolute path.
`<recipe-path>` must be **relative to the repo root** (e.g. `core/python/my-recipe`, `contrib/python/my-recipe`, `skills/retail/store-ops`), not an absolute path.

Interpret the result carefully — there are two kinds of failure:

Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/generate-python-runnability-test/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ metadata:

# Generate Python Runnability Test

Use this skill to create the `tests/test_runnability.py` file that every Python recipe under `core/python/`, `contrib/`, or `skills/python/` must ship (see `python-validate-recipe.yml` Check 4). The generated test is deliberately minimal — it just verifies the agent module imports and defines the expected globals. Business-logic testing lives elsewhere.
Use this skill to create the `tests/test_runnability.py` file that every Python recipe under `core/python/`, `contrib/python/`, or `skills/<vertical>/<solution>/` must ship (see `python-validate-recipe.yml` Check 4). The generated test is deliberately minimal — it just verifies the agent module imports and defines the expected globals. Business-logic testing lives elsewhere.

---

Expand Down Expand Up @@ -60,7 +60,7 @@ Runs `scripts/generate_runnability_test.py` against a recipe directory. Steps:

1. **Always use the script — never hand-write `tests/test_runnability.py` yourself.** The skill exists to keep the boilerplate consistent across recipes.

2. **Ask for the recipe directory** if the user hasn't given one. Recipe roots live under `core/python/<name>/`, `contrib/<name>/`, or `skills/python/<name>/`.
2. **Ask for the recipe directory** if the user hasn't given one. Recipe roots live under `core/python/<name>/`, `contrib/python/<name>/`, or `skills/<vertical>/<solution>/`.

3. **Always start with `--dry-run`** unless the user has explicitly said "apply", "generate it", "just do it", or equivalent. Show them what would land before writing.

Expand All @@ -86,7 +86,7 @@ Runs `scripts/generate_runnability_test.py` against a recipe directory. Steps:

| Field | Required | Description |
|---|---|---|
| `--recipe-dir` | Yes | Path to the recipe root (e.g. `core/python/cross-session-memory`, `contrib/my-recipe`, `skills/python/my-skill`). |
| `--recipe-dir` | Yes | Path to the recipe root (e.g. `core/python/cross-session-memory`, `contrib/python/my-recipe`, `skills/retail/store-ops`). |
| `--dry-run` | No | Print the JSON report (with the generated content in `test_content`) without writing any file. |
| `--overwrite` | No | Overwrite an existing `tests/test_runnability.py`. Default: refuse and exit 1. |
| `--agent-file` | No | Override auto-detection of the entry-point file. Path is relative to `--recipe-dir` (or absolute). Use when the recipe uses a non-standard layout (rare — <2% of recipes). |
Expand Down
9 changes: 5 additions & 4 deletions .agents/skills/prepare-python-recipe/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
name: prepare-python-recipe
description: >
End-to-end orchestration to prepare or update a Python recipe under
core/python/, contrib/, or skills/python/ so it passes every check in
.github/workflows/python-validate-recipe.yml. Runs seven phases in
core/python/, contrib/python/, or skills/<vertical>/<solution>/ so it
passes every check in .github/workflows/python-validate-recipe.yml.
Runs seven phases in
order on an already-in-place recipe: manifest.yaml generation,
environment-variable extraction, pyproject.toml alignment, ruff
format+check, per-recipe `uv lock`, runnability-test generation, and a
Expand Down Expand Up @@ -55,7 +56,7 @@ The skill assumes the user has already:
1. **Deactivated** any active Python virtual environment.
2. **Pulled latest** from `origin` (`git pull` at the repo root).
3. **Synced repo root deps** (`uv sync` at the repo root).
4. **Placed the recipe at its target path** — either freshly scaffolded, moved from another location, or renamed to its final basename under `core/python/<name>/`, `contrib/<name>/`, or `skills/python/<name>/`.
4. **Placed the recipe at its target path** — either freshly scaffolded, moved from another location, or renamed to its final basename under `core/python/<name>/`, `contrib/python/<name>/`, or `skills/<vertical>/<solution>/`.
5. **Committed the original recipe** so `git diff` shows what the skill changed.

If the user has NOT done these and asks you to run the skill anyway, tell them to complete the prerequisites first and stop. Do NOT run `git pull`, `git commit`, deactivate their venv, or move/rename directories on their behalf — those are deliberately out of scope.
Expand Down Expand Up @@ -124,7 +125,7 @@ At the end, print a summary table and remind the user to `git diff` and commit

| Field | Required | Description |
|---|---|---|
| Recipe directory | Yes | Path to the recipe root (e.g. `core/python/cross-session-memory`, `contrib/my-recipe`, `skills/python/my-skill`). Passed to every sub-script as `--recipe-dir`. |
| Recipe directory | Yes | Path to the recipe root (e.g. `core/python/cross-session-memory`, `contrib/python/my-recipe`, `skills/retail/store-ops`). Passed to every sub-script as `--recipe-dir`. |

If the user has not specified the recipe directory, ask for it before proceeding.

Expand Down
9 changes: 6 additions & 3 deletions .agents/skills/scaffold-python-recipe/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ You **must** have both pieces of information below before running the script. If

### 1. Output Directory (Required — must ask if not provided)

The user must choose one of these two valid locations:
- `contrib/`
- `core/python/`
The user must choose one of these valid locations:
- `contrib/python/` — community recipes; preferred for new Python recipes
- `core/python/` — curated recipes
- `contrib/` — legacy flat layout, still accepted; prefer `contrib/python/`

If the user has not specified which directory, ask them to choose. Do not proceed until a valid choice is confirmed.

This skill **cannot** scaffold a vertical skill (`skills/<vertical>/<solution>/`). Those have a different shape — `SKILL.md`, `EVAL.yaml`, `scripts/`, `assets/`, `references/`, `tests/unit/` — and no template ships for them yet. If the user asks for one, tell them so rather than scaffolding into the wrong place.

### 2. Recipe Name (Required — must ask if not provided)

The recipe name must satisfy **all** of the following rules:
Expand Down
32 changes: 25 additions & 7 deletions .agents/skills/scaffold-python-recipe/scripts/scaffold.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,32 @@ def is_safe_output_dir(output_dir: str) -> bool:

Blocks relative escapes like ``../../other-repo`` for any caller. Absolute
destinations are allowed here so the function stays usable as a testable
primitive; the CLI additionally restricts ``--output-dir`` to the two
documented locations (see ``__main__``).
primitive; the CLI additionally restricts ``--output-dir`` to the
documented locations (see ``ALLOWED_OUTPUT_DIRS``).
"""
return ".." not in re.split(r"[\\/]", output_dir)


# Locations the CLI will scaffold into. `contrib` (flat) stays accepted
# because recipes already live there, but `contrib/python` is the documented
# home for new Python recipes.
#
# Vertical skills (skills/<vertical>/<solution>) are deliberately absent:
# they need a different template — SKILL.md, EVAL.yaml, scripts/, assets/,
# references/, tests/unit/ — which this skill does not ship. Refusing is
# better than scaffolding a recipe-shaped tree into a skills path.
ALLOWED_OUTPUT_DIRS = ("contrib", "contrib/python", "core/python")


def is_allowed_output_dir(output_dir: str) -> bool:
"""Return True if ``output_dir`` is one of the documented locations.

Trailing slashes are tolerated to match the paths shown in the skill's
instructions.
"""
return output_dir.rstrip("/") in ALLOWED_OUTPUT_DIRS


def replace_in_file(filepath: str, replacements: dict[str, str]) -> None:
"""Reads a file, replaces placeholder tokens, and writes it back."""
with open(filepath, encoding="utf-8") as f:
Expand Down Expand Up @@ -175,13 +195,11 @@ def scaffold(

# Enforce the documented, valid locations (SKILL.md) at the CLI so a direct
# invocation can't scaffold outside the repo (e.g. --output-dir
# ../../other-repo) or into an unintended location. Trailing slashes are
# tolerated to match the paths shown in the skill's instructions.
allowed_output_dirs = ("contrib", "core/python")
if args.output_dir.rstrip("/") not in allowed_output_dirs:
# ../../other-repo) or into an unintended location.
if not is_allowed_output_dir(args.output_dir):
print(
"Error: --output-dir must be one of "
f"{allowed_output_dirs} (got '{args.output_dir}')."
f"{ALLOWED_OUTPUT_DIRS} (got '{args.output_dir}')."
)
sys.exit(2)

Expand Down
53 changes: 53 additions & 0 deletions .agents/skills/scaffold-python-recipe/tests/test_scaffold.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,3 +272,56 @@ def test_scaffold_omits_cache_junk(tmp_path, templates_with_junk):
if p.name in {".ruff_cache", "__pycache__"} or p.suffix == ".pyc"
]
assert leaked == []


# ---------------------------------------------------------------------------
# is_allowed_output_dir / CLI --output-dir allow-list
# ---------------------------------------------------------------------------


@pytest.mark.parametrize(
"output_dir",
[
"contrib",
"contrib/python",
"core/python",
"contrib/python/",
"core/python/",
],
)
def test_allows_documented_output_dirs(output_dir):
"""contrib/python is the documented home for new Python recipes. It was
previously rejected, so scaffolding into it was impossible."""
assert m.is_allowed_output_dir(output_dir) is True


@pytest.mark.parametrize(
"output_dir",
[
# Vertical skills need a template this skill does not ship — SKILL.md,
# EVAL.yaml, scripts/, assets/, references/, tests/unit/ — so they must
# be refused rather than filled with a recipe-shaped tree.
"skills",
"skills/retail",
"skills/retail/store-ops",
# Retired roots and other stray locations.
"python/agents",
"core",
"docs",
"../../other-repo",
"",
],
)
def test_rejects_everything_else(output_dir):
assert m.is_allowed_output_dir(output_dir) is False


def test_allow_list_matches_the_skill_docs():
"""SKILL.md tells the assistant which locations to offer; drift between
the prose and the check would send contributors down a dead end."""
skill_md = (Path(__file__).resolve().parents[1] / "SKILL.md").read_text(
encoding="utf-8"
)
for allowed in m.ALLOWED_OUTPUT_DIRS:
assert f"`{allowed}/`" in skill_md, allowed
assert "skills/<vertical>/<solution>/" in skill_md
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@
/contrib/java/** @happyhuman
/contrib/typescript/** @happyhuman
/contrib/kotlin/** @happyhuman

# --- skills/ ---
/skills/** @happyhuman
36 changes: 31 additions & 5 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# AUTO-GENERATED — do not edit by hand.
# Owned by .github/scripts/generate_dependabot.py
# GENERATED FILE — every line below is written by
# .github/scripts/generate_dependabot.py. Do not edit it by hand.
#
# This file is regenerated automatically on every push to main that touches
# core/, contrib/, or skills/. To force an immediate refresh, trigger the
# "Sync Dependabot Config" workflow manually from the Actions tab, or run:
# Nothing triggers that script for you — there is no push hook, schedule, or
# bot that keeps this in sync. Whoever adds or removes a recipe has to run it
# and commit the result in the same pull request:
#
# python .github/scripts/generate_dependabot.py
#
# CI enforces this — the "Check dependabot.yml is up to date" job fails the
# PR if this file no longer matches the recipe tree.
#
# Ecosystems tracked: uv (Python), gomod (Go), maven (Java),
# gradle (Kotlin), npm (TypeScript/JS), github-actions

Expand Down Expand Up @@ -58,6 +61,29 @@ updates:
patterns:
- "*"

- package-ecosystem: "uv"
directory: "/contrib/python/market-research-agent"
schedule:
interval: weekly
day: monday
time: "02:00"
commit-message:
prefix: chore
include: scope
labels:
- dependencies
open-pull-requests-limit: 1
# Wait a few days after a release before opening a PR, so the community
# catches broken releases before we auto-merge them. Security updates
# bypass this cooldown and fire immediately.
cooldown:
default-days: 7
semver-major-days: 14
groups:
all-dependencies:
patterns:
- "*"

- package-ecosystem: "uv"
directory: "/core/python/ambient-expense-agent"
schedule:
Expand Down
Loading
Loading