wendkeep 0.85.0 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.en.md +2 -1
  3. package/README.md +2 -1
  4. package/docs/en/commands/evidence-embeddings.md +243 -0
  5. package/docs/en/commands/mcp.md +67 -7
  6. package/docs/pt-BR/commands/evidence-embeddings.md +244 -0
  7. package/docs/pt-BR/commands/mcp.md +66 -7
  8. package/hooks/evidence-context.mjs +41 -7
  9. package/hooks/evidence-recall.mjs +10 -0
  10. package/package.json +1 -1
  11. package/packages/mcp/src/effects.mjs +3 -2
  12. package/packages/mcp/src/evidence-recall.mjs +130 -0
  13. package/packages/mcp/src/executor.mjs +4 -0
  14. package/packages/mcp/src/server.mjs +31 -1
  15. package/packages/vault/src/evidence-embedding-plugin.mjs +531 -0
  16. package/packages/vault/src/evidence-index-store.mjs +360 -0
  17. package/packages/vault/src/evidence-recall-page.mjs +381 -0
  18. package/packages/vault/src/evidence-search-index.mjs +917 -0
  19. package/packages/vault/src/index.mjs +12 -1
  20. package/packages/vault/src/memory-ledger-view-base.mjs +545 -0
  21. package/packages/vault/src/memory-ledger-view.mjs +41 -0
  22. package/packages/vault/src/memory-rotation-store.mjs +967 -0
  23. package/packages/vault/src/memory-segment-store.mjs +820 -0
  24. package/packages/vault/src/memory-snapshot-store.mjs +1105 -0
  25. package/packages/vault/src/memory-store-base.mjs +1161 -0
  26. package/packages/vault/src/memory-store-core.mjs +2 -0
  27. package/packages/vault/src/memory-store.mjs +46 -1161
  28. package/src/doctor.mjs +41 -5
  29. package/src/evidence-search-health.mjs +221 -0
  30. package/src/memory-scale-health.mjs +210 -0
  31. package/src/observer-snapshot.mjs +87 -1
@@ -63,20 +63,71 @@ O `init` gera a entrada genérica reproduzível:
63
63
  ```
64
64
 
65
65
  Reads: `wendkeep_project_status`, `wendkeep_context_status`, `wendkeep_memory_recall`,
66
- `wendkeep_memory_conflicts`, `wendkeep_change_list`, `wendkeep_change_show`,
67
- `wendkeep_change_status`, `wendkeep_spec_effective`, `wendkeep_task_show`,
68
- `wendkeep_task_evaluate`, `wendkeep_handoff_current`, `wendkeep_evidence_latest` e
69
- `wendkeep_observer_query`.
66
+ `wendkeep_evidence_recall`, `wendkeep_memory_conflicts`, `wendkeep_change_list`,
67
+ `wendkeep_change_show`, `wendkeep_change_status`, `wendkeep_spec_effective`,
68
+ `wendkeep_task_show`, `wendkeep_task_evaluate`, `wendkeep_handoff_current`,
69
+ `wendkeep_evidence_latest` e `wendkeep_observer_query`.
70
70
 
71
71
  Writes: `wendkeep_memory_assert`, `wendkeep_checkpoint_create`, `wendkeep_context_select`,
72
72
  `wendkeep_task_claim`, `wendkeep_task_complete` e `wendkeep_handoff_publish`.
73
73
 
74
+ ## Recall paginado e indexado de evidências
75
+
76
+ `wendkeep_evidence_recall` é a superfície bounded para recuperar evidências do Vault. Ela seleciona
77
+ candidatos pelo sidecar lexical persistente ou pelo SQLite/FTS5 opcional, reranqueia com o scorer
78
+ canônico e devolve uma página compacta. `wendkeep_memory_recall` permanece disponível como API
79
+ legada e não ganha silenciosamente o novo contrato.
80
+
81
+ Entrada principal:
82
+
83
+ - `project_root` e `query` são obrigatórios;
84
+ - `limit` aceita 1 a 100 resultados por página;
85
+ - `cursor` é opaco e só vale para a mesma consulta, filtros e índice lógico;
86
+ - `max_bytes` aceita 2 a 524288 e limita exatamente o JSON serializado de `results`; o padrão é
87
+ 64 KiB;
88
+ - `candidate_limit` aceita 1 a 4096 candidatos;
89
+ - `posting_budget` aceita 1 a 1048576 postings visitados;
90
+ - `backend` aceita `auto`, `sqlite` ou `lexical`;
91
+ - `filters` aceita igualdade por `authority`, `validity`, `entity_type`, `project_id`,
92
+ `change_slug`, `session_id`, `work_session_id` e `logical_path`, além de
93
+ `logical_path_prefix`. Cada filtro pode ser string ou lista de strings.
94
+
95
+ Exemplo de chamada:
96
+
97
+ ```json
98
+ {
99
+ "name": "wendkeep_evidence_recall",
100
+ "arguments": {
101
+ "project_root": "<projeto>",
102
+ "query": "contrato de autenticação",
103
+ "limit": 5,
104
+ "max_bytes": 65536,
105
+ "candidate_limit": 512,
106
+ "posting_budget": 65536,
107
+ "backend": "auto",
108
+ "filters": {
109
+ "authority": "verified",
110
+ "validity": "active",
111
+ "logical_path_prefix": "04-Decisões/"
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ A resposta contém `results`, `next_cursor`, `has_more`, `as_of`, contagens e bytes da página. Cada
118
+ resultado omite `content`, informa `content_bytes`, mantém um `excerpt` bounded e substitui
119
+ `logical_path` por `logical_ref`, uma referência relativa ao Vault — nunca um caminho absoluto. O
120
+ bloco `candidates` expõe backend, quantidade, postings, rebuild e fallback. Quando o orçamento de
121
+ candidatos não cobriu todo o conjunto possível, `complete_candidate_set` é `false`; isso impede que
122
+ o consumidor interprete uma seleção truncada como exaustiva.
123
+
74
124
  ## Resultado esperado
75
125
 
76
126
  O handshake e `tools/list` retornam JSON-RPC válido. Cada tool declara effect/capability e schemas
77
127
  versionados. Reads conhecidas não entram no mutation gate, mas mantêm binding explícito de
78
- projeto/worktree, paginação por cursor, budget padrão de 1 MiB, redaction, timeout e cancelamento.
79
- Observer aparece indisponível abaixo de Node 22.13 sem impedir Core no Node 18.
128
+ projeto/worktree, paginação por cursor, budgets, redaction, timeout e cancelamento. Observer aparece
129
+ indisponível abaixo de Node 22.13 sem impedir Core no Node 18. O recall indexado também funciona no
130
+ Node 18 pelo fallback lexical; SQLite/FTS5 permanece opcional.
80
131
 
81
132
  Writes exigem `project_root`, `session_id`, `active_context_id`, `actor`, `reason`, capability exata
82
133
  e `lease.id`/`lease.expires_at`; o executor revalida a autorização causal e os gates da CLI. A
@@ -89,8 +140,16 @@ código e duração — nunca argumentos ou payload.
89
140
  - `MCP_CAPABILITY_REQUIRED` / `MCP_SCOPE_AUTH_REQUIRED`: capability ausente ou não autorizada.
90
141
  - `MCP_LEASE_EXPIRED`: obtenha autorização/lease nova; não altere timestamp manualmente.
91
142
  - `MCP_PROJECT_SCOPE_MISMATCH`: `project_root` e `worktree_root` pertencem a bindings diferentes.
92
- - `MCP_REQUEST_TOO_LARGE` / `MCP_RESPONSE_TOO_LARGE`: use `limit` e o cursor retornado.
143
+ - `MCP_REQUEST_TOO_LARGE` / `MCP_RESPONSE_TOO_LARGE`: reduza os budgets e continue pelo cursor.
93
144
  - `MCP_RUNTIME_UNSUPPORTED`: use Node 22.13+ para Observer; Core permanece disponível.
145
+ - `MCP_EVIDENCE_QUERY_REQUIRED`: informe uma consulta não vazia.
146
+ - `MCP_EVIDENCE_CURSOR_INVALID`: cursor adulterado, stale ou usado com outra consulta/filtros.
147
+ - `MCP_EVIDENCE_BUDGET_TOO_SMALL`: nem os metadados mínimos do próximo resultado cabem em
148
+ `max_bytes`.
149
+ - `MCP_EVIDENCE_BACKEND_UNAVAILABLE`: o backend SQLite foi exigido, mas FTS5 não está disponível;
150
+ use `auto` ou `lexical`.
151
+ - `MCP_EVIDENCE_ARTIFACT_UNSAFE`: um artefato derivado violou a fronteira física do Vault.
152
+ - `MCP_EVIDENCE_RECALL_INVALID`: filtro, backend ou limite fora do contrato.
94
153
 
95
154
  ## Próximos passos
96
155
 
@@ -2,7 +2,13 @@
2
2
  // UserPromptSubmit: bounded, read-only retrieval from the local evidence index.
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import { readHookInput, readSessionRegistry, writeHookOutput } from './obsidian-common.mjs';
5
- import { loadEvidenceIndex, recallEvidence, renderEvidenceContext } from './evidence-recall.mjs';
5
+ import {
6
+ EVIDENCE_SEARCH_MAX_CANDIDATES,
7
+ loadEvidenceIndex,
8
+ recallEvidence,
9
+ renderEvidenceContext,
10
+ searchEvidenceCandidates,
11
+ } from './evidence-recall.mjs';
6
12
  import { sanitizeMemoryText } from './memory-schema.mjs';
7
13
  import { resolveHookOperatingProfile } from './operating-profile-runtime.mjs';
8
14
  import {
@@ -11,16 +17,44 @@ import {
11
17
  } from './active-context-handoff-evidence.mjs';
12
18
  import { isBootstrapPrompt } from '../packages/integrations/src/prompt-content.mjs';
13
19
 
20
+ function scopedRows(rows, activeContext, registry) {
21
+ return scopeEvidenceRows(rows, { activeContext, registry });
22
+ }
23
+
24
+ function indexedScopedEvidence(vaultBase, query, topK, activeContext, registry) {
25
+ const initialLimit = Math.min(
26
+ EVIDENCE_SEARCH_MAX_CANDIDATES,
27
+ Math.max(512, Number(topK || 0) * 64),
28
+ );
29
+ const first = searchEvidenceCandidates(vaultBase, query, {
30
+ candidateLimit: initialLimit,
31
+ });
32
+ let scoped = scopedRows(first.rows, activeContext, registry);
33
+ if (scoped.length >= topK || !first.has_more
34
+ || initialLimit >= EVIDENCE_SEARCH_MAX_CANDIDATES) return scoped;
35
+ const expanded = searchEvidenceCandidates(vaultBase, query, {
36
+ candidateLimit: EVIDENCE_SEARCH_MAX_CANDIDATES,
37
+ });
38
+ scoped = scopedRows(expanded.rows, activeContext, registry);
39
+ return scoped;
40
+ }
41
+
14
42
  export function buildPromptEvidenceContext(vaultBase, prompt, {
15
43
  topK = 3, maxBytes = 3072, rows = null, activeContext = null, registry = null,
16
44
  } = {}) {
17
45
  const query = sanitizeMemoryText(String(prompt || '')).trim();
18
46
  if (!query || isBootstrapPrompt(query)) return '';
19
- const evidence = rows || loadEvidenceIndex(vaultBase);
20
- const scoped = scopeEvidenceRows(evidence, {
21
- activeContext,
22
- registry: registry || readSessionRegistry(vaultBase),
23
- });
47
+ const effectiveRegistry = registry || readSessionRegistry(vaultBase);
48
+ let scoped;
49
+ if (rows) {
50
+ scoped = scopedRows(rows, activeContext, effectiveRegistry);
51
+ } else {
52
+ try {
53
+ scoped = indexedScopedEvidence(vaultBase, query, topK, activeContext, effectiveRegistry);
54
+ } catch {
55
+ scoped = scopedRows(loadEvidenceIndex(vaultBase), activeContext, effectiveRegistry);
56
+ }
57
+ }
24
58
  if (!scoped.length) return '';
25
59
  return sanitizeMemoryText(renderEvidenceContext(
26
60
  recallEvidence(scoped, query, { topK }),
@@ -53,4 +87,4 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
53
87
  } catch {
54
88
  writeHookOutput({});
55
89
  }
56
- }
90
+ }
@@ -1 +1,11 @@
1
1
  export * from '../packages/vault/src/evidence-recall.mjs';
2
+ export * from '../packages/vault/src/evidence-recall-page.mjs';
3
+ export * from '../packages/vault/src/evidence-search-index.mjs';
4
+ export {
5
+ EVIDENCE_INDEX_STATE_FILE,
6
+ EVIDENCE_INDEX_STATE_VERSION,
7
+ buildIncrementalEvidenceIndex,
8
+ buildIncrementalEvidenceIndex as buildEvidenceIndex,
9
+ loadEvidenceIndexState,
10
+ refreshEvidenceIndex,
11
+ } from '../packages/vault/src/evidence-index-store.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.85.0",
3
+ "version": "0.86.0",
4
4
  "description": "Vault-first persistent memory for AI coding agents, with an optional profile-aware governance runtime: OFF, FLOW, GUIDE, GOVERN, or ASSURE. Local-first and agent-agnostic (Claude Code, Codex, Cursor…).",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -6,6 +6,7 @@ const TOOLS = [
6
6
  ['wendkeep_project_status', 'read', 'project:status'],
7
7
  ['wendkeep_context_status', 'read', 'context:status'],
8
8
  ['wendkeep_memory_recall', 'read', 'memory:recall'],
9
+ ['wendkeep_evidence_recall', 'read', 'evidence:recall'],
9
10
  ['wendkeep_memory_conflicts', 'read', 'memory:conflicts'],
10
11
  ['wendkeep_change_list', 'read', 'change:list'],
11
12
  ['wendkeep_change_show', 'read', 'change:show'],
@@ -55,10 +56,10 @@ function deepFreeze(value) {
55
56
 
56
57
  export const MCP_EFFECT_MANIFEST = deepFreeze({
57
58
  schema_version: 1,
58
- catalog_version: '2026-08-24',
59
+ catalog_version: '2026-08-27',
59
60
  server_aliases: ['wendkeep', 'wendkeep-native'],
60
61
  tools: TOOLS,
61
- integrity: 'sha256:de96c2f3f5ae3d814440b541db778f87be4550ebd2ce9dd5ae2377f354770046',
62
+ integrity: 'sha256:1bb4d5dc62008ed23afa0d850a89bbbb77ea9cc31da79cde557aa2f02034fdde',
62
63
  });
63
64
 
64
65
  export function verifyMcpEffectManifest(manifest) {
@@ -0,0 +1,130 @@
1
+ import {
2
+ EVIDENCE_RECALL_DEFAULT_LIMIT,
3
+ EVIDENCE_RECALL_DEFAULT_MAX_BYTES,
4
+ EVIDENCE_RECALL_MAX_LIMIT,
5
+ EvidenceRecallBudgetError,
6
+ EvidenceRecallCursorError,
7
+ recallEvidencePage,
8
+ } from '../../vault/src/evidence-recall-page.mjs';
9
+ import {
10
+ EVIDENCE_SEARCH_DEFAULT_CANDIDATES,
11
+ EVIDENCE_SEARCH_DEFAULT_POSTING_BUDGET,
12
+ EVIDENCE_SEARCH_MAX_CANDIDATES,
13
+ EVIDENCE_SEARCH_MAX_POSTING_BUDGET,
14
+ searchEvidenceCandidates,
15
+ } from '../../vault/src/evidence-search-index.mjs';
16
+
17
+ export const MCP_EVIDENCE_RECALL_MAX_BYTES = 512 * 1024;
18
+
19
+ function integer(value, fallback, { min = 1, max } = {}) {
20
+ const number = Number(value ?? fallback);
21
+ if (!Number.isSafeInteger(number) || number < min || number > max) {
22
+ throw new RangeError(`value must be an integer between ${min} and ${max}`);
23
+ }
24
+ return number;
25
+ }
26
+
27
+ function backend(value) {
28
+ const normalized = String(value ?? 'auto').trim().toLowerCase();
29
+ if (!['auto', 'sqlite', 'lexical'].includes(normalized)) {
30
+ throw new TypeError('backend must be auto, sqlite, or lexical');
31
+ }
32
+ return normalized;
33
+ }
34
+
35
+ function filters(value) {
36
+ if (value === undefined || value === null) return {};
37
+ if (typeof value !== 'object' || Array.isArray(value)) {
38
+ throw new TypeError('filters must be an object');
39
+ }
40
+ return value;
41
+ }
42
+
43
+ function logicalReference(result) {
44
+ const { logical_path: logicalPath, ...rest } = result || {};
45
+ return {
46
+ ...rest,
47
+ logical_ref: String(logicalPath || ''),
48
+ };
49
+ }
50
+
51
+ function mappedError(error) {
52
+ if (String(error?.code || '').startsWith('MCP_')) return error;
53
+ let code = 'MCP_EVIDENCE_RECALL_FAILED';
54
+ if (error instanceof EvidenceRecallCursorError
55
+ || error?.code === 'EVIDENCE_RECALL_CURSOR_INVALID') {
56
+ code = 'MCP_EVIDENCE_CURSOR_INVALID';
57
+ } else if (error instanceof EvidenceRecallBudgetError
58
+ || error?.code === 'EVIDENCE_RECALL_BUDGET_TOO_SMALL') {
59
+ code = 'MCP_EVIDENCE_BUDGET_TOO_SMALL';
60
+ } else if (error?.code === 'EVIDENCE_SEARCH_SQLITE_UNAVAILABLE'
61
+ || error?.code === 'EVIDENCE_SEARCH_FTS5_UNAVAILABLE') {
62
+ code = 'MCP_EVIDENCE_BACKEND_UNAVAILABLE';
63
+ } else if (error?.code === 'VAULT_PATH_UNSAFE') {
64
+ code = 'MCP_EVIDENCE_ARTIFACT_UNSAFE';
65
+ } else if (error instanceof TypeError || error instanceof RangeError) {
66
+ code = 'MCP_EVIDENCE_RECALL_INVALID';
67
+ }
68
+ return Object.assign(new Error(error?.message || 'evidence recall failed'), {
69
+ code,
70
+ cause: error,
71
+ });
72
+ }
73
+
74
+ export function recallEvidenceForMcp(vaultBase, args = {}) {
75
+ try {
76
+ const query = String(args.query || '').trim();
77
+ if (!query) {
78
+ throw Object.assign(new Error('query is required'), {
79
+ code: 'MCP_EVIDENCE_QUERY_REQUIRED',
80
+ });
81
+ }
82
+ const limit = integer(args.limit, EVIDENCE_RECALL_DEFAULT_LIMIT, {
83
+ max: EVIDENCE_RECALL_MAX_LIMIT,
84
+ });
85
+ const maxBytes = integer(args.max_bytes, EVIDENCE_RECALL_DEFAULT_MAX_BYTES, {
86
+ min: 2,
87
+ max: MCP_EVIDENCE_RECALL_MAX_BYTES,
88
+ });
89
+ const candidateLimit = integer(
90
+ args.candidate_limit,
91
+ Math.max(EVIDENCE_SEARCH_DEFAULT_CANDIDATES, limit),
92
+ { max: EVIDENCE_SEARCH_MAX_CANDIDATES },
93
+ );
94
+ const postingBudget = integer(
95
+ args.posting_budget,
96
+ EVIDENCE_SEARCH_DEFAULT_POSTING_BUDGET,
97
+ { max: EVIDENCE_SEARCH_MAX_POSTING_BUDGET },
98
+ );
99
+ const normalizedFilters = filters(args.filters);
100
+ const candidates = searchEvidenceCandidates(vaultBase, query, {
101
+ candidateLimit,
102
+ postingBudget,
103
+ filters: normalizedFilters,
104
+ backend: backend(args.backend),
105
+ sqlite: 'auto',
106
+ });
107
+ const page = recallEvidencePage(candidates.rows, query, {
108
+ cursor: args.cursor || null,
109
+ filters: normalizedFilters,
110
+ limit,
111
+ maxBytes,
112
+ });
113
+ return {
114
+ schema_version: 1,
115
+ ...page,
116
+ results: page.results.map(logicalReference),
117
+ candidates: {
118
+ backend: candidates.backend,
119
+ count: candidates.candidate_count,
120
+ posting_entries: candidates.posting_entries,
121
+ has_more: candidates.has_more,
122
+ rebuilt: candidates.rebuilt,
123
+ fallback_reason: candidates.fallback_reason || '',
124
+ },
125
+ complete_candidate_set: candidates.has_more !== true,
126
+ };
127
+ } catch (error) {
128
+ throw mappedError(error);
129
+ }
130
+ }
@@ -23,6 +23,7 @@ import {
23
23
  } from '../../vault/src/memory-store.mjs';
24
24
  import { scopeForMemoryKey } from '../../vault/src/memory-scope.mjs';
25
25
  import { sanitizeMemoryText } from '../../vault/src/memory-schema.mjs';
26
+ import { recallEvidenceForMcp } from './evidence-recall.mjs';
26
27
 
27
28
  const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
28
29
  const BIN = join(ROOT, 'bin', 'wendkeep.mjs');
@@ -305,6 +306,9 @@ export async function executeNativeMcpTool(tool, args, { signal } = {}) {
305
306
  topK: Math.min(Number(args.limit || 10), 100),
306
307
  }));
307
308
  }
309
+ if (tool.name === 'wendkeep_evidence_recall') {
310
+ return sanitize(recallEvidenceForMcp(ctx.vaultBase, args));
311
+ }
308
312
  if (tool.name === 'wendkeep_memory_conflicts') {
309
313
  return sanitize(listMemoryCandidates(ctx.vaultBase, { activeOnly: true }).candidates);
310
314
  }
@@ -3,6 +3,9 @@ import { MCP_EFFECT_MANIFEST, resolveMcpToolEffect } from './effects.mjs';
3
3
  const DEFAULT_PAGE_SIZE = 50;
4
4
  const MAX_PAGE_SIZE = 100;
5
5
  const DEFAULT_TIMEOUT_MS = 10_000;
6
+ const MAX_EVIDENCE_BYTES = 512 * 1024;
7
+ const MAX_EVIDENCE_CANDIDATES = 4096;
8
+ const MAX_EVIDENCE_POSTINGS = 1_048_576;
6
9
 
7
10
  function boundedInteger(value, fallback, maximum = MAX_PAGE_SIZE) {
8
11
  const parsed = Number.parseInt(value, 10);
@@ -42,6 +45,7 @@ function availability(tool, nodeVersion) {
42
45
 
43
46
  const TOOL_REQUIRED_ARGUMENTS = Object.freeze({
44
47
  wendkeep_context_status: ['session_id'],
48
+ wendkeep_evidence_recall: ['query'],
45
49
  wendkeep_change_show: ['change'],
46
50
  wendkeep_change_status: ['change'],
47
51
  wendkeep_task_show: ['session_id', 'task'],
@@ -55,6 +59,13 @@ const TOOL_REQUIRED_ARGUMENTS = Object.freeze({
55
59
  wendkeep_handoff_publish: ['payload'],
56
60
  });
57
61
 
62
+ const stringOrStringList = {
63
+ oneOf: [
64
+ { type: 'string' },
65
+ { type: 'array', items: { type: 'string' }, uniqueItems: true },
66
+ ],
67
+ };
68
+
58
69
  function inputSchema(tool) {
59
70
  const required = ['project_root'];
60
71
  required.push(...(TOOL_REQUIRED_ARGUMENTS[tool.name] || []));
@@ -87,6 +98,25 @@ function inputSchema(tool) {
87
98
  query: { type: 'string' },
88
99
  cursor: { type: 'string' },
89
100
  limit: { type: 'integer', minimum: 1, maximum: MAX_PAGE_SIZE },
101
+ max_bytes: { type: 'integer', minimum: 2, maximum: MAX_EVIDENCE_BYTES },
102
+ candidate_limit: { type: 'integer', minimum: 1, maximum: MAX_EVIDENCE_CANDIDATES },
103
+ posting_budget: { type: 'integer', minimum: 1, maximum: MAX_EVIDENCE_POSTINGS },
104
+ backend: { type: 'string', enum: ['auto', 'sqlite', 'lexical'] },
105
+ filters: {
106
+ type: 'object',
107
+ additionalProperties: false,
108
+ properties: {
109
+ authority: stringOrStringList,
110
+ validity: stringOrStringList,
111
+ entity_type: stringOrStringList,
112
+ project_id: stringOrStringList,
113
+ change_slug: stringOrStringList,
114
+ session_id: stringOrStringList,
115
+ work_session_id: stringOrStringList,
116
+ logical_path: stringOrStringList,
117
+ logical_path_prefix: stringOrStringList,
118
+ },
119
+ },
90
120
  payload: { type: 'object' },
91
121
  },
92
122
  };
@@ -339,4 +369,4 @@ export function createNativeMcpServer({
339
369
  };
340
370
  }
341
371
 
342
- export { supportsObserverSql };
372
+ export { supportsObserverSql };