aidex-mcp 1.4.1
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/CHANGELOG.md +128 -0
- package/LICENSE +21 -0
- package/MCP-API-REFERENCE.md +690 -0
- package/README.md +314 -0
- package/build/commands/files.d.ts +28 -0
- package/build/commands/files.js +124 -0
- package/build/commands/index.d.ts +14 -0
- package/build/commands/index.js +14 -0
- package/build/commands/init.d.ts +24 -0
- package/build/commands/init.js +396 -0
- package/build/commands/link.d.ts +45 -0
- package/build/commands/link.js +167 -0
- package/build/commands/note.d.ts +29 -0
- package/build/commands/note.js +105 -0
- package/build/commands/query.d.ts +36 -0
- package/build/commands/query.js +176 -0
- package/build/commands/scan.d.ts +25 -0
- package/build/commands/scan.js +104 -0
- package/build/commands/session.d.ts +52 -0
- package/build/commands/session.js +216 -0
- package/build/commands/signature.d.ts +52 -0
- package/build/commands/signature.js +171 -0
- package/build/commands/summary.d.ts +56 -0
- package/build/commands/summary.js +324 -0
- package/build/commands/update.d.ts +36 -0
- package/build/commands/update.js +273 -0
- package/build/constants.d.ts +10 -0
- package/build/constants.js +10 -0
- package/build/db/database.d.ts +69 -0
- package/build/db/database.js +126 -0
- package/build/db/index.d.ts +7 -0
- package/build/db/index.js +6 -0
- package/build/db/queries.d.ts +163 -0
- package/build/db/queries.js +273 -0
- package/build/db/schema.sql +136 -0
- package/build/index.d.ts +13 -0
- package/build/index.js +74 -0
- package/build/parser/extractor.d.ts +41 -0
- package/build/parser/extractor.js +249 -0
- package/build/parser/index.d.ts +7 -0
- package/build/parser/index.js +7 -0
- package/build/parser/languages/c.d.ts +28 -0
- package/build/parser/languages/c.js +70 -0
- package/build/parser/languages/cpp.d.ts +28 -0
- package/build/parser/languages/cpp.js +91 -0
- package/build/parser/languages/csharp.d.ts +32 -0
- package/build/parser/languages/csharp.js +97 -0
- package/build/parser/languages/go.d.ts +28 -0
- package/build/parser/languages/go.js +83 -0
- package/build/parser/languages/index.d.ts +21 -0
- package/build/parser/languages/index.js +107 -0
- package/build/parser/languages/java.d.ts +28 -0
- package/build/parser/languages/java.js +58 -0
- package/build/parser/languages/php.d.ts +28 -0
- package/build/parser/languages/php.js +75 -0
- package/build/parser/languages/python.d.ts +28 -0
- package/build/parser/languages/python.js +67 -0
- package/build/parser/languages/ruby.d.ts +28 -0
- package/build/parser/languages/ruby.js +68 -0
- package/build/parser/languages/rust.d.ts +28 -0
- package/build/parser/languages/rust.js +73 -0
- package/build/parser/languages/typescript.d.ts +28 -0
- package/build/parser/languages/typescript.js +82 -0
- package/build/parser/tree-sitter.d.ts +30 -0
- package/build/parser/tree-sitter.js +132 -0
- package/build/server/mcp-server.d.ts +7 -0
- package/build/server/mcp-server.js +36 -0
- package/build/server/tools.d.ts +18 -0
- package/build/server/tools.js +1245 -0
- package/build/viewer/git-status.d.ts +25 -0
- package/build/viewer/git-status.js +163 -0
- package/build/viewer/index.d.ts +5 -0
- package/build/viewer/index.js +5 -0
- package/build/viewer/server.d.ts +12 -0
- package/build/viewer/server.js +1122 -0
- package/package.json +66 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* note command - Session notes for cross-session communication
|
|
3
|
+
*
|
|
4
|
+
* Stores a single text note in the project's AiDex database that persists
|
|
5
|
+
* between sessions. Useful for:
|
|
6
|
+
* - Reminders for the next session ("Test glob pattern fix!")
|
|
7
|
+
* - User requests ("Remember to refactor X")
|
|
8
|
+
* - Auto-generated notes before session end
|
|
9
|
+
*
|
|
10
|
+
* v1.3.0 - Session tracking integration
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
import { PRODUCT_NAME, INDEX_DIR, TOOL_PREFIX } from '../constants.js';
|
|
15
|
+
import { openDatabase } from '../db/index.js';
|
|
16
|
+
// ============================================================
|
|
17
|
+
// Constants
|
|
18
|
+
// ============================================================
|
|
19
|
+
const NOTE_KEY = 'session_note';
|
|
20
|
+
// ============================================================
|
|
21
|
+
// Implementation
|
|
22
|
+
// ============================================================
|
|
23
|
+
export function note(params) {
|
|
24
|
+
const { path: projectPath, note: newNote, append, clear } = params;
|
|
25
|
+
// Validate project path
|
|
26
|
+
const dbPath = join(projectPath, INDEX_DIR, 'index.db');
|
|
27
|
+
if (!existsSync(dbPath)) {
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
note: null,
|
|
31
|
+
action: 'read',
|
|
32
|
+
error: `No ${PRODUCT_NAME} index found at ${projectPath}. Run ${TOOL_PREFIX}init first.`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Open database (read-write for writing, read-only for reading)
|
|
36
|
+
const isWriteOperation = newNote !== undefined || clear;
|
|
37
|
+
const db = openDatabase(dbPath, !isWriteOperation);
|
|
38
|
+
try {
|
|
39
|
+
if (clear) {
|
|
40
|
+
// Clear the note
|
|
41
|
+
db.deleteMetadata(NOTE_KEY);
|
|
42
|
+
db.close();
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
note: null,
|
|
46
|
+
action: 'clear',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (newNote !== undefined) {
|
|
50
|
+
// Write or append note
|
|
51
|
+
let finalNote = newNote;
|
|
52
|
+
if (append) {
|
|
53
|
+
// Get existing note first
|
|
54
|
+
const existing = db.getMetadata(NOTE_KEY);
|
|
55
|
+
if (existing) {
|
|
56
|
+
finalNote = existing + '\n' + newNote;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Save the note
|
|
60
|
+
db.setMetadata(NOTE_KEY, finalNote);
|
|
61
|
+
db.close();
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
note: finalNote,
|
|
65
|
+
action: append ? 'append' : 'write',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Read note
|
|
69
|
+
const currentNote = db.getMetadata(NOTE_KEY);
|
|
70
|
+
db.close();
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
note: currentNote,
|
|
74
|
+
action: 'read',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
db.close();
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
note: null,
|
|
82
|
+
action: 'read',
|
|
83
|
+
error: error instanceof Error ? error.message : String(error),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get note for a project (used internally by other tools to include in output)
|
|
89
|
+
*/
|
|
90
|
+
export function getSessionNote(projectPath) {
|
|
91
|
+
const dbPath = join(projectPath, INDEX_DIR, 'index.db');
|
|
92
|
+
if (!existsSync(dbPath)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const db = openDatabase(dbPath, true);
|
|
97
|
+
const currentNote = db.getMetadata(NOTE_KEY);
|
|
98
|
+
db.close();
|
|
99
|
+
return currentNote;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=note.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* query command - Search for terms in the index
|
|
3
|
+
*/
|
|
4
|
+
export type QueryMode = 'exact' | 'contains' | 'starts_with';
|
|
5
|
+
export interface QueryParams {
|
|
6
|
+
path: string;
|
|
7
|
+
term: string;
|
|
8
|
+
mode?: QueryMode;
|
|
9
|
+
fileFilter?: string;
|
|
10
|
+
typeFilter?: string[];
|
|
11
|
+
modifiedSince?: string;
|
|
12
|
+
modifiedBefore?: string;
|
|
13
|
+
limit?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface QueryMatch {
|
|
16
|
+
file: string;
|
|
17
|
+
lineNumber: number;
|
|
18
|
+
lineType: string;
|
|
19
|
+
modified?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface QueryResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
term: string;
|
|
24
|
+
mode: QueryMode;
|
|
25
|
+
matches: QueryMatch[];
|
|
26
|
+
totalMatches: number;
|
|
27
|
+
truncated: boolean;
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function query(params: QueryParams): QueryResult;
|
|
31
|
+
/**
|
|
32
|
+
* Parse time offset string to Unix timestamp
|
|
33
|
+
* Supports: "2h" (hours), "30m" (minutes), "1d" (days), "1w" (weeks), or ISO date string
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseTimeOffset(input: string): number | null;
|
|
36
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* query command - Search for terms in the index
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { PRODUCT_NAME, INDEX_DIR, TOOL_PREFIX } from '../constants.js';
|
|
7
|
+
import { openDatabase, createQueries } from '../db/index.js';
|
|
8
|
+
// ============================================================
|
|
9
|
+
// Main query function
|
|
10
|
+
// ============================================================
|
|
11
|
+
export function query(params) {
|
|
12
|
+
const mode = params.mode ?? 'exact';
|
|
13
|
+
const limit = params.limit ?? 100;
|
|
14
|
+
// Validate project path
|
|
15
|
+
const dbPath = join(params.path, INDEX_DIR, 'index.db');
|
|
16
|
+
if (!existsSync(dbPath)) {
|
|
17
|
+
return {
|
|
18
|
+
success: false,
|
|
19
|
+
term: params.term,
|
|
20
|
+
mode,
|
|
21
|
+
matches: [],
|
|
22
|
+
totalMatches: 0,
|
|
23
|
+
truncated: false,
|
|
24
|
+
error: `No ${PRODUCT_NAME} index found at ${params.path}. Run ${TOOL_PREFIX}init first.`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// Open database
|
|
28
|
+
const db = openDatabase(dbPath, true);
|
|
29
|
+
const queries = createQueries(db);
|
|
30
|
+
try {
|
|
31
|
+
// Search for items
|
|
32
|
+
const items = queries.searchItems(params.term, mode, 1000);
|
|
33
|
+
if (items.length === 0) {
|
|
34
|
+
db.close();
|
|
35
|
+
return {
|
|
36
|
+
success: true,
|
|
37
|
+
term: params.term,
|
|
38
|
+
mode,
|
|
39
|
+
matches: [],
|
|
40
|
+
totalMatches: 0,
|
|
41
|
+
truncated: false,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// Parse time filters
|
|
45
|
+
const modifiedSinceTs = params.modifiedSince ? parseTimeOffset(params.modifiedSince) : null;
|
|
46
|
+
const modifiedBeforeTs = params.modifiedBefore ? parseTimeOffset(params.modifiedBefore) : null;
|
|
47
|
+
// Collect all occurrences
|
|
48
|
+
let allMatches = [];
|
|
49
|
+
for (const item of items) {
|
|
50
|
+
const occurrences = queries.getOccurrencesByItem(item.id);
|
|
51
|
+
for (const occ of occurrences) {
|
|
52
|
+
// Apply file filter
|
|
53
|
+
if (params.fileFilter && !matchesGlob(occ.path, params.fileFilter)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// Apply type filter
|
|
57
|
+
if (params.typeFilter && params.typeFilter.length > 0) {
|
|
58
|
+
if (!params.typeFilter.includes(occ.line_type)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Apply time filters
|
|
63
|
+
if (modifiedSinceTs !== null && occ.modified !== null) {
|
|
64
|
+
if (occ.modified < modifiedSinceTs) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (modifiedBeforeTs !== null && occ.modified !== null) {
|
|
69
|
+
if (occ.modified > modifiedBeforeTs) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
allMatches.push({
|
|
74
|
+
file: occ.path,
|
|
75
|
+
lineNumber: occ.line_number,
|
|
76
|
+
lineType: occ.line_type,
|
|
77
|
+
modified: occ.modified ?? undefined,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Remove duplicates (same file + line)
|
|
82
|
+
const seen = new Set();
|
|
83
|
+
allMatches = allMatches.filter(m => {
|
|
84
|
+
const key = `${m.file}:${m.lineNumber}`;
|
|
85
|
+
if (seen.has(key))
|
|
86
|
+
return false;
|
|
87
|
+
seen.add(key);
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
// Sort by file, then line number
|
|
91
|
+
allMatches.sort((a, b) => {
|
|
92
|
+
const fileCompare = a.file.localeCompare(b.file);
|
|
93
|
+
if (fileCompare !== 0)
|
|
94
|
+
return fileCompare;
|
|
95
|
+
return a.lineNumber - b.lineNumber;
|
|
96
|
+
});
|
|
97
|
+
const totalMatches = allMatches.length;
|
|
98
|
+
const truncated = allMatches.length > limit;
|
|
99
|
+
if (truncated) {
|
|
100
|
+
allMatches = allMatches.slice(0, limit);
|
|
101
|
+
}
|
|
102
|
+
db.close();
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
term: params.term,
|
|
106
|
+
mode,
|
|
107
|
+
matches: allMatches,
|
|
108
|
+
totalMatches,
|
|
109
|
+
truncated,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
db.close();
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
term: params.term,
|
|
117
|
+
mode,
|
|
118
|
+
matches: [],
|
|
119
|
+
totalMatches: 0,
|
|
120
|
+
truncated: false,
|
|
121
|
+
error: error instanceof Error ? error.message : String(error),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// ============================================================
|
|
126
|
+
// Helper functions
|
|
127
|
+
// ============================================================
|
|
128
|
+
/**
|
|
129
|
+
* Parse time offset string to Unix timestamp
|
|
130
|
+
* Supports: "2h" (hours), "30m" (minutes), "1d" (days), "1w" (weeks), or ISO date string
|
|
131
|
+
*/
|
|
132
|
+
export function parseTimeOffset(input) {
|
|
133
|
+
if (!input)
|
|
134
|
+
return null;
|
|
135
|
+
// Try relative time format: 2h, 30m, 1d, 1w
|
|
136
|
+
const match = input.match(/^(\d+)([mhdw])$/i);
|
|
137
|
+
if (match) {
|
|
138
|
+
const value = parseInt(match[1], 10);
|
|
139
|
+
const unit = match[2].toLowerCase();
|
|
140
|
+
const now = Date.now();
|
|
141
|
+
switch (unit) {
|
|
142
|
+
case 'm': return now - value * 60 * 1000; // minutes
|
|
143
|
+
case 'h': return now - value * 60 * 60 * 1000; // hours
|
|
144
|
+
case 'd': return now - value * 24 * 60 * 60 * 1000; // days
|
|
145
|
+
case 'w': return now - value * 7 * 24 * 60 * 60 * 1000; // weeks
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Try ISO date string
|
|
149
|
+
const date = new Date(input);
|
|
150
|
+
if (!isNaN(date.getTime())) {
|
|
151
|
+
return date.getTime();
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Simple glob matching (supports * and ** patterns)
|
|
157
|
+
* Handles patterns like "** /folder/** " correctly for paths starting with folder/
|
|
158
|
+
*/
|
|
159
|
+
function matchesGlob(path, pattern) {
|
|
160
|
+
// Normalize path separators
|
|
161
|
+
const normalizedPath = path.replace(/\\/g, '/');
|
|
162
|
+
const normalizedPattern = pattern.replace(/\\/g, '/');
|
|
163
|
+
// Convert glob to regex using placeholders to avoid interference
|
|
164
|
+
let regex = normalizedPattern
|
|
165
|
+
.replace(/\./g, '\\.') // Escape dots
|
|
166
|
+
.replace(/\*\*\//g, '<<<STARSTAR_SLASH>>>') // **/ placeholder
|
|
167
|
+
.replace(/\/\*\*/g, '<<<SLASH_STARSTAR>>>') // /** placeholder
|
|
168
|
+
.replace(/\*\*/g, '<<<STARSTAR>>>') // standalone ** placeholder
|
|
169
|
+
.replace(/\*/g, '[^/]*') // * matches anything except /
|
|
170
|
+
.replace(/<<<STARSTAR_SLASH>>>/g, '(.*/)?') // **/ = optional prefix ending with /
|
|
171
|
+
.replace(/<<<SLASH_STARSTAR>>>/g, '(/.*)?') // /** = optional suffix starting with /
|
|
172
|
+
.replace(/<<<STARSTAR>>>/g, '.*'); // ** matches anything
|
|
173
|
+
regex = '^' + regex + '$';
|
|
174
|
+
return new RegExp(regex, 'i').test(normalizedPath);
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scan command - Find all AiDex index directories!
|
|
3
|
+
*/
|
|
4
|
+
export interface ScanParams {
|
|
5
|
+
path: string;
|
|
6
|
+
maxDepth?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface IndexedProject {
|
|
9
|
+
path: string;
|
|
10
|
+
name: string;
|
|
11
|
+
files: number;
|
|
12
|
+
items: number;
|
|
13
|
+
methods: number;
|
|
14
|
+
types: number;
|
|
15
|
+
lastIndexed: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ScanResult {
|
|
18
|
+
success: boolean;
|
|
19
|
+
searchPath: string;
|
|
20
|
+
projects: IndexedProject[];
|
|
21
|
+
scannedDirs: number;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function scan(params: ScanParams): ScanResult;
|
|
25
|
+
//# sourceMappingURL=scan.d.ts.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scan command - Find all AiDex index directories!
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readdirSync } from 'fs';
|
|
5
|
+
import { join, basename } from 'path';
|
|
6
|
+
import { INDEX_DIR } from '../constants.js';
|
|
7
|
+
import { openDatabase } from '../db/index.js';
|
|
8
|
+
// ============================================================
|
|
9
|
+
// Default excluded directories
|
|
10
|
+
// ============================================================
|
|
11
|
+
const EXCLUDED_DIRS = new Set([
|
|
12
|
+
'node_modules',
|
|
13
|
+
'.git',
|
|
14
|
+
'.svn',
|
|
15
|
+
'.hg',
|
|
16
|
+
'__pycache__',
|
|
17
|
+
'.cache',
|
|
18
|
+
'dist',
|
|
19
|
+
'build',
|
|
20
|
+
'out',
|
|
21
|
+
'target',
|
|
22
|
+
'bin',
|
|
23
|
+
'obj',
|
|
24
|
+
'.next',
|
|
25
|
+
'.nuxt',
|
|
26
|
+
'vendor',
|
|
27
|
+
'.gradle',
|
|
28
|
+
'.idea',
|
|
29
|
+
'.vscode',
|
|
30
|
+
]);
|
|
31
|
+
// ============================================================
|
|
32
|
+
// Main scan function
|
|
33
|
+
// ============================================================
|
|
34
|
+
export function scan(params) {
|
|
35
|
+
const { path: searchPath, maxDepth = 10 } = params;
|
|
36
|
+
// Validate path
|
|
37
|
+
if (!existsSync(searchPath)) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
searchPath,
|
|
41
|
+
projects: [],
|
|
42
|
+
scannedDirs: 0,
|
|
43
|
+
error: `Path does not exist: ${searchPath}`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const projects = [];
|
|
47
|
+
let scannedDirs = 0;
|
|
48
|
+
function scanDirectory(dirPath, depth) {
|
|
49
|
+
if (depth > maxDepth)
|
|
50
|
+
return;
|
|
51
|
+
scannedDirs++;
|
|
52
|
+
// Check if this directory has an index dir
|
|
53
|
+
const indexPath = join(dirPath, INDEX_DIR);
|
|
54
|
+
const dbPath = join(indexPath, 'index.db');
|
|
55
|
+
if (existsSync(dbPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const db = openDatabase(dbPath, true);
|
|
58
|
+
const stats = db.getStats();
|
|
59
|
+
const projectName = db.getMetadata('project_name') ?? basename(dirPath);
|
|
60
|
+
const lastIndexed = db.getMetadata('last_indexed') ?? 'unknown';
|
|
61
|
+
db.close();
|
|
62
|
+
projects.push({
|
|
63
|
+
path: dirPath,
|
|
64
|
+
name: projectName,
|
|
65
|
+
files: stats.files,
|
|
66
|
+
items: stats.items,
|
|
67
|
+
methods: stats.methods,
|
|
68
|
+
types: stats.types,
|
|
69
|
+
lastIndexed,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Skip invalid databases
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Scan subdirectories
|
|
77
|
+
try {
|
|
78
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
if (!entry.isDirectory())
|
|
81
|
+
continue;
|
|
82
|
+
if (entry.name.startsWith('.') && entry.name !== INDEX_DIR)
|
|
83
|
+
continue;
|
|
84
|
+
if (EXCLUDED_DIRS.has(entry.name))
|
|
85
|
+
continue;
|
|
86
|
+
const subPath = join(dirPath, entry.name);
|
|
87
|
+
scanDirectory(subPath, depth + 1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Skip directories we can't read
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
scanDirectory(searchPath, 0);
|
|
95
|
+
// Sort by path
|
|
96
|
+
projects.sort((a, b) => a.path.localeCompare(b.path));
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
searchPath,
|
|
100
|
+
projects,
|
|
101
|
+
scannedDirs,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session command - Session tracking and external change detection
|
|
3
|
+
*
|
|
4
|
+
* Tracks session start/end times and detects files changed outside of sessions.
|
|
5
|
+
* This enables:
|
|
6
|
+
* - "What did we do last session?" queries using time filtering
|
|
7
|
+
* - Automatic detection of externally modified files that need re-indexing
|
|
8
|
+
*/
|
|
9
|
+
export interface SessionParams {
|
|
10
|
+
path: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SessionInfo {
|
|
13
|
+
lastSessionStart: number | null;
|
|
14
|
+
lastSessionEnd: number | null;
|
|
15
|
+
currentSessionStart: number | null;
|
|
16
|
+
}
|
|
17
|
+
export interface ChangedFile {
|
|
18
|
+
path: string;
|
|
19
|
+
reason: 'modified' | 'deleted' | 'new';
|
|
20
|
+
}
|
|
21
|
+
export interface SessionResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
isNewSession: boolean;
|
|
24
|
+
sessionInfo: SessionInfo;
|
|
25
|
+
externalChanges: ChangedFile[];
|
|
26
|
+
reindexed: string[];
|
|
27
|
+
note: string | null;
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Start or continue a session.
|
|
32
|
+
* - If new session: detect external changes, store previous session times
|
|
33
|
+
* - Always: update session_end timestamp
|
|
34
|
+
*/
|
|
35
|
+
export declare function session(params: SessionParams): SessionResult;
|
|
36
|
+
/**
|
|
37
|
+
* Update session heartbeat (call periodically during session)
|
|
38
|
+
*/
|
|
39
|
+
export declare function updateSessionHeartbeat(projectPath: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get session info without starting/updating
|
|
42
|
+
*/
|
|
43
|
+
export declare function getSessionInfo(projectPath: string): SessionInfo | null;
|
|
44
|
+
/**
|
|
45
|
+
* Format session time for display
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatSessionTime(timestamp: number | null): string;
|
|
48
|
+
/**
|
|
49
|
+
* Format duration between two timestamps
|
|
50
|
+
*/
|
|
51
|
+
export declare function formatDuration(startMs: number, endMs: number): string;
|
|
52
|
+
//# sourceMappingURL=session.d.ts.map
|