Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/1ike-m0on_devcontext.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
41 KiB
JSON

{"repo": "1ike-m0on/devcontext", "files": {"docker-compose.yml": {"cf4cee8e3f2d701e5325b875bf513a0d3fdf8105": {"t": 1785746355.606039, "n": 2119, "h": "c690848b49b15a1e", "c": "services:\n backend:\n build:\n context: .\n dockerfile: Dockerfile\n ports:\n - \"18080:18080\"\n environment:\n SPRING_DATASOURCE_URL: jdbc:sqlite:/app/data/devcontext.sqlite\n DEVCONTEXT_LLM_PROVIDER: ${DEVCONTEXT_LLM_PROVIDER:-mock}\n GEMINI_API_KEY: ${GEMINI_API_KEY:-}\n GEMINI_MODEL: ${GEMINI_MODEL:-gemini-2.0-flash}\n GEMINI_BASE_URL: ${GEMINI_BASE_URL:-https://generativelanguage.googleapis.com/v1beta}\n GEMINI_TIMEOUT: ${GEMINI_TIMEOUT:-60s}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_MODEL: ${DEEPSEEK_MODEL:-deepseek-chat}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n DEEPSEEK_TIMEOUT: ${DEEPSEEK_TIMEOUT:-120s}\n DEVCONTEXT_VECTOR_PROVIDER: ${DEVCONTEXT_VECTOR_PROVIDER:-qdrant}\n QDRANT_BASE_URL: http://qdrant:6333\n QDRANT_API_KEY: ${QDRANT_API_KEY:-}\n QDRANT_DISTANCE: ${QDRANT_DISTANCE:-Cosine}\n QDRANT_TIMEOUT: ${QDRANT_TIMEOUT:-10s}\n volumes:\n - ./data/devcontext:/app/data\n - ${DEVCONTEXT_PROJECTS_DIR:-./workspace/projects}:/workspace/projects\n depends_on:\n qdrant:\n condition: service_healthy\n healthcheck:\n test: [\"CMD-SHELL\", \"curl -fsS http://localhost:18080/api/health >/dev/null\"]\n interval: 10s\n timeout: 5s\n retries: 12\n start_period: 30s\n\n frontend:\n build:\n context: ./frontend\n dockerfile: Dockerfile\n ports:\n - \"5173:80\"\n depends_on:\n backend:\n condition: service_healthy\n healthcheck:\n test: [\"CMD-SHELL\", \"curl -fsS http://localhost/ >/dev/null\"]\n interval: 10s\n timeout: 5s\n retries: 12\n start_period: 10s\n\n qdrant:\n image: qdrant/qdrant:v1.18.1\n ports:\n - \"6333:6333\"\n - \"6334:6334\"\n volumes:\n - ./data/qdrant:/qdrant/storage\n healthcheck:\n test: >\n timeout 5 bash -ec 'exec 3<>/dev/tcp/127.0.0.1/6333;\n printf \"GET /healthz HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n\" >&3;\n grep -q \"200 OK\" <&3'\n interval: 10s\n timeout: 5s\n retries: 12\n start_period: 10s\n"}, "308e32e695394696de3dec02e3ec583df041f0e2": {"t": 1785762436.2749128, "n": 1527, "h": "bdf6fea5b6c69a60", "c": "services:\n backend:\n build:\n context: .\n dockerfile: Dockerfile\n container_name: devcontext-backend\n ports:\n - \"18080:18080\"\n environment:\n SPRING_DATASOURCE_URL: jdbc:sqlite:/app/data/devcontext.sqlite\n DEVCONTEXT_LLM_PROVIDER: ${DEVCONTEXT_LLM_PROVIDER:-mock}\n GEMINI_API_KEY: ${GEMINI_API_KEY:-}\n GEMINI_MODEL: ${GEMINI_MODEL:-gemini-2.0-flash}\n GEMINI_BASE_URL: ${GEMINI_BASE_URL:-https://generativelanguage.googleapis.com/v1beta}\n GEMINI_TIMEOUT: ${GEMINI_TIMEOUT:-60s}\n DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}\n DEEPSEEK_MODEL: ${DEEPSEEK_MODEL:-deepseek-chat}\n DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}\n DEEPSEEK_TIMEOUT: ${DEEPSEEK_TIMEOUT:-120s}\n DEVCONTEXT_VECTOR_PROVIDER: ${DEVCONTEXT_VECTOR_PROVIDER:-qdrant}\n QDRANT_BASE_URL: http://qdrant:6333\n QDRANT_API_KEY: ${QDRANT_API_KEY:-}\n QDRANT_DISTANCE: ${QDRANT_DISTANCE:-Cosine}\n QDRANT_TIMEOUT: ${QDRANT_TIMEOUT:-10s}\n volumes:\n - ./data/devcontext:/app/data\n - ${DEVCONTEXT_PROJECTS_DIR:-./workspace/projects}:/workspace/projects\n depends_on:\n - qdrant\n\n frontend:\n build:\n context: ./frontend\n dockerfile: Dockerfile\n container_name: devcontext-frontend\n ports:\n - \"5173:80\"\n depends_on:\n - backend\n\n qdrant:\n image: qdrant/qdrant:latest\n container_name: devcontext-qdrant\n ports:\n - \"6333:6333\"\n - \"6334:6334\"\n volumes:\n - ./data/qdrant:/qdrant/storage\n"}}, "src/main/resources/application.yml": {"cf4cee8e3f2d701e5325b875bf513a0d3fdf8105": {"t": 1785746356.8361642, "n": 1181, "h": "59eea19200bb613f", "c": "spring:\n config:\n import:\n - optional:file:.env[.properties]\n - optional:file:./devcontext.local.yml\n - optional:file:./config/devcontext.local.yml\n application:\n name: devcontext\n datasource:\n url: jdbc:sqlite:data/devcontext.sqlite\n driver-class-name: org.sqlite.JDBC\n sql:\n init:\n mode: always\n schema-locations: classpath:db/schema.sql\n\nserver:\n port: 18080\n\ndevcontext:\n llm:\n provider: ${DEVCONTEXT_LLM_PROVIDER:mock}\n mock:\n model: ${DEVCONTEXT_MOCK_MODEL:mock-llm}\n gemini:\n api-key: ${GEMINI_API_KEY:}\n model: ${GEMINI_MODEL:gemini-2.0-flash}\n base-url: ${GEMINI_BASE_URL:https://generativelanguage.googleapis.com/v1beta}\n timeout: ${GEMINI_TIMEOUT:60s}\n deepseek:\n api-key: ${DEEPSEEK_API_KEY:}\n model: ${DEEPSEEK_MODEL:deepseek-chat}\n base-url: ${DEEPSEEK_BASE_URL:https://api.deepseek.com}\n timeout: ${DEEPSEEK_TIMEOUT:120s}\n vector:\n provider: ${DEVCONTEXT_VECTOR_PROVIDER:jdbc}\n qdrant:\n base-url: ${QDRANT_BASE_URL:http://localhost:6333}\n api-key: ${QDRANT_API_KEY:}\n distance: ${QDRANT_DISTANCE:Cosine}\n timeout: ${QDRANT_TIMEOUT:10s}\n"}, "2439bbc7de38fd9ec2383756edf6e698c9872414": {"t": 1785762415.6770327, "n": 1181, "h": "59eea19200bb613f", "c": "spring:\n config:\n import:\n - optional:file:.env[.properties]\n - optional:file:./devcontext.local.yml\n - optional:file:./config/devcontext.local.yml\n application:\n name: devcontext\n datasource:\n url: jdbc:sqlite:data/devcontext.sqlite\n driver-class-name: org.sqlite.JDBC\n sql:\n init:\n mode: always\n schema-locations: classpath:db/schema.sql\n\nserver:\n port: 18080\n\ndevcontext:\n llm:\n provider: ${DEVCONTEXT_LLM_PROVIDER:mock}\n mock:\n model: ${DEVCONTEXT_MOCK_MODEL:mock-llm}\n gemini:\n api-key: ${GEMINI_API_KEY:}\n model: ${GEMINI_MODEL:gemini-2.0-flash}\n base-url: ${GEMINI_BASE_URL:https://generativelanguage.googleapis.com/v1beta}\n timeout: ${GEMINI_TIMEOUT:60s}\n deepseek:\n api-key: ${DEEPSEEK_API_KEY:}\n model: ${DEEPSEEK_MODEL:deepseek-chat}\n base-url: ${DEEPSEEK_BASE_URL:https://api.deepseek.com}\n timeout: ${DEEPSEEK_TIMEOUT:120s}\n vector:\n provider: ${DEVCONTEXT_VECTOR_PROVIDER:jdbc}\n qdrant:\n base-url: ${QDRANT_BASE_URL:http://localhost:6333}\n api-key: ${QDRANT_API_KEY:}\n distance: ${QDRANT_DISTANCE:Cosine}\n timeout: ${QDRANT_TIMEOUT:10s}\n"}, "308e32e695394696de3dec02e3ec583df041f0e2": {"t": 1785762417.0456738, "n": 1019, "h": "d7a3fc6dd5933ba6", "c": "spring:\n config:\n import: optional:file:.env[.properties]\n application:\n name: devcontext\n datasource:\n url: jdbc:sqlite:data/devcontext.sqlite\n driver-class-name: org.sqlite.JDBC\n sql:\n init:\n mode: always\n schema-locations: classpath:db/schema.sql\n\nserver:\n port: 18080\n\ndevcontext:\n llm:\n provider: ${DEVCONTEXT_LLM_PROVIDER:mock}\n gemini:\n api-key: ${GEMINI_API_KEY:}\n model: ${GEMINI_MODEL:gemini-2.0-flash}\n base-url: ${GEMINI_BASE_URL:https://generativelanguage.googleapis.com/v1beta}\n timeout: ${GEMINI_TIMEOUT:60s}\n deepseek:\n api-key: ${DEEPSEEK_API_KEY:}\n model: ${DEEPSEEK_MODEL:deepseek-chat}\n base-url: ${DEEPSEEK_BASE_URL:https://api.deepseek.com}\n timeout: ${DEEPSEEK_TIMEOUT:120s}\n vector:\n provider: ${DEVCONTEXT_VECTOR_PROVIDER:jdbc}\n qdrant:\n base-url: ${QDRANT_BASE_URL:http://localhost:6333}\n api-key: ${QDRANT_API_KEY:}\n distance: ${QDRANT_DISTANCE:Cosine}\n timeout: ${QDRANT_TIMEOUT:10s}\n"}, "439128b6f615c7389a5d97dddd2daadedd37a27c": {"t": 1785762418.2077377, "n": 490, "h": "bca8311ef48a6257", "c": "spring:\n application:\n name: devcontext\n datasource:\n url: jdbc:sqlite:data/devcontext.sqlite\n driver-class-name: org.sqlite.JDBC\n sql:\n init:\n mode: always\n schema-locations: classpath:db/schema.sql\n\nserver:\n port: 18080\n\ndevcontext:\n llm:\n provider: ${DEVCONTEXT_LLM_PROVIDER:mock}\n gemini:\n api-key: ${GEMINI_API_KEY:}\n model: ${GEMINI_MODEL:gemini-2.0-flash}\n base-url: ${GEMINI_BASE_URL:https://generativelanguage.googleapis.com/v1beta}\n"}, "060fcbdb183a3232ca12329aa7f8d1bd07e54cbf": {"t": 1785762420.4639235, "n": 248, "h": "284787cf8b71434a", "c": "spring:\n application:\n name: devcontext\n datasource:\n url: jdbc:sqlite:data/devcontext.sqlite\n driver-class-name: org.sqlite.JDBC\n sql:\n init:\n mode: always\n schema-locations: classpath:db/schema.sql\n\nserver:\n port: 18080\n"}, "a4326c7c31e948fc045dd27946a901428a1d4f7d": {"t": 1785762422.4978607, "n": 67, "h": "e31221a207da5168", "c": "spring:\n application:\n name: devcontext\n\nserver:\n port: 18080\n"}, "aae4ae4fd8d2aac3b6ddc7780b4f9d81084a0a8b": {"t": 1785762424.8613803, "n": 67, "h": "541a88fd2a69235b", "c": "spring:\n application:\n name: devcontext\n\nserver:\n port: 8080\n\n"}}, ".github/workflows/ci.yml": {"cf4cee8e3f2d701e5325b875bf513a0d3fdf8105": {"t": 1785747034.522561, "n": 6504, "h": "ed3668c4be4cad7f", "c": "name: CI\n\non:\n pull_request:\n push:\n branches:\n - main\n\njobs:\n backend:\n name: Java backend tests\n runs-on: ubuntu-latest\n env:\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: jdbc\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Set up Java\n uses: actions/setup-java@v4\n with:\n distribution: temurin\n java-version: \"21\"\n cache: maven\n\n - name: Run tests\n run: mvn test\n\n frontend:\n name: React frontend build\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Set up Node\n uses: actions/setup-node@v4\n with:\n node-version: \"22\"\n cache: npm\n cache-dependency-path: frontend/package-lock.json\n\n - name: Install frontend dependencies\n run: npm ci --prefix frontend\n\n - name: Build frontend\n run: npm run frontend:build\n\n docs-guard:\n name: Private docs guard\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Verify private docs are not tracked\n shell: bash\n run: |\n set -euo pipefail\n blocked_paths=\"$(git ls-files -- AGENTS.md docs .ai)\"\n if [[ -n \"$blocked_paths\" ]]; then\n printf 'Private/local management docs must stay ignored, but these files are tracked:\\n%s\\n' \"$blocked_paths\"\n exit 1\n fi\n\n docker-build:\n name: Docker image build\n runs-on: ubuntu-latest\n env:\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: jdbc\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Build backend and frontend images\n run: docker compose build backend frontend\n\n compose-smoke:\n name: Docker Compose smoke\n runs-on: ubuntu-latest\n env:\n COMPOSE_FILE: docker-compose.smoke.yml\n COMPOSE_PROJECT_NAME: devcontext-smoke-${{ github.run_id }}\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Write smoke Compose file\n shell: bash\n run: |\n cat > docker-compose.smoke.yml <<'YAML'\n services:\n backend:\n build:\n context: .\n dockerfile: Dockerfile\n ports:\n - \"18080\"\n environment:\n SPRING_DATASOURCE_URL: jdbc:sqlite:/app/data/devcontext.sqlite\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: qdrant\n QDRANT_BASE_URL: http://qdrant:6333\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n QDRANT_API_KEY: \"\"\n volumes:\n - devcontext-data:/app/data\n - ./workspace/projects:/workspace/projects\n depends_on:\n qdrant:\n condition: service_healthy\n healthcheck:\n test: [\"CMD-SHELL\", \"curl -fsS http://localhost:18080/api/health >/dev/null\"]\n interval: 10s\n timeout: 5s\n retries: 12\n start_period: 30s\n\n frontend:\n build:\n context: ./frontend\n dockerfile: Dockerfile\n ports:\n - \"80\"\n depends_on:\n backend:\n condition: service_healthy\n healthcheck:\n test: [\"CMD-SHELL\", \"curl -fsS http://localhost/ >/dev/null\"]\n interval: 10s\n timeout: 5s\n retries: 12\n start_period: 10s\n\n qdrant:\n image: qdrant/qdrant:v1.18.1\n ports:\n - \"6333\"\n - \"6334\"\n volumes:\n - qdrant-data:/qdrant/storage\n healthcheck:\n test: >\n timeout 5 bash -ec 'exec 3<>/dev/tcp/127.0.0.1/6333;\n printf \"GET /healthz HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n\" >&3;\n grep -q \"200 OK\" <&3'\n interval: 10s\n timeout: 5s\n retries: 12\n start_period: 10s\n\n volumes:\n devcontext-data:\n qdrant-data:\n YAML\n\n - name: Clean up previous Compose stack\n run: docker compose down -v --remove-orphans\n\n - name: Start Compose stack\n run: docker compose up -d --build\n\n - name: Verify smoke endpoints\n shell: bash\n run: |\n set -euo pipefail\n backend_addr=\"$(docker compose port backend 18080 | sed -e 's/^0\\.0\\.0\\.0/127.0.0.1/' -e 's/^\\[::\\]/127.0.0.1/')\"\n frontend_addr=\"$(docker compose port frontend 80 | sed -e 's/^0\\.0\\.0\\.0/127.0.0.1/' -e 's/^\\[::\\]/127.0.0.1/')\"\n qdrant_addr=\"$(docker compose port qdrant 6333 | sed -e 's/^0\\.0\\.0\\.0/127.0.0.1/' -e 's/^\\[::\\]/127.0.0.1/')\"\n backend_url=\"http://${backend_addr}/api/health\"\n frontend_url=\"http://${frontend_addr}\"\n qdrant_url=\"http://${qdrant_addr}/healthz\"\n echo \"Backend smoke URL: ${backend_url}\"\n echo \"Frontend smoke URL: ${frontend_url}\"\n echo \"Qdrant smoke URL: ${qdrant_url}\"\n for attempt in {1..90}; do\n if curl -fsS \"${backend_url}\" > backend-health.json \\\n && grep -Eq '\"status\"[[:space:]]*:[[:space:]]*\"UP\"' backend-health.json \\\n && grep -Eq '\"llmProvider\"[[:space:]]*:[[:space:]]*\"mock\"' backend-health.json \\\n && grep -Eq '\"llmClient\"[[:space:]]*:[[:space:]]*\"MockLlmClient\"' backend-health.json \\\n && curl -fsS \"${frontend_url}\" > frontend-home.html \\\n && curl -fsS \"${qdrant_url}\" > qdrant-health.txt; then\n cat backend-health.json\n cat qdrant-health.txt\n exit 0\n fi\n echo \"Waiting for Compose smoke endpoints, attempt ${attempt}/90...\"\n docker compose ps\n sleep 2\n done\n echo \"Compose smoke endpoints did not become ready.\"\n exit 1\n\n - name: Show Compose logs\n if: failure()\n run: docker compose logs --no-color\n\n - name: Clean up Compose stack\n if: always()\n run: docker compose down -v --remove-orphans\n"}, "57ce8c786858642894d8011f44497e048dc9160d": {"t": 1785762418.6545413, "n": 6504, "h": "ed3668c4be4cad7f", "c": "name: CI\n\non:\n pull_request:\n push:\n branches:\n - main\n\njobs:\n backend:\n name: Java backend tests\n runs-on: ubuntu-latest\n env:\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: jdbc\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Set up Java\n uses: actions/setup-java@v4\n with:\n distribution: temurin\n java-version: \"21\"\n cache: maven\n\n - name: Run tests\n run: mvn test\n\n frontend:\n name: React frontend build\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Set up Node\n uses: actions/setup-node@v4\n with:\n node-version: \"22\"\n cache: npm\n cache-dependency-path: frontend/package-lock.json\n\n - name: Install frontend dependencies\n run: npm ci --prefix frontend\n\n - name: Build frontend\n run: npm run frontend:build\n\n docs-guard:\n name: Private docs guard\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Verify private docs are not tracked\n shell: bash\n run: |\n set -euo pipefail\n blocked_paths=\"$(git ls-files -- AGENTS.md docs .ai)\"\n if [[ -n \"$blocked_paths\" ]]; then\n printf 'Private/local management docs must stay ignored, but these files are tracked:\\n%s\\n' \"$blocked_paths\"\n exit 1\n fi\n\n docker-build:\n name: Docker image build\n runs-on: ubuntu-latest\n env:\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: jdbc\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Build backend and frontend images\n run: docker compose build backend frontend\n\n compose-smoke:\n name: Docker Compose smoke\n runs-on: ubuntu-latest\n env:\n COMPOSE_FILE: docker-compose.smoke.yml\n COMPOSE_PROJECT_NAME: devcontext-smoke-${{ github.run_id }}\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Write smoke Compose file\n shell: bash\n run: |\n cat > docker-compose.smoke.yml <<'YAML'\n services:\n backend:\n build:\n context: .\n dockerfile: Dockerfile\n ports:\n - \"18080\"\n environment:\n SPRING_DATASOURCE_URL: jdbc:sqlite:/app/data/devcontext.sqlite\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: qdrant\n QDRANT_BASE_URL: http://qdrant:6333\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n QDRANT_API_KEY: \"\"\n volumes:\n - devcontext-data:/app/data\n - ./workspace/projects:/workspace/projects\n depends_on:\n qdrant:\n condition: service_healthy\n healthcheck:\n test: [\"CMD-SHELL\", \"curl -fsS http://localhost:18080/api/health >/dev/null\"]\n interval: 10s\n timeout: 5s\n retries: 12\n start_period: 30s\n\n frontend:\n build:\n context: ./frontend\n dockerfile: Dockerfile\n ports:\n - \"80\"\n depends_on:\n backend:\n condition: service_healthy\n healthcheck:\n test: [\"CMD-SHELL\", \"curl -fsS http://localhost/ >/dev/null\"]\n interval: 10s\n timeout: 5s\n retries: 12\n start_period: 10s\n\n qdrant:\n image: qdrant/qdrant:v1.18.1\n ports:\n - \"6333\"\n - \"6334\"\n volumes:\n - qdrant-data:/qdrant/storage\n healthcheck:\n test: >\n timeout 5 bash -ec 'exec 3<>/dev/tcp/127.0.0.1/6333;\n printf \"GET /healthz HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n\" >&3;\n grep -q \"200 OK\" <&3'\n interval: 10s\n timeout: 5s\n retries: 12\n start_period: 10s\n\n volumes:\n devcontext-data:\n qdrant-data:\n YAML\n\n - name: Clean up previous Compose stack\n run: docker compose down -v --remove-orphans\n\n - name: Start Compose stack\n run: docker compose up -d --build\n\n - name: Verify smoke endpoints\n shell: bash\n run: |\n set -euo pipefail\n backend_addr=\"$(docker compose port backend 18080 | sed -e 's/^0\\.0\\.0\\.0/127.0.0.1/' -e 's/^\\[::\\]/127.0.0.1/')\"\n frontend_addr=\"$(docker compose port frontend 80 | sed -e 's/^0\\.0\\.0\\.0/127.0.0.1/' -e 's/^\\[::\\]/127.0.0.1/')\"\n qdrant_addr=\"$(docker compose port qdrant 6333 | sed -e 's/^0\\.0\\.0\\.0/127.0.0.1/' -e 's/^\\[::\\]/127.0.0.1/')\"\n backend_url=\"http://${backend_addr}/api/health\"\n frontend_url=\"http://${frontend_addr}\"\n qdrant_url=\"http://${qdrant_addr}/healthz\"\n echo \"Backend smoke URL: ${backend_url}\"\n echo \"Frontend smoke URL: ${frontend_url}\"\n echo \"Qdrant smoke URL: ${qdrant_url}\"\n for attempt in {1..90}; do\n if curl -fsS \"${backend_url}\" > backend-health.json \\\n && grep -Eq '\"status\"[[:space:]]*:[[:space:]]*\"UP\"' backend-health.json \\\n && grep -Eq '\"llmProvider\"[[:space:]]*:[[:space:]]*\"mock\"' backend-health.json \\\n && grep -Eq '\"llmClient\"[[:space:]]*:[[:space:]]*\"MockLlmClient\"' backend-health.json \\\n && curl -fsS \"${frontend_url}\" > frontend-home.html \\\n && curl -fsS \"${qdrant_url}\" > qdrant-health.txt; then\n cat backend-health.json\n cat qdrant-health.txt\n exit 0\n fi\n echo \"Waiting for Compose smoke endpoints, attempt ${attempt}/90...\"\n docker compose ps\n sleep 2\n done\n echo \"Compose smoke endpoints did not become ready.\"\n exit 1\n\n - name: Show Compose logs\n if: failure()\n run: docker compose logs --no-color\n\n - name: Clean up Compose stack\n if: always()\n run: docker compose down -v --remove-orphans\n"}, "95f6c01b798910e875d6d9e823d3f066ade7db4d": {"t": 1785762430.218998, "n": 5565, "h": "42cfdeea54d0d470", "c": "name: CI\n\non:\n pull_request:\n push:\n branches:\n - main\n\njobs:\n backend:\n name: Java backend tests\n runs-on: ubuntu-latest\n env:\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: jdbc\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Set up Java\n uses: actions/setup-java@v4\n with:\n distribution: temurin\n java-version: \"21\"\n cache: maven\n\n - name: Run tests\n run: mvn test\n\n frontend:\n name: React frontend build\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Set up Node\n uses: actions/setup-node@v4\n with:\n node-version: \"22\"\n cache: npm\n cache-dependency-path: frontend/package-lock.json\n\n - name: Install frontend dependencies\n run: npm ci --prefix frontend\n\n - name: Build frontend\n run: npm run frontend:build\n\n docs-guard:\n name: Private docs guard\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Verify private docs are not tracked\n shell: bash\n run: |\n set -euo pipefail\n blocked_paths=\"$(git ls-files -- AGENTS.md docs .ai)\"\n if [[ -n \"$blocked_paths\" ]]; then\n printf 'Private/local management docs must stay ignored, but these files are tracked:\\n%s\\n' \"$blocked_paths\"\n exit 1\n fi\n\n docker-build:\n name: Docker image build\n runs-on: ubuntu-latest\n env:\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: jdbc\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Build backend and frontend images\n run: docker compose build backend frontend\n\n compose-smoke:\n name: Docker Compose smoke\n runs-on: ubuntu-latest\n env:\n COMPOSE_FILE: docker-compose.smoke.yml\n COMPOSE_PROJECT_NAME: devcontext-smoke-${{ github.run_id }}\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Write smoke Compose file\n shell: bash\n run: |\n cat > docker-compose.smoke.yml <<'YAML'\n services:\n backend:\n build:\n context: .\n dockerfile: Dockerfile\n ports:\n - \"18080\"\n environment:\n SPRING_DATASOURCE_URL: jdbc:sqlite:/app/data/devcontext.sqlite\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: qdrant\n QDRANT_BASE_URL: http://qdrant:6333\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n QDRANT_API_KEY: \"\"\n volumes:\n - devcontext-data:/app/data\n - ./workspace/projects:/workspace/projects\n depends_on:\n - qdrant\n\n frontend:\n build:\n context: ./frontend\n dockerfile: Dockerfile\n ports:\n - \"80\"\n depends_on:\n - backend\n\n qdrant:\n image: qdrant/qdrant:latest\n ports:\n - \"6333\"\n - \"6334\"\n volumes:\n - qdrant-data:/qdrant/storage\n\n volumes:\n devcontext-data:\n qdrant-data:\n YAML\n\n - name: Clean up previous Compose stack\n run: docker compose down -v --remove-orphans\n\n - name: Start Compose stack\n run: docker compose up -d --build\n\n - name: Verify smoke endpoints\n shell: bash\n run: |\n set -euo pipefail\n backend_addr=\"$(docker compose port backend 18080 | sed -e 's/^0\\.0\\.0\\.0/127.0.0.1/' -e 's/^\\[::\\]/127.0.0.1/')\"\n frontend_addr=\"$(docker compose port frontend 80 | sed -e 's/^0\\.0\\.0\\.0/127.0.0.1/' -e 's/^\\[::\\]/127.0.0.1/')\"\n qdrant_addr=\"$(docker compose port qdrant 6333 | sed -e 's/^0\\.0\\.0\\.0/127.0.0.1/' -e 's/^\\[::\\]/127.0.0.1/')\"\n backend_url=\"http://${backend_addr}/api/health\"\n frontend_url=\"http://${frontend_addr}\"\n qdrant_url=\"http://${qdrant_addr}/healthz\"\n echo \"Backend smoke URL: ${backend_url}\"\n echo \"Frontend smoke URL: ${frontend_url}\"\n echo \"Qdrant smoke URL: ${qdrant_url}\"\n for attempt in {1..90}; do\n if curl -fsS \"${backend_url}\" > backend-health.json \\\n && grep -Eq '\"status\"[[:space:]]*:[[:space:]]*\"UP\"' backend-health.json \\\n && grep -Eq '\"llmProvider\"[[:space:]]*:[[:space:]]*\"mock\"' backend-health.json \\\n && grep -Eq '\"llmClient\"[[:space:]]*:[[:space:]]*\"MockLlmClient\"' backend-health.json \\\n && curl -fsS \"${frontend_url}\" > frontend-home.html \\\n && curl -fsS \"${qdrant_url}\" > qdrant-health.txt; then\n cat backend-health.json\n cat qdrant-health.txt\n exit 0\n fi\n echo \"Waiting for Compose smoke endpoints, attempt ${attempt}/90...\"\n docker compose ps\n sleep 2\n done\n echo \"Compose smoke endpoints did not become ready.\"\n exit 1\n\n - name: Show Compose logs\n if: failure()\n run: docker compose logs --no-color\n\n - name: Clean up Compose stack\n if: always()\n run: docker compose down -v --remove-orphans\n"}, "075142929905e3b6958cfb406bf646786b78a204": {"t": 1785762433.0311155, "n": 1923, "h": "a64783869a51732a", "c": "name: CI\n\non:\n pull_request:\n push:\n branches:\n - main\n\njobs:\n backend:\n name: Java backend tests\n runs-on: ubuntu-latest\n env:\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: jdbc\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Set up Java\n uses: actions/setup-java@v4\n with:\n distribution: temurin\n java-version: \"21\"\n cache: maven\n\n - name: Run tests\n run: mvn test\n\n frontend:\n name: React frontend build\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Set up Node\n uses: actions/setup-node@v4\n with:\n node-version: \"22\"\n cache: npm\n cache-dependency-path: frontend/package-lock.json\n\n - name: Install frontend dependencies\n run: npm ci --prefix frontend\n\n - name: Build frontend\n run: npm run frontend:build\n\n docs-guard:\n name: Private docs guard\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Verify private docs are not tracked\n shell: bash\n run: |\n set -euo pipefail\n blocked_paths=\"$(git ls-files -- AGENTS.md docs .ai)\"\n if [[ -n \"$blocked_paths\" ]]; then\n printf 'Private/local management docs must stay ignored, but these files are tracked:\\n%s\\n' \"$blocked_paths\"\n exit 1\n fi\n\n docker-build:\n name: Docker image build\n runs-on: ubuntu-latest\n env:\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: jdbc\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Build backend and frontend images\n run: docker compose build backend frontend\n"}, "5f0d4e4c79ffe14e9c624849d7185091099f9c51": {"t": 1785762435.5450885, "n": 1545, "h": "3d9d931e5b48d4a2", "c": "name: CI\n\non:\n pull_request:\n push:\n branches:\n - main\n\njobs:\n backend:\n name: Java backend tests\n runs-on: ubuntu-latest\n env:\n DEVCONTEXT_LLM_PROVIDER: mock\n DEVCONTEXT_VECTOR_PROVIDER: jdbc\n GEMINI_API_KEY: \"\"\n DEEPSEEK_API_KEY: \"\"\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Set up Java\n uses: actions/setup-java@v4\n with:\n distribution: temurin\n java-version: \"21\"\n cache: maven\n\n - name: Run tests\n run: mvn test\n\n frontend:\n name: React frontend build\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Set up Node\n uses: actions/setup-node@v4\n with:\n node-version: \"22\"\n cache: npm\n cache-dependency-path: frontend/package-lock.json\n\n - name: Install frontend dependencies\n run: npm ci --prefix frontend\n\n - name: Build frontend\n run: npm run frontend:build\n\n docs-guard:\n name: Private docs guard\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Verify private docs are not tracked\n shell: bash\n run: |\n set -euo pipefail\n blocked_paths=\"$(git ls-files -- AGENTS.md docs .ai)\"\n if [[ -n \"$blocked_paths\" ]]; then\n printf 'Private/local management docs must stay ignored, but these files are tracked:\\n%s\\n' \"$blocked_paths\"\n exit 1\n fi\n"}, "308e32e695394696de3dec02e3ec583df041f0e2": {"t": 1785762437.231808, "n": 384, "h": "60219d9e98638037", "c": "name: CI\n\non:\n pull_request:\n push:\n branches:\n - main\n\njobs:\n test:\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Set up Java\n uses: actions/setup-java@v4\n with:\n distribution: temurin\n java-version: \"21\"\n cache: maven\n\n - name: Run tests\n run: mvn test\n"}}, "src/main/java/com/devcontext/adapters/llm/DeepSeekLlmClient.java": {"cf4cee8e3f2d701e5325b875bf513a0d3fdf8105": {"t": 1786068303.74391, "n": 8096, "h": "1b77a3096eb72bed", "c": "package com.devcontext.adapters.llm;\n\nimport com.devcontext.application.llm.LlmErrorTypes;\nimport com.devcontext.application.llm.LlmRuntimeStatus;\nimport com.devcontext.common.error.ApiException;\nimport com.devcontext.config.DevContextLlmProperties;\nimport com.devcontext.domain.llm.LlmRequest;\nimport com.devcontext.domain.llm.LlmResponse;\nimport com.devcontext.ports.llm.LlmClient;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.net.http.HttpTimeoutException;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.stereotype.Component;\n\n@Component\n@ConditionalOnProperty(name = \"devcontext.llm.provider\", havingValue = \"deepseek\")\npublic class DeepSeekLlmClient implements LlmClient {\n\n private static final Logger log = LoggerFactory.getLogger(DeepSeekLlmClient.class);\n\n private final DevContextLlmProperties properties;\n private final LlmRuntimeStatus runtimeStatus;\n private final ObjectMapper objectMapper;\n private final HttpClient httpClient;\n\n public DeepSeekLlmClient(DevContextLlmProperties properties, LlmRuntimeStatus runtimeStatus, ObjectMapper objectMapper) {\n this.properties = properties;\n this.runtimeStatus = runtimeStatus;\n this.objectMapper = objectMapper;\n this.httpClient = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(10))\n .build();\n log.info(\"DeepSeek LLM client initialized with model {}, base URL {}, and timeout {}\",\n properties.deepseek().model(),\n properties.deepseek().baseUrl(),\n properties.deepseek().timeout());\n }\n\n @Override\n public LlmResponse chat(LlmRequest request) {\n DevContextLlmProperties.DeepSeek deepseek = properties.deepseek();\n if (deepseek.apiKey() == null || deepseek.apiKey().isBlank()) {\n throw failure(LlmErrorTypes.PROVIDER_NOT_CONFIGURED, \"DEEPSEEK_API_KEY is not configured\", HttpStatus.BAD_REQUEST);\n }\n String modelName = configuredModel(request, deepseek);\n HttpRequest httpRequest = buildRequest(deepseek, modelName, request.prompt());\n log.info(\"Calling DeepSeek model {} via {} with timeout {}\", modelName, trimTrailingSlash(deepseek.baseUrl()), deepseek.timeout());\n try {\n HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());\n log.info(\"DeepSeek response status {}\", response.statusCode());\n if (response.statusCode() < 200 || response.statusCode() >= 300) {\n throw failure(\n LlmErrorTypes.fromHttpStatus(response.statusCode()),\n summarizeDeepSeekError(response.statusCode(), response.body()),\n HttpStatus.BAD_GATEWAY\n );\n }\n String content = extractText(response.body());\n runtimeStatus.recordSuccess();\n return new LlmResponse(\n content,\n modelName,\n estimateTokens(request.prompt()),\n estimateTokens(content)\n );\n } catch (HttpTimeoutException e) {\n throw failure(LlmErrorTypes.TIMEOUT, \"DeepSeek request timed out\", HttpStatus.BAD_GATEWAY);\n } catch (IOException e) {\n log.warn(\"DeepSeek request failed before receiving a valid response: {}\", e.getMessage());\n throw failure(LlmErrorTypes.fromMessage(e.getMessage()), \"DeepSeek request failed: \" + safe(e.getMessage()), HttpStatus.BAD_GATEWAY);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n throw failure(LlmErrorTypes.TIMEOUT, \"DeepSeek request was interrupted\", HttpStatus.BAD_GATEWAY);\n }\n }\n\n private HttpRequest buildRequest(DevContextLlmProperties.DeepSeek deepseek, String modelName, String prompt) {\n String requestBody = serialize(Map.of(\n \"model\", modelName,\n \"messages\", List.of(Map.of(\n \"role\", \"user\",\n \"content\", prompt\n )),\n \"stream\", false\n ));\n return HttpRequest.newBuilder()\n .uri(URI.create(trimTrailingSlash(deepseek.baseUrl()) + \"/chat/completions\"))\n .timeout(deepseek.timeout())\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"Bearer \" + deepseek.apiKey())\n .POST(HttpRequest.BodyPublishers.ofString(requestBody))\n .build();\n }\n\n private String extractText(String responseBody) {\n JsonNode root;\n try {\n root = objectMapper.readTree(responseBody);\n } catch (IOException e) {\n throw failure(LlmErrorTypes.PARSE_FAILED, \"DeepSeek response was not valid JSON\", HttpStatus.BAD_GATEWAY);\n }\n JsonNode choices = root.path(\"choices\");\n if (!choices.isArray() || choices.isEmpty()) {\n throw failure(LlmErrorTypes.PARSE_FAILED, \"DeepSeek response did not include choices\", HttpStatus.BAD_GATEWAY);\n }\n String content = choices.get(0).path(\"message\").path(\"content\").asText(\"\");\n if (content.isBlank()) {\n throw failure(LlmErrorTypes.PARSE_FAILED, \"DeepSeek response did not include message content\", HttpStatus.BAD_GATEWAY);\n }\n return content;\n }\n\n private String configuredModel(LlmRequest request, DevContextLlmProperties.DeepSeek deepseek) {\n if (request.modelName() != null && !request.modelName().isBlank() && !\"mock-llm\".equals(request.modelName())) {\n return request.modelName();\n }\n return deepseek.model();\n }\n\n private String serialize(Object value) {\n try {\n return objectMapper.writeValueAsString(value);\n } catch (IOException e) {\n throw new IllegalStateException(\"Failed to serialize DeepSeek request\", e);\n }\n }\n\n private String summarizeDeepSeekError(int statusCode, String body) {\n try {\n JsonNode root = objectMapper.readTree(body);\n String message = root.path(\"error\").path(\"message\").asText();\n if (message.isBlank()) {\n message = root.path(\"message\").asText();\n }\n if (!message.isBlank()) {\n return \"DeepSeek request failed with HTTP \" + statusCode + \": \" + safe(message);\n }\n } catch (IOException ignored) {\n // Fall back to a compact raw-body summary below.\n }\n String compact = body == null ? \"\" : body.replaceAll(\"\\\\s+\", \" \").trim();\n if (compact.length() > 300) {\n compact = compact.substring(0, 300);\n }\n return compact.isBlank()\n ? \"DeepSeek request failed with HTTP \" + statusCode\n : \"DeepSeek request failed with HTTP \" + statusCode + \": \" + safe(compact);\n }\n\n private String trimTrailingSlash(String value) {\n while (value.endsWith(\"/\")) {\n value = value.substring(0, value.length() - 1);\n }\n return value;\n }\n\n private int estimateTokens(String text) {\n if (text == null || text.isBlank()) {\n return 0;\n }\n return Math.max(1, text.length() / 4);\n }\n\n private ApiException failure(String errorType, String message, HttpStatus status) {\n String safeMessage = safe(message);\n runtimeStatus.recordFailure(errorType, safeMessage);\n return new ApiException(errorType, safeMessage, status);\n }\n\n private String safe(String value) {\n return properties.maskSecrets(value);\n }\n}\n"}}}}