@tai-io/codesearch 2026.313.1614
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/dist/build-info.d.ts +3 -0
- package/dist/build-info.js +4 -0
- package/dist/config.d.ts +62 -0
- package/dist/config.js +52 -0
- package/dist/core/cleanup.d.ts +8 -0
- package/dist/core/cleanup.js +41 -0
- package/dist/core/doc-indexer.d.ts +13 -0
- package/dist/core/doc-indexer.js +76 -0
- package/dist/core/doc-searcher.d.ts +13 -0
- package/dist/core/doc-searcher.js +65 -0
- package/dist/core/file-category.d.ts +7 -0
- package/dist/core/file-category.js +75 -0
- package/dist/core/indexer.d.ts +18 -0
- package/dist/core/indexer.js +177 -0
- package/dist/core/preview.d.ts +13 -0
- package/dist/core/preview.js +58 -0
- package/dist/core/repo-map.d.ts +33 -0
- package/dist/core/repo-map.js +144 -0
- package/dist/core/searcher.d.ts +12 -0
- package/dist/core/searcher.js +97 -0
- package/dist/core/sync.d.ts +15 -0
- package/dist/core/sync.js +212 -0
- package/dist/core/targeted-indexer.d.ts +19 -0
- package/dist/core/targeted-indexer.js +127 -0
- package/dist/embedding/factory.d.ts +4 -0
- package/dist/embedding/factory.js +24 -0
- package/dist/embedding/openai.d.ts +33 -0
- package/dist/embedding/openai.js +234 -0
- package/dist/embedding/truncate.d.ts +6 -0
- package/dist/embedding/truncate.js +14 -0
- package/dist/embedding/types.d.ts +18 -0
- package/dist/embedding/types.js +2 -0
- package/dist/errors.d.ts +17 -0
- package/dist/errors.js +21 -0
- package/dist/format.d.ts +18 -0
- package/dist/format.js +151 -0
- package/dist/hooks/cli-router.d.ts +7 -0
- package/dist/hooks/cli-router.js +47 -0
- package/dist/hooks/hook-output.d.ts +56 -0
- package/dist/hooks/hook-output.js +21 -0
- package/dist/hooks/post-tool-use.d.ts +13 -0
- package/dist/hooks/post-tool-use.js +123 -0
- package/dist/hooks/stop-hook.d.ts +11 -0
- package/dist/hooks/stop-hook.js +137 -0
- package/dist/hooks/targeted-runner.d.ts +11 -0
- package/dist/hooks/targeted-runner.js +58 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +138 -0
- package/dist/paths.d.ts +11 -0
- package/dist/paths.js +54 -0
- package/dist/setup-message.d.ts +4 -0
- package/dist/setup-message.js +48 -0
- package/dist/splitter/ast.d.ts +13 -0
- package/dist/splitter/ast.js +231 -0
- package/dist/splitter/line.d.ts +10 -0
- package/dist/splitter/line.js +103 -0
- package/dist/splitter/symbol-extract.d.ts +16 -0
- package/dist/splitter/symbol-extract.js +61 -0
- package/dist/splitter/types.d.ts +16 -0
- package/dist/splitter/types.js +2 -0
- package/dist/state/doc-metadata.d.ts +18 -0
- package/dist/state/doc-metadata.js +59 -0
- package/dist/state/registry.d.ts +7 -0
- package/dist/state/registry.js +46 -0
- package/dist/state/snapshot.d.ts +26 -0
- package/dist/state/snapshot.js +100 -0
- package/dist/tool-schemas.d.ts +215 -0
- package/dist/tool-schemas.js +269 -0
- package/dist/tools.d.ts +58 -0
- package/dist/tools.js +245 -0
- package/dist/vectordb/rrf.d.ts +32 -0
- package/dist/vectordb/rrf.js +88 -0
- package/dist/vectordb/sqlite.d.ts +34 -0
- package/dist/vectordb/sqlite.js +624 -0
- package/dist/vectordb/types.d.ts +63 -0
- package/dist/vectordb/types.js +2 -0
- package/messages.yaml +69 -0
- package/package.json +79 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI subcommand router for hook events.
|
|
3
|
+
*
|
|
4
|
+
* Plugin bash hooks call `npx eidetic-codesearch hook <event>` which routes here.
|
|
5
|
+
*/
|
|
6
|
+
export async function runHook(event) {
|
|
7
|
+
switch (event) {
|
|
8
|
+
case 'stop': {
|
|
9
|
+
const { run } = await import('./stop-hook.js');
|
|
10
|
+
await run();
|
|
11
|
+
break;
|
|
12
|
+
}
|
|
13
|
+
case 'post-tool-use': {
|
|
14
|
+
const { run } = await import('./post-tool-use.js');
|
|
15
|
+
await run();
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
case 'setup-message': {
|
|
19
|
+
const mode = process.argv[4] ?? 'welcome';
|
|
20
|
+
const detail = process.argv[5];
|
|
21
|
+
const { getSetupErrorMessage, getWelcomeMessage } = await import('../setup-message.js');
|
|
22
|
+
if (mode === 'welcome') {
|
|
23
|
+
const output = {
|
|
24
|
+
hookSpecificOutput: {
|
|
25
|
+
hookEventName: 'SessionStart',
|
|
26
|
+
additionalContext: getWelcomeMessage(),
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
process.stdout.write(JSON.stringify(output));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const output = {
|
|
33
|
+
hookSpecificOutput: {
|
|
34
|
+
hookEventName: 'SessionStart',
|
|
35
|
+
additionalContext: getSetupErrorMessage(detail ?? 'OPENAI_API_KEY is not set.', mode),
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
process.stdout.write(JSON.stringify(output));
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
default:
|
|
43
|
+
process.stderr.write(`Unknown hook event: ${event ?? '(none)'}\n`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=cli-router.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strongly typed Claude Code hook output schemas.
|
|
3
|
+
*
|
|
4
|
+
* Every hook must output JSON to stdout matching these types.
|
|
5
|
+
* See: https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
6
|
+
*
|
|
7
|
+
* Base fields (available for ALL hooks):
|
|
8
|
+
* continue, suppressOutput, stopReason, decision, reason, systemMessage
|
|
9
|
+
*
|
|
10
|
+
* hookSpecificOutput varies by event:
|
|
11
|
+
* PreToolUse → permissionDecision, permissionDecisionReason, updatedInput
|
|
12
|
+
* UserPromptSubmit → additionalContext (required)
|
|
13
|
+
* PostToolUse → additionalContext (optional)
|
|
14
|
+
* SessionStart → additionalContext (optional)
|
|
15
|
+
* PreCompact / SessionEnd / Stop → no hookSpecificOutput defined
|
|
16
|
+
*/
|
|
17
|
+
interface HookOutputBase {
|
|
18
|
+
continue?: boolean;
|
|
19
|
+
suppressOutput?: boolean;
|
|
20
|
+
stopReason?: string;
|
|
21
|
+
decision?: 'approve' | 'block';
|
|
22
|
+
reason?: string;
|
|
23
|
+
systemMessage?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface PreToolUseOutput extends HookOutputBase {
|
|
26
|
+
hookSpecificOutput?: {
|
|
27
|
+
hookEventName: 'PreToolUse';
|
|
28
|
+
permissionDecision?: 'allow' | 'deny' | 'ask';
|
|
29
|
+
permissionDecisionReason?: string;
|
|
30
|
+
updatedInput?: Record<string, unknown>;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface UserPromptSubmitOutput extends HookOutputBase {
|
|
34
|
+
hookSpecificOutput: {
|
|
35
|
+
hookEventName: 'UserPromptSubmit';
|
|
36
|
+
additionalContext: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface PostToolUseOutput extends HookOutputBase {
|
|
40
|
+
hookSpecificOutput?: {
|
|
41
|
+
hookEventName: 'PostToolUse';
|
|
42
|
+
additionalContext?: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export interface SessionStartOutput extends HookOutputBase {
|
|
46
|
+
hookSpecificOutput?: {
|
|
47
|
+
hookEventName: 'SessionStart';
|
|
48
|
+
additionalContext?: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** PreCompact, SessionEnd, Stop — no hookSpecificOutput defined. */
|
|
52
|
+
export type SimpleHookOutput = HookOutputBase;
|
|
53
|
+
export type HookOutput = PreToolUseOutput | UserPromptSubmitOutput | PostToolUseOutput | SessionStartOutput | SimpleHookOutput;
|
|
54
|
+
export declare function writeHookOutput(output: HookOutput): void;
|
|
55
|
+
export {};
|
|
56
|
+
//# sourceMappingURL=hook-output.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strongly typed Claude Code hook output schemas.
|
|
3
|
+
*
|
|
4
|
+
* Every hook must output JSON to stdout matching these types.
|
|
5
|
+
* See: https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
6
|
+
*
|
|
7
|
+
* Base fields (available for ALL hooks):
|
|
8
|
+
* continue, suppressOutput, stopReason, decision, reason, systemMessage
|
|
9
|
+
*
|
|
10
|
+
* hookSpecificOutput varies by event:
|
|
11
|
+
* PreToolUse → permissionDecision, permissionDecisionReason, updatedInput
|
|
12
|
+
* UserPromptSubmit → additionalContext (required)
|
|
13
|
+
* PostToolUse → additionalContext (optional)
|
|
14
|
+
* SessionStart → additionalContext (optional)
|
|
15
|
+
* PreCompact / SessionEnd / Stop → no hookSpecificOutput defined
|
|
16
|
+
*/
|
|
17
|
+
// ── Helper to write output to stdout ──────────────────────────────────
|
|
18
|
+
export function writeHookOutput(output) {
|
|
19
|
+
process.stdout.write(JSON.stringify(output));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=hook-output.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PostToolUse hook entry point.
|
|
4
|
+
*
|
|
5
|
+
* Receives hook data via stdin when Write or Edit tools are used.
|
|
6
|
+
* Maintains a shadow git index per session to track which files were
|
|
7
|
+
* modified, without touching HEAD or the working index.
|
|
8
|
+
*
|
|
9
|
+
* Shadow index: <git-dir>/claude/indexes/<session-id>/index
|
|
10
|
+
* Base commit: <git-dir>/claude/indexes/<session-id>/base_commit
|
|
11
|
+
*/
|
|
12
|
+
export declare function run(): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=post-tool-use.d.ts.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PostToolUse hook entry point.
|
|
4
|
+
*
|
|
5
|
+
* Receives hook data via stdin when Write or Edit tools are used.
|
|
6
|
+
* Maintains a shadow git index per session to track which files were
|
|
7
|
+
* modified, without touching HEAD or the working index.
|
|
8
|
+
*
|
|
9
|
+
* Shadow index: <git-dir>/claude/indexes/<session-id>/index
|
|
10
|
+
* Base commit: <git-dir>/claude/indexes/<session-id>/base_commit
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import { execFileSync } from 'node:child_process';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
const PostToolUseInputSchema = z.object({
|
|
18
|
+
session_id: z.string(),
|
|
19
|
+
cwd: z.string(),
|
|
20
|
+
hook_event_name: z.literal('PostToolUse'),
|
|
21
|
+
tool_name: z.string(),
|
|
22
|
+
tool_input: z
|
|
23
|
+
.object({
|
|
24
|
+
file_path: z.string().optional(),
|
|
25
|
+
})
|
|
26
|
+
.passthrough(),
|
|
27
|
+
});
|
|
28
|
+
async function readStdin() {
|
|
29
|
+
const chunks = [];
|
|
30
|
+
for await (const chunk of process.stdin) {
|
|
31
|
+
chunks.push(chunk);
|
|
32
|
+
}
|
|
33
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
34
|
+
}
|
|
35
|
+
function outputSuccess() {
|
|
36
|
+
const output = {
|
|
37
|
+
hookSpecificOutput: { hookEventName: 'PostToolUse' },
|
|
38
|
+
};
|
|
39
|
+
process.stdout.write(JSON.stringify(output));
|
|
40
|
+
}
|
|
41
|
+
function outputError(message) {
|
|
42
|
+
process.stderr.write(`[codesearch:post-tool-use] ${message}\n`);
|
|
43
|
+
const output = {
|
|
44
|
+
hookSpecificOutput: { hookEventName: 'PostToolUse' },
|
|
45
|
+
};
|
|
46
|
+
process.stdout.write(JSON.stringify(output));
|
|
47
|
+
}
|
|
48
|
+
export async function run() {
|
|
49
|
+
try {
|
|
50
|
+
const input = await readStdin();
|
|
51
|
+
const parseResult = PostToolUseInputSchema.safeParse(JSON.parse(input));
|
|
52
|
+
if (!parseResult.success) {
|
|
53
|
+
outputError(`Invalid hook input: ${parseResult.error.message}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const { session_id, cwd, tool_name, tool_input } = parseResult.data;
|
|
57
|
+
// Safety check beyond matcher — only process Write and Edit
|
|
58
|
+
if (tool_name !== 'Write' && tool_name !== 'Edit') {
|
|
59
|
+
outputSuccess();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const filePath = tool_input.file_path;
|
|
63
|
+
if (!filePath) {
|
|
64
|
+
outputSuccess();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Resolve git dir
|
|
68
|
+
let gitDir;
|
|
69
|
+
try {
|
|
70
|
+
gitDir = execFileSync('git', ['-C', cwd, 'rev-parse', '--git-dir'], {
|
|
71
|
+
encoding: 'utf-8',
|
|
72
|
+
timeout: 5000,
|
|
73
|
+
}).trim();
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Not a git repo — silently skip
|
|
77
|
+
outputSuccess();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!path.isAbsolute(gitDir)) {
|
|
81
|
+
gitDir = path.resolve(cwd, gitDir);
|
|
82
|
+
}
|
|
83
|
+
const shadowDir = path.join(gitDir, 'claude', 'indexes', session_id);
|
|
84
|
+
const shadowIndex = path.join(shadowDir, 'index');
|
|
85
|
+
const baseCommitFile = path.join(shadowDir, 'base_commit');
|
|
86
|
+
// First call for this session: seed shadow index from HEAD
|
|
87
|
+
if (!fs.existsSync(shadowIndex)) {
|
|
88
|
+
fs.mkdirSync(shadowDir, { recursive: true });
|
|
89
|
+
let headSha;
|
|
90
|
+
try {
|
|
91
|
+
headSha = execFileSync('git', ['-C', cwd, 'rev-parse', 'HEAD'], {
|
|
92
|
+
encoding: 'utf-8',
|
|
93
|
+
timeout: 5000,
|
|
94
|
+
}).trim();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Empty repo / no commits — cannot seed from HEAD
|
|
98
|
+
outputSuccess();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Seed shadow index from HEAD tree
|
|
102
|
+
execFileSync('git', ['-C', cwd, 'read-tree', `--index-output=${shadowIndex}`, 'HEAD'], {
|
|
103
|
+
timeout: 5000,
|
|
104
|
+
});
|
|
105
|
+
fs.writeFileSync(baseCommitFile, headSha, 'utf-8');
|
|
106
|
+
}
|
|
107
|
+
// Stage the file into the shadow index
|
|
108
|
+
const absoluteFilePath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
109
|
+
execFileSync('git', ['-C', cwd, 'add', absoluteFilePath], {
|
|
110
|
+
env: { ...process.env, GIT_INDEX_FILE: shadowIndex },
|
|
111
|
+
timeout: 5000,
|
|
112
|
+
});
|
|
113
|
+
outputSuccess();
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
outputError(err instanceof Error ? err.message : String(err));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// CLI router calls run() directly; self-execute when run as standalone script
|
|
120
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
121
|
+
void run();
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=post-tool-use.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Stop hook entry point.
|
|
4
|
+
*
|
|
5
|
+
* Receives hook data via stdin when a Claude session ends.
|
|
6
|
+
* Commits the session's shadow git index to refs/heads/claude/<session-id>,
|
|
7
|
+
* diffs against base commit to find modified files, then spawns a
|
|
8
|
+
* detached background targeted re-indexer.
|
|
9
|
+
*/
|
|
10
|
+
export declare function run(): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=stop-hook.d.ts.map
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Stop hook entry point.
|
|
4
|
+
*
|
|
5
|
+
* Receives hook data via stdin when a Claude session ends.
|
|
6
|
+
* Commits the session's shadow git index to refs/heads/claude/<session-id>,
|
|
7
|
+
* diffs against base commit to find modified files, then spawns a
|
|
8
|
+
* detached background targeted re-indexer.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import os from 'node:os';
|
|
14
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
const StopInputSchema = z.object({
|
|
19
|
+
session_id: z.string(),
|
|
20
|
+
cwd: z.string(),
|
|
21
|
+
hook_event_name: z.literal('Stop'),
|
|
22
|
+
stop_hook_active: z.boolean().optional(),
|
|
23
|
+
});
|
|
24
|
+
async function readStdin() {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
for await (const chunk of process.stdin) {
|
|
27
|
+
chunks.push(chunk);
|
|
28
|
+
}
|
|
29
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
30
|
+
}
|
|
31
|
+
function outputSuccess() {
|
|
32
|
+
const output = {};
|
|
33
|
+
process.stdout.write(JSON.stringify(output));
|
|
34
|
+
}
|
|
35
|
+
function outputError(message) {
|
|
36
|
+
process.stderr.write(`[codesearch:stop-hook] ${message}\n`);
|
|
37
|
+
const output = {};
|
|
38
|
+
process.stdout.write(JSON.stringify(output));
|
|
39
|
+
}
|
|
40
|
+
export async function run() {
|
|
41
|
+
try {
|
|
42
|
+
const input = await readStdin();
|
|
43
|
+
const parseResult = StopInputSchema.safeParse(JSON.parse(input));
|
|
44
|
+
if (!parseResult.success) {
|
|
45
|
+
outputError(`Invalid hook input: ${parseResult.error.message}`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const { session_id, cwd } = parseResult.data;
|
|
49
|
+
// Resolve git dir
|
|
50
|
+
let gitDir;
|
|
51
|
+
try {
|
|
52
|
+
gitDir = execFileSync('git', ['-C', cwd, 'rev-parse', '--git-dir'], {
|
|
53
|
+
encoding: 'utf-8',
|
|
54
|
+
timeout: 5000,
|
|
55
|
+
}).trim();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Not a git repo — nothing to do
|
|
59
|
+
outputSuccess();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!path.isAbsolute(gitDir)) {
|
|
63
|
+
gitDir = path.resolve(cwd, gitDir);
|
|
64
|
+
}
|
|
65
|
+
const shadowDir = path.join(gitDir, 'claude', 'indexes', session_id);
|
|
66
|
+
const shadowIndex = path.join(shadowDir, 'index');
|
|
67
|
+
const baseCommitFile = path.join(shadowDir, 'base_commit');
|
|
68
|
+
// No shadow index means no edits happened this session
|
|
69
|
+
if (!fs.existsSync(shadowIndex)) {
|
|
70
|
+
outputSuccess();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!fs.existsSync(baseCommitFile)) {
|
|
74
|
+
// Missing base commit file — clean up and bail
|
|
75
|
+
try {
|
|
76
|
+
fs.rmSync(shadowDir, { recursive: true, force: true });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Cleanup is best-effort
|
|
80
|
+
}
|
|
81
|
+
outputSuccess();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const baseCommit = fs.readFileSync(baseCommitFile, 'utf-8').trim();
|
|
85
|
+
// Write tree from shadow index
|
|
86
|
+
const treeSha = execFileSync('git', ['-C', cwd, 'write-tree'], {
|
|
87
|
+
env: { ...process.env, GIT_INDEX_FILE: shadowIndex },
|
|
88
|
+
encoding: 'utf-8',
|
|
89
|
+
timeout: 10000,
|
|
90
|
+
}).trim();
|
|
91
|
+
// Create a commit object pointing to that tree
|
|
92
|
+
const commitSha = execFileSync('git', ['-C', cwd, 'commit-tree', treeSha, '-p', baseCommit, '-m', `codesearch: session ${session_id}`], { encoding: 'utf-8', timeout: 10000 }).trim();
|
|
93
|
+
// Store under refs/heads/claude/<session-id> for history
|
|
94
|
+
execFileSync('git', ['-C', cwd, 'update-ref', `refs/heads/claude/${session_id}`, commitSha], {
|
|
95
|
+
timeout: 5000,
|
|
96
|
+
});
|
|
97
|
+
// Find files that changed between base and new commit
|
|
98
|
+
const diffOutput = execFileSync('git', ['-C', cwd, 'diff-tree', '--no-commit-id', '--name-only', '-r', baseCommit, commitSha], { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
99
|
+
const modifiedFiles = diffOutput
|
|
100
|
+
.split('\n')
|
|
101
|
+
.map((f) => f.trim())
|
|
102
|
+
.filter((f) => f.length > 0);
|
|
103
|
+
// Clean up shadow index directory
|
|
104
|
+
try {
|
|
105
|
+
fs.rmSync(shadowDir, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
process.stderr.write(`[codesearch:stop-hook] Failed to clean shadow index: ${String(err)}\n`);
|
|
109
|
+
}
|
|
110
|
+
if (modifiedFiles.length === 0) {
|
|
111
|
+
outputSuccess();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// Write manifest for targeted runner
|
|
115
|
+
const manifest = { projectPath: cwd, modifiedFiles };
|
|
116
|
+
const manifestFile = path.join(os.tmpdir(), `codesearch-reindex-${session_id}.json`);
|
|
117
|
+
fs.writeFileSync(manifestFile, JSON.stringify(manifest), 'utf-8');
|
|
118
|
+
// Spawn detached background targeted runner
|
|
119
|
+
const runnerPath = path.join(__dirname, 'targeted-runner.js');
|
|
120
|
+
const child = spawn(process.execPath, [runnerPath, manifestFile], {
|
|
121
|
+
detached: true,
|
|
122
|
+
stdio: 'ignore',
|
|
123
|
+
env: process.env,
|
|
124
|
+
windowsHide: true,
|
|
125
|
+
});
|
|
126
|
+
child.unref();
|
|
127
|
+
outputSuccess();
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
outputError(err instanceof Error ? err.message : String(err));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// CLI router calls run() directly; self-execute when run as standalone script
|
|
134
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
135
|
+
void run();
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=stop-hook.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone CLI for background targeted re-indexing.
|
|
4
|
+
* Spawned as a detached child process by stop-hook.ts.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node targeted-runner.js <manifest-json-path>
|
|
7
|
+
*
|
|
8
|
+
* Manifest JSON: { projectPath: string, modifiedFiles: string[] }
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=targeted-runner.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone CLI for background targeted re-indexing.
|
|
4
|
+
* Spawned as a detached child process by stop-hook.ts.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node targeted-runner.js <manifest-json-path>
|
|
7
|
+
*
|
|
8
|
+
* Manifest JSON: { projectPath: string, modifiedFiles: string[] }
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import { indexFiles } from '../core/targeted-indexer.js';
|
|
12
|
+
import { createEmbedding } from '../embedding/factory.js';
|
|
13
|
+
import { SqliteVectorDB } from '../vectordb/sqlite.js';
|
|
14
|
+
import { getCodesearchDbPath } from '../paths.js';
|
|
15
|
+
import { loadConfig } from '../config.js';
|
|
16
|
+
async function main() {
|
|
17
|
+
const manifestPath = process.argv[2];
|
|
18
|
+
if (!manifestPath) {
|
|
19
|
+
process.stderr.write('Usage: targeted-runner.js <manifest-json-path>\n');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
let manifest;
|
|
23
|
+
try {
|
|
24
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
process.stderr.write(`[targeted-runner] Failed to read manifest: ${String(err)}\n`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
// Clean up manifest file
|
|
31
|
+
try {
|
|
32
|
+
fs.unlinkSync(manifestPath);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Ignore — best effort
|
|
36
|
+
}
|
|
37
|
+
const { projectPath, modifiedFiles } = manifest;
|
|
38
|
+
if (!projectPath || !Array.isArray(modifiedFiles) || modifiedFiles.length === 0) {
|
|
39
|
+
process.stderr.write('[targeted-runner] Empty or invalid manifest, nothing to do.\n');
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const config = loadConfig();
|
|
44
|
+
const embedding = createEmbedding(config);
|
|
45
|
+
await embedding.initialize();
|
|
46
|
+
const vectordb = new SqliteVectorDB(getCodesearchDbPath());
|
|
47
|
+
const result = await indexFiles(projectPath, modifiedFiles, embedding, vectordb);
|
|
48
|
+
process.stderr.write(`[targeted-runner] Re-indexed ${result.processedFiles} files ` +
|
|
49
|
+
`(${result.totalChunks} chunks, ${result.skippedFiles} deleted) ` +
|
|
50
|
+
`in ${result.durationMs}ms\n`);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
process.stderr.write(`[targeted-runner] Failed: ${String(err)}\n`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
void main();
|
|
58
|
+
//# sourceMappingURL=targeted-runner.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CRITICAL: Redirect console outputs to stderr BEFORE any imports
|
|
3
|
+
// Only MCP protocol messages should go to stdout
|
|
4
|
+
console.log = (...args) => {
|
|
5
|
+
process.stderr.write('[LOG] ' + args.join(' ') + '\n');
|
|
6
|
+
};
|
|
7
|
+
console.warn = (...args) => {
|
|
8
|
+
process.stderr.write('[WARN] ' + args.join(' ') + '\n');
|
|
9
|
+
};
|
|
10
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
import { loadConfig } from './config.js';
|
|
14
|
+
import { createEmbedding } from './embedding/factory.js';
|
|
15
|
+
import { SqliteVectorDB } from './vectordb/sqlite.js';
|
|
16
|
+
import { getCodesearchDbPath } from './paths.js';
|
|
17
|
+
import { StateManager, cleanupOrphanedSnapshots } from './state/snapshot.js';
|
|
18
|
+
import { listProjects } from './state/registry.js';
|
|
19
|
+
import { ToolHandlers } from './tools.js';
|
|
20
|
+
import { TOOL_DEFINITIONS } from './tool-schemas.js';
|
|
21
|
+
import { getSetupErrorMessage } from './setup-message.js';
|
|
22
|
+
import { BUILD_VERSION, BUILD_TIMESTAMP } from './build-info.js';
|
|
23
|
+
const SERVER_INSTRUCTIONS = `# Eidetic Codesearch — Workflow
|
|
24
|
+
|
|
25
|
+
**Before searching:** Ensure the codebase is indexed.
|
|
26
|
+
- \`list\` → see what's already indexed
|
|
27
|
+
- \`index(path="...", dryRun=true)\` → preview before indexing
|
|
28
|
+
- \`index(path="...")\` → index (incremental, only re-embeds changed files)
|
|
29
|
+
|
|
30
|
+
**Searching efficiently:**
|
|
31
|
+
- \`search(query="...")\` → returns compact table by default (~20 tokens/result)
|
|
32
|
+
- Review the table, then use Read tool to fetch full code for interesting results
|
|
33
|
+
- Add \`compact=false\` only when you need all code snippets immediately
|
|
34
|
+
- Use \`extensionFilter\` to narrow by file type
|
|
35
|
+
- Use \`project\` param instead of \`path\` for convenience
|
|
36
|
+
- Start with specific queries, broaden if no results
|
|
37
|
+
|
|
38
|
+
**After first index:**
|
|
39
|
+
- Re-indexing is incremental (only changed files re-embedded)
|
|
40
|
+
- Use \`project\` param instead of \`path\` for convenience
|
|
41
|
+
- Use \`cleanup(path="...", dryRun=true)\` to preview stale vectors
|
|
42
|
+
|
|
43
|
+
**Documentation caching (saves ~5K tokens per repeated doc fetch):**
|
|
44
|
+
- After fetching docs via query-docs or WebFetch, cache them: \`ingest(content="...", source="<url>", library="<name>", topic="<topic>")\`
|
|
45
|
+
- Next time you need the same docs: \`lookup(query="...", library="<name>")\`
|
|
46
|
+
- Stale docs (past TTL) still return results but are flagged \`[STALE]\``;
|
|
47
|
+
async function main() {
|
|
48
|
+
// CLI subcommand routing — hooks call `npx eidetic-codesearch hook <event>`
|
|
49
|
+
if (process.argv[2] === 'hook') {
|
|
50
|
+
const { runHook } = await import('./hooks/cli-router.js');
|
|
51
|
+
await runHook(process.argv[3]);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
const config = loadConfig();
|
|
55
|
+
console.log(`Config loaded. Model: ${config.embeddingModel}`);
|
|
56
|
+
let handlers = null;
|
|
57
|
+
let setupError = null;
|
|
58
|
+
try {
|
|
59
|
+
const embedding = createEmbedding(config);
|
|
60
|
+
await embedding.initialize();
|
|
61
|
+
const vectordb = new SqliteVectorDB(getCodesearchDbPath());
|
|
62
|
+
console.log('Using SQLite vector database.');
|
|
63
|
+
const cleaned = await cleanupOrphanedSnapshots(vectordb);
|
|
64
|
+
if (cleaned > 0) {
|
|
65
|
+
console.log(`Cleaned ${cleaned} orphaned snapshot(s).`);
|
|
66
|
+
}
|
|
67
|
+
const state = new StateManager();
|
|
68
|
+
const hydrated = await state.hydrate(listProjects(), vectordb);
|
|
69
|
+
if (hydrated > 0) {
|
|
70
|
+
console.log(`Hydrated ${hydrated} project(s) from registry.`);
|
|
71
|
+
}
|
|
72
|
+
handlers = new ToolHandlers(embedding, vectordb, state);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
setupError = err instanceof Error ? err.message : String(err);
|
|
76
|
+
console.warn(`Codesearch initialization failed: ${setupError}`);
|
|
77
|
+
console.warn('Server will start in setup-required mode. All tool calls will return setup instructions.');
|
|
78
|
+
}
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
80
|
+
const server = new Server({ name: '@tai-io/codesearch', version: BUILD_VERSION }, { capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS });
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
82
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
83
|
+
tools: [...TOOL_DEFINITIONS],
|
|
84
|
+
}));
|
|
85
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
86
|
+
const { name, arguments: args } = request.params;
|
|
87
|
+
if (!handlers) {
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: 'text',
|
|
92
|
+
text: getSetupErrorMessage(setupError ?? 'Unknown error'),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
isError: true,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
switch (name) {
|
|
99
|
+
case 'index':
|
|
100
|
+
return handlers.handleIndex(args ?? {});
|
|
101
|
+
case 'search':
|
|
102
|
+
return handlers.handleSearch(args ?? {});
|
|
103
|
+
case 'clear':
|
|
104
|
+
return handlers.handleClear(args ?? {});
|
|
105
|
+
case 'list':
|
|
106
|
+
return handlers.handleList();
|
|
107
|
+
case 'ingest':
|
|
108
|
+
return handlers.handleIngest(args ?? {});
|
|
109
|
+
case 'lookup':
|
|
110
|
+
return handlers.handleLookup(args ?? {});
|
|
111
|
+
case 'cleanup':
|
|
112
|
+
return handlers.handleCleanup(args ?? {});
|
|
113
|
+
case 'browse':
|
|
114
|
+
return handlers.handleBrowse(args ?? {});
|
|
115
|
+
default:
|
|
116
|
+
return {
|
|
117
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
118
|
+
isError: true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
const transport = new StdioServerTransport();
|
|
123
|
+
await server.connect(transport);
|
|
124
|
+
console.log(`Eidetic Codesearch MCP server v${BUILD_VERSION} (built ${BUILD_TIMESTAMP}) started on stdio.`);
|
|
125
|
+
}
|
|
126
|
+
process.on('SIGINT', () => {
|
|
127
|
+
console.error('Received SIGINT, shutting down...');
|
|
128
|
+
process.exit(0);
|
|
129
|
+
});
|
|
130
|
+
process.on('SIGTERM', () => {
|
|
131
|
+
console.error('Received SIGTERM, shutting down...');
|
|
132
|
+
process.exit(0);
|
|
133
|
+
});
|
|
134
|
+
main().catch((err) => {
|
|
135
|
+
console.error('Fatal error:', err);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
138
|
+
//# sourceMappingURL=index.js.map
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function normalizePath(inputPath: string): string;
|
|
2
|
+
export declare function getDataDir(): string;
|
|
3
|
+
export declare function getSnapshotDir(): string;
|
|
4
|
+
export declare function getCacheDir(): string;
|
|
5
|
+
export declare function getRegistryPath(): string;
|
|
6
|
+
export declare function pathToCollectionName(absolutePath: string): string;
|
|
7
|
+
export declare function getCodesearchDbPath(): string;
|
|
8
|
+
export declare function getSnapshotDbPath(): string;
|
|
9
|
+
export declare function getDocMetadataPath(): string;
|
|
10
|
+
export declare function docCollectionName(library: string): string;
|
|
11
|
+
//# sourceMappingURL=paths.d.ts.map
|