agent-session-analytics 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/.gitignore +3 -0
  2. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/CHANGELOG.md +8 -1
  3. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/PKG-INFO +5 -3
  4. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/README.md +4 -2
  5. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/pyproject.toml +1 -1
  6. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_adapters/cursor.py +1 -1
  7. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_db.py +12 -2
  8. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/references/canonical-schema.md +3 -2
  9. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/references/harness-coverage.md +1 -1
  10. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/conftest.py +1 -1
  11. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/test_paths.py +41 -2
  12. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/LICENSE +0 -0
  13. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/__init__.py +0 -0
  14. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/__main__.py +0 -0
  15. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_adapters/__init__.py +0 -0
  16. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_adapters/_jsonl.py +0 -0
  17. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_adapters/claude.py +0 -0
  18. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_adapters/codex.py +0 -0
  19. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_adapters/copilot.py +0 -0
  20. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_adapters/pi_family.py +0 -0
  21. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_cli.py +0 -0
  22. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_emitters.py +0 -0
  23. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_ingest.py +0 -0
  24. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_query.py +0 -0
  25. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/_tables.py +0 -0
  26. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/py.typed +0 -0
  27. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/src/agent_session_analytics/references/query-conventions.md +0 -0
  28. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/__init__.py +0 -0
  29. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/test_cli.py +0 -0
  30. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/test_codex_envelope.py +0 -0
  31. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/test_codex_ingest.py +0 -0
  32. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/test_duckdb_settings_edges.py +0 -0
  33. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/test_flattened_equivalence.py +0 -0
  34. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/test_flattened_tables.py +0 -0
  35. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/test_malformed_rows.py +0 -0
  36. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/test_pi_family_tokens.py +0 -0
  37. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/test_user_turns.py +0 -0
  38. {agent_session_analytics-0.2.0 → agent_session_analytics-0.2.1}/tests/test_user_turns_edges.py +0 -0
@@ -13,6 +13,9 @@ __pycache__/
13
13
  .cheese/
14
14
  .context/
15
15
 
16
+ # Skill archives: build output. `just build` writes them; CI publishes them on dist (ADR-012).
17
+ skills/*/scripts/*.pyz
18
+
16
19
  # Skill-creator iteration workspaces (eval runs, benchmarks, viewer HTML)
17
20
  skills/*-workspace/
18
21
 
@@ -13,6 +13,12 @@ Push a `agent-session-analytics-v<version>` tag after the release commit merges.
13
13
 
14
14
  ## [Unreleased]
15
15
 
16
+ ## [0.2.1] - 2026-10-25
17
+
18
+ ### Changed
19
+
20
+ - The default database path is now `<cache>/agent-session-analytics/sessions.duckdb`, not `<cache>/dotfiles/session-analytics/sessions.duckdb`. If the new file does not exist and the legacy file exists, the package keeps using the legacy file. It never moves, copies, or deletes the legacy file. `SESSIONS_DB` still overrides both.
21
+
16
22
  ## [0.2.0] - 2026-10-24
17
23
 
18
24
  ### Changed
@@ -61,6 +67,7 @@ Push a `agent-session-analytics-v<version>` tag after the release commit merges.
61
67
 
62
68
  - The canned `query.sh` reports. Callers write their own SQL with `sql`.
63
69
 
64
- [Unreleased]: https://github.com/paulnsorensen/skillz-that-grillz/compare/agent-session-analytics-v0.2.0...HEAD
70
+ [Unreleased]: https://github.com/paulnsorensen/skillz-that-grillz/compare/agent-session-analytics-v0.2.1...HEAD
71
+ [0.2.1]: https://github.com/paulnsorensen/skillz-that-grillz/compare/agent-session-analytics-v0.2.0...agent-session-analytics-v0.2.1
65
72
  [0.2.0]: https://github.com/paulnsorensen/skillz-that-grillz/compare/agent-session-analytics-v0.1.0...agent-session-analytics-v0.2.0
66
73
  [0.1.0]: https://github.com/paulnsorensen/skillz-that-grillz/releases/tag/agent-session-analytics-v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agent-session-analytics
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Ingest coding-agent session logs into DuckDB and query them read-only.
5
5
  Project-URL: Homepage, https://github.com/paulnsorensen/skillz-that-grillz/tree/main/packages/agent-session-analytics
6
6
  Project-URL: Source, https://github.com/paulnsorensen/skillz-that-grillz/tree/main/packages/agent-session-analytics
@@ -96,8 +96,10 @@ A consumer that queries the database directly runs
96
96
  - `SESSIONS_DB` names the database. A relative value joins the current
97
97
  directory. The package does not expand `~` in a set value.
98
98
  - Otherwise an absolute `XDG_CACHE_HOME` selects
99
- `<cache>/dotfiles/session-analytics/sessions.duckdb`. A relative or unset
100
- value uses `~/.cache`.
99
+ `<cache>/agent-session-analytics/sessions.duckdb`. A relative or unset
100
+ value uses `~/.cache`. If that file does not exist and the legacy
101
+ `<cache>/dotfiles/session-analytics/sessions.duckdb` exists, the package uses
102
+ the legacy file. It never moves, copies, or deletes the legacy file.
101
103
  - `SESSIONS_DUCKDB_MEMORY_LIMIT` sets the DuckDB memory limit, from 16MiB up to
102
104
  but not including 16EiB. The default is `4GB`.
103
105
  - `SESSIONS_DUCKDB_THREADS` sets the DuckDB thread count, from 1 to 256. The
@@ -73,8 +73,10 @@ A consumer that queries the database directly runs
73
73
  - `SESSIONS_DB` names the database. A relative value joins the current
74
74
  directory. The package does not expand `~` in a set value.
75
75
  - Otherwise an absolute `XDG_CACHE_HOME` selects
76
- `<cache>/dotfiles/session-analytics/sessions.duckdb`. A relative or unset
77
- value uses `~/.cache`.
76
+ `<cache>/agent-session-analytics/sessions.duckdb`. A relative or unset
77
+ value uses `~/.cache`. If that file does not exist and the legacy
78
+ `<cache>/dotfiles/session-analytics/sessions.duckdb` exists, the package uses
79
+ the legacy file. It never moves, copies, or deletes the legacy file.
78
80
  - `SESSIONS_DUCKDB_MEMORY_LIMIT` sets the DuckDB memory limit, from 16MiB up to
79
81
  but not including 16EiB. The default is `4GB`.
80
82
  - `SESSIONS_DUCKDB_THREADS` sets the DuckDB thread count, from 1 to 256. The
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-session-analytics"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Ingest coding-agent session logs into DuckDB and query them read-only."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -52,7 +52,7 @@ def _cursor_resolve_slug(slug: str, fs_root: str = "/") -> str:
52
52
  """Decode a dash-encoded absolute path, preferring the longest existing dir.
53
53
 
54
54
  Cursor project slugs replace path separators with dashes
55
- (``Users-paul-Dev-easy-cheese``). A directory whose own name contains a
55
+ (``Users-alex-src-app``). A directory whose own name contains a
56
56
  dash is ambiguous if every dash is treated as a separator. Walk
57
57
  left-to-right and at each step take the longest prefix of remaining
58
58
  segments that exists under ``fs_root``; fall back to a naive split for
@@ -36,8 +36,18 @@ def _xdg_cache_home() -> str:
36
36
  return os.path.abspath(os.path.expanduser("~/.cache"))
37
37
 
38
38
 
39
- DEFAULT_DB_DIR = os.path.join(_xdg_cache_home(), "dotfiles", "session-analytics")
40
- DB_PATH = configured_path("SESSIONS_DB", os.path.join(DEFAULT_DB_DIR, "sessions.duckdb"))
39
+ def _default_db_path() -> str:
40
+ # The legacy `dotfiles/` cache keeps working when it is the only one that
41
+ # exists. Nothing moves, copies, or deletes it; another tool may share it.
42
+ cache = _xdg_cache_home()
43
+ path = os.path.join(cache, "agent-session-analytics", "sessions.duckdb")
44
+ legacy = os.path.join(cache, "dotfiles", "session-analytics", "sessions.duckdb")
45
+ if not os.path.exists(path) and os.path.exists(legacy):
46
+ return legacy
47
+ return path
48
+
49
+
50
+ DB_PATH = configured_path("SESSIONS_DB", _default_db_path())
41
51
  DB_DIR = os.path.dirname(DB_PATH)
42
52
  DB_TMP_PATH = f"{DB_PATH}.tmp"
43
53
  # Derived from the database file name, not DB_DIR, so a SESSIONS_DB pointed
@@ -4,8 +4,9 @@ Contents: [tool_uses](#tool_uses), [tool_results](#tool_results), [stop_events](
4
4
 
5
5
  The shape every harness adapter normalizes into and every analytics pack queries
6
6
  against. `agent-session-analytics ingest` loads one canonical row shape (the Claude envelope plus a
7
- `harness` tag) into `${XDG_CACHE_HOME:-$HOME/.cache}/dotfiles/session-analytics/sessions.duckdb` by default, then flattens it into
8
- the tables below. **Pack authors: write SQL against these tables; never reach
7
+ `harness` tag) into `${XDG_CACHE_HOME:-$HOME/.cache}/agent-session-analytics/sessions.duckdb` by default, then flattens it into
8
+ the tables below. If that file is absent, an existing legacy `dotfiles/session-analytics/` database is used.
9
+ **Pack authors: write SQL against these tables; never reach
9
10
  into a harness's native format.**
10
11
 
11
12
  Every session-scoped table carries a `harness` column
@@ -133,7 +133,7 @@ slashes).
133
133
  A directory whose own name contains a dash is ambiguous if every dash
134
134
  is a separator.
135
135
  So the adapter walks left-to-right. At each step it takes the longest prefix of remaining segments that exists on disk
136
- (`Users-paul-Dev-easy-cheese` → `/Users/paul/Dev/easy-cheese` when that path is
136
+ (`Users-alex-src-app` → `/Users/alex/src/app` when that path is
137
137
  real). Unresolved tails fall back to a naive split. `gitBranch` is always null
138
138
  (not recorded).
139
139
 
@@ -27,7 +27,7 @@ class Sandbox:
27
27
 
28
28
  @property
29
29
  def database(self) -> Path:
30
- return self.root / "cache" / "dotfiles" / "session-analytics" / "sessions.duckdb"
30
+ return self.root / "cache" / "agent-session-analytics" / "sessions.duckdb"
31
31
 
32
32
  def run(
33
33
  self, *args: str, extra_env: dict[str, str] | None = None
@@ -36,7 +36,7 @@ def eval_ingest(expr: str, env: dict[str, str], cwd: Path, *argv: str) -> str:
36
36
  return result.stdout.strip()
37
37
 
38
38
 
39
- DEFAULT = "home/.cache/dotfiles/session-analytics/sessions.duckdb"
39
+ DEFAULT = "home/.cache/agent-session-analytics/sessions.duckdb"
40
40
 
41
41
  DB_PATH_CASES: dict[str, tuple[Callable[[Path], dict[str, str]], Callable[[Path], str]]] = {
42
42
  "sessions_db_absolute": (
@@ -53,7 +53,7 @@ DB_PATH_CASES: dict[str, tuple[Callable[[Path], dict[str, str]], Callable[[Path]
53
53
  ),
54
54
  "xdg_cache_absolute": (
55
55
  lambda root: {"XDG_CACHE_HOME": str(root / "cache")},
56
- lambda root: str(root / "cache" / "dotfiles" / "session-analytics" / "sessions.duckdb"),
56
+ lambda root: str(root / "cache" / "agent-session-analytics" / "sessions.duckdb"),
57
57
  ),
58
58
  "xdg_cache_relative_is_ignored": (
59
59
  lambda root: {"XDG_CACHE_HOME": "rel/c"},
@@ -92,6 +92,45 @@ def test_db_path_command_honours_sessions_db(sandbox: Sandbox) -> None:
92
92
  assert json.loads(result.stdout) == {"path": str(override)}
93
93
 
94
94
 
95
+ def _touch(path: Path) -> None:
96
+ path.parent.mkdir(parents=True)
97
+ _ = path.write_text("")
98
+
99
+
100
+ def _db_path(sandbox: Sandbox, extra_env: dict[str, str] | None = None) -> str:
101
+ result = sandbox.run("db-path", extra_env=extra_env)
102
+ assert result.returncode == 0, result.stderr
103
+ return cast(str, json.loads(result.stdout)["path"])
104
+
105
+
106
+ def test_default_is_the_new_path_when_nothing_exists(sandbox: Sandbox) -> None:
107
+ assert _db_path(sandbox) == str(sandbox.database)
108
+ assert "dotfiles" not in sandbox.database.parts
109
+
110
+
111
+ def test_default_falls_back_to_the_legacy_path_when_only_it_exists(sandbox: Sandbox) -> None:
112
+ legacy = sandbox.root / "cache" / "dotfiles" / "session-analytics" / "sessions.duckdb"
113
+ _touch(legacy)
114
+ assert _db_path(sandbox) == str(legacy)
115
+ assert legacy.exists()
116
+ assert not sandbox.database.exists()
117
+
118
+
119
+ def test_default_prefers_the_new_path_when_both_exist(sandbox: Sandbox) -> None:
120
+ legacy = sandbox.root / "cache" / "dotfiles" / "session-analytics" / "sessions.duckdb"
121
+ _touch(legacy)
122
+ _touch(sandbox.database)
123
+ assert _db_path(sandbox) == str(sandbox.database)
124
+ assert legacy.exists()
125
+
126
+
127
+ def test_sessions_db_beats_an_existing_legacy_file(sandbox: Sandbox) -> None:
128
+ legacy = sandbox.root / "cache" / "dotfiles" / "session-analytics" / "sessions.duckdb"
129
+ _touch(legacy)
130
+ override = sandbox.root / "elsewhere.duckdb"
131
+ assert _db_path(sandbox, {"SESSIONS_DB": str(override)}) == str(override)
132
+
133
+
95
134
  NAMES = ("CLAUDE_CONFIG_DIR", "CODEX_HOME", "CURSOR_HOME")
96
135
 
97
136