vexp-mcp 1.2.14

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.
Files changed (57) hide show
  1. package/dist/daemon-client.d.ts +19 -0
  2. package/dist/daemon-client.d.ts.map +1 -0
  3. package/dist/daemon-client.js +111 -0
  4. package/dist/daemon-client.js.map +1 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +238 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/tools/get-context-capsule.d.ts +65 -0
  10. package/dist/tools/get-context-capsule.d.ts.map +1 -0
  11. package/dist/tools/get-context-capsule.js +101 -0
  12. package/dist/tools/get-context-capsule.js.map +1 -0
  13. package/dist/tools/get-impact-graph.d.ts +48 -0
  14. package/dist/tools/get-impact-graph.d.ts.map +1 -0
  15. package/dist/tools/get-impact-graph.js +95 -0
  16. package/dist/tools/get-impact-graph.js.map +1 -0
  17. package/dist/tools/get-session-context.d.ts +44 -0
  18. package/dist/tools/get-session-context.d.ts.map +1 -0
  19. package/dist/tools/get-session-context.js +51 -0
  20. package/dist/tools/get-session-context.js.map +1 -0
  21. package/dist/tools/get-skeleton.d.ts +44 -0
  22. package/dist/tools/get-skeleton.d.ts.map +1 -0
  23. package/dist/tools/get-skeleton.js +79 -0
  24. package/dist/tools/get-skeleton.js.map +1 -0
  25. package/dist/tools/index-status.d.ts +15 -0
  26. package/dist/tools/index-status.d.ts.map +1 -0
  27. package/dist/tools/index-status.js +99 -0
  28. package/dist/tools/index-status.js.map +1 -0
  29. package/dist/tools/save-observation.d.ts +44 -0
  30. package/dist/tools/save-observation.d.ts.map +1 -0
  31. package/dist/tools/save-observation.js +46 -0
  32. package/dist/tools/save-observation.js.map +1 -0
  33. package/dist/tools/search-logic-flow.d.ts +47 -0
  34. package/dist/tools/search-logic-flow.d.ts.map +1 -0
  35. package/dist/tools/search-logic-flow.js +90 -0
  36. package/dist/tools/search-logic-flow.js.map +1 -0
  37. package/dist/tools/search-memory.d.ts +54 -0
  38. package/dist/tools/search-memory.d.ts.map +1 -0
  39. package/dist/tools/search-memory.js +58 -0
  40. package/dist/tools/search-memory.js.map +1 -0
  41. package/dist/tools/workspace-setup.d.ts +33 -0
  42. package/dist/tools/workspace-setup.d.ts.map +1 -0
  43. package/dist/tools/workspace-setup.js +115 -0
  44. package/dist/tools/workspace-setup.js.map +1 -0
  45. package/package.json +32 -0
  46. package/src/daemon-client.ts +134 -0
  47. package/src/index.ts +299 -0
  48. package/src/tools/get-context-capsule.ts +154 -0
  49. package/src/tools/get-impact-graph.ts +134 -0
  50. package/src/tools/get-session-context.ts +79 -0
  51. package/src/tools/get-skeleton.ts +103 -0
  52. package/src/tools/index-status.ts +141 -0
  53. package/src/tools/save-observation.ts +63 -0
  54. package/src/tools/search-logic-flow.ts +147 -0
  55. package/src/tools/search-memory.ts +85 -0
  56. package/src/tools/workspace-setup.ts +155 -0
  57. package/tsconfig.json +20 -0
@@ -0,0 +1,90 @@
1
+ import { z } from "zod";
2
+ export const SearchLogicFlowSchema = z.object({
3
+ start: z.string().describe("FQN of the starting symbol"),
4
+ end: z.string().describe("FQN of the ending symbol"),
5
+ max_paths: z.number().optional().default(3).describe("Max number of paths to return"),
6
+ cross_repo: z.boolean().optional().default(false).describe("Allow cross-repo traversal"),
7
+ });
8
+ export const SEARCH_LOGIC_FLOW_DEFINITION = {
9
+ name: "search_logic_flow",
10
+ description: "Find execution paths between two symbols — shows how data or control flows from A to B through the call graph. " +
11
+ "WHEN TO USE: (1) When asked 'how does X reach Y?' or 'what happens between A and B?'. " +
12
+ "(2) To trace an indirect call chain (e.g. HTTP handler → DB query). " +
13
+ "(3) To understand the execution path for a bug report or feature trace. " +
14
+ "Both start and end must be exact FQNs from get_context_capsule or get_impact_graph results.",
15
+ inputSchema: {
16
+ type: "object",
17
+ properties: {
18
+ start: { type: "string", description: "FQN of the start symbol" },
19
+ end: { type: "string", description: "FQN of the end symbol" },
20
+ max_paths: { type: "number", description: "Max paths to return (default: 3)" },
21
+ cross_repo: { type: "boolean", description: "Allow tracing across repo boundaries (e.g. frontend→API→backend). Reports linking edges when start/end are in different repos" },
22
+ },
23
+ required: ["start", "end"],
24
+ },
25
+ };
26
+ export async function handleSearchLogicFlow(params, daemon) {
27
+ const validated = SearchLogicFlowSchema.parse(params);
28
+ const result = await daemon.call("search_logic_flow", validated);
29
+ // Handle cross-repo gap response
30
+ if (typeof result === 'object' && result !== null && 'cross_repo_gap' in result) {
31
+ return formatCrossRepoGap(result);
32
+ }
33
+ return formatFlowAsText(validated.start, validated.end, result);
34
+ }
35
+ function formatCrossRepoGap(gap) {
36
+ const lines = [];
37
+ lines.push(`# Cross-Repo Logic Flow`);
38
+ lines.push(`> \`${gap.start.fqn}\` (${gap.start.repo}) → \`${gap.end.fqn}\` (${gap.end.repo})`);
39
+ lines.push("");
40
+ lines.push(gap.message);
41
+ lines.push("");
42
+ if (gap.linking_edges.length > 0) {
43
+ lines.push("## Linking Edges");
44
+ lines.push("");
45
+ for (const edge of gap.linking_edges) {
46
+ lines.push(`- **${edge.edge_type}**: \`${edge.source_fqn}\` (${edge.source_repo}) → \`${edge.target_fqn}\` (${edge.target_repo}) — confidence: ${(edge.confidence * 100).toFixed(0)}%`);
47
+ }
48
+ lines.push("");
49
+ }
50
+ if (gap.start_neighborhood) {
51
+ lines.push("## Start Symbol Neighborhood");
52
+ lines.push("");
53
+ lines.push(gap.start_neighborhood);
54
+ }
55
+ return lines.join("\n");
56
+ }
57
+ function formatFlowAsText(start, end, paths) {
58
+ const lines = [];
59
+ lines.push(`# Logic Flow: \`${start}\` → \`${end}\``);
60
+ lines.push(`> ${paths.length} path(s) found`);
61
+ lines.push("");
62
+ if (!Array.isArray(paths) || paths.length === 0) {
63
+ lines.push("*No execution path found between these symbols. " +
64
+ "They may not be connected, or the path may exceed the search depth.*");
65
+ return lines.join("\n");
66
+ }
67
+ paths.forEach((path, idx) => {
68
+ lines.push(`## Path ${idx + 1} — ${path.nodes.length} steps (cost: ${path.total_cost.toFixed(1)})`);
69
+ lines.push("");
70
+ path.nodes.forEach((node, nodeIdx) => {
71
+ const isLast = nodeIdx === path.nodes.length - 1;
72
+ const edge = path.edges_description[nodeIdx] ?? "";
73
+ if (nodeIdx === 0) {
74
+ lines.push(`1. **START** \`${node.fqn}\``);
75
+ lines.push(` *${node.file_path}:${node.start_line}*`);
76
+ }
77
+ else {
78
+ const edgeLabel = edge ? ` via \`${edge}\`` : "";
79
+ lines.push(`${nodeIdx + 1}.${edgeLabel} → \`${node.fqn}\``);
80
+ lines.push(` *${node.file_path}:${node.start_line}*`);
81
+ }
82
+ if (!isLast) {
83
+ lines.push(" ↓");
84
+ }
85
+ });
86
+ lines.push("");
87
+ });
88
+ return lines.join("\n");
89
+ }
90
+ //# sourceMappingURL=search-logic-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-logic-flow.js","sourceRoot":"","sources":["../../src/tools/search-logic-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ import type { DaemonClient } from "../daemon-client.js";
3
+ export declare const SearchMemorySchema: z.ZodObject<{
4
+ query: z.ZodString;
5
+ max_results: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
6
+ include_stale: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
7
+ time_range_days: z.ZodOptional<z.ZodNumber>;
8
+ session_filter: z.ZodOptional<z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ query: string;
11
+ max_results: number;
12
+ include_stale: boolean;
13
+ time_range_days?: number | undefined;
14
+ session_filter?: string | undefined;
15
+ }, {
16
+ query: string;
17
+ max_results?: number | undefined;
18
+ include_stale?: boolean | undefined;
19
+ time_range_days?: number | undefined;
20
+ session_filter?: string | undefined;
21
+ }>;
22
+ export type SearchMemoryParams = z.infer<typeof SearchMemorySchema>;
23
+ export declare const SEARCH_MEMORY_DEFINITION: {
24
+ name: string;
25
+ description: string;
26
+ inputSchema: {
27
+ type: "object";
28
+ properties: {
29
+ query: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ max_results: {
34
+ type: string;
35
+ description: string;
36
+ };
37
+ include_stale: {
38
+ type: string;
39
+ description: string;
40
+ };
41
+ time_range_days: {
42
+ type: string;
43
+ description: string;
44
+ };
45
+ session_filter: {
46
+ type: string;
47
+ description: string;
48
+ };
49
+ };
50
+ required: string[];
51
+ };
52
+ };
53
+ export declare function handleSearchMemory(params: unknown, daemon: DaemonClient): Promise<string>;
54
+ //# sourceMappingURL=search-memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-memory.d.ts","sourceRoot":"","sources":["../../src/tools/search-memory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;EAM7B,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmBpC,CAAC;AAEF,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,MAAM,CAAC,CAIjB"}
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ export const SearchMemorySchema = z.object({
3
+ query: z.string().describe("Search query for memories"),
4
+ max_results: z.number().optional().default(10).describe("Max results"),
5
+ include_stale: z.boolean().optional().default(true).describe("Include stale observations (demoted but shown)"),
6
+ time_range_days: z.number().optional().describe("Only search last N days (default: 30)"),
7
+ session_filter: z.string().optional().describe("Filter to specific session ID"),
8
+ });
9
+ export const SEARCH_MEMORY_DEFINITION = {
10
+ name: "search_memory",
11
+ description: "Search across all session memories using keywords and semantic similarity. " +
12
+ "Finds relevant observations from any session, ranked by relevance, recency, " +
13
+ "and code-graph proximity. Stale observations (linked to changed code) are " +
14
+ "demoted but still included. Each result explains WHY it surfaced (transparency). " +
15
+ "Use this to find past decisions, explored code paths, or previously encountered patterns.",
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: {
19
+ query: { type: "string", description: "Search query for memories" },
20
+ max_results: { type: "number", description: "Max results (default: 10)" },
21
+ include_stale: { type: "boolean", description: "Include stale observations (default: true, but demoted)" },
22
+ time_range_days: { type: "number", description: "Only search last N days (default: 30)" },
23
+ session_filter: { type: "string", description: "Filter to specific session ID" },
24
+ },
25
+ required: ["query"],
26
+ },
27
+ };
28
+ export async function handleSearchMemory(params, daemon) {
29
+ const validated = SearchMemorySchema.parse(params);
30
+ const results = await daemon.call("search_memory", validated);
31
+ return formatSearchResults(results, validated.max_results ?? 10);
32
+ }
33
+ function formatSearchResults(results, maxResults) {
34
+ const lines = [];
35
+ lines.push("# Memory Search Results");
36
+ lines.push(`> ${results.length} results found`);
37
+ lines.push("");
38
+ if (results.length === 0) {
39
+ lines.push("*No matching memories found.*");
40
+ return lines.join("\n");
41
+ }
42
+ // Use detailed format (Layer 3) when few results
43
+ const detailed = maxResults <= 5;
44
+ for (const mem of results) {
45
+ const date = new Date(mem.created_at * 1000);
46
+ const timeStr = `${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
47
+ const staleTag = mem.stale ? " ~stale~" : "";
48
+ lines.push(`- [${timeStr}${staleTag}] (${mem.obs_type}) ${mem.content}`);
49
+ if (detailed) {
50
+ lines.push(` Score: ${mem.score.toFixed(2)} | Session: ${mem.session_id.slice(0, 8)} | Why: ${mem.why}`);
51
+ }
52
+ else {
53
+ lines.push(` Why: ${mem.why}`);
54
+ }
55
+ }
56
+ return lines.join("\n");
57
+ }
58
+ //# sourceMappingURL=search-memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-memory.js","sourceRoot":"","sources":["../../src/tools/search-memory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;IACvD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC;IACtE,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,gDAAgD,CAAC;IAC9G,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IACxF,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;CAChF,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,IAAI,EAAE,eAAe;IACrB,WAAW,EACT,6EAA6E;QAC7E,8EAA8E;QAC9E,4EAA4E;QAC5E,mFAAmF;QACnF,2FAA2F;IAC7F,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,2BAA2B,EAAE;YACnE,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,2BAA2B,EAAE;YACzE,aAAa,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,yDAAyD,EAAE;YAC1G,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,uCAAuC,EAAE;YACzF,cAAc,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+BAA+B,EAAE;SACjF;QACD,QAAQ,EAAE,CAAC,OAAO,CAAC;KACpB;CACF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAe,EACf,MAAoB;IAEpB,MAAM,SAAS,GAAG,kBAAkB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,SAAS,CAAyB,CAAC;IACtF,OAAO,mBAAmB,CAAC,OAAO,EAAE,SAAS,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;AACnE,CAAC;AAaD,SAAS,mBAAmB,CAAC,OAA6B,EAAE,UAAkB;IAC5E,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACtC,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,MAAM,gBAAgB,CAAC,CAAC;IAChD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC5C,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,iDAAiD;IACjD,MAAM,QAAQ,GAAG,UAAU,IAAI,CAAC,CAAC;IAEjC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QACvM,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QAE7C,KAAK,CAAC,IAAI,CAAC,MAAM,OAAO,GAAG,QAAQ,MAAM,GAAG,CAAC,QAAQ,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAEzE,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QAC5G,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import type { DaemonClient } from "../daemon-client.js";
3
+ export declare const WorkspaceSetupSchema: z.ZodObject<{
4
+ workspace_root: z.ZodOptional<z.ZodString>;
5
+ detect_agents: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ detect_agents: boolean;
8
+ workspace_root?: string | undefined;
9
+ }, {
10
+ workspace_root?: string | undefined;
11
+ detect_agents?: boolean | undefined;
12
+ }>;
13
+ export type WorkspaceSetupParams = z.infer<typeof WorkspaceSetupSchema>;
14
+ export declare const WORKSPACE_SETUP_DEFINITION: {
15
+ name: string;
16
+ description: string;
17
+ inputSchema: {
18
+ type: "object";
19
+ properties: {
20
+ workspace_root: {
21
+ type: string;
22
+ description: string;
23
+ };
24
+ detect_agents: {
25
+ type: string;
26
+ description: string;
27
+ };
28
+ };
29
+ required: never[];
30
+ };
31
+ };
32
+ export declare function handleWorkspaceSetup(params: unknown, daemon: DaemonClient): Promise<string>;
33
+ //# sourceMappingURL=workspace-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-setup.d.ts","sourceRoot":"","sources":["../../src/tools/workspace-setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,eAAO,MAAM,oBAAoB;;;;;;;;;EAG/B,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;CAoBtC,CAAC;AAsBF,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,MAAM,CAAC,CAIjB"}
@@ -0,0 +1,115 @@
1
+ import { z } from "zod";
2
+ export const WorkspaceSetupSchema = z.object({
3
+ workspace_root: z.string().optional().describe("Workspace root path (default: CWD)"),
4
+ detect_agents: z.boolean().optional().default(true).describe("Auto-detect AI agents"),
5
+ });
6
+ export const WORKSPACE_SETUP_DEFINITION = {
7
+ name: "workspace_setup",
8
+ description: "Set up vexp for the current workspace. Generates configuration files, " +
9
+ "detects AI coding agents (Claude Code, Cursor, Windsurf, Continue), " +
10
+ "and provides setup instructions. Run this once when starting to work on a new project.",
11
+ inputSchema: {
12
+ type: "object",
13
+ properties: {
14
+ workspace_root: {
15
+ type: "string",
16
+ description: "Workspace root path (default: current directory)",
17
+ },
18
+ detect_agents: {
19
+ type: "boolean",
20
+ description: "Auto-detect installed AI agents (default: true)",
21
+ },
22
+ },
23
+ required: [],
24
+ },
25
+ };
26
+ export async function handleWorkspaceSetup(params, daemon) {
27
+ const validated = WorkspaceSetupSchema.parse(params);
28
+ const result = await daemon.call("workspace_setup", validated);
29
+ return formatSetupAsText(result);
30
+ }
31
+ function formatSetupAsText(setup) {
32
+ const lines = [];
33
+ lines.push(`# vexp Workspace Setup`);
34
+ lines.push(`> v${setup.vexp_version} | \`${setup.workspace_root}\``);
35
+ lines.push("");
36
+ // Generated files
37
+ if (setup.generated_files.length > 0) {
38
+ lines.push("## Generated Files");
39
+ lines.push("");
40
+ for (const f of setup.generated_files) {
41
+ lines.push(`- \`${f}\``);
42
+ }
43
+ lines.push("");
44
+ }
45
+ // workspace.json preview
46
+ if (setup.workspace_json) {
47
+ lines.push("## workspace.json");
48
+ lines.push("");
49
+ lines.push("```json");
50
+ lines.push(setup.workspace_json);
51
+ lines.push("```");
52
+ lines.push("");
53
+ }
54
+ // Detected agents
55
+ if (setup.detected_agents.length > 0) {
56
+ lines.push("## Detected AI Agents");
57
+ lines.push("");
58
+ for (const agent of setup.detected_agents) {
59
+ lines.push(`- **${agent}**`);
60
+ }
61
+ lines.push("");
62
+ }
63
+ // Agent configs
64
+ if (setup.agent_configs.length > 0) {
65
+ lines.push("## Agent Configuration Files");
66
+ lines.push("");
67
+ for (const cfg of setup.agent_configs) {
68
+ const existsLabel = cfg.already_exists ? " *(already exists — merge manually)*" : " *(created)*";
69
+ lines.push(`### ${cfg.agent} — \`${cfg.config_file}\`${existsLabel}`);
70
+ lines.push("");
71
+ lines.push("```");
72
+ lines.push(cfg.content);
73
+ lines.push("```");
74
+ lines.push("");
75
+ }
76
+ }
77
+ // Git hooks
78
+ if (setup.git_hooks_installed) {
79
+ lines.push("## Git Hooks");
80
+ lines.push("");
81
+ lines.push("✓ Git hooks installed: `pre-commit`, `post-merge`, `post-checkout`");
82
+ lines.push("✓ Merge driver configured for `index.db`");
83
+ lines.push("");
84
+ }
85
+ // .gitignore additions
86
+ if (setup.suggested_gitignore_additions.length > 0) {
87
+ lines.push("## Suggested .gitignore Additions");
88
+ lines.push("");
89
+ lines.push("Add these to your `.gitignore`:");
90
+ lines.push("```gitignore");
91
+ for (const line of setup.suggested_gitignore_additions) {
92
+ lines.push(line);
93
+ }
94
+ lines.push("```");
95
+ lines.push("");
96
+ }
97
+ // Index status
98
+ if (setup.index_started) {
99
+ lines.push("## Indexing");
100
+ lines.push("");
101
+ lines.push("✓ Initial indexing started in background. Use `index_status` to check progress.");
102
+ lines.push("");
103
+ }
104
+ // Setup instructions
105
+ if (setup.setup_instructions.length > 0) {
106
+ lines.push("## Next Steps");
107
+ lines.push("");
108
+ for (let i = 0; i < setup.setup_instructions.length; i++) {
109
+ lines.push(`${i + 1}. ${setup.setup_instructions[i]}`);
110
+ }
111
+ lines.push("");
112
+ }
113
+ return lines.join("\n");
114
+ }
115
+ //# sourceMappingURL=workspace-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-setup.js","sourceRoot":"","sources":["../../src/tools/workspace-setup.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "vexp-mcp",
3
+ "version": "1.2.14",
4
+ "description": "vexp MCP server — AI context tools for coding agents",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsx src/index.ts",
10
+ "test": "node --experimental-vm-modules node_modules/.bin/jest",
11
+ "clean": "rm -rf dist"
12
+ },
13
+ "dependencies": {
14
+ "@modelcontextprotocol/sdk": "^1.0.4",
15
+ "express": "^4.18.3",
16
+ "winston": "^3.11.0",
17
+ "better-sqlite3": "^9.4.3",
18
+ "uuid": "^9.0.0",
19
+ "zod": "^3.22.4"
20
+ },
21
+ "devDependencies": {
22
+ "@types/express": "^4.17.21",
23
+ "@types/better-sqlite3": "^7.6.8",
24
+ "@types/node": "^20.11.0",
25
+ "@types/uuid": "^9.0.7",
26
+ "typescript": "^5.4.0",
27
+ "tsx": "^4.7.0",
28
+ "jest": "^29.7.0",
29
+ "@jest/globals": "^29.7.0",
30
+ "ts-jest": "^29.1.2"
31
+ }
32
+ }
@@ -0,0 +1,134 @@
1
+ import * as net from "net";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ import { randomUUID } from "crypto";
5
+
6
+ interface SocketRequest {
7
+ id: number;
8
+ tool: string;
9
+ params: unknown;
10
+ session_id?: string;
11
+ }
12
+
13
+ interface SocketResponse {
14
+ type: "response" | "error";
15
+ result?: unknown;
16
+ message?: string;
17
+ }
18
+
19
+ /**
20
+ * Client per comunicare con il vexp-core daemon via Unix socket / Named pipe.
21
+ * Protocol: JSON lines (newline-delimited JSON).
22
+ */
23
+ export class DaemonClient {
24
+ private socketPath: string;
25
+ private requestCounter = 0;
26
+ private sessionId: string;
27
+
28
+ constructor(socketPath?: string, sessionId?: string) {
29
+ this.socketPath = socketPath ?? getDefaultSocketPath();
30
+ this.sessionId = sessionId ?? randomUUID();
31
+ }
32
+
33
+ /**
34
+ * Invia una richiesta al daemon e attende la risposta.
35
+ */
36
+ async call(tool: string, params: unknown): Promise<unknown> {
37
+ return new Promise((resolve, reject) => {
38
+ const id = ++this.requestCounter;
39
+ const request: SocketRequest = { id, tool, params, session_id: this.sessionId };
40
+ const requestLine = JSON.stringify(request) + "\n";
41
+
42
+ let client: net.Socket;
43
+ let responseBuffer = "";
44
+ let settled = false;
45
+
46
+ const timeout = setTimeout(() => {
47
+ if (!settled) {
48
+ settled = true;
49
+ client?.destroy();
50
+ reject(new Error(`Timeout: no response from daemon for tool '${tool}' after 30s`));
51
+ }
52
+ }, 30_000);
53
+
54
+ const settle = (fn: () => void) => {
55
+ if (!settled) {
56
+ settled = true;
57
+ clearTimeout(timeout);
58
+ fn();
59
+ }
60
+ };
61
+
62
+ if (process.platform === "win32") {
63
+ client = net.createConnection(this.socketPath);
64
+ } else {
65
+ client = net.createConnection({ path: this.socketPath });
66
+ }
67
+
68
+ client.on("connect", () => {
69
+ client.write(requestLine);
70
+ });
71
+
72
+ client.on("data", (chunk: Buffer) => {
73
+ responseBuffer += chunk.toString();
74
+ const newlineIndex = responseBuffer.indexOf("\n");
75
+ if (newlineIndex !== -1) {
76
+ const line = responseBuffer.slice(0, newlineIndex);
77
+ client.destroy();
78
+ try {
79
+ const response: SocketResponse = JSON.parse(line);
80
+ if (response.type === "response") {
81
+ settle(() => resolve(response.result));
82
+ } else {
83
+ settle(() => reject(new Error(response.message ?? "Daemon error")));
84
+ }
85
+ } catch (e) {
86
+ settle(() => reject(new Error(`Invalid response JSON: ${line}`)));
87
+ }
88
+ }
89
+ });
90
+
91
+ client.on("error", (err: Error) => {
92
+ settle(() => reject(new Error(`Daemon connection error: ${err.message}. Is vexp daemon running?`)));
93
+ });
94
+
95
+ client.on("close", () => {
96
+ settle(() => reject(new Error("Daemon connection closed unexpectedly")));
97
+ });
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Verifica se il daemon è raggiungibile
103
+ */
104
+ async health(): Promise<boolean> {
105
+ try {
106
+ await this.call("index_status", {});
107
+ return true;
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+ }
113
+
114
+ /** FNV-1a 64-bit hash — must match vexp-core/src/utils.rs md5_hash() */
115
+ function fnvHash(input: string): string {
116
+ let hash = BigInt("0xcbf29ce484222325");
117
+ const prime = BigInt("0x100000001b3");
118
+ const mask = BigInt("0xffffffffffffffff");
119
+ const bytes = Buffer.from(input, "utf-8");
120
+ for (const byte of bytes) {
121
+ hash ^= BigInt(byte);
122
+ hash = (hash * prime) & mask;
123
+ }
124
+ return hash.toString(16);
125
+ }
126
+
127
+ function getDefaultSocketPath(): string {
128
+ const workspaceRoot = process.env["VEXP_WORKSPACE"] ?? process.cwd();
129
+ if (process.platform === "win32") {
130
+ const hash = fnvHash(workspaceRoot);
131
+ return `\\\\.\\pipe\\vexp-${hash.slice(0, 8)}`;
132
+ }
133
+ return path.join(workspaceRoot, ".vexp", "daemon.sock");
134
+ }