@shahmilsaari/memory-core 0.2.14 → 0.2.15
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 +41 -4
- package/dist/{chunk-25Y2KI7M.js → chunk-DUUQHRIB.js} +6 -2
- package/dist/cli.js +479 -98
- package/dist/{db-5X5LTUCB.js → db-VLOR7L6Q.js} +1 -1
- package/package.json +4 -1
- package/templates/AGENTS.md.hbs +2 -3
- package/templates/AI_RULES.md.hbs +1 -2
- package/templates/CLAUDE.md.hbs +1 -1
- package/templates/PROJECT_MEMORY.md.hbs +2 -2
- package/templates/copilot-instructions.md.hbs +1 -1
- package/templates/cursorrules.hbs +1 -1
package/README.md
CHANGED
|
@@ -169,6 +169,7 @@ For the full CLI reference, examples, and command behavior notes, see [COMMANDS.
|
|
|
169
169
|
|
|
170
170
|
New setup-management commands:
|
|
171
171
|
- `memory-core status` — show project name, provider/model, agents, hook state, generated files, and health checks
|
|
172
|
+
- `memory-core auto-sync` — show or change automatic agent file regeneration (`on`, `off`, or `status`)
|
|
172
173
|
- `memory-core provider set <provider>` — switch code-checking provider without rerunning `init`
|
|
173
174
|
- `memory-core model set <model>` — update chat or embedding model from the CLI
|
|
174
175
|
- `memory-core model doctor` — verify database, Ollama, model installation, and cloud API key presence
|
|
@@ -189,7 +190,7 @@ Walks you through:
|
|
|
189
190
|
- Hook mode — **advisory** (logs violations, never blocks) or **strict** (blocks commits)
|
|
190
191
|
- Whether to enable caveman mode (optional token saver)
|
|
191
192
|
|
|
192
|
-
Generates config files for every selected AI agent, saves your choices to `.memory-core.json`, and automatically adds all generated files to `.gitignore` under a `# memory-core generated files` block.
|
|
193
|
+
Generates config files for every selected AI agent, saves your choices to `.memory-core.json`, enables auto-sync by default, and automatically adds all generated files to `.gitignore` under a `# memory-core generated files` block.
|
|
193
194
|
|
|
194
195
|
At the end, the banner shows live ✓/✗ status for PostgreSQL and Ollama so you know everything is working.
|
|
195
196
|
|
|
@@ -197,7 +198,7 @@ At the end, the banner shows live ✓/✗ status for PostgreSQL and Ollama so yo
|
|
|
197
198
|
|
|
198
199
|
### `sync` — Refresh all agent files
|
|
199
200
|
|
|
200
|
-
|
|
201
|
+
Regenerate every agent file on demand. This still exists even though `remember`, `import`, `edit`, `remove`, `forget`, and `ignore` auto-sync by default after changing memories.
|
|
201
202
|
|
|
202
203
|
```bash
|
|
203
204
|
npx @shahmilsaari/memory-core sync
|
|
@@ -209,6 +210,18 @@ Multi-selects which agents to sync (pre-checked from your `.memory-core.json` co
|
|
|
209
210
|
3 updated, 11 already up to date
|
|
210
211
|
```
|
|
211
212
|
|
|
213
|
+
Use `--no-sync` on memory-changing commands when you want to save database changes without regenerating files immediately.
|
|
214
|
+
|
|
215
|
+
Manage the default:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
npx @shahmilsaari/memory-core auto-sync # show current setting
|
|
219
|
+
npx @shahmilsaari/memory-core auto-sync off # save only, sync manually later
|
|
220
|
+
npx @shahmilsaari/memory-core auto-sync on # restore default behavior
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Auto-sync failures are non-fatal. If regeneration cannot run, memory-core prints the reason and tells you to run `memory-core sync` manually.
|
|
224
|
+
|
|
212
225
|
---
|
|
213
226
|
|
|
214
227
|
### `remember` — Save a decision
|
|
@@ -358,12 +371,14 @@ npx @shahmilsaari/memory-core hook uninstall # remove the hook
|
|
|
358
371
|
npx @shahmilsaari/memory-core list
|
|
359
372
|
```
|
|
360
373
|
|
|
361
|
-
|
|
374
|
+
By default, shows memories relevant to the current project context: detected stack, current project name, plus shared/global memories. Use `--all` for the old database-wide view.
|
|
362
375
|
|
|
363
376
|
```bash
|
|
377
|
+
npx @shahmilsaari/memory-core list --all
|
|
364
378
|
npx @shahmilsaari/memory-core list --type rule
|
|
365
379
|
npx @shahmilsaari/memory-core list --scope global
|
|
366
380
|
npx @shahmilsaari/memory-core list --arch clean-architecture
|
|
381
|
+
npx @shahmilsaari/memory-core list --project my-api
|
|
367
382
|
npx @shahmilsaari/memory-core list --limit 50
|
|
368
383
|
```
|
|
369
384
|
|
|
@@ -372,6 +387,9 @@ npx @shahmilsaari/memory-core list --limit 50
|
|
|
372
387
|
| `--type <type>` | Filter by type: `decision` `rule` `pattern` `note` |
|
|
373
388
|
| `--scope <scope>` | Filter by scope: `global` `project` |
|
|
374
389
|
| `--arch <architecture>` | Filter by architecture profile |
|
|
390
|
+
| `--project <name>` | Filter by project name |
|
|
391
|
+
| `--all` | Show the full shared database |
|
|
392
|
+
| `--current` | Explicitly use the current project context |
|
|
375
393
|
| `--limit <n>` | Max results (default 200) |
|
|
376
394
|
|
|
377
395
|
---
|
|
@@ -568,7 +586,7 @@ npx @shahmilsaari/memory-core seed # load 281 best-practice rules
|
|
|
568
586
|
npx @shahmilsaari/memory-core remember "All auth goes through middleware, never in controllers" \
|
|
569
587
|
--type decision --scope global
|
|
570
588
|
|
|
571
|
-
#
|
|
589
|
+
# Optional: refresh agent files manually anytime
|
|
572
590
|
npx @shahmilsaari/memory-core sync
|
|
573
591
|
|
|
574
592
|
# Not sure how something was decided? Search.
|
|
@@ -656,6 +674,24 @@ Save an ignore pattern: `npx @shahmilsaari/memory-core ignore "your exception he
|
|
|
656
674
|
|
|
657
675
|
---
|
|
658
676
|
|
|
677
|
+
## Release checks
|
|
678
|
+
|
|
679
|
+
Before publishing, run the same checks as CI:
|
|
680
|
+
|
|
681
|
+
```bash
|
|
682
|
+
npm run lint
|
|
683
|
+
npm run typecheck
|
|
684
|
+
npm test
|
|
685
|
+
npm run build
|
|
686
|
+
npm run smoke:pack
|
|
687
|
+
npm run smoke:npx
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
`smoke:pack` verifies the npm tarball includes the built CLI plus templates/profiles.
|
|
691
|
+
`smoke:npx` installs that tarball into a fresh temporary project and runs `npx --no-install memory-core init --quick`.
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
659
695
|
## Roadmap
|
|
660
696
|
|
|
661
697
|
| | Feature |
|
|
@@ -669,6 +705,7 @@ Save an ignore pattern: `npx @shahmilsaari/memory-core ignore "your exception he
|
|
|
669
705
|
| ✓ | CI/CD — `ci-setup` generates GitHub Actions workflow for PR enforcement |
|
|
670
706
|
| ✓ | Violation stats — see which rules fire most and which files break most |
|
|
671
707
|
| ✓ | Agent selection — choose which agents to generate files for during init |
|
|
708
|
+
| ✓ | Auto-sync — memory-changing commands refresh selected agent files by default |
|
|
672
709
|
| ✓ | Export / import — portable memories.json for version control and team sharing |
|
|
673
710
|
| ✓ | List / remove / edit — full CRUD for stored memories |
|
|
674
711
|
| ✓ | False positive tagging — `ignore` command saves exceptions for hook and watcher |
|
|
@@ -84,12 +84,16 @@ async function listMemories(filters = {}) {
|
|
|
84
84
|
if (filters.architecture) {
|
|
85
85
|
if (Array.isArray(filters.architecture)) {
|
|
86
86
|
params.push(filters.architecture);
|
|
87
|
-
where.push(`architecture = ANY($${params.length})`);
|
|
87
|
+
where.push(filters.includeGlobal ? `(architecture IS NULL OR architecture = ANY($${params.length}))` : `architecture = ANY($${params.length})`);
|
|
88
88
|
} else {
|
|
89
89
|
params.push(filters.architecture);
|
|
90
|
-
where.push(`architecture = $${params.length}`);
|
|
90
|
+
where.push(filters.includeGlobal ? `(architecture IS NULL OR architecture = $${params.length})` : `architecture = $${params.length}`);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
if (filters.projectName) {
|
|
94
|
+
params.push(filters.projectName);
|
|
95
|
+
where.push(filters.includeGlobal ? `(project_name IS NULL OR project_name = $${params.length})` : `project_name = $${params.length}`);
|
|
96
|
+
}
|
|
93
97
|
if (filters.tags?.length) {
|
|
94
98
|
params.push(filters.tags);
|
|
95
99
|
where.push(`tags && $${params.length}::text[]`);
|
package/dist/cli.js
CHANGED
|
@@ -7,13 +7,14 @@ import {
|
|
|
7
7
|
deleteMemories,
|
|
8
8
|
deleteMemory,
|
|
9
9
|
getMemory,
|
|
10
|
+
getPool,
|
|
10
11
|
listMemories,
|
|
11
12
|
runMigrations,
|
|
12
13
|
saveMemory,
|
|
13
14
|
searchMemories,
|
|
14
15
|
updateMemory,
|
|
15
16
|
upsertMemory
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-DUUQHRIB.js";
|
|
17
18
|
import "./chunk-KSLFLWB4.js";
|
|
18
19
|
|
|
19
20
|
// src/cli.ts
|
|
@@ -24,7 +25,6 @@ import ora from "ora";
|
|
|
24
25
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync2, appendFileSync, rmSync, unlinkSync as unlinkSync2 } from "fs";
|
|
25
26
|
import { join as join6, dirname as dirname2 } from "path";
|
|
26
27
|
import { homedir } from "os";
|
|
27
|
-
import { execSync as execSync2 } from "child_process";
|
|
28
28
|
|
|
29
29
|
// src/generator.ts
|
|
30
30
|
import { readFileSync as readFileSync2, readdirSync, writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
|
|
@@ -112,6 +112,17 @@ var FRAMEWORK_ARCHITECTURE_MAP = {
|
|
|
112
112
|
"Vue.js": ["vue"],
|
|
113
113
|
Svelte: ["svelte"]
|
|
114
114
|
};
|
|
115
|
+
var KNOWN_ARCHITECTURE_KEYS = /* @__PURE__ */ new Set([
|
|
116
|
+
...Object.values(FRAMEWORK_ARCHITECTURE_MAP).flat(),
|
|
117
|
+
"angular",
|
|
118
|
+
"clean-architecture",
|
|
119
|
+
"express",
|
|
120
|
+
"fastify",
|
|
121
|
+
"hexagonal",
|
|
122
|
+
"modular-monolith",
|
|
123
|
+
"mvc",
|
|
124
|
+
"react-native"
|
|
125
|
+
]);
|
|
115
126
|
function normalizeText(value) {
|
|
116
127
|
return value.toLowerCase().replace(/[`"'()[\]{}.,:;!?/\\<>|=*+-]/g, " ").replace(/\s+/g, " ").trim();
|
|
117
128
|
}
|
|
@@ -139,6 +150,33 @@ function mergeMemory(primary, secondary) {
|
|
|
139
150
|
reason
|
|
140
151
|
};
|
|
141
152
|
}
|
|
153
|
+
function memoryArchitectureKeys(memory) {
|
|
154
|
+
if (memory.architecture && memory.architecture !== "global") {
|
|
155
|
+
return [memory.architecture];
|
|
156
|
+
}
|
|
157
|
+
return (memory.tags ?? []).filter((tag) => KNOWN_ARCHITECTURE_KEYS.has(tag));
|
|
158
|
+
}
|
|
159
|
+
function getStackReason(memory, activeArchitectures) {
|
|
160
|
+
const architectureKeys = memoryArchitectureKeys(memory);
|
|
161
|
+
if (architectureKeys.length === 0) {
|
|
162
|
+
return {
|
|
163
|
+
included: true,
|
|
164
|
+
reason: "global memory: no architecture-specific tag"
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const matched = architectureKeys.filter((architecture) => activeArchitectures.has(architecture));
|
|
168
|
+
if (matched.length > 0) {
|
|
169
|
+
return {
|
|
170
|
+
included: true,
|
|
171
|
+
reason: `matched active architecture: ${matched.join(", ")}`
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const active = [...activeArchitectures].join(", ") || "none detected";
|
|
175
|
+
return {
|
|
176
|
+
included: false,
|
|
177
|
+
reason: `excluded: tagged for ${architectureKeys.join(", ")}; active stack is ${active}`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
142
180
|
function dedupeMemories(memories, threshold = 0.8) {
|
|
143
181
|
const deduped = [];
|
|
144
182
|
for (const memory of memories) {
|
|
@@ -160,6 +198,9 @@ function inferProjectArchitectures(cwd = process.cwd(), config) {
|
|
|
160
198
|
const inferred = /* @__PURE__ */ new Set();
|
|
161
199
|
if (config?.backendArchitecture) inferred.add(config.backendArchitecture);
|
|
162
200
|
if (config?.frontendFramework) inferred.add(config.frontendFramework);
|
|
201
|
+
if (config?.projectType === "backend" && !config.backendArchitecture) {
|
|
202
|
+
inferred.add("clean-architecture");
|
|
203
|
+
}
|
|
163
204
|
const detected = detectProject(cwd);
|
|
164
205
|
for (const architecture of FRAMEWORK_ARCHITECTURE_MAP[detected.framework] ?? []) {
|
|
165
206
|
inferred.add(architecture);
|
|
@@ -169,13 +210,63 @@ function inferProjectArchitectures(cwd = process.cwd(), config) {
|
|
|
169
210
|
function getAllowPatterns(config) {
|
|
170
211
|
return [...new Set(config?.allowPatterns?.filter(Boolean) ?? [])];
|
|
171
212
|
}
|
|
213
|
+
function filterRelevantMemories(memories, config, cwd = process.cwd()) {
|
|
214
|
+
return explainMemorySelection(memories, config, cwd).included;
|
|
215
|
+
}
|
|
216
|
+
function explainMemorySelection(memories, config, cwd = process.cwd(), threshold = 0.8) {
|
|
217
|
+
const activeArchitectures = inferProjectArchitectures(cwd, config);
|
|
218
|
+
const activeSet = new Set(activeArchitectures);
|
|
219
|
+
const included = [];
|
|
220
|
+
const decisions = [];
|
|
221
|
+
for (const memory of memories) {
|
|
222
|
+
const stackDecision = getStackReason(memory, activeSet);
|
|
223
|
+
if (!stackDecision.included) {
|
|
224
|
+
decisions.push({
|
|
225
|
+
memory,
|
|
226
|
+
status: "excluded",
|
|
227
|
+
reason: stackDecision.reason
|
|
228
|
+
});
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const existingIndex = included.findIndex((candidate) => {
|
|
232
|
+
if (candidate.content_hash && memory.content_hash && candidate.content_hash === memory.content_hash) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return similarityScore(candidate.content, memory.content) >= threshold;
|
|
236
|
+
});
|
|
237
|
+
if (existingIndex === -1) {
|
|
238
|
+
included.push(memory);
|
|
239
|
+
decisions.push({
|
|
240
|
+
memory,
|
|
241
|
+
status: "included",
|
|
242
|
+
reason: stackDecision.reason
|
|
243
|
+
});
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
included[existingIndex] = mergeMemory(included[existingIndex], memory);
|
|
247
|
+
decisions.push({
|
|
248
|
+
memory,
|
|
249
|
+
status: "excluded",
|
|
250
|
+
reason: `duplicate or near-duplicate of memory #${included[existingIndex].id}`
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
included,
|
|
255
|
+
excluded: decisions.filter((decision) => decision.status === "excluded"),
|
|
256
|
+
decisions,
|
|
257
|
+
activeArchitectures
|
|
258
|
+
};
|
|
259
|
+
}
|
|
172
260
|
function buildContextQuery(parts, maxLength = 1200) {
|
|
173
261
|
return parts.filter(Boolean).join("\n").slice(0, maxLength);
|
|
174
262
|
}
|
|
175
263
|
async function retrieveContextualMemories(options) {
|
|
264
|
+
return (await retrieveMemorySelection(options)).included;
|
|
265
|
+
}
|
|
266
|
+
async function retrieveMemorySelection(options) {
|
|
176
267
|
const architectures = inferProjectArchitectures(options.cwd, options.config);
|
|
177
268
|
const memories = await retrieve(options.query, architectures, options.limit ?? 15);
|
|
178
|
-
return
|
|
269
|
+
return explainMemorySelection(memories, options.config, options.cwd);
|
|
179
270
|
}
|
|
180
271
|
|
|
181
272
|
// src/generator.ts
|
|
@@ -229,10 +320,15 @@ function listProfiles(layer) {
|
|
|
229
320
|
if (layer === "frontend") return all.filter((p) => p.layer === "frontend" || p.layer === "fullstack");
|
|
230
321
|
return all;
|
|
231
322
|
}
|
|
232
|
-
function buildTemplateData(options) {
|
|
323
|
+
function buildTemplateData(options, cwd = process.cwd()) {
|
|
233
324
|
const backend = options.backendArchitecture ? loadProfile(options.backendArchitecture) : null;
|
|
234
325
|
const frontend = options.frontendFramework ? loadProfile(options.frontendFramework) : null;
|
|
235
|
-
const dedupedMemories =
|
|
326
|
+
const dedupedMemories = filterRelevantMemories(options.memories, {
|
|
327
|
+
projectType: options.projectType,
|
|
328
|
+
backendArchitecture: options.backendArchitecture,
|
|
329
|
+
frontendFramework: options.frontendFramework,
|
|
330
|
+
language: options.language
|
|
331
|
+
}, cwd);
|
|
236
332
|
const allRules = [
|
|
237
333
|
...backend?.rules ?? [],
|
|
238
334
|
...frontend?.rules ?? []
|
|
@@ -300,7 +396,7 @@ function writeFile(filePath, content) {
|
|
|
300
396
|
return "written";
|
|
301
397
|
}
|
|
302
398
|
async function generate(options, cwd = process.cwd(), onlyAgents) {
|
|
303
|
-
const data = buildTemplateData(options);
|
|
399
|
+
const data = buildTemplateData(options, cwd);
|
|
304
400
|
const written = [];
|
|
305
401
|
const skipped = [];
|
|
306
402
|
const files = onlyAgents ? OUTPUT_FILES.filter((f) => onlyAgents.includes(f.agent)) : OUTPUT_FILES;
|
|
@@ -922,7 +1018,7 @@ async function promptToSaveViolations(violations) {
|
|
|
922
1018
|
default: selected.reason ?? selected.issue ?? ""
|
|
923
1019
|
});
|
|
924
1020
|
const { embed: embed2 } = await import("./embedding-PAYD2JYW.js");
|
|
925
|
-
const { upsertMemory: upsertMemory2 } = await import("./db-
|
|
1021
|
+
const { upsertMemory: upsertMemory2 } = await import("./db-VLOR7L6Q.js");
|
|
926
1022
|
await upsertMemory2({
|
|
927
1023
|
type: "rule",
|
|
928
1024
|
scope: "project",
|
|
@@ -939,7 +1035,7 @@ async function promptToSaveViolations(violations) {
|
|
|
939
1035
|
}
|
|
940
1036
|
async function loadIgnorePatterns() {
|
|
941
1037
|
try {
|
|
942
|
-
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-
|
|
1038
|
+
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-VLOR7L6Q.js");
|
|
943
1039
|
const ignores = await listMemories2({ type: "ignore", limit: 1e3 });
|
|
944
1040
|
await closePool2();
|
|
945
1041
|
return ignores.map((ignore) => ignore.content);
|
|
@@ -996,6 +1092,124 @@ ${violation.file}`.toLowerCase();
|
|
|
996
1092
|
return !allowPatterns.some((pattern) => haystack.includes(pattern.toLowerCase()));
|
|
997
1093
|
});
|
|
998
1094
|
}
|
|
1095
|
+
function normalizeViolation(value) {
|
|
1096
|
+
if (!value || typeof value !== "object") return null;
|
|
1097
|
+
const candidate = value;
|
|
1098
|
+
if (typeof candidate.rule !== "string" || typeof candidate.issue !== "string") return null;
|
|
1099
|
+
return {
|
|
1100
|
+
rule: candidate.rule,
|
|
1101
|
+
file: typeof candidate.file === "string" ? candidate.file : "diff",
|
|
1102
|
+
line: typeof candidate.line === "number" ? candidate.line : void 0,
|
|
1103
|
+
issue: candidate.issue,
|
|
1104
|
+
suggestion: typeof candidate.suggestion === "string" ? candidate.suggestion : void 0,
|
|
1105
|
+
reason: typeof candidate.reason === "string" ? candidate.reason : void 0
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
function parseModelViolations(raw) {
|
|
1109
|
+
const candidates = [
|
|
1110
|
+
raw,
|
|
1111
|
+
raw.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "")
|
|
1112
|
+
];
|
|
1113
|
+
const objectStart = raw.indexOf("{");
|
|
1114
|
+
const objectEnd = raw.lastIndexOf("}");
|
|
1115
|
+
if (objectStart !== -1 && objectEnd > objectStart) {
|
|
1116
|
+
candidates.push(raw.slice(objectStart, objectEnd + 1));
|
|
1117
|
+
}
|
|
1118
|
+
const arrayStart = raw.indexOf("[");
|
|
1119
|
+
const arrayEnd = raw.lastIndexOf("]");
|
|
1120
|
+
if (arrayStart !== -1 && arrayEnd > arrayStart) {
|
|
1121
|
+
candidates.push(raw.slice(arrayStart, arrayEnd + 1));
|
|
1122
|
+
}
|
|
1123
|
+
for (const candidate of candidates) {
|
|
1124
|
+
try {
|
|
1125
|
+
const parsed = JSON.parse(candidate);
|
|
1126
|
+
const items = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.violations) ? parsed.violations : parsed?.rule ? [parsed] : null;
|
|
1127
|
+
if (!items) continue;
|
|
1128
|
+
return {
|
|
1129
|
+
valid: true,
|
|
1130
|
+
violations: items.map(normalizeViolation).filter((violation) => violation !== null)
|
|
1131
|
+
};
|
|
1132
|
+
} catch {
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return { valid: false, violations: [] };
|
|
1136
|
+
}
|
|
1137
|
+
function getAddedLines(diff) {
|
|
1138
|
+
const lines = [];
|
|
1139
|
+
let currentFile = "diff";
|
|
1140
|
+
let newLineNumber = 0;
|
|
1141
|
+
for (const line of diff.split("\n")) {
|
|
1142
|
+
if (line.startsWith("+++ b/")) {
|
|
1143
|
+
currentFile = line.slice("+++ b/".length);
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
const hunk = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
1147
|
+
if (hunk) {
|
|
1148
|
+
newLineNumber = Number(hunk[1]);
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
1152
|
+
lines.push({
|
|
1153
|
+
file: currentFile,
|
|
1154
|
+
line: Number.isFinite(newLineNumber) ? newLineNumber : void 0,
|
|
1155
|
+
content: line.slice(1)
|
|
1156
|
+
});
|
|
1157
|
+
newLineNumber += 1;
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
if (!line.startsWith("-") && newLineNumber > 0) {
|
|
1161
|
+
newLineNumber += 1;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return lines;
|
|
1165
|
+
}
|
|
1166
|
+
function dedupeViolations(violations) {
|
|
1167
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1168
|
+
const deduped = [];
|
|
1169
|
+
for (const violation of violations) {
|
|
1170
|
+
const key = [
|
|
1171
|
+
violation.rule,
|
|
1172
|
+
violation.file,
|
|
1173
|
+
violation.line ?? "",
|
|
1174
|
+
violation.issue
|
|
1175
|
+
].join("\0");
|
|
1176
|
+
if (seen.has(key)) continue;
|
|
1177
|
+
seen.add(key);
|
|
1178
|
+
deduped.push(violation);
|
|
1179
|
+
}
|
|
1180
|
+
return deduped;
|
|
1181
|
+
}
|
|
1182
|
+
function findDeterministicViolations(diff, rules, avoids, allowPatterns = []) {
|
|
1183
|
+
const rulePhrases = rules.flatMap(
|
|
1184
|
+
(rule) => extractForbiddenPhrases(rule).map((phrase) => ({ rule, phrase }))
|
|
1185
|
+
);
|
|
1186
|
+
const avoidPhrases = avoids.map((avoid) => ({
|
|
1187
|
+
rule: `Avoid: ${avoid}`,
|
|
1188
|
+
phrase: avoid.toLowerCase()
|
|
1189
|
+
}));
|
|
1190
|
+
const phrases = [...rulePhrases, ...avoidPhrases].filter((item) => item.phrase.length > 0);
|
|
1191
|
+
if (phrases.length === 0) return [];
|
|
1192
|
+
const violations = [];
|
|
1193
|
+
for (const addedLine of getAddedLines(diff)) {
|
|
1194
|
+
const normalizedLine = addedLine.content.toLowerCase();
|
|
1195
|
+
if (allowPatterns.some((pattern) => normalizedLine.includes(pattern.toLowerCase()))) {
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
for (const { rule, phrase } of phrases) {
|
|
1199
|
+
if (normalizedLine.includes(phrase)) {
|
|
1200
|
+
violations.push({
|
|
1201
|
+
rule,
|
|
1202
|
+
file: addedLine.file,
|
|
1203
|
+
line: addedLine.line,
|
|
1204
|
+
issue: `Added line contains forbidden phrase: "${phrase}"`,
|
|
1205
|
+
suggestion: "Remove this pattern or add an explicit ignore memory if it is intentional.",
|
|
1206
|
+
reason: reasonMap.get(rule)
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
return dedupeViolations(violations);
|
|
1212
|
+
}
|
|
999
1213
|
async function verifyViolations(diff, violations, allowPatterns, debug) {
|
|
1000
1214
|
if (violations.length === 0) return violations;
|
|
1001
1215
|
const systemPrompt = `You are verifying candidate architecture violations.
|
|
@@ -1023,9 +1237,8 @@ ${JSON.stringify(violations, null, 2)}`;
|
|
|
1023
1237
|
{ role: "system", content: systemPrompt },
|
|
1024
1238
|
{ role: "user", content: userPrompt }
|
|
1025
1239
|
]);
|
|
1026
|
-
const parsed =
|
|
1027
|
-
if (
|
|
1028
|
-
if (Array.isArray(parsed)) return parsed;
|
|
1240
|
+
const parsed = parseModelViolations(raw);
|
|
1241
|
+
if (parsed.valid) return parsed.violations;
|
|
1029
1242
|
return violations;
|
|
1030
1243
|
} catch {
|
|
1031
1244
|
return violations;
|
|
@@ -1142,7 +1355,8 @@ Do not include any text outside the JSON object.`;
|
|
|
1142
1355
|
console.log(chalk.dim(diffToSend));
|
|
1143
1356
|
console.log(chalk.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"));
|
|
1144
1357
|
}
|
|
1145
|
-
|
|
1358
|
+
const deterministicViolations = findDeterministicViolations(diff, rules, avoids, allowPatterns);
|
|
1359
|
+
let modelViolations = [];
|
|
1146
1360
|
try {
|
|
1147
1361
|
const raw = await callChatModel([
|
|
1148
1362
|
{ role: "system", content: systemPrompt },
|
|
@@ -1153,36 +1367,29 @@ ${diffToSend}` }
|
|
|
1153
1367
|
if (options.verbose || options.debug) {
|
|
1154
1368
|
console.log(chalk.gray(` raw response: ${options.debug ? raw : raw.slice(0, 200)}`));
|
|
1155
1369
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
violations = parsed.violations;
|
|
1162
|
-
} else if (parsed?.rule) {
|
|
1163
|
-
violations = [parsed];
|
|
1164
|
-
} else {
|
|
1165
|
-
violations = [];
|
|
1166
|
-
}
|
|
1167
|
-
} catch {
|
|
1168
|
-
violations = [];
|
|
1370
|
+
const parsed = parseModelViolations(raw);
|
|
1371
|
+
if (parsed.valid) {
|
|
1372
|
+
modelViolations = parsed.violations;
|
|
1373
|
+
} else {
|
|
1374
|
+
console.log(chalk.yellow(" \u26A0 AI returned invalid JSON \u2014 using deterministic checks only."));
|
|
1169
1375
|
}
|
|
1170
1376
|
} catch (err) {
|
|
1171
1377
|
if (err.message?.startsWith("MODEL_NOT_FOUND:")) {
|
|
1172
1378
|
printModelMissing(err.message.split(":")[1]);
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
console.log(chalk.yellow("\n \u26A0 Ollama not running \u2014 skipping rule check."));
|
|
1379
|
+
modelViolations = [];
|
|
1380
|
+
} else if (err.cause?.code === "ECONNREFUSED" || err.message?.includes("ECONNREFUSED")) {
|
|
1381
|
+
console.log(chalk.yellow("\n \u26A0 Ollama not running \u2014 using deterministic checks only."));
|
|
1177
1382
|
console.log(chalk.gray(" Start it: ollama serve\n"));
|
|
1178
|
-
|
|
1383
|
+
modelViolations = [];
|
|
1384
|
+
} else {
|
|
1385
|
+
console.log(chalk.yellow(`
|
|
1386
|
+
\u26A0 AI rule check failed: ${err.message}`));
|
|
1387
|
+
console.log(chalk.gray(" Using deterministic checks only.\n"));
|
|
1388
|
+
modelViolations = [];
|
|
1179
1389
|
}
|
|
1180
|
-
console.log(chalk.yellow(`
|
|
1181
|
-
\u26A0 Rule check failed: ${err.message}
|
|
1182
|
-
`));
|
|
1183
|
-
return;
|
|
1184
1390
|
}
|
|
1185
|
-
|
|
1391
|
+
modelViolations = await verifyViolations(diff, modelViolations, allowPatterns, options.debug ?? false);
|
|
1392
|
+
let violations = dedupeViolations([...deterministicViolations, ...modelViolations]);
|
|
1186
1393
|
violations = applyAllowPatterns(violations, allowPatterns);
|
|
1187
1394
|
if (violations.length === 0) {
|
|
1188
1395
|
console.log(chalk.green(" \u2713 No rule violations \u2014 commit allowed.\n"));
|
|
@@ -1454,7 +1661,7 @@ ${JSON.stringify(violations, null, 2)}`;
|
|
|
1454
1661
|
}
|
|
1455
1662
|
async function loadIgnorePatterns2() {
|
|
1456
1663
|
try {
|
|
1457
|
-
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-
|
|
1664
|
+
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-VLOR7L6Q.js");
|
|
1458
1665
|
const ignores = await listMemories2({ type: "ignore", limit: 1e3 });
|
|
1459
1666
|
await closePool2();
|
|
1460
1667
|
return ignores.map((ignore) => ignore.content);
|
|
@@ -1633,6 +1840,55 @@ async function startWatch(options = {}) {
|
|
|
1633
1840
|
});
|
|
1634
1841
|
}
|
|
1635
1842
|
|
|
1843
|
+
// src/remote-install.ts
|
|
1844
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
1845
|
+
var CAVEMAN_INSTALL_URL = "https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh";
|
|
1846
|
+
var MAX_INSTALLER_BYTES = 2e5;
|
|
1847
|
+
var TRUSTED_INSTALL_HOSTS = /* @__PURE__ */ new Set(["raw.githubusercontent.com"]);
|
|
1848
|
+
function assertTrustedInstallerUrl(url) {
|
|
1849
|
+
const parsed = new URL(url);
|
|
1850
|
+
if (parsed.protocol !== "https:") {
|
|
1851
|
+
throw new Error("Remote installer URL must use HTTPS");
|
|
1852
|
+
}
|
|
1853
|
+
if (!TRUSTED_INSTALL_HOSTS.has(parsed.hostname)) {
|
|
1854
|
+
throw new Error(`Remote installer host is not trusted: ${parsed.hostname}`);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
function defaultRunScript(script) {
|
|
1858
|
+
return spawnSync3("bash", ["-s"], {
|
|
1859
|
+
input: script,
|
|
1860
|
+
encoding: "utf-8",
|
|
1861
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
async function fetchRemoteInstaller(url, fetchImpl = fetch) {
|
|
1865
|
+
assertTrustedInstallerUrl(url);
|
|
1866
|
+
const response = await fetchImpl(url, {
|
|
1867
|
+
signal: AbortSignal.timeout(15e3)
|
|
1868
|
+
});
|
|
1869
|
+
if (!response.ok) {
|
|
1870
|
+
throw new Error(`Failed to download installer (${response.status})`);
|
|
1871
|
+
}
|
|
1872
|
+
const script = await response.text();
|
|
1873
|
+
if (script.length > MAX_INSTALLER_BYTES) {
|
|
1874
|
+
throw new Error(`Installer is too large (${script.length} bytes)`);
|
|
1875
|
+
}
|
|
1876
|
+
if (script.includes("\0")) {
|
|
1877
|
+
throw new Error("Installer contains null bytes");
|
|
1878
|
+
}
|
|
1879
|
+
return script;
|
|
1880
|
+
}
|
|
1881
|
+
async function installCavemanTokenSaver(options = {}) {
|
|
1882
|
+
const url = options.url ?? CAVEMAN_INSTALL_URL;
|
|
1883
|
+
const script = await fetchRemoteInstaller(url, options.fetchImpl ?? fetch);
|
|
1884
|
+
const result = (options.runScript ?? defaultRunScript)(script);
|
|
1885
|
+
if (result.error) throw result.error;
|
|
1886
|
+
if (result.status !== 0) {
|
|
1887
|
+
const stderr = result.stderr ? String(result.stderr).trim() : "";
|
|
1888
|
+
throw new Error(stderr || `Installer exited with status ${result.status}`);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1636
1892
|
// src/cli.ts
|
|
1637
1893
|
function printBanner(projectName, agentCount, status) {
|
|
1638
1894
|
const pg = status ? status.postgresOk ? chalk3.green(" \u2713 PostgreSQL ") + chalk3.bold("connected") : chalk3.red(" \u2717 PostgreSQL ") + chalk3.bold("not connected \u2014 check DATABASE_URL") : chalk3.green(" \u2713 Memory ") + chalk3.bold("PostgreSQL + pgvector ready");
|
|
@@ -1697,6 +1953,7 @@ async function checkConnections(dbUrl, ollamaUrl, chatModel) {
|
|
|
1697
1953
|
}
|
|
1698
1954
|
var { version } = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf-8"));
|
|
1699
1955
|
var CONFIG_FILE = ".memory-core.json";
|
|
1956
|
+
var LOCAL_GENERATED_FILES = [".memory-core-stats.json"];
|
|
1700
1957
|
var DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
1701
1958
|
var DEFAULT_EMBEDDING_MODEL = "nomic-embed-text";
|
|
1702
1959
|
var DEFAULT_CHAT_MODEL = "llama3.2";
|
|
@@ -1772,6 +2029,22 @@ function ensureEnvFileIgnored(envPath = getEnvPath()) {
|
|
|
1772
2029
|
`);
|
|
1773
2030
|
}
|
|
1774
2031
|
}
|
|
2032
|
+
function appendMissingGitignoreEntries(entries, heading) {
|
|
2033
|
+
const gitignorePath = join6(process.cwd(), ".gitignore");
|
|
2034
|
+
const existing = existsSync6(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : "";
|
|
2035
|
+
const existingEntries = new Set(
|
|
2036
|
+
existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
2037
|
+
);
|
|
2038
|
+
const toAdd = entries.filter((entry) => !existingEntries.has(entry));
|
|
2039
|
+
if (toAdd.length === 0) {
|
|
2040
|
+
return 0;
|
|
2041
|
+
}
|
|
2042
|
+
const prefix = existing.trim().length > 0 ? "\n" : "";
|
|
2043
|
+
appendFileSync(gitignorePath, `${prefix}${heading}
|
|
2044
|
+
${toAdd.join("\n")}
|
|
2045
|
+
`);
|
|
2046
|
+
return toAdd.length;
|
|
2047
|
+
}
|
|
1775
2048
|
function normalizeProvider(value) {
|
|
1776
2049
|
const provider2 = value.trim().toLowerCase();
|
|
1777
2050
|
if (provider2 === "ollama" || provider2 === "openai" || provider2 === "anthropic" || provider2 === "minimax") {
|
|
@@ -1877,6 +2150,9 @@ function printMemoryTable(memories, title = "Rules in memory") {
|
|
|
1877
2150
|
});
|
|
1878
2151
|
console.log(chalk3.gray("\n Use: memory-core remove <id> | memory-core edit <id>\n"));
|
|
1879
2152
|
}
|
|
2153
|
+
function getCurrentListArchitectures(config) {
|
|
2154
|
+
return inferProjectArchitectures(process.cwd(), config).filter((architecture) => architecture !== "global");
|
|
2155
|
+
}
|
|
1880
2156
|
function printStatusLine(label, value) {
|
|
1881
2157
|
console.log(` ${chalk3.dim(label.padEnd(18))} ${value}`);
|
|
1882
2158
|
}
|
|
@@ -1974,6 +2250,7 @@ async function printProjectStatus() {
|
|
|
1974
2250
|
printStatusLine("Architectures", architectures.length ? architectures.join(", ") : chalk3.gray("none detected"));
|
|
1975
2251
|
printStatusLine("Agents", config?.agents?.length ? `${config.agents.length} selected` : chalk3.gray("none saved"));
|
|
1976
2252
|
printStatusLine("Caveman", config?.caveman?.enabled ? `enabled (${config.caveman.intensity})` : "disabled");
|
|
2253
|
+
printStatusLine("Auto sync", config?.autoSync === false ? "disabled" : "enabled");
|
|
1977
2254
|
printStatusLine("Allow patterns", String(getAllowPatterns(config).length));
|
|
1978
2255
|
printStatusLine("Env file", `${existsSync6(envPath) ? "present" : "missing"} (${envPath.split("/").pop()})`);
|
|
1979
2256
|
printStatusLine("Memory file", existsSync6(memoryFilePath) ? MEMORY_FILE : chalk3.gray("not exported"));
|
|
@@ -2001,6 +2278,94 @@ async function printProjectStatus() {
|
|
|
2001
2278
|
}
|
|
2002
2279
|
console.log();
|
|
2003
2280
|
}
|
|
2281
|
+
function printMemorySelection(selection, limit = 4) {
|
|
2282
|
+
const active = selection.activeArchitectures.join(", ") || "none detected";
|
|
2283
|
+
console.log(chalk3.gray(` Stack filter: ${active}`));
|
|
2284
|
+
const included = selection.decisions.filter((decision) => decision.status === "included");
|
|
2285
|
+
if (included.length > 0) {
|
|
2286
|
+
console.log(chalk3.gray(` Included ${included.length}:`));
|
|
2287
|
+
for (const decision of included.slice(0, limit)) {
|
|
2288
|
+
console.log(chalk3.gray(` + ${decision.memory.content} (${decision.reason})`));
|
|
2289
|
+
}
|
|
2290
|
+
if (included.length > limit) {
|
|
2291
|
+
console.log(chalk3.gray(` \u2026 ${included.length - limit} more included`));
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
if (selection.excluded.length > 0) {
|
|
2295
|
+
console.log(chalk3.gray(` Excluded ${selection.excluded.length}:`));
|
|
2296
|
+
for (const decision of selection.excluded.slice(0, limit)) {
|
|
2297
|
+
console.log(chalk3.gray(` - ${decision.memory.content} (${decision.reason})`));
|
|
2298
|
+
}
|
|
2299
|
+
if (selection.excluded.length > limit) {
|
|
2300
|
+
console.log(chalk3.gray(` \u2026 ${selection.excluded.length - limit} more excluded`));
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
function getConfiguredAgents(config) {
|
|
2305
|
+
const agents = config.agents?.length ? config.agents : AGENT_NAMES.filter((agent) => agent !== "Shared");
|
|
2306
|
+
return [.../* @__PURE__ */ new Set([...agents, "Shared"])];
|
|
2307
|
+
}
|
|
2308
|
+
async function syncGeneratedFiles(config, agents, options = {}) {
|
|
2309
|
+
const memorySpinner = ora(options.label ?? "Syncing memories\u2026").start();
|
|
2310
|
+
let memories = [];
|
|
2311
|
+
try {
|
|
2312
|
+
const archQuery = [config.backendArchitecture, config.frontendFramework, config.language].filter(Boolean).join(" ");
|
|
2313
|
+
const selection = await retrieveMemorySelection({
|
|
2314
|
+
query: archQuery,
|
|
2315
|
+
cwd: process.cwd(),
|
|
2316
|
+
config,
|
|
2317
|
+
limit: 25
|
|
2318
|
+
});
|
|
2319
|
+
memories = selection.included;
|
|
2320
|
+
memorySpinner.succeed(`Found ${memories.length} memories`);
|
|
2321
|
+
if (options.showSelection) {
|
|
2322
|
+
printMemorySelection(selection);
|
|
2323
|
+
}
|
|
2324
|
+
} catch (err) {
|
|
2325
|
+
memorySpinner.warn(`Could not retrieve memories: ${err.message}`);
|
|
2326
|
+
}
|
|
2327
|
+
const spinner = ora("Regenerating agent files\u2026").start();
|
|
2328
|
+
const result = await generate(
|
|
2329
|
+
{
|
|
2330
|
+
projectName: config.projectName,
|
|
2331
|
+
projectType: config.projectType,
|
|
2332
|
+
backendArchitecture: config.backendArchitecture,
|
|
2333
|
+
frontendFramework: config.frontendFramework,
|
|
2334
|
+
language: config.language,
|
|
2335
|
+
memories,
|
|
2336
|
+
caveman: config.caveman
|
|
2337
|
+
},
|
|
2338
|
+
process.cwd(),
|
|
2339
|
+
agents
|
|
2340
|
+
);
|
|
2341
|
+
spinner.succeed(
|
|
2342
|
+
`Synced \u2014 ${chalk3.green(`${result.written.length} updated`)}, ${chalk3.dim(`${result.skipped.length} already up to date`)}`
|
|
2343
|
+
);
|
|
2344
|
+
if (result.written.length > 0) {
|
|
2345
|
+
result.written.forEach((file) => console.log(chalk3.gray(` \u2713 ${file}`)));
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
async function autoSyncGeneratedFiles(config, action, enabled = true) {
|
|
2349
|
+
if (!enabled) {
|
|
2350
|
+
console.log(chalk3.gray(" Auto-sync skipped (--no-sync). Run memory-core sync when ready."));
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
if (!config) {
|
|
2354
|
+
return;
|
|
2355
|
+
}
|
|
2356
|
+
if (config.autoSync === false) {
|
|
2357
|
+
console.log(chalk3.gray(" Auto-sync disabled for this project. Run memory-core sync when ready."));
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
try {
|
|
2361
|
+
await syncGeneratedFiles(config, getConfiguredAgents(config), {
|
|
2362
|
+
label: `Auto-syncing agent files after ${action}\u2026`
|
|
2363
|
+
});
|
|
2364
|
+
} catch (err) {
|
|
2365
|
+
console.log(chalk3.yellow(` Auto-sync skipped: ${err.message}`));
|
|
2366
|
+
console.log(chalk3.gray(" Run memory-core sync manually when ready."));
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2004
2369
|
var program = new Command();
|
|
2005
2370
|
program.name("memory-core").description("Universal AI memory core \u2014 generate AI context files for all coding agents").version(version);
|
|
2006
2371
|
program.command("init").description("Initialize memory-core in the current project").option("--quick", "Use smart defaults and skip optional prompts").action(async (opts) => {
|
|
@@ -2212,11 +2577,20 @@ program.command("init").description("Initialize memory-core in the current proje
|
|
|
2212
2577
|
message: "Pull relevant memories from previous projects?",
|
|
2213
2578
|
default: true
|
|
2214
2579
|
});
|
|
2215
|
-
|
|
2216
|
-
message: "Install caveman token saver?
|
|
2580
|
+
let installCaveman = quick ? false : await confirm({
|
|
2581
|
+
message: "Install caveman token saver? Downloads and runs the upstream installer.",
|
|
2217
2582
|
default: false
|
|
2218
2583
|
});
|
|
2219
2584
|
let cavemanIntensity = "full";
|
|
2585
|
+
if (installCaveman) {
|
|
2586
|
+
const allowRemoteInstaller = await confirm({
|
|
2587
|
+
message: `Security check: download and execute installer from ${CAVEMAN_INSTALL_URL}?`,
|
|
2588
|
+
default: false
|
|
2589
|
+
});
|
|
2590
|
+
if (!allowRemoteInstaller) {
|
|
2591
|
+
installCaveman = false;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2220
2594
|
if (installCaveman) {
|
|
2221
2595
|
cavemanIntensity = await select({
|
|
2222
2596
|
message: "Caveman intensity?",
|
|
@@ -2258,20 +2632,23 @@ program.command("init").description("Initialize memory-core in the current proje
|
|
|
2258
2632
|
language,
|
|
2259
2633
|
caveman: { enabled: installCaveman, intensity: cavemanIntensity },
|
|
2260
2634
|
agents: selectedAgents,
|
|
2261
|
-
allowPatterns: []
|
|
2635
|
+
allowPatterns: [],
|
|
2636
|
+
autoSync: true
|
|
2262
2637
|
};
|
|
2263
2638
|
let memories = [];
|
|
2264
2639
|
if (pullMemories) {
|
|
2265
2640
|
const spinner2 = ora("Retrieving relevant memories\u2026").start();
|
|
2266
2641
|
try {
|
|
2267
2642
|
const archQuery = [backendArchitecture, frontendFramework, language].filter(Boolean).join(" ");
|
|
2268
|
-
|
|
2643
|
+
const selection = await retrieveMemorySelection({
|
|
2269
2644
|
query: archQuery,
|
|
2270
2645
|
cwd: process.cwd(),
|
|
2271
2646
|
config,
|
|
2272
2647
|
limit: 20
|
|
2273
2648
|
});
|
|
2649
|
+
memories = selection.included;
|
|
2274
2650
|
spinner2.succeed(`Found ${memories.length} relevant memories`);
|
|
2651
|
+
printMemorySelection(selection);
|
|
2275
2652
|
} catch (err) {
|
|
2276
2653
|
spinner2.warn(`Could not retrieve memories: ${err.message}`);
|
|
2277
2654
|
}
|
|
@@ -2279,10 +2656,7 @@ program.command("init").description("Initialize memory-core in the current proje
|
|
|
2279
2656
|
if (installCaveman) {
|
|
2280
2657
|
const spinner2 = ora("Installing caveman token saver\u2026").start();
|
|
2281
2658
|
try {
|
|
2282
|
-
|
|
2283
|
-
"curl -fsSL https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh | bash",
|
|
2284
|
-
{ stdio: "pipe", cwd: process.cwd() }
|
|
2285
|
-
);
|
|
2659
|
+
await installCavemanTokenSaver();
|
|
2286
2660
|
spinner2.succeed("Caveman installed");
|
|
2287
2661
|
} catch (err) {
|
|
2288
2662
|
spinner2.warn(`Caveman install failed: ${err.message}`);
|
|
@@ -2296,15 +2670,11 @@ program.command("init").description("Initialize memory-core in the current proje
|
|
|
2296
2670
|
);
|
|
2297
2671
|
writeProjectConfig(config);
|
|
2298
2672
|
spinner.succeed(`Generated ${written.written.length} files`);
|
|
2299
|
-
const
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
if (toAdd.length > 0) {
|
|
2305
|
-
const block = "\n# memory-core generated files\n" + toAdd.join("\n") + "\n";
|
|
2306
|
-
appendFileSync(gitignorePath, block);
|
|
2307
|
-
console.log(chalk3.green(` \u2713 Added ${toAdd.length} generated files to .gitignore`));
|
|
2673
|
+
const gitignoreEntries = [...written.written, ...LOCAL_GENERATED_FILES];
|
|
2674
|
+
if (gitignoreEntries.length > 0) {
|
|
2675
|
+
const added = appendMissingGitignoreEntries(gitignoreEntries, "# memory-core generated files");
|
|
2676
|
+
if (added > 0) {
|
|
2677
|
+
console.log(chalk3.green(` \u2713 Added ${added} generated files to .gitignore`));
|
|
2308
2678
|
}
|
|
2309
2679
|
}
|
|
2310
2680
|
if (enableHook) {
|
|
@@ -2339,44 +2709,32 @@ program.command("sync").description("Re-pull memories and regenerate AI agent fi
|
|
|
2339
2709
|
console.log(chalk3.yellow(" No agents selected \u2014 nothing to sync."));
|
|
2340
2710
|
process.exit(0);
|
|
2341
2711
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
limit: 25
|
|
2351
|
-
});
|
|
2352
|
-
spinner.text = `Found ${memories.length} memories \u2014 regenerating files\u2026`;
|
|
2353
|
-
} catch (err) {
|
|
2354
|
-
spinner.warn(`Could not retrieve memories: ${err.message}`);
|
|
2712
|
+
await syncGeneratedFiles(config, [...selectedAgents, "Shared"], { showSelection: true });
|
|
2713
|
+
await closePool();
|
|
2714
|
+
});
|
|
2715
|
+
program.command("auto-sync [mode]").description("Show or change automatic agent file sync (on|off)").action((mode) => {
|
|
2716
|
+
const config = readProjectConfig();
|
|
2717
|
+
if (!config) {
|
|
2718
|
+
console.error(chalk3.red("No .memory-core.json found. Run: memory-core init"));
|
|
2719
|
+
process.exit(1);
|
|
2355
2720
|
}
|
|
2356
|
-
const
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
language: config.language,
|
|
2363
|
-
memories,
|
|
2364
|
-
caveman: config.caveman
|
|
2365
|
-
},
|
|
2366
|
-
process.cwd(),
|
|
2367
|
-
[...selectedAgents, "Shared"]
|
|
2368
|
-
);
|
|
2369
|
-
const updatedCount = result.written.length;
|
|
2370
|
-
const skippedCount = result.skipped.length;
|
|
2371
|
-
spinner.succeed(
|
|
2372
|
-
`Synced \u2014 ${chalk3.green(`${updatedCount} updated`)}, ${chalk3.dim(`${skippedCount} already up to date`)}`
|
|
2373
|
-
);
|
|
2374
|
-
if (result.written.length > 0) {
|
|
2375
|
-
result.written.forEach((f) => console.log(chalk3.gray(` \u2713 ${f}`)));
|
|
2721
|
+
const normalized = mode?.trim().toLowerCase();
|
|
2722
|
+
if (!normalized || normalized === "status") {
|
|
2723
|
+
console.log(chalk3.bold("\n Auto-sync\n"));
|
|
2724
|
+
console.log(` Status: ${config.autoSync === false ? chalk3.yellow("disabled") : chalk3.green("enabled")}`);
|
|
2725
|
+
console.log(chalk3.gray(" Manual sync is always available: memory-core sync\n"));
|
|
2726
|
+
return;
|
|
2376
2727
|
}
|
|
2377
|
-
|
|
2728
|
+
if (normalized !== "on" && normalized !== "off") {
|
|
2729
|
+
console.error(chalk3.red("Use: memory-core auto-sync [on|off|status]"));
|
|
2730
|
+
process.exit(1);
|
|
2731
|
+
}
|
|
2732
|
+
const enabled = normalized === "on";
|
|
2733
|
+
writeProjectConfig({ ...config, autoSync: enabled });
|
|
2734
|
+
console.log(chalk3.green(`Auto-sync ${enabled ? "enabled" : "disabled"}`));
|
|
2735
|
+
console.log(chalk3.gray(" Manual sync is always available: memory-core sync"));
|
|
2378
2736
|
});
|
|
2379
|
-
program.command("remember <text>").description("Save a new memory to the central database").option("-t, --type <type>", "Memory type (decision|rule|pattern|note)", "decision").option("-s, --scope <scope>", "Scope (global|project)", "project").option("--tags <tags>", "Comma-separated tags").option("-r, --reason <reason>", "Why this rule exists \u2014 helps agents understand intent and debug violations").action(async (text, opts) => {
|
|
2737
|
+
program.command("remember <text>").description("Save a new memory to the central database").option("-t, --type <type>", "Memory type (decision|rule|pattern|note)", "decision").option("-s, --scope <scope>", "Scope (global|project)", "project").option("--tags <tags>", "Comma-separated tags").option("-r, --reason <reason>", "Why this rule exists \u2014 helps agents understand intent and debug violations").option("--no-sync", "Skip automatic agent file sync after saving").action(async (text, opts) => {
|
|
2380
2738
|
const config = readProjectConfig();
|
|
2381
2739
|
let reason = opts.reason;
|
|
2382
2740
|
if (!reason) {
|
|
@@ -2401,6 +2759,7 @@ program.command("remember <text>").description("Save a new memory to the central
|
|
|
2401
2759
|
const reasonLine = reason ? chalk3.gray(`
|
|
2402
2760
|
Why: ${reason}`) : "";
|
|
2403
2761
|
spinner.succeed(chalk3.green(`Memory saved: "${text}"`) + reasonLine);
|
|
2762
|
+
await autoSyncGeneratedFiles(config, "remember", opts.sync);
|
|
2404
2763
|
} catch (err) {
|
|
2405
2764
|
spinner.fail(`Failed: ${err.message}`);
|
|
2406
2765
|
process.exit(1);
|
|
@@ -2455,9 +2814,10 @@ program.command("export").description(`Export DB memories to ${MEMORY_FILE}`).op
|
|
|
2455
2814
|
await closePool();
|
|
2456
2815
|
}
|
|
2457
2816
|
});
|
|
2458
|
-
program.command("import").description(`Import memories from ${MEMORY_FILE}`).option("--url <url>", "Import memories from a remote URL").option("-f, --file <file>", `Import file (default: ${MEMORY_FILE})`).action(async (opts) => {
|
|
2817
|
+
program.command("import").description(`Import memories from ${MEMORY_FILE}`).option("--url <url>", "Import memories from a remote URL").option("-f, --file <file>", `Import file (default: ${MEMORY_FILE})`).option("--no-sync", "Skip automatic agent file sync after import").action(async (opts) => {
|
|
2459
2818
|
const spinner = ora("Reading memories\u2026").start();
|
|
2460
2819
|
try {
|
|
2820
|
+
const config = readProjectConfig();
|
|
2461
2821
|
const memories = opts.url ? await readMemoryFileFromUrl(opts.url) : opts.file ? parseMemoryFile(readFileSync6(join6(process.cwd(), opts.file), "utf-8")) : readMemoryFile();
|
|
2462
2822
|
let inserted = 0;
|
|
2463
2823
|
let skipped = 0;
|
|
@@ -2479,6 +2839,9 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
|
|
|
2479
2839
|
else skipped++;
|
|
2480
2840
|
}
|
|
2481
2841
|
spinner.succeed(`Imported ${inserted} memories, skipped ${skipped} duplicates`);
|
|
2842
|
+
if (inserted > 0) {
|
|
2843
|
+
await autoSyncGeneratedFiles(config, "import", opts.sync);
|
|
2844
|
+
}
|
|
2482
2845
|
} catch (err) {
|
|
2483
2846
|
spinner.fail(`Import failed: ${err.message}`);
|
|
2484
2847
|
process.exit(1);
|
|
@@ -2486,15 +2849,24 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
|
|
|
2486
2849
|
await closePool();
|
|
2487
2850
|
}
|
|
2488
2851
|
});
|
|
2489
|
-
program.command("list").description("List memories from the local database").option("--type <type>", "Filter by type").option("--scope <scope>", "Filter by scope").option("--arch <architecture>", "Filter by architecture").option("-n, --limit <n>", "Maximum rows to show", "200").action(async (opts) => {
|
|
2852
|
+
program.command("list").description("List memories from the local database").option("--type <type>", "Filter by type").option("--scope <scope>", "Filter by scope").option("--arch <architecture>", "Filter by architecture").option("--project <name>", "Filter by project name").option("--current", "Only show memories relevant to the current project (default)").option("--all", "Show all memories in the database").option("-n, --limit <n>", "Maximum rows to show", "200").action(async (opts) => {
|
|
2490
2853
|
try {
|
|
2854
|
+
const config = readProjectConfig();
|
|
2855
|
+
const architectures = opts.arch ? opts.arch : opts.all ? void 0 : getCurrentListArchitectures(config);
|
|
2856
|
+
const projectName = opts.project ? opts.project : opts.all || opts.arch ? void 0 : config?.projectName;
|
|
2491
2857
|
const memories = await listMemories({
|
|
2492
2858
|
type: opts.type,
|
|
2493
2859
|
scope: opts.scope,
|
|
2494
|
-
architecture:
|
|
2860
|
+
architecture: architectures,
|
|
2861
|
+
projectName,
|
|
2862
|
+
includeGlobal: !opts.all,
|
|
2495
2863
|
limit: parseInt(opts.limit, 10)
|
|
2496
2864
|
});
|
|
2497
|
-
|
|
2865
|
+
const title = opts.all ? "All memories" : `Current project memories${architectures ? ` (${Array.isArray(architectures) ? architectures.join(", ") : architectures})` : ""}`;
|
|
2866
|
+
printMemoryTable(memories, title);
|
|
2867
|
+
if (!opts.all) {
|
|
2868
|
+
console.log(chalk3.gray(" Showing current project context plus shared/global memories. Use --all for the full database.\n"));
|
|
2869
|
+
}
|
|
2498
2870
|
} catch (err) {
|
|
2499
2871
|
console.error(chalk3.red(`List failed: ${err.message}`));
|
|
2500
2872
|
process.exit(1);
|
|
@@ -2502,14 +2874,16 @@ program.command("list").description("List memories from the local database").opt
|
|
|
2502
2874
|
await closePool();
|
|
2503
2875
|
}
|
|
2504
2876
|
});
|
|
2505
|
-
program.command("remove <id>").description("Remove a memory by ID").action(async (id) => {
|
|
2877
|
+
program.command("remove <id>").description("Remove a memory by ID").option("--no-sync", "Skip automatic agent file sync after removal").action(async (id, opts) => {
|
|
2506
2878
|
try {
|
|
2879
|
+
const config = readProjectConfig();
|
|
2507
2880
|
const deleted = await deleteMemory(parseInt(id, 10));
|
|
2508
2881
|
if (!deleted) {
|
|
2509
2882
|
console.log(chalk3.yellow(`No memory found with ID ${id}`));
|
|
2510
2883
|
process.exit(1);
|
|
2511
2884
|
}
|
|
2512
2885
|
console.log(chalk3.green(`Removed memory ${id}`));
|
|
2886
|
+
await autoSyncGeneratedFiles(config, "remove", opts.sync);
|
|
2513
2887
|
} catch (err) {
|
|
2514
2888
|
console.error(chalk3.red(`Remove failed: ${err.message}`));
|
|
2515
2889
|
process.exit(1);
|
|
@@ -2517,8 +2891,9 @@ program.command("remove <id>").description("Remove a memory by ID").action(async
|
|
|
2517
2891
|
await closePool();
|
|
2518
2892
|
}
|
|
2519
2893
|
});
|
|
2520
|
-
program.command("forget").description("Bulk-delete memories by tag, scope, type, or architecture").option("--tag <tag>", "Delete memories with this tag").option("--scope <scope>", "Filter by scope").option("--type <type>", "Filter by type").option("--arch <architecture>", "Filter by architecture").action(async (opts) => {
|
|
2894
|
+
program.command("forget").description("Bulk-delete memories by tag, scope, type, or architecture").option("--tag <tag>", "Delete memories with this tag").option("--scope <scope>", "Filter by scope").option("--type <type>", "Filter by type").option("--arch <architecture>", "Filter by architecture").option("--no-sync", "Skip automatic agent file sync after deletion").action(async (opts) => {
|
|
2521
2895
|
try {
|
|
2896
|
+
const config = readProjectConfig();
|
|
2522
2897
|
const deleted = await deleteMemories({
|
|
2523
2898
|
tag: opts.tag,
|
|
2524
2899
|
scope: opts.scope,
|
|
@@ -2526,6 +2901,9 @@ program.command("forget").description("Bulk-delete memories by tag, scope, type,
|
|
|
2526
2901
|
architecture: opts.arch
|
|
2527
2902
|
});
|
|
2528
2903
|
console.log(chalk3.green(`Deleted ${deleted} memories`));
|
|
2904
|
+
if (deleted > 0) {
|
|
2905
|
+
await autoSyncGeneratedFiles(config, "forget", opts.sync);
|
|
2906
|
+
}
|
|
2529
2907
|
} catch (err) {
|
|
2530
2908
|
console.error(chalk3.red(`Forget failed: ${err.message}`));
|
|
2531
2909
|
process.exit(1);
|
|
@@ -2533,9 +2911,10 @@ program.command("forget").description("Bulk-delete memories by tag, scope, type,
|
|
|
2533
2911
|
await closePool();
|
|
2534
2912
|
}
|
|
2535
2913
|
});
|
|
2536
|
-
program.command("edit <id>").description("Edit a memory interactively").action(async (id) => {
|
|
2914
|
+
program.command("edit <id>").description("Edit a memory interactively").option("--no-sync", "Skip automatic agent file sync after editing").action(async (id, opts) => {
|
|
2537
2915
|
const memoryId = parseInt(id, 10);
|
|
2538
2916
|
try {
|
|
2917
|
+
const config = readProjectConfig();
|
|
2539
2918
|
const existing = await getMemory(memoryId);
|
|
2540
2919
|
if (!existing) {
|
|
2541
2920
|
console.log(chalk3.yellow(`No memory found with ID ${id}`));
|
|
@@ -2558,6 +2937,7 @@ program.command("edit <id>").description("Edit a memory interactively").action(a
|
|
|
2558
2937
|
embedding
|
|
2559
2938
|
});
|
|
2560
2939
|
console.log(chalk3.green(`Updated memory ${id}`));
|
|
2940
|
+
await autoSyncGeneratedFiles(config, "edit", opts.sync);
|
|
2561
2941
|
} catch (err) {
|
|
2562
2942
|
console.error(chalk3.red(`Edit failed: ${err.message}`));
|
|
2563
2943
|
process.exit(1);
|
|
@@ -2565,8 +2945,9 @@ program.command("edit <id>").description("Edit a memory interactively").action(a
|
|
|
2565
2945
|
await closePool();
|
|
2566
2946
|
}
|
|
2567
2947
|
});
|
|
2568
|
-
program.command("ignore [pattern]").description("Manage project-scoped false-positive ignore patterns").option("--list", "List ignored patterns").option("--remove <id>", "Remove an ignored pattern by ID").action(async (pattern, opts) => {
|
|
2948
|
+
program.command("ignore [pattern]").description("Manage project-scoped false-positive ignore patterns").option("--list", "List ignored patterns").option("--remove <id>", "Remove an ignored pattern by ID").option("--no-sync", "Skip automatic agent file sync after changing ignore patterns").action(async (pattern, opts) => {
|
|
2569
2949
|
try {
|
|
2950
|
+
const config = readProjectConfig();
|
|
2570
2951
|
if (opts.list) {
|
|
2571
2952
|
printMemoryTable(await listMemories({ type: "ignore", limit: 1e3 }), "Ignored patterns");
|
|
2572
2953
|
return;
|
|
@@ -2578,13 +2959,13 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
|
|
|
2578
2959
|
process.exit(1);
|
|
2579
2960
|
}
|
|
2580
2961
|
console.log(chalk3.green(`Removed ignore pattern ${opts.remove}`));
|
|
2962
|
+
await autoSyncGeneratedFiles(config, "ignore remove", opts.sync);
|
|
2581
2963
|
return;
|
|
2582
2964
|
}
|
|
2583
2965
|
if (!pattern) {
|
|
2584
2966
|
console.error(chalk3.red("Provide a pattern, --list, or --remove <id>"));
|
|
2585
2967
|
process.exit(1);
|
|
2586
2968
|
}
|
|
2587
|
-
const config = readProjectConfig();
|
|
2588
2969
|
const embedding = await embed(pattern);
|
|
2589
2970
|
await upsertMemory({
|
|
2590
2971
|
type: "ignore",
|
|
@@ -2596,6 +2977,7 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
|
|
|
2596
2977
|
embedding
|
|
2597
2978
|
});
|
|
2598
2979
|
console.log(chalk3.green(`Ignored pattern saved: "${pattern}"`));
|
|
2980
|
+
await autoSyncGeneratedFiles(config, "ignore", opts.sync);
|
|
2599
2981
|
} catch (err) {
|
|
2600
2982
|
console.error(chalk3.red(`Ignore failed: ${err.message}`));
|
|
2601
2983
|
process.exit(1);
|
|
@@ -2673,7 +3055,6 @@ program.command("reset").description("Remove memory-core generated files and loc
|
|
|
2673
3055
|
default: false
|
|
2674
3056
|
});
|
|
2675
3057
|
if (ok) {
|
|
2676
|
-
const { getPool } = await import("./db-5X5LTUCB.js");
|
|
2677
3058
|
await getPool().query("DROP TABLE IF EXISTS memories");
|
|
2678
3059
|
await closePool();
|
|
2679
3060
|
console.log(chalk3.yellow("Dropped memories table"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shahmilsaari/memory-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"description": "Universal AI memory core — generate AI context files from architecture profiles with RAG support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsup",
|
|
16
16
|
"typecheck": "tsc --noEmit",
|
|
17
|
+
"lint": "node scripts/lint.mjs",
|
|
18
|
+
"smoke:pack": "node scripts/pack-smoke.mjs",
|
|
19
|
+
"smoke:npx": "node scripts/npx-init-smoke.mjs",
|
|
17
20
|
"dev": "tsx src/cli.ts",
|
|
18
21
|
"start": "node dist/cli.js",
|
|
19
22
|
"test": "node --import tsx --test test/**/*.test.ts"
|
package/templates/AGENTS.md.hbs
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# AGENTS.md — {{projectName}}
|
|
2
|
-
<!-- Generated by memory-core on {{generatedAt}} —
|
|
2
|
+
<!-- Generated by memory-core on {{generatedAt}} — auto-sync keeps this fresh; manual refresh: memory-core sync -->
|
|
3
3
|
|
|
4
4
|
**Type:** {{projectType}} | **Language:** {{language}}
|
|
5
5
|
{{#if hasBackend}}**Backend:** {{backendArchitecture}}{{/if}}
|
|
6
6
|
{{#if hasFrontend}}**Frontend:** {{frontendFramework}}{{/if}}
|
|
7
7
|
|
|
8
|
-
---
|
|
9
8
|
{{#if hasBackend}}
|
|
10
9
|
## Backend Rules — {{backendArchitecture}}
|
|
11
10
|
{{bullet backendRules}}
|
|
@@ -30,7 +29,7 @@
|
|
|
30
29
|
{{#if hasMemories}}
|
|
31
30
|
|
|
32
31
|
---
|
|
33
|
-
## Memory from Previous Projects
|
|
32
|
+
## Relevant Memory from Previous Projects
|
|
34
33
|
{{#each memories}}
|
|
35
34
|
- [{{type}}{{#if this.architecture}} · {{this.architecture}}{{/if}}] {{content}}
|
|
36
35
|
{{/each}}
|
|
@@ -7,7 +7,6 @@ These rules apply to ALL AI agents in this project.
|
|
|
7
7
|
{{#if hasBackend}}**Backend:** {{backendArchitecture}}{{/if}}
|
|
8
8
|
{{#if hasFrontend}}**Frontend:** {{frontendFramework}}{{/if}}
|
|
9
9
|
|
|
10
|
-
---
|
|
11
10
|
{{#if hasBackend}}
|
|
12
11
|
## Backend — {{backendArchitecture}}
|
|
13
12
|
{{numbered backendRules}}
|
|
@@ -26,7 +25,7 @@ These rules apply to ALL AI agents in this project.
|
|
|
26
25
|
{{#if hasMemories}}
|
|
27
26
|
|
|
28
27
|
---
|
|
29
|
-
## Inherited Decisions
|
|
28
|
+
## Relevant Inherited Decisions
|
|
30
29
|
{{#each memories}}
|
|
31
30
|
{{@index}}. **[{{type}}]** {{content}}
|
|
32
31
|
{{#if tags.length}}_tags: {{join tags ", "}}_{{/if}}
|
package/templates/CLAUDE.md.hbs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Project Memory — {{projectName}}
|
|
2
|
-
<!-- Generated by memory-core on {{generatedAt}} —
|
|
2
|
+
<!-- Generated by memory-core on {{generatedAt}} — auto-sync keeps this fresh; manual refresh: memory-core sync -->
|
|
3
3
|
|
|
4
4
|
Architecture: {{architecture}}
|
|
5
5
|
Stack: {{language}}
|
|
@@ -28,4 +28,4 @@ memory-core remember "Your architectural decision here"
|
|
|
28
28
|
---
|
|
29
29
|
_Add memories: `memory-core remember "..."`_
|
|
30
30
|
_Search memories: `memory-core search "..."`_
|
|
31
|
-
|
|
31
|
+
_Auto-sync is enabled by default after memory changes. Manual refresh: `memory-core sync`._
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Copilot Instructions
|
|
2
|
-
<!-- Generated by memory-core on {{generatedAt}} —
|
|
2
|
+
<!-- Generated by memory-core on {{generatedAt}} — auto-sync keeps this fresh; manual refresh: memory-core sync -->
|
|
3
3
|
|
|
4
4
|
Project: **{{projectName}}** · Type: **{{projectType}}** · Language: **{{language}}**
|
|
5
5
|
{{#if hasBackend}}Backend: {{backendArchitecture}}{{/if}}{{#if hasFrontend}} | Frontend: {{frontendFramework}}{{/if}}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Cursor Rules — {{projectName}}
|
|
2
|
-
# Generated by memory-core on {{generatedAt}} —
|
|
2
|
+
# Generated by memory-core on {{generatedAt}} — auto-sync keeps this fresh; manual refresh: memory-core sync
|
|
3
3
|
|
|
4
4
|
Type: {{projectType}} | Language: {{language}}
|
|
5
5
|
{{#if hasBackend}}Backend: {{backendArchitecture}}{{/if}}{{#if hasFrontend}} | Frontend: {{frontendFramework}}{{/if}}
|