diff --git a/README.md b/README.md index b73747c3..8c5f9dd3 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,8 @@ generated/ # exclude generated dir !*.cob # include COBOL files ``` +Run `semble file-types` to see which extensions are indexed per content type. Extensions not listed (including data files such as `.json` and `.csv`) are not indexed by default and can be force-included with a `!` pattern as above. + Semble also always skips a set of well-known non-source directories regardless of ignore files (e.g. `node_modules/`, `.venv/`, `dist/`, `build/`, `__pycache__/`, and similar). diff --git a/src/semble/cli.py b/src/semble/cli.py index 94e7123c..60e7e714 100644 --- a/src/semble/cli.py +++ b/src/semble/cli.py @@ -18,6 +18,7 @@ from semble.cache import cache_key, resolve_cache_folder, save_index_to_cache from semble.index import SembleIndex +from semble.index.files import get_extensions from semble.index.types import PersistencePath from semble.installer.agents import AGENTS, IntegrationType from semble.stats import format_savings_report @@ -26,7 +27,19 @@ from semble.version import __version__ _CLI_DISPATCH_ARGS = frozenset( - {"search", "find-related", "install", "uninstall", "savings", "-h", "--help", "clear", "--version", "-V"} + { + "search", + "find-related", + "file-types", + "install", + "uninstall", + "savings", + "-h", + "--help", + "clear", + "--version", + "-V", + } ) _CLEAR_CHOICE = Literal["all", "index", "savings", "orphans"] @@ -292,6 +305,16 @@ def _cli_main() -> None: related_p.add_argument("line", type=int, help="Line number (1-indexed).") _add_query_args(related_p) + file_types_p = sub.add_parser("file-types", help="List the file extensions indexed per content type.") + file_types_p.add_argument( + "--content", + nargs="+", + default=["all"], + choices=[ct.value for ct in ContentType] + ["all"], + metavar="TYPE", + help="Content types to list (space-separated). Choices: code, docs, config, all. Default: all.", + ) + sub.add_parser("savings", help="Show token savings and usage stats.") install_p = sub.add_parser("install", help="Configure semble across coding agents.") @@ -332,6 +355,8 @@ def _cli_main() -> None: run(args.command, agent_ids=args.agent, integration_ids=integration_ids, yes=args.yes) elif args.command == "clear": _run_clear(args.type) + elif args.command == "file-types": + print(json.dumps({ct.value: get_extensions([ct]) for ct in _resolve_content(args.content, False)})) elif args.command == "search": _run_search( args.path, diff --git a/src/semble/version.py b/src/semble/version.py index 0a1957c5..1f5fd03f 100644 --- a/src/semble/version.py +++ b/src/semble/version.py @@ -1,2 +1,2 @@ -__version_triple__ = (0, 6, 2) +__version_triple__ = (0, 7, 0) __version__ = ".".join(map(str, __version_triple__)) diff --git a/tests/test_cli.py b/tests/test_cli.py index 3a581ed1..9161a2f3 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -478,3 +478,22 @@ def test_cli_clear_command( assert not (tmp_path / sha).exists() if setup_savings: assert not savings_file.exists() + + +@pytest.mark.parametrize( + ("content", "expected"), + [ + ([], {"code": ".py", "docs": ".md", "config": ".toml"}), + (["--content", "docs"], {"docs": ".md"}), + ], +) +def test_cli_file_types( + content: list[str], expected: dict[str, str], monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + """`semble file-types` prints the indexed extensions per requested content type.""" + monkeypatch.setattr(sys, "argv", ["semble", "file-types", *content]) + main() + out = json.loads(capsys.readouterr().out) + assert list(out) == list(expected) + assert all(extension in out[content_type] for content_type, extension in expected.items()) + assert all(".json" not in extensions for extensions in out.values())