@shahmilsaari/memory-core 0.2.15 → 0.2.16
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 +54 -4
- package/dist/{chunk-DUUQHRIB.js → chunk-WUL7HLAA.js} +26 -11
- package/dist/cli.js +69 -6
- package/dist/{db-VLOR7L6Q.js → db-MF3VKVKH.js} +1 -1
- package/package.json +2 -2
- package/templates/AGENTS.md.hbs +1 -1
- package/templates/AI_RULES.md.hbs +1 -2
- package/templates/ARCHITECTURE.md.hbs +1 -1
- package/templates/CLAUDE.md.hbs +1 -1
- package/templates/DEVIN.md.hbs +1 -1
- package/templates/PROJECT_MEMORY.md.hbs +1 -4
- package/templates/amazonq-guidelines.md.hbs +1 -1
- package/templates/clinerules.hbs +1 -1
- package/templates/copilot-instructions.md.hbs +1 -1
- package/templates/cursor-rule.mdc.hbs +1 -1
- package/templates/cursorrules.hbs +1 -1
- package/templates/gemini-styleguide.md.hbs +1 -1
- package/templates/jetbrains-ai.md.hbs +1 -1
- package/templates/roo-rule.md.hbs +1 -1
- package/templates/windsurfrules.hbs +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ You decide the architecture. memory-core remembers it. Every AI tool — Copilot
|
|
|
8
8
|
npx @shahmilsaari/memory-core init
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Fully local or cloud — your choice. **v0.2.
|
|
11
|
+
Fully local or cloud — your choice. **v0.2.16**
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -133,6 +133,7 @@ CREATE TABLE IF NOT EXISTS memories (
|
|
|
133
133
|
title TEXT,
|
|
134
134
|
content TEXT NOT NULL,
|
|
135
135
|
reason TEXT,
|
|
136
|
+
context JSONB NOT NULL DEFAULT '{}',
|
|
136
137
|
tags TEXT[] DEFAULT '{}',
|
|
137
138
|
embedding vector(768),
|
|
138
139
|
created_at TIMESTAMP DEFAULT now()
|
|
@@ -240,6 +241,9 @@ npx @shahmilsaari/memory-core remember "Use DTOs for all API responses" \
|
|
|
240
241
|
--type rule \
|
|
241
242
|
--scope global \
|
|
242
243
|
--reason "Raw DB entities expose internal schema and sensitive fields" \
|
|
244
|
+
--applies-to "controllers,API responses" \
|
|
245
|
+
--avoid-when "internal domain transformations" \
|
|
246
|
+
--example "return UserResponseDto instead of UserEntity" \
|
|
243
247
|
--tags "api,dto"
|
|
244
248
|
```
|
|
245
249
|
|
|
@@ -248,8 +252,14 @@ npx @shahmilsaari/memory-core remember "Use DTOs for all API responses" \
|
|
|
248
252
|
| `--type` | `decision` `rule` `pattern` `note` | `decision` |
|
|
249
253
|
| `--scope` | `global` `project` | `project` |
|
|
250
254
|
| `--reason` | any text | asked interactively |
|
|
255
|
+
| `--applies-to` | comma-separated use cases | none |
|
|
256
|
+
| `--avoid-when` | comma-separated exceptions | none |
|
|
257
|
+
| `--example` | comma-separated examples | none |
|
|
258
|
+
| `--source` | source note, ticket, or review | none |
|
|
251
259
|
| `--tags` | comma-separated | none |
|
|
252
260
|
|
|
261
|
+
Structured context is stored in the database as JSON and rendered into generated agent files as `Why`, `Use when`, `Avoid when`, and `Examples`, which gives AI agents clearer guidance than a flat rule sentence.
|
|
262
|
+
|
|
253
263
|
---
|
|
254
264
|
|
|
255
265
|
### `search` — Find a rule or decision
|
|
@@ -306,6 +316,7 @@ Options:
|
|
|
306
316
|
```bash
|
|
307
317
|
npx @shahmilsaari/memory-core watch --path src/ # watch a specific folder only
|
|
308
318
|
npx @shahmilsaari/memory-core watch --verbose # show extra details
|
|
319
|
+
npx @shahmilsaari/memory-core watch --debug # show prompt, diff, and raw model output
|
|
309
320
|
```
|
|
310
321
|
|
|
311
322
|
Only checks source files — ignores `node_modules`, `dist`, config files, JSON, etc.
|
|
@@ -317,9 +328,11 @@ Only checks source files — ignores `node_modules`, `dist`, config files, JSON,
|
|
|
317
328
|
```bash
|
|
318
329
|
npx @shahmilsaari/memory-core check --staged # check staged files
|
|
319
330
|
npx @shahmilsaari/memory-core check --staged --verbose # with extra detail
|
|
331
|
+
npx @shahmilsaari/memory-core check --staged --debug # show prompt, diff, and raw model output
|
|
332
|
+
npx @shahmilsaari/memory-core check --ci # CI mode using memories.json
|
|
320
333
|
```
|
|
321
334
|
|
|
322
|
-
|
|
335
|
+
`--staged` is the same path used by the pre-commit hook. `--ci` reads `memories.json` and uses a deterministic CI-friendly diff check, so pull requests can enforce rules without a local database or Ollama setup.
|
|
323
336
|
|
|
324
337
|
---
|
|
325
338
|
|
|
@@ -398,19 +411,41 @@ npx @shahmilsaari/memory-core list --limit 50
|
|
|
398
411
|
|
|
399
412
|
```bash
|
|
400
413
|
npx @shahmilsaari/memory-core remove <id>
|
|
414
|
+
npx @shahmilsaari/memory-core remove <id> --no-sync
|
|
401
415
|
```
|
|
402
416
|
|
|
403
417
|
Deletes a memory by its ID. Get the ID from `list` or `search`.
|
|
404
418
|
|
|
405
419
|
---
|
|
406
420
|
|
|
421
|
+
### `forget` — Bulk-delete memories
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
npx @shahmilsaari/memory-core forget --tag nextjs --scope global
|
|
425
|
+
npx @shahmilsaari/memory-core forget --type ignore
|
|
426
|
+
npx @shahmilsaari/memory-core forget --arch nuxt
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Deletes memories by filter. At least one filter is required, so an empty `forget` command cannot wipe the database by accident.
|
|
430
|
+
|
|
431
|
+
| Flag | What it does |
|
|
432
|
+
|---|---|
|
|
433
|
+
| `--tag <tag>` | Delete memories with a tag |
|
|
434
|
+
| `--scope <scope>` | Filter by scope: `global` `project` |
|
|
435
|
+
| `--type <type>` | Filter by type |
|
|
436
|
+
| `--arch <architecture>` | Filter by architecture profile |
|
|
437
|
+
| `--no-sync` | Skip automatic agent file regeneration |
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
407
441
|
### `edit` — Edit a memory
|
|
408
442
|
|
|
409
443
|
```bash
|
|
410
444
|
npx @shahmilsaari/memory-core edit <id>
|
|
445
|
+
npx @shahmilsaari/memory-core edit <id> --no-sync
|
|
411
446
|
```
|
|
412
447
|
|
|
413
|
-
Opens the memory interactively so you can update its content, reason, tags, type, or scope.
|
|
448
|
+
Opens the memory interactively so you can update its content, reason, structured context, tags, type, or scope.
|
|
414
449
|
|
|
415
450
|
---
|
|
416
451
|
|
|
@@ -431,6 +466,7 @@ Exports all memories from the database to `memories.json` in the project root (o
|
|
|
431
466
|
npx @shahmilsaari/memory-core import
|
|
432
467
|
npx @shahmilsaari/memory-core import --file path/to/my-rules.json
|
|
433
468
|
npx @shahmilsaari/memory-core import --url https://example.com/team-rules.json
|
|
469
|
+
npx @shahmilsaari/memory-core import --no-sync
|
|
434
470
|
```
|
|
435
471
|
|
|
436
472
|
Imports memories into the local database. Skips duplicates by content hash — safe to run more than once.
|
|
@@ -439,6 +475,7 @@ Imports memories into the local database. Skips duplicates by content hash — s
|
|
|
439
475
|
|---|---|
|
|
440
476
|
| `--file <path>` | Import from a custom local file path |
|
|
441
477
|
| `--url <url>` | Import from a remote URL |
|
|
478
|
+
| `--no-sync` | Skip automatic agent file regeneration |
|
|
442
479
|
|
|
443
480
|
---
|
|
444
481
|
|
|
@@ -453,8 +490,21 @@ Saves a pattern so the hook and watcher never flag it again. Useful for intentio
|
|
|
453
490
|
```bash
|
|
454
491
|
npx @shahmilsaari/memory-core ignore --list # show all saved ignore patterns
|
|
455
492
|
npx @shahmilsaari/memory-core ignore --remove <id> # delete an ignore pattern
|
|
493
|
+
npx @shahmilsaari/memory-core ignore "..." --no-sync # save without regenerating agent files
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
### `allow` — Allow intentional project patterns
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
npx @shahmilsaari/memory-core allow "generated by sqlc"
|
|
502
|
+
npx @shahmilsaari/memory-core allow --list
|
|
503
|
+
npx @shahmilsaari/memory-core allow --remove "generated by sqlc"
|
|
456
504
|
```
|
|
457
505
|
|
|
506
|
+
Stores lightweight per-project allowlist strings in `.memory-core.json`. Hook and watch checks treat these patterns as intentional before surfacing violations.
|
|
507
|
+
|
|
458
508
|
---
|
|
459
509
|
|
|
460
510
|
### `ci-setup` — GitHub Actions integration
|
|
@@ -463,7 +513,7 @@ npx @shahmilsaari/memory-core ignore --remove <id> # delete an ignore pattern
|
|
|
463
513
|
npx @shahmilsaari/memory-core ci-setup
|
|
464
514
|
```
|
|
465
515
|
|
|
466
|
-
Generates `.github/workflows/memory-core.yml`. Adds a PR check that runs
|
|
516
|
+
Generates `.github/workflows/memory-core.yml`. Adds a PR check that runs `npx @shahmilsaari/memory-core check --ci`, using `memories.json` so CI can enforce architecture rules without a project-local database.
|
|
467
517
|
|
|
468
518
|
---
|
|
469
519
|
|
|
@@ -28,6 +28,7 @@ async function runMigrations() {
|
|
|
28
28
|
await client.query("BEGIN");
|
|
29
29
|
await client.query(`ALTER TABLE memories ADD COLUMN IF NOT EXISTS reason TEXT`);
|
|
30
30
|
await client.query(`ALTER TABLE memories ADD COLUMN IF NOT EXISTS content_hash TEXT`);
|
|
31
|
+
await client.query(`ALTER TABLE memories ADD COLUMN IF NOT EXISTS context JSONB NOT NULL DEFAULT '{}'::jsonb`);
|
|
31
32
|
await client.query(
|
|
32
33
|
`UPDATE memories
|
|
33
34
|
SET content_hash = md5(trim(content))
|
|
@@ -45,12 +46,24 @@ async function runMigrations() {
|
|
|
45
46
|
}
|
|
46
47
|
async function saveMemory(memory) {
|
|
47
48
|
await runMigrations();
|
|
48
|
-
const { type, scope, architecture, projectName, title, content, reason, tags, embedding } = memory;
|
|
49
|
+
const { type, scope, architecture, projectName, title, content, reason, context, tags, embedding } = memory;
|
|
49
50
|
const contentHash = hashMemoryContent(content);
|
|
50
51
|
await getPool().query(
|
|
51
|
-
`INSERT INTO memories (type, scope, architecture, project_name, title, content, reason, tags, embedding, content_hash)
|
|
52
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
|
53
|
-
[
|
|
52
|
+
`INSERT INTO memories (type, scope, architecture, project_name, title, content, reason, context, tags, embedding, content_hash)
|
|
53
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, $10, $11)`,
|
|
54
|
+
[
|
|
55
|
+
type,
|
|
56
|
+
scope,
|
|
57
|
+
architecture ?? null,
|
|
58
|
+
projectName ?? null,
|
|
59
|
+
title ?? null,
|
|
60
|
+
content,
|
|
61
|
+
reason ?? null,
|
|
62
|
+
JSON.stringify(context ?? {}),
|
|
63
|
+
tags ?? [],
|
|
64
|
+
`[${embedding.join(",")}]`,
|
|
65
|
+
contentHash
|
|
66
|
+
]
|
|
54
67
|
);
|
|
55
68
|
}
|
|
56
69
|
async function upsertMemory(memory) {
|
|
@@ -101,7 +114,7 @@ async function listMemories(filters = {}) {
|
|
|
101
114
|
const limit = filters.limit ?? 200;
|
|
102
115
|
params.push(limit);
|
|
103
116
|
const result = await getPool().query(
|
|
104
|
-
`SELECT id, type, scope, architecture, project_name, title, content, reason, tags, content_hash
|
|
117
|
+
`SELECT id, type, scope, architecture, project_name, title, content, reason, context, tags, content_hash
|
|
105
118
|
FROM memories
|
|
106
119
|
${where.length ? `WHERE ${where.join(" AND ")}` : ""}
|
|
107
120
|
ORDER BY id ASC
|
|
@@ -113,7 +126,7 @@ async function listMemories(filters = {}) {
|
|
|
113
126
|
async function getMemory(id) {
|
|
114
127
|
await runMigrations();
|
|
115
128
|
const result = await getPool().query(
|
|
116
|
-
`SELECT id, type, scope, architecture, project_name, title, content, reason, tags, content_hash
|
|
129
|
+
`SELECT id, type, scope, architecture, project_name, title, content, reason, context, tags, content_hash
|
|
117
130
|
FROM memories
|
|
118
131
|
WHERE id = $1`,
|
|
119
132
|
[id]
|
|
@@ -173,11 +186,12 @@ async function updateMemory(id, patch) {
|
|
|
173
186
|
title = $4,
|
|
174
187
|
content = $5,
|
|
175
188
|
reason = $6,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
189
|
+
context = $7::jsonb,
|
|
190
|
+
tags = $8,
|
|
191
|
+
content_hash = $9,
|
|
192
|
+
embedding = COALESCE($10::vector, embedding)
|
|
179
193
|
WHERE id = $1
|
|
180
|
-
RETURNING id, type, scope, architecture, project_name, title, content, reason, tags, content_hash`,
|
|
194
|
+
RETURNING id, type, scope, architecture, project_name, title, content, reason, context, tags, content_hash`,
|
|
181
195
|
[
|
|
182
196
|
id,
|
|
183
197
|
patch.type ?? current.type,
|
|
@@ -185,6 +199,7 @@ async function updateMemory(id, patch) {
|
|
|
185
199
|
patch.title ?? current.title ?? null,
|
|
186
200
|
content,
|
|
187
201
|
patch.reason ?? current.reason ?? null,
|
|
202
|
+
JSON.stringify(patch.context ?? current.context ?? {}),
|
|
188
203
|
patch.tags ?? current.tags ?? [],
|
|
189
204
|
contentHash,
|
|
190
205
|
embedding
|
|
@@ -211,7 +226,7 @@ async function searchMemories(embedding, architectures, limit = 10) {
|
|
|
211
226
|
await client.query("BEGIN");
|
|
212
227
|
await client.query("SET LOCAL ivfflat.probes = 10");
|
|
213
228
|
const result = await client.query(
|
|
214
|
-
`SELECT id, type, scope, architecture, project_name, title, content, reason, tags,
|
|
229
|
+
`SELECT id, type, scope, architecture, project_name, title, content, reason, context, tags,
|
|
215
230
|
1 - (embedding <=> $1) AS similarity
|
|
216
231
|
FROM memories
|
|
217
232
|
${whereClause}
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
searchMemories,
|
|
15
15
|
updateMemory,
|
|
16
16
|
upsertMemory
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-WUL7HLAA.js";
|
|
18
18
|
import "./chunk-KSLFLWB4.js";
|
|
19
19
|
|
|
20
20
|
// src/cli.ts
|
|
@@ -307,6 +307,23 @@ Handlebars.registerHelper(
|
|
|
307
307
|
(arr) => Array.isArray(arr) ? arr.map((i, idx) => `${idx + 1}. ${i}`).join("\n") : ""
|
|
308
308
|
);
|
|
309
309
|
Handlebars.registerHelper("json", (val) => JSON.stringify(val, null, 2));
|
|
310
|
+
Handlebars.registerHelper("memoryBlock", (memory) => {
|
|
311
|
+
const meta = [memory.type, memory.architecture].filter(Boolean).join(" \xB7 ");
|
|
312
|
+
const label = memory.title ? `${memory.title}: ${memory.content}` : memory.content;
|
|
313
|
+
const lines = [`- [${meta || "memory"}] ${label}`];
|
|
314
|
+
if (memory.reason) lines.push(` Why: ${memory.reason}`);
|
|
315
|
+
if (memory.context?.appliesTo?.length) lines.push(` Use when: ${memory.context.appliesTo.join("; ")}`);
|
|
316
|
+
if (memory.context?.avoidWhen?.length) lines.push(` Avoid when: ${memory.context.avoidWhen.join("; ")}`);
|
|
317
|
+
if (memory.context?.examples?.length) {
|
|
318
|
+
lines.push(" Examples:");
|
|
319
|
+
for (const example of memory.context.examples) lines.push(` - ${example}`);
|
|
320
|
+
}
|
|
321
|
+
if (memory.tags?.length) lines.push(` Tags: ${memory.tags.join(", ")}`);
|
|
322
|
+
if (memory.project_name || memory.context?.source) {
|
|
323
|
+
lines.push(` Source: ${memory.context?.source ?? memory.project_name}`);
|
|
324
|
+
}
|
|
325
|
+
return new Handlebars.SafeString(lines.join("\n"));
|
|
326
|
+
});
|
|
310
327
|
function loadProfile(name) {
|
|
311
328
|
const profilePath = join2(PKG_ROOT, "profiles", `${name}.yml`);
|
|
312
329
|
if (!existsSync2(profilePath)) throw new Error(`Profile not found: ${name}`);
|
|
@@ -919,9 +936,28 @@ function toPortableMemory(memory) {
|
|
|
919
936
|
title: memory.title,
|
|
920
937
|
content: memory.content,
|
|
921
938
|
reason: memory.reason,
|
|
939
|
+
context: memory.context,
|
|
922
940
|
tags: memory.tags ?? []
|
|
923
941
|
};
|
|
924
942
|
}
|
|
943
|
+
function normalizeStringArray(value) {
|
|
944
|
+
if (!Array.isArray(value)) return void 0;
|
|
945
|
+
const entries = value.filter((entry) => typeof entry === "string" && entry.trim() !== "");
|
|
946
|
+
return entries.length ? entries : void 0;
|
|
947
|
+
}
|
|
948
|
+
function parseContext(value) {
|
|
949
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
950
|
+
const record = value;
|
|
951
|
+
const context = {};
|
|
952
|
+
const appliesTo = normalizeStringArray(record.appliesTo);
|
|
953
|
+
const avoidWhen = normalizeStringArray(record.avoidWhen);
|
|
954
|
+
const examples = normalizeStringArray(record.examples);
|
|
955
|
+
if (appliesTo) context.appliesTo = appliesTo;
|
|
956
|
+
if (avoidWhen) context.avoidWhen = avoidWhen;
|
|
957
|
+
if (examples) context.examples = examples;
|
|
958
|
+
if (typeof record.source === "string" && record.source.trim() !== "") context.source = record.source;
|
|
959
|
+
return Object.keys(context).length ? context : void 0;
|
|
960
|
+
}
|
|
925
961
|
function writeMemoryFile(memories, cwd = process.cwd()) {
|
|
926
962
|
const path = join3(cwd, MEMORY_FILE);
|
|
927
963
|
writeFileSync2(path, JSON.stringify(memories, null, 2) + "\n", "utf-8");
|
|
@@ -960,6 +996,7 @@ function parseMemoryFile(raw) {
|
|
|
960
996
|
title: typeof record.title === "string" ? record.title : void 0,
|
|
961
997
|
content: record.content,
|
|
962
998
|
reason: typeof record.reason === "string" ? record.reason : void 0,
|
|
999
|
+
context: parseContext(record.context),
|
|
963
1000
|
tags: Array.isArray(record.tags) ? record.tags.filter((tag) => typeof tag === "string") : []
|
|
964
1001
|
};
|
|
965
1002
|
});
|
|
@@ -1018,7 +1055,7 @@ async function promptToSaveViolations(violations) {
|
|
|
1018
1055
|
default: selected.reason ?? selected.issue ?? ""
|
|
1019
1056
|
});
|
|
1020
1057
|
const { embed: embed2 } = await import("./embedding-PAYD2JYW.js");
|
|
1021
|
-
const { upsertMemory: upsertMemory2 } = await import("./db-
|
|
1058
|
+
const { upsertMemory: upsertMemory2 } = await import("./db-MF3VKVKH.js");
|
|
1022
1059
|
await upsertMemory2({
|
|
1023
1060
|
type: "rule",
|
|
1024
1061
|
scope: "project",
|
|
@@ -1035,7 +1072,7 @@ async function promptToSaveViolations(violations) {
|
|
|
1035
1072
|
}
|
|
1036
1073
|
async function loadIgnorePatterns() {
|
|
1037
1074
|
try {
|
|
1038
|
-
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-
|
|
1075
|
+
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-MF3VKVKH.js");
|
|
1039
1076
|
const ignores = await listMemories2({ type: "ignore", limit: 1e3 });
|
|
1040
1077
|
await closePool2();
|
|
1041
1078
|
return ignores.map((ignore) => ignore.content);
|
|
@@ -1661,7 +1698,7 @@ ${JSON.stringify(violations, null, 2)}`;
|
|
|
1661
1698
|
}
|
|
1662
1699
|
async function loadIgnorePatterns2() {
|
|
1663
1700
|
try {
|
|
1664
|
-
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-
|
|
1701
|
+
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-MF3VKVKH.js");
|
|
1665
1702
|
const ignores = await listMemories2({ type: "ignore", limit: 1e3 });
|
|
1666
1703
|
await closePool2();
|
|
1667
1704
|
return ignores.map((ignore) => ignore.content);
|
|
@@ -2131,6 +2168,21 @@ function updateProjectConfig(mutator) {
|
|
|
2131
2168
|
function parseTags(tags) {
|
|
2132
2169
|
return tags ? tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
2133
2170
|
}
|
|
2171
|
+
function parseList(value) {
|
|
2172
|
+
const entries = value?.split(",").map((entry) => entry.trim()).filter(Boolean) ?? [];
|
|
2173
|
+
return entries.length ? entries : void 0;
|
|
2174
|
+
}
|
|
2175
|
+
function buildMemoryContext(opts) {
|
|
2176
|
+
const context = {};
|
|
2177
|
+
const appliesTo = parseList(opts.appliesTo);
|
|
2178
|
+
const avoidWhen = parseList(opts.avoidWhen);
|
|
2179
|
+
const examples = parseList(opts.example);
|
|
2180
|
+
if (appliesTo) context.appliesTo = appliesTo;
|
|
2181
|
+
if (avoidWhen) context.avoidWhen = avoidWhen;
|
|
2182
|
+
if (examples) context.examples = examples;
|
|
2183
|
+
if (opts.source?.trim()) context.source = opts.source.trim();
|
|
2184
|
+
return Object.keys(context).length ? context : void 0;
|
|
2185
|
+
}
|
|
2134
2186
|
function truncate(value, length) {
|
|
2135
2187
|
if (!value) return "";
|
|
2136
2188
|
return value.length > length ? `${value.slice(0, Math.max(0, length - 1))}\u2026` : value;
|
|
@@ -2734,7 +2786,7 @@ program.command("auto-sync [mode]").description("Show or change automatic agent
|
|
|
2734
2786
|
console.log(chalk3.green(`Auto-sync ${enabled ? "enabled" : "disabled"}`));
|
|
2735
2787
|
console.log(chalk3.gray(" Manual sync is always available: memory-core sync"));
|
|
2736
2788
|
});
|
|
2737
|
-
program.command("remember <text>").description("Save a new memory to the central database").option("-t, --type <type>", "Memory type (decision|rule|pattern|note)", "decision").option("-s, --scope <scope>", "Scope (global|project)", "project").option("--tags <tags>", "Comma-separated tags").option("-r, --reason <reason>", "Why this rule exists \u2014 helps agents understand intent and debug violations").option("--no-sync", "Skip automatic agent file sync after saving").action(async (text, opts) => {
|
|
2789
|
+
program.command("remember <text>").description("Save a new memory to the central database").option("-t, --type <type>", "Memory type (decision|rule|pattern|note)", "decision").option("-s, --scope <scope>", "Scope (global|project)", "project").option("--tags <tags>", "Comma-separated tags").option("-r, --reason <reason>", "Why this rule exists \u2014 helps agents understand intent and debug violations").option("--applies-to <items>", "Comma-separated situations where this memory applies").option("--avoid-when <items>", "Comma-separated situations where this memory should not be used").option("--example <items>", "Comma-separated examples that teach agents how to apply this memory").option("--source <source>", "Human-readable source for this memory").option("--no-sync", "Skip automatic agent file sync after saving").action(async (text, opts) => {
|
|
2738
2790
|
const config = readProjectConfig();
|
|
2739
2791
|
let reason = opts.reason;
|
|
2740
2792
|
if (!reason) {
|
|
@@ -2753,7 +2805,8 @@ program.command("remember <text>").description("Save a new memory to the central
|
|
|
2753
2805
|
projectName: config?.projectName,
|
|
2754
2806
|
content: text,
|
|
2755
2807
|
reason: reason || void 0,
|
|
2756
|
-
|
|
2808
|
+
context: buildMemoryContext(opts),
|
|
2809
|
+
tags: parseTags(opts.tags),
|
|
2757
2810
|
embedding
|
|
2758
2811
|
});
|
|
2759
2812
|
const reasonLine = reason ? chalk3.gray(`
|
|
@@ -2787,6 +2840,10 @@ program.command("search <query>").description("Search memories using semantic si
|
|
|
2787
2840
|
const sim = m.similarity ? chalk3.gray(` (${(m.similarity * 100).toFixed(0)}% match)`) : "";
|
|
2788
2841
|
console.log(chalk3.cyan(` ${i + 1}. [${m.type}] ${m.title ?? ""}`));
|
|
2789
2842
|
console.log(chalk3.white(` ${m.content}`) + sim);
|
|
2843
|
+
if (m.reason) console.log(chalk3.gray(` why: ${m.reason}`));
|
|
2844
|
+
if (m.context?.appliesTo?.length) console.log(chalk3.gray(` use when: ${m.context.appliesTo.join("; ")}`));
|
|
2845
|
+
if (m.context?.avoidWhen?.length) console.log(chalk3.gray(` avoid when: ${m.context.avoidWhen.join("; ")}`));
|
|
2846
|
+
if (m.context?.examples?.length) console.log(chalk3.gray(` examples: ${m.context.examples.join("; ")}`));
|
|
2790
2847
|
if (m.tags?.length) console.log(chalk3.gray(` tags: ${m.tags.join(", ")}`));
|
|
2791
2848
|
console.log();
|
|
2792
2849
|
});
|
|
@@ -2832,6 +2889,7 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
|
|
|
2832
2889
|
title: memory.title,
|
|
2833
2890
|
content: memory.content,
|
|
2834
2891
|
reason: memory.reason,
|
|
2892
|
+
context: memory.context,
|
|
2835
2893
|
tags: memory.tags,
|
|
2836
2894
|
embedding
|
|
2837
2895
|
});
|
|
@@ -2925,6 +2983,10 @@ program.command("edit <id>").description("Edit a memory interactively").option("
|
|
|
2925
2983
|
const title = await input({ message: "Title?", default: existing.title ?? "" });
|
|
2926
2984
|
const content = await input({ message: "Content?", default: existing.content });
|
|
2927
2985
|
const reason = await input({ message: "Reason?", default: existing.reason ?? "" });
|
|
2986
|
+
const appliesTo = await input({ message: "Applies to? (comma-separated)", default: existing.context?.appliesTo?.join(",") ?? "" });
|
|
2987
|
+
const avoidWhen = await input({ message: "Avoid when? (comma-separated)", default: existing.context?.avoidWhen?.join(",") ?? "" });
|
|
2988
|
+
const examples = await input({ message: "Examples? (comma-separated)", default: existing.context?.examples?.join(",") ?? "" });
|
|
2989
|
+
const source = await input({ message: "Source?", default: existing.context?.source ?? "" });
|
|
2928
2990
|
const tags = await input({ message: "Tags?", default: existing.tags.join(",") });
|
|
2929
2991
|
const embedding = content === existing.content ? void 0 : await embed(content);
|
|
2930
2992
|
await updateMemory(memoryId, {
|
|
@@ -2933,6 +2995,7 @@ program.command("edit <id>").description("Edit a memory interactively").option("
|
|
|
2933
2995
|
title: title || void 0,
|
|
2934
2996
|
content,
|
|
2935
2997
|
reason: reason || void 0,
|
|
2998
|
+
context: buildMemoryContext({ appliesTo, avoidWhen, example: examples, source }),
|
|
2936
2999
|
tags: parseTags(tags),
|
|
2937
3000
|
embedding
|
|
2938
3001
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shahmilsaari/memory-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "Universal AI memory core — generate AI context files from architecture profiles with RAG support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"smoke:npx": "node scripts/npx-init-smoke.mjs",
|
|
20
20
|
"dev": "tsx src/cli.ts",
|
|
21
21
|
"start": "node dist/cli.js",
|
|
22
|
-
"test": "node --import tsx --test test
|
|
22
|
+
"test": "node --import tsx --test test/*.test.ts"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@inquirer/prompts": "^5.0.0",
|
package/templates/AGENTS.md.hbs
CHANGED
|
@@ -27,8 +27,7 @@ These rules apply to ALL AI agents in this project.
|
|
|
27
27
|
---
|
|
28
28
|
## Relevant Inherited Decisions
|
|
29
29
|
{{#each memories}}
|
|
30
|
-
{{
|
|
31
|
-
{{#if tags.length}}_tags: {{join tags ", "}}_{{/if}}
|
|
30
|
+
{{memoryBlock this}}
|
|
32
31
|
{{/each}}
|
|
33
32
|
{{/if}}
|
|
34
33
|
{{#if caveman.enabled}}
|
package/templates/CLAUDE.md.hbs
CHANGED
package/templates/DEVIN.md.hbs
CHANGED
|
@@ -9,10 +9,7 @@ Stack: {{language}}
|
|
|
9
9
|
_Pulled from central memory via semantic search on "{{architecture}} {{language}}"_
|
|
10
10
|
|
|
11
11
|
{{#each memories}}
|
|
12
|
-
|
|
13
|
-
{{content}}
|
|
14
|
-
{{#if tags.length}}_Tags: {{join tags ", "}}_{{/if}}
|
|
15
|
-
{{#if this.project_name}}_Source: {{this.project_name}}_{{/if}}
|
|
12
|
+
{{memoryBlock this}}
|
|
16
13
|
|
|
17
14
|
{{/each}}
|
|
18
15
|
{{else}}
|
package/templates/clinerules.hbs
CHANGED