import { Type, type TObject } from "typebox"; import { StringEnum } from "@earendil-works/pi-ai"; /** * Tool definitions with typed typebox schemas mirroring the binary's `--help` * flags (JSON keys are snake_case; binary flags are kebab-case) so the LLM can * discover parameters. The binary still validates server-side. */ export type ToolDef = { tool: string; description: string; parameters: TObject }; const project = () => Type.String({ description: "Indexed project name (see cbm_list_projects)." }); export const TOOLS: ToolDef[] = [ { tool: "list_projects", description: "List all indexed projects in the codebase-memory graph.", parameters: Type.Object({ include_details: Type.Optional(Type.Boolean({ description: "Include branch, node/edge counts and database size. Slower." })), limit: Type.Optional(Type.Integer({ description: "Max projects per call. Response carries 'total' and 'has_more'." })), offset: Type.Optional(Type.Integer({ description: "Skip the first N projects for pagination." })), }), }, { tool: "index_repository", description: "Index a repository into the knowledge graph. Requires 'repo_path'.", parameters: Type.Object({ repo_path: Type.String({ description: "Path to the repository (required)." }), mode: Type.Optional(StringEnum(["full", "moderate", "fast", "cross-repo-intelligence"] as const, { description: "full: all files + similarity/semantic edges. moderate: filtered + similarity/semantic. fast: filtered, no similarity/semantic. cross-repo-intelligence: match Routes/Channels across projects." })), name: Type.Optional(Type.String({ description: "Override the derived project name." })), target_projects: Type.Optional(Type.Array(Type.String(), { description: "Projects to search for cross-repo links (cross-repo-intelligence mode). Use [\"*\"] for all." })), persistence: Type.Optional(Type.Boolean({ description: "Write compressed artifact to .codebase-memory/graph.db.zst for team sharing." })), }), }, { tool: "search_graph", description: "Search the code knowledge graph (BM25 query, name_pattern regex, or semantic_query vector search) for functions, classes, routes, variables. Requires 'project'.", parameters: Type.Object({ project: project(), query: Type.Optional(Type.String({ description: "Natural-language/keyword BM25 search. camelCase is split into words. When set, name_pattern is ignored." })), label: Type.Optional(Type.String()), name_pattern: Type.Optional(Type.String({ description: "Exact-match regex over node names." })), qn_pattern: Type.Optional(Type.String({ description: "Regex over qualified names." })), file_pattern: Type.Optional(Type.String()), relationship: Type.Optional(Type.String()), min_degree: Type.Optional(Type.Integer()), max_degree: Type.Optional(Type.Integer()), exclude_entry_points: Type.Optional(Type.Boolean()), include_connected: Type.Optional(Type.Boolean()), semantic_query: Type.Optional(Type.Array(Type.String(), { description: "ARRAY of keyword strings (e.g. [\"send\",\"publish\"]), NOT one string. Per-keyword min-cosine; results in 'semantic_results'." })), limit: Type.Optional(Type.Integer({ description: "Max results (default 50). Use with offset to paginate; check 'has_more'." })), offset: Type.Optional(Type.Integer({ description: "Skip first N matches for pagination." })), format: Type.Optional(StringEnum(["tree", "json"] as const, { description: "tree (default): prefix-grouped text rows. json: the SAME tree model as structured JSON (cols + column-ordered row arrays)." })), fields: Type.Optional(Type.Array(Type.String(), { description: "Extra per-node property columns, e.g. complexity, cognitive, signature, docstring, return_type, is_test. Core columns (qn/label/file/lines) are always present." })), detail: Type.Optional(StringEnum(["ids", "default"] as const, { description: "ids: bare qualified-name enumeration (one column) — cheapest for wide sweeps. default: full rows." })), }), }, { tool: "query_graph", description: "Run a read-only Cypher query against the knowledge graph. Requires 'project' and 'query'.", parameters: Type.Object({ project: project(), query: Type.String({ description: "Cypher query (required)." }), graph: Type.Optional(Type.String({ description: "Which graph to query: the code knowledge graph (default) or 'missed' (only files not fully indexed, laid out as their file structure)." })), max_rows: Type.Optional(Type.Integer({ description: "Row limit (default unlimited up to 100k). No offset - use search_graph for paging." })), }), }, { tool: "trace_path", description: "Trace paths through the code graph: calls, data_flow, or cross_service. Requires 'project' and 'function_name'.", parameters: Type.Object({ project: project(), function_name: Type.String({ description: "Function/method to trace from (required)." }), direction: Type.Optional(StringEnum(["inbound", "outbound", "both"] as const)), depth: Type.Optional(Type.Integer({ description: "Max hops (default 3)." })), mode: Type.Optional(StringEnum(["calls", "data_flow", "cross_service"] as const, { description: "calls: CALLS edges. data_flow: CALLS+DATA_FLOWS with args. cross_service: HTTP/async routes + CROSS_* edges." })), parameter_name: Type.Optional(Type.String({ description: "data_flow mode: scope to a specific parameter." })), edge_types: Type.Optional(Type.Array(Type.String())), risk_labels: Type.Optional(Type.Boolean({ description: "Add CRITICAL/HIGH/MEDIUM/LOW risk by hop distance." })), include_tests: Type.Optional(Type.Boolean({ description: "Include test files (default false)." })), limit: Type.Optional(Type.Integer({ description: "Rows per page. callees_total/callers_total always carry exact full counts; truncated pages carry 'next'." })), cursor: Type.Optional(Type.String({ description: "Resume token from a previous response's 'next' field. Pass back with ALL other args identical. Stale after a reindex — re-run the original query." })), format: Type.Optional(StringEnum(["tree", "json"] as const, { description: "tree (default): prefix-grouped text rows. json: the SAME tree model as structured JSON." })), include_evidence: Type.Optional(Type.Boolean({ description: "Add how each hop was resolved (lsp | language_rule | heuristic | unresolved) plus resolver confidence. Off by default." })), }), }, { tool: "get_code_snippet", description: "Read source for a function/class/symbol by qualified_name. Requires 'project' and 'qualified_name'.", parameters: Type.Object({ project: project(), qualified_name: Type.String({ description: "Full qualified_name from search_graph, or short function name (required)." }), include_neighbors: Type.Optional(Type.Boolean()), }), }, { tool: "get_graph_schema", description: "Get node labels and edge types of the knowledge graph. Requires 'project'.", parameters: Type.Object({ project: project() }), }, { tool: "search_code", description: "Graph-augmented grep: find text patterns and enrich with graph structure. Requires 'project' and 'pattern'.", parameters: Type.Object({ project: project(), pattern: Type.String({ description: "Text or regex pattern to grep (required). Regex is only honored when regex=true is also passed." }), file_pattern: Type.Optional(Type.String({ description: "Glob for grep --include (e.g. *.go)." })), path_filter: Type.Optional(Type.String({ description: "Regex over result file paths (e.g. ^src/ or \\.(go|ts)$)." })), mode: Type.Optional(StringEnum(["compact", "full", "files"] as const, { description: "compact: signatures+metadata (default). full: with source. files: file list." })), context: Type.Optional(Type.Integer({ description: "Lines of context around each match (compact mode only)." })), regex: Type.Optional(Type.Boolean({ description: "Set true to interpret `pattern` as a regex. When false/omitted the pattern is matched literally, so regex metacharacters (|, .*, (), etc.) will NOT work. Pass regex=true whenever your pattern uses regex syntax." })), limit: Type.Optional(Type.Integer({ description: "Max enriched results (default 10). Check 'total_results'/'total_grep_matches' for truncation. No offset — raise limit or narrow with file_pattern/path_filter." })), debug: Type.Optional(Type.Boolean({ description: "Include scope_ms, scan_ms, and enrich_ms phase timing diagnostics." })), }), }, { tool: "detect_changes", description: "Detect code changes and their graph impact. Requires 'project'.", parameters: Type.Object({ project: project(), scope: Type.Optional(StringEnum(["files", "impact"] as const, { description: "files: changed files only (no traversal). impact (default): files + the transitive impact set." })), direction: Type.Optional(StringEnum(["inbound", "outbound", "both"] as const, { description: "inbound (default) = blast radius: transitive CALLERS of changed symbols. outbound = what the changed code depends on. both = union." })), depth: Type.Optional(Type.Integer({ description: "Max traversal hops from the changed symbols." })), limit: Type.Optional(Type.Integer({ description: "Per-symbol impacted rows shown (nearest hops first). impacted_total is always exact." })), base_branch: Type.Optional(Type.String({ description: "Default 'main'." })), since: Type.Optional(Type.String({ description: "Git ref/tag to diff from (e.g. HEAD~5). Diffs ...HEAD." })), format: Type.Optional(StringEnum(["tree", "json"] as const)), }), }, { tool: "manage_adr", description: "Create/update/read Architecture Decision Records. Requires 'project'.", parameters: Type.Object({ project: project(), mode: Type.Optional(StringEnum(["get", "update", "sections"] as const, { description: "update replaces the entire ADR document; sections only lists existing headings." })), content: Type.Optional(Type.String({ description: "Complete replacement document required by update." })), }), }, { tool: "get_architecture", description: "Architecture overview of an indexed project: node/edge stats, languages, packages, entry points, hotspots. Requires 'project'.", parameters: Type.Object({ project: project(), path: Type.Optional(Type.String({ description: "Optional directory prefix to scope architecture (e.g. apps/hoa)." })), aspects: Type.Optional(Type.Array(Type.String(), { description: "Aspects to include. 'all' = everything; 'overview' = compact summary (all except file_tree); omit = all. 'cycles' is opt-in ONLY (never via all/overview)." })), }), }, { tool: "check_index_coverage", description: "Check whether specific files or path scopes are covered by the index. Requires 'project' plus 'paths' or 'scopes'.", parameters: Type.Object({ project: project(), paths: Type.Optional(Type.Array(Type.String(), { description: "Repository-relative files to check exactly. Required if 'scopes' is omitted." })), scopes: Type.Optional(Type.Array(Type.String(), { description: "Repository-relative path prefixes; use '.' for the project root. Required if 'paths' is omitted." })), scope_limit: Type.Optional(Type.Integer()), scope_offset: Type.Optional(Type.Integer()), }), }, { tool: "ingest_traces", description: "Ingest runtime traces to enhance the knowledge graph. Requires 'project' and 'traces'.", parameters: Type.Object({ project: project(), traces: Type.Array( Type.Object({ caller: Type.Optional(Type.String()), callee: Type.Optional(Type.String()), count: Type.Optional(Type.Integer()), }), { description: "Array of {caller, callee, count} runtime call traces (required)." }, ), }), }, ];