Skip to content

feat: respect $XDG_CONFIG_HOME for config directory#26

Merged
emarkou merged 6 commits into
mainfrom
feat/xdg-config-home
May 13, 2026
Merged

feat: respect $XDG_CONFIG_HOME for config directory#26
emarkou merged 6 commits into
mainfrom
feat/xdg-config-home

Conversation

@emarkou

@emarkou emarkou commented May 12, 2026

Copy link
Copy Markdown
Owner

What

Config and cache files now follow the XDG Base Directory spec.

Behaviour

Condition Config dir
$XDG_CONFIG_HOME set $XDG_CONFIG_HOME/devskim/
$XDG_CONFIG_HOME unset ~/.config/devskim/
~/.devskim exists and XDG path does not ~/.devskim/ (legacy, no migration needed)

Existing installs are detected automatically — no manual migration required.

Changes

  • config.py: _resolve_config_dir() replaces the hardcoded Path.home() / ".devskim"
  • README.md: updated config path references
  • tests/test_config.py: four new tests covering all resolution branches

Summary by CodeRabbit

  • Documentation

    • README updated to document XDG-compliant config location (defaults to $XDG_CONFIG_HOME/devskim/config.toml) and legacy fallback to the previous home-based path.
  • New Features

    • Configuration, cache, and data now default to XDG locations with automatic legacy fallback when needed.
    • Cache storage and persisted "seen" data moved to appropriate XDG cache/data locations.
  • Tests

    • Added and expanded tests covering XDG resolution, cache/data locations, and legacy fallback behavior.

Review Change Stack

Default config location is now $XDG_CONFIG_HOME/devskim (falling back
to ~/.config/devskim per XDG spec). Existing installs with ~/.devskim
are detected automatically and continue to work unchanged.
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Switches runtime config resolution to $XDG_CONFIG_HOME/devskim (default ~/.config/devskim), adds resolvers for config/cache/data with legacy ~/.devskim fallback for config, moves seen.json to DATA_DIR, updates tests, and revises README and Config docstring.

Changes

XDG Config Directory Migration

Layer / File(s) Summary
XDG resolution and module constants
devskim/config.py
Adds _resolve_config_dir(), _resolve_cache_dir(), _resolve_data_dir(); sets CONFIG_DIR/CONFIG_PATH, CACHE_DIR/CACHE_PATH, and DATA_DIR from XDG envs with sensible defaults and legacy ~/.devskim fallback for config; updates Config docstring and save_cache() to create CACHE_DIR before writing.
Config, cache, and data tests
tests/test_config.py
Adds tests covering _resolve_config_dir() (env absolute, ignore relative, default, legacy fallback, precedence), _resolve_cache_dir(), and _resolve_data_dir() behaviors; updates cache tests to patch devskim.config.CACHE_DIR.
Move seen.json to data dir
devskim/seen.py, tests/test_seen.py
SEEN_PATH now uses DATA_DIR/seen.json; mark_seen() creates DATA_DIR before writes; tests updated to patch seen_module.DATA_DIR.
Documentation updates
README.md
README install/run and Config sections updated to reference $XDG_CONFIG_HOME/devskim/config.toml (default ~/.config/devskim/config.toml) and document legacy ~/.devskim fallback behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped from home to XDG's trail,
New config paths without derail.
If old burrows still remain,
I'll peek inside and use them again.
Cache and data snug in their dale.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: respect $XDG_CONFIG_HOME for config directory' accurately captures the primary change: implementing XDG Base Directory specification support for config directory resolution.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/xdg-config-home

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented May 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.99%. Comparing base (1a7a9b5) to head (4bada1a).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #26      +/-   ##
==========================================
+ Coverage   62.44%   70.99%   +8.54%     
==========================================
  Files          18       18              
  Lines         988     1010      +22     
==========================================
+ Hits          617      717     +100     
+ Misses        371      293      -78     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/test_config.py (1)

9-44: ⚡ Quick win

Add one resolver test for relative XDG_CONFIG_HOME.

Current branch coverage is good, but an explicit relative-path case will prevent regressions around fallback behavior.

Suggested test
+def test_resolve_config_dir_relative_xdg_env_falls_back_to_default(tmp_path):
+    env = dict(os.environ)
+    env["XDG_CONFIG_HOME"] = "relative/path"
+    with patch.dict(os.environ, env, clear=True):
+        with patch("devskim.config.Path") as mock_path:
+            mock_path.home.return_value = tmp_path
+            result = _resolve_config_dir()
+    assert result == tmp_path / ".config" / "devskim"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_config.py` around lines 9 - 44, Add a new unit test (e.g.,
test_resolve_config_dir_xdg_relative) that covers a relative XDG_CONFIG_HOME
value: create a directory under tmp_path (xdg), create tmp_path/xdg/devskim, set
os.environ["XDG_CONFIG_HOME"] to a relative path string (not absolute) and
ensure the code resolves it correctly by either chdiring to an appropriate
parent directory or using os.path.relpath to produce the relative string; call
_resolve_config_dir() and assert the result equals the resolved absolute path
(xdg/devskim). This mirrors the existing tests (test_resolve_config_dir_xdg_env,
test_resolve_config_dir_xdg_default) and ensures _resolve_config_dir handles
relative XDG_CONFIG_HOME values without falling back to legacy paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@devskim/config.py`:
- Around line 27-29: The CACHE_PATH is incorrectly placed under CONFIG_DIR;
create a separate XDG-aware cache directory resolver (e.g.,
_resolve_cache_dir()) that reads $XDG_CACHE_HOME and falls back to
~/.cache/devskim, use that to set CACHE_DIR and then CACHE_PATH = CACHE_DIR /
"cache.json", and update the other cache-related assignments (the same pattern
around the other CACHE_PATH/CACHE_DIR usage) to use this new resolver so cache
files live under the XDG cache location instead of CONFIG_DIR; ensure the
resolver returns a pathlib.Path and that calling code creates the directory if
missing.
- Around line 16-18: Check that the XDG_CONFIG_HOME environment value (variable
xdg) is an absolute path before using it; if not absolute, ignore it and fall
back to the existing default logic instead of returning Path(xdg) / "devskim".
Update the code around xdg and the Path(xdg) / "devskim" return to validate
absoluteness (e.g., os.path.isabs or Path(xdg).is_absolute) and only use the XDG
path when that check passes.

In `@README.md`:
- Around line 60-63: Replace hardcoded `~/.config/devskim/config.toml` examples
with a reference that uses the XDG variable and its fallback (e.g.
`${XDG_CONFIG_HOME:-~/.config}/devskim/config.toml`) wherever the README shows
the edit path (including the other occurrence noted), and update the stale
feature bullet that still mentions `~/.devskim/config.toml` to describe the
XDG-compliant location and fallback behavior instead.

---

Nitpick comments:
In `@tests/test_config.py`:
- Around line 9-44: Add a new unit test (e.g.,
test_resolve_config_dir_xdg_relative) that covers a relative XDG_CONFIG_HOME
value: create a directory under tmp_path (xdg), create tmp_path/xdg/devskim, set
os.environ["XDG_CONFIG_HOME"] to a relative path string (not absolute) and
ensure the code resolves it correctly by either chdiring to an appropriate
parent directory or using os.path.relpath to produce the relative string; call
_resolve_config_dir() and assert the result equals the resolved absolute path
(xdg/devskim). This mirrors the existing tests (test_resolve_config_dir_xdg_env,
test_resolve_config_dir_xdg_default) and ensures _resolve_config_dir handles
relative XDG_CONFIG_HOME values without falling back to legacy paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 953fbbcc-b799-4d60-82b9-036313cba563

📥 Commits

Reviewing files that changed from the base of the PR and between f2c19e7 and 7feddbd.

📒 Files selected for processing (3)
  • README.md
  • devskim/config.py
  • tests/test_config.py

Comment thread devskim/config.py
Comment thread devskim/config.py Outdated
Comment thread README.md Outdated
emarkou added 3 commits May 12, 2026 21:42
Cache files now live under $XDG_CACHE_HOME/devskim (default ~/.cache/devskim)
instead of the config directory. Adds _resolve_cache_dir() mirroring the
existing config resolver pattern.
Persistent read-state belongs under XDG_DATA_HOME (~/.local/share/devskim)
not the config directory. Adds _resolve_data_dir() and DATA_DIR constant,
updates seen.py to use DATA_DIR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@devskim/config.py`:
- Around line 22-23: The fallback currently returns the path represented by
legacy even when it is a regular file; update the condition in the selection
logic that uses the symbols legacy and xdg_default so it only accepts legacy
when it exists and is a directory (use legacy.is_dir()) and xdg_default does not
exist; i.e., add the directory check to prevent returning a file path as the
fallback target.

In `@tests/test_config.py`:
- Around line 22-26: The test currently checks for a non-prefix "relative"
string which can miss other relative forms; change the assertion in
test_resolve_config_dir_xdg_relative_ignored (and the other relative-XDG test at
the other location) to assert the path is absolute instead (e.g., use
Path(result).is_absolute() or os.path.isabs(result)) so the guard strictly
verifies absoluteness of _resolve_config_dir's return value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 06390989-7ab1-42de-a985-03346230db3a

📥 Commits

Reviewing files that changed from the base of the PR and between 9458fe9 and 5af96af.

📒 Files selected for processing (2)
  • devskim/config.py
  • tests/test_config.py

Comment thread devskim/config.py Outdated
Comment thread tests/test_config.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
tests/test_config.py (1)

23-27: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strengthen relative-path guards with absolute-path assertions

At Line 26, Line 153, and Line 174, string-prefix checks are weaker than asserting absoluteness directly. Use result.is_absolute() to make these tests robust against alternate relative forms.

Suggested fix
 def test_resolve_config_dir_xdg_relative_ignored():
     with patch.dict(os.environ, {"XDG_CONFIG_HOME": "relative/path"}, clear=False):
         result = _resolve_config_dir()
-    assert not str(result).startswith("relative")
+    assert result.is_absolute()
@@
 def test_resolve_cache_dir_xdg_relative_ignored():
     with patch.dict(os.environ, {"XDG_CACHE_HOME": "relative/cache"}, clear=False):
         result = _resolve_cache_dir()
-    assert not str(result).startswith("relative")
+    assert result.is_absolute()
@@
 def test_resolve_data_dir_xdg_relative_ignored():
     with patch.dict(os.environ, {"XDG_DATA_HOME": "relative/data"}, clear=False):
         result = _resolve_data_dir()
-    assert not str(result).startswith("relative")
+    assert result.is_absolute()

Also applies to: 150-154, 171-175

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_config.py` around lines 23 - 27, The test
test_resolve_config_dir_xdg_relative_ignored currently checks that the resolved
path doesn't start with a literal "relative" string; change the assertion to
check path absoluteness by calling _resolve_config_dir() and asserting
result.is_absolute() (use pathlib.Path.is_absolute()) so the guard rejects any
relative input forms; update the analogous assertions in the other tests
referenced (around lines with similar prefix checks) to use .is_absolute() on
the _resolve_config_dir() return value instead of string-prefix checks.
devskim/config.py (1)

22-23: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard legacy fallback with a directory check

Line 22 should require ~/.devskim to be a directory, not just an existing path; otherwise a file at that location can cause runtime failure when creating/writing config files.

Suggested fix
-    if legacy.exists() and not xdg_default.exists():
+    if legacy.is_dir() and not xdg_default.exists():
         return legacy
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@devskim/config.py` around lines 22 - 23, The fallback check currently allows
any existing path at `legacy` (e.g., ~/.devskim) including files; change the
condition to require a directory by using `legacy.is_dir()` (or combining
`legacy.exists() and legacy.is_dir()`) instead of `legacy.exists()` so the
fallback only returns `legacy` when it is a directory and `xdg_default` does not
exist; update the conditional around the `legacy` and `xdg_default` variables
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@devskim/config.py`:
- Around line 22-23: The fallback check currently allows any existing path at
`legacy` (e.g., ~/.devskim) including files; change the condition to require a
directory by using `legacy.is_dir()` (or combining `legacy.exists() and
legacy.is_dir()`) instead of `legacy.exists()` so the fallback only returns
`legacy` when it is a directory and `xdg_default` does not exist; update the
conditional around the `legacy` and `xdg_default` variables accordingly.

In `@tests/test_config.py`:
- Around line 23-27: The test test_resolve_config_dir_xdg_relative_ignored
currently checks that the resolved path doesn't start with a literal "relative"
string; change the assertion to check path absoluteness by calling
_resolve_config_dir() and asserting result.is_absolute() (use
pathlib.Path.is_absolute()) so the guard rejects any relative input forms;
update the analogous assertions in the other tests referenced (around lines with
similar prefix checks) to use .is_absolute() on the _resolve_config_dir() return
value instead of string-prefix checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e86b47b-85c2-4878-ba13-ef7b84210b0b

📥 Commits

Reviewing files that changed from the base of the PR and between 5af96af and 3fc48ce.

📒 Files selected for processing (4)
  • devskim/config.py
  • devskim/seen.py
  • tests/test_config.py
  • tests/test_seen.py

@emarkou
emarkou merged commit 3dca523 into main May 13, 2026
11 checks passed
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.

1 participant