@pimzino/sgrep 1.3.0
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 +413 -0
- package/build/.claude-plugin/marketplace.json +20 -0
- package/build/commands/ask.d.ts +11 -0
- package/build/commands/ask.d.ts.map +1 -0
- package/build/commands/ask.js +65 -0
- package/build/commands/ask.js.map +1 -0
- package/build/commands/enhance.d.ts +16 -0
- package/build/commands/enhance.d.ts.map +1 -0
- package/build/commands/enhance.js +107 -0
- package/build/commands/enhance.js.map +1 -0
- package/build/commands/search.d.ts +12 -0
- package/build/commands/search.d.ts.map +1 -0
- package/build/commands/search.js +65 -0
- package/build/commands/search.js.map +1 -0
- package/build/commands/watch.d.ts +13 -0
- package/build/commands/watch.d.ts.map +1 -0
- package/build/commands/watch.js +179 -0
- package/build/commands/watch.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +124 -0
- package/build/index.js.map +1 -0
- package/build/install/claude-code.d.ts +10 -0
- package/build/install/claude-code.d.ts.map +1 -0
- package/build/install/claude-code.js +255 -0
- package/build/install/claude-code.js.map +1 -0
- package/build/install/codex.d.ts +4 -0
- package/build/install/codex.d.ts.map +1 -0
- package/build/install/codex.js +123 -0
- package/build/install/codex.js.map +1 -0
- package/build/install/cursor.d.ts +4 -0
- package/build/install/cursor.d.ts.map +1 -0
- package/build/install/cursor.js +91 -0
- package/build/install/cursor.js.map +1 -0
- package/build/install/droid.d.ts +4 -0
- package/build/install/droid.d.ts.map +1 -0
- package/build/install/droid.js +226 -0
- package/build/install/droid.js.map +1 -0
- package/build/install/opencode.d.ts +4 -0
- package/build/install/opencode.d.ts.map +1 -0
- package/build/install/opencode.js +276 -0
- package/build/install/opencode.js.map +1 -0
- package/build/plugins/sgrep/.claude-plugin/plugin.json +9 -0
- package/build/plugins/sgrep/hooks/hooks.json +39 -0
- package/build/plugins/sgrep/hooks/sgrep_enhance.js +160 -0
- package/build/plugins/sgrep/hooks/sgrep_watch.js +240 -0
- package/build/plugins/sgrep/hooks/sgrep_watch_kill.js +158 -0
- package/build/plugins/sgrep/skills/sgrep/SKILL.md +97 -0
- package/build/utils/config.d.ts +26 -0
- package/build/utils/config.d.ts.map +1 -0
- package/build/utils/config.js +106 -0
- package/build/utils/config.js.map +1 -0
- package/build/utils/context-manager.d.ts +33 -0
- package/build/utils/context-manager.d.ts.map +1 -0
- package/build/utils/context-manager.js +244 -0
- package/build/utils/context-manager.js.map +1 -0
- package/build/utils/context.d.ts +25 -0
- package/build/utils/context.d.ts.map +1 -0
- package/build/utils/context.js +132 -0
- package/build/utils/context.js.map +1 -0
- package/build/utils/file-walker.d.ts +20 -0
- package/build/utils/file-walker.d.ts.map +1 -0
- package/build/utils/file-walker.js +239 -0
- package/build/utils/file-walker.js.map +1 -0
- package/build/utils/output.d.ts +38 -0
- package/build/utils/output.d.ts.map +1 -0
- package/build/utils/output.js +150 -0
- package/build/utils/output.js.map +1 -0
- package/build/utils/prompt-parser.d.ts +12 -0
- package/build/utils/prompt-parser.d.ts.map +1 -0
- package/build/utils/prompt-parser.js +54 -0
- package/build/utils/prompt-parser.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { execSync } = require("child_process");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
|
|
8
|
+
const DEBUG_ENABLED = process.env.SGREP_DEBUG === "1" || process.env.SGREP_DEBUG === "true";
|
|
9
|
+
const DEBUG_LOG_FILE = process.env.SGREP_ENHANCE_LOG || path.join(os.tmpdir(), "sgrep-enhance.log");
|
|
10
|
+
|
|
11
|
+
function debugLog(message) {
|
|
12
|
+
if (!DEBUG_ENABLED) return;
|
|
13
|
+
try {
|
|
14
|
+
const dir = path.dirname(DEBUG_LOG_FILE);
|
|
15
|
+
if (!fs.existsSync(dir)) {
|
|
16
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
const stamp = new Date().toISOString();
|
|
19
|
+
fs.appendFileSync(DEBUG_LOG_FILE, `[${stamp}] ${message}\n`);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
// Ignore errors
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function debugLogSection(title, content) {
|
|
26
|
+
debugLog(`\n========== ${title} ==========`);
|
|
27
|
+
debugLog(content);
|
|
28
|
+
debugLog(`========== END ${title} ==========\n`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readHookInput() {
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
let data = "";
|
|
34
|
+
process.stdin.setEncoding("utf8");
|
|
35
|
+
process.stdin.on("data", (chunk) => {
|
|
36
|
+
data += chunk;
|
|
37
|
+
});
|
|
38
|
+
process.stdin.on("end", () => {
|
|
39
|
+
if (!data.trim()) {
|
|
40
|
+
debugLog("No input data received from stdin");
|
|
41
|
+
resolve(null);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(data);
|
|
46
|
+
debugLogSection("RAW HOOK INPUT", JSON.stringify(parsed, null, 2));
|
|
47
|
+
resolve(parsed);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
debugLog(`Failed to decode JSON: ${e.message}`);
|
|
50
|
+
debugLog(`Raw data: ${data}`);
|
|
51
|
+
resolve(null);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Determine if enhancement should proceed.
|
|
59
|
+
* Auto-confirms on all platforms (no user prompting).
|
|
60
|
+
*/
|
|
61
|
+
function shouldEnhance(prompt) {
|
|
62
|
+
debugLog("Auto-confirming enhancement (prompting disabled on all platforms)");
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function main() {
|
|
67
|
+
debugLog("\n\n====================================================");
|
|
68
|
+
debugLog("SGREP ENHANCE HOOK TRIGGERED - UserPromptSubmit");
|
|
69
|
+
debugLog("====================================================");
|
|
70
|
+
|
|
71
|
+
const payload = await readHookInput();
|
|
72
|
+
if (!payload) {
|
|
73
|
+
debugLog("ERROR: No payload received - exiting");
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Log all payload fields
|
|
78
|
+
debugLog(`Hook Event: ${payload.hook_event_name || 'unknown'}`);
|
|
79
|
+
debugLog(`Session ID: ${payload.session_id || 'unknown'}`);
|
|
80
|
+
debugLog(`CWD: ${payload.cwd || 'unknown'}`);
|
|
81
|
+
debugLog(`Permission Mode: ${payload.permission_mode || 'unknown'}`);
|
|
82
|
+
|
|
83
|
+
const prompt = payload.prompt || "";
|
|
84
|
+
debugLogSection("ORIGINAL PROMPT", prompt);
|
|
85
|
+
|
|
86
|
+
// Skip enhancement for very short prompts or commands
|
|
87
|
+
if (prompt.length < 10) {
|
|
88
|
+
debugLog("SKIP: Prompt too short (< 10 chars)");
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
if (prompt.startsWith("/")) {
|
|
92
|
+
debugLog("SKIP: Prompt is a slash command");
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check if enhancement should proceed (auto-confirms on all platforms)
|
|
97
|
+
debugLog("Checking if enhancement should proceed...");
|
|
98
|
+
const shouldProceed = shouldEnhance(prompt);
|
|
99
|
+
debugLog(`Enhancement decision: ${shouldProceed ? 'proceeding' : 'skipped'}`);
|
|
100
|
+
|
|
101
|
+
if (!shouldProceed) {
|
|
102
|
+
debugLog("SKIP: Enhancement not proceeding");
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
// Call sgrep enhance with JSON output
|
|
108
|
+
debugLog("Calling: sgrep enhance --json \"<prompt>\"");
|
|
109
|
+
|
|
110
|
+
// Escape the prompt for shell
|
|
111
|
+
const escapedPrompt = prompt.replace(/"/g, '\\"').replace(/\n/g, ' ');
|
|
112
|
+
|
|
113
|
+
const result = execSync(`sgrep enhance --json "${escapedPrompt}"`, {
|
|
114
|
+
encoding: "utf8",
|
|
115
|
+
timeout: 60000, // 60 second timeout
|
|
116
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
debugLogSection("SGREP ENHANCE RAW RESULT", result);
|
|
120
|
+
|
|
121
|
+
// Parse the JSON result
|
|
122
|
+
const parsed = JSON.parse(result);
|
|
123
|
+
const enhanced = parsed.enhanced || null;
|
|
124
|
+
|
|
125
|
+
if (!enhanced) {
|
|
126
|
+
debugLog("SKIP: No enhanced prompt in result");
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (enhanced === prompt) {
|
|
131
|
+
debugLog("SKIP: Enhanced prompt same as original");
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
debugLogSection("ENHANCED PROMPT", enhanced);
|
|
136
|
+
|
|
137
|
+
// Output JSON with additionalContext - this adds context to Claude
|
|
138
|
+
const response = {
|
|
139
|
+
hookSpecificOutput: {
|
|
140
|
+
hookEventName: "UserPromptSubmit",
|
|
141
|
+
additionalContext: `[sgrep enhanced context]\n${enhanced}`
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
debugLogSection("HOOK RESPONSE", JSON.stringify(response, null, 2));
|
|
146
|
+
console.log(JSON.stringify(response));
|
|
147
|
+
debugLog("SUCCESS: Enhancement complete");
|
|
148
|
+
} catch (e) {
|
|
149
|
+
debugLog(`ERROR: Enhancement failed - ${e.message}`);
|
|
150
|
+
if (e.stderr) {
|
|
151
|
+
debugLog(`STDERR: ${e.stderr}`);
|
|
152
|
+
}
|
|
153
|
+
// On any error, just continue without enhancement
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
debugLog("====================================================\n");
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
main();
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { spawn, execSync } = require("child_process");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
|
|
8
|
+
const DEBUG_ENABLED = process.env.SGREP_DEBUG === "1" || process.env.SGREP_DEBUG === "true";
|
|
9
|
+
const DEBUG_LOG_FILE = process.env.SGREP_WATCH_LOG || path.join(os.tmpdir(), "sgrep-watch.log");
|
|
10
|
+
|
|
11
|
+
function debugLog(message) {
|
|
12
|
+
if (!DEBUG_ENABLED) return;
|
|
13
|
+
try {
|
|
14
|
+
const dir = path.dirname(DEBUG_LOG_FILE);
|
|
15
|
+
if (!fs.existsSync(dir)) {
|
|
16
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
const stamp = new Date().toISOString();
|
|
19
|
+
fs.appendFileSync(DEBUG_LOG_FILE, `[${stamp}] ${message}\n`);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
// Ignore errors
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function debugLogSection(title, content) {
|
|
26
|
+
debugLog(`\n========== ${title} ==========`);
|
|
27
|
+
debugLog(content);
|
|
28
|
+
debugLog(`========== END ${title} ==========\n`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readHookInput() {
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
let data = "";
|
|
34
|
+
process.stdin.setEncoding("utf8");
|
|
35
|
+
process.stdin.on("data", (chunk) => {
|
|
36
|
+
data += chunk;
|
|
37
|
+
});
|
|
38
|
+
process.stdin.on("end", () => {
|
|
39
|
+
if (!data.trim()) {
|
|
40
|
+
debugLog("No input data received from stdin");
|
|
41
|
+
resolve(null);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(data);
|
|
46
|
+
debugLogSection("RAW HOOK INPUT", JSON.stringify(parsed, null, 2));
|
|
47
|
+
resolve(parsed);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
debugLog(`Failed to decode JSON: ${e.message}`);
|
|
50
|
+
debugLog(`Raw data: ${data}`);
|
|
51
|
+
resolve(null);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function main() {
|
|
58
|
+
debugLog("\n\n====================================================");
|
|
59
|
+
debugLog("SGREP WATCH HOOK TRIGGERED - SessionStart");
|
|
60
|
+
debugLog("====================================================");
|
|
61
|
+
|
|
62
|
+
const payload = await readHookInput();
|
|
63
|
+
if (!payload) {
|
|
64
|
+
debugLog("ERROR: No payload received - exiting with error");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Log all payload fields for debugging
|
|
69
|
+
debugLog(`Hook Event: ${payload.hook_event_name || 'unknown'}`);
|
|
70
|
+
debugLog(`Session ID: ${payload.session_id || 'unknown'}`);
|
|
71
|
+
debugLog(`Source: ${payload.source || 'unknown'} (startup/resume/clear/compact)`);
|
|
72
|
+
debugLog(`CWD: ${payload.cwd || 'unknown'}`);
|
|
73
|
+
debugLog(`Permission Mode: ${payload.permission_mode || 'unknown'}`);
|
|
74
|
+
debugLog(`Transcript Path: ${payload.transcript_path || 'unknown'}`);
|
|
75
|
+
debugLog(`Model: ${payload.model || 'unknown'}`);
|
|
76
|
+
if (payload.agent_type) {
|
|
77
|
+
debugLog(`Agent Type: ${payload.agent_type}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const sessionId = payload.session_id || "default";
|
|
81
|
+
const pidFile = path.join(os.tmpdir(), `sgrep-watch-pid-${sessionId}.txt`);
|
|
82
|
+
const logFile = path.join(os.tmpdir(), `sgrep-watch-command-${sessionId}.log`);
|
|
83
|
+
|
|
84
|
+
debugLog(`PID file path: ${pidFile}`);
|
|
85
|
+
debugLog(`Log file path: ${logFile}`);
|
|
86
|
+
|
|
87
|
+
if (fs.existsSync(pidFile)) {
|
|
88
|
+
const existingPid = fs.readFileSync(pidFile, "utf8").trim();
|
|
89
|
+
debugLog(`SKIP: PID file already exists with PID: ${existingPid}`);
|
|
90
|
+
// Check if process is still running
|
|
91
|
+
if (existingPid && existingPid !== "0") {
|
|
92
|
+
try {
|
|
93
|
+
process.kill(parseInt(existingPid, 10), 0); // Signal 0 = check if process exists
|
|
94
|
+
debugLog(`Process ${existingPid} is still running`);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
debugLog(`Process ${existingPid} is not running, will restart`);
|
|
97
|
+
fs.unlinkSync(pidFile);
|
|
98
|
+
// Fall through to start new process
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (fs.existsSync(pidFile)) {
|
|
102
|
+
// Still exists, so process is running
|
|
103
|
+
const response = {
|
|
104
|
+
hookSpecificOutput: {
|
|
105
|
+
hookEventName: "SessionStart",
|
|
106
|
+
additionalContext: "The sgrep semantic search tool is available. Use `sgrep \"query\"` for semantic code search, `sgrep ask \"question\"` for explanations, and `sgrep enhance \"prompt\"` to improve vague prompts.",
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
console.log(JSON.stringify(response));
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Start sgrep watch as a detached process
|
|
115
|
+
debugLog("Starting sgrep watch process...");
|
|
116
|
+
|
|
117
|
+
// Check if sgrep is available
|
|
118
|
+
let sgrepPath = "sgrep";
|
|
119
|
+
const isWindows = process.platform === "win32";
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
// On Windows, we need to find sgrep.cmd specifically
|
|
123
|
+
const whichCmd = isWindows ? "where sgrep.cmd" : "which sgrep";
|
|
124
|
+
const whichResult = execSync(whichCmd, {
|
|
125
|
+
encoding: "utf8",
|
|
126
|
+
timeout: 5000,
|
|
127
|
+
}).trim().split(/\r?\n/)[0];
|
|
128
|
+
sgrepPath = whichResult;
|
|
129
|
+
debugLog(`Found sgrep at: ${sgrepPath}`);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
debugLog(`Could not find sgrep.cmd, trying 'where sgrep'...`);
|
|
132
|
+
// Fallback to finding sgrep without extension
|
|
133
|
+
try {
|
|
134
|
+
const whichResult = execSync(isWindows ? "where sgrep" : "which sgrep", {
|
|
135
|
+
encoding: "utf8",
|
|
136
|
+
timeout: 5000,
|
|
137
|
+
}).trim().split(/\r?\n/)[0];
|
|
138
|
+
|
|
139
|
+
// On Windows, if we got a path without extension, add .cmd
|
|
140
|
+
if (isWindows && !whichResult.toLowerCase().endsWith(".cmd") && !whichResult.toLowerCase().endsWith(".exe")) {
|
|
141
|
+
sgrepPath = whichResult + ".cmd";
|
|
142
|
+
debugLog(`Found sgrep at: ${whichResult}, using: ${sgrepPath}`);
|
|
143
|
+
} else {
|
|
144
|
+
sgrepPath = whichResult;
|
|
145
|
+
debugLog(`Found sgrep at: ${sgrepPath}`);
|
|
146
|
+
}
|
|
147
|
+
} catch (e2) {
|
|
148
|
+
debugLog(`WARNING: Could not find sgrep in PATH, using 'sgrep' directly`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const out = fs.openSync(logFile, "w");
|
|
154
|
+
const isWindows = process.platform === "win32";
|
|
155
|
+
|
|
156
|
+
// Use --pid-file option to have sgrep write its own PID
|
|
157
|
+
// This gives us the actual Node.js process PID, not the cmd.exe wrapper
|
|
158
|
+
const args = ["watch", "--pid-file", pidFile];
|
|
159
|
+
debugLog(`Spawning: ${sgrepPath} ${args.join(" ")}`);
|
|
160
|
+
|
|
161
|
+
let child;
|
|
162
|
+
if (isWindows) {
|
|
163
|
+
// On Windows, we need to handle .cmd/.bat scripts differently
|
|
164
|
+
const isCmdScript = sgrepPath.toLowerCase().endsWith(".cmd") ||
|
|
165
|
+
sgrepPath.toLowerCase().endsWith(".bat");
|
|
166
|
+
|
|
167
|
+
if (isCmdScript) {
|
|
168
|
+
debugLog(`sgrep is a batch script: ${sgrepPath}`);
|
|
169
|
+
child = spawn("cmd.exe", ["/c", sgrepPath, ...args], {
|
|
170
|
+
detached: true,
|
|
171
|
+
stdio: ["ignore", out, out],
|
|
172
|
+
windowsHide: true,
|
|
173
|
+
});
|
|
174
|
+
} else {
|
|
175
|
+
child = spawn(sgrepPath, args, {
|
|
176
|
+
detached: true,
|
|
177
|
+
stdio: ["ignore", out, out],
|
|
178
|
+
windowsHide: true,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
child = spawn(sgrepPath, args, {
|
|
183
|
+
detached: true,
|
|
184
|
+
stdio: ["ignore", out, out],
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
debugLog(`Spawn returned wrapper PID: ${child.pid}`);
|
|
189
|
+
|
|
190
|
+
// Handle any spawn errors
|
|
191
|
+
child.on("error", (err) => {
|
|
192
|
+
debugLog(`Spawn error event: ${err.message}`);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Unref so this script can exit
|
|
196
|
+
child.unref();
|
|
197
|
+
|
|
198
|
+
// Wait a moment for sgrep to write its PID file
|
|
199
|
+
debugLog("Waiting for sgrep to write its PID...");
|
|
200
|
+
let actualPid = null;
|
|
201
|
+
for (let i = 0; i < 20; i++) { // Wait up to 2 seconds
|
|
202
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
203
|
+
if (fs.existsSync(pidFile)) {
|
|
204
|
+
const content = fs.readFileSync(pidFile, "utf8").trim();
|
|
205
|
+
if (content && content !== "0") {
|
|
206
|
+
actualPid = content;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (actualPid) {
|
|
213
|
+
debugLog(`SUCCESS: sgrep watch started with PID: ${actualPid}`);
|
|
214
|
+
} else {
|
|
215
|
+
debugLog(`WARNING: sgrep watch may not have started properly - no PID file found`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Log the command output file location
|
|
219
|
+
debugLog(`sgrep watch output will be logged to: ${logFile}`);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
debugLog(`ERROR: Failed to start sgrep watch - ${e.message}`);
|
|
222
|
+
if (e.stack) {
|
|
223
|
+
debugLog(`Stack: ${e.stack}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const response = {
|
|
228
|
+
hookSpecificOutput: {
|
|
229
|
+
hookEventName: "SessionStart",
|
|
230
|
+
additionalContext: "The sgrep semantic search tool is available. Use `sgrep \"query\"` for semantic code search, `sgrep ask \"question\"` for explanations, and `sgrep enhance \"prompt\"` to improve vague prompts.",
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
debugLogSection("HOOK RESPONSE", JSON.stringify(response, null, 2));
|
|
235
|
+
console.log(JSON.stringify(response));
|
|
236
|
+
debugLog("====================================================\n");
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
main();
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const { execSync } = require("child_process");
|
|
7
|
+
|
|
8
|
+
const DEBUG_ENABLED = process.env.SGREP_DEBUG === "1" || process.env.SGREP_DEBUG === "true";
|
|
9
|
+
const DEBUG_LOG_FILE = process.env.SGREP_WATCH_KILL_LOG || path.join(os.tmpdir(), "sgrep-watch-kill.log");
|
|
10
|
+
|
|
11
|
+
function debugLog(message) {
|
|
12
|
+
if (!DEBUG_ENABLED) return;
|
|
13
|
+
try {
|
|
14
|
+
const dir = path.dirname(DEBUG_LOG_FILE);
|
|
15
|
+
if (!fs.existsSync(dir)) {
|
|
16
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
const stamp = new Date().toISOString();
|
|
19
|
+
fs.appendFileSync(DEBUG_LOG_FILE, `[${stamp}] ${message}\n`);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
// Ignore errors
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function debugLogSection(title, content) {
|
|
26
|
+
debugLog(`\n========== ${title} ==========`);
|
|
27
|
+
debugLog(content);
|
|
28
|
+
debugLog(`========== END ${title} ==========\n`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Kill a process by PID, with platform-specific handling
|
|
33
|
+
*/
|
|
34
|
+
function killProcess(pid) {
|
|
35
|
+
debugLog(`Attempting to kill process with PID: ${pid}`);
|
|
36
|
+
|
|
37
|
+
// First try Node.js process.kill
|
|
38
|
+
try {
|
|
39
|
+
process.kill(pid, "SIGTERM");
|
|
40
|
+
debugLog(`SUCCESS: Sent SIGTERM to process ${pid}`);
|
|
41
|
+
return true;
|
|
42
|
+
} catch (e) {
|
|
43
|
+
if (e.code === "ESRCH") {
|
|
44
|
+
debugLog(`INFO: Process ${pid} already exited`);
|
|
45
|
+
return true; // Consider success if already gone
|
|
46
|
+
}
|
|
47
|
+
debugLog(`process.kill failed: ${e.message} (code: ${e.code})`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// On Windows, try taskkill as fallback
|
|
51
|
+
if (process.platform === "win32") {
|
|
52
|
+
try {
|
|
53
|
+
execSync(`taskkill /PID ${pid} /F`, {
|
|
54
|
+
encoding: "utf8",
|
|
55
|
+
timeout: 5000,
|
|
56
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
57
|
+
});
|
|
58
|
+
debugLog(`SUCCESS: Killed process ${pid} using taskkill`);
|
|
59
|
+
return true;
|
|
60
|
+
} catch (e) {
|
|
61
|
+
debugLog(`taskkill failed: ${e.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function readHookInput() {
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
let data = "";
|
|
71
|
+
process.stdin.setEncoding("utf8");
|
|
72
|
+
process.stdin.on("data", (chunk) => {
|
|
73
|
+
data += chunk;
|
|
74
|
+
});
|
|
75
|
+
process.stdin.on("end", () => {
|
|
76
|
+
if (!data.trim()) {
|
|
77
|
+
debugLog("No input data received from stdin");
|
|
78
|
+
resolve(null);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const parsed = JSON.parse(data);
|
|
83
|
+
debugLogSection("RAW HOOK INPUT", JSON.stringify(parsed, null, 2));
|
|
84
|
+
resolve(parsed);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
debugLog(`Failed to decode JSON: ${e.message}`);
|
|
87
|
+
debugLog(`Raw data: ${data}`);
|
|
88
|
+
resolve(null);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function main() {
|
|
95
|
+
debugLog("\n\n====================================================");
|
|
96
|
+
debugLog("SGREP WATCH KILL HOOK TRIGGERED - SessionEnd");
|
|
97
|
+
debugLog("====================================================");
|
|
98
|
+
|
|
99
|
+
const payload = await readHookInput();
|
|
100
|
+
if (!payload) {
|
|
101
|
+
debugLog("ERROR: No payload received - exiting with error");
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Log all payload fields for debugging
|
|
106
|
+
debugLog(`Hook Event: ${payload.hook_event_name || 'unknown'}`);
|
|
107
|
+
debugLog(`Session ID: ${payload.session_id || 'unknown'}`);
|
|
108
|
+
debugLog(`Reason: ${payload.reason || 'unknown'} (clear/logout/prompt_input_exit/other)`);
|
|
109
|
+
debugLog(`CWD: ${payload.cwd || 'unknown'}`);
|
|
110
|
+
debugLog(`Permission Mode: ${payload.permission_mode || 'unknown'}`);
|
|
111
|
+
debugLog(`Transcript Path: ${payload.transcript_path || 'unknown'}`);
|
|
112
|
+
|
|
113
|
+
const sessionId = payload.session_id || "default";
|
|
114
|
+
const pidFile = path.join(os.tmpdir(), `sgrep-watch-pid-${sessionId}.txt`);
|
|
115
|
+
|
|
116
|
+
debugLog(`Looking for PID file: ${pidFile}`);
|
|
117
|
+
|
|
118
|
+
if (!fs.existsSync(pidFile)) {
|
|
119
|
+
debugLog(`SKIP: PID file not found - sgrep watch may not have been started`);
|
|
120
|
+
process.exit(0); // Not an error - process may not have been started
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let killed = false;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const pidContent = fs.readFileSync(pidFile, "utf8").trim();
|
|
127
|
+
debugLog(`PID file content: "${pidContent}"`);
|
|
128
|
+
|
|
129
|
+
const pid = parseInt(pidContent, 10);
|
|
130
|
+
debugLog(`Parsed PID: ${pid}`);
|
|
131
|
+
|
|
132
|
+
if (!isNaN(pid) && pid > 0) {
|
|
133
|
+
killed = killProcess(pid);
|
|
134
|
+
} else {
|
|
135
|
+
debugLog(`WARNING: Invalid PID (${pid}), cannot kill process`);
|
|
136
|
+
}
|
|
137
|
+
} catch (e) {
|
|
138
|
+
debugLog(`ERROR reading PID file: ${e.message}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Clean up PID file (sgrep watch should also clean it up on exit, but be safe)
|
|
142
|
+
try {
|
|
143
|
+
if (fs.existsSync(pidFile)) {
|
|
144
|
+
fs.unlinkSync(pidFile);
|
|
145
|
+
debugLog(`SUCCESS: Removed PID file: ${pidFile}`);
|
|
146
|
+
}
|
|
147
|
+
} catch (cleanupErr) {
|
|
148
|
+
debugLog(`Failed to clean up PID file: ${cleanupErr.message}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
debugLog(`Kill operation completed. Success: ${killed}`);
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
debugLog("====================================================\n");
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
main();
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sgrep
|
|
3
|
+
description: "Semantic grep - search codebases by meaning using AI. Use this skill when you need to search for code by functionality, understand codebase architecture, or enhance prompts with codebase context. Replaces pattern-based grep with natural language understanding."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
sgrep (semantic grep) is a powerful CLI tool that uses AI to search codebases by meaning rather than exact text patterns. It understands what code does, not just what it's named.
|
|
9
|
+
|
|
10
|
+
## CRITICAL: How to Run sgrep
|
|
11
|
+
|
|
12
|
+
**ALWAYS run sgrep from the project root directory. NEVER use `cd` before running sgrep.**
|
|
13
|
+
|
|
14
|
+
sgrep automatically searches the entire codebase from your current working directory. Using `cd` to navigate into subdirectories before running sgrep will limit your search scope and miss relevant code.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# CORRECT - run from project root
|
|
18
|
+
sgrep "authentication middleware"
|
|
19
|
+
sgrep ask "how does routing work?"
|
|
20
|
+
|
|
21
|
+
# WRONG - do NOT cd into subdirectories
|
|
22
|
+
cd src/frontend && sgrep "routing" # BAD: limits search scope
|
|
23
|
+
cd api && sgrep "authentication" # BAD: misses related code elsewhere
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## When to Use This Skill
|
|
27
|
+
|
|
28
|
+
Invoke this skill when:
|
|
29
|
+
|
|
30
|
+
- User asks to search for code by functionality (e.g., "find authentication code")
|
|
31
|
+
- User wants to understand how something works in the codebase
|
|
32
|
+
- User needs to find related files across the codebase
|
|
33
|
+
- User wants to enhance a vague prompt with codebase context
|
|
34
|
+
- You need to explore unfamiliar code before making changes
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
### Search - Find code by meaning
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
sgrep "authentication middleware"
|
|
42
|
+
sgrep "database connection handling"
|
|
43
|
+
sgrep "error handling patterns"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use natural language to describe what you're looking for. Returns file paths and relevant code snippets from the entire codebase.
|
|
47
|
+
|
|
48
|
+
### Ask - Get explanations about the codebase
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
sgrep ask "how does the authentication system work?"
|
|
52
|
+
sgrep ask "what patterns are used for API endpoints?"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Ask questions in natural language and get synthesized explanations with code references.
|
|
56
|
+
|
|
57
|
+
### Enhance - Improve prompts with codebase context
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
sgrep enhance "fix the bug"
|
|
61
|
+
sgrep enhance "add authentication"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Takes a vague prompt and enhances it using codebase context to make it clearer, more specific, and actionable.
|
|
65
|
+
|
|
66
|
+
## Examples
|
|
67
|
+
|
|
68
|
+
### Good Usage
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
sgrep "user login handling"
|
|
72
|
+
sgrep "HashRouter configuration"
|
|
73
|
+
sgrep "React Router setup"
|
|
74
|
+
sgrep ask "how does the caching layer work?"
|
|
75
|
+
sgrep ask "what is the routing architecture?"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Avoid
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd src && sgrep "query" # WRONG: don't cd first
|
|
82
|
+
sgrep "function" # Too vague, be more specific
|
|
83
|
+
sgrep "const" # Use regular grep for exact text matching
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Tips
|
|
87
|
+
|
|
88
|
+
- **Run from project root** - sgrep searches the entire codebase automatically
|
|
89
|
+
- **Never use cd** - it limits your search and misses related code
|
|
90
|
+
- Use descriptive natural language queries
|
|
91
|
+
- For exact text matching, use regular grep instead
|
|
92
|
+
- Use `sgrep ask` when you need explanations, not just code snippets
|
|
93
|
+
|
|
94
|
+
## Keywords
|
|
95
|
+
|
|
96
|
+
semantic search, code search, grep, files, local files, codebase, find code,
|
|
97
|
+
search by meaning, natural language search, code understanding, prompt enhancement
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface SgrepConfig {
|
|
2
|
+
defaultDir?: string;
|
|
3
|
+
maxResults?: number;
|
|
4
|
+
outputFormat?: "pretty" | "json";
|
|
5
|
+
noColor?: boolean;
|
|
6
|
+
debug?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Loads configuration with cascading priority:
|
|
10
|
+
* 1. CLI flags (handled by caller)
|
|
11
|
+
* 2. Environment variables
|
|
12
|
+
* 3. Local config file (.sgreprc.json)
|
|
13
|
+
* 4. Global config file (~/.config/sgrep/config.json)
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadConfig(workingDir?: string): SgrepConfig;
|
|
16
|
+
/**
|
|
17
|
+
* Merges CLI options with config, CLI options take precedence.
|
|
18
|
+
*/
|
|
19
|
+
export declare function mergeWithCliOptions(config: SgrepConfig, cliOptions: {
|
|
20
|
+
dir?: string;
|
|
21
|
+
maxResults?: number;
|
|
22
|
+
json?: boolean;
|
|
23
|
+
noColor?: boolean;
|
|
24
|
+
debug?: boolean;
|
|
25
|
+
}): SgrepConfig;
|
|
26
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA4CD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,UAAU,GAAE,MAAsB,GAAG,WAAW,CAuC1E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE;IACV,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GACA,WAAW,CAoBb"}
|