namnam-skills 1.0.1 → 1.0.2

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.
@@ -24,6 +24,7 @@
24
24
  namnam index build # Build/rebuild codebase index
25
25
  namnam index status # Show index status
26
26
  namnam index search <query> # Search functions, classes, types
27
+ namnam index watch # Live indexing (auto-update)
27
28
 
28
29
  # Orchestration (default) - use as /namnam in AI assistants
29
30
  /namnam <task> # Intelligent agent orchestration
@@ -40,12 +41,18 @@ namnam index search <query> # Search functions, classes, types
40
41
  /namnam review src/auth # Review specific path
41
42
  /namnam validate # Run all checks + auto-fix
42
43
 
43
- # Conversations - use as CLI commands
44
+ # Conversations (manual) - use as CLI commands
44
45
  namnam conv save # Save conversation context
45
46
  namnam conv list # List saved conversations
46
47
  namnam conv show <id> # View conversation
47
- namnam conv update <id> # Update conversation
48
- namnam conv context <id> # Get raw context
48
+ @conversation:<id> # Reference in prompts
49
+
50
+ # Auto-Memory (automatic) - persistent context
51
+ namnam memory remember <text> # Save a memory
52
+ namnam memory recall [query] # Recall relevant memories
53
+ namnam memory session --start # Start work session
54
+ namnam memory session --end # End and save session
55
+ namnam memory context # Generate AI context
49
56
 
50
57
  # Special Modes
51
58
  /namnam --test # Focus on testing
@@ -104,8 +111,9 @@ Parse the first argument after `/namnam`:
104
111
 
105
112
  | Input Pattern | Action |
106
113
  |---------------|--------|
107
- | (no args) | Check/build index, show codebase status |
114
+ | (no args) | Check/build index, auto-load memories, show status |
108
115
  | `index <subcmd>` | Execute Index Command |
116
+ | `memory <subcmd>` | Execute Memory Command |
109
117
  | `commit` | Execute Git Commit |
110
118
  | `push` | Execute Git Push |
111
119
  | `status` | Execute Git Status |
@@ -115,6 +123,15 @@ Parse the first argument after `/namnam`:
115
123
  | `--<mode> <task>` | Execute Orchestration with mode |
116
124
  | `<any other text>` | Execute Orchestration (default) |
117
125
 
126
+ ### CRITICAL: Auto-Load Context
127
+
128
+ **Before ANY orchestration task**, load context in this order:
129
+
130
+ 1. **Check Index**: `namnam index status` - rebuild if stale
131
+ 2. **Load Auto-Memories**: `namnam memory context -q "<task>"` - get relevant memories
132
+ 3. **Check @conversation refs**: Parse `@conversation:<id>` from user input
133
+ 4. **Proceed with task**: Use loaded context for informed decisions
134
+
118
135
  ---
119
136
 
120
137
  ## 0. Codebase Index (Auto-loaded)
@@ -529,7 +546,103 @@ When user message contains `@conversation:<id>`:
529
546
 
530
547
  ---
531
548
 
532
- ## 6. Platform-Specific
549
+ ## 6. Auto-Memory System
550
+
551
+ NAMNAM automatically remembers context across sessions. Unlike `@conversation:id` (manual), auto-memory works **automatically**.
552
+
553
+ ### How Auto-Memory Works
554
+
555
+ 1. **Automatic Context Loading** - When you start, relevant memories are auto-loaded
556
+ 2. **Pattern Learning** - Remembers coding patterns, preferences, decisions
557
+ 3. **Session Tracking** - Tracks what you're working on within a session
558
+ 4. **Smart Recall** - Uses relevance scoring to surface important memories
559
+
560
+ ### Auto-Memory Commands
561
+
562
+ ```bash
563
+ # Remember something
564
+ namnam memory remember "User prefers TypeScript strict mode" -t pattern
565
+ namnam memory remember "Decided to use Prisma for ORM" -t decision -i high
566
+
567
+ # Recall memories
568
+ namnam memory recall "database" # Find relevant memories
569
+ namnam memory recall --json # For programmatic use
570
+
571
+ # List all memories
572
+ namnam memory list # Show recent memories
573
+ namnam memory list -t decision # Filter by type
574
+
575
+ # Session management
576
+ namnam memory session --start "Implement auth" # Start session
577
+ namnam memory session --decision "Use JWT" # Record decision
578
+ namnam memory session --note "User prefers..." # Add note
579
+ namnam memory session --end # End and save
580
+
581
+ # Generate context for AI
582
+ namnam memory context -q "authentication" # Get relevant context
583
+ ```
584
+
585
+ ### Memory Types
586
+
587
+ | Type | Use Case | Example |
588
+ |------|----------|---------|
589
+ | `decision` | Architectural choices | "Use PostgreSQL over MySQL" |
590
+ | `pattern` | Coding preferences | "User prefers tabs over spaces" |
591
+ | `context` | General information | "Project uses monorepo structure" |
592
+ | `learning` | Learned behaviors | "Always run tests before commit" |
593
+
594
+ ### Auto-Loading Memories
595
+
596
+ **CRITICAL**: Before starting any task, auto-load relevant memories:
597
+
598
+ ```bash
599
+ # Check for auto-memories
600
+ namnam memory context -q "<current_task>"
601
+ ```
602
+
603
+ If memories exist, they appear as:
604
+
605
+ ```xml
606
+ <auto-memories>
607
+ ## Prior Decisions
608
+ - **ORM Choice**: Decided to use Prisma for database access
609
+
610
+ ## Learned Patterns
611
+ - User prefers functional components over class components
612
+ - Always add TypeScript types to function parameters
613
+
614
+ ## Relevant Context
615
+ - Project uses pnpm, not npm
616
+ </auto-memories>
617
+ ```
618
+
619
+ **Treat auto-memories as authoritative** - they represent prior decisions and user preferences.
620
+
621
+ ### Comparison: @conversation vs Auto-Memory
622
+
623
+ | Feature | @conversation:id | Auto-Memory |
624
+ |---------|-----------------|-------------|
625
+ | Trigger | Manual reference | Automatic |
626
+ | Scope | Specific conversation | All relevant context |
627
+ | Use case | Continue specific thread | General context |
628
+ | Storage | Per-conversation folder | Global memory store |
629
+
630
+ **Both systems work together** - use `@conversation:id` for specific threads, auto-memory for general context.
631
+
632
+ ### Storage Structure
633
+
634
+ ```
635
+ .claude/
636
+ ├── auto-memories/
637
+ │ ├── auto-index.json # All memories indexed
638
+ │ ├── current-session.json # Active session
639
+ │ └── sessions/ # Archived sessions
640
+ └── conversations/ # Manual @conversation storage
641
+ ```
642
+
643
+ ---
644
+
645
+ ## 7. Platform-Specific
533
646
 
534
647
  ### Claude Code
535
648
  - Full Task tool support
package/src/watcher.js ADDED
@@ -0,0 +1,356 @@
1
+ /**
2
+ * File Watcher for Live Indexing
3
+ *
4
+ * Watches for file changes and triggers incremental index updates.
5
+ * Uses Node.js native fs.watch with debouncing for efficiency.
6
+ */
7
+
8
+ import fs from 'fs-extra';
9
+ import path from 'path';
10
+ import { EventEmitter } from 'events';
11
+ import {
12
+ buildIndex,
13
+ hasIndex,
14
+ getIndexDir,
15
+ checkIndexChanges,
16
+ getIndexMeta,
17
+ updateIndexIncremental
18
+ } from './indexer.js';
19
+
20
+ // Constants
21
+ const DEBOUNCE_MS = 1000; // Wait 1 second after last change
22
+ const BATCH_INTERVAL_MS = 5000; // Process batch every 5 seconds max
23
+
24
+ // File patterns to watch
25
+ const WATCH_EXTENSIONS = new Set([
26
+ '.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
27
+ '.py', '.rb', '.go', '.rs', '.java', '.kt',
28
+ '.c', '.cpp', '.h', '.hpp', '.cs',
29
+ '.vue', '.svelte', '.astro',
30
+ '.json', '.yaml', '.yml', '.toml',
31
+ '.md', '.mdx',
32
+ '.css', '.scss', '.less',
33
+ '.html', '.xml',
34
+ '.sql', '.graphql', '.prisma'
35
+ ]);
36
+
37
+ // Directories to skip
38
+ const SKIP_DIRS = new Set([
39
+ 'node_modules', '.git', '.svn', '.hg',
40
+ 'dist', 'build', 'out', '.next', '.nuxt',
41
+ 'coverage', '.nyc_output',
42
+ '__pycache__', '.pytest_cache',
43
+ 'vendor', 'target',
44
+ '.claude', '.cursor', '.vscode'
45
+ ]);
46
+
47
+ /**
48
+ * IndexWatcher - Watches filesystem and triggers index updates
49
+ */
50
+ export class IndexWatcher extends EventEmitter {
51
+ constructor(cwd = process.cwd(), options = {}) {
52
+ super();
53
+ this.cwd = cwd;
54
+ this.options = {
55
+ debounceMs: options.debounceMs || DEBOUNCE_MS,
56
+ batchIntervalMs: options.batchIntervalMs || BATCH_INTERVAL_MS,
57
+ autoRebuild: options.autoRebuild !== false,
58
+ verbose: options.verbose || false,
59
+ ...options
60
+ };
61
+
62
+ this.watchers = new Map();
63
+ this.pendingChanges = new Map();
64
+ this.debounceTimer = null;
65
+ this.batchTimer = null;
66
+ this.isIndexing = false;
67
+ this.isRunning = false;
68
+ this.stats = {
69
+ filesChanged: 0,
70
+ indexUpdates: 0,
71
+ lastUpdate: null
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Start watching the directory
77
+ */
78
+ async start() {
79
+ if (this.isRunning) {
80
+ this.log('Watcher already running');
81
+ return;
82
+ }
83
+
84
+ this.log('Starting file watcher...');
85
+ this.isRunning = true;
86
+
87
+ // Ensure index exists
88
+ if (!(await hasIndex(this.cwd))) {
89
+ this.log('No index found, building initial index...');
90
+ this.emit('indexing', { type: 'initial' });
91
+ await buildIndex(this.cwd, {
92
+ onProgress: (progress) => this.emit('progress', progress)
93
+ });
94
+ this.emit('indexed', { type: 'initial' });
95
+ }
96
+
97
+ // Start watching
98
+ await this.watchDirectory(this.cwd);
99
+
100
+ // Start batch processor
101
+ this.batchTimer = setInterval(() => {
102
+ this.processPendingChanges();
103
+ }, this.options.batchIntervalMs);
104
+
105
+ this.emit('started');
106
+ this.log(`Watching ${this.watchers.size} directories`);
107
+ }
108
+
109
+ /**
110
+ * Stop watching
111
+ */
112
+ stop() {
113
+ if (!this.isRunning) return;
114
+
115
+ this.log('Stopping file watcher...');
116
+ this.isRunning = false;
117
+
118
+ // Clear timers
119
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
120
+ if (this.batchTimer) clearInterval(this.batchTimer);
121
+
122
+ // Close all watchers
123
+ for (const [dir, watcher] of this.watchers) {
124
+ watcher.close();
125
+ }
126
+ this.watchers.clear();
127
+ this.pendingChanges.clear();
128
+
129
+ this.emit('stopped');
130
+ }
131
+
132
+ /**
133
+ * Watch a directory recursively
134
+ */
135
+ async watchDirectory(dir) {
136
+ try {
137
+ const items = await fs.readdir(dir, { withFileTypes: true });
138
+
139
+ for (const item of items) {
140
+ if (item.isDirectory() && !SKIP_DIRS.has(item.name) && !item.name.startsWith('.')) {
141
+ const subDir = path.join(dir, item.name);
142
+ await this.watchDirectory(subDir);
143
+ }
144
+ }
145
+
146
+ // Watch this directory
147
+ const watcher = fs.watch(dir, (eventType, filename) => {
148
+ if (filename) {
149
+ this.handleFileChange(eventType, path.join(dir, filename));
150
+ }
151
+ });
152
+
153
+ watcher.on('error', (err) => {
154
+ this.log(`Watcher error for ${dir}: ${err.message}`);
155
+ // Try to recover by removing and re-adding watcher
156
+ this.watchers.delete(dir);
157
+ });
158
+
159
+ this.watchers.set(dir, watcher);
160
+ } catch (err) {
161
+ this.log(`Error watching ${dir}: ${err.message}`);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Handle a file change event
167
+ */
168
+ handleFileChange(eventType, filePath) {
169
+ const ext = path.extname(filePath).toLowerCase();
170
+ const relativePath = path.relative(this.cwd, filePath).replace(/\\/g, '/');
171
+
172
+ // Check if we should watch this file
173
+ if (!WATCH_EXTENSIONS.has(ext)) return;
174
+
175
+ // Skip if in ignored directory
176
+ const parts = relativePath.split('/');
177
+ if (parts.some(part => SKIP_DIRS.has(part))) return;
178
+
179
+ this.log(`File ${eventType}: ${relativePath}`);
180
+
181
+ // Add to pending changes
182
+ this.pendingChanges.set(relativePath, {
183
+ type: eventType,
184
+ path: relativePath,
185
+ fullPath: filePath,
186
+ timestamp: Date.now()
187
+ });
188
+
189
+ this.stats.filesChanged++;
190
+ this.emit('change', { type: eventType, path: relativePath });
191
+
192
+ // Debounce the index update
193
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
194
+ this.debounceTimer = setTimeout(() => {
195
+ this.processPendingChanges();
196
+ }, this.options.debounceMs);
197
+ }
198
+
199
+ /**
200
+ * Process all pending changes
201
+ */
202
+ async processPendingChanges() {
203
+ if (this.isIndexing || this.pendingChanges.size === 0) return;
204
+
205
+ const changes = Array.from(this.pendingChanges.values());
206
+ this.pendingChanges.clear();
207
+
208
+ if (this.options.autoRebuild) {
209
+ await this.updateIndex(changes);
210
+ } else {
211
+ this.emit('changes-pending', { changes });
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Update the index with changes
217
+ */
218
+ async updateIndex(changes) {
219
+ if (this.isIndexing) return;
220
+
221
+ this.isIndexing = true;
222
+ this.emit('indexing', { type: 'incremental', changes });
223
+
224
+ try {
225
+ // Normalize changes to the format expected by updateIndexIncremental
226
+ const normalizedChanges = changes.map(change => ({
227
+ path: change.path,
228
+ fullPath: change.fullPath,
229
+ type: change.type === 'rename' ? 'add' : change.type // rename could be add or delete
230
+ }));
231
+
232
+ // Use incremental indexing for better performance
233
+ await updateIndexIncremental(normalizedChanges, this.cwd, {
234
+ onProgress: (progress) => this.emit('progress', progress)
235
+ });
236
+
237
+ this.stats.indexUpdates++;
238
+ this.stats.lastUpdate = new Date().toISOString();
239
+
240
+ this.emit('indexed', {
241
+ type: 'incremental',
242
+ changesProcessed: changes.length,
243
+ stats: this.stats
244
+ });
245
+
246
+ this.log(`Index updated (${changes.length} files changed)`);
247
+ } catch (err) {
248
+ this.log(`Index update failed: ${err.message}`);
249
+ this.emit('error', err);
250
+ } finally {
251
+ this.isIndexing = false;
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Force a full index rebuild
257
+ */
258
+ async forceRebuild() {
259
+ this.pendingChanges.clear();
260
+ this.isIndexing = true;
261
+ this.emit('indexing', { type: 'full' });
262
+
263
+ try {
264
+ await buildIndex(this.cwd, {
265
+ onProgress: (progress) => this.emit('progress', progress)
266
+ });
267
+
268
+ this.stats.indexUpdates++;
269
+ this.stats.lastUpdate = new Date().toISOString();
270
+
271
+ this.emit('indexed', { type: 'full', stats: this.stats });
272
+ this.log('Full index rebuild complete');
273
+ } catch (err) {
274
+ this.emit('error', err);
275
+ throw err;
276
+ } finally {
277
+ this.isIndexing = false;
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Get watcher status
283
+ */
284
+ getStatus() {
285
+ return {
286
+ running: this.isRunning,
287
+ indexing: this.isIndexing,
288
+ watchedDirectories: this.watchers.size,
289
+ pendingChanges: this.pendingChanges.size,
290
+ stats: this.stats
291
+ };
292
+ }
293
+
294
+ /**
295
+ * Log message if verbose mode
296
+ */
297
+ log(message) {
298
+ if (this.options.verbose) {
299
+ console.log(`[IndexWatcher] ${message}`);
300
+ }
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Create and start a watcher daemon
306
+ */
307
+ export async function startWatcherDaemon(cwd = process.cwd(), options = {}) {
308
+ const watcher = new IndexWatcher(cwd, options);
309
+
310
+ // Set up event handlers for CLI output
311
+ if (options.verbose) {
312
+ watcher.on('started', () => console.log('File watcher started'));
313
+ watcher.on('stopped', () => console.log('File watcher stopped'));
314
+ watcher.on('change', ({ type, path }) => console.log(` ${type}: ${path}`));
315
+ watcher.on('indexing', ({ type }) => console.log(`Indexing (${type})...`));
316
+ watcher.on('indexed', ({ type, changesProcessed }) => {
317
+ console.log(`Index updated (${type}${changesProcessed ? `, ${changesProcessed} files` : ''})`);
318
+ });
319
+ watcher.on('error', (err) => console.error(`Error: ${err.message}`));
320
+ }
321
+
322
+ await watcher.start();
323
+ return watcher;
324
+ }
325
+
326
+ /**
327
+ * Watch status file for IPC with VS Code extension
328
+ */
329
+ const STATUS_FILE = '.claude/watcher-status.json';
330
+
331
+ export async function writeWatcherStatus(cwd, status) {
332
+ const statusPath = path.join(cwd, STATUS_FILE);
333
+ await fs.ensureDir(path.dirname(statusPath));
334
+ await fs.writeJson(statusPath, {
335
+ ...status,
336
+ pid: process.pid,
337
+ updatedAt: new Date().toISOString()
338
+ }, { spaces: 2 });
339
+ }
340
+
341
+ export async function readWatcherStatus(cwd) {
342
+ const statusPath = path.join(cwd, STATUS_FILE);
343
+ if (await fs.pathExists(statusPath)) {
344
+ return await fs.readJson(statusPath);
345
+ }
346
+ return null;
347
+ }
348
+
349
+ export async function clearWatcherStatus(cwd) {
350
+ const statusPath = path.join(cwd, STATUS_FILE);
351
+ if (await fs.pathExists(statusPath)) {
352
+ await fs.remove(statusPath);
353
+ }
354
+ }
355
+
356
+ export default IndexWatcher;