@takara-ai/miru-code 0.4.9 → 0.4.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takara-ai/miru-code",
3
- "version": "0.4.9",
3
+ "version": "0.4.10",
4
4
  "description": "Miru (見る) — hybrid code search for agents. Bun, TypeScript, Takara embeddings.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -55,8 +55,8 @@ ${SEARCH_POLICY_BODY}
55
55
  ### MCP workflow
56
56
 
57
57
  1. Call \`search\` with \`repo\` set to the project root (local path or https:// git URL).
58
- 2. Use returned \`file_path\` and \`line\` from hits; call \`find_related\` to explore neighbors.
59
- 3. Read files only when a chunk lacks context or you are about to edit.
58
+ 2. Use \`absolute_path\` (local repos) or \`file_path\` plus line from hits; call \`find_related\` to explore neighbors.
59
+ 3. Read files via \`absolute_path\` when a chunk lacks context or you are about to edit.
60
60
 
61
61
  ### CLI fallback (no MCP in this session)
62
62
 
package/src/mcp/server.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import * as z from "zod";
3
3
  import type { ContentType } from "../types.ts";
4
- import { formatResults, resolveChunk } from "../utils.ts";
4
+ import { formatResults, localRepoRoot, resolveChunk } from "../utils.ts";
5
5
  import { getIndexForRepo, type IndexCache, toolText } from "./index-cache.ts";
6
6
 
7
7
  const REPO_DESCRIPTION =
@@ -13,6 +13,7 @@ const SERVER_INSTRUCTIONS =
13
13
  "DO NOT use Grep, Glob, SemanticSearch, or Read to explore code when this server is available. " +
14
14
  "Call `search` first for any question about how code works; call `find_related` on a hit to explore neighbors. " +
15
15
  "Always pass the project root as `repo` (local path or https:// git URL). Never guess URLs. " +
16
+ "Local repos return `absolute_path` on each hit — use it for Read/edit. " +
16
17
  "Native search is only for exact literal confirmation or files outside the indexed repo.";
17
18
 
18
19
  export function createMcpServer(cache: IndexCache): McpServer {
@@ -45,7 +46,8 @@ export function createMcpServer(cache: IndexCache): McpServer {
45
46
  if (results.length === 0) {
46
47
  return toolText(JSON.stringify({ error: "No results found." }));
47
48
  }
48
- return toolText(JSON.stringify(formatResults(query, results)));
49
+ const repoRoot = localRepoRoot(repo);
50
+ return toolText(JSON.stringify(formatResults(query, results, { repoRoot })));
49
51
  } catch (err) {
50
52
  return toolText(err instanceof Error ? err.message : String(err));
51
53
  }
@@ -62,7 +64,7 @@ export function createMcpServer(cache: IndexCache): McpServer {
62
64
  file_path: z
63
65
  .string()
64
66
  .describe(
65
- "Path to the file as stored in the index (use file_path from a search result).",
67
+ "Path from a search hit (`file_path` or `absolute_path` for local repos).",
66
68
  ),
67
69
  line: z.number().int().describe("Line number (1-indexed)."),
68
70
  repo: z.string().describe(REPO_DESCRIPTION),
@@ -72,7 +74,8 @@ export function createMcpServer(cache: IndexCache): McpServer {
72
74
  async ({ file_path: filePath, line, repo, top_k: topK }) => {
73
75
  try {
74
76
  const index = await getIndexForRepo(repo, cache);
75
- const chunk = resolveChunk(index.chunks, filePath, line);
77
+ const repoRoot = localRepoRoot(repo);
78
+ const chunk = resolveChunk(index.chunks, filePath, line, repoRoot);
76
79
  if (!chunk) {
77
80
  return toolText(
78
81
  `No chunk found at ${filePath}:${line}. Make sure the file is indexed and the line number is within a known chunk.`,
@@ -85,7 +88,9 @@ export function createMcpServer(cache: IndexCache): McpServer {
85
88
  );
86
89
  }
87
90
  return toolText(
88
- JSON.stringify(formatResults(`Chunks related to ${filePath}:${line}`, results)),
91
+ JSON.stringify(
92
+ formatResults(`Chunks related to ${filePath}:${line}`, results, { repoRoot }),
93
+ ),
89
94
  );
90
95
  } catch (err) {
91
96
  return toolText(err instanceof Error ? err.message : String(err));
package/src/utils.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { resolve } from "node:path";
1
+ import { join, relative, resolve, sep } from "node:path";
2
2
  import type { Chunk, ContentType, SearchResult } from "./types.ts";
3
- import { searchResultToDict } from "./types.ts";
3
+ import { chunkToDict } from "./types.ts";
4
4
 
5
5
  const GIT_URL_SCHEMES = [
6
6
  "https://",
@@ -23,10 +23,38 @@ export function isAllowedRepoSource(repo: string): boolean {
23
23
  return repo.startsWith("https://") || repo.startsWith("http://");
24
24
  }
25
25
 
26
- export function resolveChunk(chunks: Chunk[], filePath: string, line: number): Chunk | null {
26
+ /** Resolved local repo root for MCP output; null for remote git URLs. */
27
+ export function localRepoRoot(repo: string): string | null {
28
+ return isGitUrl(repo) ? null : resolve(repo);
29
+ }
30
+
31
+ /** Map an absolute or repo-relative path to the index-relative form. */
32
+ export function toIndexedFilePath(filePath: string, repoRoot?: string | null): string {
33
+ if (!repoRoot) {
34
+ return filePath;
35
+ }
36
+ const root = resolve(repoRoot);
37
+ const candidate = resolve(root, filePath);
38
+ if (candidate === root) {
39
+ return "";
40
+ }
41
+ const prefix = root.endsWith(sep) ? root : `${root}${sep}`;
42
+ if (candidate.startsWith(prefix)) {
43
+ return relative(root, candidate).replace(/\\/g, "/");
44
+ }
45
+ return filePath;
46
+ }
47
+
48
+ export function resolveChunk(
49
+ chunks: Chunk[],
50
+ filePath: string,
51
+ line: number,
52
+ repoRoot?: string | null,
53
+ ): Chunk | null {
54
+ const indexedPath = toIndexedFilePath(filePath, repoRoot);
27
55
  let fallback: Chunk | null = null;
28
56
  for (const chunk of chunks) {
29
- if (chunk.file_path === filePath && chunk.start_line <= line && line <= chunk.end_line) {
57
+ if (chunk.file_path === indexedPath && chunk.start_line <= line && line <= chunk.end_line) {
30
58
  if (line < chunk.end_line) {
31
59
  return chunk;
32
60
  }
@@ -38,13 +66,31 @@ export function resolveChunk(chunks: Chunk[], filePath: string, line: number): C
38
66
  return fallback;
39
67
  }
40
68
 
69
+ function chunkToResponseDict(chunk: Chunk, repoRoot?: string | null): Record<string, unknown> {
70
+ const dict = chunkToDict(chunk);
71
+ if (!repoRoot) {
72
+ return dict;
73
+ }
74
+ const absolutePath = join(resolve(repoRoot), chunk.file_path);
75
+ return {
76
+ ...dict,
77
+ absolute_path: absolutePath,
78
+ location: `${absolutePath}:${chunk.start_line}-${chunk.end_line}`,
79
+ };
80
+ }
81
+
41
82
  export function formatResults(
42
83
  query: string,
43
84
  results: SearchResult[],
85
+ options?: { repoRoot?: string | null },
44
86
  ): { query: string; results: Record<string, unknown>[] } {
87
+ const repoRoot = options?.repoRoot ?? null;
45
88
  return {
46
89
  query,
47
- results: results.map(searchResultToDict),
90
+ results: results.map((result) => ({
91
+ chunk: chunkToResponseDict(result.chunk, repoRoot),
92
+ score: result.score,
93
+ })),
48
94
  };
49
95
  }
50
96