{"version":3,"file":"workspace-search.d.ts","sourceRoot":"","sources":["../../../src/core/tools/workspace-search.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AA4F/D,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAStF;AA0DD,uEAAuE;AACvE,wBAAgB,wBAAwB,IAAI,MAAM,EAAE,CASnD","sourcesContent":["/**\n * Workspace retrieval tools (1.7.0).\n *\n * Provider-independent index-backed search. Search tools are read-only and\n * parallel-safe; the refresh tool writes Jensen-managed runtime state only\n * (never workspace source files).\n */\n\nimport type { AgentTool } from \"@apholdings/jensen-agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { WorkspaceIndex } from \"../workspace/index.js\";\nimport type { WorkspaceRetrievalResult } from \"../workspace/types.js\";\n\nconst searchSchema = Type.Object({\n\tquery: Type.String({ description: \"Search query (natural language, identifier, or path hint)\" }),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default 20)\" })),\n\tmode: Type.Optional(\n\t\tType.Union(\n\t\t\t[\n\t\t\t\tType.Literal(\"hybrid\"),\n\t\t\t\tType.Literal(\"lexical\"),\n\t\t\t\tType.Literal(\"semantic\"),\n\t\t\t\tType.Literal(\"symbol\"),\n\t\t\t\tType.Literal(\"path\"),\n\t\t\t],\n\t\t\t{\n\t\t\t\tdescription: \"Retrieval mode (default hybrid)\",\n\t\t\t},\n\t\t),\n\t),\n\tlanguage: Type.Optional(Type.String({ description: \"Filter by language (e.g. typescript, python)\" })),\n\tfileClass: Type.Optional(\n\t\tType.String({ description: \"Filter by file class (source, test, documentation, configuration)\" }),\n\t),\n\tfreshOnly: Type.Optional(\n\t\tType.Boolean({ description: \"Only return results whose current file hash matches the index\" }),\n\t),\n});\n\ntype SearchToolInput = Static<typeof searchSchema>;\n\nfunction formatResults(results: WorkspaceRetrievalResult[], limit: number): string {\n\tconst bounded = results.slice(0, limit);\n\tif (bounded.length === 0) {\n\t\treturn \"No matches found. Run `jensen index build` if the workspace has not been indexed yet.\";\n\t}\n\tconst lines: string[] = [];\n\tfor (const r of bounded) {\n\t\tconst sym = r.symbol?.name ? ` (${r.symbol.name})` : \"\";\n\t\tconst linesRange = `${r.location.startLine + 1}-${r.location.endLine + 1}`;\n\t\tlines.push(\n\t\t\t`- ${r.file.workspaceRelativePath}:${linesRange}${sym} [${r.score.reasonCodes.join(\",\")}] fresh=${r.freshness}`,\n\t\t);\n\t}\n\tlines.push(\n\t\t`\\n[${bounded.length} result(s); evidence ids ` +\n\t\t\tbounded\n\t\t\t\t.slice(0, 5)\n\t\t\t\t.map((r) => r.evidenceId)\n\t\t\t\t.join(\", \") +\n\t\t\t(bounded.length > 5 ? \", …\" : \"\") +\n\t\t\t\"]\",\n\t);\n\treturn lines.join(\"\\n\");\n}\n\nfunction openIndex(cwd: string): WorkspaceIndex {\n\t// Semantic-only searches need an enabled embedding backend; others degrade.\n\treturn new WorkspaceIndex(cwd);\n}\n\nfunction makeSearchTool(name: string, mode: string, cwd: string): AgentTool<any> {\n\treturn {\n\t\tname,\n\t\tlabel: name,\n\t\tdescription: `${mode} workspace retrieval over the durable workspace index. Returns addressable, evidence-backed code locations with freshness labels. Read-only.`,\n\t\tparameters: searchSchema,\n\t\tisConcurrencySafe: () => true,\n\t\texecute: async (_id: string, input: SearchToolInput) => {\n\t\t\tconst index = openIndex(cwd);\n\t\t\ttry {\n\t\t\t\tconst { results } = index.search({\n\t\t\t\t\tquery: input.query,\n\t\t\t\t\tmode: mode === \"symbol\" ? \"symbol\" : (mode as never),\n\t\t\t\t\tlimit: input.limit ?? 20,\n\t\t\t\t\tlanguageFilters: input.language ? [input.language] : undefined,\n\t\t\t\t\tfileClassFilters: input.fileClass ? [input.fileClass] : undefined,\n\t\t\t\t\tfreshOnly: input.freshOnly,\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: formatResults(results, input.limit ?? 20) }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t} finally {\n\t\t\t\tindex.close();\n\t\t\t}\n\t\t},\n\t};\n}\n\nexport function createWorkspaceSearchTools(cwd: string): Record<string, AgentTool<any>> {\n\treturn {\n\t\tworkspace_search: makeSearchTool(\"workspace_search\", \"hybrid\", cwd),\n\t\tworkspace_search_lexical: makeSearchTool(\"workspace_search_lexical\", \"lexical\", cwd),\n\t\tworkspace_search_semantic: makeSearchTool(\"workspace_search_semantic\", \"semantic\", cwd),\n\t\tworkspace_search_symbols: makeSearchTool(\"workspace_search_symbols\", \"symbol\", cwd),\n\t\tworkspace_retrieval_status: makeStatusTool(cwd),\n\t\tworkspace_index_refresh: makeRefreshTool(cwd),\n\t};\n}\n\nfunction makeStatusTool(cwd: string): AgentTool<any> {\n\treturn {\n\t\tname: \"workspace_retrieval_status\",\n\t\tlabel: \"workspace_retrieval_status\",\n\t\tdescription:\n\t\t\t\"Report the workspace index status: generation, embed mode, counts. Read-only. Use to decide whether a build/refresh is needed.\",\n\t\tparameters: Type.Object({}),\n\t\tisConcurrencySafe: () => true,\n\t\texecute: async () => {\n\t\t\tconst index = new WorkspaceIndex(cwd);\n\t\t\ttry {\n\t\t\t\tconst s = index.status();\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `generation=${s.currentGeneration ?? \"none\"} ready=${s.hasReadyGeneration} files=${s.fileCount ?? 0} chunks=${s.chunkCount ?? 0} symbols=${s.symbolCount ?? 0} embeddings=${s.embeddingCount ?? 0} embedMode=${(s.embedding as { mode: string }).mode} workspaceId=${s.workspaceId}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t} finally {\n\t\t\t\tindex.close();\n\t\t\t}\n\t\t},\n\t};\n}\n\nfunction makeRefreshTool(cwd: string): AgentTool<any> {\n\treturn {\n\t\tname: \"workspace_index_refresh\",\n\t\tlabel: \"workspace_index_refresh\",\n\t\tdescription:\n\t\t\t\"Incrementally refresh the durable workspace index to match current files. Writes Jensen-managed runtime state only; never modifies source files. Not parallel-safe (exclusive index-writer).\",\n\t\tparameters: Type.Object({}),\n\t\tisConcurrencySafe: () => false,\n\t\texecute: async () => {\n\t\t\tconst index = new WorkspaceIndex(cwd);\n\t\t\ttry {\n\t\t\t\tconst report = await index.refresh();\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `refresh complete: added=${report.added} changed=${report.changed} removed=${report.removed} generation=${report.generationId}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t} finally {\n\t\t\t\tindex.close();\n\t\t\t}\n\t\t},\n\t};\n}\n\n/** Store a status tool for validation that the factory is complete. */\nexport function workspaceSearchToolNames(): string[] {\n\treturn [\n\t\t\"workspace_search\",\n\t\t\"workspace_search_lexical\",\n\t\t\"workspace_search_semantic\",\n\t\t\"workspace_search_symbols\",\n\t\t\"workspace_retrieval_status\",\n\t\t\"workspace_index_refresh\",\n\t];\n}\n"]}