feat: Created the explore agent for exploring codebases to help answer questions
This commit is contained in:
Executable
+447
@@ -0,0 +1,447 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shared Agent Utilities - Minimal, focused helper functions
|
||||
set -euo pipefail
|
||||
|
||||
#############################
|
||||
## CONTEXT FILE MANAGEMENT ##
|
||||
#############################
|
||||
|
||||
get_context_file() {
|
||||
local project_dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
||||
echo "${project_dir}/.loki-context"
|
||||
}
|
||||
|
||||
# Initialize context file for a new task
|
||||
# Usage: init_context "Task description"
|
||||
init_context() {
|
||||
local task="$1"
|
||||
local project_dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
||||
local context_file
|
||||
context_file=$(get_context_file)
|
||||
|
||||
cat > "${context_file}" <<EOF
|
||||
## Project: ${project_dir}
|
||||
## Task: ${task}
|
||||
## Started: $(date -Iseconds)
|
||||
|
||||
### Prior Findings
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Append findings to the context file
|
||||
# Usage: append_context "agent_name" "finding summary
|
||||
append_context() {
|
||||
local agent="$1"
|
||||
local finding="$2"
|
||||
local context_file
|
||||
context_file=$(get_context_file)
|
||||
|
||||
if [[ -f "${context_file}" ]]; then
|
||||
{
|
||||
echo ""
|
||||
echo "[${agent}]:"
|
||||
echo "${finding}"
|
||||
} >> "${context_file}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Read the current context (returns empty string if no context)
|
||||
# Usage: context=$(read_context)
|
||||
read_context() {
|
||||
local context_file
|
||||
context_file=$(get_context_file)
|
||||
|
||||
if [[ -f "${context_file}" ]]; then
|
||||
cat "${context_file}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Clear the context file
|
||||
clear_context() {
|
||||
local context_file
|
||||
context_file=$(get_context_file)
|
||||
rm -f "${context_file}"
|
||||
}
|
||||
|
||||
#######################
|
||||
## PROJECT DETECTION ##
|
||||
#######################
|
||||
|
||||
# Cache file name for detected project info
|
||||
_LOKI_PROJECT_CACHE=".loki-project.json"
|
||||
|
||||
# Read cached project detection if valid
|
||||
# Usage: _read_project_cache "/path/to/project"
|
||||
# Returns: cached JSON on stdout (exit 0) or nothing (exit 1)
|
||||
_read_project_cache() {
|
||||
local dir="$1"
|
||||
local cache_file="${dir}/${_LOKI_PROJECT_CACHE}"
|
||||
|
||||
if [[ -f "${cache_file}" ]]; then
|
||||
local cached
|
||||
cached=$(cat "${cache_file}" 2>/dev/null) || return 1
|
||||
if echo "${cached}" | jq -e '.type and .build != null and .test != null and .check != null' &>/dev/null; then
|
||||
echo "${cached}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Write project detection result to cache
|
||||
# Usage: _write_project_cache "/path/to/project" '{"type":"rust",...}'
|
||||
_write_project_cache() {
|
||||
local dir="$1"
|
||||
local json="$2"
|
||||
local cache_file="${dir}/${_LOKI_PROJECT_CACHE}"
|
||||
|
||||
echo "${json}" > "${cache_file}" 2>/dev/null || true
|
||||
}
|
||||
|
||||
_detect_heuristic() {
|
||||
local dir="$1"
|
||||
|
||||
# Rust
|
||||
if [[ -f "${dir}/Cargo.toml" ]]; then
|
||||
echo '{"type":"rust","build":"cargo build","test":"cargo test","check":"cargo check"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Go
|
||||
if [[ -f "${dir}/go.mod" ]]; then
|
||||
echo '{"type":"go","build":"go build ./...","test":"go test ./...","check":"go vet ./..."}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Node.JS/Deno/Bun
|
||||
if [[ -f "${dir}/deno.json" ]] || [[ -f "${dir}/deno.jsonc" ]]; then
|
||||
echo '{"type":"deno","build":"deno task build","test":"deno test","check":"deno lint"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -f "${dir}/package.json" ]]; then
|
||||
local pm="npm"
|
||||
|
||||
[[ -f "${dir}/bun.lockb" ]] || [[ -f "${dir}/bun.lock" ]] && pm="bun"
|
||||
[[ -f "${dir}/pnpm-lock.yaml" ]] && pm="pnpm"
|
||||
[[ -f "${dir}/yarn.lock" ]] && pm="yarn"
|
||||
|
||||
echo "{\"type\":\"nodejs\",\"build\":\"${pm} run build\",\"test\":\"${pm} test\",\"check\":\"${pm} run lint\"}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Python
|
||||
if [[ -f "${dir}/pyproject.toml" ]] || [[ -f "${dir}/setup.py" ]] || [[ -f "${dir}/setup.cfg" ]]; then
|
||||
local test_cmd="pytest"
|
||||
local check_cmd="ruff check ."
|
||||
|
||||
if [[ -f "${dir}/poetry.lock" ]]; then
|
||||
test_cmd="poetry run pytest"
|
||||
check_cmd="poetry run ruff check ."
|
||||
elif [[ -f "${dir}/uv.lock" ]]; then
|
||||
test_cmd="uv run pytest"
|
||||
check_cmd="uv run ruff check ."
|
||||
fi
|
||||
|
||||
echo "{\"type\":\"python\",\"build\":\"\",\"test\":\"${test_cmd}\",\"check\":\"${check_cmd}\"}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# JVM (Maven)
|
||||
if [[ -f "${dir}/pom.xml" ]]; then
|
||||
echo '{"type":"java","build":"mvn compile","test":"mvn test","check":"mvn verify"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# JVM (Gradle)
|
||||
if [[ -f "${dir}/build.gradle" ]] || [[ -f "${dir}/build.gradle.kts" ]]; then
|
||||
local gw="gradle"
|
||||
[[ -f "${dir}/gradlew" ]] && gw="./gradlew"
|
||||
echo "{\"type\":\"java\",\"build\":\"${gw} build\",\"test\":\"${gw} test\",\"check\":\"${gw} check\"}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# .NET / C#
|
||||
if compgen -G "${dir}/*.sln" &>/dev/null || compgen -G "${dir}/*.csproj" &>/dev/null; then
|
||||
echo '{"type":"dotnet","build":"dotnet build","test":"dotnet test","check":"dotnet build --warnaserrors"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# C/C++ (CMake)
|
||||
if [[ -f "${dir}/CMakeLists.txt" ]]; then
|
||||
echo '{"type":"cmake","build":"cmake --build build","test":"ctest --test-dir build","check":"cmake --build build"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Ruby
|
||||
if [[ -f "${dir}/Gemfile" ]]; then
|
||||
local test_cmd="bundle exec rake test"
|
||||
[[ -f "${dir}/Rakefile" ]] && grep -q "rspec" "${dir}/Gemfile" 2>/dev/null && test_cmd="bundle exec rspec"
|
||||
echo "{\"type\":\"ruby\",\"build\":\"\",\"test\":\"${test_cmd}\",\"check\":\"bundle exec rubocop\"}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Elixir
|
||||
if [[ -f "${dir}/mix.exs" ]]; then
|
||||
echo '{"type":"elixir","build":"mix compile","test":"mix test","check":"mix credo"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# PHP
|
||||
if [[ -f "${dir}/composer.json" ]]; then
|
||||
echo '{"type":"php","build":"","test":"./vendor/bin/phpunit","check":"./vendor/bin/phpstan analyse"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Swift
|
||||
if [[ -f "${dir}/Package.swift" ]]; then
|
||||
echo '{"type":"swift","build":"swift build","test":"swift test","check":"swift build"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Zig
|
||||
if [[ -f "${dir}/build.zig" ]]; then
|
||||
echo '{"type":"zig","build":"zig build","test":"zig build test","check":"zig build"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Generic build systems (last resort before LLM)
|
||||
if [[ -f "${dir}/justfile" ]] || [[ -f "${dir}/Justfile" ]]; then
|
||||
echo '{"type":"just","build":"just build","test":"just test","check":"just lint"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -f "${dir}/Makefile" ]] || [[ -f "${dir}/makefile" ]] || [[ -f "${dir}/GNUmakefile" ]]; then
|
||||
echo '{"type":"make","build":"make build","test":"make test","check":"make lint"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Gather lightweight evidence about a project for LLM analysis
|
||||
# Usage: _gather_project_evidence "/path/to/project"
|
||||
# Returns: evidence string on stdout
|
||||
_gather_project_evidence() {
|
||||
local dir="$1"
|
||||
local evidence=""
|
||||
|
||||
evidence+="Root files and directories:"$'\n'
|
||||
evidence+=$(ls -1 "${dir}" 2>/dev/null | head -50)
|
||||
evidence+=$'\n\n'
|
||||
|
||||
evidence+="File extension counts:"$'\n'
|
||||
evidence+=$(find "${dir}" -type f \
|
||||
-not -path '*/.git/*' \
|
||||
-not -path '*/node_modules/*' \
|
||||
-not -path '*/target/*' \
|
||||
-not -path '*/dist/*' \
|
||||
-not -path '*/__pycache__/*' \
|
||||
-not -path '*/vendor/*' \
|
||||
-not -path '*/.build/*' \
|
||||
2>/dev/null \
|
||||
| sed 's/.*\.//' | sort | uniq -c | sort -rn | head -10)
|
||||
evidence+=$'\n\n'
|
||||
|
||||
local config_patterns=("*.toml" "*.yaml" "*.yml" "*.json" "*.xml" "*.gradle" "*.gradle.kts" "*.cabal" "*.pro" "Makefile" "justfile" "Justfile" "Dockerfile" "Taskfile*" "BUILD" "WORKSPACE" "flake.nix" "shell.nix" "default.nix")
|
||||
local found_configs=0
|
||||
|
||||
for pattern in "${config_patterns[@]}"; do
|
||||
if [[ ${found_configs} -ge 5 ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
local files
|
||||
files=$(find "${dir}" -maxdepth 1 -name "${pattern}" -type f 2>/dev/null)
|
||||
|
||||
while IFS= read -r f; do
|
||||
if [[ -n "${f}" && ${found_configs} -lt 5 ]]; then
|
||||
local basename
|
||||
basename=$(basename "${f}")
|
||||
evidence+="--- ${basename} (first 30 lines) ---"$'\n'
|
||||
evidence+=$(head -30 "${f}" 2>/dev/null)
|
||||
evidence+=$'\n\n'
|
||||
found_configs=$((found_configs + 1))
|
||||
fi
|
||||
done <<< "${files}"
|
||||
done
|
||||
|
||||
echo "${evidence}"
|
||||
}
|
||||
|
||||
# LLM-based project detection fallback
|
||||
# Usage: _detect_with_llm "/path/to/project"
|
||||
# Returns: JSON on stdout or empty (exit 1)
|
||||
_detect_with_llm() {
|
||||
local dir="$1"
|
||||
local evidence
|
||||
evidence=$(_gather_project_evidence "${dir}")
|
||||
local prompt
|
||||
prompt=$(cat <<-EOF
|
||||
|
||||
Analyze this project directory and determine the project type, primary language, and the correct shell commands to build, test, and check (lint/typecheck) it.
|
||||
|
||||
EOF
|
||||
)
|
||||
prompt+=$'\n'"${evidence}"$'\n'
|
||||
prompt+=$(cat <<-EOF
|
||||
|
||||
Respond with ONLY a valid JSON object. No markdown fences, no explanation, no extra text.
|
||||
The JSON must have exactly these 4 keys:
|
||||
{"type":"<language>","build":"<build command>","test":"<test command>","check":"<lint or typecheck command>"}
|
||||
|
||||
Rules:
|
||||
- "type" must be a single lowercase word (e.g. rust, go, python, nodejs, java, ruby, elixir, cpp, c, zig, haskell, scala, kotlin, dart, swift, php, dotnet, etc.)
|
||||
- If a command doesn't apply to this project, use an empty string, ""
|
||||
- Use the most standard/common commands for the detected ecosystem
|
||||
- If you detect a package manager lockfile, use that package manager (e.g. pnpm over npm)
|
||||
EOF
|
||||
)
|
||||
|
||||
local llm_response
|
||||
llm_response=$(loki --no-stream "${prompt}" 2>/dev/null) || return 1
|
||||
|
||||
llm_response=$(echo "${llm_response}" | sed 's/^```json//;s/^```//;s/```$//' | tr -d '\n' | sed 's/^[[:space:]]*//')
|
||||
llm_response=$(echo "${llm_response}" | grep -o '{[^}]*}' | head -1)
|
||||
|
||||
if echo "${llm_response}" | jq -e '.type and .build != null and .test != null and .check != null' &>/dev/null; then
|
||||
echo "${llm_response}" | jq -c '{type: (.type // "unknown"), build: (.build // ""), test: (.test // ""), check: (.check // "")}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Detect project type and return build/test commands
|
||||
# Uses: cached result -> fast heuristics -> LLM fallback
|
||||
detect_project() {
|
||||
local dir="${1:-.}"
|
||||
|
||||
local cached
|
||||
if cached=$(_read_project_cache "${dir}"); then
|
||||
echo "${cached}" | jq -c '{type, build, test, check}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
local result
|
||||
if result=$(_detect_heuristic "${dir}"); then
|
||||
local enriched
|
||||
enriched=$(echo "${result}" | jq -c '. + {"_detected_by":"heuristic","_cached_at":"'"$(date -Iseconds)"'"}')
|
||||
|
||||
_write_project_cache "${dir}" "${enriched}"
|
||||
|
||||
echo "${result}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if result=$(_detect_with_llm "${dir}"); then
|
||||
local enriched
|
||||
enriched=$(echo "${result}" | jq -c '. + {"_detected_by":"llm","_cached_at":"'"$(date -Iseconds)"'"}')
|
||||
|
||||
_write_project_cache "${dir}" "${enriched}"
|
||||
|
||||
echo "${result}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo '{"type":"unknown","build":"","test":"","check":""}'
|
||||
}
|
||||
|
||||
######################
|
||||
## AGENT INVOCATION ##
|
||||
######################
|
||||
|
||||
# Invoke a subagent with optional context injection
|
||||
# Usage: invoke_agent <agent_name> <prompt> [extra_args...]
|
||||
invoke_agent() {
|
||||
local agent="$1"
|
||||
local prompt="$2"
|
||||
shift 2
|
||||
|
||||
local context
|
||||
context=$(read_context)
|
||||
|
||||
local full_prompt
|
||||
if [[ -n "${context}" ]]; then
|
||||
full_prompt="## Orchestrator Context
|
||||
|
||||
The orchestrator (sisyphus) has gathered this context from prior work:
|
||||
|
||||
<context>
|
||||
${context}
|
||||
</context>
|
||||
|
||||
## Your Task
|
||||
|
||||
${prompt}"
|
||||
else
|
||||
full_prompt="${prompt}"
|
||||
fi
|
||||
|
||||
env AUTO_CONFIRM=true loki --agent "${agent}" "$@" "${full_prompt}" 2>&1
|
||||
}
|
||||
|
||||
# Invoke a subagent and capture a summary of its findings
|
||||
# Usage: result=$(invoke_agent_with_summary "explore" "find auth patterns")
|
||||
invoke_agent_with_summary() {
|
||||
local agent="$1"
|
||||
local prompt="$2"
|
||||
shift 2
|
||||
|
||||
local output
|
||||
output=$(invoke_agent "${agent}" "${prompt}" "$@")
|
||||
|
||||
local summary=""
|
||||
|
||||
if echo "${output}" | grep -q "FINDINGS:"; then
|
||||
summary=$(echo "${output}" | sed -n '/FINDINGS:/,/^[A-Z_]*COMPLETE/p' | grep "^- " | sed 's/^- / - /')
|
||||
elif echo "${output}" | grep -q "CODER_COMPLETE:"; then
|
||||
summary=$(echo "${output}" | grep "CODER_COMPLETE:" | sed 's/CODER_COMPLETE: *//')
|
||||
elif echo "${output}" | grep -q "ORACLE_COMPLETE"; then
|
||||
summary=$(echo "${output}" | sed -n '/^## Recommendation/,/^## /{/^## Recommendation/d;/^## /d;p}' | sed '/^$/d' | head -10)
|
||||
fi
|
||||
|
||||
# Failsafe: extract up to 5 meaningful lines if no markers found
|
||||
if [[ -z "${summary}" ]]; then
|
||||
summary=$(echo "${output}" | grep -v "^$" | grep -v "^#" | grep -v "^\-\-\-" | tail -10 | head -5)
|
||||
fi
|
||||
|
||||
if [[ -n "${summary}" ]]; then
|
||||
append_context "${agent}" "${summary}"
|
||||
fi
|
||||
|
||||
echo "${output}"
|
||||
}
|
||||
|
||||
###########################
|
||||
## FILE SEARCH UTILITIES ##
|
||||
###########################
|
||||
|
||||
search_files() {
|
||||
local pattern="$1"
|
||||
local dir="${2:-.}"
|
||||
|
||||
find "${dir}" -type f -name "${pattern}" \
|
||||
-not -path '*/target/*' \
|
||||
-not -path '*/node_modules/*' \
|
||||
-not -path '*/.git/*' \
|
||||
-not -path '*/dist/*' \
|
||||
-not -path '*/__pycache__/*' \
|
||||
2>/dev/null | head -25
|
||||
}
|
||||
|
||||
get_tree() {
|
||||
local dir="${1:-.}"
|
||||
local depth="${2:-3}"
|
||||
|
||||
if command -v tree &>/dev/null; then
|
||||
tree -L "${depth}" --noreport -I 'node_modules|target|dist|.git|__pycache__|*.pyc' "${dir}" 2>/dev/null || find "${dir}" -maxdepth "${depth}" -type f | head -50
|
||||
else
|
||||
find "${dir}" -maxdepth "${depth}" -type f \
|
||||
-not -path '*/target/*' \
|
||||
-not -path '*/node_modules/*' \
|
||||
-not -path '*/.git/*' \
|
||||
2>/dev/null | head -50
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# Explore
|
||||
|
||||
An AI agent specialized in exploring codebases, finding patterns, and understanding project structures.
|
||||
|
||||
This agent is designed to be delegated to by the **[Sisyphus](../sisyphus/README.md)** agent to gather information and context. Sisyphus
|
||||
acts as the coordinator/architect, while Explore handles the research and discovery phase.
|
||||
|
||||
It can also be used as a standalone tool for understanding codebases and finding specific information.
|
||||
|
||||
## Features
|
||||
|
||||
- 🔍 Deep codebase exploration and pattern matching
|
||||
- 📂 File system navigation and content analysis
|
||||
- 🧠 Context gathering for complex tasks
|
||||
- 🛡️ Read-only operations for safe investigation
|
||||
@@ -0,0 +1,74 @@
|
||||
name: explore
|
||||
description: Fast codebase exploration agent - finds patterns, structures, and relevant files
|
||||
version: 1.0.0
|
||||
temperature: 0.1
|
||||
top_p: 0.95
|
||||
|
||||
variables:
|
||||
- name: project_dir
|
||||
description: Project directory to explore
|
||||
default: '.'
|
||||
|
||||
global_tools:
|
||||
- fs_read.sh
|
||||
- fs_grep.sh
|
||||
- fs_glob.sh
|
||||
- fs_ls.sh
|
||||
|
||||
instructions: |
|
||||
You are a codebase explorer. Your job: Search, find, report. Nothing else.
|
||||
|
||||
## Your Mission
|
||||
|
||||
Given a search task, you:
|
||||
1. Search for relevant files and patterns
|
||||
2. Read key files to understand structure
|
||||
3. Report findings concisely
|
||||
4. Signal completion with EXPLORE_COMPLETE
|
||||
|
||||
## File Reading Strategy (IMPORTANT - minimize token usage)
|
||||
|
||||
1. **Find first, read second** - Never read a file without knowing why
|
||||
2. **Use grep to locate** - `fs_grep --pattern "struct User" --include "*.rs"` finds exactly where things are
|
||||
3. **Use glob to discover** - `fs_glob --pattern "*.rs" --path src/` finds files by name
|
||||
4. **Read targeted sections** - `fs_read --path "src/main.rs" --offset 50 --limit 30` reads only lines 50-79
|
||||
5. **Never read entire large files** - If a file is 500+ lines, read the relevant section only
|
||||
|
||||
## Available Actions
|
||||
|
||||
- `fs_grep --pattern "struct User" --include "*.rs"` - Find content across files
|
||||
- `fs_glob --pattern "*.rs" --path src/` - Find files by name pattern
|
||||
- `fs_read --path "src/main.rs"` - Read a file (with line numbers)
|
||||
- `fs_read --path "src/main.rs" --offset 100 --limit 50` - Read lines 100-149 only
|
||||
- `get_structure` - See project layout
|
||||
- `search_content --pattern "struct User"` - Agent-level content search
|
||||
|
||||
## Output Format
|
||||
|
||||
Always end your response with a findings summary:
|
||||
|
||||
```
|
||||
FINDINGS:
|
||||
- [Key finding 1]
|
||||
- [Key finding 2]
|
||||
- Relevant files: [list]
|
||||
|
||||
EXPLORE_COMPLETE
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Be fast** - Don't read every file, read representative ones
|
||||
2. **Be focused** - Answer the specific question asked
|
||||
3. **Be concise** - Report findings, not your process
|
||||
4. **Never modify files** - You are read-only
|
||||
5. **Limit reads** - Max 5 file reads per exploration
|
||||
|
||||
## Context
|
||||
- Project: {{project_dir}}
|
||||
- CWD: {{__cwd__}}
|
||||
|
||||
conversation_starters:
|
||||
- 'Find how authentication is implemented'
|
||||
- 'What patterns are used for API endpoints'
|
||||
- 'Show me the project structure'
|
||||
Executable
+157
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$LLM_PROMPT_UTILS_FILE"
|
||||
source "$LLM_ROOT_DIR/agents/.shared/utils.sh"
|
||||
|
||||
# @env LLM_OUTPUT=/dev/stdout
|
||||
# @env LLM_AGENT_VAR_PROJECT_DIR=.
|
||||
# @describe Explore agent tools for codebase search and analysis
|
||||
|
||||
_project_dir() {
|
||||
local dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
||||
(cd "${dir}" 2>/dev/null && pwd) || echo "${dir}"
|
||||
}
|
||||
|
||||
# @cmd Get project structure and layout
|
||||
get_structure() {
|
||||
local project_dir
|
||||
project_dir=$(_project_dir)
|
||||
|
||||
info "Project structure:" >> "$LLM_OUTPUT"
|
||||
echo "" >> "$LLM_OUTPUT"
|
||||
|
||||
local project_info
|
||||
project_info=$(detect_project "${project_dir}")
|
||||
|
||||
{
|
||||
echo "Type: $(echo "${project_info}" | jq -r '.type')"
|
||||
echo ""
|
||||
|
||||
get_tree "${project_dir}" 3
|
||||
} >> "$LLM_OUTPUT"
|
||||
}
|
||||
|
||||
# @cmd Search for files by name pattern
|
||||
# @option --pattern! File name pattern (e.g., "*.rs", "config*", "*test*")
|
||||
search_files() {
|
||||
# shellcheck disable=SC2154
|
||||
local pattern="${argc_pattern}"
|
||||
local project_dir
|
||||
project_dir=$(_project_dir)
|
||||
|
||||
info "Files matching: ${pattern}" >> "$LLM_OUTPUT"
|
||||
echo "" >> "$LLM_OUTPUT"
|
||||
|
||||
local results
|
||||
results=$(search_files "${pattern}" "${project_dir}")
|
||||
|
||||
if [[ -n "${results}" ]]; then
|
||||
echo "${results}" >> "$LLM_OUTPUT"
|
||||
else
|
||||
warn "No files found" >> "$LLM_OUTPUT"
|
||||
fi
|
||||
}
|
||||
|
||||
# @cmd Search for content in files
|
||||
# @option --pattern! Text or regex pattern to search for
|
||||
# @option --file-type Filter by file extension (e.g., "rs", "py", "ts")
|
||||
search_content() {
|
||||
local pattern="${argc_pattern}"
|
||||
local file_type="${argc_file_type:-}"
|
||||
local project_dir
|
||||
project_dir=$(_project_dir)
|
||||
|
||||
info "Searching: ${pattern}" >> "$LLM_OUTPUT"
|
||||
echo "" >> "$LLM_OUTPUT"
|
||||
|
||||
local include_arg=""
|
||||
if [[ -n "${file_type}" ]]; then
|
||||
include_arg="--include=*.${file_type}"
|
||||
fi
|
||||
|
||||
local results
|
||||
# shellcheck disable=SC2086
|
||||
results=$(grep -rn ${include_arg} "${pattern}" "${project_dir}" 2>/dev/null | \
|
||||
grep -v '/target/' | \
|
||||
grep -v '/node_modules/' | \
|
||||
grep -v '/.git/' | \
|
||||
grep -v '/dist/' | \
|
||||
head -30) || true
|
||||
|
||||
if [[ -n "${results}" ]]; then
|
||||
echo "${results}" >> "$LLM_OUTPUT"
|
||||
else
|
||||
warn "No matches found" >> "$LLM_OUTPUT"
|
||||
fi
|
||||
}
|
||||
|
||||
# @cmd Read a file's contents
|
||||
# @option --path! Path to the file (relative to project root)
|
||||
# @option --lines Maximum lines to read (default: 200)
|
||||
read_file() {
|
||||
# shellcheck disable=SC2154
|
||||
local file_path="${argc_path}"
|
||||
local max_lines="${argc_lines:-200}"
|
||||
local project_dir
|
||||
project_dir=$(_project_dir)
|
||||
|
||||
local full_path="${project_dir}/${file_path}"
|
||||
|
||||
if [[ ! -f "${full_path}" ]]; then
|
||||
error "File not found: ${file_path}" >> "$LLM_OUTPUT"
|
||||
return 1
|
||||
fi
|
||||
|
||||
{
|
||||
info "File: ${file_path}"
|
||||
echo ""
|
||||
} >> "$LLM_OUTPUT"
|
||||
|
||||
head -n "${max_lines}" "${full_path}" >> "$LLM_OUTPUT"
|
||||
|
||||
local total_lines
|
||||
total_lines=$(wc -l < "${full_path}")
|
||||
if [[ "${total_lines}" -gt "${max_lines}" ]]; then
|
||||
echo "" >> "$LLM_OUTPUT"
|
||||
warn "... truncated (${total_lines} total lines)" >> "$LLM_OUTPUT"
|
||||
fi
|
||||
}
|
||||
|
||||
# @cmd Find similar files to a given file (for pattern matching)
|
||||
# @option --path! Path to the reference file
|
||||
find_similar() {
|
||||
local file_path="${argc_path}"
|
||||
local project_dir
|
||||
project_dir=$(_project_dir)
|
||||
|
||||
local ext="${file_path##*.}"
|
||||
local dir
|
||||
dir=$(dirname "${file_path}")
|
||||
|
||||
info "Files similar to: ${file_path}" >> "$LLM_OUTPUT"
|
||||
echo "" >> "$LLM_OUTPUT"
|
||||
|
||||
local results
|
||||
results=$(find "${project_dir}/${dir}" -maxdepth 1 -type f -name "*.${ext}" \
|
||||
! -name "$(basename "${file_path}")" \
|
||||
! -name "*test*" \
|
||||
! -name "*spec*" \
|
||||
2>/dev/null | head -5)
|
||||
|
||||
if [[ -n "${results}" ]]; then
|
||||
echo "${results}" >> "$LLM_OUTPUT"
|
||||
else
|
||||
results=$(find "${project_dir}" -type f -name "*.${ext}" \
|
||||
! -name "$(basename "${file_path}")" \
|
||||
! -name "*test*" \
|
||||
-not -path '*/target/*' \
|
||||
2>/dev/null | head -5)
|
||||
if [[ -n "${results}" ]]; then
|
||||
echo "${results}" >> "$LLM_OUTPUT"
|
||||
else
|
||||
warn "No similar files found" >> "$LLM_OUTPUT"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
Reference in New Issue
Block a user