---
phase: 28-ollama-integration
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - server/src/services/ollama.ts
  - server/src/routes/ollama.ts
  - server/src/routes/index.ts
  - server/src/app.ts
  - server/src/data/ollama-model-catalog.json
  - server/src/__tests__/ollama-service.test.ts
autonomous: true
requirements: [OLLA-01, OLLA-02, OLLA-04, OLLA-05]

must_haves:
  truths:
    - "detectOllama() returns installed:true + version when Ollama responds at /api/version"
    - "detectOllama() returns installed:false + installUrl when Ollama is absent or times out"
    - "listOllamaModels() returns model list with parameterSize, family, quantization from /api/tags"
    - "getRecommendedModel() returns highest-quality model that fits within 75% system RAM"
    - "GET /companies/:companyId/ollama/status returns OllamaStatus JSON"
    - "GET /companies/:companyId/ollama/models returns OllamaModel[] + ramGb"
  artifacts:
    - path: "server/src/services/ollama.ts"
      provides: "Ollama detection, model listing, recommendation logic"
      exports: ["detectOllama", "listOllamaModels", "OllamaStatus", "OllamaModel"]
    - path: "server/src/routes/ollama.ts"
      provides: "HTTP routes for Ollama status and model listing"
      exports: ["ollamaRoutes"]
    - path: "server/src/data/ollama-model-catalog.json"
      provides: "Static model catalog for RAM-based recommendations"
      contains: "qwen2.5-coder"
    - path: "server/src/__tests__/ollama-service.test.ts"
      provides: "Unit tests for ollamaService"
      min_lines: 60
  key_links:
    - from: "server/src/routes/ollama.ts"
      to: "server/src/services/ollama.ts"
      via: "import detectOllama, listOllamaModels"
      pattern: "import.*from.*services/ollama"
    - from: "server/src/app.ts"
      to: "server/src/routes/ollama.ts"
      via: "api.use(ollamaRoutes())"
      pattern: "ollamaRoutes"
---

<objective>
Create the server-side Ollama integration service, HTTP routes, model catalog, and unit tests.

Purpose: Provides the backend API surface that the UI (Plan 02) will consume to detect Ollama, list available models, and recommend a model based on system RAM.
Output: Working server routes at /companies/:companyId/ollama/status and /models, plus unit tests.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/28-ollama-integration/28-RESEARCH.md

@server/src/app.ts
@server/src/routes/index.ts
@server/src/routes/agents.ts (for assertCompanyAccess pattern)
@ui/src/api/client.ts (for API client pattern reference)
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Create ollamaService + model catalog + unit tests</name>
  <files>server/src/services/ollama.ts, server/src/data/ollama-model-catalog.json, server/src/__tests__/ollama-service.test.ts</files>
  <read_first>
    - server/src/services/heartbeat.ts (lines 1-30 for import patterns)
    - .planning/phases/28-ollama-integration/28-RESEARCH.md (Pattern 1, Pattern 5, Code Examples)
  </read_first>
  <behavior>
    - detectOllama returns { installed: true, version: "0.5.x" } when fetch to /api/version succeeds
    - detectOllama returns { installed: false, version: null, installUrl: "https://ollama.com/download" } when fetch rejects (ECONNREFUSED)
    - detectOllama returns { installed: false, version: null, installUrl } when fetch times out (AbortController)
    - listOllamaModels returns OllamaModel[] mapped from /api/tags response with name, parameterSize, quantization, sizeBytes, family
    - listOllamaModels returns empty array when Ollama is absent
    - getRecommendedModel marks the highest-quality model that fits within 75% of given RAM budget as recommended=true
    - getRecommendedModel with 8GB RAM recommends a 7b model, not a 32b model
    - Model catalog JSON contains at least qwen2, llama, mistral, phi, deepseek families
  </behavior>
  <action>
    1. Create `server/src/data/ollama-model-catalog.json` with the static catalog from RESEARCH Pattern 5. Include families: qwen2, llama, mistral, phi, deepseek. Each variant has name, ramGb, vramGb, quality fields.

    2. Create `server/src/services/ollama.ts`:
       - `const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL ?? "http://localhost:11434"`
       - `const OLLAMA_TIMEOUT_MS = 3000`
       - `const INSTALL_URL = "https://ollama.com/download"`
       - Export interface `OllamaStatus { installed: boolean; version: string | null; installUrl: string }`
       - Export interface `OllamaModel { name: string; parameterSize: string; quantization: string; sizeBytes: number; family: string; recommended: boolean; recommendationReason: string | null }`
       - Export `async function detectOllama(): Promise<OllamaStatus>` — fetch /api/version with AbortController 3s timeout. On success return installed:true + version. On any error return installed:false + installUrl.
       - Export `async function listOllamaModels(): Promise<OllamaModel[]>` — fetch /api/tags, map response.models to OllamaModel[]. On error return [].
       - Export `function getRecommendedModel(models: OllamaModel[], systemRamBytes: number): OllamaModel[]` — reads catalog JSON, computes usableRamGb = (systemRamBytes / 1024^3) * 0.75, for each model checks if a catalog entry matches by name and fits in RAM. Returns models with `recommended` field set. The highest-quality model within budget gets recommended=true + a recommendationReason string. All others get recommended=false.
       - Anti-pattern: Do NOT poll in a loop. Do NOT hard-code localhost:11434 — always use OLLAMA_BASE_URL.

    3. Create `server/src/__tests__/ollama-service.test.ts`:
       - Mock global fetch using vi.stubGlobal("fetch", vi.fn())
       - Test detectOllama success case (mock returns { version: "0.5.1" })
       - Test detectOllama failure case (mock rejects with ECONNREFUSED)
       - Test detectOllama timeout case (mock never resolves, verify AbortController fires)
       - Test listOllamaModels success with mock /api/tags response matching OllamaTagsResponse shape from RESEARCH
       - Test listOllamaModels returns [] on fetch error
       - Test getRecommendedModel with 8GB RAM → recommends 7b-class model
       - Test getRecommendedModel with 32GB RAM → recommends 32b-class model
       - Test getRecommendedModel with models not in catalog → recommended=false for all
  </action>
  <verify>
    <automated>cd /opt/nexus/server && npx vitest run src/__tests__/ollama-service.test.ts</automated>
  </verify>
  <acceptance_criteria>
    - grep -q "detectOllama" server/src/services/ollama.ts
    - grep -q "listOllamaModels" server/src/services/ollama.ts
    - grep -q "getRecommendedModel" server/src/services/ollama.ts
    - grep -q "OLLAMA_BASE_URL" server/src/services/ollama.ts
    - grep -q "qwen2.5-coder" server/src/data/ollama-model-catalog.json
    - grep -q "detectOllama" server/src/__tests__/ollama-service.test.ts
    - grep -q "getRecommendedModel" server/src/__tests__/ollama-service.test.ts
  </acceptance_criteria>
  <done>All ollama service tests pass. detectOllama, listOllamaModels, and getRecommendedModel functions exported and tested. Model catalog JSON file exists with 5+ model families.</done>
</task>

<task type="auto">
  <name>Task 2: Create Ollama HTTP routes and mount in app</name>
  <files>server/src/routes/ollama.ts, server/src/routes/index.ts, server/src/app.ts</files>
  <read_first>
    - server/src/routes/agents.ts (lines 1-60 for route pattern, assertCompanyAccess usage)
    - server/src/app.ts (lines 134-170 for route mounting pattern)
    - server/src/routes/index.ts
  </read_first>
  <action>
    1. Create `server/src/routes/ollama.ts`:
       - Import Router from express, import assertCompanyAccess from "./authz.js", import detectOllama/listOllamaModels/getRecommendedModel from "../services/ollama.js"
       - Import `os` for `os.totalmem()`
       - Export function `ollamaRoutes()` returning Router
       - `GET /companies/:companyId/ollama/status`:
         - Call `assertCompanyAccess(req, companyId)`
         - Call `detectOllama()`, return JSON response
       - `GET /companies/:companyId/ollama/models`:
         - Call `assertCompanyAccess(req, companyId)`
         - Call `detectOllama()` first — if not installed, return `{ models: [], ramGb: 0 }`
         - Call `listOllamaModels()`, then `getRecommendedModel(models, os.totalmem())`
         - Return `{ models: enrichedModels, ramGb: Math.round(os.totalmem() / 1073741824) }`
       - Wrap each handler in try/catch, return 500 on unexpected errors

    2. Add `export { ollamaRoutes } from "./ollama.js"` to `server/src/routes/index.ts`

    3. In `server/src/app.ts`:
       - Add import: `import { ollamaRoutes } from "./routes/ollama.js"`
       - Add `api.use(ollamaRoutes())` after the `api.use(agentRoutes(db))` line (around line 152)
  </action>
  <verify>
    <automated>cd /opt/nexus/server && npx tsc --noEmit 2>&1 | head -20</automated>
  </verify>
  <acceptance_criteria>
    - grep -q "ollamaRoutes" server/src/routes/ollama.ts
    - grep -q "assertCompanyAccess" server/src/routes/ollama.ts
    - grep -q "/companies/:companyId/ollama/status" server/src/routes/ollama.ts
    - grep -q "/companies/:companyId/ollama/models" server/src/routes/ollama.ts
    - grep -q "ollamaRoutes" server/src/routes/index.ts
    - grep -q "ollamaRoutes" server/src/app.ts
    - grep -q "os.totalmem" server/src/routes/ollama.ts
  </acceptance_criteria>
  <done>Ollama routes mounted at /companies/:companyId/ollama/status and /models. TypeScript compiles without errors. Routes use assertCompanyAccess for auth and os.totalmem() for RAM detection.</done>
</task>

</tasks>

<verification>
- `cd /opt/nexus/server && npx vitest run src/__tests__/ollama-service.test.ts` — all tests pass
- `cd /opt/nexus/server && npx tsc --noEmit` — no type errors
- Ollama service gracefully returns installed:false when Ollama is not running (no crashes)
</verification>

<success_criteria>
- Ollama detection service exists with detectOllama, listOllamaModels, getRecommendedModel
- Model catalog JSON ships with 5+ model families
- HTTP routes mounted and accessible at /companies/:companyId/ollama/status and /models
- Unit tests cover success, failure, timeout, and recommendation scenarios
- All code compiles without TypeScript errors
</success_criteria>

<output>
After completion, create `.planning/phases/28-ollama-integration/28-01-SUMMARY.md`
</output>
