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.
- package/CHANGELOG.md +40 -0
- package/README.en.md +2 -1
- package/README.md +2 -1
- package/docs/en/commands/evidence-embeddings.md +243 -0
- package/docs/en/commands/mcp.md +67 -7
- package/docs/pt-BR/commands/evidence-embeddings.md +244 -0
- package/docs/pt-BR/commands/mcp.md +66 -7
- package/hooks/evidence-context.mjs +41 -7
- package/hooks/evidence-recall.mjs +10 -0
- package/package.json +1 -1
- package/packages/mcp/src/effects.mjs +3 -2
- package/packages/mcp/src/evidence-recall.mjs +130 -0
- package/packages/mcp/src/executor.mjs +4 -0
- package/packages/mcp/src/server.mjs +31 -1
- package/packages/vault/src/evidence-embedding-plugin.mjs +531 -0
- package/packages/vault/src/evidence-index-store.mjs +360 -0
- package/packages/vault/src/evidence-recall-page.mjs +381 -0
- package/packages/vault/src/evidence-search-index.mjs +917 -0
- package/packages/vault/src/index.mjs +12 -1
- package/packages/vault/src/memory-ledger-view-base.mjs +545 -0
- package/packages/vault/src/memory-ledger-view.mjs +41 -0
- package/packages/vault/src/memory-rotation-store.mjs +967 -0
- package/packages/vault/src/memory-segment-store.mjs +820 -0
- package/packages/vault/src/memory-snapshot-store.mjs +1105 -0
- package/packages/vault/src/memory-store-base.mjs +1161 -0
- package/packages/vault/src/memory-store-core.mjs +2 -0
- package/packages/vault/src/memory-store.mjs +46 -1161
- package/src/doctor.mjs +41 -5
- package/src/evidence-search-health.mjs +221 -0
- package/src/memory-scale-health.mjs +210 -0
- 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
|
-
`
|
|
67
|
-
`
|
|
68
|
-
`wendkeep_task_evaluate`, `wendkeep_handoff_current`,
|
|
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,
|
|
79
|
-
|
|
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`:
|
|
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 {
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
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-
|
|
59
|
+
catalog_version: '2026-08-27',
|
|
59
60
|
server_aliases: ['wendkeep', 'wendkeep-native'],
|
|
60
61
|
tools: TOOLS,
|
|
61
|
-
integrity: 'sha256:
|
|
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 };
|