Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
33d0592
chore(cli): prepare first public npm release
WilliamAGH Sep 1, 2026
97d00cc
feat(cli): explicit ask and auth command hierarchy
WilliamAGH Sep 1, 2026
655f3d0
feat(cli): expose source-aware knowledge inventory
WilliamAGH Sep 1, 2026
8b3ab61
fix(knowledge): return defensive metadata copies
WilliamAGH Sep 1, 2026
f651698
docs(readme): improve JavaChat onboarding
WilliamAGH Sep 1, 2026
8b26605
docs(readme): clarify retrieval and session behavior
WilliamAGH Sep 1, 2026
96daafa
docs(cli): define JavaChat at every public entry point
WilliamAGH Sep 1, 2026
aa8cd0e
feat(ingestion): register phase-one research sources
WilliamAGH Sep 1, 2026
41b894b
Merge remote-tracking branch 'origin/dev' into dev
WilliamAGH Sep 1, 2026
e6844cf
feat(cli): add self-update and clearer source gaps
WilliamAGH Sep 1, 2026
1a1befd
fix(ingestion): snapshot local staging scripts
WilliamAGH Sep 1, 2026
478afdc
fix(auth): retain API-key availability after revoked responses
WilliamAGH Sep 1, 2026
ce8b434
fix(auth): log unavailable API-key revocations
WilliamAGH Sep 1, 2026
ccf4491
fix(ingestion): surface local file enumeration failures
WilliamAGH Sep 1, 2026
1d56c32
fix(docs): ignore blank citation base overrides
WilliamAGH Sep 1, 2026
1564c5e
fix(ingestion): preserve document citation provenance
WilliamAGH Sep 1, 2026
907790e
fix(auth): satisfy lifecycle fixture lint
WilliamAGH Sep 1, 2026
a92a46e
merge(dev): preserve concurrent CLI and issue fixes
WilliamAGH Sep 1, 2026
cacf649
fix(retrieval): preserve exact and adjacent version intent
WilliamAGH Sep 1, 2026
70323c1
feat(ingestion): add Kotlin Nothing API corpus
WilliamAGH Sep 1, 2026
c4f4604
fix(ingestion): remove skip-link text before flattening
WilliamAGH Sep 1, 2026
84dd50a
Merge branch 'worktree-issues-208-213-final' into dev
WilliamAGH Sep 1, 2026
724c810
fix(markdown): preserve list and inline-code boundaries
WilliamAGH Sep 1, 2026
79f3a1f
fix(streaming): enforce visible-content and event boundaries
WilliamAGH Sep 1, 2026
816a734
fix(ingestion): complete batch state and escape repository paths
WilliamAGH Sep 1, 2026
ac62ca7
fix(retrieval): enforce strict relevance and proxy trust
WilliamAGH Sep 1, 2026
c4e3677
Merge branch 'worktree-issues-148-171' into dev
WilliamAGH Sep 1, 2026
7a3dc99
fix(chat): make source-gap notes deterministic
WilliamAGH Sep 2, 2026
4f3f9a9
Merge branch 'worktree-javachat-deterministic-source-note' into dev
WilliamAGH Sep 2, 2026
bdc7b28
fix(lint): name Clicky initializer cleanup precisely
WilliamAGH Sep 2, 2026
36a16ed
docs(skills): shorten JavaChat selection description
WilliamAGH Sep 10, 2026
2fa7f69
ci: publish multi-platform image and deploy its digest to Dokploy
WilliamAGH Oct 2, 2026
e6f4b8a
build: upgrade Gradle wrapper to 9.8.0
WilliamAGH Oct 2, 2026
10d1817
ci: pull deployed images through the registry.haiku.host registry row
WilliamAGH Oct 2, 2026
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
128 changes: 128 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ on:
permissions:
contents: read

env:
IMAGE_REGISTRY: registry.haiku.host
IMAGE_REPOSITORY: registry.haiku.host/java-chat/java-chat

jobs:
writer-lease-portability:
runs-on: macos-15
Expand Down Expand Up @@ -309,3 +313,127 @@ jobs:
--project-name java-chat-ci \
--file infra/docker-compose-qdrant.yml \
down --volumes

# One multi-platform image per environment: each platform builds natively,
# pushes by digest, and the manifest job joins the digests under one index.
image:
if: github.event_name == 'push' && (github.ref_name == 'dev' || github.ref_name == 'main')
needs:
- docker-smoke
environment: ${{ github.ref_name == 'main' && 'production' || 'development' }}
strategy:
fail-fast: true
matrix:
include:
- platform: linux/amd64
runner: ubuntu-24.04
- platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 30

steps:
- name: Checkout code
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Log in to the image registry
uses: docker/login-action@v4
with:
registry: ${{ env.IMAGE_REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}

- name: Build and push platform image by digest
id: build
uses: docker/build-push-action@v7
with:
context: .
platforms: ${{ matrix.platform }}
build-args: |
SOURCE_COMMIT=${{ github.sha }}
VITE_CLERK_PUBLISHABLE_KEY=${{ vars.VITE_CLERK_PUBLISHABLE_KEY }}
outputs: type=image,name=${{ env.IMAGE_REPOSITORY }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ github.ref_name }}-${{ matrix.runner }}
cache-to: type=gha,mode=max,scope=${{ github.ref_name }}-${{ matrix.runner }}

- name: Record platform digest
env:
PLATFORM_DIGEST: ${{ steps.build.outputs.digest }}
run: |
mkdir -p "${RUNNER_TEMP}/digests"
touch "${RUNNER_TEMP}/digests/${PLATFORM_DIGEST#sha256:}"

- name: Upload platform digest
uses: actions/upload-artifact@v6
with:
name: image-digest-${{ matrix.runner }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1

deploy:
needs:
- image
runs-on: ubuntu-24.04
timeout-minutes: 30
environment:
name: ${{ github.ref_name == 'main' && 'production' || 'development' }}
url: ${{ github.ref_name == 'main' && 'https://javachat.ai' || 'https://dev.javachat.ai' }}
concurrency:
group: dokploy-${{ github.ref_name }}
cancel-in-progress: false
Comment on lines +387 to +389

steps:
- name: Checkout code
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Download platform digests
uses: actions/download-artifact@v8
with:
path: ${{ runner.temp }}/digests
pattern: image-digest-*
merge-multiple: true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Log in to the image registry
uses: docker/login-action@v4
with:
registry: ${{ env.IMAGE_REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}

- name: Publish multi-platform image index
id: index
working-directory: ${{ runner.temp }}/digests
env:
IMAGE_TAG: ${{ env.IMAGE_REPOSITORY }}:${{ github.ref_name }}-${{ github.sha }}
run: |
set -euo pipefail
platform_images=()
for platform_digest in *; do
platform_images+=("${IMAGE_REPOSITORY}@sha256:${platform_digest}")
done
docker buildx imagetools create --tag "$IMAGE_TAG" "${platform_images[@]}"
docker buildx imagetools inspect "$IMAGE_TAG"
index_digest="$(docker buildx imagetools inspect "$IMAGE_TAG" --format '{{json .Manifest}}' | jq --exit-status --raw-output .digest)"
printf 'reference=%s@%s\n' "$IMAGE_REPOSITORY" "$index_digest" >>"$GITHUB_OUTPUT"

- name: Deploy image digest to Dokploy
env:
DOKPLOY_URL: ${{ secrets.DOKPLOY_URL }}
DOKPLOY_API_KEY: ${{ secrets.DOKPLOY_API_KEY }}
DOKPLOY_APPLICATION_ID: ${{ vars.DOKPLOY_APPLICATION_ID }}
DOKPLOY_REGISTRY_ID: ${{ vars.DOKPLOY_REGISTRY_ID }}
IMAGE_REFERENCE: ${{ steps.index.outputs.reference }}
SOURCE_COMMIT: ${{ github.sha }}
PUBLIC_URL: ${{ github.ref_name == 'main' && 'https://javachat.ai' || 'https://dev.javachat.ai' }}
run: scripts/deploy_dokploy_image.sh
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ bin/
!cli/bin/**
dogfood-output/
lesson-curation-work/
/.local-embedding-staging-run.*/

### Build artifacts (root level) ###
BOOT-INF/
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ alwaysApply: true
## [DP1] Deployment Ownership

- [DP1a] **Dokploy Is The Sole Deployment Owner**: Deploy Java Chat only through the existing Dokploy applications. Never create or retain a Coolify application, service, container, webhook, or deploy hook for this repository. Never invoke Coolify to deploy, start, or restart Java Chat; use its API only for read-only absence verification and the exact stale-resource retirement required by [DP1d].
- [DP1b] **Branch Routing**: Configure Dokploy push auto-deploy so `dev` owns `dev.javachat.ai` and `main` owns `javachat.ai` plus `www.javachat.ai`. Never route production deployment from `prod` or another branch.
- [DP1b] **Branch Routing**: `.github/workflows/build.yml` publishes one linux/amd64 + linux/arm64 image per push to `dev` or `main` and deploys its index digest to the Dokploy application named by the GitHub environment variable `DOKPLOY_APPLICATION_ID` (`development` for `dev` → `dev.javachat.ai`; `production` for `main` → `javachat.ai` plus `www.javachat.ai`). Both applications use `sourceType: docker`; never return them to Dokploy Git builds, which emit a single architecture. Never route production deployment from `prod` or another branch.
- [DP1c] **No Push-To-Coolify Path**: Never add or retain a GitHub Actions workflow, repository webhook, Git hook, script, secret, variable, or external integration that sends a repository push to Coolify.
- [DP1d] **Retire Stale Owners**: Treat any Java Chat Coolify resource as an incident-grade stale deployment owner. Stop and delete its exact application and containers after proving the matching Dokploy application is healthy and receiving the intended domain.
- [DP1e] **Release Verification**: Before completing a release, prove tracked GitHub Actions, Git hooks, and scripts contain zero Coolify deployment paths; the GitHub repository has zero Coolify-targeting webhooks and zero Coolify-named Actions secrets, variables, or integrations; Coolify has zero Java Chat applications or containers; both Dokploy applications use push auto-deploy on their required branches and their latest deployment receipts name the released commits; and `https://dev.javachat.ai/actuator/health`, `https://javachat.ai/actuator/health`, and `https://www.javachat.ai/actuator/health` each return JSON field `status` equal to `UP` through the matching Dokploy application.
- [DP1e] **Release Verification**: Before completing a release, prove tracked GitHub Actions, Git hooks, and scripts contain zero Coolify deployment paths; the GitHub repository has zero Coolify-targeting webhooks and zero Coolify-named Actions secrets, variables, or integrations; Coolify has zero Java Chat applications or containers; both Dokploy applications run the CI-published image digest for their branch and `/actuator/info` `deployment.commit` names the released commit; and `https://dev.javachat.ai/actuator/health`, `https://javachat.ai/actuator/health`, and `https://www.javachat.ai/actuator/health` each return JSON field `status` equal to `UP` through the matching Dokploy application.

## [CC1] Clean Code & DDD (Mandatory)

Expand Down
166 changes: 123 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,81 +1,161 @@
# Java Chat
# JavaChat

[![Java Chat Application](src/main/resources/static/images/java-chat-app.png)](https://javachat.ai)
[![JavaChat application](src/main/resources/static/images/java-chat-app.png)](https://javachat.ai)

Learn programming in Java and more with **streaming answers**, **citations**, and **guided lessons** grounded with a deep knowledge base of ingested documentation (RAG).
JavaChat is a web application and command-line research assistant for software developers. It
searches a version-aware library of technical documentation before generating an answer. Use it to
learn an unfamiliar API, compare documented behavior across releases, or find the commands,
configuration, and migration details needed to move development work forward. Answers can include
links to the retrieved pages, and JavaChat is designed to make missing source coverage visible.

Built with Spring Boot + WebFlux, Svelte, and Qdrant.
Use JavaChat in whichever form fits your workflow:

## Features
- **Web:** open [javachat.ai](https://javachat.ai) for chat and guided lessons.
- **Terminal:** install the [JavaChat CLI](cli/README.md) to research from any directory and receive
source links when retrieval finds supporting documentation.
- **Self-hosted development:** run the Svelte and Spring Boot application locally and connect it to
Qdrant and an OpenAI-compatible gateway.

- Streaming chat over SSE (`/api/chat/stream`) with a final `citation` event
- Guided learning mode (`/learn`) with lesson-scoped chat (`/api/guided/*`)
- Documentation ingestion pipeline (fetch → chunk → embed → dedupe → index)
- Chunking uses JTokkit's CL100K_BASE tokenizer (GPT-3.5/4 style) for token counting
- Embeddings use strict provider selection and fail fast when the provider is unavailable (no runtime fallbacks)
## What JavaChat helps you do

## Quick start
- **Learn APIs and platforms:** request a focused explanation, short example, applicable version,
and source pages you can inspect.
- **Research technical decisions:** investigate releases, migration paths, commands, configuration
rules, prerequisites, and operational caveats.
- **Move real work forward:** keep the CLI beside your code while implementing an integration,
debugging a library, planning an upgrade, or configuring a deployment.
- **See the source boundary:** JavaChat can retrieve the exact requested release or nearby releases
of the same technology when they are indexed, and can report when the requested evidence is absent.

## Example queries possible with JavaChat

- “What does `Thread.ofVirtual()` return in Java 25, and what is the shortest documented example?”
- “When should I use `MULTISET` instead of a flat join in jOOQ 3.21.7?”
- “How do I add a 30-second delay between tasks in a Docker Swarm rolling update?”
- “What defaults can I configure with Spring AI 1.1.2 `ChatClient.Builder`?”
- “How does a Clerk session token differ from a backend Secret Key?”

Representative coverage in the shared JavaChat knowledge index includes:

- **Languages:** Java SE 21, 25, and 26; Kotlin 2.4.10; Groovy 5.0.7; Scala 3; and Python 3.14.7.
- **Libraries:** Spring Framework 7.0.7; Spring AI 1.1.x; four indexed Jackson 2.x/3.x releases;
jOOQ 3.21.7; HikariCP 7.x; and Lombok 1.18.46.
- **Platforms:** PostgreSQL 17/18, Docker, Cloudflare, Clerk, Dokploy, Traefik, Doppler, and Infisical.
- **Repository snapshots:** OpenAI Java, Anthropic SDKs, Langfuse, Dokploy, Infisical, and Traefik.

Run `javachat list all` for the complete current inventory. Chat retrieves supporting material from
the documentation collections.

## Install the CLI

The CLI supports macOS and Linux and requires Node.js 24.18 or newer.

```bash
npm install --global @wcallahan/javachat-cli
javachat auth login
javachat ask "How do Java records work?"
```

Upgrade an npm-installed copy with `javachat update`. The command updates the project-local package
that owns the invoked binary, or the active global npm installation.

Use `javachat list all` to see the documentation packages, source repositories, versions, and
revisions available on the selected deployment. See the [CLI README](cli/README.md) for all
commands, non-interactive authentication, local development, and package verification.

## How it works

1. JavaChat searches the documentation index for material relevant to the question and requested
version.
2. The strongest matches are supplied as context for the answer.
3. The answer streams to the web app or CLI, with source links when retrieval returns citations.
When matching source documents are absent, JavaChat still answers from the model's general
knowledge and labels that limitation in the answer.
4. Guided lessons keep their own conversations, while ordinary chat can continue a prior session.

The repository also contains the pipeline that fetches, chunks, embeds, deduplicates, and indexes
the source material. See [Architecture](docs/architecture.md) for Qdrant, dense and sparse search,
reciprocal-rank fusion, and streaming details.

## Run the application locally

### Prerequisites

This project uses **Gradle Toolchains** with **BellSoft Liberica JDK 25** and **mise** (or **asdf**) for reproducible builds.
- BellSoft Liberica JDK 25
- Node.js 24.18
- Docker, when running the local Qdrant service
- `mise` or another Java version manager for the pinned development toolchain

#### Option 1: Using mise (recommended)
Install the pinned Java version:

```bash
# Install mise if you don't have it: https://mise.jdnow.dev/
mise install
```

#### Option 2: Using asdf
Install and select the frontend's pinned Node.js version with `nvm`:

```bash
# Install asdf if you don't have it: https://asdf-vm.com/
asdf plugin add java https://github.com/halcyon/asdf-java.git
asdf install
nvm install 24.18.0
nvm use 24.18.0
```

**What happens**: Gradle Toolchains will auto-download BellSoft Liberica JDK 25 on first build if not present locally. The `mise`/`asdf` setup ensures your shell and IDE (IntelliJ) use the correct Java version.

### Running
Then configure and start the full application:

```bash
cp .env.example .env
# edit .env: set SPRING_PROFILE=local, shared generation collections, local state paths,
# and the shared gateway OPENAI_BASE_URL/OPENAI_API_KEY
make compose-up # fresh Qdrant 1.18.3 generation-specific volume
make dev # bootstraps missing schemas only on loopback Qdrant
# Set OPENAI_BASE_URL and OPENAI_API_KEY in .env.
make compose-up
make dev
```

Open `http://localhost:8085/`.
Open [http://localhost:8085](http://localhost:8085). The local commands create missing Qdrant
schemas only for loopback connections; remote Qdrant deployments remain fail-closed.

For a fuller explanation of the environment variables, local collection setup, and optional
documentation ingestion, read [Getting started](docs/getting-started.md).

## Index documentation (RAG)
## Useful development commands

```bash
# Run only after the generation configuration and gateway/Qdrant preflight are valid.
make full-pipeline # fetch all docs + ingest into Qdrant
make process-all # ingest only (incremental, upload to Qdrant)
REPO_PATH=/absolute/path/to/repository make process-github-repo
REPO_URL=https://github.com/owner/repository make process-github-repo
SYNC_EXISTING=1 make process-github-repo
make help # list repository-owned commands
make build # build the frontend and backend
make test # run shell contracts and JVM tests
make lint # run frontend and JVM static analysis
make health # check the running application
```

Ingestion writes dense + BM25 sparse vectors to four hybrid Qdrant collections, queried via RRF fusion.
To work only on the npm package:

Full command reference (scrape flags, doc set filtering, HTTP API, full re-ingest):
**[docs/pipeline-commands.md](docs/pipeline-commands.md)**
```bash
cd cli
npm install
npm run dev -- --help
npm test
```

GitHub source repository ingestion details:
**[docs/github-repository-ingestion.md](docs/github-repository-ingestion.md)**
## Index documentation and repositories

## Documentation
Documentation ingestion is optional for ordinary application development. Run it only after the
gateway and Qdrant preflight checks pass.

Start with `docs/README.md`.
```bash
make full-pipeline
REPO_PATH=/absolute/path/to/repository make process-github-repo
REPO_URL=https://github.com/owner/repository make process-github-repo
SYNC_EXISTING=1 make process-github-repo
```

See [Pipeline commands](docs/pipeline-commands.md) for source selection and ingestion controls, and
[GitHub repository ingestion](docs/github-repository-ingestion.md) for repository synchronization.

## Contributing
## Documentation

See `CONTRIBUTING.md`.
- [Documentation index](docs/README.md)
- [Getting started](docs/getting-started.md)
- [Architecture](docs/architecture.md)
- [HTTP and streaming API](docs/api.md)
- [Contributing](CONTRIBUTING.md)

## License

See `LICENSE.md`.
See [LICENSE.md](LICENSE.md).
Loading
Loading