introspy 0.1.0__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.
- introspy-0.1.0/.claude/hooks/session-start.sh +14 -0
- introspy-0.1.0/.claude/mcp.json +8 -0
- introspy-0.1.0/.claude/settings.json +14 -0
- introspy-0.1.0/.claude/skills/nolegend/SKILL.md +207 -0
- introspy-0.1.0/.claude/skills/python-review/SKILL.md +110 -0
- introspy-0.1.0/.github/workflows/ci.yml +72 -0
- introspy-0.1.0/.github/workflows/release.yml +36 -0
- introspy-0.1.0/.gitignore +19 -0
- introspy-0.1.0/CLAUDE.md +50 -0
- introspy-0.1.0/PKG-INFO +14 -0
- introspy-0.1.0/README.md +97 -0
- introspy-0.1.0/docs/architecture.md +197 -0
- introspy-0.1.0/pyproject.toml +159 -0
- introspy-0.1.0/queries.sql +124 -0
- introspy-0.1.0/schema-notes.md +74 -0
- introspy-0.1.0/scripts/install-hooks.sh +9 -0
- introspy-0.1.0/scripts/pre-commit.sh +32 -0
- introspy-0.1.0/src/introspect/__init__.py +1 -0
- introspy-0.1.0/src/introspect/api/__init__.py +0 -0
- introspy-0.1.0/src/introspect/api/handlers/__init__.py +0 -0
- introspy-0.1.0/src/introspect/api/handlers/_helpers.py +489 -0
- introspy-0.1.0/src/introspect/api/handlers/bash.py +173 -0
- introspy-0.1.0/src/introspect/api/handlers/cost_breakdown.py +441 -0
- introspy-0.1.0/src/introspect/api/handlers/cost_overview.py +789 -0
- introspy-0.1.0/src/introspect/api/handlers/dashboard.py +92 -0
- introspy-0.1.0/src/introspect/api/handlers/mcps.py +120 -0
- introspy-0.1.0/src/introspect/api/handlers/raw.py +102 -0
- introspy-0.1.0/src/introspect/api/handlers/refresh.py +204 -0
- introspy-0.1.0/src/introspect/api/handlers/search.py +73 -0
- introspy-0.1.0/src/introspect/api/handlers/sessions.py +1780 -0
- introspy-0.1.0/src/introspect/api/handlers/stats.py +306 -0
- introspy-0.1.0/src/introspect/api/handlers/subagents.py +305 -0
- introspy-0.1.0/src/introspect/api/handlers/tokenscape.py +1468 -0
- introspy-0.1.0/src/introspect/api/handlers/tools.py +140 -0
- introspy-0.1.0/src/introspect/api/main.py +154 -0
- introspy-0.1.0/src/introspect/api/routes.py +190 -0
- introspy-0.1.0/src/introspect/cli.py +587 -0
- introspy-0.1.0/src/introspect/db.py +1012 -0
- introspy-0.1.0/src/introspect/mcp/__init__.py +0 -0
- introspy-0.1.0/src/introspect/mcp/_register.py +29 -0
- introspy-0.1.0/src/introspect/mcp/refresh_bridge.py +47 -0
- introspy-0.1.0/src/introspect/mcp/server.py +15 -0
- introspy-0.1.0/src/introspect/mcp/tools.py +441 -0
- introspy-0.1.0/src/introspect/pricing.py +156 -0
- introspy-0.1.0/src/introspect/projects.py +44 -0
- introspy-0.1.0/src/introspect/refresh.py +277 -0
- introspy-0.1.0/src/introspect/search.py +281 -0
- introspy-0.1.0/src/introspect/sql_fragments.py +165 -0
- introspy-0.1.0/src/introspect/templates/_cost_portfolio_panel.html +143 -0
- introspy-0.1.0/src/introspect/templates/_daily_cost_panel.html +45 -0
- introspy-0.1.0/src/introspect/templates/_hourly_cost_panel.html +21 -0
- introspy-0.1.0/src/introspect/templates/_refresh_indicator.html +58 -0
- introspy-0.1.0/src/introspect/templates/_session_cost.html +107 -0
- introspy-0.1.0/src/introspect/templates/_session_cost_bloat.html +104 -0
- introspy-0.1.0/src/introspect/templates/_session_messages.html +107 -0
- introspy-0.1.0/src/introspect/templates/_session_subagents.html +85 -0
- introspy-0.1.0/src/introspect/templates/_session_tokenscape.html +130 -0
- introspy-0.1.0/src/introspect/templates/_spend_shapes.html +37 -0
- introspy-0.1.0/src/introspect/templates/base.html +373 -0
- introspy-0.1.0/src/introspect/templates/bash.html +127 -0
- introspy-0.1.0/src/introspect/templates/cost_overview.html +16 -0
- introspy-0.1.0/src/introspect/templates/dashboard.html +92 -0
- introspy-0.1.0/src/introspect/templates/mcps.html +125 -0
- introspy-0.1.0/src/introspect/templates/partial.html +2 -0
- introspy-0.1.0/src/introspect/templates/raw.html +175 -0
- introspy-0.1.0/src/introspect/templates/search.html +70 -0
- introspy-0.1.0/src/introspect/templates/session_detail.html +68 -0
- introspy-0.1.0/src/introspect/templates/sessions.html +139 -0
- introspy-0.1.0/src/introspect/templates/stats.html +345 -0
- introspy-0.1.0/src/introspect/templates/tools.html +129 -0
- introspy-0.1.0/tests/__init__.py +0 -0
- introspy-0.1.0/tests/conftest.py +145 -0
- introspy-0.1.0/tests/e2e/__init__.py +0 -0
- introspy-0.1.0/tests/e2e/conftest.py +278 -0
- introspy-0.1.0/tests/e2e/data/projects/project-alpha/aaaa1111-aaaa-aaaa-aaaa-aaaaaaaaaaaa.jsonl +9 -0
- introspy-0.1.0/tests/e2e/data/projects/project-beta/bbbb2222-bbbb-bbbb-bbbb-bbbbbbbbbbbb.jsonl +11 -0
- introspy-0.1.0/tests/e2e/test_crawl.py +33 -0
- introspy-0.1.0/tests/e2e/test_flows.py +75 -0
- introspy-0.1.0/tests/routes/__init__.py +0 -0
- introspy-0.1.0/tests/routes/conftest.py +241 -0
- introspy-0.1.0/tests/routes/cost_helpers.py +255 -0
- introspy-0.1.0/tests/routes/test_app_lifecycle.py +158 -0
- introspy-0.1.0/tests/routes/test_cost_breakdown.py +393 -0
- introspy-0.1.0/tests/routes/test_cost_chart.py +386 -0
- introspy-0.1.0/tests/routes/test_cost_overview.py +603 -0
- introspy-0.1.0/tests/routes/test_cost_tab.py +330 -0
- introspy-0.1.0/tests/routes/test_dashboard_stats.py +150 -0
- introspy-0.1.0/tests/routes/test_raw_page.py +81 -0
- introspy-0.1.0/tests/routes/test_refresh_ui.py +319 -0
- introspy-0.1.0/tests/routes/test_search_page.py +116 -0
- introspy-0.1.0/tests/routes/test_session_detail.py +311 -0
- introspy-0.1.0/tests/routes/test_session_subagents.py +537 -0
- introspy-0.1.0/tests/routes/test_sessions_list.py +261 -0
- introspy-0.1.0/tests/routes/test_spend_shape.py +420 -0
- introspy-0.1.0/tests/routes/test_tokenscape.py +364 -0
- introspy-0.1.0/tests/routes/test_tokenscape_subagents.py +628 -0
- introspy-0.1.0/tests/routes/test_tool_pages.py +254 -0
- introspy-0.1.0/tests/routes/tokenscape_helpers.py +221 -0
- introspy-0.1.0/tests/test_cli.py +206 -0
- introspy-0.1.0/tests/test_db.py +646 -0
- introspy-0.1.0/tests/test_mcp_tools.py +510 -0
- introspy-0.1.0/tests/test_pricing.py +135 -0
- introspy-0.1.0/tests/test_projects.py +185 -0
- introspy-0.1.0/tests/test_refresh.py +422 -0
- introspy-0.1.0/tests/test_search.py +325 -0
- introspy-0.1.0/uv.lock +1390 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Only run in remote (Claude Code on the web) environments
|
|
5
|
+
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
|
|
6
|
+
exit 0
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
# Install dependencies
|
|
10
|
+
cd "$CLAUDE_PROJECT_DIR"
|
|
11
|
+
uv sync --quiet
|
|
12
|
+
|
|
13
|
+
# Install pre-commit hooks
|
|
14
|
+
bash scripts/install-hooks.sh
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nolegend
|
|
3
|
+
description: >
|
|
4
|
+
Tufte-style Plotly charts in introspect's FastAPI/Jinja stack. Use this
|
|
5
|
+
whenever you add or modify a chart — bar, line, scatter, sparkline, or
|
|
6
|
+
any go.Figure-driven visualization. Trigger on words like "clean",
|
|
7
|
+
"minimal", "professional", "boardroom-ready", or whenever a new
|
|
8
|
+
visualization is being built or an existing one is unclear. Charts in
|
|
9
|
+
this repo are built server-side with go.Figure + nolegend.activate()
|
|
10
|
+
and bootstrapped client-side via Plotly.newPlot in base.html — there is
|
|
11
|
+
no Plotly Express or marimo here.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# nolegend — Plotly visualization rules for introspect
|
|
15
|
+
|
|
16
|
+
*Remove the legend to become one.*
|
|
17
|
+
|
|
18
|
+
Every chart should tell a story, not decorate the page. Apply Tufte's
|
|
19
|
+
principles, then refine with the helpers from the `nolegend` package.
|
|
20
|
+
|
|
21
|
+
## Where charts live in this project
|
|
22
|
+
|
|
23
|
+
- Server side: `go.Figure` built in a handler (e.g.
|
|
24
|
+
`cost_breakdown.py:_build_figure`, `sessions.py:_render_multi_chart`),
|
|
25
|
+
serialised via `fig.to_json()`, embedded into a `<script
|
|
26
|
+
type="application/json">` tag.
|
|
27
|
+
- Client side: `base.html`'s `initCostChart` JS finds elements with
|
|
28
|
+
`class="cost-chart"`, parses the JSON, and runs `Plotly.newPlot`. Click
|
|
29
|
+
/ select handlers are added via `el.on('plotly_click', ...)` →
|
|
30
|
+
`htmx.ajax(...)` for HTMX fragment swaps.
|
|
31
|
+
- Template: `nolegend.activate()` is called lazily on first render
|
|
32
|
+
(`_ensure_template()` pattern — keeps imports side-effect-free).
|
|
33
|
+
- Plotly Express is **not** used. Build figures directly with
|
|
34
|
+
`go.Figure` and `add_trace`. Keep Plotly Express advice from upstream
|
|
35
|
+
nolegend out of this project.
|
|
36
|
+
|
|
37
|
+
## Core principles
|
|
38
|
+
|
|
39
|
+
1. **Maximize data-ink ratio.** Every pixel encodes data. Remove
|
|
40
|
+
gridlines, borders, backgrounds, decorations. The `tufte` template
|
|
41
|
+
does most of this — don't undo it with overrides.
|
|
42
|
+
2. **No chartjunk.** No 3D, no gradient fills, no drop shadows, no
|
|
43
|
+
coloured backgrounds.
|
|
44
|
+
3. **Direct-label over legends.** This is the load-bearing rule. With a
|
|
45
|
+
view toggle that flips trace visibility, set
|
|
46
|
+
`showlegend=False` on the layout and put the label on the line itself
|
|
47
|
+
(see "Direct labels on toggled views" below).
|
|
48
|
+
4. **Titles convey insight, not description.** When the chart has its
|
|
49
|
+
own caption (most do here, via the surrounding `<h2>`), keep the
|
|
50
|
+
in-figure title empty so the plot area isn't pushed down.
|
|
51
|
+
5. **Range frames.** Axes span the data range, not arbitrary round
|
|
52
|
+
numbers. The `tufte` template does this for line/scatter; bars
|
|
53
|
+
default to starting at 0.
|
|
54
|
+
6. **Restrained colour.** ≤ 5 colours per chart. Gray (`#b0b0b0`) is
|
|
55
|
+
for context. Keep the most expensive / most important series in the
|
|
56
|
+
highest-contrast slot (e.g. `_INVOCATION_COLOR_PALETTE` puts red at
|
|
57
|
+
index 0 so the runaway subagent stands out).
|
|
58
|
+
|
|
59
|
+
## Direct labels on toggled views
|
|
60
|
+
|
|
61
|
+
The session detail Cost tab is the canonical example: one figure with
|
|
62
|
+
multiple Scatter traces, view toggle (`Total / Agent / Category /
|
|
63
|
+
Invocations`) flips visibility via `Plotly.restyle`. Without a legend
|
|
64
|
+
the user can't tell which line is which — so we direct-label every
|
|
65
|
+
line trace at its endpoint.
|
|
66
|
+
|
|
67
|
+
Pattern (from `_render_multi_chart`):
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
text_labels = [""] * len(ys)
|
|
71
|
+
if ys:
|
|
72
|
+
text_labels[-1] = f" {name}" # leading space avoids hugging the line
|
|
73
|
+
|
|
74
|
+
fig.add_trace(go.Scatter(
|
|
75
|
+
x=x_axis,
|
|
76
|
+
y=ys,
|
|
77
|
+
mode="lines+text",
|
|
78
|
+
name=name,
|
|
79
|
+
line={"color": color, "width": 1.5},
|
|
80
|
+
text=text_labels,
|
|
81
|
+
textposition="middle right",
|
|
82
|
+
textfont={"color": color, "size": 11},
|
|
83
|
+
cliponaxis=False,
|
|
84
|
+
customdata=...,
|
|
85
|
+
hovertemplate=...,
|
|
86
|
+
))
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Plus, on the layout:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
fig.update_layout(
|
|
93
|
+
showlegend=False,
|
|
94
|
+
margin={"l": 60, "r": 110, "t": 20, "b": 60}, # right margin holds labels
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Why per-trace `text` rather than `add_annotation`:
|
|
99
|
+
|
|
100
|
+
- Annotations are figure-wide and **don't toggle** with trace visibility.
|
|
101
|
+
When the user flips from "Total" to "By agent", annotation labels for
|
|
102
|
+
hidden traces would still float in space.
|
|
103
|
+
- Per-trace `text` is part of the trace, so visibility toggling
|
|
104
|
+
(`Plotly.restyle({visible: ...})`) hides the labels too, automatically.
|
|
105
|
+
- One label per trace at the last point (rest empty) is enough — the
|
|
106
|
+
reader sees the colour + name pair at the line endpoint.
|
|
107
|
+
|
|
108
|
+
For stacked bar charts (cost-overview daily/hourly), use
|
|
109
|
+
`add_annotation` instead — bars don't have an "endpoint" to attach text
|
|
110
|
+
to, so place the label at the peak segment of each top-N group. See
|
|
111
|
+
`cost_breakdown.py:_compute_top_group_annotations` for the pattern.
|
|
112
|
+
|
|
113
|
+
## Hover, click, select interactivity
|
|
114
|
+
|
|
115
|
+
Charts in this repo are interactive surfaces, not static images. Keep
|
|
116
|
+
this in mind:
|
|
117
|
+
|
|
118
|
+
- `customdata` is your friend. Put per-point identifiers
|
|
119
|
+
(uuid, day, session_id) in `customdata` so the click handler can fire
|
|
120
|
+
the right HTMX request. Example: session cost chart's bucket points
|
|
121
|
+
carry `[first_uuid, last_uuid, msg_count]`.
|
|
122
|
+
- `hovertemplate` should reference customdata — it gives the user the
|
|
123
|
+
context they need before they decide to click.
|
|
124
|
+
- `dragmode='select'` for any chart that supports box-filtering. Force
|
|
125
|
+
it via `Plotly.relayout` after `newPlot` in case the modebar resets it.
|
|
126
|
+
- Line-only traces don't reliably populate `evt.points` on box-select.
|
|
127
|
+
Read `evt.range.x` and look up customdata from `el.data[trace_idx]`.
|
|
128
|
+
- Markers always-visible: when a chart has a "marker overlay" trace
|
|
129
|
+
(spike/slope highlights), keep it visible across all view toggles by
|
|
130
|
+
flagging its trace index in the JS `viewMap` logic.
|
|
131
|
+
|
|
132
|
+
## Colour rules (hard constraints)
|
|
133
|
+
|
|
134
|
+
- Never use more than 5 colours in a single chart (excluding the
|
|
135
|
+
marker overlay).
|
|
136
|
+
- Never use rainbow / jet colorscales — not perceptually uniform.
|
|
137
|
+
- Never use red and green together without another differentiator.
|
|
138
|
+
- Use `_MAIN_AGENT_COLOR` for "main / total" series so the same colour
|
|
139
|
+
consistently means the same thing across the app's charts.
|
|
140
|
+
- For sequential data, `nolegend.SEQUENTIAL` or `viridis`.
|
|
141
|
+
- When in doubt: fewer colours. One colour + gray usually suffices.
|
|
142
|
+
|
|
143
|
+
## Pinned colour map for re-rendered panels
|
|
144
|
+
|
|
145
|
+
When two panels show overlapping groups (e.g. daily totals and hourly
|
|
146
|
+
drill-down by project), build a single canonical colour map from the
|
|
147
|
+
all-time aggregate and pass it to both renderers. This way "project
|
|
148
|
+
foo" is the same colour in every panel. See
|
|
149
|
+
`cost_breakdown.py:_canonical_color_map` for the pattern.
|
|
150
|
+
|
|
151
|
+
## Chart type selection
|
|
152
|
+
|
|
153
|
+
Match the chart to the question, not the other way around.
|
|
154
|
+
|
|
155
|
+
| Question type | Chart |
|
|
156
|
+
|---|---|
|
|
157
|
+
| How did it change over time? | Line |
|
|
158
|
+
| How do groups compare? | Horizontal bar |
|
|
159
|
+
| What's the relationship? | Scatter |
|
|
160
|
+
| What's the distribution? | Histogram or box |
|
|
161
|
+
| What's the composition? | Stacked bar |
|
|
162
|
+
| What's the trend per group? | Small multiples (faceted bars/lines) |
|
|
163
|
+
| What's the quick trend? | Sparkline |
|
|
164
|
+
|
|
165
|
+
**Never use:** pie charts, donut charts, 3D charts, radar/spider charts,
|
|
166
|
+
gauge charts.
|
|
167
|
+
|
|
168
|
+
## Typography and titles
|
|
169
|
+
|
|
170
|
+
- **In-card heading carries the title.** The surrounding `<h2>` in the
|
|
171
|
+
Jinja template is the chart's title — keep `fig.update_layout(title=...)`
|
|
172
|
+
empty so the plot area sits at the top of its card.
|
|
173
|
+
- **Axis labels: only when non-obvious.** "Day" on a daily-bucket
|
|
174
|
+
x-axis is fine. "USD" on a cost y-axis is useful. Skip generic
|
|
175
|
+
"Value" / "Count".
|
|
176
|
+
- **Font: serif (Georgia).** The `tufte` template handles this. The
|
|
177
|
+
base.html stylesheet pins `#js-plotly-tester` to Georgia too —
|
|
178
|
+
don't remove that rule, it prevents axis-title displacement after
|
|
179
|
+
HTMX swaps.
|
|
180
|
+
|
|
181
|
+
## Anti-patterns to catch and fix
|
|
182
|
+
|
|
183
|
+
| Anti-pattern | Fix |
|
|
184
|
+
|---|---|
|
|
185
|
+
| Legend box with 2+ entries on a toggled chart | Direct labels via per-trace `text` |
|
|
186
|
+
| Gridlines visible | `showgrid=False` (template handles it) |
|
|
187
|
+
| Default Plotly blue everywhere | Use the project palette constants |
|
|
188
|
+
| Title says "Chart of X by Y" | Rewrite the surrounding `<h2>` as the insight |
|
|
189
|
+
| Pie chart | Replace with horizontal bar |
|
|
190
|
+
| Too many colours (>5) | Aggregate, fold into "Other", or use spotlight |
|
|
191
|
+
| Y-axis starts at 0 when data starts at 800 | Trust the tufte template's range frame |
|
|
192
|
+
| Annotations on a toggled-trace chart | Use per-trace `text` instead |
|
|
193
|
+
| `fig.update_layout(title=...)` set | Drop it — the `<h2>` is the title |
|
|
194
|
+
| HTMX swap rebuilds chart from scratch and loses selection state | Use `Plotly.restyle` for visibility flips |
|
|
195
|
+
|
|
196
|
+
## Checklist before merging a chart change
|
|
197
|
+
|
|
198
|
+
- [ ] `nolegend.activate()` called (lazy `_ensure_template()` pattern)
|
|
199
|
+
- [ ] No legend box (direct labels or single series)
|
|
200
|
+
- [ ] No gridlines, no in-figure title
|
|
201
|
+
- [ ] ≤ 5 colours, palette pinned across panels if shared
|
|
202
|
+
- [ ] Axes have units where non-obvious
|
|
203
|
+
- [ ] Right margin ≥ 80–110px when direct-labeling
|
|
204
|
+
- [ ] `customdata` carries enough info for click/select handlers
|
|
205
|
+
- [ ] `cliponaxis=False` on text-bearing traces so end labels render
|
|
206
|
+
- [ ] Test added in `tests/routes/` asserting the figure JSON
|
|
207
|
+
shape (e.g. trace name, customdata presence, no `<polyline>`)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-review
|
|
3
|
+
context: fork
|
|
4
|
+
description: Deep Python code quality review. Auto-invoke when finishing a task, before marking work complete, when the user asks to review code, or when preparing a PR. Focuses on design judgment, naming, performance, and test quality — things that ruff and ty cannot catch.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Deep Code Review
|
|
8
|
+
|
|
9
|
+
Review the changes for design quality — what automated tools miss. Report findings as 🔴 Must Fix, 🟡 Should Fix, 🟢 Suggestion.
|
|
10
|
+
|
|
11
|
+
First, confirm `uv run poe check` passes (ruff, ty, tests). Fix those before proceeding.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Function Design
|
|
16
|
+
|
|
17
|
+
Prefer pure functions: data in, data out, no side effects. When side effects are necessary (I/O, logging, state mutation), isolate them — push them to the edges, keep the core logic pure and testable.
|
|
18
|
+
|
|
19
|
+
- Functions that both compute and mutate → split into a pure computation and a thin side-effecting wrapper.
|
|
20
|
+
- Over ~30 lines → smell. Justify or split.
|
|
21
|
+
- Mixed abstraction levels (HTTP parsing interleaved with business logic).
|
|
22
|
+
- \>5 parameters → group into a dataclass or split.
|
|
23
|
+
- Boolean params make call sites unreadable (`process(data, True, False)`) → enum or separate functions.
|
|
24
|
+
- `@staticmethod` on a single-method class, or classes with only `__init__` + one method → just a function.
|
|
25
|
+
- Inheritance for code reuse where composition is simpler.
|
|
26
|
+
|
|
27
|
+
## 2. Naming
|
|
28
|
+
|
|
29
|
+
- Single-letter variables outside comprehensions/lambdas. `d`, `x`, `r` — name the thing.
|
|
30
|
+
- Generic names that mean nothing: `data`, `result`, `info`, `item`, `obj`, `tmp`, `val`, `manager`, `handler`, `processor`, `helper`, `utils`. Name the *what*.
|
|
31
|
+
- Misleading: `users` that's a count, `get_` that mutates, `status` that's a bool.
|
|
32
|
+
- Inconsistent: `user_id` / `userId` / `uid` across functions.
|
|
33
|
+
- Unnecessary abbreviation. `cfg` is fine. `proc_dat_xfrm` is not.
|
|
34
|
+
- Negated booleans (`not_found`, `is_not_valid`) → invert the name.
|
|
35
|
+
|
|
36
|
+
## 3. Comments
|
|
37
|
+
|
|
38
|
+
Comments exist for *why*, never *what*.
|
|
39
|
+
|
|
40
|
+
- Restating code (`# increment counter` above `counter += 1`) → delete.
|
|
41
|
+
- Commented-out code → it's in git, delete.
|
|
42
|
+
- TODOs without a ticket reference → link to tracker or delete.
|
|
43
|
+
- *Missing* why-comments on magic numbers, workarounds, performance hacks, non-obvious decisions.
|
|
44
|
+
- Docstrings that parrot the signature (`"""Gets the user."""` on `get_user()`) → explain behavior/constraints/edge cases or remove.
|
|
45
|
+
- Excessive inline comments on straightforward code → the code is too clever, simplify it.
|
|
46
|
+
|
|
47
|
+
## 4. Error Handling Design
|
|
48
|
+
|
|
49
|
+
Strategy, not syntax (ruff handles syntax).
|
|
50
|
+
|
|
51
|
+
- `try` blocks wrapping 20 lines → wrap only what can throw.
|
|
52
|
+
- `except Exception as e: raise Exception(str(e))` → destroys traceback/type. `raise` or wrap in domain exception.
|
|
53
|
+
- `raise OtherError("failed")` discarding original → chain with `from e`.
|
|
54
|
+
- Error types that are `str` or bare `Exception` → domain-specific exceptions.
|
|
55
|
+
- `assert` for runtime validation in non-test code → stripped by `-O`, use `if`/`raise`.
|
|
56
|
+
- String-based error discrimination (`if "not found" in str(e)`) → exception types.
|
|
57
|
+
- Inconsistent strategy within a module: some functions return `None`, others raise. Pick one.
|
|
58
|
+
|
|
59
|
+
## 5. Performance
|
|
60
|
+
|
|
61
|
+
Patterns that cause real problems, not micro-optimization.
|
|
62
|
+
|
|
63
|
+
- `in` on a `list` that should be a `set` (>~20 elements: O(n) vs O(1)).
|
|
64
|
+
- `+` string concatenation in a loop → `"".join()`.
|
|
65
|
+
- `await` in a loop → `asyncio.gather()` / `TaskGroup`.
|
|
66
|
+
- N+1: querying a DB or API per item instead of batching.
|
|
67
|
+
- `f.read()` on large files when line-by-line streaming works.
|
|
68
|
+
- Repeated computation inside a loop → hoist or `@cache`.
|
|
69
|
+
- Building a list only to immediately iterate it → use the iterator.
|
|
70
|
+
- Unnecessary `.copy()` / `deepcopy` → restructure ownership.
|
|
71
|
+
|
|
72
|
+
## 6. Test Quality
|
|
73
|
+
|
|
74
|
+
- No assertions — calling code without checking results is not a test.
|
|
75
|
+
- Weak assertions: `assert result is not None` without checking actual value.
|
|
76
|
+
- Mocking 5 things → testing setup, not behavior. Restructure for testability.
|
|
77
|
+
- 3+ tests differing only in input/output → `@pytest.mark.parametrize`.
|
|
78
|
+
- Missing edge cases: empty input, error paths, boundary conditions.
|
|
79
|
+
- Useless names: `test_process_data` → `test_returns_error_on_empty_input`.
|
|
80
|
+
- 20 lines of setup for 2 lines of test → extract fixtures.
|
|
81
|
+
|
|
82
|
+
## 7. Duplicated Logic
|
|
83
|
+
|
|
84
|
+
- Functions/blocks doing the same thing with minor variations → extract with concrete params.
|
|
85
|
+
- Watch for: HTTP handling, validation, serialization boilerplate, similar conditional chains.
|
|
86
|
+
- Copy-paste with slight modifications — the second copy will drift into a bug.
|
|
87
|
+
|
|
88
|
+
## 8. Module Design
|
|
89
|
+
|
|
90
|
+
- God modules (>500 lines, mixed responsibilities) → suggest split.
|
|
91
|
+
- In-function imports for lazy loading must have a `# lazy:` comment. Uncommented → why?
|
|
92
|
+
- Side effects at import time (starting servers, DB connections, thread spawning at module level) → 🔴. Import must be inert.
|
|
93
|
+
- Public names (`no _` prefix) that aren't intended API.
|
|
94
|
+
- CLI entrypoints containing logic instead of parsing args and delegating.
|
|
95
|
+
|
|
96
|
+
## 9. Dependencies
|
|
97
|
+
|
|
98
|
+
- Mixed HTTP clients (`requests` + `httpx`) → pick one.
|
|
99
|
+
- Mixed serialization (`json` + `orjson`/`msgspec`) → use what's in the dep tree.
|
|
100
|
+
- Vendored code that should be a dep, or trivial deps that could be a 10-line util.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Output
|
|
105
|
+
|
|
106
|
+
Group by severity, then area. Each finding: **what** + **where** (file:line) + **why** + **concrete fix**.
|
|
107
|
+
|
|
108
|
+
End with: X must-fix, Y should-fix, Z suggestions.
|
|
109
|
+
|
|
110
|
+
$ARGUMENTS
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
name: Lint
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v5
|
|
16
|
+
- run: uv sync --frozen
|
|
17
|
+
- run: uv run ruff check .
|
|
18
|
+
|
|
19
|
+
format:
|
|
20
|
+
name: Format
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- uses: astral-sh/setup-uv@v5
|
|
25
|
+
- run: uv sync --frozen
|
|
26
|
+
- run: uv run ruff format --check .
|
|
27
|
+
|
|
28
|
+
typecheck:
|
|
29
|
+
name: Type Check
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: astral-sh/setup-uv@v5
|
|
34
|
+
- run: uv sync --frozen
|
|
35
|
+
- run: uv run ty check
|
|
36
|
+
|
|
37
|
+
vulns:
|
|
38
|
+
name: Dependency Vulnerabilities
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
- uses: astral-sh/setup-uv@v5
|
|
43
|
+
- run: uv sync --frozen
|
|
44
|
+
- run: uvx pysentry-rs .
|
|
45
|
+
continue-on-error: true
|
|
46
|
+
|
|
47
|
+
dead-code:
|
|
48
|
+
name: Dead Code Detection
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@v4
|
|
52
|
+
- uses: astral-sh/setup-uv@v5
|
|
53
|
+
- run: uv sync --frozen
|
|
54
|
+
- run: uv run vulture src
|
|
55
|
+
|
|
56
|
+
unused-deps:
|
|
57
|
+
name: Unused Dependencies
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v4
|
|
61
|
+
- uses: astral-sh/setup-uv@v5
|
|
62
|
+
- run: uv sync --frozen
|
|
63
|
+
- run: uv run deptry .
|
|
64
|
+
|
|
65
|
+
test:
|
|
66
|
+
name: Test
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
- uses: astral-sh/setup-uv@v5
|
|
71
|
+
- run: uv sync --frozen
|
|
72
|
+
- run: uv run pytest -q --tb=short
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
name: Build distribution
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v5
|
|
16
|
+
- run: uv build
|
|
17
|
+
- uses: actions/upload-artifact@v4
|
|
18
|
+
with:
|
|
19
|
+
name: dist
|
|
20
|
+
path: dist/
|
|
21
|
+
|
|
22
|
+
publish:
|
|
23
|
+
name: Publish to PyPI
|
|
24
|
+
needs: build
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
environment:
|
|
27
|
+
name: pypi
|
|
28
|
+
url: https://pypi.org/p/introspy
|
|
29
|
+
permissions:
|
|
30
|
+
id-token: write
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/download-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: dist
|
|
35
|
+
path: dist/
|
|
36
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
introspy-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Introspect
|
|
2
|
+
|
|
3
|
+
Explore Claude Code conversation logs via CLI, web UI, MCP server.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
- `db.py` — DuckDB schema over `~/.claude/projects/**/*.jsonl`; materialized at server startup, lazy views as fallback
|
|
8
|
+
- `refresh.py` — background rebuild loop + window picker (`1`/`7`/`30`/`month`)
|
|
9
|
+
- `pricing.py` — model pricing as Python rates + DuckDB `CASE` SQL
|
|
10
|
+
- `sql_fragments.py` — shared SQL building blocks (cost / tool / file / command rollups)
|
|
11
|
+
- `projects.py` — git worktree-aware `cwd` → canonical project
|
|
12
|
+
- `search.py` — FTS via BM25, ILIKE fallback
|
|
13
|
+
- `api/routes.py` → `api/handlers/<name>.py` → `templates/<name>.html`
|
|
14
|
+
- `api/handlers/_helpers.py` — shared: `parent(request)`, `conn(request)`, pagination, sort allowlists; re-exports SQL fragments
|
|
15
|
+
- `mcp/` — FastMCP tools mounted on FastAPI; `refresh_bridge.py` plumbs `app.state` to stateless tool fns
|
|
16
|
+
- `cli.py` — Typer commands
|
|
17
|
+
|
|
18
|
+
## Key Patterns
|
|
19
|
+
|
|
20
|
+
- **Adding a page**: handler in `handlers/`, route in `routes.py`, template, tests in `tests/routes/`
|
|
21
|
+
- **DB access**: `request.state.conn` (read-only, per-request), `json_extract()` for JSON fields, `# noqa: S608` for dynamic SQL
|
|
22
|
+
- **Pagination**: 1-based, fetch `size+1` to detect next page
|
|
23
|
+
- **HTMX**: `parent(request)` selects `base.html` (full) vs `partial.html` (fragment)
|
|
24
|
+
- **Charts**: build `plotly.graph_objects.Figure` server-side, style with `nolegend.activate()`, embed JSON for `Plotly.newPlot` (see `/python-review` skill `nolegend`)
|
|
25
|
+
- **Cost SQL**: reuse `SESSION_COST_SUBQUERY` / `session_cost_subquery_filtered()` from `sql_fragments.py` — never hand-roll cost math in handlers
|
|
26
|
+
- **Materialization**: `materialize_views()` runs on web startup and rebuilds derived tables (incl. `session_stats`, `assistant_message_costs`, `session_messages_enriched`); CLI commands call `ensure_materialized()` so they share the on-disk DB
|
|
27
|
+
- **Views/tables** (`db.py`): `raw_data`, `raw_messages`, `project_map`, `logical_sessions`, `assistant_message_costs`, `tool_calls`, `session_messages_enriched`, `conversation_turns`, `session_titles`, `message_commands`, `file_reads`, `file_writes`, `session_stats`, `search_corpus`, `materialize_meta`
|
|
28
|
+
|
|
29
|
+
## Test Fixtures (`conftest.py`)
|
|
30
|
+
|
|
31
|
+
`make_user_message()`, `make_assistant_message()`, `write_jsonl()`, `glob_pattern()`. Route tests use `_patched_client()` context manager (defined in `tests/routes/conftest.py`).
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
- `uv run introspect query "SELECT ..."` — ad-hoc SQL against the views (use this to study real data; `introspect views` lists them)
|
|
36
|
+
- `uv run poe check` — run lint, typecheck, vulns, then tests
|
|
37
|
+
- `uv run poe fix` — auto-format and fix lint issues
|
|
38
|
+
- `uv run poe test` — run tests only
|
|
39
|
+
- `uv run poe check-all` — run all checks including dead-code and unused-deps
|
|
40
|
+
|
|
41
|
+
## Stack
|
|
42
|
+
|
|
43
|
+
uv, ruff (lint/format), ty (type check), pytest, poethepoet (task runner)
|
|
44
|
+
|
|
45
|
+
## Notes
|
|
46
|
+
|
|
47
|
+
- ty is in beta — may produce false positives. Prefer `# ty: ignore[rule]` over blanket suppression.
|
|
48
|
+
- Pre-commit hook auto-fixes and restages files. Only blocks on unfixable issues.
|
|
49
|
+
- All user-facing features must have tests. When adding new routes, template variables, query parameters, or UI functionality, add corresponding tests in `tests/routes/`.
|
|
50
|
+
- **IMPORTANT**: After completing any task, you MUST run the `/python-review` skill to review all changes. Apply all 🔴 Must Fix and 🟡 Should Fix findings before marking work as complete.
|
introspy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: introspy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Explore and search Claude Code conversation logs
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: duckdb>=1.2.0
|
|
7
|
+
Requires-Dist: fastapi>=0.115.0
|
|
8
|
+
Requires-Dist: jinja2>=3.1.0
|
|
9
|
+
Requires-Dist: mcp>=1.0.0
|
|
10
|
+
Requires-Dist: nolegend>=0.1.2
|
|
11
|
+
Requires-Dist: plotly>=5.0
|
|
12
|
+
Requires-Dist: rich>=13.0.0
|
|
13
|
+
Requires-Dist: typer>=0.15.0
|
|
14
|
+
Requires-Dist: uvicorn>=0.34.0
|
introspy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Introspect
|
|
2
|
+
|
|
3
|
+
Explore and search your Claude Code conversation logs using SQL, full-text search, a web UI, or an MCP server.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Python 3.11+
|
|
8
|
+
- [uv](https://docs.astral.sh/uv/)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv sync
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Activate the project's virtual environment before running the `introspect`
|
|
17
|
+
commands below (or prefix each one with `uv run`):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
source .venv/bin/activate
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Web UI
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
introspect serve
|
|
29
|
+
# Runs on http://127.0.0.1:8000 by default
|
|
30
|
+
introspect serve --port 3000 --host 0.0.0.0
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### CLI
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# List recent sessions
|
|
37
|
+
introspect sessions
|
|
38
|
+
|
|
39
|
+
# Show summary statistics
|
|
40
|
+
introspect stats
|
|
41
|
+
|
|
42
|
+
# Search conversation logs
|
|
43
|
+
introspect search "some query"
|
|
44
|
+
|
|
45
|
+
# Show tool call history
|
|
46
|
+
introspect tools
|
|
47
|
+
introspect tools --failed
|
|
48
|
+
introspect tools --name Bash
|
|
49
|
+
|
|
50
|
+
# Run an ad-hoc SQL query
|
|
51
|
+
introspect query "SELECT * FROM logical_sessions LIMIT 5"
|
|
52
|
+
|
|
53
|
+
# Rebuild the search index
|
|
54
|
+
introspect refresh
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### MCP Server
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
introspect mcp
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This starts an MCP server over stdio for integration with Claude Code.
|
|
64
|
+
|
|
65
|
+
Alternatively, the web server exposes the same MCP tools over HTTP at
|
|
66
|
+
`http://127.0.0.1:8000/mcp`. To launch a Claude Code session wired up to it:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# In one terminal
|
|
70
|
+
introspect serve
|
|
71
|
+
|
|
72
|
+
# In another
|
|
73
|
+
uv run poe claude
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The `claude` poe task runs `claude --mcp-config .claude/mcp.json`, so the MCP
|
|
77
|
+
server is only registered for that session — no changes to your global Claude
|
|
78
|
+
Code config.
|
|
79
|
+
|
|
80
|
+
## Development
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Install dependencies (including dev tools)
|
|
84
|
+
uv sync
|
|
85
|
+
|
|
86
|
+
# Auto-format and fix lint issues
|
|
87
|
+
uv run poe fix
|
|
88
|
+
|
|
89
|
+
# Run lint, typecheck, security scan, and tests
|
|
90
|
+
uv run poe check
|
|
91
|
+
|
|
92
|
+
# Run tests only
|
|
93
|
+
uv run poe test
|
|
94
|
+
|
|
95
|
+
# Run all checks including dead-code and unused-deps
|
|
96
|
+
uv run poe check-all
|
|
97
|
+
```
|