grimoire-framework 1.4.1 → 1.5.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/.grimoire/install-manifest.yaml +2 -2
- package/bin/commands/context.js +319 -0
- package/bin/commands/memory.js +54 -6
- package/bin/grimoire-cli.js +3 -0
- package/package.json +1 -1
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
# - SHA256 hashes for change detection
|
|
8
8
|
# - File types for categorization
|
|
9
9
|
#
|
|
10
|
-
version: 1.
|
|
11
|
-
generated_at: "2026-02-22T17:
|
|
10
|
+
version: 1.5.0
|
|
11
|
+
generated_at: "2026-02-22T17:38:38.314Z"
|
|
12
12
|
generator: scripts/generate-install-manifest.js
|
|
13
13
|
file_count: 1011
|
|
14
14
|
files:
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grimoire context — Live Context Manager
|
|
3
|
+
*
|
|
4
|
+
* grimoire context update Regenera CONTEXT.md a partir de todos os .jsonl
|
|
5
|
+
* grimoire context show Exibe CONTEXT.md no terminal
|
|
6
|
+
* grimoire context clean Remove arquivos context/ gerados
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
|
|
15
|
+
// ── Paths ─────────────────────────────────────────────────────────────────────
|
|
16
|
+
function findGrimoireDir() {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const direct = path.join(cwd, '.grimoire');
|
|
19
|
+
const sub = path.join(cwd, 'grimoire', '.grimoire');
|
|
20
|
+
if (fs.existsSync(direct)) return direct;
|
|
21
|
+
if (fs.existsSync(sub)) return sub;
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getGlobalMemoryDir() {
|
|
26
|
+
const d = path.join(os.homedir(), '.grimoire', 'memory');
|
|
27
|
+
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
|
|
28
|
+
return d;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getContextDir(grimoireDir) {
|
|
32
|
+
const d = path.join(grimoireDir, 'context');
|
|
33
|
+
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
|
|
34
|
+
return d;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Load all memory entries ───────────────────────────────────────────────────
|
|
38
|
+
function loadAllEntries(grimoireDir, includeGlobal = true) {
|
|
39
|
+
const entries = [];
|
|
40
|
+
|
|
41
|
+
// Local sessions
|
|
42
|
+
const sessionsDir = path.join(grimoireDir, 'memory', 'sessions');
|
|
43
|
+
if (fs.existsSync(sessionsDir)) {
|
|
44
|
+
for (const f of fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl')).sort()) {
|
|
45
|
+
const raw = fs.readFileSync(path.join(sessionsDir, f), 'utf8');
|
|
46
|
+
for (const line of raw.split('\n').filter(l => l.trim())) {
|
|
47
|
+
try { entries.push({ ...JSON.parse(line), date: f.slice(0, 10), source: 'local' }); } catch (_) { }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Pinned (local)
|
|
53
|
+
const pinnedFile = path.join(grimoireDir, 'memory', 'pinned.jsonl');
|
|
54
|
+
if (fs.existsSync(pinnedFile)) {
|
|
55
|
+
const raw = fs.readFileSync(pinnedFile, 'utf8');
|
|
56
|
+
for (const line of raw.split('\n').filter(l => l.trim())) {
|
|
57
|
+
try { entries.push({ ...JSON.parse(line), source: 'pinned' }); } catch (_) { }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Global
|
|
62
|
+
if (includeGlobal) {
|
|
63
|
+
const globalFile = path.join(getGlobalMemoryDir(), 'global.jsonl');
|
|
64
|
+
if (fs.existsSync(globalFile)) {
|
|
65
|
+
const raw = fs.readFileSync(globalFile, 'utf8');
|
|
66
|
+
for (const line of raw.split('\n').filter(l => l.trim())) {
|
|
67
|
+
try { entries.push({ ...JSON.parse(line), source: 'global' }); } catch (_) { }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return entries;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Build Markdown files ──────────────────────────────────────────────────────
|
|
76
|
+
function buildContextMd(entries) {
|
|
77
|
+
const now = new Date().toISOString().split('T')[0];
|
|
78
|
+
const pinned = entries.filter(e => e.source === 'pinned');
|
|
79
|
+
const recent = entries.filter(e => e.source !== 'pinned').slice(-20).reverse();
|
|
80
|
+
const globals = entries.filter(e => e.source === 'global').slice(-10).reverse();
|
|
81
|
+
|
|
82
|
+
const lines = [];
|
|
83
|
+
lines.push(`# 🧠 Contexto do Projeto\n`);
|
|
84
|
+
lines.push(`> Auto-gerado em ${now} via \`grimoire context update\`\n`);
|
|
85
|
+
|
|
86
|
+
if (pinned.length) {
|
|
87
|
+
lines.push(`## 📌 Fixadas\n`);
|
|
88
|
+
pinned.forEach(e => lines.push(`- ${e.content}`));
|
|
89
|
+
lines.push('');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const decisions = recent.filter(e => e.tag === 'decisão' || e.tag === 'decisao' || e.tag === 'decision');
|
|
93
|
+
if (decisions.length) {
|
|
94
|
+
lines.push(`## ✅ Últimas Decisões\n`);
|
|
95
|
+
decisions.slice(0, 8).forEach(e => lines.push(`- **${e.date || ''}** ${e.content}`));
|
|
96
|
+
lines.push('');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const patterns = recent.filter(e => e.tag === 'padrão' || e.tag === 'padrao' || e.tag === 'pattern');
|
|
100
|
+
if (patterns.length) {
|
|
101
|
+
lines.push(`## 🔧 Padrões Estabelecidos\n`);
|
|
102
|
+
patterns.slice(0, 8).forEach(e => lines.push(`- **${e.date || ''}** ${e.content}`));
|
|
103
|
+
lines.push('');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const other = recent.filter(e => !['decisão', 'decisao', 'decision', 'padrão', 'padrao', 'pattern'].includes(e.tag));
|
|
107
|
+
if (other.length) {
|
|
108
|
+
lines.push(`## 📝 Entradas Recentes\n`);
|
|
109
|
+
other.slice(0, 10).forEach(e => {
|
|
110
|
+
const tag = e.tag ? ` \`#${e.tag}\`` : '';
|
|
111
|
+
const src = e.source === 'global' ? ' 🌐' : '';
|
|
112
|
+
lines.push(`- **${e.date || ''}** ${e.content}${tag}${src}`);
|
|
113
|
+
});
|
|
114
|
+
lines.push('');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (globals.length) {
|
|
118
|
+
lines.push(`## 🌐 Memória Global (cross-projeto)\n`);
|
|
119
|
+
globals.slice(0, 5).forEach(e => {
|
|
120
|
+
const tag = e.tag ? ` \`#${e.tag}\`` : '';
|
|
121
|
+
lines.push(`- ${e.content}${tag}`);
|
|
122
|
+
});
|
|
123
|
+
lines.push('');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return lines.join('\n');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function buildTagMd(entries, tag, title, emoji) {
|
|
130
|
+
const variants = [tag, tag.normalize('NFD').replace(/[\u0300-\u036f]/g, '')];
|
|
131
|
+
const filtered = entries.filter(e => variants.includes(e.tag)).slice(-30).reverse();
|
|
132
|
+
if (!filtered.length) return null;
|
|
133
|
+
|
|
134
|
+
const lines = [`# ${emoji} ${title}\n`, `> Auto-gerado pelo Grimoire\n`];
|
|
135
|
+
filtered.forEach(e => {
|
|
136
|
+
const src = e.source === 'global' ? ' 🌐' : '';
|
|
137
|
+
lines.push(`- **${e.date || ''}** ${e.content}${src}`);
|
|
138
|
+
});
|
|
139
|
+
return lines.join('\n');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Inject into GEMINI.md ─────────────────────────────────────────────────────
|
|
143
|
+
const MARKER_START = '<!-- grimoire-MANAGED-START: memory-context -->';
|
|
144
|
+
const MARKER_END = '<!-- grimoire-MANAGED-END: memory-context -->';
|
|
145
|
+
|
|
146
|
+
function buildGeminiBlock(entries) {
|
|
147
|
+
const now = new Date().toISOString().split('T')[0];
|
|
148
|
+
const pinned = entries.filter(e => e.source === 'pinned');
|
|
149
|
+
const recent = entries.filter(e => e.source !== 'pinned').slice(-15).reverse();
|
|
150
|
+
const decisions = recent.filter(e => ['decisão', 'decisao', 'decision'].includes(e.tag)).slice(0, 5);
|
|
151
|
+
const patterns = recent.filter(e => ['padrão', 'padrao', 'pattern'].includes(e.tag)).slice(0, 5);
|
|
152
|
+
|
|
153
|
+
const block = [];
|
|
154
|
+
block.push(MARKER_START);
|
|
155
|
+
block.push(`## 🧠 Contexto do Projeto (auto-gerado)\n`);
|
|
156
|
+
|
|
157
|
+
if (pinned.length) {
|
|
158
|
+
block.push(`### 📌 Fixadas\n`);
|
|
159
|
+
pinned.forEach(e => block.push(`- ${e.content}`));
|
|
160
|
+
block.push('');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (decisions.length) {
|
|
164
|
+
block.push(`### ✅ Últimas decisões\n`);
|
|
165
|
+
decisions.forEach(e => block.push(`- ${e.date}: ${e.content}`));
|
|
166
|
+
block.push('');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (patterns.length) {
|
|
170
|
+
block.push(`### 🔧 Padrões\n`);
|
|
171
|
+
patterns.forEach(e => block.push(`- ${e.date}: ${e.content}`));
|
|
172
|
+
block.push('');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Any other recent entries without special tag
|
|
176
|
+
const others = recent.filter(e => !['decisão', 'decisao', 'decision', 'padrão', 'padrao', 'pattern'].includes(e.tag)).slice(0, 5);
|
|
177
|
+
if (others.length) {
|
|
178
|
+
block.push(`### 📝 Recentes\n`);
|
|
179
|
+
others.forEach(e => {
|
|
180
|
+
const tag = e.tag ? ` [#${e.tag}]` : '';
|
|
181
|
+
block.push(`- ${e.date}: ${e.content}${tag}`);
|
|
182
|
+
});
|
|
183
|
+
block.push('');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
block.push(`> _Atualizado em ${now}. Use \`grimoire context update\` para regenerar._`);
|
|
187
|
+
block.push(MARKER_END);
|
|
188
|
+
return block.join('\n');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function injectIntoGeminiMd(grimoireDir, entries) {
|
|
192
|
+
// GEMINI.md can be at project root or parent
|
|
193
|
+
const cwd = process.cwd();
|
|
194
|
+
const candidates = [
|
|
195
|
+
path.join(cwd, 'GEMINI.md'),
|
|
196
|
+
path.join(path.dirname(grimoireDir), 'GEMINI.md'),
|
|
197
|
+
];
|
|
198
|
+
const geminiPath = candidates.find(p => fs.existsSync(p));
|
|
199
|
+
if (!geminiPath) return false;
|
|
200
|
+
|
|
201
|
+
const original = fs.readFileSync(geminiPath, 'utf8');
|
|
202
|
+
const newBlock = buildGeminiBlock(entries);
|
|
203
|
+
|
|
204
|
+
let updated;
|
|
205
|
+
if (original.includes(MARKER_START)) {
|
|
206
|
+
// Replace existing block
|
|
207
|
+
const re = new RegExp(`${regEscape(MARKER_START)}[\\s\\S]*?${regEscape(MARKER_END)}`, 'g');
|
|
208
|
+
updated = original.replace(re, newBlock);
|
|
209
|
+
} else {
|
|
210
|
+
// Append before last ---
|
|
211
|
+
const lastHr = original.lastIndexOf('\n---');
|
|
212
|
+
if (lastHr !== -1) {
|
|
213
|
+
updated = original.slice(0, lastHr) + '\n\n' + newBlock + '\n' + original.slice(lastHr);
|
|
214
|
+
} else {
|
|
215
|
+
updated = original + '\n\n' + newBlock + '\n';
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (updated !== original) {
|
|
220
|
+
fs.writeFileSync(geminiPath, updated, 'utf8');
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function regEscape(s) {
|
|
227
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── Core update logic ─────────────────────────────────────────────────────────
|
|
231
|
+
function doUpdate(grimoireDir, silent = false) {
|
|
232
|
+
const contextDir = getContextDir(grimoireDir);
|
|
233
|
+
const entries = loadAllEntries(grimoireDir, true);
|
|
234
|
+
|
|
235
|
+
// CONTEXT.md
|
|
236
|
+
fs.writeFileSync(path.join(contextDir, 'CONTEXT.md'), buildContextMd(entries), 'utf8');
|
|
237
|
+
|
|
238
|
+
// decisions.md
|
|
239
|
+
const dec = buildTagMd(entries, 'decisão', 'Decisões', '✅') ||
|
|
240
|
+
buildTagMd(entries, 'decisao', 'Decisões', '✅') ||
|
|
241
|
+
buildTagMd(entries, 'decision', 'Decisões', '✅');
|
|
242
|
+
if (dec) fs.writeFileSync(path.join(contextDir, 'decisions.md'), dec, 'utf8');
|
|
243
|
+
|
|
244
|
+
// patterns.md
|
|
245
|
+
const pat = buildTagMd(entries, 'padrão', 'Padrões', '🔧') ||
|
|
246
|
+
buildTagMd(entries, 'padrao', 'Padrões', '🔧') ||
|
|
247
|
+
buildTagMd(entries, 'pattern', 'Padrões', '🔧');
|
|
248
|
+
if (pat) fs.writeFileSync(path.join(contextDir, 'patterns.md'), pat, 'utf8');
|
|
249
|
+
|
|
250
|
+
// pinned.md
|
|
251
|
+
const pinned = entries.filter(e => e.source === 'pinned');
|
|
252
|
+
if (pinned.length) {
|
|
253
|
+
const pinnedLines = ['# 📌 Memórias Fixadas\n', '> Estas entradas são sempre incluídas no contexto dos agentes.\n'];
|
|
254
|
+
pinned.forEach(e => pinnedLines.push(`- ${e.content}`));
|
|
255
|
+
fs.writeFileSync(path.join(contextDir, 'pinned.md'), pinnedLines.join('\n'), 'utf8');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Inject GEMINI.md
|
|
259
|
+
const injected = injectIntoGeminiMd(grimoireDir, entries);
|
|
260
|
+
|
|
261
|
+
if (!silent) {
|
|
262
|
+
console.log('\n✅ Contexto atualizado!\n');
|
|
263
|
+
console.log(` 📄 .grimoire/context/CONTEXT.md`);
|
|
264
|
+
if (dec) console.log(` ✅ .grimoire/context/decisions.md`);
|
|
265
|
+
if (pat) console.log(` 🔧 .grimoire/context/patterns.md`);
|
|
266
|
+
if (pinned.length) console.log(` 📌 .grimoire/context/pinned.md`);
|
|
267
|
+
if (injected) console.log(` 🔮 GEMINI.md atualizado com bloco memory-context`);
|
|
268
|
+
console.log(`\n ${entries.length} entradas indexadas`);
|
|
269
|
+
console.log(` grimoire context show ← ver o resultado\n`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { entries, injected };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ── run ───────────────────────────────────────────────────────────────────────
|
|
276
|
+
function run(args) {
|
|
277
|
+
const sub = args[0] || 'update';
|
|
278
|
+
|
|
279
|
+
const grimoireDir = findGrimoireDir();
|
|
280
|
+
if (!grimoireDir) {
|
|
281
|
+
console.error('❌ .grimoire/ not found. Run: npx grimoire-framework install');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
switch (sub) {
|
|
286
|
+
case 'update':
|
|
287
|
+
doUpdate(grimoireDir);
|
|
288
|
+
break;
|
|
289
|
+
|
|
290
|
+
case 'show': {
|
|
291
|
+
const contextFile = path.join(grimoireDir, 'context', 'CONTEXT.md');
|
|
292
|
+
if (!fs.existsSync(contextFile)) {
|
|
293
|
+
console.log('\n⚠️ Contexto ainda não gerado. Rode: grimoire context update\n');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
console.log('\n' + fs.readFileSync(contextFile, 'utf8'));
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
case 'clean': {
|
|
301
|
+
const contextDir = path.join(grimoireDir, 'context');
|
|
302
|
+
if (fs.existsSync(contextDir)) {
|
|
303
|
+
fs.readdirSync(contextDir).forEach(f => fs.unlinkSync(path.join(contextDir, f)));
|
|
304
|
+
console.log('\n✅ Arquivos de contexto removidos (.jsonl preservados)\n');
|
|
305
|
+
} else {
|
|
306
|
+
console.log('\nℹ️ Nada para limpar.\n');
|
|
307
|
+
}
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
default:
|
|
312
|
+
console.log('\nUsage:\n');
|
|
313
|
+
console.log(' grimoire context update Regenera CONTEXT.md + injeta GEMINI.md');
|
|
314
|
+
console.log(' grimoire context show Exibe CONTEXT.md');
|
|
315
|
+
console.log(' grimoire context clean Remove arquivos gerados\n');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
module.exports = { run, doUpdate, loadAllEntries, getGlobalMemoryDir };
|
package/bin/commands/memory.js
CHANGED
|
@@ -23,7 +23,8 @@ async function run(args) {
|
|
|
23
23
|
baseDir = path.join(baseDir, 'grimoire');
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const grimoireDir = path.join(baseDir, '.grimoire');
|
|
27
|
+
const memoryDir = path.join(grimoireDir, 'memory');
|
|
27
28
|
|
|
28
29
|
if (!existsSync(memoryDir)) {
|
|
29
30
|
console.error('❌ Memory system not found. Run "grimoire install" first.');
|
|
@@ -33,7 +34,10 @@ async function run(args) {
|
|
|
33
34
|
try {
|
|
34
35
|
switch (subCommand) {
|
|
35
36
|
case 'save':
|
|
36
|
-
await saveMemory(args.slice(1), memoryDir);
|
|
37
|
+
await saveMemory(args.slice(1), memoryDir, grimoireDir);
|
|
38
|
+
break;
|
|
39
|
+
case 'pin':
|
|
40
|
+
await pinMemory(args.slice(1), memoryDir, grimoireDir);
|
|
37
41
|
break;
|
|
38
42
|
case 'list-tags':
|
|
39
43
|
await listTags(memoryDir);
|
|
@@ -66,10 +70,18 @@ async function run(args) {
|
|
|
66
70
|
}
|
|
67
71
|
}
|
|
68
72
|
|
|
73
|
+
// ── Auto-update context (silent) ────────────────────────────────────────────
|
|
74
|
+
function silentContextUpdate(grimoireDir) {
|
|
75
|
+
try {
|
|
76
|
+
const { doUpdate } = require('./context');
|
|
77
|
+
doUpdate(grimoireDir, true); // silent = true
|
|
78
|
+
} catch (_) { } // never block memory save
|
|
79
|
+
}
|
|
80
|
+
|
|
69
81
|
/**
|
|
70
82
|
* Saves memory using JSONL append (O(1) complexity)
|
|
71
83
|
*/
|
|
72
|
-
async function saveMemory(args, memoryDir) {
|
|
84
|
+
async function saveMemory(args, memoryDir, grimoireDir) {
|
|
73
85
|
// Extract --tag value if present
|
|
74
86
|
const tagIdx = args.findIndex(a => a === '--tag' || a.startsWith('--tag='));
|
|
75
87
|
let tag = null;
|
|
@@ -98,6 +110,10 @@ async function saveMemory(args, memoryDir) {
|
|
|
98
110
|
}
|
|
99
111
|
}
|
|
100
112
|
|
|
113
|
+
// Extract --global flag
|
|
114
|
+
const isGlobal = filteredArgs.includes('--global');
|
|
115
|
+
filteredArgs = filteredArgs.filter(a => a !== '--global');
|
|
116
|
+
|
|
101
117
|
const content = filteredArgs.join(' ');
|
|
102
118
|
if (!content) {
|
|
103
119
|
console.error('❌ Please provide content to save.');
|
|
@@ -105,8 +121,6 @@ async function saveMemory(args, memoryDir) {
|
|
|
105
121
|
}
|
|
106
122
|
|
|
107
123
|
const today = new Date().toISOString().split('T')[0];
|
|
108
|
-
const sessionFile = path.join(memoryDir, 'sessions', `${today}.jsonl`);
|
|
109
|
-
|
|
110
124
|
const entry = {
|
|
111
125
|
timestamp: new Date().toISOString(),
|
|
112
126
|
content: content,
|
|
@@ -114,6 +128,21 @@ async function saveMemory(args, memoryDir) {
|
|
|
114
128
|
...(storyId ? { story: storyId } : {}),
|
|
115
129
|
};
|
|
116
130
|
|
|
131
|
+
if (isGlobal) {
|
|
132
|
+
// Save to global memory
|
|
133
|
+
const { getGlobalMemoryDir } = require('./context');
|
|
134
|
+
const globalDir = getGlobalMemoryDir();
|
|
135
|
+
const globalFile = require('path').join(globalDir, 'global.jsonl');
|
|
136
|
+
const fss = require('fs');
|
|
137
|
+
fss.appendFileSync(globalFile, JSON.stringify({ ...entry, source: 'global' }) + '\n', 'utf8');
|
|
138
|
+
const tagLabel = tag ? ` [#${tag}]` : '';
|
|
139
|
+
console.log(`✅ Memory saved globally 🌐${tagLabel}`);
|
|
140
|
+
silentContextUpdate(grimoireDir);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const sessionFile = require('path').join(memoryDir, 'sessions', `${today}.jsonl`);
|
|
145
|
+
|
|
117
146
|
// Ensure file exists for lockfile
|
|
118
147
|
if (!existsSync(sessionFile)) {
|
|
119
148
|
await fs.writeFile(sessionFile, '', 'utf8');
|
|
@@ -134,7 +163,10 @@ async function saveMemory(args, memoryDir) {
|
|
|
134
163
|
// Append entry as a new line in JSONL format
|
|
135
164
|
await fs.appendFile(sessionFile, JSON.stringify(entry) + '\n', 'utf8');
|
|
136
165
|
const tagLabel = tag ? ` [#${tag}]` : '';
|
|
137
|
-
|
|
166
|
+
const storyLabel = storyId ? ` [${storyId}]` : '';
|
|
167
|
+
console.log(`✅ Memory saved${tagLabel}${storyLabel}`);
|
|
168
|
+
// Auto-update .grimoire/context/ silently
|
|
169
|
+
silentContextUpdate(grimoireDir);
|
|
138
170
|
} catch (err) {
|
|
139
171
|
console.error(`❌ Failed to acquire lock for memory file: ${err.message}`);
|
|
140
172
|
} finally {
|
|
@@ -142,6 +174,22 @@ async function saveMemory(args, memoryDir) {
|
|
|
142
174
|
}
|
|
143
175
|
}
|
|
144
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Pins a memory entry permanently in pinned.jsonl
|
|
179
|
+
*/
|
|
180
|
+
async function pinMemory(args, memoryDir, grimoireDir) {
|
|
181
|
+
const content = args.filter(a => !a.startsWith('-')).join(' ');
|
|
182
|
+
if (!content) {
|
|
183
|
+
console.log('Usage: grimoire memory pin "texto"\n');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const pinnedFile = require('path').join(memoryDir, 'pinned.jsonl');
|
|
187
|
+
const entry = { timestamp: new Date().toISOString(), content, source: 'pinned' };
|
|
188
|
+
require('fs').appendFileSync(pinnedFile, JSON.stringify(entry) + '\n', 'utf8');
|
|
189
|
+
console.log(`📌 Memoria fixada: "${content}"`);
|
|
190
|
+
silentContextUpdate(grimoireDir);
|
|
191
|
+
}
|
|
192
|
+
|
|
145
193
|
/**
|
|
146
194
|
* Lists sessions scanning the directory
|
|
147
195
|
*/
|
package/bin/grimoire-cli.js
CHANGED