Skip to content

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

Closed

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 variable — ASKCC_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 default — english (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.ENGLISH → default=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_var — monkeypatch.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.

Activity

  1. self-assigned this
    on Apr 27, 2026
  2. monkut commented on Apr 27, 2026

    @monkut
    OwnerAuthor

    Plan summary

    Updated the issue description with a finalized Implementation Plan, Acceptance Criteria (preserved from the original spec), and a Dependencies section. Assigned to @me.

    Sections added/updated in the issue body

    • Implementation Plan — replaces the previous "Suggested fix" section with file/line references, a step-by-step task list, the list of files touched, and risks/open questions.
    • Acceptance Criteria — preserved the original checklist (already well-formed); normalized header capitalization to ## Acceptance Criteria.
    • Dependencies — added; "None identified" (Python 3.14 already pinned, tomllib is stdlib).

    Approach in one paragraph

    Mirror the existing _resolve_effort_level() pattern (askcc/settings.py:50-63): add _load_user_config() (caches a parsed ~/.askcc/config.toml at module load; missing → silent {}, malformed → warned {}) and _resolve_default_language() (env > config > built-in default, warn-and-fall-through on invalid values). Export DEFAULT_LANGUAGE: SupportedLanguage and wire it into the argparse default= at askcc/cli.py:126.

    Risks / open questions flagged for review

    • SupportedLanguage enum is moved from askcc/definitions.py to askcc/settings.py to avoid a circular import (definitions.py already imports from settings.py). This matches the existing VALID_EFFORT_LEVELS precedent. Two internal import sites (askcc/cli.py:11, tests/test_askcc.py:19) are updated in this PR; the package is private (Private :: Do Not Upload) so there is no external API impact.
    • Module-level cache for the parsed TOML config — refresh requires a fresh process, by design. Tests inject a dict via an optional argument rather than reloading the module.
    • TOML parse-error log message intentionally omits the exception detail to avoid echoing config contents into logs. Easy to flip if maintainer disagrees.

    No needs:decision block — the spec is complete and the judgment calls above are minor.

    :tokens-used: model: N/A, input: 23, output: 21950

  3. added
    action:developIssue is ready for development
    and removed
    action:planIssue is ready for planning
    on Apr 27, 2026
  4. monkut commented on Apr 27, 2026

    @monkut
    OwnerAuthor

    Implementation opened in #93 on branch `feature/92-configurable-default-language`.

    Summary of changes

    • `askcc/settings.py`: Added `tomllib` import, moved `SupportedLanguage` enum here (to avoid circular import with `definitions.py`), and added `USER_CONFIG_PATH`, `_load_user_config()`, `_USER_CONFIG` (module-level cache), `_resolve_default_language()`, and `DEFAULT_LANGUAGE`.
    • `askcc/definitions.py`: Removed `SupportedLanguage` (now in `settings.py`).
    • `askcc/cli.py`: Switched `--language` argparse `default=` to `settings.DEFAULT_LANGUAGE` and updated help text to document the four-level precedence (CLI > env > config > built-in).
    • `tests/test_askcc.py`: Updated `SupportedLanguage` import path; added `TestDefaultLanguageResolution` with 11 tests covering env var, config file, invalid values, missing/malformed files, and CLI-flag override.
    • `README.md`: Added `ASKCC_LANGUAGE` to the env var table and a "User Configuration File" subsection documenting the `~/.askcc/config.toml` format and precedence order.

    Verification

    • `uv run pytest` → 197 passed
    • `uv run ruff check` → clean
    • `uv run pyright` → 0 errors

    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 (`USER_CONFIG_PATH = ASKCC_HOME / "config.toml"`)
    • 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

    :tokens-used: model: N/A, input: 74, output: 23821

  5. added and removed
    action:developIssue is ready for development
    on Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions