Commit Graph
100 Commits
Author SHA1 Message Date
Lucas WintherandClaude Opus 5 f41711a6ed docs: fix unsatisfiable exact-mode test in plan Task 9
The test used query 'certificat', which is a literal substring of the
fixture's 'certificate' comment text. Exact mode is substring containment
by design, so the query matched and the toHaveLength(0) assertion could
never pass. Replaced with an insertion typo, which cannot be a substring
of the shorter original.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-18 11:44:01 +02:00
Lucas WintherandClaude Opus 5 8f29eb651d feat(activities): rank work package activities by relevance
Adds optional query/matchMode to listWorkPackageActivities, ranking the
already-fetched activity list client-side via rankRecords (no extra API
calls). Exposes the same fields on the
openproject_list_work_package_activities tool. When no query is supplied,
every activity is returned exactly as before, and onlyComments continues
to filter independently.

Also fixes a defect in the brief's exact-mode test fixture: the specified
typo query "certificat" is a literal prefix substring of "certificate", so
byte-for-byte exact containment matching (scoreExact in src/search/rank.ts)
finds it regardless of typo tolerance, making the test unable to pass
against a correct implementation. Swapped in "certificatee" (not a
substring of "certificate") to preserve the test's intent without touching
the assertion. Updated one pre-existing test-tools.test.ts call site to
supply matchMode, now required by the extended args type.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-18 11:42:39 +02:00
Lucas WintherandClaude Opus 5 a28067f61e fix(work-packages): propagate fatal auth/rate-limit errors from candidate fetch
The union candidate fetch wrapped both listWorkPackages() legs in an
unconditional .catch(() => ({ items: [] })), swallowing 401/403/429 and
OpenProjectAuthenticationError along with genuinely transient failures. An
expired token would silently degrade to "0 candidates", and the LLM would
report work packages as not found rather than surfacing the auth failure.

Replace the inline catches with a shared listCandidates() helper that uses
isFatalSearchError (already used by searchPipeline) to rethrow fatal errors
and only swallow non-fatal ones, preserving the property that a bad response
on one leg doesn't kill the other leg's candidates.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-18 11:34:17 +02:00
Lucas WintherandClaude Opus 5 326db0659f feat(work-packages): add ranked search tool over subject, description, and comments
Registers openproject_search_work_packages (readOnly: true), taking the tool
registry from 18 to 19. searchWorkPackages now unions a precise server-side
subject filter with a broad recency-ordered candidate window, ranks via the
shared search pipeline, and deep-enriches top candidates with description and
comment text. Exact-mode requests skip the broad query entirely.

searchWorkPackages's return type changes from a flat PaginatedResult<WorkPackageSummary>
to WorkPackageSearchPage, nesting the summary under `workPackage` alongside
score, matchedFields, and an optional snippet.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-18 11:23:48 +02:00
Lucas WintherandClaude Opus 5 9dee458b1f feat(wikis): search page body text with fuzzy relevance ranking
Retain the page body from the formattable text field (already fetched
per-page, zero extra API calls) and rank title/text matches with
rankRecords instead of exact substring on title alone. searchWikiPages
now returns WikiPageSearchResult[] with score, snippet, and
matchedFields; empty-query behavior (project scoping + limit, no
ranking) is preserved. Adds a matchMode tool parameter defaulting to
fuzzy.

Updates the pre-existing tool-metadata assertion in tests/tools.test.ts
to match the new search_wiki_pages description introduced here.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-18 07:48:29 +02:00
Lucas WintherandClaude Opus 5 2d44a229e0 feat(meetings): fuzzy-rank meeting search over agenda, participants, and project
Routes searchMeetings through the shared searchPipeline instead of a
hand-rolled Promise.all fan-out, fixing two live bugs: an expired token
on agenda-item fetch previously produced "no results" (swallowed via
.catch(() => undefined)) instead of propagating the auth error, and up
to 250 candidate meetings could fire simultaneous agenda-item requests.
The pipeline caps enrichment at 25 candidates with 8 in flight, and
isFatalSearchError now lets 401/403/429 propagate while other failures
degrade a single record and set `degraded`.

Also removes the old substring-based extractSnippet from
src/services/meetings.ts in favor of src/search/snippet.ts, and adds
matchMode ("fuzzy" | "exact") to SearchMeetingsParams and the
openproject_search_meetings tool schema.

Updated two pre-existing tests in tests/meetings.test.ts whose fixtures
assumed the old implementation's API surface: one didn't stub the
meeting-detail endpoint the pipeline's deep enrichment now also fetches
(for participants), and one assumed a 50-item candidate page size where
the new pipeline hardcodes 100. Updated one assertion in
tests/tools.test.ts that checked the old literal tool description.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-18 07:40:49 +02:00
Lucas WintherandClaude Opus 5 c47f0669ca feat(search): add two-phase pipeline with bounded concurrency and recency backfill
Adds searchPipeline, which ranks candidates shallowly at no API cost, then
enriches a bounded subset (DEFAULT_ENRICH_LIMIT=25, DEFAULT_CONCURRENCY=8)
and re-ranks, taking max(shallow, deep) so a strong title match is never
diluted by unrelated deep content. Auth and rate-limit errors abort the
whole search via isFatalSearchError instead of degrading silently; other
enrichment failures degrade one record and are counted. A recency backfill
spends leftover enrichment budget on the most recently updated candidates
so a deep-only match is reachable at all. Adds the src/search/ barrel
export.

Reconciles the previously uncommitted pipeline.ts (written against an
earlier, defective version of this task's brief) with the corrected brief:
mapWithConcurrency's phase-2 callback is now given an explicit
EnrichedCandidate return type so its `deep: TDeep | undefined` field is a
single type rather than an inferred union, which is what the `entry is
Pair` narrowing predicate requires under TS2677.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-18 07:30:48 +02:00
Lucas WintherandClaude Opus 5 7b07241367 docs: fix unnarrowable type predicate in plan Task 5 pipeline
mapWithConcurrency's callback return was inferred as a union of two object
literal types rather than one type with deep: TDeep | undefined, so the
'entry is Pair' predicate was rejected: a type predicate's type must be
assignable to its parameter's type. Added an explicit EnrichedCandidate
annotation on the call. Tests passed but tsc did not, so this was only
visible via typecheck.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-18 07:28:20 +02:00
Lucas WintherandClaude Opus 5 3d3b8ff512 feat(search): add generic field-spec record ranking with exact-mode bypass
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-17 10:55:37 +02:00
Lucas WintherandClaude Haiku 4.5 df9f093b5d feat(search): add match-centred snippet extraction
Co-Authored-By: Claude Haiku 4.5 <[email protected]>
2026-09-17 10:50:54 +02:00
Lucas WintherandClaude Opus 5 926e241c7b feat(search): add bounded levenshtein and coverage-weighted field scoring
Adds boundedLevenshtein with row-minimum early exit, token scoring
(exact / prefix / contains / shared-prefix / edit distance), and
coverage-weighted field aggregation scaled by BASE_SCALE so that only a
verbatim phrase match reaches 1.0.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-17 10:46:58 +02:00
Lucas WintherandClaude Opus 5 c4e303c8b7 docs: fix unreachable shared-prefix test cases in plan Task 2
The guard tests used 3-char query tokens whose field tokens were literal
continuations ('car'/'carpet'), so the startsWith rule returned 0.9 before
the shared-prefix rule was reached. With MIN_SHARED_PREFIX=4 the rule is
unreachable for any 3-char token, making the assertion impossible to
satisfy. Swapped in cases where neither token is a true prefix of the
other, which actually exercise the guard.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-17 10:26:54 +02:00
Lucas WintherandClaude Opus 5 28a834eb32 fix(search): eliminate mutable regex state with matchAll
Replace manual exec loop with matchAll to eliminate cross-call mutable
lastIndex state on module-level TOKEN_PATTERN regex. matchAll clones
the regex internally, ensuring no shared state while maintaining
identical behavior and original-text offset semantics.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-17 10:19:17 +02:00
Lucas WintherandClaude Opus 5 5b0f73ae0e feat(search): add unicode-aware tokenizer with original-text offsets
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-17 10:14:30 +02:00
Lucas WintherandClaude Opus 5 582295c960 docs: correct scoring design defects found in preflight scan
Hand-computing the plan's own test expectations against its own formula
showed four assertions could not pass:

- Body-only matches maxed at 1.0*1/3 = 0.333, below minScore 0.35, so no
  low-weight field could ever return a result. The gate now uses the raw
  field score; the weighted score only orders.
- The phrase bonus was dead code whenever every token matched, since the
  base already saturated at 1.0. Base is now scaled by 0.85.
- Natural-language queries tanked the coverage multiplier; query tokens
  are now stopword-filtered.
- 'decide' vs 'decision' is 4 edits apart and unmatchable; scoreToken
  gains a shared-prefix rule.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-17 10:13:10 +02:00
Lucas WintherandClaude Opus 5 cbd8992b11 docs: add fuzzy search implementation plan and correct spec tool gap
Adds an 11-task TDD implementation plan covering the pure search unit
(tokenize, score, rank, snippet, pipeline), per-service integration for
meetings, wikis, work packages and activities, a recall acceptance
suite, and documentation.

Corrects two errors found while writing the plan:
- The spec's behaviour table named an openproject_search_work_packages
  tool that does not exist. searchWorkPackages is a service helper that
  only tests call, so ranked work package search would have been
  unreachable by any LLM. The plan now registers the tool (18 -> 19) and
  leaves openproject_list_work_packages as a pure server-side filter.
- Plan Task 6 originally identified matching agenda items by substring
  searching the meeting-level snippet, which cannot work because
  snippets are trimmed and ellipsized. Agenda items are now ranked
  directly.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-17 10:06:56 +02:00
Lucas WintherandClaude Opus 5 5dc609cc7d docs: add design spec for fuzzy search by default
Specifies replacing exact substring matching with token-aware fuzzy
matching as the default across all search tools, plus relevance ranking
and wider content coverage (wiki body text, work package descriptions
and comments, meeting participants and project names).

Key decisions:
- Hand-rolled zero-dependency scorer in src/search/; fuse.js rejected
  because its Bitap implementation caps patterns at 32 chars and targets
  short fields, not the long-text matching required here.
- matchMode: 'fuzzy' | 'exact' escape hatch, defaulting to fuzzy, with
  exact mode preserving current behaviour byte-for-byte.
- Two-phase pipeline: cheap shallow rank, deep-enrich top 25 with
  bounded concurrency, recency backfill for deep-only matches.

Also records two known limitations left out of scope: the two-phase
recall ceiling and the wiki cache's ID 1..50 probe window.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-17 09:54:30 +02:00
Lucas Winther 7097d1c174 fix: resolve final review findings (wiki cache tenant isolation, meeting search pagination, ambient test) 2026-09-07 03:12:55 +02:00
Lucas Winther e0aa91e388 docs: record ADR-018 and update documentation for meetings, wikis, and activities tools 2026-09-07 03:06:38 +02:00
Lucas Winther d4a905d419 test: add live container tests for meetings, wikis, and activities 2026-09-07 03:01:52 +02:00
Lucas Winther d6e78d8cc3 feat: wire meetings, wikis, and activities tools into server registry (18 tools) 2026-09-07 02:56:00 +02:00
Lucas Winther 2fb25a9604 fix(wikis): refine projectId schema in searchWikiPagesShape 2026-09-07 02:51:46 +02:00
Lucas Winther 16e54614ac feat: add wikis MCP tools 2026-09-07 02:50:09 +02:00
Lucas Winther 271f15b4e3 feat: add meetings MCP tools 2026-09-07 02:43:04 +02:00
Lucas Winther 190278fcce feat: add openproject_list_work_package_activities MCP tool 2026-09-07 02:39:01 +02:00
Lucas Winther 6003925bcf fix: discriminate discovery probe errors and evict cache on refresh 2026-09-07 02:34:35 +02:00
Lucas Winther 1ce61506b2 feat: add wikis domain service with caching discovery search 2026-09-07 02:31:03 +02:00
Lucas Winther c0125121cc fix: resolve typecheck errors and improve searchMeetings pagination slicing 2026-09-07 02:26:14 +02:00
Lucas Winther 8c3db68307 feat: add meetings domain service 2026-09-07 02:22:10 +02:00
Lucas Winther 456c90adba feat: add listWorkPackageActivities domain service 2026-09-07 02:14:18 +02:00
Lucas Winther 324e7fce9c docs: add implementation plan for meetings, wikis, and activities tools 2026-09-07 02:11:03 +02:00
Lucas Winther e08eac3a2b docs: add design spec for meetings, wikis, and activities tools 2026-09-07 02:09:14 +02:00
Lucas Winther 75531eca2d feat(http): add OpenAPI 3.1.0 specification and REST tool execution endpoint 2026-09-07 01:38:32 +02:00
Lucas Winther ba1ce2171e feat(http): support MCP Streamable HTTP transport on /sse, /mcp, and / for Open WebUI 2026-09-07 01:31:04 +02:00
Lucas Winther 2c1ff8f7db feat(http): allow config.apiKey fallback on server for single-tenant deployments 2026-09-07 01:23:31 +02:00
Lucas Winther 914883957e docs: ensure consistent PORT and HOST_PORT documentation across codebase and docs 2026-09-07 01:20:29 +02:00
Lucas Winther c2bb2e0cf7 docs(readme): clarify PORT vs HOST_PORT in docker compose deployment instructions 2026-09-07 01:18:41 +02:00
Lucas Winther 86c7dadb9b fix(config): support HOST_PORT as fallback for PORT environment variable 2026-09-07 01:16:48 +02:00
Lucas Winther c9ba15bca0 docs: update ARCHITECTURE.md, README.md, and plan with hosted remote server details 2026-09-07 01:11:07 +02:00
Lucas Winther d0dc27b2e3 feat(server): wire HTTP transport into CLI, add docker-compose.server.yml, and update docs 2026-09-07 00:14:49 +02:00
Lucas Winther ec85bf811b feat(http): implement hosted remote MCP server with HTTP/SSE transport 2026-09-06 23:36:34 +02:00
Lucas Winther cda83e320f feat(config): support port and host options for HTTP/SSE transport 2026-09-06 21:22:50 +02:00
Lucas Winther 2fad0036e7 docs(plan): add implementation plan for hosted remote MCP server and multi-tenant credential scoping 2026-09-06 21:13:07 +02:00
Lucas Winther 1f3eae4258 docs(spec): add specification for hosted remote MCP server and multi-tenant credential scoping 2026-09-06 21:12:14 +02:00
Lucas Winther 2fb02257f0 docs: update README with detailed usage instructions, tool reference, and client guides 2026-09-06 21:08:41 +02:00
Lucas Winther e56bd574e2 fix(tests): skip live container tests when OPENPROJECT_API_KEY is not set 2026-09-06 21:05:36 +02:00
Lucas Winther 03a1f44517 fix: restore strict noUncheckedIndexedAccess and skip redundant docker build in CI 2026-09-06 20:54:12 +02:00
Lucas Winther a30d913470 docs: record ADR-015, update README with Docker usage, and update TODO.md 2026-09-06 20:48:55 +02:00
Lucas Winther 2b93ebe0a1 fix(ci): lowercase Docker image name in CI workflow and optimize Dockerfile layer creation 2026-09-06 20:48:54 +02:00
Lucas Winther 82138fff61 fix(types): resolve typecheck errors in queries service and tool registration 2026-09-06 20:48:52 +02:00
Lucas Winther 51c92ea927 ci: create GitHub Actions workflow for testing and multi-arch Docker publishing 2026-09-06 20:44:09 +02:00
Lucas Winther df2bad2f62 feat(docker): add multi-stage Dockerfile and .dockerignore 2026-09-06 20:40:27 +02:00
Lucas Winther 1a32b8a179 docs(plan): add implementation plan for Docker packaging and GitHub Actions CI pipeline 2026-09-06 20:37:44 +02:00
Lucas Winther 7ce86012d0 docs(spec): add specification for Docker packaging and GitHub Actions CI pipeline 2026-09-06 20:36:43 +02:00
Lucas Winther 815741b45b docs(plan): update progress ledger and mark Task 3 complete 2026-09-06 20:02:16 +02:00
Lucas Winther 191e37252b test(openapi): add live container tests, update tool count assertions, and update TODO.md 2026-09-06 20:00:02 +02:00
Lucas Winther eaf6d67a30 docs(plan): update progress ledger and mark Task 2 complete 2026-09-06 19:58:20 +02:00
Lucas Winther a5f4dcc2ca feat(tools): add openproject_get_openapi_spec tool definition and registration 2026-09-06 19:56:34 +02:00
Lucas Winther 1039a6aa7a docs(plan): update progress ledger and mark Task 1 complete 2026-09-06 19:49:31 +02:00
Lucas Winther 69f2a95371 fix(openapi): guard suffix matching and add ambient context test 2026-09-06 19:49:13 +02:00
Lucas Winther 25fc2c9057 feat(services): implement OpenAPI specification service and in-memory caching 2026-09-06 19:40:33 +02:00
Lucas Winther 635ac88f5e docs(plan): add OpenAPI tool implementation plan 2026-09-06 19:37:56 +02:00
Lucas Winther 6e166e98b8 docs(spec): add OpenAPI tool design specification 2026-09-06 19:37:04 +02:00
Lucas Winther d9b96862fe docs: record ADR-013 (stdio hygiene) and ADR-014 (OpenAPI discovery) 2026-09-06 19:31:43 +02:00
Lucas Winther fd9697b2b7 docs(plan): update progress ledger and mark Task 4 plan steps complete 2026-09-06 19:23:18 +02:00
Lucas Winther 3d4a064593 refactor(server): add shutdown idempotency guard and test teardown blocks 2026-09-06 19:22:46 +02:00
Lucas Winther f1e84d281a feat(server): wire CLI entrypoint, live E2E tool calling, and mark Task 4 complete in TODO.md 2026-09-06 19:17:40 +02:00
Lucas Winther 8297f97228 docs(plan): update progress ledger and check off Task 3 2026-09-06 19:16:26 +02:00
Lucas Winther 4994b2c8db test(server): clean up read-only manifest filter assertions 2026-09-06 19:16:04 +02:00
Lucas Winther af2a8f3af8 feat(server): add read-only mode enforcement and execution guard tests 2026-09-06 19:14:25 +02:00
Lucas Winther d49e0fc881 feat(server): implement MCP server factory and transport lifecycle 2026-09-06 19:03:09 +02:00
Lucas Winther 3f618b2cf3 feat(tools): add execution wrapper support to tool registration 2026-09-06 18:52:22 +02:00
Lucas Winther 08eff37db0 docs: add design spec and implementation plan for MCP server assembly 2026-09-06 18:40:47 +02:00
Lucas Winther 6b8045111b fix(tools): address final code review findings on validation, concurrency, and testing 2026-09-06 18:37:21 +02:00
Lucas Winther 9bfedf8a8b docs: add OpenAPI support requirement to spec, architecture, and plan 2026-09-06 18:11:58 +02:00
Lucas Winther ebb47eb659 feat(tools): add live container tests and mark Task 3 complete in TODO.md 2026-09-06 18:10:00 +02:00
Lucas Winther 4eb17c2e5c feat(tools): add tool registry and barrel export 2026-09-06 18:04:50 +02:00
Lucas Winther 8cdca0fd83 feat(tools): implement metadata tools and registration 2026-09-06 18:01:43 +02:00
Lucas Winther ff05b48f81 feat(tools): implement query tools and registration 2026-09-06 17:54:42 +02:00
Lucas Winther e3c956f900 docs: record completion of tasks 1-3 in MCP tools plan and TODO.md 2026-09-06 02:21:45 +02:00
Lucas Winther 3910c5548f fix(tools): address review finding on heterogeneous tool registration and McpToolResponse index signature 2026-09-06 02:18:47 +02:00
Lucas Winther d9176a258c feat(tools): implement work package tools and registration 2026-09-06 01:50:58 +02:00
Lucas Winther 8ccbebe34f feat(tools): implement project tools and registration 2026-09-06 01:30:17 +02:00
Lucas Winther 43eeee2e04 fix(tools): address review findings on error codes, registerTool assertions, and strict typing 2026-09-06 01:03:40 +02:00
Lucas Winther bb321bfdbe feat(tools): add common tool utilities, types, and error formatting 2026-09-06 00:48:25 +02:00
Lucas Winther 290d9a2cc9 refactor(client): expose error hierarchy via src/client/errors.ts and add code property 2026-09-06 00:48:04 +02:00
Lucas Winther f8f9ae65a2 docs: add design spec and implementation plan for MCP tools 2026-09-06 00:45:30 +02:00
Lucas Winther a57e8ba7df fix(services): encode URI in getProject and improve test assertions 2026-09-06 00:23:58 +02:00
Lucas Winther ca22a0e109 docs: check off tasks 4-6 in domain services implementation plan 2026-09-06 00:22:20 +02:00
Lucas Winther 78ae79372c feat(services): add live container tests and mark Task 2 complete in TODO.md 2026-09-05 23:12:49 +02:00
Lucas Winther ed38e5b537 fix(services): resolve project identifier in queries/work packages and use results link 2026-09-05 23:12:46 +02:00
Lucas Winther a8e4ce9bc4 feat(services): implement metadata domain service and barrel export 2026-09-05 22:57:31 +02:00
Lucas Winther 8db5a92bc5 feat(services): implement queries domain service 2026-09-05 22:49:18 +02:00
Lucas Winther cbf676007f docs: track completion of tasks 1-3 in domain services plan and TODO.md 2026-09-05 18:22:12 +02:00
Lucas Winther b1348dbea8 feat(services): implement work packages domain service 2026-09-05 18:17:24 +02:00
Lucas Winther 6e0d3faa61 feat(services): implement projects domain service 2026-09-05 17:49:52 +02:00
Lucas Winther c50ab8989c feat(services): implement shared client resolution helper 2026-09-05 17:45:20 +02:00
Lucas Winther 86e60c9f28 chore: ignore .superpowers/ and .worktrees/ directories 2026-09-05 17:43:16 +02:00
Lucas Winther 25677e867d docs(plans): add domain services layer implementation plan 2026-09-05 17:41:52 +02:00
Lucas Winther 091791faef docs(specs): add domain services layer design specification 2026-09-05 17:37:53 +02:00