Files
Lucas WintherandClaude Opus 5 97f115e641 docs: record ADR-019 and document fuzzy search behaviour
Documents the fuzzy-search-by-default change shipped across tasks 1-10:
appends ADR-019 (fuzzy default, hand-rolled zero-dependency scorer, why
fuse.js/fastest-levenshtein were rejected), describes the src/search/
pure computation unit in ARCHITECTURE.md, updates README.md's tool
table/count (18 -> 19) and adds a Search behaviour section, and updates
TODO.md with the completed milestone, two accepted limitations, and
three tracked follow-ups. No source or test files were changed;
bun test (349 pass) and bun run typecheck remain unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-24 09:05:57 +02:00

172 lines
15 KiB
Markdown

# Project Progress & Roadmap
> [!IMPORTANT]
> **CRITICAL AGENT POLICY**:
> **Never continue automatically to the next task or phase.**
> After finishing any individual task, milestone, or commit, the agent must **always stop and check in with the user**. Present what was completed, outline the next logical step, and await explicit user approval before proceeding.
---
## 1. Completed Tasks
- [x] **Agentic Development Guidelines & Infrastructure**
- [x] Created [AGENTS.md](../AGENTS.md) defining architectural conventions, coding practices, and agent workflow.
- [x] Created [CLAUDE.md](../CLAUDE.md) and [GEMINI.md](../GEMINI.md) pointing to [AGENTS.md](../AGENTS.md).
- [x] Created [docs/ARCHITECTURE.md](ARCHITECTURE.md) documenting layers, data flow, tool specifications, and API specifics.
- [x] Created [docs/DECISIONS.md](DECISIONS.md) establishing initial ADRs (stack, transport, auth, scoping, testing).
- [x] Created root [.gitignore](../.gitignore) protecting credentials, cache, and IDE artifacts.
- [x] **OpenProject 17 Local Docker Stack & Seeding**
- [x] Created [docker-compose.yml](../docker-compose.yml) configured for OpenProject 17 (`openproject/openproject:17-slim`), PostgreSQL 17, and Memcached.
- [x] Created [.env.example](../.env.example) documenting all stack and MCP client environment variables.
- [x] Created [scripts/seed-test-data.rb](../scripts/seed-test-data.rb) (Rails runner script) to:
- Activate default admin user and remove password reset requirements.
- Generate a live personal API token via `Token::API.create_and_return_value(admin)`.
- Seed dedicated test project `mcp-test-project` (ID: 4).
- Seed 4 custom work packages (IDs: 38-41) across Task, Bug, Feature types.
- Seed saved query `MCP Active Tasks` (ID: 30).
- [x] Created [scripts/seed.sh](../scripts/seed.sh) orchestrating health checks and automated writing to `.env.test` and `.env.local`.
- [x] Created lifecycle scripts [scripts/start.sh](../scripts/start.sh) and [scripts/stop.sh](../scripts/stop.sh).
- [x] Verified live container execution and validated HTTP 200 API v3 access (`/api/v3/projects`) with generated token.
- [x] Recorded [ADR-009](DECISIONS.md#adr-009-target-openproject-17-as-primary-runtime-and-test-environment) targeting OpenProject 17.
- [x] **Bun Runtime Migration & Tooling**
- [x] Switched project runtime from Node.js to Bun (v1.3.14).
- [x] Created [package.json](../package.json) with `@modelcontextprotocol/sdk` and `zod`.
- [x] Created [tsconfig.json](../tsconfig.json) with Bun bundler settings and strict typing.
- [x] Generated [bun.lock](../bun.lock).
- [x] Added [src/index.ts](../src/index.ts) entrypoint stub and [tests/smoke.test.ts](../tests/smoke.test.ts) using `bun:test`.
- [x] Updated [AGENTS.md](../AGENTS.md), [CLAUDE.md](../CLAUDE.md), and [GEMINI.md](../GEMINI.md) with Bun init rules.
- [x] Recorded [ADR-010](DECISIONS.md#adr-010-adopt-bun-as-runtime-package-manager-and-test-runner) adopting Bun.
- [x] Updated [README.md](../README.md) with Bun prerequisites and quickstart commands.
---
## 2. Next Up (Phase 1: Read/Browse Implementation)
- [x] **Task 1: OpenProject REST API v3 Client Layer**
- [x] Implement `src/config/index.ts` to parse and validate `OPENPROJECT_BASE_URL`, `OPENPROJECT_API_KEY`, and `OPENPROJECT_READ_ONLY` (or `--read-only`) via Zod.
- [x] Implement `src/context.ts` providing `RequestContext` interface and `AsyncLocalStorage` scoping (`getRequestContext`, `runWithContext`) for concurrent multi-user safety.
- [x] Implement `src/client/api-client.ts` with native `fetch`, HTTP Basic Auth injection, error normalization, and rate/timeout handling.
- [x] Implement `src/client/hal-parser.ts` to unpack HAL+JSON (`_links`, `_embedded`) into clean, token-efficient JSON models.
- [x] Implement `src/client/filter-builder.ts` to translate LLM-friendly filter arguments into OpenProject's JSON filter syntax.
- [x] Add unit tests in `tests/client.test.ts` verifying authentication headers, response parsing, and error sanitization against live container.
- [x] **Task 2: Domain Services**
- [x] Implement `src/services/helper.ts` (`resolveClient` for ambient `RequestContext` resolution).
- [x] Implement `src/services/projects.ts` (`listProjects`, `getProject`, `getProjectSchema`).
- [x] Implement `src/services/work-packages.ts` (`listWorkPackages`, `getWorkPackage`, `searchWorkPackages`).
- [x] Implement `src/services/queries.ts` (`listQueries`, `getQuery`, `getQueryResults`).
- [x] Implement `src/services/metadata.ts` (`listStatuses`, `listTypes`, `listPriorities`, `listUsers`).
- [x] Implement `src/services/openapi.ts` (`getOpenApiSpec` with in-memory caching, summary mode, path endpoint lookup, tag filtering, schema inspection).
- [x] Add service tests in `tests/services.test.ts` (unit tests and live container integration suite passing).
- [x] **Task 3: MCP Tool Definitions & Schema Registration**
- [x] Define Zod schemas and register tools in `src/tools/`:
- [x] `openproject_list_projects`
- [x] `openproject_get_project`
- [x] `openproject_list_work_packages`
- [x] `openproject_get_work_package`
- [x] `openproject_list_queries`
- [x] `openproject_get_query`
- [x] `openproject_list_types`
- [x] `openproject_list_statuses`
- [x] `openproject_list_priorities`
- [x] `openproject_list_users`
- [x] `openproject_get_openapi_spec`
- [x] **Task 4: MCP Stdio Server Assembly & Integration**
- [x] Wire domain tools into `@modelcontextprotocol/sdk` Server using `StdioServerTransport` in `src/server.ts` and `src/index.ts`.
- [x] Implement read-only mode tool filtering (omits mutating tools when `readOnly` is enabled) and execution guard (`SERVER_READ_ONLY`).
- [x] Ensure all logging is strictly redirected to `stderr`.
- [x] Add end-to-end integration tests in `tests/mcp-server.test.ts` verifying tool calling against the running OpenProject 17 instance.
- [x] Add read-only mode verification tests in `tests/read-only.test.ts`.
- [x] **Task 5: OpenAPI Introspection Tool & Service**
- [x] Implement `src/services/openapi.ts` with in-memory caching, multi-mode queries (summary, path, tag, schema), and ambient client resolution.
- [x] Implement `src/tools/openapi.ts` registering `openproject_get_openapi_spec` (`readOnly: true`) into `allTools` (bringing tool count to 11).
- [x] Add comprehensive test suite in `tests/openapi.test.ts` covering service methods, MCP client execution, error handling, and live OpenProject 17 container integration.
- [x] Update suite regressions and assertions across all test suites (`tests/tools.test.ts`, `tests/read-only.test.ts`, `tests/mcp-server.test.ts`).
- [x] **Task 6: Docker Container Packaging & CI/CD Pipeline**
- [x] Created multi-stage [Dockerfile](../Dockerfile) using `oven/bun:1-slim` builder and unprivileged `bun` user runner.
- [x] Created [.dockerignore](../.dockerignore) excluding secrets, local dependencies, test files, and docs from build context.
- [x] Implemented GitHub Actions CI workflow [.github/workflows/ci.yml](../.github/workflows/ci.yml) with automated type checking, testing, and multi-arch Docker image publishing (`linux/amd64`, `linux/arm64`) to `ghcr.io`.
- [x] Added automated test coverage in [tests/docker.test.ts](../tests/docker.test.ts) and [tests/ci-workflow.test.ts](../tests/ci-workflow.test.ts).
- [x] Recorded [ADR-015](DECISIONS.md#adr-015-docker-container-packaging-and-github-actions-cicd-pipeline).
- [x] Updated [README.md](../README.md) with Docker Quickstart, read-only mode instructions, and client integration settings (Claude Desktop & Cursor).
- [x] **Task 7: Hosted Remote MCP Server (HTTP/SSE Transport) & Multi-Tenant Credential Scoping**
- [x] Extend configuration loader with `port` and `host` options (`src/config/index.ts`, `tests/config.test.ts`).
- [x] Implement hosted HTTP/SSE MCP server with `Bun.serve` and Web Streams SSE (`src/http-server.ts`, `tests/http-server.test.ts`).
- [x] Implement credential extraction hierarchy (`Authorization: Bearer`, `X-OpenProject-Api-Key`, `?apiKey=`) with 401 unauthorized rejection.
- [x] Implement per-session client isolation and `runWithContext` dynamic scoping.
- [x] Wire dual-transport execution in CLI entrypoint (`src/index.ts`).
- [x] Create production `docker-compose.server.yml` with `/health` check probe.
- [x] Record [ADR-016](DECISIONS.md#adr-016-hosted-remote-mcp-server-architecture-httpsse-transport--multi-tenant-credential-scoping).
- [x] Update documentation with hosted deployment guide and remote client setups for Cursor and Claude Desktop in [README.md](../README.md).
- [x] **Task 8: OpenAPI 3.1.0 Specification & REST Tool Execution Bridge**
- [x] Implement dynamic OpenAPI specification generator `src/openapi-spec.ts`.
- [x] Expose `GET /openapi.json` and `GET /swagger.json` in `src/http-server.ts`.
- [x] Expose REST tool execution endpoint `POST /api/tools/:toolName` with Zod parameter validation and `RequestContext` execution.
- [x] Add automated test coverage in `tests/http-server.test.ts`.
- [x] Record [ADR-017](DECISIONS.md#adr-017-openapi-310-specification--rest-tool-execution-bridge-for-openapi-clients) in `docs/DECISIONS.md`.
- [x] Document OpenAPI connection mode and server endpoints reference table in [README.md](../README.md).
- [x] **Task 9: Meetings, Wikis, and Activities Extension (18 Tools Total)**
- [x] Implemented `listWorkPackageActivities` in `src/services/work-packages.ts` separating comments from field mutations with `onlyComments` filter.
- [x] Implemented `listMeetings`, `getMeeting` (with embedded agenda items/outcomes), and `searchMeetings` (deep search across titles, locations, and agenda item notes) in `src/services/meetings.ts`.
- [x] Implemented `getWikiPage` (with attachments), `listWikiPageLinks`, and `searchWikiPages` with smart discovery caching and consecutive 404 cutoff in `src/services/wikis.ts`.
- [x] Registered `openproject_list_work_package_activities` in `src/tools/work-packages.ts`.
- [x] Registered `openproject_list_meetings`, `openproject_get_meeting`, `openproject_search_meetings` in `src/tools/meetings.ts`.
- [x] Registered `openproject_get_wiki_page`, `openproject_search_wiki_pages`, `openproject_list_wiki_page_links` in `src/tools/wikis.ts`.
- [x] Wired all 18 tools into server registry (`src/tools/index.ts`, `src/services/index.ts`) and updated all test suite assertions to 18 tools.
- [x] Added live OpenProject 17 container integration tests in `tests/services.test.ts` and `tests/mcp-server.test.ts`.
- [x] Recorded [ADR-018](DECISIONS.md#adr-018-add-meetings-wikis-and-activities-tools-with-smart-wiki-discovery-and-deep-meeting-search) and updated [ARCHITECTURE.md](ARCHITECTURE.md), [TODO.md](TODO.md), and [README.md](../README.md).
- [x] **Task 10: Fuzzy Search by Default (19 Tools Total)**
- [x] Implemented a pure, zero-dependency fuzzy-matching unit in `src/search/` (`tokenize.ts`, `score.ts`, `snippet.ts`, `rank.ts`, `pipeline.ts`), with no I/O and no module-level mutable state.
- [x] Made fuzzy matching the default across all search tools via an optional `matchMode: "fuzzy" | "exact"` parameter; `"exact"` reproduces the previous substring-containment behavior byte for byte.
- [x] Widened content coverage: wiki page body text, work package descriptions and comments, and meeting participants and project names are now searchable, not just titles/subjects.
- [x] Added `openproject_search_work_packages`, exposing the previously tool-unreachable `searchWorkPackages` service helper as a ranked search tool over subjects, descriptions, and comments.
- [x] Added optional `query`/`matchMode` ranking to `openproject_list_work_package_activities` (ranks already-fetched activities client-side, no extra API cost).
- [x] Fixed two live bugs: a `.catch(() => undefined)` in meeting search that turned an expired token into a silent "no results found", and an unbounded `Promise.all` over up to 250 candidates in meeting search, replaced by the bounded two-phase pipeline (25-item enrichment window, concurrency 8).
- [x] Added `tests/search/` unit tests (`tokenize`, `score`, `snippet`, `rank`, `pipeline`) and a recall suite (`tests/search/recall.test.ts`) covering realistic natural-language and misspelled queries.
- [x] Recorded [ADR-019](DECISIONS.md#adr-019-fuzzy-search-by-default-with-a-hand-rolled-zero-dependency-scorer) and updated [ARCHITECTURE.md](ARCHITECTURE.md), [TODO.md](TODO.md), and [README.md](../README.md).
### Known Limitations (Accepted)
- **Two-phase recall ceiling**: A record whose only match is in deep content (a work package description/comment, a meeting agenda note or outcome) is findable only if it lands in the 25-item enrichment window (top-by-shallow-score, backfilled by recency). Beyond that window, deep-only matches are missed. See `src/search/pipeline.ts` (`DEFAULT_ENRICH_LIMIT`).
- **Wiki discovery probe window**: `searchWikiPages` builds its cache by probing wiki page IDs 1..50 with a 5-consecutive-miss cutoff. Pages beyond that window, or past a gap of five consecutive deleted/missing IDs, are invisible to search regardless of ranking quality. See `src/services/wikis.ts`.
### Follow-ups (Tracked, Out of This Plan's Scope)
- [ ] `getMeeting` in `src/services/meetings.ts` still swallows auth errors via a `.catch(() => undefined)` on its agenda-item fetch (same bug class fixed for `searchMeetings`). An expired token can still surface as a silently empty meeting detail on that path.
- [ ] `tests/search/recall.test.ts` under-tests ranking-under-competition: most cases have only one corpus document with any lexical overlap, so the suite largely proves "clears the minScore threshold" rather than "ranks the right document above plausible near-misses". Widening the corpus with near-miss documents would strengthen it.
- [ ] `SearchWorkPackagesOptions.typeId` / `assigneeId` (in `src/services/work-packages.ts`) are unreachable from the MCP tool — `searchWorkPackagesShape` in `src/tools/work-packages.ts` never exposes them. Dead surface area worth trimming or wiring up.
---
## 3. Backlog & Future Phases
- [x] **Phase 4: Remote Transport (Completed)**
- [x] Add SSE (Server-Sent Events) transport option for remote deployments.
- [x] Multi-tenant credential scoping with per-session isolation.
- [x] **Phase 3 Extension: Meetings, Wikis, and Activities (Completed - 18 Tools Total)**
- [x] Meetings listing, detail inspection, and deep agenda search.
- [x] Wiki pages retrieval, smart cached discovery search, and work package links.
- [x] Work package activities and comments filtering.
- [x] **Phase 3.5: Fuzzy Search by Default (Completed - 19 Tools Total)**
- [x] Zero-dependency fuzzy scorer (`src/search/`) applied by default across all search tools, with `matchMode: "exact"` opt-out.
- [x] Widened content coverage (wiki bodies, work package descriptions/comments, meeting participants/project names).
- [x] New `openproject_search_work_packages` tool; ranked activities via `openproject_list_work_package_activities`.
- [x] Two known, accepted limitations recorded above (two-phase recall ceiling, wiki discovery probe window).
- [ ] **Phase 2: Mutating Operations**
- [ ] Create work package tool (`openproject_create_work_package`).
- [ ] Update work package tool (`openproject_update_work_package`).
- [ ] Add comments / activities to work packages.
- [ ] Time logging tool (`openproject_log_time`).
- [ ] **Phase 3 (Future): Binary Attachments & File Resources**
- [ ] Inspect and download binary attachment file contents via MCP resources.