toon-memory 1.0.4 → 1.0.6
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/bin/setup.js +25 -62
- package/bin/toon-memory.js +0 -0
- package/mcp/server.js +24261 -17365
- package/package.json +1 -1
- package/skills/toon-memory.md +34 -0
- package/src/mcp/server.ts +227 -283
- package/src/memory.ts +189 -196
- package/README.md +0 -66
package/src/memory.ts
CHANGED
|
@@ -1,244 +1,237 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin"
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
const MEMORY_DIR = path.join(process.cwd(), ".opencode", "memory")
|
|
8
|
-
const MEMORY_FILE = path.join(MEMORY_DIR, "data.toon")
|
|
9
|
-
|
|
10
|
-
interface MemoryEntry {
|
|
11
|
-
id: string
|
|
12
|
-
category: "decision" | "pattern" | "bug" | "knowledge"
|
|
13
|
-
key: string
|
|
14
|
-
content: string
|
|
15
|
-
file: string
|
|
16
|
-
tags: string
|
|
17
|
-
date: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface MemoryData {
|
|
21
|
-
version: number
|
|
22
|
-
entries: MemoryEntry[]
|
|
23
|
-
summaries: Record<string, string>
|
|
24
|
-
}
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
|
|
3
|
+
import { dirname, join } from "path"
|
|
4
|
+
import { fileURLToPath } from "url"
|
|
5
|
+
import { randomBytes } from "crypto"
|
|
25
6
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const MEMORY_DIR = join(__dirname, "..", "memory")
|
|
9
|
+
const MEMORY_FILE = join(MEMORY_DIR, "data.toon")
|
|
29
10
|
|
|
30
|
-
function
|
|
11
|
+
function ensureMemoryFile() {
|
|
31
12
|
if (!existsSync(MEMORY_DIR)) mkdirSync(MEMORY_DIR, { recursive: true })
|
|
13
|
+
if (!existsSync(MEMORY_FILE)) {
|
|
14
|
+
writeFileSync(MEMORY_FILE, "version: 1\nentries[0|]{id|category|key|content|file|tags|date}:\n")
|
|
15
|
+
}
|
|
32
16
|
}
|
|
33
17
|
|
|
34
|
-
function readMemory()
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const raw = readFileSync(MEMORY_FILE, "utf-8")
|
|
39
|
-
const parsed = decode(raw) as MemoryData
|
|
40
|
-
if (!parsed || typeof parsed !== "object") return defaultData()
|
|
41
|
-
if (!Array.isArray(parsed.entries)) parsed.entries = []
|
|
42
|
-
if (!parsed.summaries || typeof parsed.summaries !== "object") parsed.summaries = {}
|
|
43
|
-
return parsed
|
|
44
|
-
} catch {
|
|
45
|
-
return defaultData()
|
|
46
|
-
}
|
|
18
|
+
function readMemory() {
|
|
19
|
+
ensureMemoryFile()
|
|
20
|
+
return readFileSync(MEMORY_FILE, "utf-8")
|
|
47
21
|
}
|
|
48
22
|
|
|
49
|
-
function writeMemory(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
writeFileSync(MEMORY_FILE, toon, "utf-8")
|
|
23
|
+
function writeMemory(content: string) {
|
|
24
|
+
ensureMemoryFile()
|
|
25
|
+
writeFileSync(MEMORY_FILE, content)
|
|
53
26
|
}
|
|
54
27
|
|
|
55
|
-
function
|
|
56
|
-
return
|
|
28
|
+
function generateId(): string {
|
|
29
|
+
return randomBytes(4).toString("hex")
|
|
57
30
|
}
|
|
58
31
|
|
|
59
|
-
export const
|
|
60
|
-
description:
|
|
61
|
-
"Guarda un hecho en la memoria persistente del proyecto (decisiones, patrones, bugs, conocimiento). Se recuerda entre sesiones. Usar cuando el agente tome una decisión importante, resuelva un bug, o observe un patrón.",
|
|
32
|
+
export const memoryRemember = tool({
|
|
33
|
+
description: "Guarda un hecho en la memoria persistente del proyecto (decisiones, patrones, bugs, conocimiento). Se recuerda entre sesiones.",
|
|
62
34
|
args: {
|
|
63
|
-
category: tool.schema
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
tags: tool.schema
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
.default(""),
|
|
35
|
+
category: tool.schema.enum({
|
|
36
|
+
description: "Categoría del hecho",
|
|
37
|
+
options: ["decision", "pattern", "bug", "knowledge"],
|
|
38
|
+
}),
|
|
39
|
+
key: tool.schema.string({
|
|
40
|
+
description: "Título corto en kebab-case (ej: risk-engine-prioridad)",
|
|
41
|
+
}),
|
|
42
|
+
content: tool.schema.string({
|
|
43
|
+
description: "Contenido detallado del hecho",
|
|
44
|
+
}),
|
|
45
|
+
file: tool.schema.string({
|
|
46
|
+
description: "Archivo o línea relacionada (ej: spec.md:145)",
|
|
47
|
+
}),
|
|
48
|
+
tags: tool.schema.string({
|
|
49
|
+
description: "Tags separados por punto y coma (ej: risk;spec)",
|
|
50
|
+
}),
|
|
80
51
|
},
|
|
81
|
-
async execute(
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
key
|
|
90
|
-
|
|
91
|
-
file,
|
|
92
|
-
tags,
|
|
93
|
-
date: new Date().toISOString().slice(0, 10),
|
|
94
|
-
}
|
|
95
|
-
if (existing) {
|
|
96
|
-
const idx = state.entries.indexOf(existing)
|
|
97
|
-
state.entries[idx] = entry
|
|
98
|
-
} else {
|
|
99
|
-
state.entries.push(entry)
|
|
100
|
-
}
|
|
101
|
-
writeMemory(state)
|
|
102
|
-
return {
|
|
103
|
-
title: existing ? "🧠 Actualizado" : "🧠 Guardado",
|
|
104
|
-
output: `${category}/${key} (${entry.id})\n${content}`,
|
|
52
|
+
async execute(args) {
|
|
53
|
+
const data = readMemory()
|
|
54
|
+
const id = generateId()
|
|
55
|
+
const date = new Date().toISOString().split("T")[0]
|
|
56
|
+
const lines = data.split("\n")
|
|
57
|
+
|
|
58
|
+
let headerIdx = lines.findIndex((l) => l.startsWith("entries["))
|
|
59
|
+
if (headerIdx === -1) {
|
|
60
|
+
lines.push(`entries[0|]{id|category|key|content|file|tags|date}:`)
|
|
61
|
+
headerIdx = lines.length - 1
|
|
105
62
|
}
|
|
63
|
+
|
|
64
|
+
const match = lines[headerIdx].match(/entries\[(\d+)\|/)
|
|
65
|
+
const count = match ? parseInt(match[1]) : 0
|
|
66
|
+
const newEntry = `${id}|${args.category}|${args.key}|${args.content}|${args.file || ""}|${args.tags || ""}|${date}`
|
|
67
|
+
|
|
68
|
+
lines.splice(headerIdx + 1, 0, ` ${newEntry}`)
|
|
69
|
+
lines[headerIdx] = lines[headerIdx].replace(/entries\[\d+\|/, `[${count + 1}|`)
|
|
70
|
+
|
|
71
|
+
writeMemory(lines.join("\n"))
|
|
72
|
+
return `🧠 Guardado: ${args.category}/${args.key} (${id})\n${args.content}`
|
|
106
73
|
},
|
|
107
74
|
})
|
|
108
75
|
|
|
109
|
-
export const
|
|
110
|
-
description:
|
|
111
|
-
"Busca en la memoria persistente del proyecto. Devuelve entradas relevantes por categoría, tag, o texto libre. Usar ANTES de leer archivos para evitar re-investigar.",
|
|
76
|
+
export const memoryRecall = tool({
|
|
77
|
+
description: "Busca en la memoria persistente del proyecto. Devuelve entradas relevantes por categoría, tag, o texto libre. Usar ANTES de leer archivos para evitar re-investigar.",
|
|
112
78
|
args: {
|
|
113
|
-
query: tool.schema
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
category: tool.schema
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.default(""),
|
|
79
|
+
query: tool.schema.string({
|
|
80
|
+
description: "Texto a buscar (busca en key, content, tags)",
|
|
81
|
+
}),
|
|
82
|
+
category: tool.schema.string({
|
|
83
|
+
description: "Filtrar por categoría (decision|pattern|bug|knowledge). Vacío = todos.",
|
|
84
|
+
}),
|
|
120
85
|
},
|
|
121
|
-
async execute(
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
86
|
+
async execute(args) {
|
|
87
|
+
const data = readMemory()
|
|
88
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"))
|
|
89
|
+
const queryLower = args.query.toLowerCase()
|
|
90
|
+
|
|
91
|
+
const results = lines
|
|
92
|
+
.map((line) => {
|
|
93
|
+
const trimmed = line.trim()
|
|
94
|
+
const parts = trimmed.split("|")
|
|
95
|
+
if (parts.length < 7) return null
|
|
96
|
+
const [id, cat, key, content, file, tags, date] = parts
|
|
97
|
+
if (args.category && cat !== args.category) return null
|
|
98
|
+
const searchStr = `${id} ${cat} ${key} ${content} ${file} ${tags}`.toLowerCase()
|
|
99
|
+
if (!searchStr.includes(queryLower)) return null
|
|
100
|
+
return { id, cat, key, content, file, tags, date }
|
|
101
|
+
})
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
|
|
133
104
|
if (results.length === 0) {
|
|
134
|
-
return {
|
|
135
|
-
title: "🔍 Sin resultados",
|
|
136
|
-
output: `No encontré nada para "${query}"${category ? ` en categoría "${category}"` : ""}.`,
|
|
137
|
-
}
|
|
105
|
+
return `No se encontraron resultados para "${args.query}"`
|
|
138
106
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
`[${e.category}] ${e.key} (${e.id})\n ${e.content}\n File: ${e.file || "N/A"} | Tags: ${e.tags || "N/A"} | Date: ${e.date}`,
|
|
143
|
-
)
|
|
107
|
+
|
|
108
|
+
return results
|
|
109
|
+
.map((r) => `[${r!.cat}] ${r!.key} (${r!.id})\n ${r!.content}\n File: ${r!.file} | Tags: ${r!.tags} | Date: ${r!.date}`)
|
|
144
110
|
.join("\n\n")
|
|
145
|
-
return { title: `🔍 ${results.length} resultados`, output }
|
|
146
111
|
},
|
|
147
112
|
})
|
|
148
113
|
|
|
149
|
-
export const
|
|
114
|
+
export const memoryForget = tool({
|
|
150
115
|
description: "Elimina una entrada de la memoria por su key o id.",
|
|
151
116
|
args: {
|
|
152
|
-
key: tool.schema
|
|
153
|
-
|
|
154
|
-
|
|
117
|
+
key: tool.schema.string({
|
|
118
|
+
description: "Key o id de la entrada a eliminar",
|
|
119
|
+
}),
|
|
155
120
|
},
|
|
156
|
-
async execute(
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
title: "⚠️ No encontrado",
|
|
165
|
-
output: `No encontré "${key}" en la memoria.`,
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
writeMemory(state)
|
|
169
|
-
return {
|
|
170
|
-
title: "🗑️ Eliminado",
|
|
171
|
-
output: `"${key}" eliminado. Quedan ${state.entries.length} entradas.`,
|
|
121
|
+
async execute(args) {
|
|
122
|
+
const data = readMemory()
|
|
123
|
+
const lines = data.split("\n")
|
|
124
|
+
const headerIdx = lines.findIndex((l) => l.startsWith("entries["))
|
|
125
|
+
|
|
126
|
+
if (headerIdx === -1) {
|
|
127
|
+
return "No hay entradas en memoria"
|
|
172
128
|
}
|
|
129
|
+
|
|
130
|
+
const entryLines = lines.slice(headerIdx + 1).filter((l) => l.trim().length > 0)
|
|
131
|
+
const filtered = entryLines.filter((l) => {
|
|
132
|
+
const parts = l.trim().split("|")
|
|
133
|
+
return parts[0] !== args.key && parts[2] !== args.key
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const removed = entryLines.length - filtered.length
|
|
137
|
+
const match = lines[headerIdx].match(/entries\[(\d+)\|/)
|
|
138
|
+
const count = match ? parseInt(match[1]) : 0
|
|
139
|
+
lines[headerIdx] = lines[headerIdx].replace(/entries\[\d+\|/, `[${count - removed}|`)
|
|
140
|
+
lines.splice(headerIdx + 1, entryLines.length, ...filtered.map((l) => ` ${l.trim()}`))
|
|
141
|
+
|
|
142
|
+
writeMemory(lines.join("\n"))
|
|
143
|
+
return `"${args.key}" eliminado. Quedan ${count - removed} entradas.`
|
|
173
144
|
},
|
|
174
145
|
})
|
|
175
146
|
|
|
176
|
-
export const
|
|
177
|
-
description:
|
|
178
|
-
"Muestra estadísticas de la memoria: total entradas, por categoría, y resúmenes de archivos.",
|
|
147
|
+
export const memoryStats = tool({
|
|
148
|
+
description: "Muestra estadísticas de la memoria: total entradas, por categoría, y resúmenes de archivos.",
|
|
179
149
|
args: {},
|
|
180
150
|
async execute() {
|
|
181
|
-
const
|
|
151
|
+
const data = readMemory()
|
|
152
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"))
|
|
153
|
+
const entries = lines.map((l) => {
|
|
154
|
+
const parts = l.trim().split("|")
|
|
155
|
+
return { category: parts[1] || "unknown" }
|
|
156
|
+
})
|
|
157
|
+
|
|
182
158
|
const byCategory: Record<string, number> = {}
|
|
183
|
-
for (const e of
|
|
159
|
+
for (const e of entries) {
|
|
184
160
|
byCategory[e.category] = (byCategory[e.category] || 0) + 1
|
|
185
161
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
`
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
"Últimas 5 entradas:",
|
|
204
|
-
recent || " (ninguna)",
|
|
205
|
-
].join("\n"),
|
|
206
|
-
}
|
|
162
|
+
|
|
163
|
+
const summaryLines = data.split("\n").filter((l) => l.includes(":") && !l.startsWith(" ") && !l.startsWith("version") && !l.startsWith("entries"))
|
|
164
|
+
const stats = [
|
|
165
|
+
`Entradas totales: ${entries.length}`,
|
|
166
|
+
`Resúmenes de archivos: ${summaryLines.length}`,
|
|
167
|
+
"",
|
|
168
|
+
"Por categoría:",
|
|
169
|
+
...Object.entries(byCategory).map(([k, v]) => ` ${k}: ${v}`),
|
|
170
|
+
"",
|
|
171
|
+
`Últimas 5 entradas:`,
|
|
172
|
+
...lines.slice(-5).map((l) => {
|
|
173
|
+
const parts = l.trim().split("|")
|
|
174
|
+
return ` [${parts[1]}] ${parts[2]} (${parts[0]})`
|
|
175
|
+
}),
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
return stats.join("\n")
|
|
207
179
|
},
|
|
208
180
|
})
|
|
209
181
|
|
|
210
|
-
export const
|
|
211
|
-
description:
|
|
212
|
-
"Guarda o recupera un resumen de un archivo grande. El agente puede leer el resumen en vez del archivo completo para ahorrar tokens.",
|
|
182
|
+
export const memorySummary = tool({
|
|
183
|
+
description: "Guarda o recupera un resumen de un archivo grande. El agente puede leer el resumen en vez del archivo completo para ahorrar tokens.",
|
|
213
184
|
args: {
|
|
214
|
-
action: tool.schema
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
185
|
+
action: tool.schema.enum({
|
|
186
|
+
description: "get para leer, set para guardar",
|
|
187
|
+
options: ["get", "set"],
|
|
188
|
+
}),
|
|
189
|
+
file: tool.schema.string({
|
|
190
|
+
description: "Ruta del archivo",
|
|
191
|
+
}),
|
|
192
|
+
summary: tool.schema.string({
|
|
193
|
+
description: "Resumen del archivo (solo para set)",
|
|
194
|
+
}),
|
|
224
195
|
},
|
|
225
|
-
async execute(
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
196
|
+
async execute(args) {
|
|
197
|
+
const data = readMemory()
|
|
198
|
+
|
|
199
|
+
if (args.action === "get") {
|
|
200
|
+
const lines = data.split("\n")
|
|
201
|
+
const summaryIdx = lines.findIndex((l) => l.trim().startsWith("summaries:"))
|
|
202
|
+
if (summaryIdx === -1) {
|
|
203
|
+
return `No hay resúmenes guardados para "${args.file}"`
|
|
234
204
|
}
|
|
235
|
-
|
|
205
|
+
|
|
206
|
+
const summaryLines = lines.slice(summaryIdx + 1).filter((l) => l.includes(":"))
|
|
207
|
+
const match = summaryLines.find((l) => l.startsWith(` ${args.file}:`))
|
|
208
|
+
if (!match) {
|
|
209
|
+
return `No hay resumen para "${args.file}"`
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const summaryText = match.replace(` ${args.file}: `, "")
|
|
213
|
+
return summaryText
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const lines = data.split("\n")
|
|
217
|
+
let summaryIdx = lines.findIndex((l) => l.trim().startsWith("summaries:"))
|
|
218
|
+
|
|
219
|
+
if (summaryIdx === -1) {
|
|
220
|
+
lines.push("", "summaries:")
|
|
221
|
+
summaryIdx = lines.length - 1
|
|
236
222
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
223
|
+
|
|
224
|
+
const summaryLines = lines.slice(summaryIdx + 1).filter((l) => l.includes(":"))
|
|
225
|
+
const existingIdx = summaryLines.findIndex((l) => l.startsWith(` ${args.file}:`))
|
|
226
|
+
|
|
227
|
+
if (existingIdx !== -1) {
|
|
228
|
+
summaryLines[existingIdx] = ` ${args.file}: ${args.summary}`
|
|
229
|
+
} else {
|
|
230
|
+
summaryLines.push(` ${args.file}: ${args.summary}`)
|
|
242
231
|
}
|
|
232
|
+
|
|
233
|
+
lines.splice(summaryIdx + 1, lines.length - summaryIdx - 1, ...summaryLines)
|
|
234
|
+
writeMemory(lines.join("\n"))
|
|
235
|
+
return `📝 Resumen guardado para ${args.file}`
|
|
243
236
|
},
|
|
244
237
|
})
|
package/README.md
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# toon-memory
|
|
2
|
-
|
|
3
|
-
Persistent memory system for [OpenCode](https://opencode.ai) AI agent using TOON format.
|
|
4
|
-
|
|
5
|
-
**40% fewer tokens than JSON** for the same data. The agent remembers decisions, patterns, and bugs between sessions.
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
# In your project
|
|
11
|
-
npm install toon-memory @toon-format/toon
|
|
12
|
-
npx toon-memory
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Or globally:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install -g toon-memory @toon-format/toon
|
|
19
|
-
# Then in any project:
|
|
20
|
-
npx toon-memory
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## What it does
|
|
24
|
-
|
|
25
|
-
Copies 3 files to your project:
|
|
26
|
-
|
|
27
|
-
| File | Purpose |
|
|
28
|
-
|------|---------|
|
|
29
|
-
| `.opencode/tools/memory.ts` | 5 tools for the AI agent |
|
|
30
|
-
| `.opencode/memory/data.toon` | Your project's memory (TOON format) |
|
|
31
|
-
| `.opencode/skills/toon-memory.md` | Usage instructions |
|
|
32
|
-
|
|
33
|
-
## Tools
|
|
34
|
-
|
|
35
|
-
| Tool | Description |
|
|
36
|
-
|------|-------------|
|
|
37
|
-
| `memory_remember` | Save a decision, pattern, bug, or knowledge |
|
|
38
|
-
| `memory_recall` | Search memory (use BEFORE reading files) |
|
|
39
|
-
| `memory_forget` | Remove an entry by key or id |
|
|
40
|
-
| `memory_stats` | View memory state |
|
|
41
|
-
| `memory_summary` | Save/retrieve file summaries |
|
|
42
|
-
|
|
43
|
-
## How it works
|
|
44
|
-
|
|
45
|
-
The agent stores entries in TOON format (Token-Oriented Object Notation):
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
version: 1
|
|
49
|
-
entries[2|]{id|category|key|content|file|tags|date}:
|
|
50
|
-
a1b2c3d4|decision|use-pandas|Use pandas for time series|indicators.py|python;analytics|2026-07-10
|
|
51
|
-
e5f6g7h8|bug|redis-pool|max_connections missing|redis.adapter.ts|redis;pool|2026-07-10
|
|
52
|
-
summaries:
|
|
53
|
-
path/to/file.py: Brief summary of what this file does
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Token savings
|
|
57
|
-
|
|
58
|
-
| Format | Tokens (10 entries) | vs JSON |
|
|
59
|
-
|--------|---------------------|---------|
|
|
60
|
-
| JSON | ~800 | baseline |
|
|
61
|
-
| YAML | ~650 | -19% |
|
|
62
|
-
| **TOON** | **~480** | **-40%** |
|
|
63
|
-
|
|
64
|
-
## License
|
|
65
|
-
|
|
66
|
-
MIT
|