Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .githooks/commit-msg
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#!/bin/sh

node scripts/validate-commit-message.mjs --edit "$1"
set -eu

cd "$(git rev-parse --show-toplevel)"
exec node scripts/validate-commit-message.mjs --edit "$1"
11 changes: 11 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh

set -eu

cd "$(git rev-parse --show-toplevel)"

echo "Checking staged changes for whitespace errors..."
GIT_PAGER=cat git --no-pager diff --no-ext-diff --cached --check

echo "Running pre-commit type checks..."
npm run verify:quick
8 changes: 8 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh

set -eu

cd "$(git rev-parse --show-toplevel)"

echo "Running pre-push verification..."
npm run verify
52 changes: 34 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@ name: CI
on:
pull_request:
branches: [main, dev]
push:
branches: [main, dev]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
validate:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -21,10 +32,29 @@ jobs:
webview-ui/package-lock.json

- name: Validate PR title
run: node scripts/validate-commit-message.mjs --message "${{ github.event.pull_request.title }}" --source "pull request title"
if: github.event_name == 'pull_request'
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: npm run lint:commit-msg -- --message "$PR_TITLE" --source "pull request title"

- name: Validate PR commit messages
run: node scripts/validate-commit-range.mjs "${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
if: github.event_name == 'pull_request'
env:
COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
run: npm run lint:commits -- "$COMMIT_RANGE"

- name: Validate pushed commit messages
if: github.event_name == 'push'
env:
BEFORE_SHA: ${{ github.event.before }}
AFTER_SHA: ${{ github.sha }}
run: |
if git cat-file -e "$BEFORE_SHA^{commit}" 2>/dev/null; then
COMMIT_RANGE="$BEFORE_SHA..$AFTER_SHA"
else
COMMIT_RANGE="$AFTER_SHA"
fi
npm run lint:commits -- "$COMMIT_RANGE"

- name: Install root deps
run: npm ci
Expand All @@ -33,19 +63,5 @@ jobs:
run: npm ci
working-directory: webview-ui

- name: Type check
run: npx tsc --noEmit

- name: Type check webview-ui
run: npx tsc --noEmit
working-directory: webview-ui

- name: Run backend tests
run: npx vitest run --coverage

- name: Run UI tests
run: npx vitest run --coverage
working-directory: webview-ui

- name: Build
run: npm run build
- name: Verify project
run: npm run verify
54 changes: 30 additions & 24 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ Data flows one way: `JSONL files → FileWatcher → DashboardStore → Panel.bu
| `parsers/SettingsParser.ts` | Reads `CLAUDE.md`, `.claude/settings.json`, `.mcp.json`, `.claude/commands/*.md` |
| `watchers/FileWatcher.ts` | `fs.watch()` on `~/.claude/projects/*.jsonl`, debounces 300ms, calls `store.onFileChanged()` |
| `watchers/EventWatcher.ts` | Polls `~/.claude/.dashboard-events.jsonl` every 500ms for live tool_use/session_stop events |
| `hooks/HookManager.ts` | Injects PostToolUse/Stop hooks into `~/.claude/settings.json` (with user consent) |
| `hooks/HookManager.ts` | Injects/removes PostToolUse+Stop hooks in `~/.claude/settings.json` (with user consent). Hooks are armed by a `~/.claude/.dashboard-live` marker and no-op without it |
| `hooks/stripDashboardHooks.ts` | Pure helper that strips dashboard hook entries from a parsed settings object (shared with uninstall) |
| `uninstall.ts` | `vscode:uninstall` script — removes hooks, marker, and event file after uninstall |
| `alerts/AlertManager.ts` | Budget alerts (80%/100%) and weekly Monday digest |
| `webviews/DashboardPanel.ts` | Singleton webview for main dashboard. `buildState()` assembles all analytics |
| `webviews/ProjectPanel.ts` | Per-project webview (`Map<projectId, panel>`). Strips turns from sessions (loaded on demand via `getSessionTurns` message) |
Expand All @@ -103,42 +105,46 @@ Data flows one way: `JSONL files → FileWatcher → DashboardStore → Panel.bu

| View | File | Description |
|---|---|---|
| Dashboard | `views/Dashboard.tsx` | 3 tabs: Overview (recap, stats, projects), Charts (usage, cost), Insights (heatmap, efficiency, tools, hot files) |
| ProjectDetail | `views/ProjectDetail.tsx` | 12 tabs: Sessions, Weekly, CLAUDE.md, Commands, Tools, MCP Servers, Subagents, Files, Memory, Todos, Commits, Settings |
| Dashboard | `views/Dashboard.tsx` | 3 tabs: Home (budget, stats, weekly recap, projects), Analytics (spend/patterns/tools/efficiency with a 7/30/90-day range control), Sessions (cross-project browser + prompt search) |
| ProjectDetail | `views/ProjectDetail.tsx` | 5 tabs: Overview (stat cards, recent sessions, quick links), Sessions (day-grouped list + subagent toggle + detail), Activity (usage trend, files, tools, commits), Setup (CLAUDE.md, memory, commands, MCP, automation), Work (plans + todos — hidden when both empty) |
| Sidebar | `views/Sidebar.tsx` | Compact project list grouped by Active/Recent/Older with stats |

**Components (all in `components/`):**

| Component | Used in |
|---|---|
| `SessionList.tsx`, `SessionDetail.tsx` | ProjectDetail → Sessions tab |
| `WeeklyStatsTab.tsx` | ProjectDetail → Weekly tab |
| `MarkdownView.tsx` (+ `CommandBlock`) | ProjectDetail → CLAUDE.md & Commands tabs |
| `ToolUsageBar.tsx` | ProjectDetail → Tools tab, Dashboard → Insights |
| `UsageLineChart.tsx` | Dashboard → Charts (30-day line) |
| `ProjectBarChart.tsx` | Dashboard → Charts (by project) |
| `HeatmapGrid.tsx` | Dashboard → Insights |
| `PatternChart.tsx` | Dashboard → Insights |
| `ProductivityChart.tsx` | Dashboard → Insights |
| `EfficiencyCards.tsx` | Dashboard → Insights |
| `HotFilesList.tsx` | Dashboard → Insights |
| `RecentChanges.tsx` | Dashboard → Insights |
| `ProjectCard.tsx` | Dashboard → Overview |
| `PromptSearch.tsx` | Dashboard → Search tab |
| `ActiveSessionCard.tsx`, `LiveSessionBanner.tsx` | Dashboard → Overview |
| `ActivityFeed.tsx` | Dashboard |
| `SessionDetail.tsx` (+ `modelLabel`, `modelBadgeColor`) | ProjectDetail → Sessions tab; includes the copy-resume-command button |
| `SessionsBrowser.tsx` | Dashboard → Sessions tab (cross-project rows + backend-served prompt search) |
| `WeeklyStatsTab.tsx` | ProjectDetail → Activity (usage trend) |
| `MarkdownView.tsx` (+ `CommandBlock`) | ProjectDetail → Setup (CLAUDE.md, memory, commands) |
| `ToolUsageBar.tsx` | ProjectDetail → Activity, Dashboard → Analytics |
| `UsageLineChart.tsx` | Dashboard → Analytics (30-day line) |
| `ProjectBarChart.tsx` | Dashboard → Analytics (by project) |
| `HeatmapGrid.tsx` | Dashboard → Analytics |
| `PatternChart.tsx` | Dashboard → Analytics |
| `ProductivityChart.tsx` | Dashboard → Analytics |
| `EfficiencyCards.tsx` | Dashboard → Analytics |
| `HotFilesList.tsx` | Dashboard → Analytics |
| `RecentChanges.tsx` | Dashboard → Analytics |
| `PromptSearch.tsx` | Deprecated (superseded by `SessionsBrowser`); pending deletion |

### Message Protocol

**Backend → Webview:**
- `{ type: 'stateUpdate', payload: {...} }` — full or partial state refresh
- `{ type: 'liveEvent', payload: { type, tool?, projectId?, sessionId?, timestamp } }` — real-time hook events
- `{ type: 'stateUpdate', payload: {...} }` — full or partial state refresh (dashboard payload includes `showTour`)
- `{ type: 'liveEvent', payload: { type, tool?, projectId?, sessionId?, timestamp } }` — real-time hook events (App.tsx ignores these for state; the store follows each with a debounced `stateUpdate`)
- `{ type: 'sessionTurns', sessionId, turns[] }` — lazy-loaded turn data
- `{ type: 'allSessions', sessions[] }` — cross-project `SessionRow[]` for the dashboard Sessions tab
- `{ type: 'promptSearchResults', query, results[] }` — backend-served prompt search
- `{ type: 'selectSession', sessionId }` — deep-link: select a session in an already-open ProjectPanel

**Webview → Backend:**
- `{ type: 'openDashboard' }` / `{ type: 'openProject', projectId }`
- `{ type: 'openDashboard' }` / `{ type: 'openProject', projectId, sessionId? }`
- `{ type: 'getSessionTurns', sessionId }` — triggers `sessionTurns` response
- `{ type: 'getAllSessions' }` / `{ type: 'searchPrompts', query }` — dashboard Sessions tab
- `{ type: 'exportSessions', format: 'json' | 'csv' }`
- `{ type: 'setBudget' }` — opens the budget input box; `{ type: 'dismissTour' }`; `{ type: 'refresh' }`
- `{ type: 'openFile', path }` / `{ type: 'openFolder', path }` — every rendered file/folder is a door

### State Management

Expand All @@ -160,7 +166,7 @@ Session turns are **lazy-loaded**: initial state ships sessions with `turns: []`
- **ClaudeCommit** — `{ hash, shortHash, author, date, subject, filesChanged }`
- **HookConfig** — `{ event, matcher?, command }`

Other types: `DailyUsage`, `ProjectUsage`, `HeatmapCell`, `PatternCount`, `ToolUsageStat`, `HotFile`, `ProjectedCost`, `StreakData`, `EfficiencyStats`, `WeeklyRecap`, `RecentFileChange`, `ProductivityHour`, `BudgetStatus`, `ProjectFile`, `ProjectToolCall`, `WeeklyProjectStats`, `McpServer`, `PromptSearchResult`
Other types: `DailyUsage`, `ProjectUsage`, `HeatmapCell`, `PatternCount`, `ToolUsageStat`, `HotFile`, `ProjectedCost`, `StreakData`, `EfficiencyStats`, `WeeklyRecap`, `RecentFileChange`, `ProductivityHour`, `BudgetStatus`, `ProjectFile`, `ProjectToolCall`, `WeeklyProjectStats`, `McpServer`, `PromptSearchResult`, `SessionRow`. Session/SessionRow also carry `pricingConfidence: 'exact' | 'fallback'`.

### How to Add a New ProjectDetail Tab

Expand All @@ -180,7 +186,7 @@ Other types: `DailyUsage`, `ProjectUsage`, `HeatmapCell`, `PatternCount`, `ToolU
2. **Add to `DashboardPanel.buildState()`** return object
3. **Add interface** to `types.ts`, add prop to `Dashboard.tsx`
4. **Add to `App.tsx`** dashboard destructuring
5. **Render** in the appropriate tab (Overview/Charts/Insights) in `Dashboard.tsx`
5. **Render** in the appropriate tab (Home/Analytics/Sessions) in `Dashboard.tsx`

### Testing

Expand Down
12 changes: 11 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,17 @@ chore: bump vsce to 2.24

Keep the subject line under 72 characters. Use the body for the "why" when needed.

Running `npm install` in the repo configures a local `commit-msg` hook that rejects non-conventional commit subjects before Git creates the commit.
Running `npm install` configures the repository's native Git hooks from `.githooks`:

| Hook | Checks |
|---|---|
| `pre-commit` | Staged whitespace errors and TypeScript in the extension and webview |
| `commit-msg` | Conventional Commit format and the 72-character subject limit |
| `pre-push` | TypeScript, coverage thresholds, all tests, and the production build |

Run `npm run hooks:install` to restore the hook configuration manually. Run `npm run verify:quick` for the pre-commit checks or `npm run verify` for the complete pre-push/CI suite.

Git hooks are a fast local safeguard, while GitHub Actions repeats the commit validation and full verification for pull requests and pushes to `main` or `dev`.

---

Expand Down
Loading
Loading