@gotza02/sequential-thinking 2026.2.30 → 2026.2.32
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 +1 -0
- package/SYSTEM_INSTRUCTION.md +8 -1
- package/dist/codestore.d.ts +102 -2
- package/dist/codestore.js +499 -17
- package/dist/graph.d.ts +10 -1
- package/dist/graph.js +471 -93
- package/dist/http-server.d.ts +15 -0
- package/dist/http-server.js +732 -70
- package/dist/lib.js +84 -28
- package/dist/notes.d.ts +39 -2
- package/dist/notes.js +233 -24
- package/dist/system_test.js +1 -1
- package/dist/tools/codestore.js +1 -1
- package/dist/tools/filesystem.js +359 -40
- package/dist/tools/thinking.js +7 -1
- package/package.json +1 -1
package/dist/lib.js
CHANGED
|
@@ -63,53 +63,104 @@ export class SequentialThinkingServer {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
attemptRecovery(data) {
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
// Try incremental approach: find the longest valid JSON prefix
|
|
67
|
+
let braceCount = 0;
|
|
68
|
+
let lastValidIndex = -1;
|
|
69
|
+
for (let i = 0; i < data.length; i++) {
|
|
70
|
+
const char = data[i];
|
|
71
|
+
if (char === '{') {
|
|
72
|
+
braceCount++;
|
|
73
|
+
}
|
|
74
|
+
else if (char === '}') {
|
|
75
|
+
braceCount--;
|
|
76
|
+
// When brace count returns to 0, we have a complete object
|
|
77
|
+
if (braceCount === 0) {
|
|
78
|
+
lastValidIndex = i;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (lastValidIndex !== -1) {
|
|
68
83
|
try {
|
|
69
|
-
const recoveredData = data.substring(0,
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
84
|
+
const recoveredData = data.substring(0, lastValidIndex + 1).trim();
|
|
85
|
+
const parsed = JSON.parse(recoveredData);
|
|
86
|
+
// Handle new format with version
|
|
87
|
+
if (parsed.version && parsed.blocks) {
|
|
88
|
+
this.blocks = parsed.blocks;
|
|
89
|
+
this.thoughtHistory = parsed.thoughtHistory || [];
|
|
90
|
+
if (this.blocks.length > 0) {
|
|
91
|
+
const activeBlock = this.blocks.find(b => b.status === 'active');
|
|
92
|
+
this.currentBlockId = activeBlock?.id || this.blocks[this.blocks.length - 1].id;
|
|
93
|
+
}
|
|
94
|
+
this.rebuildBranches();
|
|
95
|
+
console.log(`Successfully recovered ${this.thoughtHistory.length} thoughts from new format.`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Handle legacy array format
|
|
99
|
+
if (Array.isArray(parsed)) {
|
|
100
|
+
parsed.forEach(thought => this.addToMemory(thought));
|
|
74
101
|
this.rebuildBlocks();
|
|
75
|
-
console.log(`Successfully recovered ${
|
|
102
|
+
console.log(`Successfully recovered ${parsed.length} thoughts from legacy format.`);
|
|
76
103
|
}
|
|
77
104
|
}
|
|
78
105
|
catch (recoveryError) {
|
|
79
106
|
console.error('Recovery failed, starting with empty history.');
|
|
80
107
|
}
|
|
81
108
|
}
|
|
109
|
+
else {
|
|
110
|
+
console.error('No valid JSON structure found for recovery.');
|
|
111
|
+
}
|
|
82
112
|
}
|
|
83
113
|
rebuildBlocks() {
|
|
114
|
+
// Map existing blocks to preserve their metadata
|
|
84
115
|
const oldBlocksMap = new Map();
|
|
85
116
|
for (const b of this.blocks) {
|
|
86
|
-
oldBlocksMap.set(b.id, b);
|
|
117
|
+
oldBlocksMap.set(b.id, { ...b });
|
|
87
118
|
}
|
|
88
|
-
|
|
89
|
-
const
|
|
119
|
+
// Map thoughts by blockId from thoughtHistory
|
|
120
|
+
const thoughtsByBlock = new Map();
|
|
90
121
|
for (const t of this.thoughtHistory) {
|
|
91
122
|
const bid = t.blockId || 'default';
|
|
92
|
-
if (!
|
|
93
|
-
|
|
94
|
-
const newBlock = {
|
|
95
|
-
id: bid,
|
|
96
|
-
topic: oldBlock ? oldBlock.topic : t.thought.substring(0, 50),
|
|
97
|
-
status: oldBlock ? oldBlock.status : 'active',
|
|
98
|
-
thoughts: [],
|
|
99
|
-
createdAt: oldBlock ? oldBlock.createdAt : new Date().toISOString(),
|
|
100
|
-
updatedAt: new Date().toISOString()
|
|
101
|
-
};
|
|
102
|
-
blockMap.set(bid, newBlock);
|
|
103
|
-
this.blocks.push(newBlock);
|
|
123
|
+
if (!thoughtsByBlock.has(bid)) {
|
|
124
|
+
thoughtsByBlock.set(bid, []);
|
|
104
125
|
}
|
|
105
|
-
|
|
126
|
+
thoughtsByBlock.get(bid).push(t);
|
|
106
127
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
// Merge old blocks with new thoughts
|
|
129
|
+
const mergedBlocks = [];
|
|
130
|
+
const mergedBlockIds = new Set();
|
|
131
|
+
// First, update existing blocks with their thoughts
|
|
132
|
+
for (const [bid, thoughts] of thoughtsByBlock) {
|
|
133
|
+
const oldBlock = oldBlocksMap.get(bid);
|
|
134
|
+
// Merge old thoughts with new thoughts (avoid duplicates by thoughtNumber)
|
|
135
|
+
const oldThoughts = oldBlock?.thoughts || [];
|
|
136
|
+
const oldThoughtNumbers = new Set(oldThoughts.map(t => t.thoughtNumber));
|
|
137
|
+
const newThoughts = thoughts.filter(t => !oldThoughtNumbers.has(t.thoughtNumber));
|
|
138
|
+
const mergedThoughts = [...oldThoughts, ...newThoughts];
|
|
139
|
+
const mergedBlock = {
|
|
140
|
+
id: bid,
|
|
141
|
+
topic: oldBlock?.topic || thoughts[0]?.thought.substring(0, 50) || 'Untitled',
|
|
142
|
+
status: oldBlock?.status || 'active',
|
|
143
|
+
thoughts: mergedThoughts,
|
|
144
|
+
createdAt: oldBlock?.createdAt || new Date().toISOString(),
|
|
145
|
+
updatedAt: new Date().toISOString()
|
|
146
|
+
};
|
|
147
|
+
mergedBlocks.push(mergedBlock);
|
|
148
|
+
mergedBlockIds.add(bid);
|
|
149
|
+
}
|
|
150
|
+
// Then, preserve blocks that have no thoughts in current thoughtHistory
|
|
151
|
+
for (const [bid, oldBlock] of oldBlocksMap) {
|
|
152
|
+
if (!mergedBlockIds.has(bid)) {
|
|
153
|
+
mergedBlocks.push(oldBlock);
|
|
111
154
|
}
|
|
112
155
|
}
|
|
156
|
+
this.blocks = mergedBlocks;
|
|
157
|
+
// Ensure currentBlockId points to a valid block
|
|
158
|
+
if (this.blocks.length > 0) {
|
|
159
|
+
const activeBlock = this.blocks.find(b => b.status === 'active');
|
|
160
|
+
this.currentBlockId = activeBlock?.id ||
|
|
161
|
+
(this.blocks.find(b => b.id === this.currentBlockId)?.id) ||
|
|
162
|
+
this.blocks[this.blocks.length - 1].id;
|
|
163
|
+
}
|
|
113
164
|
else {
|
|
114
165
|
this.currentBlockId = null;
|
|
115
166
|
}
|
|
@@ -409,6 +460,11 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
|
|
|
409
460
|
recentInBlock.every(t => t.thoughtType === input.thoughtType)) {
|
|
410
461
|
warnings.push(`⚠️ TYPE LOOP: You've used '${input.thoughtType}' for 4 consecutive steps. Consider using a different thought type to progress.`);
|
|
411
462
|
}
|
|
463
|
+
// Rule 7: Struggle Detection (Rule of 3)
|
|
464
|
+
const executionCount = blockThoughts.filter(t => t.thoughtType === 'execution').length;
|
|
465
|
+
if (executionCount >= 3 && input.thoughtType === 'execution') {
|
|
466
|
+
warnings.push(`🛑 STRUGGLE DETECTED: You have executed 3+ commands in this block without reaching a solution. If you are fixing a bug and it's not working, STOP. Do not try a 4th time linearly. Use 'branchFromThought' to try a completely different approach.`);
|
|
467
|
+
}
|
|
412
468
|
// C. Update State
|
|
413
469
|
this.addToMemory(input);
|
|
414
470
|
await this.saveHistory();
|
package/dist/notes.d.ts
CHANGED
|
@@ -12,14 +12,51 @@ export interface Note {
|
|
|
12
12
|
export declare class NotesManager {
|
|
13
13
|
private filePath;
|
|
14
14
|
private notes;
|
|
15
|
-
private
|
|
15
|
+
private lastModifiedTime;
|
|
16
16
|
private mutex;
|
|
17
17
|
constructor(storagePath?: string);
|
|
18
|
+
/**
|
|
19
|
+
* Load notes from disk with automatic reload detection.
|
|
20
|
+
* Uses mtime to detect if file has been modified externally.
|
|
21
|
+
*/
|
|
18
22
|
private load;
|
|
23
|
+
/**
|
|
24
|
+
* Attempt to recover notes from corrupted JSON using incremental parsing.
|
|
25
|
+
* Similar to the approach in lib.ts but adapted for notes format.
|
|
26
|
+
*/
|
|
27
|
+
private attemptRecovery;
|
|
28
|
+
/**
|
|
29
|
+
* Backup corrupted file with timestamp
|
|
30
|
+
*/
|
|
31
|
+
private backupCorruptedFile;
|
|
32
|
+
/**
|
|
33
|
+
* Atomic save using write-to-temp + rename pattern.
|
|
34
|
+
* This ensures either the old file or new file exists, never a partial write.
|
|
35
|
+
*/
|
|
19
36
|
private save;
|
|
37
|
+
/**
|
|
38
|
+
* Reload notes from disk (useful for external changes)
|
|
39
|
+
*/
|
|
40
|
+
reload(): Promise<void>;
|
|
20
41
|
addNote(title: string, content: string, tags?: string[], priority?: Priority, expiresAt?: string): Promise<Note>;
|
|
21
42
|
listNotes(tag?: string, includeExpired?: boolean): Promise<Note[]>;
|
|
22
43
|
searchNotes(query: string): Promise<Note[]>;
|
|
23
44
|
deleteNote(id: string): Promise<boolean>;
|
|
24
|
-
updateNote(id: string, updates: Partial<Pick<Note, 'title' | 'content' | 'tags'>>): Promise<Note | null>;
|
|
45
|
+
updateNote(id: string, updates: Partial<Pick<Note, 'title' | 'content' | 'tags' | 'priority' | 'expiresAt'>>): Promise<Note | null>;
|
|
46
|
+
/**
|
|
47
|
+
* Get a single note by ID
|
|
48
|
+
*/
|
|
49
|
+
getNote(id: string): Promise<Note | null>;
|
|
50
|
+
/**
|
|
51
|
+
* Clear all notes (use with caution!)
|
|
52
|
+
*/
|
|
53
|
+
clearAll(): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Export notes as JSON string
|
|
56
|
+
*/
|
|
57
|
+
export(): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Import notes from JSON string
|
|
60
|
+
*/
|
|
61
|
+
import(jsonData: string, merge?: boolean): Promise<number>;
|
|
25
62
|
}
|
package/dist/notes.js
CHANGED
|
@@ -1,52 +1,185 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
|
+
import { existsSync, statSync } from 'fs';
|
|
2
3
|
import * as path from 'path';
|
|
3
4
|
import { AsyncMutex } from './utils.js';
|
|
4
5
|
export class NotesManager {
|
|
5
6
|
filePath;
|
|
6
7
|
notes = [];
|
|
7
|
-
|
|
8
|
+
lastModifiedTime = 0; // mtime for reload detection
|
|
8
9
|
mutex = new AsyncMutex();
|
|
9
10
|
constructor(storagePath = 'project_notes.json') {
|
|
10
11
|
this.filePath = path.resolve(storagePath);
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Load notes from disk with automatic reload detection.
|
|
15
|
+
* Uses mtime to detect if file has been modified externally.
|
|
16
|
+
*/
|
|
17
|
+
async load(forceReload = false) {
|
|
15
18
|
try {
|
|
19
|
+
// Check if file exists and get its mtime
|
|
20
|
+
if (!existsSync(this.filePath)) {
|
|
21
|
+
this.notes = [];
|
|
22
|
+
this.lastModifiedTime = 0;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const stats = statSync(this.filePath);
|
|
26
|
+
const currentMtime = stats.mtimeMs;
|
|
27
|
+
// Skip reload if file hasn't been modified (unless forced)
|
|
28
|
+
if (!forceReload && currentMtime === this.lastModifiedTime && this.notes.length > 0) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
16
31
|
const data = await fs.readFile(this.filePath, 'utf-8');
|
|
17
|
-
|
|
32
|
+
if (!data.trim()) {
|
|
33
|
+
this.notes = [];
|
|
34
|
+
this.lastModifiedTime = currentMtime;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(data);
|
|
39
|
+
// New format with version
|
|
40
|
+
if (parsed.version && parsed.notes) {
|
|
41
|
+
this.notes = parsed.notes || [];
|
|
42
|
+
}
|
|
43
|
+
// Legacy format (direct array)
|
|
44
|
+
else if (Array.isArray(parsed)) {
|
|
45
|
+
this.notes = parsed;
|
|
46
|
+
}
|
|
47
|
+
// Invalid format
|
|
48
|
+
else {
|
|
49
|
+
throw new Error('Invalid storage format');
|
|
50
|
+
}
|
|
51
|
+
this.lastModifiedTime = currentMtime;
|
|
52
|
+
}
|
|
53
|
+
catch (parseError) {
|
|
54
|
+
console.error(`[NotesManager] Parse error, attempting recovery:`, parseError);
|
|
55
|
+
this.attemptRecovery(data);
|
|
56
|
+
this.lastModifiedTime = currentMtime;
|
|
57
|
+
}
|
|
18
58
|
}
|
|
19
59
|
catch (error) {
|
|
20
|
-
// Case 1: File doesn't exist (Normal first run)
|
|
21
60
|
if (error.code === 'ENOENT') {
|
|
22
61
|
this.notes = [];
|
|
62
|
+
this.lastModifiedTime = 0;
|
|
23
63
|
}
|
|
24
|
-
// Case 2: Corrupted JSON or other read errors
|
|
25
64
|
else {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
65
|
+
console.error(`[NotesManager] Load error:`, error);
|
|
66
|
+
this.notes = [];
|
|
67
|
+
this.lastModifiedTime = 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Attempt to recover notes from corrupted JSON using incremental parsing.
|
|
73
|
+
* Similar to the approach in lib.ts but adapted for notes format.
|
|
74
|
+
*/
|
|
75
|
+
attemptRecovery(data) {
|
|
76
|
+
let braceCount = 0;
|
|
77
|
+
let lastValidIndex = -1;
|
|
78
|
+
for (let i = 0; i < data.length; i++) {
|
|
79
|
+
const char = data[i];
|
|
80
|
+
if (char === '{') {
|
|
81
|
+
braceCount++;
|
|
82
|
+
}
|
|
83
|
+
else if (char === '}') {
|
|
84
|
+
braceCount--;
|
|
85
|
+
if (braceCount === 0) {
|
|
86
|
+
lastValidIndex = i;
|
|
32
87
|
}
|
|
33
|
-
|
|
34
|
-
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (lastValidIndex !== -1) {
|
|
91
|
+
try {
|
|
92
|
+
const recoveredData = data.substring(0, lastValidIndex + 1).trim();
|
|
93
|
+
const parsed = JSON.parse(recoveredData);
|
|
94
|
+
// Try to extract notes from recovered data
|
|
95
|
+
if (parsed.version && parsed.notes) {
|
|
96
|
+
this.notes = parsed.notes;
|
|
97
|
+
console.log(`[NotesManager] Recovered ${this.notes.length} notes from new format.`);
|
|
98
|
+
}
|
|
99
|
+
else if (Array.isArray(parsed)) {
|
|
100
|
+
this.notes = parsed;
|
|
101
|
+
console.log(`[NotesManager] Recovered ${this.notes.length} notes from legacy format.`);
|
|
102
|
+
}
|
|
103
|
+
else if (parsed.notes && Array.isArray(parsed.notes)) {
|
|
104
|
+
this.notes = parsed.notes;
|
|
105
|
+
console.log(`[NotesManager] Recovered ${this.notes.length} notes.`);
|
|
35
106
|
}
|
|
36
|
-
|
|
107
|
+
else {
|
|
108
|
+
this.notes = [];
|
|
109
|
+
}
|
|
110
|
+
// Backup the corrupted file
|
|
111
|
+
this.backupCorruptedFile(data);
|
|
112
|
+
}
|
|
113
|
+
catch (recoveryError) {
|
|
114
|
+
console.error('[NotesManager] Recovery failed:', recoveryError);
|
|
37
115
|
this.notes = [];
|
|
116
|
+
this.backupCorruptedFile(data);
|
|
38
117
|
}
|
|
39
118
|
}
|
|
40
|
-
|
|
119
|
+
else {
|
|
120
|
+
console.error('[NotesManager] No valid JSON structure found.');
|
|
121
|
+
this.notes = [];
|
|
122
|
+
}
|
|
41
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Backup corrupted file with timestamp
|
|
126
|
+
*/
|
|
127
|
+
async backupCorruptedFile(corruptedData) {
|
|
128
|
+
try {
|
|
129
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
130
|
+
const backupPath = `${this.filePath}.corrupted.${timestamp}`;
|
|
131
|
+
await fs.writeFile(backupPath, corruptedData, 'utf-8');
|
|
132
|
+
console.error(`[NotesManager] Corrupted data backed up to: ${backupPath}`);
|
|
133
|
+
}
|
|
134
|
+
catch (backupError) {
|
|
135
|
+
console.error('[NotesManager] Failed to backup corrupted file:', backupError);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Atomic save using write-to-temp + rename pattern.
|
|
140
|
+
* This ensures either the old file or new file exists, never a partial write.
|
|
141
|
+
*/
|
|
42
142
|
async save() {
|
|
43
|
-
|
|
143
|
+
const tmpPath = `${this.filePath}.tmp`;
|
|
144
|
+
// Prepare storage format with version
|
|
145
|
+
const storage = {
|
|
146
|
+
version: '2.0',
|
|
147
|
+
notes: this.notes,
|
|
148
|
+
metadata: {
|
|
149
|
+
lastModified: new Date().toISOString()
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
try {
|
|
153
|
+
// Step 1: Write to temporary file
|
|
154
|
+
await fs.writeFile(tmpPath, JSON.stringify(storage, null, 2), 'utf-8');
|
|
155
|
+
// Step 2: Atomic rename (overwrites target if exists)
|
|
156
|
+
await fs.rename(tmpPath, this.filePath);
|
|
157
|
+
// Step 3: Update our mtime cache
|
|
158
|
+
const stats = await fs.stat(this.filePath);
|
|
159
|
+
this.lastModifiedTime = stats.mtimeMs;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
// Clean up temp file if something went wrong
|
|
163
|
+
try {
|
|
164
|
+
await fs.unlink(tmpPath);
|
|
165
|
+
}
|
|
166
|
+
catch { }
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Reload notes from disk (useful for external changes)
|
|
172
|
+
*/
|
|
173
|
+
async reload() {
|
|
174
|
+
return this.mutex.dispatch(async () => {
|
|
175
|
+
await this.load(true);
|
|
176
|
+
});
|
|
44
177
|
}
|
|
45
178
|
async addNote(title, content, tags = [], priority = 'medium', expiresAt) {
|
|
46
179
|
return this.mutex.dispatch(async () => {
|
|
47
180
|
await this.load();
|
|
48
181
|
const note = {
|
|
49
|
-
id: Date.now().toString(36)
|
|
182
|
+
id: `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`,
|
|
50
183
|
title,
|
|
51
184
|
content,
|
|
52
185
|
tags,
|
|
@@ -64,15 +197,23 @@ export class NotesManager {
|
|
|
64
197
|
return this.mutex.dispatch(async () => {
|
|
65
198
|
await this.load();
|
|
66
199
|
const now = new Date();
|
|
67
|
-
let
|
|
200
|
+
let filteredNotes = this.notes;
|
|
201
|
+
// Filter expired notes
|
|
68
202
|
if (!includeExpired) {
|
|
69
|
-
|
|
203
|
+
filteredNotes = filteredNotes.filter(n => !n.expiresAt || new Date(n.expiresAt) > now);
|
|
70
204
|
}
|
|
205
|
+
// Filter by tag
|
|
71
206
|
if (tag) {
|
|
72
|
-
|
|
207
|
+
filteredNotes = filteredNotes.filter(n => n.tags.includes(tag));
|
|
73
208
|
}
|
|
74
|
-
|
|
75
|
-
|
|
209
|
+
// Sort by priority (highest first)
|
|
210
|
+
return filteredNotes.sort((a, b) => {
|
|
211
|
+
const priorityMap = {
|
|
212
|
+
critical: 4,
|
|
213
|
+
high: 3,
|
|
214
|
+
medium: 2,
|
|
215
|
+
low: 1
|
|
216
|
+
};
|
|
76
217
|
return (priorityMap[b.priority] || 0) - (priorityMap[a.priority] || 0);
|
|
77
218
|
});
|
|
78
219
|
});
|
|
@@ -113,4 +254,72 @@ export class NotesManager {
|
|
|
113
254
|
return this.notes[index];
|
|
114
255
|
});
|
|
115
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Get a single note by ID
|
|
259
|
+
*/
|
|
260
|
+
async getNote(id) {
|
|
261
|
+
return this.mutex.dispatch(async () => {
|
|
262
|
+
await this.load();
|
|
263
|
+
return this.notes.find(n => n.id === id) || null;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Clear all notes (use with caution!)
|
|
268
|
+
*/
|
|
269
|
+
async clearAll() {
|
|
270
|
+
return this.mutex.dispatch(async () => {
|
|
271
|
+
this.notes = [];
|
|
272
|
+
await this.save();
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Export notes as JSON string
|
|
277
|
+
*/
|
|
278
|
+
async export() {
|
|
279
|
+
return this.mutex.dispatch(async () => {
|
|
280
|
+
await this.load();
|
|
281
|
+
return JSON.stringify({
|
|
282
|
+
version: '2.0',
|
|
283
|
+
notes: this.notes,
|
|
284
|
+
exportedAt: new Date().toISOString()
|
|
285
|
+
}, null, 2);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Import notes from JSON string
|
|
290
|
+
*/
|
|
291
|
+
async import(jsonData, merge = false) {
|
|
292
|
+
return this.mutex.dispatch(async () => {
|
|
293
|
+
await this.load();
|
|
294
|
+
try {
|
|
295
|
+
const parsed = JSON.parse(jsonData);
|
|
296
|
+
let importedNotes = [];
|
|
297
|
+
if (parsed.version && parsed.notes) {
|
|
298
|
+
importedNotes = parsed.notes;
|
|
299
|
+
}
|
|
300
|
+
else if (Array.isArray(parsed)) {
|
|
301
|
+
importedNotes = parsed;
|
|
302
|
+
}
|
|
303
|
+
if (merge) {
|
|
304
|
+
// Merge: avoid duplicates by ID
|
|
305
|
+
const existingIds = new Set(this.notes.map(n => n.id));
|
|
306
|
+
for (const note of importedNotes) {
|
|
307
|
+
if (!existingIds.has(note.id)) {
|
|
308
|
+
this.notes.push(note);
|
|
309
|
+
existingIds.add(note.id);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
// Replace all
|
|
315
|
+
this.notes = importedNotes;
|
|
316
|
+
}
|
|
317
|
+
await this.save();
|
|
318
|
+
return this.notes.length;
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
throw new Error(`Failed to import notes: ${error}`);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
116
325
|
}
|
package/dist/system_test.js
CHANGED
|
@@ -36,7 +36,7 @@ async function testSystem() {
|
|
|
36
36
|
console.log("2. Searching for 'Deepest'...");
|
|
37
37
|
const results = await codeDb.searchSnippets("Deepest");
|
|
38
38
|
console.log(`Found ${results.length} result(s):`);
|
|
39
|
-
results.forEach(r => console.log(`- ${r.title} (${r.language})`));
|
|
39
|
+
results.forEach(r => console.log(`- ${r.snippet.title} (${r.snippet.language})`));
|
|
40
40
|
console.log("\n✅ Test Complete!");
|
|
41
41
|
// Cleanup
|
|
42
42
|
try {
|
package/dist/tools/codestore.js
CHANGED
|
@@ -32,7 +32,7 @@ export function registerCodeDbTools(server, db) {
|
|
|
32
32
|
if (results.length > 0) {
|
|
33
33
|
output += `SNIPPETS FOUND:\n`;
|
|
34
34
|
results.forEach(s => {
|
|
35
|
-
output += `ID: ${s.id} | ${s.title} (${s.language})\nDesc: ${s.description}\nCode:\n\`\`\`\n${s.code}\n\`\`\`\n\n`;
|
|
35
|
+
output += `ID: ${s.snippet.id} | ${s.snippet.title} (${s.snippet.language})\nDesc: ${s.snippet.description}\nCode:\n\`\`\`\n${s.snippet.code}\n\`\`\`\n\n`;
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
const matchedPatterns = Object.entries(patterns).filter(([k, v]) => k.toLowerCase().includes(query.toLowerCase()) || v.toLowerCase().includes(query.toLowerCase()));
|