Skip to content

Add keyword filtering to agentseek create --list-templates - #173

Merged
webup merged 5 commits into
ob-labs:mainfrom
popwatt:feat/create-template-filter
Aug 11, 2026
Merged

Add keyword filtering to agentseek create --list-templates#173
webup merged 5 commits into
ob-labs:mainfrom
popwatt:feat/create-template-filter

Conversation

@popwatt

@popwatt popwatt commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Narrow safety fixes on top of current main, so listing/filtering never executes Cookiecutter:

  1. --filter outside listing mode is rejectedcreate bub/default --filter x --no-input exits 2 instead of silently generating a project.
  2. Direct sources cannot be combined with listing modecreate <url-or-absolute-path> --list-templates (with or without --filter) and the bare --template form now exit 2 with Listing templates cannot be combined with a direct template source (URL or absolute path). instead of dispatching to Cookiecutter passthrough.

Background

Rebased onto current main; the filter feature itself (already merged via #111) is untouched. This PR keeps only the safety corrections requested in review.

Regressions added

  • test_filter_without_list_mode_is_rejected--filter without listing flags: exit 2, zero Cookiecutter calls.
  • test_list_templates_with_url_spec_rejects_without_cookiecutter — URL + --list-templates --filter: exit 2, zero Cookiecutter calls.
  • test_list_templates_with_absolute_path_spec_rejects_without_cookiecutter — absolute path + --list-templates --filter: exit 2, zero Cookiecutter calls.
  • test_template_flag_no_value_with_url_spec_rejects_without_cookiecutter — bare --template + URL: exit 2, zero Cookiecutter calls.
  • test_create_with_url_spec_and_template_value_passes_directory — URL + --template <value> still passes directory to Cookiecutter (guards the sentinel-vs-value distinction).

Verification

  • uv run python -m pytest tests/cli_commands/test_create.py — 117 passed, 6 skipped
  • uv run ruff check / uv run ty check / uv lock --locked / git diff --check — pass

@popwatt

popwatt commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

#86

@webup webup 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.

Thanks for the contribution. I’m requesting changes at exact head 55fbefc09431063ad74f5e67443f451242e1085d for two merge blockers:

  1. This branch is based on d181808d, the commit immediately before PR #111. The requested filter feature, documentation, and focused tests were already merged by #111 (c074ad63) and remain on current main. This head is 36 commits behind and conflicts in all six touched files; resolving against current main leaves no substantive feature delta. Please rebase and remove the duplicate work; closing this PR as a duplicate is appropriate unless you keep only the narrow safety correction below.
  2. --filter is accepted outside listing mode and then silently ignored, so a missing --list-templates can generate a project instead of failing. The inline comment includes the exact reproduction and required regression.

Verification on this head: the 32 focused create tests, 98-test full suite, Ruff, ty, strict MkDocs build, and git diff --check pass. Current main’s five filter tests also pass, confirming the feature is already present. GitHub reports this PR as CONFLICTING / DIRTY with no hosted checks.

action="store_true",
help="List templates available for the chosen type and exit.",
)
parser.add_argument(

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.

--filter is registered for every create path, but args.filter is only consumed in the list branch. At this exact head, create bub/default --filter definitely-does-not-match --no-input exits 0 and invokes _run_cookiecutter once for bub/default in the current directory; before this commit, argparse rejects the unknown option with exit 2. Please reject --filter unless list mode is active (or deliberately make it imply listing), and add a non-list regression so omitting --list-templates cannot trigger project generation.

@popwatt
popwatt force-pushed the feat/create-template-filter branch from 55fbefc to 89a40ec Compare August 11, 2026 12:57
@popwatt

popwatt commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review — I've reworked the PR per your feedback.

  1. Duplicate work removed. The branch is now rebased on current main (595b1bd); the earlier re-implementation of the filter feature (duplicate of Add keyword filtering for create template listings #111) is gone. The diff is now only the narrow safety correction below.

  2. --filter outside listing mode now fails. create() rejects --filter unless --list-templates or --template (with no value) is active, exiting 2 with:

    --filter requires --list-templates (or --template without a value).
    

    The new regression test test_filter_without_list_mode_is_rejected asserts exit code 2 and that cookiecutter is never invoked for create bub/default --filter definitely-does-not-match --no-input.

@webup webup 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.

Thanks for rebasing onto current main, removing the duplicate #111 implementation, and fixing the original named-template reproduction. One merge-blocking dispatch bypass remains at exact head 89a40ecc137be57d5aaa05b5bdb07acc251f4b66.

The new guard trusts the listing flags, but direct URL/absolute-path specs are dispatched to _handle_external_spec() before the actual listing branch. As a result, create https://example.com/template.git --list-templates --filter needle --no-input still exits 0 and invokes Cookiecutter once; the same occurs with an absolute path and with bare --template. This contradicts the documented contract that listing/filtering do not execute Cookiecutter hooks. Please resolve listing mode before external passthrough or reject direct sources combined with listing/filter flags, and add URL/absolute-path regressions proving Cookiecutter is never invoked.

Everything else revalidated cleanly: the PR is now one commit over main, git diff --check and uv lock --check pass, filter tests are 6/6, the complete create suite is 118 passed / 1 skipped, the full suite is 1144 passed / 1 skipped, Ruff/format/ty pass, and all hosted checks are green. GitHub reports MERGEABLE / CLEAN.

Comment thread src/agentseek/cli/commands/create.py Outdated
"""Scaffold a new agent project from a pre-built template."""
args = _parse_new_args(ctx)
# --- --filter is only meaningful when listing templates ---
if args.filter is not None and not (args.list_templates or args.template == _TEMPLATE_LIST_SENTINEL):

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.

This condition checks for listing flags, but not whether control flow will actually enter the listing branch. For a direct source, e.g. create https://example.com/template.git --list-templates --filter needle --no-input, the guard passes and the external-spec branch below invokes _run_cookiecutter before reaching the list branch (exit 0, one generation call in a mocked reproduction). Absolute paths and bare --template --filter behave the same way. Please reject these direct-source combinations or route listing before external passthrough, with a regression asserting exit 2 and zero Cookiecutter calls.

@popwatt

popwatt commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for catching the remaining dispatch bypass — fixed in ac90337.

The bypass: direct URL/absolute-path specs were dispatched to _handle_external_spec() before the listing branch, so create https://example.com/template.git --list-templates --filter needle --no-input exited 0 and invoked Cookiecutter once (reproduced locally: both URL and absolute-path variants).

The fix: create() now computes listing_mode once (--list-templates or bare --template) and the external-passthrough branch rejects that combination before touching Cookiecutter:

Listing templates cannot be combined with a direct template source (URL or absolute path).

This also covers the no---filter case (--list-templates alone) and bare --template with a direct source. Direct sources without listing flags are unaffected (existing test_create_with_url_spec_passes_through still passes).

Regressions added:

  • test_list_templates_with_url_spec_rejects_without_cookiecutter — exit 2, zero Cookiecutter calls
  • test_list_templates_with_absolute_path_spec_rejects_without_cookiecutter — exit 2, zero Cookiecutter calls

Verification on this head: create suite 115 passed / 6 skipped, full suite 1094 passed / 53 skipped, Ruff/ty/uv lock --locked/git diff --check all pass.

@popwatt

popwatt commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Added two more regressions to lock the listing-mode boundary (self-review pass):

  • test_template_flag_no_value_with_url_spec_rejects_without_cookiecutter — bare --template + URL also exits 2 with zero Cookiecutter calls (sentinel branch).
  • test_create_with_url_spec_and_template_value_passes_directory — URL + --template <value> still reaches Cookiecutter with directory set, so the fix's sentinel-vs-value distinction can never silently regress.

Head is now 3dfa84e; create suite 117 passed / 6 skipped.

@webup webup 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.

Thanks for fixing the direct-source dispatch bypass and adding the focused regressions. I revalidated exact head 3dfa84e8893baca169e8315822d7297f42bb7895: the original URL, absolute-path, and bare---template cases now exit 2 without invoking Cookiecutter. Two merge blockers remain. First, the string listing sentinel now collides with a valid direct-source directory value, regressing passthrough for --template __list__. Second, the changed production file fails the repository's Ruff format gate. The inline comments include exact reproductions and requested regressions.

Everything else is clean: the complete create suite is 122 passed / 1 skipped, the full suite is 1148 passed / 1 skipped, Ruff lint, ty, lock checks, and git diff --check pass, and the worktree is clean. GitHub reports MERGEABLE / UNSTABLE; no hosted checks are reported for this head yet.

Comment thread src/agentseek/cli/commands/create.py Outdated
def create(ctx: typer.Context) -> None:
"""Scaffold a new agent project from a pre-built template."""
args = _parse_new_args(ctx)
listing_mode = args.list_templates or args.template == _TEMPLATE_LIST_SENTINEL

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.

[P2] Avoid colliding with a valid external directory value. _TEMPLATE_LIST_SENTINEL is the user-enterable string __list__, so this comparison cannot distinguish bare --template from --template __list__. At this head, create https://example.com/template.git --template __list__ --no-input exits 2 with zero Cookiecutter calls; at parent 89a40ecc, external dispatch ran first and forwarded directory='__list__'. This newly regresses the promised --template <value> passthrough for direct sources. Please use a non-colliding representation (or preserve option arity) and add a regression proving an explicit __list__ directory is still forwarded.

# --- External spec (URL or absolute path) → passthrough to cookiecutter ---
if args.spec and _is_external_spec(args.spec):
if listing_mode:
typer.echo(

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.

[P1] Restore the repository formatting gate. uv run --locked ruff format --check src tests contrib scripts exits 1 because Ruff joins these adjacent literals into one line. make lint runs pre-commit's ruff-format hook, so this head cannot pass that gate. Please run the formatter and push the result.

@popwatt

popwatt commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

CI quality job failed on head ac90337 (make lint typecheck). Root cause was the ruff-format pre-commit hook: the multi-line error string in create() fits within the 120-char line length, so ruff collapses it to one line, and pre-commit exits non-zero when a hook reformats files.

Fixed in 75230be (style-only, no behavior change): the string is now on a single line.

Verified locally with the exact CI commands:

  • uv run pre-commit run -a — all hooks pass (incl. ruff-format)
  • uv run ty check src tests — All checks passed!
  • uv run python -m pytest tests/cli_commands/test_create.py — 117 passed, 6 skipped
  • git diff --check — clean

Head is now 75230be; the branch stays 2 files changed (+136/-2) vs main.

@popwatt

popwatt commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — both merge blockers are addressed. Head is now b30dc69.

P1 (format gate) — already restored in 75230be (the string collapse fix). Verified with the exact command on the current head:

uv run --locked ruff format --check src tests contrib scripts
96 files already formatted

P2 (sentinel collision) — fixed in b30dc69. _TEMPLATE_LIST_SENTINEL is no longer the user-enterable string "__list__"; it is now a plain sentinel object, and listing_mode compares with is instead of ==. So --template __list__ is an ordinary value again and is forwarded as the Cookiecutter directory for direct sources:

  • create https://github.com/foo/bar.git --template __list__ --no-input → exit 0, source.directory == "__list__"
  • bare --template (no value) still means list mode: create <url> --template → exit 2, zero Cookiecutter calls

Regression added: test_create_with_url_spec_and_literal_list_template_value_passes_directory asserts the literal __list__ directory is forwarded to Cookiecutter.

Verification on this head: create suite 118 passed / 6 skipped, pre-commit all hooks pass, ruff format --check src tests contrib scripts clean, ty check src tests clean, uv lock --locked and git diff --check pass.

@webup webup 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.

Approved at exact head b30dc69f335a3656795d1b1bc4b5f8aa9ace7169. Both requested fixes are complete: 75230be restores the Ruff-format gate, and b30dc69 replaces the colliding string sentinel with object identity while preserving literal --template __list__ as a direct-source directory. The original URL/absolute-path listing and non-list --filter safety cases still exit 2 with zero Cookiecutter calls; ordinary and valued direct-source generation still pass through.

Fresh validation: 6 focused regressions pass; the full suite is 1149 passed / 1 skipped; Ruff format reports 96 files formatted; Ruff lint, ty, both lock checks, and git diff --check pass; the checkout is clean. No code blocker remains.

Merge gate: the exact-head Main run 31498731461 and OpenVINO run 31498731215 are both action_required with zero jobs, so they still need authorization and a green result before merge. The immediately preceding 75230be runs were green, but they are not exact-head evidence.

@popwatt

popwatt commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review and approval! Quick heads-up on the merge gate: the exact-head workflow runs are both action_required (zero jobs) and need a maintainer to approve before they can produce a green result:

  • Main: run 31498731461 (head b30dc69)
  • OpenVINO Template Smoke: run 31498731215 (head b30dc69)

Could you approve these when convenient? The preceding 75230be runs were green; these will re-run the same checks on the current head. Happy to adjust anything else if needed.

@webup
webup merged commit 6f3f0dd into ob-labs:main Aug 11, 2026
13 checks passed
@webup

webup commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Thanks for the contribution and for iterating quickly on the review feedback. The safety fixes and focused regression coverage landed cleanly—great work!

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.

2 participants