@shahmilsaari/memory-core 0.1.2 → 0.1.4
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 +218 -188
- package/dist/cli.js +294 -65
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,75 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# memory-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Every AI coding agent in your project, following your rules. Automatically.**
|
|
4
|
+
|
|
5
|
+
You decide the architecture. memory-core remembers it. Every AI tool — Copilot, Cursor, Claude Code, Windsurf, and 10 more — reads those rules before writing a single line of code.
|
|
4
6
|
|
|
5
7
|
```bash
|
|
6
8
|
npx @shahmilsaari/memory-core init
|
|
7
9
|
```
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## How it works
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
memory-core init
|
|
15
|
-
│
|
|
16
|
-
├── You pick: Backend / Frontend / Fullstack
|
|
17
|
-
├── You pick: Architecture (Clean, MVC, React, Vue, …)
|
|
18
|
-
│
|
|
19
|
-
▼
|
|
20
|
-
PostgreSQL + pgvector
|
|
21
|
-
192 predefined rules — each with a WHY
|
|
22
|
-
+ your own saved decisions
|
|
23
|
-
│
|
|
24
|
-
▼
|
|
25
|
-
18 AI agent config files generated
|
|
26
|
-
│
|
|
27
|
-
▼
|
|
28
|
-
Every agent reads its file → follows your rules
|
|
29
|
-
|
|
30
|
-
git commit → pre-commit hook → Ollama checks diff
|
|
31
|
-
→ violations blocked with Rule + Why + Fix
|
|
32
|
-
```
|
|
11
|
+
No OpenAI key. No cloud. Fully local.
|
|
33
12
|
|
|
34
13
|
---
|
|
35
14
|
|
|
36
|
-
##
|
|
15
|
+
## What does it actually do?
|
|
37
16
|
|
|
38
|
-
|
|
39
|
-
|---|---|
|
|
40
|
-
| Claude Code | `CLAUDE.md` |
|
|
41
|
-
| GitHub Copilot | `.github/copilot-instructions.md` |
|
|
42
|
-
| Cursor | `.cursorrules` + `.cursor/rules/memory-core.mdc` |
|
|
43
|
-
| Windsurf | `.windsurfrules` |
|
|
44
|
-
| Cline | `.clinerules` |
|
|
45
|
-
| Roo Code | `.roo/rules/memory-core.md` |
|
|
46
|
-
| Aider | `.aider.conf.yml` |
|
|
47
|
-
| Continue.dev | `.continue/config.json` |
|
|
48
|
-
| Devin | `DEVIN.md` |
|
|
49
|
-
| Amazon Q | `.amazonq/dev/guidelines.md` |
|
|
50
|
-
| Gemini Code Assist | `.gemini/styleguide.md` |
|
|
51
|
-
| Zed AI | `.zed/settings.json` |
|
|
52
|
-
| JetBrains AI | `.idea/ai-instructions.md` |
|
|
53
|
-
| OpenHands / AGENTS | `AGENTS.md` |
|
|
17
|
+
Without memory-core, every AI agent starts fresh. It doesn't know you're using Clean Architecture. It doesn't know controllers shouldn't call the database. It doesn't know why you made that decision six months ago.
|
|
54
18
|
|
|
55
|
-
|
|
19
|
+
With memory-core:
|
|
20
|
+
|
|
21
|
+
1. You run `init` once and answer a few questions about your project
|
|
22
|
+
2. memory-core generates config files for every AI agent you use
|
|
23
|
+
3. Those agents read the files and follow your rules — automatically
|
|
24
|
+
4. When you commit code, a hook catches violations before they land in the repo
|
|
25
|
+
5. Watch mode catches violations as you type, not just at commit time
|
|
56
26
|
|
|
57
27
|
---
|
|
58
28
|
|
|
59
|
-
##
|
|
29
|
+
## Before you start
|
|
60
30
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
|
64
|
-
|
|
65
|
-
|
|
|
66
|
-
|
|
|
31
|
+
You need three things installed:
|
|
32
|
+
|
|
33
|
+
| What | Why | Install |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| PostgreSQL 14+ | Stores your rules and decisions | See below |
|
|
36
|
+
| pgvector | Makes search smart (finds similar rules, not just exact matches) | See below |
|
|
37
|
+
| Ollama | Runs AI locally — no API key needed | See below |
|
|
67
38
|
|
|
68
39
|
---
|
|
69
40
|
|
|
70
|
-
##
|
|
41
|
+
## Install
|
|
71
42
|
|
|
72
|
-
###
|
|
43
|
+
### PostgreSQL
|
|
73
44
|
|
|
74
45
|
**macOS**
|
|
75
46
|
```bash
|
|
@@ -83,13 +54,20 @@ sudo apt install postgresql postgresql-contrib
|
|
|
83
54
|
sudo systemctl start postgresql
|
|
84
55
|
```
|
|
85
56
|
|
|
86
|
-
**Windows** — download from
|
|
57
|
+
**Windows** — [download from postgresql.org](https://www.postgresql.org/download/windows/)
|
|
87
58
|
|
|
88
59
|
---
|
|
89
60
|
|
|
90
|
-
###
|
|
61
|
+
### pgvector
|
|
62
|
+
|
|
63
|
+
pgvector adds AI-powered search to PostgreSQL.
|
|
64
|
+
|
|
65
|
+
**macOS + PostgreSQL 17+** (easiest)
|
|
66
|
+
```bash
|
|
67
|
+
brew install pgvector
|
|
68
|
+
```
|
|
91
69
|
|
|
92
|
-
**macOS
|
|
70
|
+
**macOS + PostgreSQL 16** (brew's pgvector targets 17+, so build from source)
|
|
93
71
|
```bash
|
|
94
72
|
xcode-select --install
|
|
95
73
|
git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git /tmp/pgvector
|
|
@@ -98,24 +76,21 @@ make PG_CONFIG=/opt/homebrew/opt/postgresql@16/bin/pg_config
|
|
|
98
76
|
make install PG_CONFIG=/opt/homebrew/opt/postgresql@16/bin/pg_config
|
|
99
77
|
```
|
|
100
78
|
|
|
101
|
-
**macOS with PostgreSQL@17+**
|
|
102
|
-
```bash
|
|
103
|
-
brew install pgvector
|
|
104
|
-
```
|
|
105
|
-
|
|
106
79
|
**Linux**
|
|
107
80
|
```bash
|
|
108
81
|
sudo apt install postgresql-16-pgvector # replace 16 with your version
|
|
109
82
|
```
|
|
110
83
|
|
|
111
|
-
|
|
84
|
+
Check it worked:
|
|
112
85
|
```bash
|
|
113
86
|
psql -U $(whoami) -c "SELECT * FROM pg_available_extensions WHERE name = 'vector';"
|
|
114
87
|
```
|
|
115
88
|
|
|
116
89
|
---
|
|
117
90
|
|
|
118
|
-
###
|
|
91
|
+
### Ollama
|
|
92
|
+
|
|
93
|
+
Ollama runs AI models on your machine. Two models are needed — one for search, one for code checking.
|
|
119
94
|
|
|
120
95
|
**macOS**
|
|
121
96
|
```bash
|
|
@@ -129,28 +104,24 @@ curl -fsSL https://ollama.com/install.sh | sh
|
|
|
129
104
|
ollama serve &
|
|
130
105
|
```
|
|
131
106
|
|
|
132
|
-
**Windows** — download from
|
|
107
|
+
**Windows** — [download from ollama.com](https://ollama.com/download)
|
|
133
108
|
|
|
134
|
-
|
|
109
|
+
Then pull the two models:
|
|
135
110
|
```bash
|
|
136
|
-
ollama pull nomic-embed-text #
|
|
137
|
-
ollama pull llama3.2 #
|
|
111
|
+
ollama pull nomic-embed-text # used for search
|
|
112
|
+
ollama pull llama3.2 # used to check your code
|
|
138
113
|
```
|
|
139
114
|
|
|
140
115
|
---
|
|
141
116
|
|
|
142
|
-
###
|
|
117
|
+
### Create the database
|
|
143
118
|
|
|
144
119
|
```bash
|
|
145
120
|
createdb memory_core
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
Then run the schema (get `setup.sql` from the repo or copy below):
|
|
149
|
-
```bash
|
|
150
121
|
psql -U $(whoami) -d memory_core -f setup.sql
|
|
151
122
|
```
|
|
152
123
|
|
|
153
|
-
`setup.sql
|
|
124
|
+
`setup.sql`:
|
|
154
125
|
```sql
|
|
155
126
|
CREATE EXTENSION IF NOT EXISTS vector;
|
|
156
127
|
|
|
@@ -169,38 +140,31 @@ CREATE TABLE IF NOT EXISTS memories (
|
|
|
169
140
|
);
|
|
170
141
|
|
|
171
142
|
CREATE INDEX IF NOT EXISTS memories_embedding_idx
|
|
172
|
-
ON memories USING ivfflat (embedding vector_cosine_ops)
|
|
173
|
-
WITH (lists = 100);
|
|
174
|
-
|
|
143
|
+
ON memories USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
|
|
175
144
|
CREATE INDEX IF NOT EXISTS memories_architecture_idx ON memories (architecture);
|
|
176
145
|
CREATE INDEX IF NOT EXISTS memories_scope_idx ON memories (scope);
|
|
177
146
|
```
|
|
178
147
|
|
|
179
148
|
---
|
|
180
149
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
Create `.env` in the project where you run memory-core (or `.memory-core.env` per project):
|
|
184
|
-
|
|
185
|
-
```env
|
|
186
|
-
DATABASE_URL=postgresql://YOUR_USERNAME@localhost:5432/memory_core
|
|
187
|
-
OLLAMA_URL=http://localhost:11434
|
|
188
|
-
OLLAMA_MODEL=nomic-embed-text
|
|
189
|
-
OLLAMA_CHAT_MODEL=llama3.2
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Find your username: `whoami`
|
|
193
|
-
|
|
194
|
-
---
|
|
150
|
+
## Quick start
|
|
195
151
|
|
|
196
|
-
|
|
152
|
+
```bash
|
|
153
|
+
# 1. Go to your project
|
|
154
|
+
cd my-api
|
|
197
155
|
|
|
198
|
-
|
|
156
|
+
# 2. Initialize — answers a few questions, generates all config files
|
|
157
|
+
npx @shahmilsaari/memory-core init
|
|
199
158
|
|
|
200
|
-
|
|
159
|
+
# 3. Load 192 predefined best-practice rules
|
|
201
160
|
npx @shahmilsaari/memory-core seed
|
|
161
|
+
|
|
162
|
+
# 4. Install the pre-commit hook (optional but recommended)
|
|
163
|
+
npx @shahmilsaari/memory-core hook install
|
|
202
164
|
```
|
|
203
165
|
|
|
166
|
+
That's it. Every AI agent in your project now has your rules.
|
|
167
|
+
|
|
204
168
|
---
|
|
205
169
|
|
|
206
170
|
## Commands
|
|
@@ -208,107 +172,108 @@ npx @shahmilsaari/memory-core seed
|
|
|
208
172
|
### `init` — Set up a project
|
|
209
173
|
|
|
210
174
|
```bash
|
|
211
|
-
cd your-project
|
|
212
175
|
npx @shahmilsaari/memory-core init
|
|
213
176
|
```
|
|
214
177
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
Project
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
Language? → TypeScript
|
|
222
|
-
Install caveman? → no
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Generates all 18 agent config files and saves `.memory-core.json`.
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
|
|
229
|
-
### `sync` — Regenerate all agent files
|
|
230
|
-
|
|
231
|
-
```bash
|
|
232
|
-
npx @shahmilsaari/memory-core sync
|
|
233
|
-
```
|
|
178
|
+
Asks you:
|
|
179
|
+
- Project name
|
|
180
|
+
- Project type (Backend / Frontend / Fullstack)
|
|
181
|
+
- Architecture (Clean Architecture, MVC, React, Vue, etc.)
|
|
182
|
+
- Language
|
|
183
|
+
- Whether to enable caveman mode (optional token saver)
|
|
234
184
|
|
|
235
|
-
|
|
185
|
+
Generates config files for every supported AI agent and saves your choices to `.memory-core.json`.
|
|
236
186
|
|
|
237
187
|
---
|
|
238
188
|
|
|
239
189
|
### `remember` — Save a decision
|
|
240
190
|
|
|
191
|
+
Made a decision your team should never forget? Save it.
|
|
192
|
+
|
|
241
193
|
```bash
|
|
242
194
|
npx @shahmilsaari/memory-core remember "Controllers must never call the database directly"
|
|
243
195
|
```
|
|
244
196
|
|
|
245
|
-
|
|
197
|
+
It will ask you *why* — that reason gets stored alongside the rule and shown to AI agents and developers when a violation is caught.
|
|
246
198
|
|
|
247
|
-
With flags:
|
|
199
|
+
With flags (skip the prompts):
|
|
248
200
|
```bash
|
|
249
201
|
npx @shahmilsaari/memory-core remember "Use DTOs for all API responses" \
|
|
250
202
|
--type rule \
|
|
251
203
|
--scope global \
|
|
252
|
-
--reason "Raw DB entities
|
|
253
|
-
--tags "api,dto
|
|
204
|
+
--reason "Raw DB entities expose internal schema and sensitive fields" \
|
|
205
|
+
--tags "api,dto"
|
|
254
206
|
```
|
|
255
207
|
|
|
256
|
-
| Flag |
|
|
208
|
+
| Flag | Options | Default |
|
|
257
209
|
|---|---|---|
|
|
258
210
|
| `--type` | `decision` `rule` `pattern` `note` | `decision` |
|
|
259
211
|
| `--scope` | `global` `project` | `project` |
|
|
260
|
-
| `--reason` |
|
|
212
|
+
| `--reason` | any text | asked interactively |
|
|
261
213
|
| `--tags` | comma-separated | none |
|
|
262
214
|
|
|
263
215
|
---
|
|
264
216
|
|
|
265
|
-
### `
|
|
217
|
+
### `sync` — Refresh all agent files
|
|
218
|
+
|
|
219
|
+
After saving new memories, regenerate every agent file to include them.
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
npx @shahmilsaari/memory-core sync
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### `search` — Find a rule or decision
|
|
266
228
|
|
|
267
229
|
```bash
|
|
268
230
|
npx @shahmilsaari/memory-core search "error handling"
|
|
269
231
|
npx @shahmilsaari/memory-core search "auth strategy" --limit 10
|
|
270
232
|
```
|
|
271
233
|
|
|
234
|
+
Uses AI search — finds related rules even if you don't use the exact words.
|
|
235
|
+
|
|
272
236
|
---
|
|
273
237
|
|
|
274
238
|
### `seed` — Load predefined rules
|
|
275
239
|
|
|
240
|
+
192 best-practice rules across all supported architectures, each with a plain-English reason explaining why the rule exists.
|
|
241
|
+
|
|
276
242
|
```bash
|
|
277
|
-
npx @shahmilsaari/memory-core seed
|
|
278
|
-
npx @shahmilsaari/memory-core seed --arch clean-architecture # one
|
|
279
|
-
npx @shahmilsaari/memory-core seed --force # re-seed existing
|
|
243
|
+
npx @shahmilsaari/memory-core seed # all architectures
|
|
244
|
+
npx @shahmilsaari/memory-core seed --arch clean-architecture # one only
|
|
245
|
+
npx @shahmilsaari/memory-core seed --force # re-seed existing
|
|
280
246
|
```
|
|
281
247
|
|
|
282
|
-
192 rules across all architectures. Every rule includes a `reason` explaining why it exists.
|
|
283
|
-
|
|
284
248
|
---
|
|
285
249
|
|
|
286
|
-
### `hook install` —
|
|
250
|
+
### `hook install` — Block bad commits
|
|
287
251
|
|
|
288
252
|
```bash
|
|
289
253
|
npx @shahmilsaari/memory-core hook install
|
|
290
254
|
```
|
|
291
255
|
|
|
292
|
-
Installs a git pre-commit hook. Every `git commit
|
|
256
|
+
Installs a git pre-commit hook. Every time you run `git commit`, your staged files are checked against your architecture rules before the commit goes through.
|
|
257
|
+
|
|
258
|
+
When a violation is found, the commit is blocked and you see exactly what's wrong and how to fix it:
|
|
293
259
|
|
|
294
260
|
```
|
|
295
261
|
✗ 2 rule violations found — commit blocked
|
|
296
262
|
|
|
297
263
|
[1] src/controllers/user.ts:32
|
|
298
264
|
Rule: Thin controllers — business logic belongs in services
|
|
299
|
-
Why: Logic in controllers cannot be reused from
|
|
300
|
-
|
|
265
|
+
Why: Logic in controllers cannot be reused from other entry points
|
|
266
|
+
— it gets siloed and duplicated across handlers
|
|
301
267
|
Issue: Password validation logic inside route handler
|
|
302
268
|
Fix: Move to UserService.validateCredentials()
|
|
303
269
|
|
|
304
270
|
[2] src/domain/user.entity.ts:5
|
|
305
271
|
Rule: Domain has zero external imports
|
|
306
|
-
Why: Framework imports
|
|
307
|
-
infrastructure — you can't swap the DB or test without spinning up the stack
|
|
272
|
+
Why: Framework imports tie business logic to infrastructure
|
|
308
273
|
Issue: Imports 'typeorm' directly
|
|
309
|
-
Fix: Define IUserRepository interface in domain
|
|
274
|
+
Fix: Define IUserRepository interface in domain/
|
|
310
275
|
|
|
311
|
-
To bypass
|
|
276
|
+
To bypass: git commit --no-verify
|
|
312
277
|
To save as memory: memory-core remember "<lesson>"
|
|
313
278
|
```
|
|
314
279
|
|
|
@@ -318,26 +283,63 @@ npx @shahmilsaari/memory-core hook uninstall # remove the hook
|
|
|
318
283
|
|
|
319
284
|
---
|
|
320
285
|
|
|
321
|
-
### `
|
|
286
|
+
### `watch` — Catch violations as you type
|
|
322
287
|
|
|
323
288
|
```bash
|
|
324
|
-
npx @shahmilsaari/memory-core
|
|
325
|
-
|
|
289
|
+
npx @shahmilsaari/memory-core watch
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Runs in the background and checks each file the moment you save it. You see violations immediately — before you even think about committing.
|
|
293
|
+
|
|
326
294
|
```
|
|
295
|
+
archmind watch — real-time rule enforcement
|
|
327
296
|
|
|
328
|
-
|
|
297
|
+
watching: /your/project
|
|
298
|
+
model: llama3.2
|
|
299
|
+
rules: 24
|
|
300
|
+
ctrl+c to stop
|
|
301
|
+
|
|
302
|
+
[10:42:11] saved: src/controllers/user.ts
|
|
303
|
+
|
|
304
|
+
✗ 1 violation in src/controllers/user.ts
|
|
305
|
+
|
|
306
|
+
[1] src/controllers/user.ts:34
|
|
307
|
+
Rule: Thin controllers — business logic belongs in services
|
|
308
|
+
Why: Logic in controllers cannot be reused from other entry points
|
|
309
|
+
Issue: Password hashing inside the route handler
|
|
310
|
+
Fix: Move to UserService.hashPassword()
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Options:
|
|
314
|
+
```bash
|
|
315
|
+
npx @shahmilsaari/memory-core watch --path src/ # watch a specific folder only
|
|
316
|
+
npx @shahmilsaari/memory-core watch --verbose # show extra details
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Only checks source files — ignores `node_modules`, `dist`, config files, JSON, etc.
|
|
329
320
|
|
|
330
321
|
---
|
|
331
322
|
|
|
332
|
-
### `
|
|
323
|
+
### `check` — Manual check (for CI)
|
|
333
324
|
|
|
334
|
-
|
|
325
|
+
```bash
|
|
326
|
+
npx @shahmilsaari/memory-core check --staged # check staged files
|
|
327
|
+
npx @shahmilsaari/memory-core check --staged --verbose # with extra detail
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Same as the pre-commit hook. Use this in CI/CD pipelines.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
### `global` — Apply rules to every project
|
|
335
335
|
|
|
336
336
|
```bash
|
|
337
337
|
npx @shahmilsaari/memory-core global
|
|
338
338
|
```
|
|
339
339
|
|
|
340
|
-
|
|
340
|
+
Writes your rules to the global config of each AI agent — so they follow your rules in every project on your machine, not just the current one.
|
|
341
|
+
|
|
342
|
+
| Agent | Where it writes |
|
|
341
343
|
|---|---|
|
|
342
344
|
| Claude Code | `~/.claude/CLAUDE.md` |
|
|
343
345
|
| GitHub Copilot | VS Code `settings.json` |
|
|
@@ -350,63 +352,89 @@ npx @shahmilsaari/memory-core global
|
|
|
350
352
|
|
|
351
353
|
---
|
|
352
354
|
|
|
355
|
+
## Supported agents
|
|
356
|
+
|
|
357
|
+
| Agent | File generated |
|
|
358
|
+
|---|---|
|
|
359
|
+
| Claude Code | `CLAUDE.md` |
|
|
360
|
+
| GitHub Copilot | `.github/copilot-instructions.md` |
|
|
361
|
+
| Cursor | `.cursorrules` + `.cursor/rules/memory-core.mdc` |
|
|
362
|
+
| Windsurf | `.windsurfrules` |
|
|
363
|
+
| Cline | `.clinerules` |
|
|
364
|
+
| Roo Code | `.roo/rules/memory-core.md` |
|
|
365
|
+
| Aider | `.aider.conf.yml` |
|
|
366
|
+
| Continue.dev | `.continue/config.json` |
|
|
367
|
+
| Devin | `DEVIN.md` |
|
|
368
|
+
| Amazon Q | `.amazonq/dev/guidelines.md` |
|
|
369
|
+
| Gemini Code Assist | `.gemini/styleguide.md` |
|
|
370
|
+
| Zed AI | `.zed/settings.json` |
|
|
371
|
+
| JetBrains AI | `.idea/ai-instructions.md` |
|
|
372
|
+
| OpenHands | `AGENTS.md` |
|
|
373
|
+
|
|
374
|
+
Plus shared files: `AI_RULES.md`, `ARCHITECTURE.md`, `PROJECT_MEMORY.md`
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
353
378
|
## Architecture profiles
|
|
354
379
|
|
|
355
|
-
|
|
356
|
-
|
|
380
|
+
Pick the one that matches how your project is structured.
|
|
381
|
+
|
|
382
|
+
**Backend**
|
|
383
|
+
| Profile | Use when… |
|
|
357
384
|
|---|---|
|
|
358
|
-
|
|
|
359
|
-
|
|
|
360
|
-
|
|
|
361
|
-
|
|
|
362
|
-
|
|
|
363
|
-
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
| Profile |
|
|
385
|
+
| Clean Architecture | You want strict separation between domain, application, and infrastructure |
|
|
386
|
+
| Modular Monolith | You're building feature modules that might become microservices later |
|
|
387
|
+
| MVC | Standard web app with controllers and services |
|
|
388
|
+
| Hexagonal | You want ports and adapters for maximum testability |
|
|
389
|
+
| Next.js | Next.js 13+ with server components and server actions |
|
|
390
|
+
| Laravel | Laravel with service-repository pattern |
|
|
391
|
+
|
|
392
|
+
**Frontend**
|
|
393
|
+
| Profile | Use when… |
|
|
367
394
|
|---|---|
|
|
368
|
-
|
|
|
369
|
-
|
|
|
370
|
-
|
|
|
371
|
-
|
|
|
372
|
-
|
|
|
373
|
-
|
|
|
395
|
+
| React | Functional components, hooks, React Query, Zustand |
|
|
396
|
+
| Vue 3 | Composition API, Pinia, composables |
|
|
397
|
+
| Angular | Standalone components, signals, OnPush strategy |
|
|
398
|
+
| Svelte | Svelte 5 runes, SvelteKit load functions |
|
|
399
|
+
| Nuxt 3 | Fullstack Vue with SSR |
|
|
400
|
+
| React Native | Mobile apps |
|
|
374
401
|
|
|
375
|
-
Fullstack projects get
|
|
402
|
+
Fullstack projects get both sections in every generated file.
|
|
376
403
|
|
|
377
404
|
---
|
|
378
405
|
|
|
379
|
-
## Caveman
|
|
406
|
+
## Caveman mode (optional)
|
|
380
407
|
|
|
381
|
-
|
|
408
|
+
Cuts AI response length by 65–75% by removing filler words. Opt in during `init`.
|
|
382
409
|
|
|
383
|
-
| Level |
|
|
410
|
+
| Level | What it does |
|
|
384
411
|
|---|---|
|
|
385
|
-
| `lite` | Professional
|
|
386
|
-
| `full` | Caveman
|
|
387
|
-
| `ultra` |
|
|
412
|
+
| `lite` | Professional and concise |
|
|
413
|
+
| `full` | Caveman-style short answers |
|
|
414
|
+
| `ultra` | Absolute minimum words |
|
|
388
415
|
|
|
389
416
|
---
|
|
390
417
|
|
|
391
|
-
##
|
|
418
|
+
## Day-to-day workflow
|
|
392
419
|
|
|
393
420
|
```bash
|
|
394
|
-
#
|
|
421
|
+
# Starting a new project
|
|
395
422
|
cd my-api
|
|
396
423
|
npx @shahmilsaari/memory-core init
|
|
424
|
+
npx @shahmilsaari/memory-core seed
|
|
397
425
|
npx @shahmilsaari/memory-core hook install
|
|
398
426
|
|
|
399
|
-
#
|
|
400
|
-
npx @shahmilsaari/memory-core remember "All auth goes through middleware, never
|
|
427
|
+
# Made an architectural decision? Save it.
|
|
428
|
+
npx @shahmilsaari/memory-core remember "All auth goes through middleware, never in controllers" \
|
|
401
429
|
--type decision --scope global
|
|
402
430
|
|
|
403
|
-
#
|
|
431
|
+
# Refresh agent files after saving new memories
|
|
404
432
|
npx @shahmilsaari/memory-core sync
|
|
405
433
|
|
|
406
|
-
#
|
|
434
|
+
# Not sure how something was decided? Search.
|
|
407
435
|
npx @shahmilsaari/memory-core search "caching strategy"
|
|
408
436
|
|
|
409
|
-
#
|
|
437
|
+
# Commit code → hook checks it automatically
|
|
410
438
|
git commit -m "add user endpoint"
|
|
411
439
|
```
|
|
412
440
|
|
|
@@ -414,49 +442,51 @@ git commit -m "add user endpoint"
|
|
|
414
442
|
|
|
415
443
|
## Environment variables
|
|
416
444
|
|
|
417
|
-
|
|
445
|
+
memory-core creates `.memory-core.env` automatically during `init`. You can also set these manually:
|
|
446
|
+
|
|
447
|
+
| Variable | Required | Default | What it does |
|
|
418
448
|
|---|---|---|---|
|
|
419
449
|
| `DATABASE_URL` | Yes | — | PostgreSQL connection string |
|
|
420
|
-
| `OLLAMA_URL` | No | `http://localhost:11434` | Ollama
|
|
421
|
-
| `OLLAMA_MODEL` | No | `nomic-embed-text` |
|
|
422
|
-
| `OLLAMA_CHAT_MODEL` | No | `llama3.2` |
|
|
450
|
+
| `OLLAMA_URL` | No | `http://localhost:11434` | Where Ollama is running |
|
|
451
|
+
| `OLLAMA_MODEL` | No | `nomic-embed-text` | Model used for search |
|
|
452
|
+
| `OLLAMA_CHAT_MODEL` | No | `llama3.2` | Model used for code checking |
|
|
423
453
|
|
|
424
454
|
---
|
|
425
455
|
|
|
426
456
|
## Troubleshooting
|
|
427
457
|
|
|
428
458
|
**`extension "vector" is not available`**
|
|
429
|
-
pgvector
|
|
459
|
+
pgvector isn't installed for your PostgreSQL version. Follow the [pgvector install steps](#pgvector) above.
|
|
430
460
|
|
|
431
461
|
**`Ollama not running — skipping rule check`**
|
|
432
462
|
Start Ollama: `brew services start ollama` (macOS) or `ollama serve` (Linux).
|
|
433
463
|
|
|
434
464
|
**`Chat model "llama3.2" not found`**
|
|
435
|
-
|
|
465
|
+
Run `ollama pull llama3.2`. Or switch to another model: add `OLLAMA_CHAT_MODEL=mistral` to `.memory-core.env`.
|
|
436
466
|
|
|
437
467
|
**`DATABASE_URL is not set`**
|
|
438
|
-
|
|
468
|
+
Run `npx @shahmilsaari/memory-core init` — it will create the `.memory-core.env` file for you.
|
|
439
469
|
|
|
440
470
|
**`createdb: role does not exist`**
|
|
441
471
|
```bash
|
|
442
472
|
createuser -s $(whoami)
|
|
443
473
|
```
|
|
444
474
|
|
|
445
|
-
**
|
|
446
|
-
|
|
475
|
+
**Hook is flagging JSON or config files**
|
|
476
|
+
It won't — the hook only checks source code files: `.ts .tsx .js .jsx .py .php .rb .go .java .cs .swift .kt .rs .vue .svelte`. Everything else is skipped automatically.
|
|
447
477
|
|
|
448
478
|
---
|
|
449
479
|
|
|
450
480
|
## Roadmap
|
|
451
481
|
|
|
452
|
-
|
|
|
482
|
+
| | Feature |
|
|
453
483
|
|---|---|
|
|
454
|
-
|
|
|
455
|
-
|
|
|
456
|
-
| Violation memory
|
|
457
|
-
| Rule analytics
|
|
458
|
-
| Team sync
|
|
459
|
-
| Memory review
|
|
484
|
+
| ✓ | Watch mode — real-time violation alerts on save |
|
|
485
|
+
| | CI/CD — fail PRs that violate your rules |
|
|
486
|
+
| | Violation memory — auto-save what went wrong as a new rule |
|
|
487
|
+
| | Rule analytics — see which rules get broken most |
|
|
488
|
+
| | Team sync — share memory across your whole team |
|
|
489
|
+
| | Memory review — approve rules before they propagate |
|
|
460
490
|
|
|
461
491
|
---
|
|
462
492
|
|
package/dist/cli.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { input, select, confirm } from "@inquirer/prompts";
|
|
6
|
-
import
|
|
6
|
+
import chalk3 from "chalk";
|
|
7
7
|
import ora from "ora";
|
|
8
|
-
import { readFileSync as
|
|
9
|
-
import { join as
|
|
8
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync2, appendFileSync } from "fs";
|
|
9
|
+
import { join as join6, dirname as dirname2 } from "path";
|
|
10
10
|
import { homedir } from "os";
|
|
11
|
-
import { execSync as
|
|
11
|
+
import { execSync as execSync3 } from "child_process";
|
|
12
12
|
|
|
13
13
|
// src/generator.ts
|
|
14
14
|
import { readFileSync, readdirSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
@@ -626,10 +626,10 @@ function uninstallHook() {
|
|
|
626
626
|
console.log(chalk.green("\n \u2713 Pre-commit hook removed\n"));
|
|
627
627
|
}
|
|
628
628
|
async function checkStaged(options = {}) {
|
|
629
|
-
const
|
|
629
|
+
const SOURCE_EXTENSIONS2 = /\.(ts|tsx|js|jsx|py|php|rb|go|java|cs|swift|kt|rs|vue|svelte)$/;
|
|
630
630
|
let diff;
|
|
631
631
|
try {
|
|
632
|
-
const stagedFiles = execSync("git diff --cached --name-only", { encoding: "utf-8" }).split("\n").filter((f) => f &&
|
|
632
|
+
const stagedFiles = execSync("git diff --cached --name-only", { encoding: "utf-8" }).split("\n").filter((f) => f && SOURCE_EXTENSIONS2.test(f));
|
|
633
633
|
if (stagedFiles.length === 0) {
|
|
634
634
|
if (options.verbose) console.log(chalk.gray(" No source files staged \u2014 skipping rule check."));
|
|
635
635
|
return;
|
|
@@ -782,57 +782,283 @@ function printModelMissing(model) {
|
|
|
782
782
|
console.log(chalk.gray(" Recommended: llama3.2 | qwen2.5-coder:3b | mistral\n"));
|
|
783
783
|
}
|
|
784
784
|
|
|
785
|
+
// src/watcher.ts
|
|
786
|
+
import { watch } from "chokidar";
|
|
787
|
+
import { execSync as execSync2 } from "child_process";
|
|
788
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
789
|
+
import { join as join5, relative } from "path";
|
|
790
|
+
import chalk2 from "chalk";
|
|
791
|
+
var SOURCE_EXTENSIONS = /\.(ts|tsx|js|jsx|py|php|rb|go|java|cs|swift|kt|rs|vue|svelte)$/;
|
|
792
|
+
var reasonMap2 = new Map(
|
|
793
|
+
seeds.filter((s) => s.reason).map((s) => [s.content, s.reason])
|
|
794
|
+
);
|
|
795
|
+
function loadConfig(cwd) {
|
|
796
|
+
const configPath = join5(cwd, ".memory-core.json");
|
|
797
|
+
if (!existsSync5(configPath)) return null;
|
|
798
|
+
try {
|
|
799
|
+
return JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
800
|
+
} catch {
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function getProfileRules(config2) {
|
|
805
|
+
const rules = [];
|
|
806
|
+
const avoids = [];
|
|
807
|
+
if (config2.backendArchitecture) {
|
|
808
|
+
const profile = listProfiles("backend").find((p) => p.name === config2.backendArchitecture);
|
|
809
|
+
if (profile) {
|
|
810
|
+
rules.push(...profile.rules);
|
|
811
|
+
avoids.push(...profile.avoid);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (config2.frontendFramework) {
|
|
815
|
+
const profile = listProfiles("frontend").find((p) => p.name === config2.frontendFramework);
|
|
816
|
+
if (profile) {
|
|
817
|
+
rules.push(...profile.rules);
|
|
818
|
+
avoids.push(...profile.avoid);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return { rules, avoids };
|
|
822
|
+
}
|
|
823
|
+
async function checkFile(filePath, cwd, config2, verbose) {
|
|
824
|
+
const rel = relative(cwd, filePath);
|
|
825
|
+
let diff;
|
|
826
|
+
try {
|
|
827
|
+
diff = execSync2(`git diff HEAD -- "${rel}" 2>/dev/null || git diff --no-index /dev/null "${rel}" 2>/dev/null || true`, {
|
|
828
|
+
encoding: "utf-8",
|
|
829
|
+
cwd
|
|
830
|
+
});
|
|
831
|
+
} catch {
|
|
832
|
+
try {
|
|
833
|
+
diff = execSync2(`git diff --no-index /dev/null "${rel}"`, { encoding: "utf-8", cwd });
|
|
834
|
+
} catch (e) {
|
|
835
|
+
diff = e.stdout ?? "";
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (!diff.trim()) return;
|
|
839
|
+
const { rules, avoids } = getProfileRules(config2);
|
|
840
|
+
if (rules.length === 0) return;
|
|
841
|
+
const ollamaUrl = process.env.OLLAMA_URL ?? "http://localhost:11434";
|
|
842
|
+
const chatModel = process.env.OLLAMA_CHAT_MODEL ?? "llama3.2";
|
|
843
|
+
const MAX_DIFF = 6e3;
|
|
844
|
+
const truncated = diff.length > MAX_DIFF;
|
|
845
|
+
const diffToSend = truncated ? diff.slice(0, MAX_DIFF) + "\n\n[diff truncated]" : diff;
|
|
846
|
+
if (verbose) {
|
|
847
|
+
console.log(chalk2.dim(`
|
|
848
|
+
[watch] checking ${rel} (${diff.length} chars)\u2026`));
|
|
849
|
+
}
|
|
850
|
+
const rulesWithReasons = rules.map((r, i) => {
|
|
851
|
+
const why = reasonMap2.get(r);
|
|
852
|
+
return why ? `${i + 1}. ${r}
|
|
853
|
+
WHY: ${why}` : `${i + 1}. ${r}`;
|
|
854
|
+
}).join("\n");
|
|
855
|
+
const systemPrompt = `You are a strict code reviewer enforcing architecture rules.
|
|
856
|
+
Analyze the file diff and identify ONLY clear, definite rule violations.
|
|
857
|
+
Use the WHY for each rule to understand intent and judge edge cases.
|
|
858
|
+
|
|
859
|
+
Rules to enforce:
|
|
860
|
+
${rulesWithReasons}
|
|
861
|
+
|
|
862
|
+
Things that must never appear:
|
|
863
|
+
${avoids.map((a, i) => `${i + 1}. ${a}`).join("\n")}
|
|
864
|
+
|
|
865
|
+
IMPORTANT: Respond with JSON: {"violations":[...]} or {"violations":[]}.
|
|
866
|
+
Each violation: {"rule":"...","file":"...","line":N,"issue":"...","suggestion":"...","reason":"..."}.
|
|
867
|
+
No text outside the JSON.`;
|
|
868
|
+
try {
|
|
869
|
+
const res = await fetch(`${ollamaUrl}/api/chat`, {
|
|
870
|
+
method: "POST",
|
|
871
|
+
headers: { "Content-Type": "application/json" },
|
|
872
|
+
body: JSON.stringify({
|
|
873
|
+
model: chatModel,
|
|
874
|
+
messages: [
|
|
875
|
+
{ role: "system", content: systemPrompt },
|
|
876
|
+
{ role: "user", content: `Review this diff for ${rel}:
|
|
877
|
+
|
|
878
|
+
${diffToSend}` }
|
|
879
|
+
],
|
|
880
|
+
stream: false,
|
|
881
|
+
format: "json"
|
|
882
|
+
})
|
|
883
|
+
});
|
|
884
|
+
if (!res.ok) {
|
|
885
|
+
const body = await res.text();
|
|
886
|
+
if (body.includes("not found") || body.includes("model")) {
|
|
887
|
+
console.log(chalk2.yellow(`
|
|
888
|
+
\u26A0 Chat model "${chatModel}" not found. Pull it: ollama pull ${chatModel}
|
|
889
|
+
`));
|
|
890
|
+
}
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const data = await res.json();
|
|
894
|
+
const raw = data.message.content.trim();
|
|
895
|
+
let violations = [];
|
|
896
|
+
try {
|
|
897
|
+
const parsed = JSON.parse(raw);
|
|
898
|
+
if (Array.isArray(parsed)) {
|
|
899
|
+
violations = parsed;
|
|
900
|
+
} else if (Array.isArray(parsed?.violations)) {
|
|
901
|
+
violations = parsed.violations;
|
|
902
|
+
} else if (parsed?.rule) {
|
|
903
|
+
violations = [parsed];
|
|
904
|
+
}
|
|
905
|
+
} catch {
|
|
906
|
+
violations = [];
|
|
907
|
+
}
|
|
908
|
+
if (violations.length === 0) {
|
|
909
|
+
console.log(chalk2.green(` \u2713 ${rel}`) + chalk2.dim(" \u2014 no violations"));
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
console.log(
|
|
913
|
+
chalk2.red.bold(`
|
|
914
|
+
\u2717 ${violations.length} violation${violations.length > 1 ? "s" : ""} in ${rel}
|
|
915
|
+
`)
|
|
916
|
+
);
|
|
917
|
+
violations.forEach((v, i) => {
|
|
918
|
+
const loc = v.line ? `${v.file ?? rel}:${v.line}` : v.file ?? rel;
|
|
919
|
+
console.log(chalk2.bold(` [${i + 1}] ${loc}`));
|
|
920
|
+
console.log(chalk2.yellow(" Rule: ") + v.rule);
|
|
921
|
+
const why = v.reason ?? reasonMap2.get(v.rule);
|
|
922
|
+
if (why) console.log(chalk2.dim(" Why: ") + chalk2.dim(why));
|
|
923
|
+
if (v.issue) console.log(chalk2.red(" Issue: ") + v.issue);
|
|
924
|
+
if (v.suggestion) console.log(chalk2.green(" Fix: ") + v.suggestion);
|
|
925
|
+
console.log();
|
|
926
|
+
});
|
|
927
|
+
console.log(chalk2.dim(' Fix violations or run: memory-core remember "<lesson>"'));
|
|
928
|
+
console.log();
|
|
929
|
+
} catch (err) {
|
|
930
|
+
if (err.cause?.code === "ECONNREFUSED" || err.message?.includes("ECONNREFUSED")) {
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (verbose) {
|
|
934
|
+
console.log(chalk2.yellow(` \u26A0 Check failed for ${rel}: ${err.message}`));
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
function startWatch(options = {}) {
|
|
939
|
+
const cwd = process.cwd();
|
|
940
|
+
const config2 = loadConfig(cwd);
|
|
941
|
+
if (!config2) {
|
|
942
|
+
console.error(chalk2.red("\n No .memory-core.json found. Run: memory-core init\n"));
|
|
943
|
+
process.exit(1);
|
|
944
|
+
}
|
|
945
|
+
const { rules } = getProfileRules(config2);
|
|
946
|
+
if (rules.length === 0) {
|
|
947
|
+
console.log(chalk2.yellow("\n No architecture rules configured in .memory-core.json \u2014 nothing to watch.\n"));
|
|
948
|
+
process.exit(0);
|
|
949
|
+
}
|
|
950
|
+
const watchPath = options.path ?? cwd;
|
|
951
|
+
const ollamaUrl = process.env.OLLAMA_URL ?? "http://localhost:11434";
|
|
952
|
+
const chatModel = process.env.OLLAMA_CHAT_MODEL ?? "llama3.2";
|
|
953
|
+
console.log(chalk2.cyan("\n archmind watch \u2014 real-time rule enforcement\n"));
|
|
954
|
+
console.log(chalk2.dim(` watching: ${watchPath}`));
|
|
955
|
+
console.log(chalk2.dim(` model: ${chatModel}`));
|
|
956
|
+
console.log(chalk2.dim(` rules: ${rules.length}`));
|
|
957
|
+
console.log(chalk2.dim(" ctrl+c to stop\n"));
|
|
958
|
+
const pending = /* @__PURE__ */ new Map();
|
|
959
|
+
let ollamaWarned = false;
|
|
960
|
+
const watcher = watch(watchPath, {
|
|
961
|
+
ignored: [
|
|
962
|
+
"**/node_modules/**",
|
|
963
|
+
"**/.git/**",
|
|
964
|
+
"**/dist/**",
|
|
965
|
+
"**/build/**",
|
|
966
|
+
"**/coverage/**",
|
|
967
|
+
"**/.memory-core*"
|
|
968
|
+
],
|
|
969
|
+
ignoreInitial: true,
|
|
970
|
+
persistent: true,
|
|
971
|
+
awaitWriteFinish: { stabilityThreshold: 150, pollInterval: 50 }
|
|
972
|
+
});
|
|
973
|
+
const keepAlive = setInterval(() => {
|
|
974
|
+
}, 1 << 30);
|
|
975
|
+
const handle = (filePath) => {
|
|
976
|
+
if (!SOURCE_EXTENSIONS.test(filePath)) return;
|
|
977
|
+
if (pending.has(filePath)) clearTimeout(pending.get(filePath));
|
|
978
|
+
const timer = setTimeout(async () => {
|
|
979
|
+
pending.delete(filePath);
|
|
980
|
+
console.log(chalk2.dim(`
|
|
981
|
+
[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] saved: ${relative(cwd, filePath)}`));
|
|
982
|
+
try {
|
|
983
|
+
const ping = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(2e3) });
|
|
984
|
+
if (!ping.ok) throw new Error("not ok");
|
|
985
|
+
ollamaWarned = false;
|
|
986
|
+
} catch {
|
|
987
|
+
if (!ollamaWarned) {
|
|
988
|
+
console.log(chalk2.yellow(` \u26A0 Ollama not running at ${ollamaUrl} \u2014 skipping check.`));
|
|
989
|
+
console.log(chalk2.gray(" Start it: ollama serve\n"));
|
|
990
|
+
ollamaWarned = true;
|
|
991
|
+
}
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
await checkFile(filePath, cwd, config2, options.verbose ?? false);
|
|
995
|
+
}, 300);
|
|
996
|
+
pending.set(filePath, timer);
|
|
997
|
+
};
|
|
998
|
+
watcher.on("add", handle);
|
|
999
|
+
watcher.on("change", handle);
|
|
1000
|
+
watcher.on("error", (err) => {
|
|
1001
|
+
console.error(chalk2.red(` watcher error: ${err.message}`));
|
|
1002
|
+
});
|
|
1003
|
+
process.on("SIGINT", () => {
|
|
1004
|
+
console.log(chalk2.dim("\n\n archmind watch stopped.\n"));
|
|
1005
|
+
clearInterval(keepAlive);
|
|
1006
|
+
watcher.close();
|
|
1007
|
+
process.exit(0);
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
|
|
785
1011
|
// src/cli.ts
|
|
786
1012
|
function printBanner(projectName, agentCount) {
|
|
787
1013
|
const lines = [
|
|
788
1014
|
"",
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
1015
|
+
chalk3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 "),
|
|
1016
|
+
chalk3.cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557"),
|
|
1017
|
+
chalk3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551"),
|
|
1018
|
+
chalk3.cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551"),
|
|
1019
|
+
chalk3.cyan(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D"),
|
|
1020
|
+
chalk3.cyan(" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D"),
|
|
795
1021
|
"",
|
|
796
|
-
|
|
1022
|
+
chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 ") + chalk3.bold.white("C O R E") + chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
797
1023
|
"",
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1024
|
+
chalk3.green(` \u2713 Project `) + chalk3.bold(projectName),
|
|
1025
|
+
chalk3.green(` \u2713 Agents `) + chalk3.bold(`${agentCount} AI agents configured`),
|
|
1026
|
+
chalk3.green(` \u2713 Memory `) + chalk3.bold("PostgreSQL + pgvector ready"),
|
|
801
1027
|
"",
|
|
802
|
-
|
|
1028
|
+
chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
803
1029
|
"",
|
|
804
|
-
|
|
1030
|
+
chalk3.bold(" Every AI agent in this project now follows your rules."),
|
|
805
1031
|
"",
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1032
|
+
chalk3.gray(" Next steps:"),
|
|
1033
|
+
chalk3.gray(' memory-core remember "Your architectural decision"'),
|
|
1034
|
+
chalk3.gray(' memory-core search "query"'),
|
|
1035
|
+
chalk3.gray(" memory-core sync"),
|
|
810
1036
|
""
|
|
811
1037
|
];
|
|
812
1038
|
lines.forEach((l) => console.log(l));
|
|
813
1039
|
}
|
|
814
1040
|
var CONFIG_FILE = ".memory-core.json";
|
|
815
1041
|
function readProjectConfig() {
|
|
816
|
-
const path =
|
|
817
|
-
if (!
|
|
1042
|
+
const path = join6(process.cwd(), CONFIG_FILE);
|
|
1043
|
+
if (!existsSync6(path)) return null;
|
|
818
1044
|
try {
|
|
819
|
-
return JSON.parse(
|
|
1045
|
+
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
820
1046
|
} catch {
|
|
821
1047
|
return null;
|
|
822
1048
|
}
|
|
823
1049
|
}
|
|
824
1050
|
function writeProjectConfig(config2) {
|
|
825
|
-
writeFileSync3(
|
|
1051
|
+
writeFileSync3(join6(process.cwd(), CONFIG_FILE), JSON.stringify(config2, null, 2));
|
|
826
1052
|
}
|
|
827
1053
|
var program = new Command();
|
|
828
1054
|
program.name("memory-core").description("Universal AI memory core \u2014 generate AI context files for all coding agents").version("0.1.0");
|
|
829
1055
|
program.command("init").description("Initialize memory-core in the current project").action(async () => {
|
|
830
|
-
console.log(
|
|
1056
|
+
console.log(chalk3.bold.cyan("\n memory-core init\n"));
|
|
831
1057
|
const detected = detectProject();
|
|
832
|
-
const envPath =
|
|
833
|
-
const hasEnv =
|
|
1058
|
+
const envPath = join6(process.cwd(), ".memory-core.env");
|
|
1059
|
+
const hasEnv = existsSync6(envPath) || existsSync6(join6(process.cwd(), ".env")) || !!process.env.DATABASE_URL;
|
|
834
1060
|
if (!hasEnv) {
|
|
835
|
-
console.log(
|
|
1061
|
+
console.log(chalk3.dim(" No .memory-core.env found \u2014 let's set up your database connection.\n"));
|
|
836
1062
|
const dbUser = process.env.USER ?? process.env.USERNAME ?? "postgres";
|
|
837
1063
|
const dbUrl = await input({
|
|
838
1064
|
message: "PostgreSQL connection URL?",
|
|
@@ -853,17 +1079,17 @@ program.command("init").description("Initialize memory-core in the current proje
|
|
|
853
1079
|
process.env.OLLAMA_URL = ollamaUrl;
|
|
854
1080
|
process.env.OLLAMA_MODEL = "nomic-embed-text";
|
|
855
1081
|
process.env.OLLAMA_CHAT_MODEL = "llama3.2";
|
|
856
|
-
const gitignorePath =
|
|
857
|
-
if (
|
|
858
|
-
const gi =
|
|
1082
|
+
const gitignorePath = join6(process.cwd(), ".gitignore");
|
|
1083
|
+
if (existsSync6(gitignorePath)) {
|
|
1084
|
+
const gi = readFileSync5(gitignorePath, "utf-8");
|
|
859
1085
|
if (!gi.includes(".memory-core.env")) {
|
|
860
1086
|
appendFileSync(gitignorePath, "\n.memory-core.env\n");
|
|
861
1087
|
}
|
|
862
1088
|
} else {
|
|
863
1089
|
writeFileSync3(gitignorePath, ".memory-core.env\n");
|
|
864
1090
|
}
|
|
865
|
-
console.log(
|
|
866
|
-
console.log(
|
|
1091
|
+
console.log(chalk3.green("\n \u2713 .memory-core.env created"));
|
|
1092
|
+
console.log(chalk3.gray(" Added to .gitignore \u2014 your DB credentials stay local.\n"));
|
|
867
1093
|
}
|
|
868
1094
|
const projectName = await input({
|
|
869
1095
|
message: "Project name?",
|
|
@@ -944,7 +1170,7 @@ program.command("init").description("Initialize memory-core in the current proje
|
|
|
944
1170
|
if (installCaveman) {
|
|
945
1171
|
const spinner2 = ora("Installing caveman token saver\u2026").start();
|
|
946
1172
|
try {
|
|
947
|
-
|
|
1173
|
+
execSync3(
|
|
948
1174
|
"curl -fsSL https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh | bash",
|
|
949
1175
|
{ stdio: "pipe", cwd: process.cwd() }
|
|
950
1176
|
);
|
|
@@ -966,7 +1192,7 @@ program.command("init").description("Initialize memory-core in the current proje
|
|
|
966
1192
|
program.command("sync").description("Re-pull memories and regenerate all AI agent files").action(async () => {
|
|
967
1193
|
const config2 = readProjectConfig();
|
|
968
1194
|
if (!config2) {
|
|
969
|
-
console.error(
|
|
1195
|
+
console.error(chalk3.red("No .memory-core.json found. Run: memory-core init"));
|
|
970
1196
|
process.exit(1);
|
|
971
1197
|
}
|
|
972
1198
|
const spinner = ora("Syncing memories\u2026").start();
|
|
@@ -998,7 +1224,7 @@ program.command("remember <text>").description("Save a new memory to the central
|
|
|
998
1224
|
let reason = opts.reason;
|
|
999
1225
|
if (!reason) {
|
|
1000
1226
|
reason = await input({
|
|
1001
|
-
message:
|
|
1227
|
+
message: chalk3.dim("Why does this rule exist? (optional \u2014 helps agents debug violations)"),
|
|
1002
1228
|
default: ""
|
|
1003
1229
|
});
|
|
1004
1230
|
}
|
|
@@ -1015,9 +1241,9 @@ program.command("remember <text>").description("Save a new memory to the central
|
|
|
1015
1241
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : [],
|
|
1016
1242
|
embedding
|
|
1017
1243
|
});
|
|
1018
|
-
const reasonLine = reason ?
|
|
1244
|
+
const reasonLine = reason ? chalk3.gray(`
|
|
1019
1245
|
Why: ${reason}`) : "";
|
|
1020
|
-
spinner.succeed(
|
|
1246
|
+
spinner.succeed(chalk3.green(`Memory saved: "${text}"`) + reasonLine);
|
|
1021
1247
|
} catch (err) {
|
|
1022
1248
|
spinner.fail(`Failed: ${err.message}`);
|
|
1023
1249
|
process.exit(1);
|
|
@@ -1035,16 +1261,16 @@ program.command("search <query>").description("Search memories using semantic si
|
|
|
1035
1261
|
);
|
|
1036
1262
|
spinner.stop();
|
|
1037
1263
|
if (results.length === 0) {
|
|
1038
|
-
console.log(
|
|
1264
|
+
console.log(chalk3.yellow("No memories found."));
|
|
1039
1265
|
} else {
|
|
1040
|
-
console.log(
|
|
1266
|
+
console.log(chalk3.bold(`
|
|
1041
1267
|
${results.length} results for "${query}"
|
|
1042
1268
|
`));
|
|
1043
1269
|
results.forEach((m, i) => {
|
|
1044
|
-
const sim = m.similarity ?
|
|
1045
|
-
console.log(
|
|
1046
|
-
console.log(
|
|
1047
|
-
if (m.tags?.length) console.log(
|
|
1270
|
+
const sim = m.similarity ? chalk3.gray(` (${(m.similarity * 100).toFixed(0)}% match)`) : "";
|
|
1271
|
+
console.log(chalk3.cyan(` ${i + 1}. [${m.type}] ${m.title ?? ""}`));
|
|
1272
|
+
console.log(chalk3.white(` ${m.content}`) + sim);
|
|
1273
|
+
if (m.tags?.length) console.log(chalk3.gray(` tags: ${m.tags.join(", ")}`));
|
|
1048
1274
|
console.log();
|
|
1049
1275
|
});
|
|
1050
1276
|
}
|
|
@@ -1057,7 +1283,7 @@ program.command("search <query>").description("Search memories using semantic si
|
|
|
1057
1283
|
program.command("seed").description("Load all predefined memories into the database").option("--arch <architecture>", "Only seed a specific architecture (e.g. clean-architecture)").option("--force", "Re-seed even if memories already exist", false).action(async (opts) => {
|
|
1058
1284
|
await runMigrations();
|
|
1059
1285
|
const filtered = opts.arch ? seeds.filter((s) => s.architecture === opts.arch || s.architecture === "global") : seeds;
|
|
1060
|
-
console.log(
|
|
1286
|
+
console.log(chalk3.bold.cyan(`
|
|
1061
1287
|
Seeding ${filtered.length} memories\u2026
|
|
1062
1288
|
`));
|
|
1063
1289
|
let saved = 0;
|
|
@@ -1076,14 +1302,14 @@ program.command("seed").description("Load all predefined memories into the datab
|
|
|
1076
1302
|
tags: seed.tags,
|
|
1077
1303
|
embedding
|
|
1078
1304
|
});
|
|
1079
|
-
spinner.succeed(
|
|
1305
|
+
spinner.succeed(chalk3.gray(`[${seed.architecture}] ${seed.title}`));
|
|
1080
1306
|
saved++;
|
|
1081
1307
|
} catch (err) {
|
|
1082
1308
|
spinner.warn(`Skipped \u2014 ${err.message}`);
|
|
1083
1309
|
skipped++;
|
|
1084
1310
|
}
|
|
1085
1311
|
}
|
|
1086
|
-
console.log(
|
|
1312
|
+
console.log(chalk3.bold.green(`
|
|
1087
1313
|
Done. ${saved} memories seeded, ${skipped} skipped.
|
|
1088
1314
|
`));
|
|
1089
1315
|
await closePool();
|
|
@@ -1092,21 +1318,21 @@ program.command("global").description("Sync your memory into every AI agent glob
|
|
|
1092
1318
|
const home = homedir();
|
|
1093
1319
|
const GLOBAL_TARGETS = [
|
|
1094
1320
|
// Claude Code
|
|
1095
|
-
{ label: "Claude Code", path:
|
|
1321
|
+
{ label: "Claude Code", path: join6(home, ".claude/CLAUDE.md"), type: "md" },
|
|
1096
1322
|
// GitHub Copilot (VS Code)
|
|
1097
|
-
{ label: "Copilot", path:
|
|
1323
|
+
{ label: "Copilot", path: join6(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-copilot" },
|
|
1098
1324
|
// Cursor global rules
|
|
1099
|
-
{ label: "Cursor", path:
|
|
1325
|
+
{ label: "Cursor", path: join6(home, ".cursor/rules/memory-core.mdc"), type: "md" },
|
|
1100
1326
|
// Cline (VS Code)
|
|
1101
|
-
{ label: "Cline", path:
|
|
1327
|
+
{ label: "Cline", path: join6(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-cline" },
|
|
1102
1328
|
// Continue.dev global config
|
|
1103
|
-
{ label: "Continue.dev", path:
|
|
1329
|
+
{ label: "Continue.dev", path: join6(home, ".continue/config.json"), type: "continue" },
|
|
1104
1330
|
// Aider global config
|
|
1105
|
-
{ label: "Aider", path:
|
|
1331
|
+
{ label: "Aider", path: join6(home, ".aider.conf.yml"), type: "aider" },
|
|
1106
1332
|
// Zed global settings
|
|
1107
|
-
{ label: "Zed AI", path:
|
|
1333
|
+
{ label: "Zed AI", path: join6(home, ".config/zed/settings.json"), type: "zed" },
|
|
1108
1334
|
// Windsurf global rules
|
|
1109
|
-
{ label: "Windsurf", path:
|
|
1335
|
+
{ label: "Windsurf", path: join6(home, ".windsurf/rules/memory-core.md"), type: "md" }
|
|
1110
1336
|
];
|
|
1111
1337
|
const spinner = ora("Fetching global memories\u2026").start();
|
|
1112
1338
|
let memories = [];
|
|
@@ -1134,9 +1360,9 @@ ${rulesText}
|
|
|
1134
1360
|
writeFileSync3(filePath, content, "utf-8");
|
|
1135
1361
|
};
|
|
1136
1362
|
const readJson = (filePath) => {
|
|
1137
|
-
if (!
|
|
1363
|
+
if (!existsSync6(filePath)) return {};
|
|
1138
1364
|
try {
|
|
1139
|
-
return JSON.parse(
|
|
1365
|
+
return JSON.parse(readFileSync5(filePath, "utf-8"));
|
|
1140
1366
|
} catch {
|
|
1141
1367
|
return {};
|
|
1142
1368
|
}
|
|
@@ -1184,14 +1410,14 @@ read:
|
|
|
1184
1410
|
skipped.push(target.label);
|
|
1185
1411
|
}
|
|
1186
1412
|
}
|
|
1187
|
-
spinner.succeed(
|
|
1188
|
-
console.log(
|
|
1189
|
-
written.forEach((l) => console.log(
|
|
1413
|
+
spinner.succeed(chalk3.green(`Synced ${memories.length} memories \u2192 ${written.length} agents`));
|
|
1414
|
+
console.log(chalk3.green("\n Updated:"));
|
|
1415
|
+
written.forEach((l) => console.log(chalk3.gray(` \u2713 ${l}`)));
|
|
1190
1416
|
if (skipped.length) {
|
|
1191
|
-
console.log(
|
|
1192
|
-
skipped.forEach((l) => console.log(
|
|
1417
|
+
console.log(chalk3.yellow("\n Skipped (not installed):"));
|
|
1418
|
+
skipped.forEach((l) => console.log(chalk3.gray(` \u2717 ${l}`)));
|
|
1193
1419
|
}
|
|
1194
|
-
console.log(
|
|
1420
|
+
console.log(chalk3.bold("\n Every AI agent now follows your memory globally.\n"));
|
|
1195
1421
|
await closePool();
|
|
1196
1422
|
});
|
|
1197
1423
|
var hook = program.command("hook").description("Manage the pre-commit rule enforcement hook");
|
|
@@ -1204,4 +1430,7 @@ hook.command("uninstall").description("Remove the pre-commit hook").action(() =>
|
|
|
1204
1430
|
program.command("check").description("Check staged changes against architecture rules (used by pre-commit hook)").option("--staged", "Check git staged diff (default behaviour)").option("--verbose", "Show model and diff details").action(async (opts) => {
|
|
1205
1431
|
await checkStaged({ verbose: opts.verbose ?? false });
|
|
1206
1432
|
});
|
|
1433
|
+
program.command("watch").description("Watch source files and check violations in real-time on every save").option("--path <dir>", "Directory to watch (default: current directory)").option("--verbose", "Show diff size and model details per file").action((opts) => {
|
|
1434
|
+
startWatch({ path: opts.path, verbose: opts.verbose });
|
|
1435
|
+
});
|
|
1207
1436
|
program.parseAsync(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shahmilsaari/memory-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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,6 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@inquirer/prompts": "^5.0.0",
|
|
21
21
|
"chalk": "^5.3.0",
|
|
22
|
+
"chokidar": "^5.0.0",
|
|
22
23
|
"commander": "^12.0.0",
|
|
23
24
|
"dotenv": "^16.4.0",
|
|
24
25
|
"handlebars": "^4.7.8",
|