@hasna/terminal 3.3.4 → 3.3.6

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.
@@ -232,8 +232,8 @@ export function createServer() {
232
232
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
233
233
  });
234
234
  // ── search_semantic: AST-powered code search ───────────────────────────────
235
- server.tool("search_semantic", "Semantic code search — find functions, classes, components, hooks, types by meaning. Uses AST parsing, not string matching. Much more precise than grep for code navigation.", {
236
- query: z.string().describe("What to search for (e.g., 'auth functions', 'React components', 'database hooks')"),
235
+ server.tool("search_semantic", "Find functions, classes, components, hooks, types by NAME or SIGNATURE. Searches symbol declarations, NOT code behavior or content. Use search_content (grep) instead for pattern matching inside code (e.g., security audits, string searches, imports).", {
236
+ query: z.string().describe("Symbol name to search for (e.g., 'auth', 'login', 'UserService'). Matches function/class/type names, not code content."),
237
237
  path: z.string().optional().describe("Search root (default: cwd)"),
238
238
  kinds: z.array(z.enum(["function", "class", "interface", "type", "variable", "export", "import", "component", "hook"])).optional().describe("Filter by symbol kind"),
239
239
  exportedOnly: z.boolean().optional().describe("Only show exported symbols (default: false)"),
@@ -149,14 +149,40 @@ function extractSymbols(filePath) {
149
149
  });
150
150
  continue;
151
151
  }
152
- // Classes
152
+ // Classes — also extract methods inside the class body
153
153
  const classMatch = line.match(/(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?/);
154
154
  if (classMatch) {
155
+ const className = classMatch[1];
155
156
  symbols.push({
156
- name: classMatch[1], kind: "class", file, line: lineNum,
157
+ name: className, kind: "class", file, line: lineNum,
157
158
  signature: line.trim().replace(/\{.*$/, "").trim(),
158
159
  exported: isExported,
159
160
  });
161
+ // Walk class body to find methods
162
+ let braceDepth = 0;
163
+ for (let j = i; j < lines.length; j++) {
164
+ for (const ch of lines[j]) {
165
+ if (ch === "{")
166
+ braceDepth++;
167
+ if (ch === "}")
168
+ braceDepth--;
169
+ }
170
+ if (j > i) {
171
+ const memberLine = lines[j];
172
+ // Class methods: async methodName(...), methodName(...), get/set name(...)
173
+ const methodMatch = memberLine.match(/^\s+(?:async\s+)?(?:(?:public|private|protected|static|readonly|override|abstract)\s+)*(?:get\s+|set\s+)?(\w+)\s*[\(<]/);
174
+ const RESERVED = new Set(["if", "for", "while", "switch", "catch", "return", "throw", "new", "delete", "typeof", "void", "yield", "await", "try", "else"]);
175
+ if (methodMatch && !RESERVED.has(methodMatch[1])) {
176
+ symbols.push({
177
+ name: `${className}.${methodMatch[1]}`, kind: "function", file, line: j + 1,
178
+ signature: memberLine.trim().replace(/\{.*$/, "").trim(),
179
+ exported: isExported,
180
+ });
181
+ }
182
+ }
183
+ if (braceDepth === 0 && j > i)
184
+ break; // end of class
185
+ }
160
186
  continue;
161
187
  }
162
188
  // Interfaces
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/terminal",
3
- "version": "3.3.4",
3
+ "version": "3.3.6",
4
4
  "description": "Smart terminal wrapper for AI agents and humans — structured output, token compression, MCP server, natural language",
5
5
  "type": "module",
6
6
  "files": [
package/src/mcp/server.ts CHANGED
@@ -301,9 +301,9 @@ export function createServer(): McpServer {
301
301
 
302
302
  server.tool(
303
303
  "search_semantic",
304
- "Semantic code search — find functions, classes, components, hooks, types by meaning. Uses AST parsing, not string matching. Much more precise than grep for code navigation.",
304
+ "Find functions, classes, components, hooks, types by NAME or SIGNATURE. Searches symbol declarations, NOT code behavior or content. Use search_content (grep) instead for pattern matching inside code (e.g., security audits, string searches, imports).",
305
305
  {
306
- query: z.string().describe("What to search for (e.g., 'auth functions', 'React components', 'database hooks')"),
306
+ query: z.string().describe("Symbol name to search for (e.g., 'auth', 'login', 'UserService'). Matches function/class/type names, not code content."),
307
307
  path: z.string().optional().describe("Search root (default: cwd)"),
308
308
  kinds: z.array(z.enum(["function", "class", "interface", "type", "variable", "export", "import", "component", "hook"])).optional().describe("Filter by symbol kind"),
309
309
  exportedOnly: z.boolean().optional().describe("Only show exported symbols (default: false)"),
@@ -172,14 +172,37 @@ function extractSymbols(filePath: string): CodeSymbol[] {
172
172
  continue;
173
173
  }
174
174
 
175
- // Classes
175
+ // Classes — also extract methods inside the class body
176
176
  const classMatch = line.match(/(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?/);
177
177
  if (classMatch) {
178
+ const className = classMatch[1];
178
179
  symbols.push({
179
- name: classMatch[1], kind: "class", file, line: lineNum,
180
+ name: className, kind: "class", file, line: lineNum,
180
181
  signature: line.trim().replace(/\{.*$/, "").trim(),
181
182
  exported: isExported,
182
183
  });
184
+ // Walk class body to find methods
185
+ let braceDepth = 0;
186
+ for (let j = i; j < lines.length; j++) {
187
+ for (const ch of lines[j]) {
188
+ if (ch === "{") braceDepth++;
189
+ if (ch === "}") braceDepth--;
190
+ }
191
+ if (j > i) {
192
+ const memberLine = lines[j];
193
+ // Class methods: async methodName(...), methodName(...), get/set name(...)
194
+ const methodMatch = memberLine.match(/^\s+(?:async\s+)?(?:(?:public|private|protected|static|readonly|override|abstract)\s+)*(?:get\s+|set\s+)?(\w+)\s*[\(<]/);
195
+ const RESERVED = new Set(["if", "for", "while", "switch", "catch", "return", "throw", "new", "delete", "typeof", "void", "yield", "await", "try", "else"]);
196
+ if (methodMatch && !RESERVED.has(methodMatch[1])) {
197
+ symbols.push({
198
+ name: `${className}.${methodMatch[1]}`, kind: "function", file, line: j + 1,
199
+ signature: memberLine.trim().replace(/\{.*$/, "").trim(),
200
+ exported: isExported,
201
+ });
202
+ }
203
+ }
204
+ if (braceDepth === 0 && j > i) break; // end of class
205
+ }
183
206
  continue;
184
207
  }
185
208