Skip to content

feat: configurable default language via env var and ~/.askcc/config.toml #92

@monkut

Description

@monkut

Problem

The output language for agent comments is currently configurable only via the -l/--language CLI flag (askcc/cli.py:122-128), which defaults to english. Users who consistently want a non-default language (e.g. Japanese) must pass --language japanese on every invocation.

There is no environment variable and no persistent user-level config for this.

Expected behaviour

The default language should be resolvable from any of the following sources, with the first match winning:

  1. CLI flag-l/--language <lang> (highest precedence; existing behaviour).
  2. Environment variableASKCC_LANGUAGE (e.g. export ASKCC_LANGUAGE=japanese).
  3. User config file~/.askcc/config.toml with a [defaults] section, e.g.:
    [defaults]
    language = "japanese"
    (Resolved relative to ASKCC_HOME, defaulting to ~/.askcc, so it matches the existing convention used by templates/ and logs/.)
  4. Built-in defaultenglish (lowest precedence; preserves current behaviour for unconfigured users).

Invalid values (not in SupportedLanguage) should log a warning and fall back to the next precedence level — same pattern as _resolve_effort_level() in askcc/settings.py:50-63.

Actual behaviour

askcc/cli.py:126 hardcodes default=SupportedLanguage.ENGLISH. There is no env var lookup and no user-config-file lookup. settings.py defines ASKCC_HOME (askcc/settings.py:86) but does not load any config file from it.

Implementation Plan

Summary of current state

  • askcc/definitions.py:633-635 defines the SupportedLanguage StrEnum (english, japanese).
  • askcc/cli.py:122-128 exposes the -l/--language argparse flag with hardcoded default=SupportedLanguage.ENGLISH.
  • askcc/cli.py:280-281 appends Output all comments in <lang>. to the prompt when the resolved language is not English.
  • askcc/settings.py:39-66 defines VALID_EFFORT_LEVELS and _resolve_effort_level() — the warn-and-fall-through pattern this work mirrors. The note at askcc/settings.py:35-37 documents that the enum lives in settings.py (not definitions.py) to avoid a circular import, since definitions.py:4 already does from askcc.settings import DECISION_ISSUE_LABEL.
  • askcc/settings.py:86 defines ASKCC_HOME (env-overridable, defaults to ~/.askcc). No config file is loaded from it today.
  • Python is pinned to >=3.14,<3.15 (pyproject.toml:8), so tomllib is available in the stdlib — no new third-party dependency required.

Step-by-step tasks

  1. Move SupportedLanguage enum from askcc/definitions.py:633-635 to askcc/settings.py (insert near VALID_EFFORT_LEVELS around askcc/settings.py:39).

    • Rationale: settings.py needs to type the new DEFAULT_LANGUAGE constant as SupportedLanguage, but cannot import from definitions.py (circular import — same constraint that already pinned VALID_EFFORT_LEVELS here).
    • Update the import at askcc/cli.py:11 from from .definitions import AgentAction, AgentConfig, SupportedLanguage so that SupportedLanguage comes from .settings (keep AgentAction, AgentConfig from .definitions).
    • Update the matching import in tests/test_askcc.py:13-20.
  2. Add a TOML config-loader helper in askcc/settings.py (after the ASKCC_HOME block at line 86):

    • import tomllib at the top.
    • USER_CONFIG_PATH: Path = ASKCC_HOME / "config.toml".
    • _load_user_config(path: Path | None = None) -> dict:
      • target = path if path is not None else USER_CONFIG_PATH.
      • Return {} if target is not a file (silent no-op — covers the missing-file case).
      • Open with target.open("rb") and tomllib.load. On tomllib.TOMLDecodeError or OSError, log a warning naming the path and return {} (warned no-op).
    • Module-level cache: _USER_CONFIG: dict = _load_user_config() (evaluated at import time, satisfies the "cache the result at module load" requirement).
    • The optional path argument is only there for tests — it lets us point the loader at a tmp_path without reloading the module.
  3. Add _resolve_default_language() in askcc/settings.py (mirror _resolve_effort_level at lines 50-63):

    • Signature: _resolve_default_language(user_config: dict | None = None) -> SupportedLanguage (the optional user_config is for tests; defaults to _USER_CONFIG).
    • Step 1 — env var: os.getenv("ASKCC_LANGUAGE"). If non-empty, try SupportedLanguage(raw). On ValueError log a warning (Invalid ASKCC_LANGUAGE=%r (valid: %s). Ignoring.) and fall through.
    • Step 2 — user config: (user_config or _USER_CONFIG).get("defaults", {}).get("language"). If set, try SupportedLanguage(value). On ValueError log a warning that names USER_CONFIG_PATH and fall through.
    • Step 3 — built-in default: return SupportedLanguage.ENGLISH.
    • Module-level export: DEFAULT_LANGUAGE: SupportedLanguage = _resolve_default_language().
  4. Wire DEFAULT_LANGUAGE into askcc/cli.py:122-128:

    • Change default=SupportedLanguage.ENGLISHdefault=settings.DEFAULT_LANGUAGE.
    • Update the help= string to document the precedence in the same style as --effort (askcc/cli.py:140-142): CLI > env (ASKCC_LANGUAGE) > user config (~/.askcc/config.toml [defaults].language) > built-in default (english).
    • The runtime guard at askcc/cli.py:280 (if args.language != SupportedLanguage.ENGLISH:) keeps working unchanged because SupportedLanguage is a StrEnum and equality is preserved.
  5. Update README.md:

    • Add an ASKCC_LANGUAGE row to the Environment Variables table (README.md:96-101).
    • Add a "User Configuration File" subsection (under "Customizing Prompts" or as its own "Configuration" section) that shows the ~/.askcc/config.toml example and lists the four-level precedence for --language.
  6. Add unit tests in tests/test_askcc.py (new TestDefaultLanguageResolution class, modelled on TestThinkingSettings at lines 1604+):

    • test_default_language_from_env_varmonkeypatch.setenv("ASKCC_LANGUAGE", "japanese"); _resolve_default_language() returns SupportedLanguage.JAPANESE.
    • test_default_language_from_config_file — write a tmp_path/config.toml with [defaults]\nlanguage = "japanese"; _resolve_default_language(_load_user_config(tmp_path/"config.toml")) returns JAPANESE.
    • test_env_var_overrides_config_file — env=japanese, config=english; resolves to JAPANESE.
    • test_config_file_overrides_builtin_default — env unset, config=japanese; resolves to JAPANESE.
    • test_default_language_unset_returns_english — env unset, no config; resolves to ENGLISH.
    • test_invalid_env_value_warns_and_falls_back — env=klingon, config unset; returns ENGLISH and caplog captures Invalid ASKCC_LANGUAGE warning.
    • test_invalid_config_value_warns_and_falls_back — config has language = "klingon"; returns ENGLISH and warning is logged.
    • test_missing_config_file_silent_load_user_config(tmp_path/"missing.toml") returns {} with no warning emitted.
    • test_malformed_toml_warns — write invalid TOML to tmp_path/config.toml; _load_user_config(...) returns {} and logs a warning (does not raise).
    • CLI-flag-overrides-env precedence is enforced by argparse and is implicitly covered by existing CLI tests; one assertion in the new class can confirm explicitly.
  7. Run the verification gate before opening the PR (per DEVELOP_AGENT_PROMPT): uv run pytest, uv run ruff check, uv run pyright. All three must pass.

Files touched

  • askcc/settings.py — add tomllib import, SupportedLanguage enum (moved here), USER_CONFIG_PATH, _load_user_config, _USER_CONFIG, _resolve_default_language, DEFAULT_LANGUAGE.
  • askcc/definitions.py — remove SupportedLanguage (now lives in settings.py).
  • askcc/cli.py — split SupportedLanguage import to .settings; switch argparse default to settings.DEFAULT_LANGUAGE; update help text.
  • tests/test_askcc.py — update SupportedLanguage import path; add TestDefaultLanguageResolution.
  • README.md — document ASKCC_LANGUAGE and ~/.askcc/config.toml.

Risks / open questions

  • Moving SupportedLanguage to settings.py is an internal-import-path change. The two known call sites (askcc/cli.py:11, tests/test_askcc.py:19) are both updated in this PR. The package is marked Private :: Do Not Upload (pyproject.toml:11) so there are no external consumers — public API impact is nil. Flagged so reviewers don't need to look it up.
  • Cache lifetime_USER_CONFIG is computed once at module import. Tests inject a config dict via the optional user_config argument rather than reloading the module; production callers never need to invalidate the cache mid-run.
  • TOML parse-error logging — on malformed TOML the warning includes the path but not the exception detail (avoids accidentally echoing config contents into logs). If the maintainer prefers exception detail surfaced, that is a one-line change. Minor judgment call; default chosen for safety.

Acceptance Criteria

  • ASKCC_LANGUAGE environment variable sets the default language
  • ~/.askcc/config.toml [defaults] language sets the default language
  • Resolution order is CLI flag > env var > user config > built-in default
  • ASKCC_HOME is honored when locating the user config file
  • Invalid language values log a warning and fall back gracefully
  • Missing/malformed config files do not crash the CLI
  • Unit tests cover all four precedence levels and error cases
  • README documents the new configuration mechanism
  • uv run pytest, uv run ruff check, and uv run pyright all pass

Dependencies

None identified. Python 3.14 is already pinned (pyproject.toml:8), so tomllib is available in the stdlib — no new third-party dependencies are introduced. The SupportedLanguage enum relocation is internal-only.

Out of scope

  • Generalizing other settings (e.g. effort_level, runner) to read from the same config file. That is a natural follow-up but should be its own issue once the config-loader pattern is in place.

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions