diff --git a/.jules/bolt.md b/.jules/bolt.md index 8bc2589..fbb389b 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -5,3 +5,7 @@ ## 2025-07-17 - Grouped I/O in scripts/init.py **Learning:** Performing multiple consecutive file reads/writes on the same files (`pyproject.toml`, `mkdocs.yml`) causes redundant disk I/O overhead. **Action:** Group file modifications by file path to perform exactly one read and one write operation per file. + +## 2025-07-17 - Batch Git Config Subprocess Invocations in scripts/init.py +**Learning:** Sequential `subprocess` querying of Git configuration parameters (`user.name`, `user.email`, `github.user`) during interactive CLI initialization is slow (~3x overhead). Running a single batch subprocess query via `git config --get-regexp` and caching the result reduces the initialization delay significantly. Additionally, using an initialization flag prevents redundant subprocess calls when all configs are missing (e.g. in empty or CI environments). +**Action:** Cache git configuration queries globally on first use with an explicit initialization flag. diff --git a/AGENTS.md b/AGENTS.md index 525c7e1..a61dc79 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -153,8 +153,9 @@ Every field in a Pydantic model or pydantic-settings class must be documented us from uuid import uuid4 from pydantic import BaseModel, Field + class Item(BaseModel, populate_by_name=True, alias_generator=to_camel): - id: str = Field(description="Unique item identifier.", default_factory=lambda:str(uuid4())) + id: str = Field(description="Unique item identifier.", default_factory=lambda: str(uuid4())) name: str = Field(description="Human-readable item name.") ``` @@ -167,6 +168,7 @@ from uuid import uuid4 from pydantic import BaseModel, Field from pydantic.alias_generators import to_camel + class Item(BaseModel, populate_by_name=True, alias_generator=to_camel): item_id: str = Field(description="Unique item identifier.", default_factory=str(uuid4())) # Accepts {"itemId": "..."} from JSON; attribute is item.item_id @@ -181,8 +183,11 @@ Do not use `model_config = ConfigDict(...)` or `model_config = SettingsConfigDic ```python # Good class Item(BaseModel, extra="allow", populate_by_name=True, alias_generator=to_camel): ... + + class Settings(BaseSettings, case_sensitive=False): ... + # Bad class Item(BaseModel): model_config = ConfigDict(extra="allow") diff --git a/scripts/init.py b/scripts/init.py index dbc5bc3..92a0628 100644 --- a/scripts/init.py +++ b/scripts/init.py @@ -6,12 +6,31 @@ from click import ClickException, UsageError, command, confirm, echo, option, secho +_GIT_CONFIG_CACHE: dict[str, str] = {} +_GIT_CONFIG_INITIALIZED: bool = False + def _get_git_config(key: str) -> str: - try: - return subprocess.check_output(["/usr/bin/git", "config", key], text=True, timeout=5).strip() # noqa: S603 - except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired): - return "" + global _GIT_CONFIG_CACHE, _GIT_CONFIG_INITIALIZED + if not _GIT_CONFIG_INITIALIZED: + _GIT_CONFIG_INITIALIZED = True + try: + # Batch query user/github git config keys in a single subprocess call to optimize initialization startup + res = subprocess.run( + ["/usr/bin/git", "config", "--get-regexp", "^(user\\.name|user\\.email|github\\.user)$"], + capture_output=True, + text=True, + timeout=5, + check=False, + ) # noqa: S603 + if res.returncode == 0: + for line in res.stdout.strip().splitlines(): + if " " in line: + k, v = line.split(" ", 1) + _GIT_CONFIG_CACHE[k] = v.strip() + except (subprocess.SubprocessError, FileNotFoundError): + pass + return _GIT_CONFIG_CACHE.get(key, "") def _get_default_github() -> str: