@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.
Files changed (3) hide show
  1. package/README.md +218 -188
  2. package/dist/cli.js +294 -65
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -1,75 +1,46 @@
1
- # @shahmilsaari/memory-core
1
+ # memory-core
2
2
 
3
- Universal AI memory for any project. Install once, and every AI coding agent Copilot, Cursor, Claude Code, Windsurf, Cline, and 13 more — automatically follows your architecture rules, past decisions, and best practices. Backed by PostgreSQL + pgvector and fully local AI via Ollama. No OpenAI key required.
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
- ## Supported agents
15
+ ## What does it actually do?
37
16
 
38
- | Agent | File generated |
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
- Plus: `AI_RULES.md`, `ARCHITECTURE.md`, `PROJECT_MEMORY.md`
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
- ## Prerequisites
29
+ ## Before you start
60
30
 
61
- | Requirement | Purpose |
62
- |---|---|
63
- | PostgreSQL 14+ | Memory storage |
64
- | pgvector | Semantic similarity search |
65
- | Ollama + `nomic-embed-text` | Free local embeddings (no API key) |
66
- | Ollama + `llama3.2` | Pre-commit rule checking |
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
- ## Setup
41
+ ## Install
71
42
 
72
- ### 1. PostgreSQL
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 [postgresql.org](https://www.postgresql.org/download/windows/)
57
+ **Windows** — [download from postgresql.org](https://www.postgresql.org/download/windows/)
87
58
 
88
59
  ---
89
60
 
90
- ### 2. pgvector
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 with PostgreSQL@16** (brew pgvector only targets pg17+, build from source):
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
- Verify:
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
- ### 3. Ollama
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 [ollama.com](https://ollama.com/download)
107
+ **Windows** — [download from ollama.com](https://ollama.com/download)
133
108
 
134
- Pull the required models:
109
+ Then pull the two models:
135
110
  ```bash
136
- ollama pull nomic-embed-text # embeddings — used by remember/search/sync
137
- ollama pull llama3.2 # code analysis used by pre-commit hook
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
- ### 4. Create the database
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` contents:
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
- ### 5. Configure environment
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
- ### 6. Seed the database
152
+ ```bash
153
+ # 1. Go to your project
154
+ cd my-api
197
155
 
198
- Load 192 predefined best-practice rules (each with a reason explaining why it exists):
156
+ # 2. Initialize answers a few questions, generates all config files
157
+ npx @shahmilsaari/memory-core init
199
158
 
200
- ```bash
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
- Interactive prompts:
216
- ```
217
- Project name? → my-api
218
- Project type? → Backend / Frontend / Fullstack
219
- Backend architecture? → Clean Architecture
220
- Frontend framework? → (skipped for backend-only)
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
- Re-pulls memories from the DB and rewrites all 18 files. Run after `remember` or `seed`.
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
- Prompts: _"Why does this rule exist?"_ the reason is stored and shown to agents and developers when a violation is caught.
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 leak internal schema and expose sensitive fields to clients" \
253
- --tags "api,dto,response"
204
+ --reason "Raw DB entities expose internal schema and sensitive fields" \
205
+ --tags "api,dto"
254
206
  ```
255
207
 
256
- | Flag | Values | Default |
208
+ | Flag | Options | Default |
257
209
  |---|---|---|
258
210
  | `--type` | `decision` `rule` `pattern` `note` | `decision` |
259
211
  | `--scope` | `global` `project` | `project` |
260
- | `--reason` | free text | prompted interactively |
212
+ | `--reason` | any text | asked interactively |
261
213
  | `--tags` | comma-separated | none |
262
214
 
263
215
  ---
264
216
 
265
- ### `search` — Semantic search
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 architecture only
279
- npx @shahmilsaari/memory-core seed --force # re-seed existing entries
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` — Pre-commit enforcement
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` automatically checks staged source files against your architecture rules using Ollama (llama3.2). Violations are blocked with full context:
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 CLI, queues, or other
300
- entry points — it gets siloed and duplicated across handlers
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 in the domain layer tie business logic to
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, implement in infrastructure/
274
+ Fix: Define IUserRepository interface in domain/
310
275
 
311
- To bypass (not recommended): git commit --no-verify
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
- ### `check` — Manual rule check
286
+ ### `watch` — Catch violations as you type
322
287
 
323
288
  ```bash
324
- npx @shahmilsaari/memory-core check --staged # check staged files
325
- npx @shahmilsaari/memory-core check --staged --verbose # show model + diff details
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
- Same as the pre-commit hook — useful in CI/CD pipelines.
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
- ### `global` — Sync to all agents globally
323
+ ### `check` — Manual check (for CI)
333
324
 
334
- Writes your rules to the global config of every installed agent — so they follow your rules in every project without running `init`:
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
- | Agent | Global file |
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
- ### Backend
356
- | Profile | Best for |
380
+ Pick the one that matches how your project is structured.
381
+
382
+ **Backend**
383
+ | Profile | Use when… |
357
384
  |---|---|
358
- | **Clean Architecture** | Domain-driven APIs, strict layer separation |
359
- | **Modular Monolith** | Feature modules that could become microservices |
360
- | **MVC** | Standard web apps, controllers + services |
361
- | **Hexagonal** | Port/adapter isolation, highly testable cores |
362
- | **Next.js** | Next.js 13+ fullstack (server components + actions) |
363
- | **Laravel** | Laravel APIs with service-repository pattern |
364
-
365
- ### Frontend
366
- | Profile | Best for |
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
- | **React** | Functional components, hooks, React Query, Zustand |
369
- | **Vue 3** | Composition API, Pinia, composables |
370
- | **Angular** | Standalone components, signals, OnPush |
371
- | **Svelte** | Svelte 5 runes, SvelteKit load functions |
372
- | **Nuxt 3** | Fullstack Vue with SSR, server routes |
373
- | **React Native** | Mobile apps, FlatList, secure storage |
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 dual sections — one for backend, one for frontend — in every generated agent file.
402
+ Fullstack projects get both sections in every generated file.
376
403
 
377
404
  ---
378
405
 
379
- ## Caveman token saver (optional)
406
+ ## Caveman mode (optional)
380
407
 
381
- Compresses AI responses 65–75% by stripping filler words. Enabled optionally during `init`. Injected into every agent file automatically.
408
+ Cuts AI response length by 65–75% by removing filler words. Opt in during `init`.
382
409
 
383
- | Level | Style |
410
+ | Level | What it does |
384
411
  |---|---|
385
- | `lite` | Professional terseness |
386
- | `full` | Caveman mode |
387
- | `ultra` | Telegraphic, minimum words |
412
+ | `lite` | Professional and concise |
413
+ | `full` | Caveman-style short answers |
414
+ | `ultra` | Absolute minimum words |
388
415
 
389
416
  ---
390
417
 
391
- ## Typical workflow
418
+ ## Day-to-day workflow
392
419
 
393
420
  ```bash
394
- # New project
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
- # Save a decision mid-project
400
- npx @shahmilsaari/memory-core remember "All auth goes through middleware, never duplicated in controllers" \
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
- # Sync agent files after new decisions
431
+ # Refresh agent files after saving new memories
404
432
  npx @shahmilsaari/memory-core sync
405
433
 
406
- # Search before making an architectural decision
434
+ # Not sure how something was decided? Search.
407
435
  npx @shahmilsaari/memory-core search "caching strategy"
408
436
 
409
- # Try to commit code that breaks rules → hook blocks it with explanation
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
- | Variable | Required | Default | Description |
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 server URL |
421
- | `OLLAMA_MODEL` | No | `nomic-embed-text` | Embedding model |
422
- | `OLLAMA_CHAT_MODEL` | No | `llama3.2` | Chat model for pre-commit checks |
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 not installed for your PostgreSQL version. See [pgvector setup](#2-pgvector-setup).
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
- Pull the model: `ollama pull llama3.2`. Or set `OLLAMA_CHAT_MODEL=mistral` in `.env`.
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
- Create `.env` in your project root. See [Configure environment](#5-configure-environment).
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
- **Pre-commit hook flagging config/JSON files**
446
- The hook only checks source files (`.ts .tsx .js .jsx .py .php .rb .go .java .cs .swift .kt .rs .vue .svelte`). Config and env files are automatically skipped.
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
- | Feature | Description |
482
+ | | Feature |
453
483
  |---|---|
454
- | CI/CD check | GitHub Actions workflow fails PRs that violate rules |
455
- | Watch mode | `memory-core watch` checks files on save in real-time |
456
- | Violation memory | Auto-save caught violations as "never do X, do Y" memories |
457
- | Rule analytics | Track which rules break most, which files are worst offenders |
458
- | Team sync | `memory-core push/pull` shared memory pool across the whole team |
459
- | Memory review | `memory-core approve` team sign-off before rules propagate |
484
+ | | Watch modereal-time violation alerts on save |
485
+ | | CI/CDfail 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 chalk2 from "chalk";
6
+ import chalk3 from "chalk";
7
7
  import ora from "ora";
8
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2, appendFileSync } from "fs";
9
- import { join as join5, dirname as dirname2 } from "path";
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 execSync2 } from "child_process";
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 SOURCE_EXTENSIONS = /\.(ts|tsx|js|jsx|py|php|rb|go|java|cs|swift|kt|rs|vue|svelte)$/;
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 && SOURCE_EXTENSIONS.test(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
- chalk2.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 "),
790
- chalk2.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"),
791
- chalk2.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"),
792
- chalk2.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"),
793
- chalk2.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"),
794
- chalk2.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"),
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
- chalk2.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 ") + chalk2.bold.white("C O R E") + chalk2.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"),
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
- chalk2.green(` \u2713 Project `) + chalk2.bold(projectName),
799
- chalk2.green(` \u2713 Agents `) + chalk2.bold(`${agentCount} AI agents configured`),
800
- chalk2.green(` \u2713 Memory `) + chalk2.bold("PostgreSQL + pgvector ready"),
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
- chalk2.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"),
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
- chalk2.bold(" Every AI agent in this project now follows your rules."),
1030
+ chalk3.bold(" Every AI agent in this project now follows your rules."),
805
1031
  "",
806
- chalk2.gray(" Next steps:"),
807
- chalk2.gray(' memory-core remember "Your architectural decision"'),
808
- chalk2.gray(' memory-core search "query"'),
809
- chalk2.gray(" memory-core sync"),
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 = join5(process.cwd(), CONFIG_FILE);
817
- if (!existsSync5(path)) return null;
1042
+ const path = join6(process.cwd(), CONFIG_FILE);
1043
+ if (!existsSync6(path)) return null;
818
1044
  try {
819
- return JSON.parse(readFileSync4(path, "utf-8"));
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(join5(process.cwd(), CONFIG_FILE), JSON.stringify(config2, null, 2));
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(chalk2.bold.cyan("\n memory-core init\n"));
1056
+ console.log(chalk3.bold.cyan("\n memory-core init\n"));
831
1057
  const detected = detectProject();
832
- const envPath = join5(process.cwd(), ".memory-core.env");
833
- const hasEnv = existsSync5(envPath) || existsSync5(join5(process.cwd(), ".env")) || !!process.env.DATABASE_URL;
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(chalk2.dim(" No .memory-core.env found \u2014 let's set up your database connection.\n"));
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 = join5(process.cwd(), ".gitignore");
857
- if (existsSync5(gitignorePath)) {
858
- const gi = readFileSync4(gitignorePath, "utf-8");
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(chalk2.green("\n \u2713 .memory-core.env created"));
866
- console.log(chalk2.gray(" Added to .gitignore \u2014 your DB credentials stay local.\n"));
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
- execSync2(
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(chalk2.red("No .memory-core.json found. Run: memory-core init"));
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: chalk2.dim("Why does this rule exist? (optional \u2014 helps agents debug violations)"),
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 ? chalk2.gray(`
1244
+ const reasonLine = reason ? chalk3.gray(`
1019
1245
  Why: ${reason}`) : "";
1020
- spinner.succeed(chalk2.green(`Memory saved: "${text}"`) + reasonLine);
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(chalk2.yellow("No memories found."));
1264
+ console.log(chalk3.yellow("No memories found."));
1039
1265
  } else {
1040
- console.log(chalk2.bold(`
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 ? chalk2.gray(` (${(m.similarity * 100).toFixed(0)}% match)`) : "";
1045
- console.log(chalk2.cyan(` ${i + 1}. [${m.type}] ${m.title ?? ""}`));
1046
- console.log(chalk2.white(` ${m.content}`) + sim);
1047
- if (m.tags?.length) console.log(chalk2.gray(` tags: ${m.tags.join(", ")}`));
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(chalk2.bold.cyan(`
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(chalk2.gray(`[${seed.architecture}] ${seed.title}`));
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(chalk2.bold.green(`
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: join5(home, ".claude/CLAUDE.md"), type: "md" },
1321
+ { label: "Claude Code", path: join6(home, ".claude/CLAUDE.md"), type: "md" },
1096
1322
  // GitHub Copilot (VS Code)
1097
- { label: "Copilot", path: join5(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-copilot" },
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: join5(home, ".cursor/rules/memory-core.mdc"), type: "md" },
1325
+ { label: "Cursor", path: join6(home, ".cursor/rules/memory-core.mdc"), type: "md" },
1100
1326
  // Cline (VS Code)
1101
- { label: "Cline", path: join5(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-cline" },
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: join5(home, ".continue/config.json"), type: "continue" },
1329
+ { label: "Continue.dev", path: join6(home, ".continue/config.json"), type: "continue" },
1104
1330
  // Aider global config
1105
- { label: "Aider", path: join5(home, ".aider.conf.yml"), type: "aider" },
1331
+ { label: "Aider", path: join6(home, ".aider.conf.yml"), type: "aider" },
1106
1332
  // Zed global settings
1107
- { label: "Zed AI", path: join5(home, ".config/zed/settings.json"), type: "zed" },
1333
+ { label: "Zed AI", path: join6(home, ".config/zed/settings.json"), type: "zed" },
1108
1334
  // Windsurf global rules
1109
- { label: "Windsurf", path: join5(home, ".windsurf/rules/memory-core.md"), type: "md" }
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 (!existsSync5(filePath)) return {};
1363
+ if (!existsSync6(filePath)) return {};
1138
1364
  try {
1139
- return JSON.parse(readFileSync4(filePath, "utf-8"));
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(chalk2.green(`Synced ${memories.length} memories \u2192 ${written.length} agents`));
1188
- console.log(chalk2.green("\n Updated:"));
1189
- written.forEach((l) => console.log(chalk2.gray(` \u2713 ${l}`)));
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(chalk2.yellow("\n Skipped (not installed):"));
1192
- skipped.forEach((l) => console.log(chalk2.gray(` \u2717 ${l}`)));
1417
+ console.log(chalk3.yellow("\n Skipped (not installed):"));
1418
+ skipped.forEach((l) => console.log(chalk3.gray(` \u2717 ${l}`)));
1193
1419
  }
1194
- console.log(chalk2.bold("\n Every AI agent now follows your memory globally.\n"));
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.2",
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",