Skip to content

Quality-first architecture refactor + strict contracts + docs/CI hardening#10

Merged
clopca merged 5 commits into
mainfrom
codex/quality-first-architecture-refactor
Feb 16, 2026
Merged

Quality-first architecture refactor + strict contracts + docs/CI hardening#10
clopca merged 5 commits into
mainfrom
codex/quality-first-architecture-refactor

Conversation

@clopca

@clopca clopca commented Feb 16, 2026

Copy link
Copy Markdown
Owner

Summary

This PR applies the quality-first architecture migration end-to-end and removes compatibility-maintenance overhead that no longer aligns with project goals.

What changed

  • Refactored search to a pluggable strategy model with SearchStrategyRegistry and thin orchestrator composition.
  • Added mode resolution v2 (ModeResolverV2) with inheritance support and validation.
  • Added policy-driven provider failover via ProviderFallbackPolicy.
  • Unified contracts via canonical schema module (src/contracts/schemas.ts) and reused across adapters.
  • Added readiness/diagnostics operational services and endpoints, plus doctor CLI.
  • Added architecture governance assets (ADR template/principles, CODEOWNERS, PR checklist, engineering policy docs).
  • Expanded CI quality gates and nightly architecture workflows.
  • Removed legacy compatibility surfaces and external-compat pipeline artifacts (scripts, workflows, docs matrix) to reduce maintenance debt.
  • Updated docs to current strict model and canonical mem-* tool naming.

Validation

  • bun run lint passes
  • bun run typecheck passes
  • bun run test:repo passes (878 pass, 0 fail)
  • bun run docs:build passes

Notes

  • This PR intentionally favors end-state architecture quality over backward-compatibility shims.
  • Claude Code/Cursor/OpenCode adapter functionality and MCP runtime capability remain intact; only compatibility-reporting overhead was removed.

Note

Medium Risk
Moderate risk because it refactors core search execution, tightens MCP initialization behavior (drops legacy mode), and adds new operator HTTP endpoints/CLI that affect runtime and release workflows.

Overview
Governance + CI hardening: introduces CODEOWNERS, a PR template, ADR/policy docs, and new CI workflows/steps (typecheck, lint, check:contracts, check:architecture, check:migration, plus scheduled architecture/nightly checks) while removing the external-compat workflow and its supporting scripts/docs/artifacts.

Contracts + ops surfaces: centralizes tool schemas/metadata into src/contracts/schemas.ts (with a check-contract-drift gate) and wires contract metadata through MCP/OpenCode/HTTP; adds HTTP endpoints for readiness, diagnostics, tools/guide, and local-only queue operator routes (/v1/queue, /v1/queue/process) plus a new open-mem-doctor CLI.

Core behavior changes: refactors search into a pluggable strategy registry with extracted strategy implementations, replaces mode loading with ModeResolverV2 (supports extends + safer fallback copies), makes provider failover policy-driven, removes OPEN_MEM_MCP_COMPAT_MODE/legacy MCP mode (strict init required), and tightens a concept-search FTS query by quoting/escaping.

Written by Cursor Bugbot for commit 514a31a. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Runtime readiness, diagnostics, tools guide, and local queue operator endpoints
    • Renamed public tools from memory.* to mem-* and added mem-maintenance
    • Pluggable search strategies for improved retrieval
    • Diagnostic CLI for project health checks
  • Documentation

    • ADRs, quality-gates, deprecation & technical-debt policies; updated API docs and changelog
    • Removed external compatibility matrix/content
  • Refactor

    • Removed MCP compatibility mode and related verification workflows
    • Resolver-based mode loading and search registry introduced
  • Chores

    • Expanded CI/nightly checks and architecture health reporting scripts

@coderabbitai

coderabbitai Bot commented Feb 16, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Centralizes tool contracts and renames memory tools to mem-*, removes the external compatibility pipeline, adds readiness/diagnostics/queue HTTP endpoints, introduces search strategy registry and AI provider fallback policies, refactors mode resolution with inheritance, and adds CI/quality-gate tooling and governance docs.

Changes

Cohort / File(s) Summary
External compatibility removal
.github/workflows/external-compat.yml, scripts/check-external-compat-gate.ts, scripts/detect-client-versions.ts, scripts/external-compat.ts, scripts/render-compat-matrix.ts, scripts/smoke-platform-workers.ts, scripts/verify-external-clients-auto.ts, scripts/verify-external-clients.ts, docs/mcp-compatibility-matrix.md, docs/compatibility/external-compat-latest.json, docs/schemas/external-compat-report.schema.json, tests/fixtures/external-clients/*, tests/scripts/external-compat-scripts.test.ts
Removes the entire external MCP compatibility verification pipeline: workflows, scripts, fixtures, schema, reports, rendering, and related tests.
Tool contracts & renames
src/contracts/schemas.ts, src/contracts/api.ts, docs/tools.md, docs/api.md, docs/* (multiple docs), tests/contracts/schemas.test.ts
Adds centralized contract module (CONTRACT_VERSION, TOOL_CONTRACTS, toolSchemas, getters) and migrates docs/examples/tests to mem-* tool names; updates tool signatures and adds mem-maintenance.
MCP compatibility mode removal
src/config.ts, src/config/store.ts, src/mcp.ts, src/types.ts, docs/configuration.md, tests/config.test.ts
Deletes mcpCompatibilityMode config and env mapping; shifts MCP handling to protocol-version fields and removes strict/legacy gating.
HTTP endpoints & diagnostics
src/adapters/http/server.ts, src/services/readiness.ts, src/services/setup-diagnostics.ts, tests/servers/http-server.test.ts, tests/services/readiness.test.ts, tests/services/setup-diagnostics.test.ts
Adds /v1/readiness, /v1/diagnostics, /v1/tools/guide, /v1/queue, /v1/queue/process, local-request gating, and new DefaultReadinessService/DefaultSetupDiagnosticsService with tests.
Mode resolver & loader refactor
src/modes/resolver.ts, src/modes/loader.ts, tests/modes/resolver-v2.test.ts, tests/modes/modes.test.ts
Introduces ModeResolverV2 with ModeConfigSource, extends-based inheritance, cycle detection, and loader delegation; adds locale and promptOverrides fields.
Search strategy registry & strategies
src/search/registry.ts, src/search/orchestrator.ts, src/search/strategies/*, src/search/strategies/types.ts, tests/search/registry.test.ts, tests/search/orchestrator.test.ts
Adds InMemorySearchStrategyRegistry, extracts filter-only/semantic/hybrid executors, refactors orchestrator to use registry and normalize options.
AI provider fallback policy
src/ai/fallback-policy.ts, src/ai/fallback.ts, src/ai/provider.ts
Adds ProviderFallbackPolicy interface and DefaultProviderFallbackPolicy; wires policy into FallbackLanguageModel and createModelWithFallback (new optional policy parameter).
Governance, CI and quality gates
.github/CODEOWNERS, .github/pull_request_template.md, docs/engineering/*, docs/architecture/adr/*, .github/workflows/ci.yml, .github/workflows/nightly-matrix.yml, .github/workflows/architecture-health.yml, .github/workflows/release-gate.yml, .github/platform-adapter-runbook.md
Adds CODEOWNERS and PR template; new docs for quality gates, deprecation, technical debt, ADR template; updates CI to include typecheck/lint/contracts/architecture/migration checks, nightly matrix, and architecture health workflow.
Scripts & tooling
scripts/architecture-health-report.ts, scripts/check-architecture-fitness.ts, scripts/check-contract-drift.ts, scripts/check-migration-compat.ts, package.json, src/doctor.ts
Adds architecture-health report, architecture-fitness, contract-drift, migration checks, doctor CLI, and package.json script/bin updates; removes many compat-related scripts.
Search/result internals & small fixes
src/search/orchestrator.ts, src/search/*, src/core/memory-engine.ts
Refactors search orchestration internals to registry/executors, adds helpers and normalizeOptions; minor memory-engine optional-chaining fix.
Tests & fixtures cleanup
tests/* (added/removed tests), tests/fixtures/*
Adds tests for resolver, readiness, diagnostics, search registry; removes external-compat test suites and fixtures.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant HTTP as HTTP Server
    participant Readiness as DefaultReadinessService
    participant Runtime as MemoryEngine
    participant Config as Config

    Client->>HTTP: GET /v1/readiness
    HTTP->>Readiness: evaluate({config, adapterStatuses, runtime})
    Readiness->>Config: validate compression/provider settings
    Readiness->>Runtime: query runtime.status and runtime.queue
    Readiness-->>HTTP: {ready, status, reasons}
    HTTP-->>Client: 200/503 readiness envelope
Loading
sequenceDiagram
    participant Search as SearchOrchestrator
    participant Registry as StrategyRegistry
    participant Executor as StrategyExecutor
    participant Repo as ObservationRepository

    Search->>Registry: get(strategyId)
    Registry-->>Search: executor
    Search->>Executor: execute(deps, query, options, limit)
    Executor->>Repo: search / searchByConcept / searchByFile
    Repo-->>Executor: observations[]
    Executor-->>Search: SearchResult[]
    Search-->>Client: ranked results
Loading
sequenceDiagram
    participant CLI as Mode Loader
    participant Resolver as ModeResolverV2
    participant FS as FileSystem
    participant Map as RawModesMap

    CLI->>Resolver: loadAllRaw()
    Resolver->>FS: read mode JSON files
    FS-->>Resolver: file contents
    Resolver-->>Map: Map<id, ModeConfigSource>
    CLI->>Resolver: resolveById(id, Map)
    alt has parent
        Resolver->>Resolver: resolveById(parent, Map)
        Resolver-->>CLI: merged ModeConfig
    else cycle or missing
        Resolver-->>CLI: DEFAULT_MODE
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 I hopped through contracts, tidy and new,

mem-find and mem-history in a neat queue,
Registries plugged in, fallbacks in tow,
Diagnostics hum, readiness aglow,
A carrot of code—clean and true! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Quality-first architecture refactor + strict contracts + docs/CI hardening' clearly and specifically summarizes the main changes in the PR: architecture refactoring, contract standardization, and documentation/CI improvements.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/quality-first-architecture-refactor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Feb 16, 2026

Copy link
Copy Markdown

Greptile Summary

This PR migrates to a quality-first architecture by refactoring search into a pluggable strategy registry, adding mode inheritance via ModeResolverV2, introducing policy-driven provider failover, and unifying tool contracts into a canonical schema module (src/contracts/schemas.ts) shared across MCP, HTTP, and OpenCode adapters.

  • Search refactor: Extracted filter-only, semantic, and hybrid strategies into src/search/strategies/ with a SearchStrategyRegistry pattern. The orchestrator now delegates to registered executors instead of embedding strategy logic directly.
  • Mode resolver v2: Added extends-based inheritance with cycle detection and defensive cloning to prevent shared-state mutation.
  • Provider fallback policy: Extracted retry/failover decisions into a ProviderFallbackPolicy interface, making failover behavior injectable and testable.
  • Canonical contracts: Centralized tool metadata (names, descriptions, schemas) in TOOL_CONTRACTS, eliminating duplicated inline tool definitions across adapters.
  • Operational endpoints: Added /v1/readiness, /v1/diagnostics, /v1/tools/guide, and localhost-gated /v1/queue + /v1/queue/process. The localhost enforcement uses Host header inspection which is spoofable by remote clients — this is the most significant issue in the PR.
  • Filter normalization gap: The orchestrator's normalizeOptions merges singular concept/file into plural arrays, but the filter-only strategy still checks singular fields first, silently ignoring merged array entries.
  • Removed legacy compat: Dropped mcpCompatibilityMode, external-compat workflows/scripts, and v1 revision-diff compatibility response.
  • CI/governance: Added architecture-health, nightly-matrix workflows, CODEOWNERS, PR template, ADR template, and engineering policy docs.

Confidence Score: 3/5

  • Mostly safe structural refactor but contains a bypassable localhost access control that gates queue processing endpoints.
  • The majority of changes are well-structured refactors (strategy pattern, contract centralization, mode inheritance) with good test coverage. However, the isLocalRequest function in the HTTP server relies on the client-controlled Host and X-Forwarded-For headers rather than actual socket address, making the localhost restriction on /v1/queue and /v1/queue/process endpoints trivially bypassable by remote clients. Additionally, the filter-only strategy's singular-field short-circuit creates a subtle inconsistency with the orchestrator's normalization logic. The compat removal is intentional and well-documented.
  • src/adapters/http/server.ts (spoofable localhost check), src/search/strategies/filter-only.ts (singular field short-circuit bypasses normalized arrays)

Important Files Changed

Filename Overview
src/adapters/http/server.ts Adds readiness, diagnostics, tools/guide, and queue endpoints. The isLocalRequest function used to gate /v1/queue relies on spoofable HTTP headers (Host, X-Forwarded-For) rather than actual socket address, making the localhost restriction bypassable by remote clients.
src/contracts/schemas.ts New canonical schema module centralizing tool contracts, Zod schemas, and metadata. Clean design with consistent naming. No issues found.
src/search/orchestrator.ts Refactored to use strategy registry pattern. normalizeOptions correctly merges singular concept/file into plural arrays but preserves the original singular fields, which creates an inconsistency when filter-only strategy short-circuits on singular fields.
src/search/registry.ts Clean strategy registry abstraction with generic type parameter. Simple Map-based implementation. No issues found.
src/search/strategies/filter-only.ts Extracted filter-only strategy. Singular concept/file early-return paths ignore the normalized plural arrays from the orchestrator, silently dropping additional filter values.
src/modes/resolver.ts New ModeResolverV2 with inheritance via extends field and cycle detection. Defensive cloning prevents shared-state mutation. Well-structured.
src/modes/loader.ts Simplified to delegate to ModeResolverV2. Minor dead code: ?? getDefaultModeConfig() on line 28 is unreachable since loadMode never returns null. Redundant new ModeResolverV2 per call in loadMode.
src/adapters/mcp/server.ts Removed compatibilityMode toggle and replaced inline tool definitions with data-driven TOOL_CONTRACTS. Clean refactor, always enforces strict initialization now.
src/ai/fallback-policy.ts New policy abstraction for provider failover decisions. Clean interface with sensible default implementation. No issues.
src/ai/fallback.ts Refactored to use injected ProviderFallbackPolicy instead of inline retry logic. Policy hooks added for both doGenerate and doStream. Clean pattern.
src/services/readiness.ts New readiness service with clean interface. Evaluates adapter enablement, provider config, runtime status, and queue health. No issues.
src/services/setup-diagnostics.ts New diagnostics service checking db directory, provider config, vector support, adapters, and dashboard port. Well-structured with clear pass/warn/fail semantics.
src/doctor.ts New CLI entry point for setup diagnostics. Supports --project and --json flags. Clean implementation.
package.json Added doctor build target and new check scripts (contracts, migration, architecture, report). Removed legacy compat scripts. Properly wired into build pipeline.

Flowchart

flowchart TD
    A[SearchOrchestrator.search] --> B[normalizeOptions]
    B --> C{Strategy Registry Lookup}
    C -->|filter-only| D[executeFilterOnlyStrategy]
    C -->|semantic| E[executeSemanticStrategy]
    C -->|hybrid| F[executeHybridStrategy]
    D --> G[ObservationRepository.search / searchByConcept / searchByFile]
    E -->|no embedding model| D
    E -->|has embedding model| H[generateEmbedding + vector search]
    H -->|native vec extension| I[getVecEmbeddingMatches]
    H -->|JS fallback| J[getWithEmbeddings + cosineSimilarity]
    F --> K[hybridSearch: FTS5 + RRF merge]
    K --> L[safelyRunFts + runVectorSearch]
    G --> M[Label as project source]
    I --> M
    J --> M
    L --> M
    M --> N{entityRepo available?}
    N -->|yes| O[graphAugmentedSearch]
    N -->|no| P{userObservationRepo?}
    O --> P
    P -->|yes| Q[searchUserMemory + mergeResults]
    P -->|no| R{reranker?}
    Q --> R
    R -->|yes| S[reranker.rerank]
    R -->|no| T[Return results]
    S --> T
Loading

Last reviewed commit: 5df3b50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
package.json (1)

90-90: ⚠️ Potential issue | 🟠 Major

Remove self-dependency from package.json.

Line 90 lists "open-mem": "^0.9.0" as a dependency, but the package is named "open-mem" (line 2). This was already documented as removed in CHANGELOG.md but wasn't removed from package.json. The codebase does not import "open-mem" as a package; src/index.ts only uses path references to locate the plugin's own dist folder. Remove this entry from dependencies.

docs/architecture.md (1)

138-142: ⚠️ Potential issue | 🟡 Minor

Stale memory.* references remain in the External Surfaces section.

Lines 138 and 142 still reference memory.* tools/namespace while the rest of the document (and broader docs) now uses mem-*. These should be updated for consistency.

-- **Tools**: All 9 `memory.*` tools
+- **Tools**: All 9 `mem-*` tools

-- Same 9 tools over stdin/stdout JSON-RPC (`memory.*` namespace)
+- Same 9 tools over stdin/stdout JSON-RPC (`mem-*` namespace)
docs/api.md (1)

268-318: ⚠️ Potential issue | 🟡 Minor

Health Check response example is now visually orphaned from its endpoint.

The new endpoint sections (Readiness, Diagnostics, Tools Guide, Queue Status, Trigger Queue Processing) were inserted between the Health Check description (line 266) and its example Response: block (lines 308–318). As a result, the JSON response showing "status": "healthy" with database/queue/provider fields now appears to belong to the "Trigger Queue Processing" endpoint.

Move the Health Check response example to immediately after line 266 (before the new Readiness section), or add dedicated response examples for the new endpoints as well.

🤖 Fix all issues with AI agents
In @.github/workflows/nightly-matrix.yml:
- Around line 21-30: The "Run matrix checks" job is running identical structural
checks across a 6-job matrix and exporting unused env vars (OPEN_MEM_PROVIDER,
OPEN_MEM_ENTITY_EXTRACTION); collapse the matrix to a single job (remove
matrix.provider and matrix.vector_mode usage) and drop those unused environment
variables, then remove or adjust the redundant "bun run quality:gate" invocation
(which re-runs check:contracts and check:architecture) so the job only runs the
unique steps: bun run typecheck, bun run check:boundaries, bun run
check:contracts, bun run check:architecture (or have quality:gate run in a
separate job that excludes duplicate checks).

In `@docs/tools.md`:
- Around line 26-28: The list repeats the verb "Use" for mem-find, mem-history,
and mem-get which is stylistically repetitive; update one or two items to vary
sentence starts while keeping meaning and the command names intact (e.g., change
the first to "Run mem-find to locate candidate observations," the second to
"Inspect mem-history to understand timeline context," and keep the third as "Use
mem-get to pull complete details when needed"), ensuring each line still
references mem-find, mem-history, and mem-get exactly.

In `@scripts/check-architecture-fitness.ts`:
- Around line 19-22: The code calls walk(join(process.cwd(), "src/core")) /
"src/search" / "src/modes" unguarded so readdirSync inside walk will throw
ENOENT if a directory is missing; update the code that builds coreFiles,
searchFiles, and modeFiles to first check directory existence (e.g.,
fs.existsSync or fs.statSync(...).isDirectory()) or wrap walk(...) in a
try/catch and return an empty array when the dir is absent, then combine into
criticalFiles as before (references: walk, coreFiles, searchFiles, modeFiles,
criticalFiles).

In `@src/core/contracts.ts`:
- Around line 139-157: The file defines duplicate interfaces ReadinessService
and SetupDiagnosticsService that conflict with the service implementations;
resolve by consolidating to a single canonical contract that matches the
implementations used by DefaultReadinessService and
DefaultSetupDiagnosticsService: update or remove the ReadinessService in
src/core/contracts.ts so its signature matches the implementation (use
evaluate(input: ReadinessInput): ReadinessResult) or import and re-export the
service-side types (ReadinessInput, ReadinessResult) instead, and likewise
update SetupDiagnosticsService to run(config: OpenMemConfig):
SetupDiagnosticsResult or re-export the types from
src/services/setup-diagnostics.ts; ensure all imports across the codebase
reference the unified interface/name to avoid duplicate symbols.

In `@src/modes/resolver.ts`:
- Around line 114-116: getDefaultModeConfig currently returns the shared
DEFAULT_MODE reference (and resolveById returns DEFAULT_MODE on cycle/miss),
which allows callers to mutate the singleton; change getDefaultModeConfig to
return a copy (e.g., return { ...DEFAULT_MODE, observationTypes:
[...DEFAULT_MODE.observationTypes], promptTemplates:
[...(DEFAULT_MODE.promptTemplates || [])], /* clone any array properties */ } or
simply use structuredClone(DEFAULT_MODE)) and update resolveById to return the
same cloned copy instead of DEFAULT_MODE; alternatively, freeze DEFAULT_MODE
with Object.freeze(DEFAULT_MODE) if immutability is preferred.

In `@src/search/orchestrator.ts`:
- Around line 110-112: The code mutates objects returned by executor (results)
which may be shared/cached (notably from executeFilterOnlyStrategy in
filter-only.ts that returns deps.observations.search(...)); avoid in-place
mutation by returning new objects with a source field instead—either update each
strategy (e.g., executeFilterOnlyStrategy) to construct results that already
include source="project" or, at this call site where executor(options, { query,
limit }) is used, map the results to new objects (e.g., map each result to a
shallow copy with source set) before further use so you never modify the
original returned objects.

In `@src/search/strategies/hybrid.ts`:
- Around line 4-22: executeHybridStrategy currently drops singular options
(options.file, options.concept) because it only forwards plural fields to
hybridSearch; update this function to normalize singular→plural before calling
hybridSearch (if options.file is set, set files = [options.file] merged with
options.files; likewise for options.concept -> concepts = [options.concept]
merged with options.concepts) and then pass the normalized files/concepts to
hybridSearch (reference executeHybridStrategy, StrategyOptions, hybridSearch,
HybridSearchOptions); alternatively, if you prefer behavior consistent with
executeFilterOnlyStrategy, you can instead detect singular options and return
early with the same handling, but do not silently drop singular fields.

In `@src/search/strategies/semantic.ts`:
- Around line 46-48: The catch block in the vector-extension path currently
swallows errors and returns an empty array, causing no results when the vector
subsystem fails; change this to call and return the existing fallback function
executeFilterOnlyStrategy (the same fallback used when embedding generation
fails) so the function returns filter-only results instead of []. Locate the
try/catch around the vector extension call in semantic.ts and replace the silent
return [] in the catch with a call to executeFilterOnlyStrategy with the same
arguments/context so behavior is consistent when vectors are unavailable.

In `@src/services/readiness.ts`:
- Around line 20-22: There are two conflicting ReadinessService interface
definitions; remove the unused one in core/contracts.ts (the variant declaring
getStatus(): ReadinessStatus) and consolidate on the service interface used by
DefaultReadinessService: keep the ReadinessService that declares evaluate(input:
ReadinessInput): ReadinessResult (from services/readiness.ts), update any
imports that referenced the removed interface to import the services version,
and ensure DefaultReadinessService and any callers reference the single exported
ReadinessService type and ReadinessInput/ReadinessResult symbols.

In `@src/services/setup-diagnostics.ts`:
- Around line 17-19: There are two conflicting SetupDiagnosticsService
interfaces (one requires run(config: OpenMemConfig): SetupDiagnosticsResult
while the other declares run(): SetupDiagnosticsReport), so reconcile them by
making the contract match the actual implementation: change the contract's
SetupDiagnosticsService signature to run(config: OpenMemConfig):
SetupDiagnosticsResult and ensure the exported types reference
SetupDiagnosticsResult and OpenMemConfig (or alternatively change
DefaultSetupDiagnosticsService to implement the no-arg run returning
SetupDiagnosticsReport if you prefer that API); remove the duplicate interface
declaration so only one SetupDiagnosticsService exists, update any affected
imports/exports, and ensure DefaultSetupDiagnosticsService implements the
unified interface name exactly.
🧹 Nitpick comments (34)
src/ai/fallback-policy.ts (2)

7-13: Nit: unknown | null is redundant.

unknown already includes null in TypeScript, so unknown | null simplifies to unknown. Consider using just unknown for clarity, or if the intent is to signal that null means "no error" (as used in onAttempt), a more explicit union like Error | null or a dedicated field would convey that better.


30-36: Unsafe cast on error to extract status.

Line 31 casts input.error to Record<string, unknown> without a guard. If the error is a primitive (e.g., a thrown string) or null, the optional chaining prevents a crash, but the cast is misleading and would silently produce "unknown" for well-structured errors that store status differently.

Consider a small type guard or duck-type check:

Suggested approach
 onFailover(input: FallbackDecisionInput): void {
-	const status = (input.error as Record<string, unknown>)?.status ?? "unknown";
+	const err = input.error;
+	const status =
+		(err != null && typeof err === "object" && "status" in err ? (err as Record<string, unknown>).status : undefined) ?? "unknown";
 	if (!input.nextProvider) return;
src/ai/fallback.ts (2)

76-81: Hardcoded isConfigError check constrains custom policy behavior.

Both doGenerate (line 79) and doStream (line 125) short-circuit with throw on config errors before consulting the policy. This means a custom ProviderFallbackPolicy can never override the config-error behavior — shouldFailover is never called for these errors.

If this is intentional defense-in-depth (config errors should always halt), that's reasonable, but worth a brief inline comment explaining why the policy is bypassed here. Otherwise, if the goal is full policy pluggability, this check should be removed and left to the policy.

Also applies to: 122-127


62-102: doGenerate and doStream are near-identical — consider extracting the retry loop.

The two methods differ only in the inner call (doGenerate vs doStream on the provider model). The surrounding loop, policy hook calls, error handling, and decision construction are duplicated line-for-line. A shared private helper would eliminate ~40 lines of duplication.

Sketch
+private async executeWithFallback(
+  options: unknown,
+  method: "doGenerate" | "doStream",
+): Promise<unknown> {
+  let lastError: unknown;
+  for (let i = 0; i < this.providers.length; i++) {
+    const provider = this.providers[i];
+    try {
+      this.policy.onAttempt?.({
+        error: null,
+        provider: provider.name,
+        nextProvider: this.providers[i + 1]?.name,
+        attemptIndex: i,
+        totalProviders: this.providers.length,
+      });
+      return await (provider.model[method] as (opts: unknown) => Promise<unknown>)(options);
+    } catch (error: unknown) {
+      lastError = error;
+      if (isConfigError(error)) throw error;
+      const decision = {
+        error,
+        provider: provider.name,
+        nextProvider: this.providers[i + 1]?.name,
+        attemptIndex: i,
+        totalProviders: this.providers.length,
+      };
+      if (this.policy.shouldFailover(decision)) {
+        this.policy.onFailover(decision);
+        continue;
+      }
+      this.policy.onFinalFailure?.(decision);
+      throw error;
+    }
+  }
+  throw lastError;
+}
+
 async doGenerate(options: unknown): Promise<unknown> {
-  // ... duplicated loop ...
+  return this.executeWithFallback(options, "doGenerate");
 }
+
 async doStream(options: unknown): Promise<unknown> {
-  // ... duplicated loop ...
+  return this.executeWithFallback(options, "doStream");
 }

Also applies to: 108-148

docs/engineering/deprecation-policy.md (1)

1-14: Governance doc is a good addition but quite sparse.

Consider adding concrete examples for each lifecycle stage (e.g., what a deprecation warning looks like in code or logs) and clarifying what "explicit" means in rule 3 — does it require a changelog entry, a migration guide, or both? Linking to the ADR template for structural deprecation decisions would also tie this into the broader governance framework.

.github/CODEOWNERS (1)

1-15: Single-owner CODEOWNERS creates a bus-factor-of-1 bottleneck.

Every PR touching these paths will be blocked if @clopca is unavailable. Consider adding a team (e.g., @org/core-team) as a secondary owner, or at minimum a catch-all rule (* @clopca``) so files outside the listed paths also get review coverage.

.github/workflows/release-gate.yml (2)

20-25: Early failure in multi-command step hides downstream check results.

The | block runs with set -e by default, so if check:boundaries fails, check:contracts, check:architecture, and check:migration are skipped. If you want all checks to report independently, either split them into separate steps or use set +e with an explicit exit code accumulator.

Split into separate steps
       - name: Architecture and Contract Checks
         run: |
           bun run check:boundaries
           bun run check:contracts
           bun run check:architecture
           bun run check:migration
+      # Alternative: split into individual steps so all checks run independently
+      # - name: Boundary Check
+      #   run: bun run check:boundaries
+      # - name: Contract Check
+      #   run: bun run check:contracts
+      # - name: Architecture Check
+      #   run: bun run check:architecture
+      # - name: Migration Check
+      #   run: bun run check:migration

13-13: Consider pinning the Bun version and enabling dependency caching.

oven-sh/setup-bun@v2 without a bun-version input will install the latest, which may introduce non-determinism in your release gate. Also, this action supports caching via cache: true or a separate actions/cache step, which would speed up the gate.

Pin version and enable cache
       - uses: oven-sh/setup-bun@v2
+        with:
+          bun-version: "1.2.x"
+          cache: true
scripts/check-migration-compat.ts (2)

12-17: Exits on first missing file — consider reporting all missing files before exiting.

Currently the loop exits on the first missing file, so if multiple files are missing, only the first is reported. Accumulate all errors and exit once at the end.

Report all missing files
+let missing = false;
 for (const file of required) {
 	if (!existsSync(file)) {
 		console.error(`[migration-compat] required file missing: ${file}`);
-		process.exit(1);
+		missing = true;
 	}
 }
+if (missing) {
+	process.exit(1);
+}

6-19: Script name overpromises — this only checks file existence, not migration compatibility.

A "migration compatibility check" typically validates schema changes are backward-compatible, checks for destructive operations, or verifies migration ordering. This script only verifies that three files exist. Consider either renaming it to check-migration-files-exist.ts or adding meaningful validation (e.g., ensuring the schema file exports expected symbols, checking for breaking DDL changes).

scripts/check-contract-drift.ts (1)

27-32: Consider reporting all description failures before exiting.

Currently the script exits on the first tool with a short/missing description, hiding any subsequent failures. Collecting all errors and reporting them together would speed up fixing.

♻️ Suggested change
+let descErrors = 0;
 for (const tool of TOOL_CONTRACTS) {
 	if (!tool.description || tool.description.trim().length < 10) {
 		console.error(`[contract-drift] Missing or too-short description for ${tool.name}`);
-		process.exit(1);
+		descErrors++;
 	}
 }
+if (descErrors > 0) process.exit(1);
scripts/check-architecture-fitness.ts (1)

34-37: Adapter-import detection may produce false positives on comments or string literals.

content.includes("/adapters/") matches anywhere in the file — comments, string literals, or documentation — not just import statements. Since this is a CI gate that blocks merges, consider narrowing to actual import/require statements.

♻️ Suggested: match only import/require lines
-	if (content.includes("../adapters/") || content.includes("/adapters/")) {
-		console.error(`[architecture-fitness] adapter import leaked into core/search/modes: ${file}`);
-		process.exit(1);
-	}
+	const lines = content.split("\n");
+	for (const line of lines) {
+		if (/^\s*(import|export)\s/.test(line) || /require\s*\(/.test(line)) {
+			if (line.includes("/adapters/")) {
+				console.error(`[architecture-fitness] adapter import leaked into core/search/modes: ${file}`);
+				process.exit(1);
+			}
+		}
+	}
.github/workflows/architecture-health.yml (1)

8-22: Consider adding explicit permissions for least-privilege.

The workflow only needs to read the repo and upload an artifact. Without an explicit permissions block, it inherits the repository's default token permissions, which may be broader than necessary.

♻️ Suggested addition
 jobs:
   report:
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
     steps:
src/adapters/opencode/tools.ts (1)

27-29: Unsafe lookup: descriptions[key] silently returns undefined for missing contracts.

Object.fromEntries produces Record<string, string>, so descriptions["mem-find"] type-checks even if the key is absent at runtime. If a contract entry is ever renamed or removed from TOOL_CONTRACTS, the corresponding tool silently gets description: undefined.

Consider using the existing getToolContractByName helper (from schemas.ts) with a runtime assertion, or at minimum add a guard:

♻️ Suggested safer lookup
-	const descriptions = Object.fromEntries(
-		TOOL_CONTRACTS.map((tool) => [tool.name, tool.description]),
-	);
+	const desc = (name: string): string => {
+		const contract = TOOL_CONTRACTS.find((t) => t.name === name);
+		if (!contract) throw new Error(`Missing tool contract: ${name}`);
+		return contract.description;
+	};

Then replace descriptions["mem-find"] with desc("mem-find"), etc.

docs/architecture/adr/ADR-0001-architecture-principles.md (1)

1-24: ADR is missing the "Alternatives Considered" section from the template.

The ADR template (ADR-TEMPLATE.md) includes an "Alternatives Considered" section. Adding it here (even if brief) would improve consistency and document why other approaches were rejected.

src/services/readiness.ts (1)

48-56: "initializing" status is misleading for configuration errors.

When adapters are missing or an API key isn't configured, the status is set to "initializing", which implies a transient startup state. These are persistent configuration problems that won't self-resolve. Consider adding a "misconfigured" status or mapping config-related failures differently from runtime issues.

src/contracts/schemas.ts (1)

96-156: Consider deriving tool names as a const union for type safety.

The name field in ToolContractMetadata is typed as string, which means typos in contract names (or in consumer lookups like descriptions["mem-find"]) won't be caught at compile time. A narrow type would improve safety:

♻️ Optional: derive tool name type
export const TOOL_NAMES = [
  "mem-find", "mem-history", "mem-get", "mem-create",
  "mem-revise", "mem-remove", "mem-export", "mem-import",
  "mem-maintenance", "mem-help",
] as const;

export type ToolName = (typeof TOOL_NAMES)[number];

export interface ToolContractMetadata {
  name: ToolName;
  // ...rest
}
package.json (1)

30-38: Build scripts have heavy duplication of --external flags.

Every build:* script repeats the same 8 --external flags verbatim. This is fragile — adding a new external dependency requires updating 7+ lines.

Consider extracting these into a shared build script or a variable. Not blocking, but worth a follow-up.

src/adapters/mcp/server.ts (1)

201-218: schemaMap duplicates toolSchemas — consider using it directly.

The schemaMap object manually mirrors every key from toolSchemas. You could index toolSchemas directly with a type assertion on tool.schema, eliminating the duplicated mapping and reducing the risk of the two falling out of sync when a new tool is added.

♻️ Proposed simplification
 private getToolDefinitions(): McpToolDefinition[] {
-    const schemaMap = {
-      find: toolSchemas.find,
-      history: toolSchemas.history,
-      get: toolSchemas.get,
-      create: toolSchemas.create,
-      revise: toolSchemas.revise,
-      remove: toolSchemas.remove,
-      transferExport: toolSchemas.transferExport,
-      transferImport: toolSchemas.transferImport,
-      maintenance: toolSchemas.maintenance,
-      help: toolSchemas.help,
-    } as const;
-
-    return TOOL_CONTRACTS.map((tool) => ({
-      name: tool.name,
-      description: tool.description,
-      inputSchema: toInputSchema(schemaMap[tool.schema]),
-    }));
+    return TOOL_CONTRACTS.map((tool) => ({
+      name: tool.name,
+      description: tool.description,
+      inputSchema: toInputSchema(toolSchemas[tool.schema as keyof typeof toolSchemas]),
+    }));
 }

If ToolContractMetadata.schema were typed as keyof typeof toolSchemas instead of string, the assertion could be dropped entirely and the compiler would catch any mismatch at the contract definition site.

src/services/setup-diagnostics.ts (1)

97-117: Asymmetric default-on logic for opencode adapter.

platformOpenCodeEnabled uses !== false (default-on), while claude-code and cursor use === true (default-off). This is likely intentional since those config fields are optional booleans, but worth a brief inline comment for future readers.

.github/workflows/ci.yml (1)

22-29: check:contracts and check:architecture run twice per CI run.

Lines 23 and 25 execute these checks as standalone steps, and line 29 (quality:gate) runs them again internally (see scripts/quality-gate.ts lines 6-7). This doubles the execution time for those checks without added value.

Either remove the standalone steps here and rely on quality:gate, or strip the duplicates from the quality-gate script to keep the CI as the single orchestrator.

docs/engineering/quality-gates.md (1)

5-11: Inconsistent command format between gate entries.

Items 1–4 use the full bun run … invocation, while items 5–7 switch to a descriptive label with the script name in parentheses. Keeping a uniform format (e.g., all as bun run check:contracts) makes it easier to copy-paste commands and reduces ambiguity.

📝 Suggested consistency fix
-5. Contract checks (`check-contract-drift`)
-6. Architecture fitness checks (`check-architecture-fitness`)
-7. Migration compatibility checks (`check-migration-compat`) for storage/contract changes
+5. `bun run check:contracts`
+6. `bun run check:architecture`
+7. `bun run check:migration` (for storage/contract changes)
src/doctor.ts (1)

8-15: strict: false silently ignores unknown flags.

Typos like --projecT or --josn will be silently swallowed, falling back to defaults without any user feedback. Consider using strict: true and letting parseArgs surface unrecognised flags early, or at minimum log a warning for unexpected arguments.

docs/search.md (1)

100-103: Fenced code blocks missing language specifier (flagged by markdownlint).

The code blocks at lines 100 and 109 lack a language tag. Adding a language identifier (e.g., text or plain) would satisfy the MD040 lint rule.

Also applies to: 109-112

docs/openapi.v1.json (1)

27-31: New endpoint entries are consistent with docs and tests.

The five new paths align with docs/api.md documentation and tests/servers/http-server.test.ts coverage. The spec remains summary-only (no request/response schemas), which is a pre-existing pattern — consider fleshing out schemas as part of a future documentation pass to make the 1.0.0 contract more consumable by API clients and code generators.

src/search/strategies/types.ts (1)

25-30: Note: StrategyExecutor is a distinct type from SearchStrategyExecutor in the registry.

This file's StrategyExecutor takes (deps, query, options, limit) while SearchStrategyExecutor<TOptions> in src/search/registry.ts takes (options, context). These serve different purposes (direct execution vs. registry-based dispatch), but the similar naming could confuse contributors. A brief doc comment clarifying the distinction would help.

src/modes/loader.ts (2)

17-19: Redundant ModeResolverV2 instantiation — loadMode creates a second resolver needlessly.

loadAllModes() at line 12 already creates a ModeResolverV2(MODES_DIR). Since loadMode calls loadAllModes() on line 19, a second resolver is created here for no additional benefit. Consider extracting a shared resolver instance or reusing the one from loadAllModes.

♻️ Proposed fix — share a single resolver
 const MODES_DIR = join(import.meta.dir, ".");
 
 let modeCache: Map<string, ModeConfig> | null = null;
+const resolver = new ModeResolverV2(MODES_DIR);
 
 function loadAllModes(): Map<string, ModeConfig> {
 	if (modeCache) return modeCache;
-
-	const resolver = new ModeResolverV2(MODES_DIR);
 	modeCache = resolver.loadAllRaw();
 	return modeCache;
 }
 
 export function loadMode(modeId: string): ModeConfig {
-	const resolver = new ModeResolverV2(MODES_DIR);
 	return resolver.resolveById(modeId, loadAllModes());
 }

27-29: ?? getDefaultModeConfig() fallback is unreachable.

resolveById (per src/modes/resolver.ts line 92–110) always returns a ModeConfig — it falls back to DEFAULT_MODE internally on missing IDs or cycle detection, never returning null/undefined. The ?? guard won't trigger. This is harmless as a defensive measure, but worth a comment if intentional.

tests/modes/resolver-v2.test.ts (1)

60-93: Cycle-fallback assertion relies on a magic string "code" for DEFAULT_MODE.id.

If DEFAULT_MODE in resolver.ts is ever renamed, this test silently breaks. Consider importing getDefaultModeConfig and asserting against its .id to keep the test resilient.

Proposed improvement
 import { ModeResolverV2 } from "../../src/modes/resolver";
+import { getDefaultModeConfig } from "../../src/modes/resolver";
 
 ...
 
 		const resolved = resolver.resolveById("a", raw);
-		expect(resolved.id).toBe("code");
+		expect(resolved.id).toBe(getDefaultModeConfig().id);
src/search/strategies/semantic.ts (1)

51-52: Embedding fallback fetches up to limit * 10 rows with full embeddings into memory.

For large observation stores this could be expensive. The vector-extension path only fetches limit * 3 candidates. Consider documenting the tradeoff or capping the multiplier.

src/adapters/http/server.ts (2)

289-307: Runtime fallback object is duplicated four times across endpoints.

The same RuntimeStatusSnapshot fallback literal appears in /v1/health (line 256), /v1/readiness (line 291), /v1/metrics (line 382), and /v1/queue (line 346). Extract it into a helper to reduce drift risk.

Proposed helper extraction
+function getRuntime(
+	runtimeStatusProvider: (() => RuntimeStatusSnapshot) | undefined,
+	health: { status: string; timestamp: string },
+): RuntimeStatusSnapshot {
+	return (
+		runtimeStatusProvider?.() ?? {
+			status: health.status,
+			timestamp: health.timestamp,
+			uptimeMs: process.uptime() * 1000,
+			queue: {
+				mode: "in-process",
+				running: false,
+				processing: false,
+				pending: 0,
+				lastBatchDurationMs: 0,
+				lastProcessedAt: null,
+				lastFailedAt: null,
+				lastError: null,
+			},
+			batches: { total: 0, processedItems: 0, failedItems: 0, avgDurationMs: 0 },
+			enqueueCount: 0,
+		}
+	);
+}

Then replace each occurrence with:

const runtime = getRuntime(runtimeStatusProvider, memoryEngine.getHealth());

309-309: DefaultReadinessService is instantiated on every request.

While stateless, constructing it per-request is unnecessary. Consider creating a single instance at createDashboardApp scope. Same applies to DefaultSetupDiagnosticsService on line 325.

src/search/orchestrator.ts (1)

244-249: computeLineageInfo only resolves one level of revision depth.

If revisions can chain (A → B → C), this will always report depth: 1 and rootId as the immediate parent rather than the true root. If deeper lineage tracking is planned, this will need recursive traversal.

docs/tools.md (1)

175-177: Clarify action type with explicit union for consistency.
Other parameters use literal unions; consider listing action as a union (e.g., `folderContextDryRun | folderContextClean | ...`) instead of “enum” to match the rest of the table.

Comment thread .github/workflows/nightly-matrix.yml Outdated
Comment thread docs/tools.md Outdated
Comment thread scripts/check-architecture-fitness.ts Outdated
Comment thread src/core/contracts.ts Outdated
Comment thread src/modes/resolver.ts
Comment thread src/search/orchestrator.ts Outdated
Comment thread src/search/strategies/hybrid.ts
Comment thread src/search/strategies/semantic.ts
Comment thread src/services/readiness.ts
Comment thread src/services/setup-diagnostics.ts
Comment thread src/adapters/http/server.ts
Comment thread src/core/contracts.ts Outdated
Comment thread src/contracts/schemas.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (10)
src/modes/resolver.ts (3)

51-65: Optional fields (extends, locale, promptOverrides) are not validated.

A JSON file with "extends": 123 or "promptOverrides": "oops" would pass isValidMode and could cause subtle misbehaviour downstream (e.g. rawModes.get(123) silently misses, { ...base.promptOverrides, ...override.promptOverrides } spreads a string's characters as keys).

Consider adding lightweight optional-field checks:

Proposed tighter validation
 	return (
 		typeof v.id === "string" &&
 		typeof v.name === "string" &&
 		typeof v.description === "string" &&
 		isStringArray(v.observationTypes) &&
 		isStringArray(v.conceptVocabulary) &&
 		isStringArray(v.entityTypes) &&
-		isStringArray(v.relationshipTypes)
+		isStringArray(v.relationshipTypes) &&
+		(v.extends === undefined || typeof v.extends === "string") &&
+		(v.locale === undefined || typeof v.locale === "string") &&
+		(v.promptOverrides === undefined ||
+			(typeof v.promptOverrides === "object" &&
+				v.promptOverrides !== null &&
+				!Array.isArray(v.promptOverrides)))
 	);

67-80: Resolved modes retain the child's extends field.

After mergeMode(parent, override), the result still carries override.extends (e.g. "code"). Downstream code or serialisation that inspects extends could mistakenly believe the mode still needs resolution. Consider stripping it:

Strip `extends` after merge
 function mergeMode(base: ModeConfig, override: ModeConfig): ModeConfig {
-	return {
+	const merged: ModeConfig = {
 		...base,
 		...override,
 		observationTypes: override.observationTypes ?? base.observationTypes,
 		conceptVocabulary: override.conceptVocabulary ?? base.conceptVocabulary,
 		entityTypes: override.entityTypes ?? base.entityTypes,
 		relationshipTypes: override.relationshipTypes ?? base.relationshipTypes,
 		promptOverrides: {
 			...(base.promptOverrides ?? {}),
 			...(override.promptOverrides ?? {}),
 		},
 	};
+	delete merged.extends;
+	return merged;
 }

85-102: Duplicate id values across different JSON files are silently overwritten.

If two files declare the same id, the last one read wins, and readdirSync order is filesystem-dependent. A warning log (or collecting duplicates and throwing) would make misconfiguration easier to diagnose.

scripts/check-architecture-fitness.ts (1)

37-51: Early exit reports only the first violation — consider collecting all errors.

The script calls process.exit(1) on the first TODO/FIXME or adapter-leak hit, so a CI run surfaces only one problem at a time. Collecting all violations and reporting them together would save developers multiple fix-and-rerun cycles.

♻️ Suggested approach
+const errors: string[] = [];
 for (const file of criticalFiles) {
 	const content = readFileSync(file, "utf-8");
 	for (const regex of forbidden) {
 		if (regex.test(content)) {
-			console.error(`[architecture-fitness] anonymous TODO/FIXME found in ${file}`);
-			process.exit(1);
+			errors.push(`[architecture-fitness] anonymous TODO/FIXME found in ${file}`);
 		}
 	}
 
 	if (hasAdapterImportLeak(content)) {
-		console.error(`[architecture-fitness] adapter import leaked into core/search/modes: ${file}`);
-		process.exit(1);
+		errors.push(`[architecture-fitness] adapter import leaked into core/search/modes: ${file}`);
 	}
 }
+
+if (errors.length > 0) {
+	for (const e of errors) console.error(e);
+	process.exit(1);
+}
src/adapters/http/server.ts (2)

307-340: Duplicated RuntimeStatusSnapshot fallback object across multiple endpoints.

The same fallback object for runtimeStatusProvider?.() ?? { ... } is copy-pasted in /v1/health (line 274), /v1/readiness (line 309), /v1/queue (line 364), and /v1/metrics (line 400). Extract a helper to reduce duplication and risk of drift.

♻️ Suggested helper
+function getRuntimeSnapshot(
+	health: ReturnType<MemoryEngine["getHealth"]>,
+	runtimeStatusProvider?: () => RuntimeStatusSnapshot,
+): RuntimeStatusSnapshot {
+	return runtimeStatusProvider?.() ?? {
+		status: health.status,
+		timestamp: health.timestamp,
+		uptimeMs: process.uptime() * 1000,
+		queue: {
+			mode: "in-process",
+			running: false,
+			processing: false,
+			pending: 0,
+			lastBatchDurationMs: 0,
+			lastProcessedAt: null,
+			lastFailedAt: null,
+			lastError: null,
+		},
+		batches: { total: 0, processedItems: 0, failedItems: 0, avgDurationMs: 0 },
+		enqueueCount: 0,
+	};
+}

Then replace the duplicated blocks with:

const runtime = getRuntimeSnapshot(memoryEngine.getHealth(), runtimeStatusProvider);

Also applies to: 361-395


327-328: Readiness and diagnostics services instantiated per request.

new DefaultReadinessService() and new DefaultSetupDiagnosticsService() are created on every request. If these are stateless, consider creating them once in createDashboardApp and reusing them.

Also applies to: 343-343

src/search/strategies/semantic.ts (1)

51-52: Empty-candidate paths return [] without fallback — verify this is intentional.

When getWithEmbeddings returns no candidates (line 52) or getVecEmbeddingMatches returns none (line 25), the function returns [] rather than falling back to filter-only search. This differs from the error-handling paths which do fall back. If the intent is "no embeddings stored yet → no semantic results," this is fine; if users should still get FTS results, consider a fallback here too.

src/adapters/mcp/server.ts (1)

374-377: Fragile error detection via string matching on serialized JSON.

Line 376 uses payload.includes('"error": {') to determine isError. This is brittle — it depends on JSON.stringify formatting (e.g., the 2-space indent) and could false-positive on user content containing that substring. Since text() already returns results from ok() and fail() helpers, consider returning a structured { payload, isError } tuple from the inner function instead of post-hoc string inspection.

src/search/orchestrator.ts (2)

137-150: normalizeOptions preserves singular fields via spread — verify this is intentional.

The spread ...options on line 146 means the returned object still carries concept and file alongside the merged concepts and files. This works correctly because executeFilterOnlyStrategy checks options.concept for its early-return path (filter-only.ts line 7). However, this creates a subtle contract: strategies may see both singular and plural forms simultaneously.

If the intent is for strategies to only consume plural forms, consider also unsetting the singular fields. If the current behavior is deliberate (filter-only needs them), a brief comment would help future readers.


152-170: Silent catch in searchUserMemory swallows all errors.

Line 167's bare catch block returns [] for any failure. While this prevents user-memory errors from breaking the main search flow, it also hides potentially actionable errors (e.g., DB corruption, schema mismatch). Consider logging at warn level so issues are observable.

Comment thread src/search/strategies/hybrid.ts Outdated
Comment thread src/modes/resolver.ts
@clopca

clopca commented Feb 16, 2026

Copy link
Copy Markdown
Owner Author

@cursor review

@clopca

clopca commented Feb 16, 2026

Copy link
Copy Markdown
Owner Author

@greptile review

Comment thread src/search/strategies/types.ts Outdated
Comment thread src/modes/loader.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

78 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment thread src/adapters/http/server.ts
Comment thread src/search/strategies/filter-only.ts Outdated
Comment thread src/modes/loader.ts
Comment thread src/adapters/http/server.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (6)
src/search/strategies/filter-only.ts (2)

20-47: Concept filters shadow file filters when both are provided.

If a caller passes both concept/concepts and file/files, only the concept branch executes and the file filters are silently ignored due to the early return on line 46. This is fine if intentional (and matches the prioritized-filtering design), but consider documenting this precedence or logging a warning so callers aren't surprised.


30-46: Each concept term queries with the full limit, potentially over-fetching.

searchByConcept is called per concept with limit, so with N concepts the DB performs N queries returning up to N × limit rows before dedup. Same pattern on the file branch (line 54). This is acceptable for small N but worth keeping in mind if the concepts/files arrays grow large.

src/modes/resolver.ts (2)

99-115: Redundant spread before explicit field assignments in mergeMode.

...base and ...override on lines 101–102 set all fields, but lines 103–109 immediately overwrite every documented field with explicit ?? fallback logic. The only fields actually contributed by the spreads that survive are extends and locale. This isn't a bug, but it's easy to misread.

♻️ Optional: make the intent explicit
 function mergeMode(base: ModeConfig, override: ModeConfigSource): ModeConfig {
 	return {
-		...base,
-		...override,
 		id: override.id,
+		extends: override.extends ?? base.extends,
+		locale: override.locale ?? base.locale,
 		name: override.name ?? base.name,
 		description: override.description ?? base.description,
 		observationTypes: override.observationTypes ?? base.observationTypes,
 		conceptVocabulary: override.conceptVocabulary ?? base.conceptVocabulary,
 		entityTypes: override.entityTypes ?? base.entityTypes,
 		relationshipTypes: override.relationshipTypes ?? base.relationshipTypes,
 		promptOverrides: {
 			...(base.promptOverrides ?? {}),
 			...(override.promptOverrides ?? {}),
 		},
 	};
 }

120-137: Duplicate id across files is silently last-writer-wins.

If two JSON files in the modes directory declare the same id, modes.set(parsed.id, parsed) overwrites without warning. The order depends on readdirSync, which is filesystem-dependent. This could cause subtle, hard-to-debug behavior differences across environments.

Consider logging a warning when a duplicate id is encountered.

src/modes/loader.ts (1)

17-18: loadMode re-resolves inheritance on every call.

Each call to loadMode runs resolveById (including the recursive inheritance walk) against the cached raw sources. This is fine for typical config-loading frequency, but worth noting if this ever gets called in a hot path. A resolved-mode cache keyed by modeId could be added if needed later.

src/adapters/http/server.ts (1)

310-327: Consider reusing service instances instead of allocating per request.

new DefaultReadinessService() and new DefaultSetupDiagnosticsService() (line 330) are instantiated on every request. If these are stateless, hoist them to module or factory scope to avoid unnecessary allocations on the hot path.

♻️ Proposed refactor
 export function createDashboardApp(deps: DashboardDeps): Hono {
+	const readinessService = new DefaultReadinessService();
+	const diagnosticsService = new DefaultSetupDiagnosticsService();
 	const {
 		projectPath,
 		memoryEngine,
 		...

Then use readinessService.evaluate(...) and diagnosticsService.run(...) in the handlers.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Comment thread src/search/strategies/filter-only.ts
Comment thread src/modes/resolver.ts
Comment thread scripts/architecture-health-report.ts
@clopca clopca merged commit e204745 into main Feb 16, 2026
3 checks passed
@clopca clopca deleted the codex/quality-first-architecture-refactor branch February 16, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant