@ancleto/spec 0.6.12 → 0.6.15
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/README.md +24 -16
- package/package.json +1 -1
- package/src/cli/index.js +28 -12
- package/src/core/memory/database.js +1 -1
- package/src/core/memory/engine.js +1 -1
- package/src/core/memory/mcp-server.js +98 -0
- package/src/core/memory/tools.js +8 -2
package/README.md
CHANGED
|
@@ -101,25 +101,23 @@ ancleto install --no-mcp
|
|
|
101
101
|
ancleto update
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
> El instalador configura por defecto
|
|
104
|
+
> El instalador configura por defecto el MCP de **memoria propia** (`ancleto-memory`) y **caveman**. El MCP externo **engram** ya no se agrega por defecto: sumalo con `--with-engram` si además querés esa memoria. Si un binario no está en tu sistema, se omite con un warning sin interrumpir el flujo.
|
|
105
105
|
|
|
106
106
|
### Costo en tokens de los MCP
|
|
107
107
|
|
|
108
|
-
Los MCP no son gratis en contexto: la lista de herramientas de cada servidor viaja en **cada** request, se usen o no. Medición local
|
|
108
|
+
Los MCP no son gratis en contexto: la lista de herramientas de cada servidor viaja en **cada** request, se usen o no. Medición local:
|
|
109
109
|
|
|
110
110
|
| MCP | Herramientas | Tokens por request |
|
|
111
111
|
|---|---|---|
|
|
112
|
-
|
|
|
112
|
+
| `ancleto-memory` (memoria propia) | 3 | ~500 |
|
|
113
113
|
| caveman | 5 | ~830 |
|
|
114
|
-
|
|
|
114
|
+
| engram (solo con `--with-engram`) | 18 | ~4.900 |
|
|
115
115
|
|
|
116
|
-
Cómo bajarlo
|
|
116
|
+
Cómo bajarlo:
|
|
117
117
|
|
|
118
|
-
- Instalá con `--no-mcp
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
|
|
122
|
-
> La memoria del framework (`.ancleto/memory.db`) es independiente de engram: funciona sin él. En los tiers `minimo` y `gratis` este overhead pesa proporcionalmente más que en `normal`.
|
|
118
|
+
- Instalá con `--no-mcp` si no querés ninguno.
|
|
119
|
+
- Evitá `--with-engram` salvo que uses esa memoria: es el que más pesa (9× la memoria propia).
|
|
120
|
+
- Podés deshabilitar cualquier servidor en tu `opencode.json`: `"mcp": { "engram": { "enabled": false } }`.
|
|
123
121
|
|
|
124
122
|
---
|
|
125
123
|
|
|
@@ -192,6 +190,8 @@ ancleto memory context [--scope <project|feature|task>] [--out <archivo>]
|
|
|
192
190
|
ancleto memory doctor [--rebuild]
|
|
193
191
|
# Diagnostica la base de memoria y reconstruye el índice FTS5
|
|
194
192
|
|
|
193
|
+
ancleto mcp # Servidor MCP de memoria propia (stdio): lo consume tu IDE
|
|
194
|
+
|
|
195
195
|
ancleto discovery [--compress] [--include <glob>] [--ignore <glob>] [--token-budget <n>]
|
|
196
196
|
# Empaqueta el repo con Repomix según tu tier
|
|
197
197
|
```
|
|
@@ -200,17 +200,25 @@ ancleto discovery [--compress] [--include <glob>] [--ignore <glob>] [--token-bud
|
|
|
200
200
|
|
|
201
201
|
## Motor de memoria persistente
|
|
202
202
|
|
|
203
|
-
No hay bases de datos vectoriales: es **SQLite nativo** con búsqueda full-text (FTS5) y ranking BM25.
|
|
203
|
+
No hay bases de datos vectoriales: es **SQLite nativo** con búsqueda full-text (FTS5) y ranking BM25, en `.ancleto/memory.db`.
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
Funciona en tres piezas que comparten el mismo archivo:
|
|
206
|
+
|
|
207
|
+
| Pieza | Qué hace |
|
|
208
|
+
|---|---|
|
|
209
|
+
| **Almacenamiento** | Reglas y decisiones con supersesión atómica por `memory_key` (una sola activa por clave). |
|
|
210
|
+
| **Tools del LLM** (`ancleto mcp`) | Servidor MCP local, sin dependencias, que expone tres herramientas a tu IDE. |
|
|
211
|
+
| **CLI** | `ancleto memory context` materializa el bloque `<ProjectMemoryRules>` y `ancleto memory doctor` verifica integridad y reconstruye el índice FTS5. |
|
|
206
212
|
|
|
207
213
|
| Herramienta | Función |
|
|
208
214
|
|---|---|
|
|
209
|
-
| `searchMemory` | Recupera contexto de decisiones previas. |
|
|
210
|
-
| `recordRule` | Guarda una regla
|
|
211
|
-
| `recordDecision` | Registra el *por qué* de
|
|
215
|
+
| `searchMemory` | Recupera contexto de decisiones y reglas previas (búsqueda léxica BM25). |
|
|
216
|
+
| `recordRule` | Guarda una regla o restricción permanente. |
|
|
217
|
+
| `recordDecision` | Registra el *por qué* de una decisión. |
|
|
218
|
+
|
|
219
|
+
Cada entrada tiene un `scope`: `project` (default, entra en `<ProjectMemoryRules>`), `feature` o `task`. El orquestador inyecta los bloques `<ProjectMemoryRules>` y `<ProjectTopology>` en el system prompt de tu agente desde el día cero —incluso sin reglas previas— para que no repita errores ya resueltos.
|
|
212
220
|
|
|
213
|
-
El
|
|
221
|
+
> El mismo motor se puede consultar a mano desde la terminal: `ancleto memory context --scope project`.
|
|
214
222
|
|
|
215
223
|
---
|
|
216
224
|
|
package/package.json
CHANGED
package/src/cli/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
9
9
|
import { homedir, tmpdir } from 'node:os'
|
|
10
10
|
import { createMemoryEngine, defaultMemoryDbPath } from '../core/memory/engine.js'
|
|
11
11
|
import { memoryDoctor } from '../core/memory/doctor.js'
|
|
12
|
+
import { serveMemoryMcp } from '../core/memory/mcp-server.js'
|
|
12
13
|
import { writeDiscoveryMap } from '../core/discovery.js'
|
|
13
14
|
import { readProjectTier, buildRepomixArgs, tierTokenBudget } from '../core/repomix-tier.js'
|
|
14
15
|
import { showBanner, selectOption, stopBanner } from './ui.js'
|
|
@@ -26,8 +27,9 @@ const HELP = `ancleto - orquestador SDD liviano con subagentes optimizados para
|
|
|
26
27
|
Uso:
|
|
27
28
|
ancleto install [--project <dir>] Instala agents/commands/skills en opencode
|
|
28
29
|
(global por defecto, o en .opencode/ del proyecto)
|
|
29
|
-
Configura
|
|
30
|
+
Configura el MCP de memoria propia + caveman por defecto
|
|
30
31
|
ancleto install --no-mcp Igual que install pero sin tocar config MCP
|
|
32
|
+
ancleto install --with-engram Ademas configura el MCP externo engram (memoria opcional)
|
|
31
33
|
ancleto install --tier <nivel> normal | minimo | gratis (wizard interactivo en TTY)
|
|
32
34
|
ancleto install --agent <nombre> opencode | vscode | antigravity | cursor | roo (wizard si no esta guardado)
|
|
33
35
|
ancleto update [--project <dir>] Alias de install (re-instala sobre lo existente)
|
|
@@ -42,6 +44,8 @@ Uso:
|
|
|
42
44
|
Imprime/escribe el bloque <ProjectMemoryRules> (reglas activas)
|
|
43
45
|
ancleto memory doctor [--rebuild] Diagnostica .ancleto/memory.db (integridad, FTS5, unicidad)
|
|
44
46
|
y reconstruye el indice FTS5 con --rebuild
|
|
47
|
+
ancleto mcp Servidor MCP de memoria propia (stdio) para tu IDE
|
|
48
|
+
expone searchMemory, recordRule y recordDecision
|
|
45
49
|
ancleto check Verifica integridad de archivos instalados vs manifiesto
|
|
46
50
|
ancleto doctor Diagnostica el entorno (Node, node:sqlite, opencode.json)
|
|
47
51
|
ancleto --help Esta ayuda
|
|
@@ -107,18 +111,27 @@ function resolveBin(name, fallbacks = []) {
|
|
|
107
111
|
return null
|
|
108
112
|
}
|
|
109
113
|
|
|
110
|
-
function
|
|
114
|
+
function resolveSelfCommand() {
|
|
115
|
+
return [process.execPath, join(__dirname, 'index.js'), 'mcp']
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function buildDefaultMcp({ withEngram = false } = {}) {
|
|
111
119
|
const mcp = {}
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
// Memoria propia del framework: SQLite (.ancleto/memory.db) expuesta como tools MCP
|
|
122
|
+
mcp['ancleto-memory'] = { type: 'local', enabled: true, command: resolveSelfCommand() }
|
|
123
|
+
|
|
124
|
+
if (withEngram) {
|
|
125
|
+
const engramFallbacks = [
|
|
126
|
+
join(homedir(), 'go', 'bin', binName('engram')),
|
|
127
|
+
join(homedir(), '.local', 'bin', binName('engram'))
|
|
128
|
+
]
|
|
129
|
+
const engramBin = resolveBin('engram', engramFallbacks)
|
|
130
|
+
if (engramBin) {
|
|
131
|
+
mcp.engram = { type: 'local', enabled: true, command: [engramBin, 'mcp', '--tools=agent'] }
|
|
132
|
+
} else {
|
|
133
|
+
console.warn('ancleto: no se encontro engram en PATH; se omitio su MCP')
|
|
134
|
+
}
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
const cavemanFallbacks = [
|
|
@@ -368,7 +381,7 @@ async function install(args) {
|
|
|
368
381
|
const pi = args.indexOf('--project')
|
|
369
382
|
const project = pi >= 0 ? args[pi + 1] : null
|
|
370
383
|
const withMcp = !args.includes('--no-mcp')
|
|
371
|
-
let mcpMap = withMcp ? buildDefaultMcp() : {}
|
|
384
|
+
let mcpMap = withMcp ? buildDefaultMcp({ withEngram: args.includes('--with-engram') }) : {}
|
|
372
385
|
|
|
373
386
|
const agentFlag = scanAgentFlag(args)
|
|
374
387
|
let tier = scanTierFlag(args)
|
|
@@ -915,6 +928,9 @@ switch (cmd) {
|
|
|
915
928
|
case 'doctor':
|
|
916
929
|
await doctorCommand()
|
|
917
930
|
break
|
|
931
|
+
case 'mcp':
|
|
932
|
+
await serveMemoryMcp()
|
|
933
|
+
break
|
|
918
934
|
case '--version':
|
|
919
935
|
case '-v':
|
|
920
936
|
console.log(`ancleto ${await packageVersion()}`)
|
|
@@ -7,7 +7,7 @@ const MIGRATIONS = [
|
|
|
7
7
|
id TEXT PRIMARY KEY,
|
|
8
8
|
memory_key TEXT NOT NULL,
|
|
9
9
|
type TEXT NOT NULL CHECK (type IN ('rule', 'decision')),
|
|
10
|
-
scope TEXT NOT NULL DEFAULT '
|
|
10
|
+
scope TEXT NOT NULL DEFAULT 'project',
|
|
11
11
|
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'superseded', 'deleted')),
|
|
12
12
|
content TEXT NOT NULL,
|
|
13
13
|
justification TEXT NOT NULL DEFAULT '',
|
|
@@ -106,7 +106,7 @@ export function createMemoryEngine(dbPath = defaultMemoryDbPath()) {
|
|
|
106
106
|
const type = input.type
|
|
107
107
|
const content = String(input.content || '')
|
|
108
108
|
const justification = String(input.justification || '')
|
|
109
|
-
const scope = String(input.scope || '
|
|
109
|
+
const scope = String(input.scope || 'project')
|
|
110
110
|
if (!memory_key) throw new Error('memory_key es obligatorio')
|
|
111
111
|
if (type !== 'rule' && type !== 'decision') throw new Error(`type invalido: ${type}`)
|
|
112
112
|
if (!content.trim()) throw new Error('content es obligatorio')
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createMemoryEngine, defaultMemoryDbPath } from './engine.js'
|
|
2
|
+
import { memoryTools, createMemoryToolHandlers } from './tools.js'
|
|
3
|
+
|
|
4
|
+
const PROTOCOL_VERSION = '2024-11-05'
|
|
5
|
+
const SERVER_INFO = { name: 'ancleto-memory', version: '1.0.0' }
|
|
6
|
+
|
|
7
|
+
export function createMemoryServer({ dbPath = defaultMemoryDbPath() } = {}) {
|
|
8
|
+
let engine = null
|
|
9
|
+
let handlers = null
|
|
10
|
+
|
|
11
|
+
function toolkit() {
|
|
12
|
+
if (!engine) {
|
|
13
|
+
engine = createMemoryEngine(dbPath)
|
|
14
|
+
handlers = createMemoryToolHandlers(engine, { source: 'mcp:ancleto-memory' })
|
|
15
|
+
}
|
|
16
|
+
return handlers
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const respond = (id, result) => ({ jsonrpc: '2.0', id, result })
|
|
20
|
+
const fail = (id, code, message) => ({ jsonrpc: '2.0', id, error: { code, message } })
|
|
21
|
+
|
|
22
|
+
function handle(message) {
|
|
23
|
+
if (!message || typeof message !== 'object' || message.jsonrpc !== '2.0') return null
|
|
24
|
+
const { id, method, params } = message
|
|
25
|
+
if (typeof method === 'string' && method.startsWith('notifications/')) return null
|
|
26
|
+
if (id === undefined || id === null) return null
|
|
27
|
+
|
|
28
|
+
switch (method) {
|
|
29
|
+
case 'initialize':
|
|
30
|
+
return respond(id, { protocolVersion: PROTOCOL_VERSION, capabilities: { tools: {} }, serverInfo: SERVER_INFO })
|
|
31
|
+
case 'ping':
|
|
32
|
+
return respond(id, {})
|
|
33
|
+
case 'tools/list':
|
|
34
|
+
return respond(id, { tools: memoryTools })
|
|
35
|
+
case 'tools/call': {
|
|
36
|
+
const name = params && params.name
|
|
37
|
+
const args = (params && params.arguments) || {}
|
|
38
|
+
const tool = toolkit()[name]
|
|
39
|
+
if (!tool) return fail(id, -32602, `tool desconocida: ${name}`)
|
|
40
|
+
try {
|
|
41
|
+
const result = tool(args)
|
|
42
|
+
return respond(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] })
|
|
43
|
+
} catch (err) {
|
|
44
|
+
return respond(id, { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true })
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
default:
|
|
48
|
+
return fail(id, -32601, `metodo no soportado: ${method}`)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function close() {
|
|
53
|
+
if (engine) {
|
|
54
|
+
engine.close()
|
|
55
|
+
engine = null
|
|
56
|
+
handlers = null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { handle, close, dbPath }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function serveMemoryMcp({ dbPath = defaultMemoryDbPath(), input = process.stdin, output = process.stdout } = {}) {
|
|
64
|
+
const server = createMemoryServer({ dbPath })
|
|
65
|
+
const write = (obj) => output.write(JSON.stringify(obj) + '\n')
|
|
66
|
+
|
|
67
|
+
let stopped = false
|
|
68
|
+
const stop = () => {
|
|
69
|
+
if (stopped) return
|
|
70
|
+
stopped = true
|
|
71
|
+
server.close()
|
|
72
|
+
try { input.pause() } catch {}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
input.setEncoding('utf8')
|
|
76
|
+
let buffer = ''
|
|
77
|
+
input.on('data', (chunk) => {
|
|
78
|
+
buffer += chunk
|
|
79
|
+
const lines = buffer.split('\n')
|
|
80
|
+
buffer = lines.pop()
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
const text = line.trim()
|
|
83
|
+
if (!text) continue
|
|
84
|
+
let message
|
|
85
|
+
try {
|
|
86
|
+
message = JSON.parse(text)
|
|
87
|
+
} catch {
|
|
88
|
+
write({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'JSON invalido' } })
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
const response = server.handle(message)
|
|
92
|
+
if (response) write(response)
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
input.on('end', stop)
|
|
96
|
+
process.on('SIGINT', () => { stop(); process.exit(0) })
|
|
97
|
+
process.on('SIGTERM', () => { stop(); process.exit(0) })
|
|
98
|
+
}
|
package/src/core/memory/tools.js
CHANGED
|
@@ -2,6 +2,12 @@ import { createMemoryEngine } from './engine.js'
|
|
|
2
2
|
|
|
3
3
|
const KEY_DESCRIPTION = 'Clave conceptual estable (ej. "api-error-format"). Reusala para actualizar: la nueva version supersede automaticamente la anterior.'
|
|
4
4
|
|
|
5
|
+
const SCOPE_SCHEMA = {
|
|
6
|
+
type: 'string',
|
|
7
|
+
enum: ['project', 'feature', 'task'],
|
|
8
|
+
description: 'Ambito: project (default, entra en <ProjectMemoryRules>), feature o task para algo mas acotado.'
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
const SEARCH_SCHEMA = {
|
|
6
12
|
type: 'object',
|
|
7
13
|
properties: {
|
|
@@ -19,7 +25,7 @@ const RECORD_RULE_SCHEMA = {
|
|
|
19
25
|
memory_key: { type: 'string', description: KEY_DESCRIPTION },
|
|
20
26
|
content: { type: 'string', description: 'Texto de la regla.' },
|
|
21
27
|
justification: { type: 'string', description: 'Por que existe esta regla.' },
|
|
22
|
-
scope:
|
|
28
|
+
scope: SCOPE_SCHEMA
|
|
23
29
|
},
|
|
24
30
|
required: ['memory_key', 'content'],
|
|
25
31
|
additionalProperties: false
|
|
@@ -31,7 +37,7 @@ const RECORD_DECISION_SCHEMA = {
|
|
|
31
37
|
memory_key: { type: 'string', description: KEY_DESCRIPTION },
|
|
32
38
|
content: { type: 'string', description: 'La decision tomada.' },
|
|
33
39
|
justification: { type: 'string', description: 'Justificacion de la decision.' },
|
|
34
|
-
scope:
|
|
40
|
+
scope: SCOPE_SCHEMA
|
|
35
41
|
},
|
|
36
42
|
required: ['memory_key', 'content'],
|
|
37
43
|
additionalProperties: false
|