@vedtechsolutions/engram-mcp 1.0.1 → 1.0.3

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 CHANGED
@@ -17,10 +17,10 @@ Engram gives Claude Code **persistent, cross-session memory** that learns from e
17
17
 
18
18
  ```bash
19
19
  # Install
20
- npm install -g engram-mcp
20
+ npm install -g @vedtechsolutions/engram-mcp
21
21
 
22
22
  # Configure Claude Code
23
- npx engram-setup
23
+ npx @vedtechsolutions/engram-mcp setup
24
24
  ```
25
25
 
26
26
  That's it. Start a new Claude Code session and Engram activates automatically.
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Engram Knowledge Pack Importer
5
+ *
6
+ * Usage:
7
+ * node bin/import-pack.js <pack-file-or-name>
8
+ * npx @vedtechsolutions/engram-mcp import-pack typescript-patterns
9
+ *
10
+ * Reads a JSON knowledge pack and imports memories into the Engram database.
11
+ * Skips duplicates via content similarity check.
12
+ */
13
+
14
+ import { readFileSync, existsSync } from 'node:fs';
15
+ import { resolve, join, dirname } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const PACKS_DIR = join(__dirname, '..', 'packs');
20
+
21
+ function usage() {
22
+ console.log(`
23
+ Engram Knowledge Pack Importer
24
+
25
+ Usage:
26
+ engram-import-pack <pack>
27
+
28
+ Where <pack> is one of:
29
+ - A built-in pack name: claude-code-patterns, typescript-patterns, python-patterns
30
+ - A path to a .json pack file
31
+
32
+ Examples:
33
+ engram-import-pack typescript-patterns
34
+ engram-import-pack ./my-custom-pack.json
35
+ `);
36
+ process.exit(1);
37
+ }
38
+
39
+ async function main() {
40
+ const arg = process.argv[2];
41
+ if (!arg || arg === '--help' || arg === '-h') usage();
42
+
43
+ // Resolve pack file path
44
+ let packPath;
45
+ if (existsSync(arg)) {
46
+ packPath = resolve(arg);
47
+ } else if (existsSync(join(PACKS_DIR, `${arg}.json`))) {
48
+ packPath = join(PACKS_DIR, `${arg}.json`);
49
+ } else {
50
+ console.error(` Error: Pack not found: ${arg}`);
51
+ console.error(` Available packs: ${listPacks().join(', ')}`);
52
+ process.exit(1);
53
+ }
54
+
55
+ // Load pack
56
+ let pack;
57
+ try {
58
+ pack = JSON.parse(readFileSync(packPath, 'utf-8'));
59
+ } catch (e) {
60
+ console.error(` Error: Invalid JSON in ${packPath}: ${e.message}`);
61
+ process.exit(1);
62
+ }
63
+
64
+ if (!pack.memories || !Array.isArray(pack.memories)) {
65
+ console.error(' Error: Pack must have a "memories" array');
66
+ process.exit(1);
67
+ }
68
+
69
+ console.log(`\n Importing: ${pack.name ?? packPath}`);
70
+ console.log(` Memories: ${pack.memories.length}`);
71
+ console.log();
72
+
73
+ // Dynamic import of Engram modules (they need the DB initialized)
74
+ const dbPath = resolve(
75
+ process.env.ENGRAM_DB_PATH
76
+ ?? join(process.env.HOME ?? '', '.engram', 'engram.db')
77
+ );
78
+
79
+ if (!existsSync(dirname(dbPath))) {
80
+ const { mkdirSync } = await import('node:fs');
81
+ mkdirSync(dirname(dbPath), { recursive: true });
82
+ }
83
+
84
+ // Initialize database
85
+ const { initializeDatabase } = await import('../dist/chunk-AC6AZCP4.js');
86
+ // Fallback: try to import from the compiled output
87
+ let createMemory, searchMemories, generateEmbedding, embeddingToBuffer;
88
+ try {
89
+ const mod = await import('../dist/chunk-AC6AZCP4.js');
90
+ createMemory = mod.createMemory;
91
+ searchMemories = mod.searchMemories;
92
+ generateEmbedding = mod.generateEmbedding;
93
+ embeddingToBuffer = mod.embeddingToBuffer;
94
+ // Init DB
95
+ mod.initializeDatabase({ db_path: dbPath });
96
+ } catch {
97
+ console.error(' Error: Could not load Engram modules.');
98
+ console.error(' Make sure you are running from the engram-mcp package directory.');
99
+ process.exit(1);
100
+ }
101
+
102
+ let imported = 0;
103
+ let skipped = 0;
104
+
105
+ for (const entry of pack.memories) {
106
+ const { type, content, domains, severity, tags } = entry;
107
+
108
+ if (!type || !content) {
109
+ console.log(` SKIP (missing type or content): ${content?.substring(0, 60) ?? '(empty)'}`);
110
+ skipped++;
111
+ continue;
112
+ }
113
+
114
+ // Check for duplicates via keyword search
115
+ try {
116
+ const existing = searchMemories(content.substring(0, 100), 3);
117
+ const isDuplicate = existing.some(m =>
118
+ m.content === content ||
119
+ (m.content.length > 50 && content.includes(m.content.substring(0, 50)))
120
+ );
121
+ if (isDuplicate) {
122
+ console.log(` SKIP (duplicate): ${content.substring(0, 60)}...`);
123
+ skipped++;
124
+ continue;
125
+ }
126
+ } catch { /* search failed, proceed with import */ }
127
+
128
+ // Build type_data based on memory type
129
+ let type_data;
130
+ if (type === 'antipattern') {
131
+ type_data = {
132
+ kind: 'antipattern',
133
+ pattern_description: content,
134
+ correct_approach: null,
135
+ severity: severity ?? 'medium',
136
+ occurrences: 0,
137
+ last_seen: null,
138
+ auto_detected: false,
139
+ };
140
+ } else if (type === 'semantic') {
141
+ type_data = {
142
+ kind: 'semantic',
143
+ knowledge_type: 'convention',
144
+ source: 'knowledge_pack',
145
+ source_episodes: [],
146
+ applicable_versions: null,
147
+ deprecated_in: null,
148
+ };
149
+ } else if (type === 'procedural') {
150
+ type_data = {
151
+ kind: 'procedural',
152
+ steps: [],
153
+ preconditions: [],
154
+ postconditions: [],
155
+ success_count: 0,
156
+ failure_count: 0,
157
+ };
158
+ }
159
+
160
+ try {
161
+ createMemory({
162
+ type,
163
+ content,
164
+ summary: null,
165
+ encoding_strength: 0.7,
166
+ reinforcement: 1.5,
167
+ confidence: 0.8,
168
+ domains: domains ?? [],
169
+ version: null,
170
+ tags: [...(tags ?? []), 'knowledge-pack', pack.name?.toLowerCase().replace(/\s+/g, '-') ?? 'custom'],
171
+ storage_tier: 'long_term',
172
+ pinned: false,
173
+ type_data,
174
+ encoding_context: {
175
+ framework: domains?.[0] ?? null,
176
+ version: null,
177
+ project: null,
178
+ project_path: null,
179
+ task_type: null,
180
+ files: [],
181
+ error_context: null,
182
+ session_id: `pack-import-${Date.now()}`,
183
+ significance_score: 0.7,
184
+ },
185
+ });
186
+ console.log(` OK (${type}): ${content.substring(0, 60)}...`);
187
+ imported++;
188
+ } catch (e) {
189
+ console.log(` ERR: ${content.substring(0, 40)}... — ${e.message}`);
190
+ skipped++;
191
+ }
192
+ }
193
+
194
+ console.log(`\n Done. Imported: ${imported}, Skipped: ${skipped}`);
195
+ console.log(` Database: ${dbPath}\n`);
196
+ }
197
+
198
+ function listPacks() {
199
+ try {
200
+ const { readdirSync } = await import('node:fs');
201
+ return readdirSync(PACKS_DIR)
202
+ .filter(f => f.endsWith('.json'))
203
+ .map(f => f.replace('.json', ''));
204
+ } catch {
205
+ return ['claude-code-patterns', 'typescript-patterns', 'python-patterns'];
206
+ }
207
+ }
208
+
209
+ main().catch(e => {
210
+ console.error(` Fatal: ${e.message}`);
211
+ process.exit(1);
212
+ });
@@ -1710,7 +1710,11 @@ var CONTEXTUAL = {
1710
1710
  /** Penalty multiplier for project mismatch on episodic memories (0.3 = 70% reduction) */
1711
1711
  PROJECT_MISMATCH_PENALTY: 0.3,
1712
1712
  /** Boost for failure experiences with lessons (surfaces "I tried this and it didn't work") */
1713
- FAILURE_EXPERIENCE_BOOST: 0.2
1713
+ FAILURE_EXPERIENCE_BOOST: 0.2,
1714
+ /** Boost when memory files share the same module directory as current file */
1715
+ MODULE_PROXIMITY_BOOST: 0.2,
1716
+ /** Penalty multiplier when memory files are all from a different module (0.3 = 70% reduction) */
1717
+ MODULE_MISMATCH_PENALTY: 0.3
1714
1718
  };
1715
1719
  var REWARD = {
1716
1720
  /** Seed activation boost for positively-reinforced memories */
@@ -8758,6 +8762,19 @@ function extractCodeContext(code, filePath) {
8758
8762
  identifiers: uniqueTerms
8759
8763
  };
8760
8764
  }
8765
+ function extractModuleFromPath(filePath) {
8766
+ const parts = filePath.split(/[/\\]/).filter(Boolean);
8767
+ const markers = ["addons", "custom-addons", "extra-addons", "packages", "apps"];
8768
+ for (let i = 0; i < parts.length - 1; i++) {
8769
+ if (markers.includes(parts[i]) && i + 1 < parts.length) {
8770
+ return parts[i + 1];
8771
+ }
8772
+ }
8773
+ if (parts.length >= 3) {
8774
+ return parts[parts.length - 3];
8775
+ }
8776
+ return null;
8777
+ }
8761
8778
  function detectLanguage(code, filePath) {
8762
8779
  if (filePath) {
8763
8780
  const ext = filePath.split(".").pop()?.toLowerCase();
@@ -9033,6 +9050,18 @@ function contextualBoost(memory, context) {
9033
9050
  if (context.current_files.length > 0 && enc.files.length > 0) {
9034
9051
  const overlap = context.current_files.filter((f) => enc.files.includes(f)).length;
9035
9052
  boost += Math.min(overlap * CONTEXTUAL.FILE_BOOST_PER_MATCH, CONTEXTUAL.FILE_BOOST_CAP);
9053
+ if (overlap === 0) {
9054
+ const currentModules = context.current_files.map(extractModuleFromPath).filter(Boolean);
9055
+ const memModules = enc.files.map(extractModuleFromPath).filter(Boolean);
9056
+ if (currentModules.length > 0 && memModules.length > 0) {
9057
+ const moduleOverlap = currentModules.some((m) => memModules.includes(m));
9058
+ if (moduleOverlap) {
9059
+ boost += CONTEXTUAL.MODULE_PROXIMITY_BOOST;
9060
+ } else {
9061
+ return (1 + boost) * CONTEXTUAL.MODULE_MISMATCH_PENALTY;
9062
+ }
9063
+ }
9064
+ }
9036
9065
  }
9037
9066
  if (context.current_error && enc.error_context) {
9038
9067
  const errorKeywords = context.current_error.toLowerCase().split(/\s+/).slice(0, 10);
@@ -12541,6 +12570,7 @@ export {
12541
12570
  computeActivationProfile,
12542
12571
  contextualRecall,
12543
12572
  codeContextRecall,
12573
+ extractModuleFromPath,
12544
12574
  findSimilarDecisions,
12545
12575
  formatDecisionInjection,
12546
12576
  findSimilarChains,
@@ -12597,4 +12627,4 @@ export {
12597
12627
  composeProjectUnderstanding,
12598
12628
  formatMentalModelInjection
12599
12629
  };
12600
- //# sourceMappingURL=chunk-AC6AZCP4.js.map
12630
+ //# sourceMappingURL=chunk-QU7DOPA4.js.map
package/dist/hook.js CHANGED
@@ -79,6 +79,7 @@ import {
79
79
  estimateTokens,
80
80
  extractErrorFingerprint,
81
81
  extractKeywords,
82
+ extractModuleFromPath,
82
83
  findDuplicate,
83
84
  findErrorByFingerprint,
84
85
  findResolutionForError,
@@ -172,7 +173,7 @@ import {
172
173
  updateReasoningChain,
173
174
  updateSelfModelFromSession,
174
175
  updateTask
175
- } from "./chunk-AC6AZCP4.js";
176
+ } from "./chunk-QU7DOPA4.js";
176
177
 
177
178
  // src/hook.ts
178
179
  import { readFileSync, writeFileSync, existsSync, renameSync, statSync, readdirSync, unlinkSync, appendFileSync, openSync, readSync, closeSync } from "fs";
@@ -3527,9 +3528,7 @@ function handlePreWrite(toolInput, argFallback) {
3527
3528
  if (filePath) {
3528
3529
  const fileCount = (watcherState.prewrite_file_counts[filePath] ?? 0) + 1;
3529
3530
  watcherState.prewrite_file_counts[filePath] = fileCount;
3530
- if (fileCount <= CODE_CONTEXT_RECALL.PREWRITE_COOLDOWN_PER_FILE) {
3531
- skipCodeRecall = fileCount > 1;
3532
- }
3531
+ skipCodeRecall = fileCount % CODE_CONTEXT_RECALL.PREWRITE_COOLDOWN_PER_FILE !== 1;
3533
3532
  saveWatcherState(watcherState);
3534
3533
  }
3535
3534
  if (!skipCodeRecall && content.length >= CODE_CONTEXT_RECALL.MIN_CONTENT_LENGTH) {
@@ -3540,18 +3539,30 @@ function handlePreWrite(toolInput, argFallback) {
3540
3539
  project: watcherState.active_project
3541
3540
  }, config.retrieval);
3542
3541
  const activeDomain = watcherState.active_domain;
3542
+ const currentModule = filePath ? extractModuleFromPath(filePath) : null;
3543
+ const isModuleMismatch = (mem) => {
3544
+ if (!currentModule) return false;
3545
+ const memFiles = mem.memory.encoding_context?.files;
3546
+ if (!memFiles || memFiles.length === 0) return false;
3547
+ const memModules = memFiles.map(extractModuleFromPath).filter(Boolean);
3548
+ if (memModules.length === 0) return false;
3549
+ return !memModules.includes(currentModule);
3550
+ };
3543
3551
  for (const p of codeResult.patterns) {
3544
3552
  if (p.activation < CODE_CONTEXT_RECALL.MIN_PREWRITE_ACTIVATION) continue;
3545
3553
  if (p.memory.type === "episodic") continue;
3546
3554
  if (activeDomain && p.memory.encoding_context?.framework && p.memory.encoding_context.framework !== activeDomain) continue;
3555
+ if (isModuleMismatch(p)) continue;
3547
3556
  contextLines.push(`[ENGRAM PATTERN] ${truncate(p.memory.content, 200)}`);
3548
3557
  }
3549
3558
  for (const c of codeResult.conventions) {
3550
3559
  if (c.activation < CODE_CONTEXT_RECALL.MIN_PREWRITE_ACTIVATION) continue;
3551
3560
  if (activeDomain && c.memory.encoding_context?.framework && c.memory.encoding_context.framework !== activeDomain) continue;
3561
+ if (isModuleMismatch(c)) continue;
3552
3562
  contextLines.push(`[ENGRAM CONVENTION] ${truncate(c.memory.content, 200)}`);
3553
3563
  }
3554
3564
  for (const p of codeResult.procedural) {
3565
+ if (isModuleMismatch(p)) continue;
3555
3566
  contextLines.push(`[ENGRAM HOW-TO] ${truncate(p.memory.content, 200)}`);
3556
3567
  }
3557
3568
  } catch (e) {
package/dist/index.js CHANGED
@@ -154,7 +154,7 @@ import {
154
154
  vaccinate,
155
155
  vacuumDatabase,
156
156
  validateMultiPerspective
157
- } from "./chunk-AC6AZCP4.js";
157
+ } from "./chunk-QU7DOPA4.js";
158
158
 
159
159
  // src/index.ts
160
160
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vedtechsolutions/engram-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Cognitive memory system for AI — persistent, cross-session learning via MCP",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,7 +8,8 @@
8
8
  "bin": {
9
9
  "engram-mcp": "dist/index.js",
10
10
  "engram-hook": "dist/hook.js",
11
- "engram-setup": "bin/setup.js"
11
+ "engram-setup": "bin/setup.js",
12
+ "engram-import-pack": "bin/import-pack.js"
12
13
  },
13
14
  "scripts": {
14
15
  "setup": "node bin/setup.js",
@@ -47,6 +48,7 @@
47
48
  "files": [
48
49
  "dist/",
49
50
  "bin/",
51
+ "packs/",
50
52
  "LICENSE",
51
53
  "README.md"
52
54
  ]
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "Claude Code Best Practices",
3
+ "version": "1.0.0",
4
+ "description": "Common patterns and antipatterns for effective Claude Code usage",
5
+ "memories": [
6
+ {
7
+ "type": "antipattern",
8
+ "content": "Don't use sed/awk for file edits in Claude Code. Use the Edit tool — it shows diffs, preserves formatting, and the user can review changes before applying.",
9
+ "domains": ["claude-code"],
10
+ "severity": "high",
11
+ "tags": ["tool-usage"]
12
+ },
13
+ {
14
+ "type": "antipattern",
15
+ "content": "Don't use find/ls to search for files. Use the Glob tool — it's faster, handles permissions correctly, and returns structured results.",
16
+ "domains": ["claude-code"],
17
+ "severity": "high",
18
+ "tags": ["tool-usage"]
19
+ },
20
+ {
21
+ "type": "antipattern",
22
+ "content": "Don't use grep/rg via Bash. Use the Grep tool — it has optimized permissions, supports ripgrep syntax, and provides output modes (content, files, count).",
23
+ "domains": ["claude-code"],
24
+ "severity": "high",
25
+ "tags": ["tool-usage"]
26
+ },
27
+ {
28
+ "type": "antipattern",
29
+ "content": "Don't use cat/head/tail to read files. Use the Read tool — it shows line numbers, supports offset/limit for large files, and can read images and PDFs.",
30
+ "domains": ["claude-code"],
31
+ "severity": "medium",
32
+ "tags": ["tool-usage"]
33
+ },
34
+ {
35
+ "type": "antipattern",
36
+ "content": "Don't commit unless explicitly asked. Creating unauthorized commits can lose work or include unintended changes. Always confirm with the user first.",
37
+ "domains": ["claude-code", "git"],
38
+ "severity": "high",
39
+ "tags": ["git", "workflow"]
40
+ },
41
+ {
42
+ "type": "antipattern",
43
+ "content": "Don't use git push --force or git reset --hard without explicit user permission. These are destructive operations that can lose work.",
44
+ "domains": ["claude-code", "git"],
45
+ "severity": "critical",
46
+ "tags": ["git", "destructive"]
47
+ },
48
+ {
49
+ "type": "semantic",
50
+ "content": "Read a file before editing it. The Edit tool requires the file to have been read first in the conversation. Always Read before Edit.",
51
+ "domains": ["claude-code"],
52
+ "tags": ["tool-usage"]
53
+ },
54
+ {
55
+ "type": "semantic",
56
+ "content": "Use the Agent tool for broad codebase exploration and deep research. Use Glob/Grep directly for simple, directed searches (specific file/class/function).",
57
+ "domains": ["claude-code"],
58
+ "tags": ["tool-usage"]
59
+ },
60
+ {
61
+ "type": "semantic",
62
+ "content": "Make independent tool calls in parallel. If multiple reads or searches don't depend on each other, call them all in the same response for efficiency.",
63
+ "domains": ["claude-code"],
64
+ "tags": ["performance"]
65
+ },
66
+ {
67
+ "type": "antipattern",
68
+ "content": "Don't over-engineer. Only make changes directly requested or clearly necessary. A bug fix doesn't need surrounding code cleaned up. Don't add docstrings, comments, or type annotations to code you didn't change.",
69
+ "domains": ["claude-code"],
70
+ "severity": "medium",
71
+ "tags": ["workflow"]
72
+ },
73
+ {
74
+ "type": "semantic",
75
+ "content": "When editing, the old_string must be unique in the file. If it's not unique, include more surrounding context to make it unique, or use replace_all for global renames.",
76
+ "domains": ["claude-code"],
77
+ "tags": ["tool-usage"]
78
+ },
79
+ {
80
+ "type": "antipattern",
81
+ "content": "Don't skip git hooks with --no-verify. If a pre-commit hook fails, investigate and fix the underlying issue rather than bypassing it.",
82
+ "domains": ["claude-code", "git"],
83
+ "severity": "high",
84
+ "tags": ["git"]
85
+ },
86
+ {
87
+ "type": "semantic",
88
+ "content": "For git commits, always use a HEREDOC for the message to ensure proper formatting: git commit -m \"$(cat <<'EOF'\\nMessage here\\nEOF\\n)\"",
89
+ "domains": ["claude-code", "git"],
90
+ "tags": ["git"]
91
+ },
92
+ {
93
+ "type": "semantic",
94
+ "content": "Avoid backwards-compatibility hacks like renaming unused _vars, re-exporting types, or adding // removed comments. If something is unused, delete it completely.",
95
+ "domains": ["claude-code"],
96
+ "tags": ["code-quality"]
97
+ },
98
+ {
99
+ "type": "antipattern",
100
+ "content": "Don't generate or guess URLs unless confident they're for programming help. Only use URLs provided by the user or found in local files.",
101
+ "domains": ["claude-code"],
102
+ "severity": "medium",
103
+ "tags": ["security"]
104
+ }
105
+ ]
106
+ }
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "Python Common Patterns",
3
+ "version": "1.0.0",
4
+ "description": "Common Python antipatterns and best practices",
5
+ "memories": [
6
+ {
7
+ "type": "antipattern",
8
+ "content": "Never use mutable default arguments: def foo(items=[]). The list is shared across all calls. Use def foo(items=None): items = items or [] instead.",
9
+ "domains": ["python"],
10
+ "severity": "high",
11
+ "tags": ["gotchas"]
12
+ },
13
+ {
14
+ "type": "antipattern",
15
+ "content": "Don't use bare except: clauses. Always catch specific exceptions: except ValueError or at minimum except Exception. Bare except catches SystemExit and KeyboardInterrupt.",
16
+ "domains": ["python"],
17
+ "severity": "high",
18
+ "tags": ["error-handling"]
19
+ },
20
+ {
21
+ "type": "antipattern",
22
+ "content": "Don't use 'is' to compare values. 'is' checks identity (same object), not equality. Use == for value comparison. Exception: 'is None' is correct and preferred.",
23
+ "domains": ["python"],
24
+ "severity": "medium",
25
+ "tags": ["gotchas"]
26
+ },
27
+ {
28
+ "type": "semantic",
29
+ "content": "Use pathlib.Path instead of os.path for path manipulation. Path objects support / operator, are more readable, and handle cross-platform differences: Path('src') / 'main.py'.",
30
+ "domains": ["python"],
31
+ "tags": ["stdlib"]
32
+ },
33
+ {
34
+ "type": "antipattern",
35
+ "content": "Don't modify a list while iterating over it. Use a list comprehension to filter: items = [x for x in items if x.valid] or iterate over a copy: for x in items[:].",
36
+ "domains": ["python"],
37
+ "severity": "high",
38
+ "tags": ["gotchas"]
39
+ },
40
+ {
41
+ "type": "semantic",
42
+ "content": "Use f-strings for string formatting (Python 3.6+): f'{name} is {age}' — faster than .format() and more readable than % formatting.",
43
+ "domains": ["python"],
44
+ "tags": ["style"]
45
+ },
46
+ {
47
+ "type": "antipattern",
48
+ "content": "Don't use 'type(x) == int' for type checking. Use isinstance(x, int) — it handles subclasses correctly and supports tuple of types: isinstance(x, (int, float)).",
49
+ "domains": ["python"],
50
+ "severity": "medium",
51
+ "tags": ["type-checking"]
52
+ },
53
+ {
54
+ "type": "semantic",
55
+ "content": "Use context managers (with statement) for resource management: files, database connections, locks. They guarantee cleanup even on exceptions: with open('f') as fh:",
56
+ "domains": ["python"],
57
+ "tags": ["patterns"]
58
+ },
59
+ {
60
+ "type": "antipattern",
61
+ "content": "Don't use wildcard imports: from module import *. It pollutes the namespace, makes code harder to read, and can cause subtle name collisions. Import explicitly.",
62
+ "domains": ["python"],
63
+ "severity": "medium",
64
+ "tags": ["imports"]
65
+ },
66
+ {
67
+ "type": "semantic",
68
+ "content": "Use dataclasses for simple data containers (Python 3.7+): @dataclass with type annotations. For immutable data, use @dataclass(frozen=True). Prefer over plain dicts or namedtuples.",
69
+ "domains": ["python"],
70
+ "tags": ["patterns"]
71
+ },
72
+ {
73
+ "type": "antipattern",
74
+ "content": "Don't use global variables for shared state. Use function parameters, class attributes, or dependency injection. Globals make testing hard and introduce hidden coupling.",
75
+ "domains": ["python"],
76
+ "severity": "medium",
77
+ "tags": ["architecture"]
78
+ },
79
+ {
80
+ "type": "semantic",
81
+ "content": "Use enumerate() instead of manual index tracking: for i, item in enumerate(items) — not for i in range(len(items)). Cleaner and less error-prone.",
82
+ "domains": ["python"],
83
+ "tags": ["style"]
84
+ },
85
+ {
86
+ "type": "antipattern",
87
+ "content": "Don't concatenate strings in loops with +=. Use ''.join(parts) — string concatenation creates a new string each time (O(n^2)), while join is O(n).",
88
+ "domains": ["python"],
89
+ "severity": "medium",
90
+ "tags": ["performance"]
91
+ },
92
+ {
93
+ "type": "semantic",
94
+ "content": "Use typing module for type hints: def greet(name: str) -> str. For complex types: list[str], dict[str, int], Optional[str], Union[int, str]. Enables IDE support and static analysis.",
95
+ "domains": ["python"],
96
+ "tags": ["type-checking"]
97
+ },
98
+ {
99
+ "type": "antipattern",
100
+ "content": "Don't silence exceptions with pass: except SomeError: pass. At minimum log the error. Silent failures hide bugs and make debugging extremely difficult.",
101
+ "domains": ["python"],
102
+ "severity": "high",
103
+ "tags": ["error-handling"]
104
+ }
105
+ ]
106
+ }
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "TypeScript Common Patterns",
3
+ "version": "1.0.0",
4
+ "description": "Common TypeScript antipatterns and best practices",
5
+ "memories": [
6
+ {
7
+ "type": "antipattern",
8
+ "content": "Never use 'as any' to silence type errors. Use type guards (typeof, instanceof, discriminated unions) or proper generics. 'as any' hides real bugs and defeats the purpose of TypeScript.",
9
+ "domains": ["typescript"],
10
+ "severity": "high",
11
+ "tags": ["type-safety"]
12
+ },
13
+ {
14
+ "type": "antipattern",
15
+ "content": "Don't use == for null checks. Use === null or === undefined explicitly, or use the nullish coalescing operator (??) and optional chaining (?.).",
16
+ "domains": ["typescript"],
17
+ "severity": "medium",
18
+ "tags": ["type-safety"]
19
+ },
20
+ {
21
+ "type": "antipattern",
22
+ "content": "Don't use @ts-ignore to suppress errors. Use @ts-expect-error instead — it will alert you when the error is fixed, preventing stale suppressions.",
23
+ "domains": ["typescript"],
24
+ "severity": "medium",
25
+ "tags": ["type-safety"]
26
+ },
27
+ {
28
+ "type": "semantic",
29
+ "content": "ESM requires .js extension in import paths even when the source file is .ts. TypeScript does not rewrite extensions: import { foo } from './bar.js' (not ./bar.ts).",
30
+ "domains": ["typescript", "node"],
31
+ "tags": ["esm", "modules"]
32
+ },
33
+ {
34
+ "type": "antipattern",
35
+ "content": "Don't use enum for string unions. Use 'type Foo = \"a\" | \"b\" | \"c\"' instead — it produces no runtime JavaScript, tree-shakes better, and is more idiomatic.",
36
+ "domains": ["typescript"],
37
+ "severity": "medium",
38
+ "tags": ["patterns"]
39
+ },
40
+ {
41
+ "type": "antipattern",
42
+ "content": "Don't mutate function parameters. TypeScript won't warn you but it causes subtle bugs. Clone objects/arrays before modifying: const copy = { ...obj } or [...arr].",
43
+ "domains": ["typescript"],
44
+ "severity": "medium",
45
+ "tags": ["immutability"]
46
+ },
47
+ {
48
+ "type": "semantic",
49
+ "content": "Use 'satisfies' to validate a value matches a type without widening: const config = { port: 3000 } satisfies Config. This preserves literal types while checking structure.",
50
+ "domains": ["typescript"],
51
+ "tags": ["type-safety"]
52
+ },
53
+ {
54
+ "type": "antipattern",
55
+ "content": "Don't use ! (non-null assertion) liberally. It tells TypeScript 'trust me, this isn't null' — but if you're wrong, it's a runtime crash. Use proper null checks or optional chaining.",
56
+ "domains": ["typescript"],
57
+ "severity": "high",
58
+ "tags": ["type-safety"]
59
+ },
60
+ {
61
+ "type": "semantic",
62
+ "content": "Use Omit<T, K> to create types without specific properties, Pick<T, K> to select specific ones. Prefer these over redefining interfaces manually.",
63
+ "domains": ["typescript"],
64
+ "tags": ["utility-types"]
65
+ },
66
+ {
67
+ "type": "antipattern",
68
+ "content": "Don't use Object as a type. Use Record<string, unknown> for dictionaries, or define a proper interface. 'Object' matches almost anything and provides no safety.",
69
+ "domains": ["typescript"],
70
+ "severity": "medium",
71
+ "tags": ["type-safety"]
72
+ },
73
+ {
74
+ "type": "semantic",
75
+ "content": "Use 'readonly' on properties and arrays that shouldn't be mutated: readonly items: readonly string[]. This catches accidental mutation at compile time.",
76
+ "domains": ["typescript"],
77
+ "tags": ["immutability"]
78
+ },
79
+ {
80
+ "type": "antipattern",
81
+ "content": "Don't catch errors as 'any'. Use 'catch (e: unknown)' and narrow with instanceof Error. In TypeScript strict mode, caught values are 'unknown' by default.",
82
+ "domains": ["typescript"],
83
+ "severity": "medium",
84
+ "tags": ["error-handling"]
85
+ },
86
+ {
87
+ "type": "semantic",
88
+ "content": "Use discriminated unions for state management: type State = { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error }. Switch on the discriminant for exhaustive handling.",
89
+ "domains": ["typescript"],
90
+ "tags": ["patterns"]
91
+ },
92
+ {
93
+ "type": "antipattern",
94
+ "content": "Don't use 'any' in generic constraints. Use 'unknown' instead: function foo<T extends unknown>() not function foo<T extends any>(). 'unknown' is the safe top type.",
95
+ "domains": ["typescript"],
96
+ "severity": "medium",
97
+ "tags": ["type-safety"]
98
+ },
99
+ {
100
+ "type": "semantic",
101
+ "content": "For Node.js TypeScript projects: set 'module': 'node16' or 'nodenext' in tsconfig, not 'commonjs' or 'esnext'. This enables proper ESM/CJS interop resolution.",
102
+ "domains": ["typescript", "node"],
103
+ "tags": ["config"]
104
+ }
105
+ ]
106
+ }