Skip to content
Open
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
12 changes: 12 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"permissions": {
"allow": [
"mcp__playwright__browser_navigate",
"mcp__playwright__browser_take_screenshot",
"mcp__playwright__browser_evaluate",
"Bash(UV_LINK_MODE=copy uv run --with pytest pytest tests/test_vector_store.py -v)",
"Bash(UV_LINK_MODE=copy uv run --with pytest pytest tests/ -v)",
"Bash(git add *)"
]
}
}
12 changes: 12 additions & 0 deletions .claude/skills/commit/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: commit
description: Stage all modified tracked files and create a git commit with an appropriate message
disable-model-invocation: true
---

Stage all modified tracked files (do not include untracked files like .claude/, .playwright-mcp/, or image files unless they are clearly part of the project). Then review the diff and write a concise commit message that describes what changed and why. Format the commit message as a short summary line followed by a bullet list of key changes if needed.

Always co-author the commit:
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Use a HEREDOC to pass the commit message to avoid formatting issues.
12 changes: 12 additions & 0 deletions .claude/skills/log/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: log
description: Logs the most recent explanation or answer from the conversation into notes.md
disable-model-invocation: true
---

Look at the most recent explanation or answer you gave in this conversation. Extract the topic and key points, then append a new entry to `notes.md` in the repo root using this format:

## <topic title>
<concise explanation of what was discovered, in plain language>

If `notes.md` does not exist, create it with a `# Notes` heading first. Do not overwrite or modify any existing entries.
32 changes: 32 additions & 0 deletions .claude/skills/wrap/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
name: wrap
description: Log a session summary to memory at the end of a working session
disable-model-invocation: true
---

Run `date` to get the current date and time. Then review the conversation and write a session summary to:

`C:\Users\lcplu\.claude\projects\C--Users-lcplu-OneDrive-Documents-ClaudeCodeLearning\memory\project_session_progress.md`

Overwrite the entire file with the following format:

```
---
name: Session Progress & Next Steps
description: What was done in the latest session and what to pick up next
type: project
---

## Session — <date and time from the date command>

### Accomplished
- <bullet list of what was done this session>

### Where We Left Off
- <what we were doing at the end of the session>

### Next Steps
- <planned follow-ups or things to explore next>
```

Be specific and concise. This file is automatically loaded by Claude at the start of the next session.
44 changes: 44 additions & 0 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Claude Code Review

on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"

jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'

runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
plugins: 'code-review@claude-code-plugins'
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options

50 changes: 50 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Claude Code

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]

jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read

# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'

# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr *)'

97 changes: 97 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Running the Application

Requires Python 3.13+ and `uv`. On Windows, use Git Bash. Always use `uv` to run Python and manage all dependencies — never use `pip` directly.

```bash
# Install uv (one-time, run in Git Bash)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install dependencies
uv sync

# Copy and populate the environment file
cp .env.example .env # then add ANTHROPIC_API_KEY

# Start the server (from repo root)
./run.sh
# or manually:
cd backend && uv run uvicorn app:app --reload --port 8000
```

App runs at `http://localhost:8000`. On startup, course documents in `/docs` are automatically loaded into ChromaDB (skipping any already indexed).

## Architecture

This is a full-stack RAG (Retrieval-Augmented Generation) application. The FastAPI backend serves both the API and the static frontend from a single port.

### Query Flow

1. Frontend (`frontend/script.js`) POSTs `{ query, session_id }` to `/api/query`
2. `app.py` routes to `RAGSystem.query()`, creating a session if needed
3. `RAGSystem` fetches conversation history from `SessionManager` and calls `AIGenerator.generate_response()` with the `search_course_content` tool available
4. **First Claude call** — Claude decides to answer directly or invoke the search tool
5. If tool use: `CourseSearchTool` calls `VectorStore.search()`, which optionally resolves a fuzzy course name via semantic search on the `course_catalog` collection, then queries the `course_content` collection using `sentence-transformers` embeddings
6. **Second Claude call** — Claude synthesizes a final answer from the retrieved chunks
7. Sources (course + lesson labels) and the answer are returned to the frontend

### Key Design Decisions

- **Two ChromaDB collections**: `course_catalog` (course-level metadata for fuzzy name resolution) and `course_content` (chunked lesson text for semantic search)
- **Agentic tool loop**: Claude decides whether to search; the tool call and result are injected back into the message thread before a second API call forces a final text response
- **Session history** is passed in the system prompt as formatted text (not as message-role history), capped at `MAX_HISTORY=2` exchanges
- **Course title is the unique ID** in ChromaDB — re-ingesting the same course is a no-op
- **ChromaDB persists** to `backend/chroma_db/`. To force a full re-index, delete that directory or call `vector_store.clear_all_data()`

### Extending the Search Tool

New tools should implement the `Tool` ABC in `backend/search_tools.py` (implement `get_tool_definition()` and `execute()`), then register with `ToolManager.register_tool()` in `RAGSystem.__init__()`. The tool definition must follow the Anthropic tool schema format.

### Document Format

Course `.txt` files in `/docs` must follow this structure:
```
Course Title: <title>
Course Link: <url>
Course Instructor: <name>
Lesson 0: <lesson title>
Lesson Link: <url>
<lesson content>
Lesson 1: <lesson title>
...
```

## Code Quality

### Formatting — Black

All Python code is formatted with [Black](https://black.readthedocs.io/). Configuration lives in `pyproject.toml` under `[tool.black]` (line length 88, target Python 3.13).

```bash
# Auto-format all backend Python files
bash scripts/format.sh
# or directly:
uv run black backend/

# Check formatting without changing files (CI-safe)
bash scripts/check_quality.sh
# or directly:
uv run black --check backend/
```

Always run `bash scripts/format.sh` before committing Python changes. PRs with unformatted code will fail the quality check.

### Configuration (`backend/config.py`)

| Setting | Default | Purpose |
|---|---|---|
| `ANTHROPIC_MODEL` | `claude-sonnet-4-20250514` | Model for generation |
| `EMBEDDING_MODEL` | `all-MiniLM-L6-v2` | Sentence transformer for embeddings |
| `CHUNK_SIZE` | `800` | Max characters per chunk |
| `CHUNK_OVERLAP` | `100` | Character overlap between chunks |
| `MAX_RESULTS` | `5` | Max chunks returned per search |
| `MAX_HISTORY` | `2` | Conversation exchanges retained |
| `CHROMA_PATH` | `./chroma_db` | ChromaDB persistence directory |
Loading