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

15 KiB

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

  • Agentic Development Guidelines & Infrastructure

    • Created AGENTS.md defining architectural conventions, coding practices, and agent workflow.
    • Created CLAUDE.md and GEMINI.md pointing to AGENTS.md.
    • Created docs/ARCHITECTURE.md documenting layers, data flow, tool specifications, and API specifics.
    • Created docs/DECISIONS.md establishing initial ADRs (stack, transport, auth, scoping, testing).
    • Created root .gitignore protecting credentials, cache, and IDE artifacts.
  • OpenProject 17 Local Docker Stack & Seeding

    • Created docker-compose.yml configured for OpenProject 17 (openproject/openproject:17-slim), PostgreSQL 17, and Memcached.
    • Created .env.example documenting all stack and MCP client environment variables.
    • Created 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).
    • Created scripts/seed.sh orchestrating health checks and automated writing to .env.test and .env.local.
    • Created lifecycle scripts scripts/start.sh and scripts/stop.sh.
    • Verified live container execution and validated HTTP 200 API v3 access (/api/v3/projects) with generated token.
    • Recorded ADR-009 targeting OpenProject 17.
  • Bun Runtime Migration & Tooling


2. Next Up (Phase 1: Read/Browse Implementation)

  • Task 1: OpenProject REST API v3 Client Layer

    • Implement src/config/index.ts to parse and validate OPENPROJECT_BASE_URL, OPENPROJECT_API_KEY, and OPENPROJECT_READ_ONLY (or --read-only) via Zod.
    • Implement src/context.ts providing RequestContext interface and AsyncLocalStorage scoping (getRequestContext, runWithContext) for concurrent multi-user safety.
    • Implement src/client/api-client.ts with native fetch, HTTP Basic Auth injection, error normalization, and rate/timeout handling.
    • Implement src/client/hal-parser.ts to unpack HAL+JSON (_links, _embedded) into clean, token-efficient JSON models.
    • Implement src/client/filter-builder.ts to translate LLM-friendly filter arguments into OpenProject's JSON filter syntax.
    • Add unit tests in tests/client.test.ts verifying authentication headers, response parsing, and error sanitization against live container.
  • Task 2: Domain Services

    • Implement src/services/helper.ts (resolveClient for ambient RequestContext resolution).
    • Implement src/services/projects.ts (listProjects, getProject, getProjectSchema).
    • Implement src/services/work-packages.ts (listWorkPackages, getWorkPackage, searchWorkPackages).
    • Implement src/services/queries.ts (listQueries, getQuery, getQueryResults).
    • Implement src/services/metadata.ts (listStatuses, listTypes, listPriorities, listUsers).
    • Implement src/services/openapi.ts (getOpenApiSpec with in-memory caching, summary mode, path endpoint lookup, tag filtering, schema inspection).
    • Add service tests in tests/services.test.ts (unit tests and live container integration suite passing).
  • Task 3: MCP Tool Definitions & Schema Registration

    • Define Zod schemas and register tools in src/tools/:
      • openproject_list_projects
      • openproject_get_project
      • openproject_list_work_packages
      • openproject_get_work_package
      • openproject_list_queries
      • openproject_get_query
      • openproject_list_types
      • openproject_list_statuses
      • openproject_list_priorities
      • openproject_list_users
      • openproject_get_openapi_spec
  • Task 4: MCP Stdio Server Assembly & Integration

    • Wire domain tools into @modelcontextprotocol/sdk Server using StdioServerTransport in src/server.ts and src/index.ts.
    • Implement read-only mode tool filtering (omits mutating tools when readOnly is enabled) and execution guard (SERVER_READ_ONLY).
    • Ensure all logging is strictly redirected to stderr.
    • Add end-to-end integration tests in tests/mcp-server.test.ts verifying tool calling against the running OpenProject 17 instance.
    • Add read-only mode verification tests in tests/read-only.test.ts.
  • Task 5: OpenAPI Introspection Tool & Service

    • Implement src/services/openapi.ts with in-memory caching, multi-mode queries (summary, path, tag, schema), and ambient client resolution.
    • Implement src/tools/openapi.ts registering openproject_get_openapi_spec (readOnly: true) into allTools (bringing tool count to 11).
    • Add comprehensive test suite in tests/openapi.test.ts covering service methods, MCP client execution, error handling, and live OpenProject 17 container integration.
    • Update suite regressions and assertions across all test suites (tests/tools.test.ts, tests/read-only.test.ts, tests/mcp-server.test.ts).
  • Task 6: Docker Container Packaging & CI/CD Pipeline

    • Created multi-stage Dockerfile using oven/bun:1-slim builder and unprivileged bun user runner.
    • Created .dockerignore excluding secrets, local dependencies, test files, and docs from build context.
    • Implemented GitHub Actions CI workflow .github/workflows/ci.yml with automated type checking, testing, and multi-arch Docker image publishing (linux/amd64, linux/arm64) to ghcr.io.
    • Added automated test coverage in tests/docker.test.ts and tests/ci-workflow.test.ts.
    • Recorded ADR-015.
    • Updated README.md with Docker Quickstart, read-only mode instructions, and client integration settings (Claude Desktop & Cursor).
  • Task 7: Hosted Remote MCP Server (HTTP/SSE Transport) & Multi-Tenant Credential Scoping

    • Extend configuration loader with port and host options (src/config/index.ts, tests/config.test.ts).
    • Implement hosted HTTP/SSE MCP server with Bun.serve and Web Streams SSE (src/http-server.ts, tests/http-server.test.ts).
    • Implement credential extraction hierarchy (Authorization: Bearer, X-OpenProject-Api-Key, ?apiKey=) with 401 unauthorized rejection.
    • Implement per-session client isolation and runWithContext dynamic scoping.
    • Wire dual-transport execution in CLI entrypoint (src/index.ts).
    • Create production docker-compose.server.yml with /health check probe.
    • Record ADR-016.
    • Update documentation with hosted deployment guide and remote client setups for Cursor and Claude Desktop in README.md.
  • Task 8: OpenAPI 3.1.0 Specification & REST Tool Execution Bridge

    • Implement dynamic OpenAPI specification generator src/openapi-spec.ts.
    • Expose GET /openapi.json and GET /swagger.json in src/http-server.ts.
    • Expose REST tool execution endpoint POST /api/tools/:toolName with Zod parameter validation and RequestContext execution.
    • Add automated test coverage in tests/http-server.test.ts.
    • Record ADR-017 in docs/DECISIONS.md.
    • Document OpenAPI connection mode and server endpoints reference table in README.md.
  • Task 9: Meetings, Wikis, and Activities Extension (18 Tools Total)

    • Implemented listWorkPackageActivities in src/services/work-packages.ts separating comments from field mutations with onlyComments filter.
    • Implemented listMeetings, getMeeting (with embedded agenda items/outcomes), and searchMeetings (deep search across titles, locations, and agenda item notes) in src/services/meetings.ts.
    • Implemented getWikiPage (with attachments), listWikiPageLinks, and searchWikiPages with smart discovery caching and consecutive 404 cutoff in src/services/wikis.ts.
    • Registered openproject_list_work_package_activities in src/tools/work-packages.ts.
    • Registered openproject_list_meetings, openproject_get_meeting, openproject_search_meetings in src/tools/meetings.ts.
    • Registered openproject_get_wiki_page, openproject_search_wiki_pages, openproject_list_wiki_page_links in src/tools/wikis.ts.
    • Wired all 18 tools into server registry (src/tools/index.ts, src/services/index.ts) and updated all test suite assertions to 18 tools.
    • Added live OpenProject 17 container integration tests in tests/services.test.ts and tests/mcp-server.test.ts.
    • Recorded ADR-018 and updated ARCHITECTURE.md, TODO.md, and README.md.
  • Task 10: Fuzzy Search by Default (19 Tools Total)

    • 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.
    • 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.
    • 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.
    • Added openproject_search_work_packages, exposing the previously tool-unreachable searchWorkPackages service helper as a ranked search tool over subjects, descriptions, and comments.
    • Added optional query/matchMode ranking to openproject_list_work_package_activities (ranks already-fetched activities client-side, no extra API cost).
    • 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).
    • 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.
    • Recorded ADR-019 and updated ARCHITECTURE.md, TODO.md, and 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

  • Phase 4: Remote Transport (Completed)
    • Add SSE (Server-Sent Events) transport option for remote deployments.
    • Multi-tenant credential scoping with per-session isolation.
  • Phase 3 Extension: Meetings, Wikis, and Activities (Completed - 18 Tools Total)
    • Meetings listing, detail inspection, and deep agenda search.
    • Wiki pages retrieval, smart cached discovery search, and work package links.
    • Work package activities and comments filtering.
  • Phase 3.5: Fuzzy Search by Default (Completed - 19 Tools Total)
    • Zero-dependency fuzzy scorer (src/search/) applied by default across all search tools, with matchMode: "exact" opt-out.
    • Widened content coverage (wiki bodies, work package descriptions/comments, meeting participants/project names).
    • New openproject_search_work_packages tool; ranked activities via openproject_list_work_package_activities.
    • 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.