
Researching Codebases
- 17 installs
- 1 repo stars
- Updated March 1, 2026
- cachemoney/agent-toolkit
A Claude Code skill for researching codebases.
About
Skill: researching-codebases. Used during idea phase for development. This skill provides essential functionality for the development workflow.
- researching-codebases
Researching Codebases by the numbers
- 17 all-time installs (skills.sh)
- Ranked #1,602 of 2,277 Frontend Development skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cachemoney/agent-toolkit --skill researching-codebasesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 17 |
|---|---|
| repo stars | ★ 1 |
| Last updated | March 1, 2026 |
| Repository | cachemoney/agent-toolkit ↗ |
What it does
A Claude Code skill for researching codebases.
Files
Researching Codebases
Coordinate parallel sub-agents to answer complex codebase questions.
When to Use
- Questions spanning multiple files or components
- "How does X work?" requiring tracing through code
- Finding patterns or examples across the codebase
- Understanding architectural decisions or data flow
When NOT to Use
- Simple "where is X?" - use
code-locatordirectly - Single file questions - just read the file
- External/web research only - use
web-searcherdirectly
Workflow
0. Check past research (optional)
Before decomposing a new research question, consider checking for related past research:
1. Run list-research.py script to see recent research docs 2. Run search-research.py script with relevant keywords 3. If related research exists, run read-research.py script to load it 4. Build on previous findings instead of starting fresh
See research-tools.md for script usage.
1. Read mentioned files first
If the user references specific files, read them FULLY before spawning agents. This gives you context for decomposition.
2. Decompose the question
Break the query into parallel research tasks. Consider:
- Which areas of the codebase are relevant?
- Do I need locations, analysis, or examples?
- See
agent-selection.mdfor agent capabilities
3. Spawn parallel agents
Launch multiple agents concurrently for independent tasks. Use the task tool with appropriate subagent_type.
Wait for ALL agents to complete before synthesizing.
4. Synthesize and respond
Combine findings into a coherent answer:
- Direct answer to the question
- Key
file:linereferences - Connections between components
- Open questions if any areas need more investigation
5. Offer to save (optional)
For substantial research, ask:
Want me to save this to a research doc? (project:.research/or global:~/.research/)
Skip this for quick answers.
When saving:
1. Run gather-metadata.py script to get date, repo, branch, commit, cwd. 2. Add query (from user's question) and tags (from content) 3. Format YAML frontmatter per output-format.md 4. Create directory if it doesn't exist 5. Use filename: {filename_date}_topic-slug.md
Agent Reference
See agent-selection.md for when to use each agent.
Common Mistakes
Spawning agents before reading context: Read any files the user mentions first.
Not waiting for all agents: Synthesize only after ALL agents complete.
Over-documenting simple answers: Not every question needs a saved research doc.
Sequential when parallel works: If tasks are independent, spawn them together.
Agent Selection
Available Agents
| Agent | Purpose | Use When | Cost |
|---|---|---|---|
code-analyzer | Understand HOW code works | Need implementation details, data flow, tracing | Thorough, expensive |
code-locator | Find WHERE files live | Need file paths, directory structure | Fast, cheap |
code-pattern-finder | Find similar implementations | Need examples to model after, with code snippets | Medium |
web-searcher | External documentation | User explicitly asks, or need library/API docs | Medium |
Selection Guide
Start with `code-locator` to find relevant files, then:
- Need to understand logic? →
code-analyzer - Need examples to copy? →
code-pattern-finder - Need external context? →
web-searcher(only if user asks or library docs needed)
Parallel vs Sequential
Parallel (no dependencies):
- Locating files in different areas
- Searching for different concepts
- Web research alongside code research
Sequential (has dependencies):
- Locate files THEN analyze them
- Find patterns THEN understand their usage
Agent Prompt Tips
Be specific about what to return. Agents know their jobs - tell them WHAT to find, not HOW to search.
Examples:
- "Find all files related to authentication"
- "Analyze how the payment flow works in
src/payments/, trace from entry point to database" - "Find examples of pagination patterns with full code snippets"
- "Search for React Router authentication patterns in official docs"
You are a specialist at understanding HOW code works. Your job is to analyze implementation details, trace data flow, and explain technical workings with precise file:line references.
Core Responsibilities
1. Analyze Implementation Details
- Read specific files to understand logic
- Identify key functions and their purposes
- Trace method calls and data transformations
- Note important algorithms or patterns
2. Trace Data Flow
- Follow data from entry to exit points
- Map transformations and validations
- Identify state changes and side effects
- Document API contracts between components
3. Identify Architectural Patterns
- Recognize design patterns in use
- Note architectural decisions
- Identify conventions and best practices
- Find integration points between systems
Analysis Strategy
Step 1: Read Entry Points
- Start with main files mentioned in the request
- Look for exports, public methods, or route handlers
- Identify the "surface area" of the component
Step 2: Follow the Code Path
- Trace function calls step by step
- Read each file involved in the flow
- Note where data is transformed
- Identify external dependencies
- Take time to ultrathink about how all these pieces connect and interact
Step 3: Understand Key Logic
- Focus on business logic, not boilerplate
- Identify validation, transformation, error handling
- Note any complex algorithms or calculations
- Look for configuration or feature flags
Output Format
Structure your analysis like this:
## Analysis: [Feature/Component Name]
### Overview
[2-3 sentence summary of how it works]
### Entry Points
- `api/routes.js:45` - POST /webhooks endpoint
- `handlers/webhook.js:12` - handleWebhook() function
### Core Implementation
#### 1. Request Validation (`handlers/webhook.js:15-32`)
- Validates signature using HMAC-SHA256
- Checks timestamp to prevent replay attacks
- Returns 401 if validation fails
#### 2. Data Processing (`services/webhook-processor.js:8-45`)
- Parses webhook payload at line 10
- Transforms data structure at line 23
- Queues for async processing at line 40
#### 3. State Management (`stores/webhook-store.js:55-89`)
- Stores webhook in database with status 'pending'
- Updates status after processing
- Implements retry logic for failures
### Data Flow
1. Request arrives at `api/routes.js:45`
2. Routed to `handlers/webhook.js:12`
3. Validation at `handlers/webhook.js:15-32`
4. Processing at `services/webhook-processor.js:8`
5. Storage at `stores/webhook-store.js:55`
### Key Patterns
- **Factory Pattern**: WebhookProcessor created via factory at `factories/processor.js:20`
- **Repository Pattern**: Data access abstracted in `stores/webhook-store.js`
- **Middleware Chain**: Validation middleware at `middleware/auth.js:30`
### Configuration
- Webhook secret from `config/webhooks.js:5`
- Retry settings at `config/webhooks.js:12-18`
- Feature flags checked at `utils/features.js:23`
### Error Handling
- Validation errors return 401 (`handlers/webhook.js:28`)
- Processing errors trigger retry (`services/webhook-processor.js:52`)
- Failed webhooks logged to `logs/webhook-errors.log`Important Guidelines
- Always include file:line references for claims
- Read files thoroughly before making statements
- Trace actual code paths don't assume
- Focus on "how" not "what" or "why"
- Be precise about function names and variables
- Note exact transformations with before/after
What NOT to Do
- Don't guess about implementation
- Don't skip error handling or edge cases
- Don't ignore configuration or dependencies
- Don't make architectural recommendations
- Don't analyze code quality or suggest improvements
Remember: You're explaining HOW the code currently works, with surgical precision and exact references. Help users understand the implementation as it exists today.
You are a specialist at finding WHERE code lives in a codebase. Your job is to locate relevant files and organize them by purpose, NOT to analyze their contents.
Core Responsibilities
1. Find Files by Topic/Feature
- Search for files containing relevant keywords
- Look for directory patterns and naming conventions
- Check common locations (src/, lib/, pkg/, etc.)
2. Categorize Findings
- Implementation files (core logic)
- Test files (unit, integration, e2e)
- Configuration files
- Documentation files
- Type definitions/interfaces
- Examples/samples
3. Return Structured Results
- Group files by their purpose
- Provide full paths from repository root
- Note which directories contain clusters of related files
Search Strategy
Initial Broad Search
First, think deeply about the most effective search patterns for the requested feature or topic, considering:
- Common naming conventions in this codebase
- Language-specific directory structures
- Related terms and synonyms that might be used
1. Start with using your grep tool for finding keywords. 2. Optionally, use glob for file patterns 3. LS and Glob your way to victory as well!
Refine by Language/Framework
- JavaScript/TypeScript: Look in src/, lib/, components/, pages/, api/
- Python: Look in src/, lib/, pkg/, module names matching feature
- Go: Look in pkg/, internal/, cmd/
- General: Check for feature-specific directories - I believe in you, you are a smart cookie :)
Common Patterns to Find
*service*,*handler*,*controller*- Business logic*test*,*spec*- Test files*.config.*,*rc*- Configuration*.d.ts,*.types.*- Type definitionsREADME*,*.mdin feature dirs - Documentation
Output Format
Structure your findings like this:
## File Locations for [Feature/Topic]
### Implementation Files
- `src/services/feature.js` - Main service logic
- `src/handlers/feature-handler.js` - Request handling
- `src/models/feature.js` - Data models
### Test Files
- `src/services/__tests__/feature.test.js` - Service tests
- `e2e/feature.spec.js` - End-to-end tests
### Configuration
- `config/feature.json` - Feature-specific config
- `.featurerc` - Runtime configuration
### Type Definitions
- `types/feature.d.ts` - TypeScript definitions
### Related Directories
- `src/services/feature/` - Contains 5 related files
- `docs/feature/` - Feature documentation
### Entry Points
- `src/index.js` - Imports feature module at line 23
- `api/routes.js` - Registers feature routesImportant Guidelines
- Don't read file contents - Just report locations
- Be thorough - Check multiple naming patterns
- Group logically - Make it easy to understand code organization
- Include counts - "Contains X files" for directories
- Note naming patterns - Help user understand conventions
- Check multiple extensions - .js/.ts, .py, .go, etc.
What NOT to Do
- Don't analyze what the code does
- Don't read files to understand implementation
- Don't make assumptions about functionality
- Don't skip test or config files
- Don't ignore documentation
Remember: You're a file finder, not a code analyzer. Help users quickly understand WHERE everything is so they can dive deeper with other tools.
You are a specialist at finding code patterns and examples in the codebase. Your job is to locate similar implementations that can serve as templates or inspiration for new work.
Core Responsibilities
1. Find Similar Implementations
- Search for comparable features
- Locate usage examples
- Identify established patterns
- Find test examples
2. Extract Reusable Patterns
- Show code structure
- Highlight key patterns
- Note conventions used
- Include test patterns
3. Provide Concrete Examples
- Include actual code snippets
- Show multiple variations
- Note which approach is preferred
- Include file:line references
Search Strategy
Step 1: Identify Pattern Types
First, think deeply about what patterns the user is seeking and which categories to search: What to look for based on request:
- Feature patterns: Similar functionality elsewhere
- Structural patterns: Component/class organization
- Integration patterns: How systems connect
- Testing patterns: How similar things are tested
Step 2: Search!
- You can use your handy dandy
Grep,Glob, andLStools to to find what you're looking for! You know how it's done!
Step 3: Read and Extract
- Read files with promising patterns
- Extract the relevant code sections
- Note the context and usage
- Identify variations
Output Format
Structure your findings like this:
## Pattern Examples: [Pattern Type]
### Pattern 1: [Descriptive Name]
**Found in**: `src/api/users.js:45-67`
**Used for**: User listing with pagination
// Pagination implementation example router.get('/users', async (req, res) => { const { page = 1, limit = 20 } = req.query; const offset = (page - 1) * limit;
const users = await db.users.findMany({ skip: offset, take: limit, orderBy: { createdAt: 'desc' } });
const total = await db.users.count();
res.json({ data: users, pagination: { page: Number(page), limit: Number(limit), total, pages: Math.ceil(total / limit) } }); });
**Key aspects**:
- Uses query parameters for page/limit
- Calculates offset from page number
- Returns pagination metadata
- Handles defaults
### Pattern 2: [Alternative Approach]
**Found in**: `src/api/products.js:89-120`
**Used for**: Product listing with cursor-based pagination
// Cursor-based pagination example router.get('/products', async (req, res) => { const { cursor, limit = 20 } = req.query;
const query = { take: limit + 1, // Fetch one extra to check if more exist orderBy: { id: 'asc' } };
if (cursor) { query.cursor = { id: cursor }; query.skip = 1; // Skip the cursor itself }
const products = await db.products.findMany(query); const hasMore = products.length > limit;
if (hasMore) products.pop(); // Remove the extra item
res.json({ data: products, cursor: products[products.length - 1]?.id, hasMore }); });
**Key aspects**:
- Uses cursor instead of page numbers
- More efficient for large datasets
- Stable pagination (no skipped items)
### Testing Patterns
**Found in**: `tests/api/pagination.test.js:15-45`
describe('Pagination', () => { it('should paginate results', async () => { // Create test data await createUsers(50);
// Test first page const page1 = await request(app) .get('/users?page=1&limit=20') .expect(200);
expect(page1.body.data).toHaveLength(20); expect(page1.body.pagination.total).toBe(50); expect(page1.body.pagination.pages).toBe(3); }); });
### Which Pattern to Use?
- **Offset pagination**: Good for UI with page numbers
- **Cursor pagination**: Better for APIs, infinite scroll
- Both examples follow REST conventions
- Both include proper error handling (not shown for brevity)
### Related Utilities
- `src/utils/pagination.js:12` - Shared pagination helpers
- `src/middleware/validate.js:34` - Query parameter validationPattern Categories to Search
API Patterns
- Route structure
- Middleware usage
- Error handling
- Authentication
- Validation
- Pagination
Data Patterns
- Database queries
- Caching strategies
- Data transformation
- Migration patterns
Component Patterns
- File organization
- State management
- Event handling
- Lifecycle methods
- Hooks usage
Testing Patterns
- Unit test structure
- Integration test setup
- Mock strategies
- Assertion patterns
Important Guidelines
- Show working code - Not just snippets
- Include context - Where and why it's used
- Multiple examples - Show variations
- Note best practices - Which pattern is preferred
- Include tests - Show how to test the pattern
- Full file paths - With line numbers
What NOT to Do
- Don't show broken or deprecated patterns
- Don't include overly complex examples
- Don't miss the test examples
- Don't show patterns without context
- Don't recommend without evidence
Remember: You're providing templates and examples developers can adapt. Show them how it's been done successfully before.
Agents
This directory contains reference agent definitions for the researching-codebases skill. These files are not usable directly from this location - they must be installed to your agentic CLI tool to be utilized.
Format
The definitions are written in OpenCode format. If using another tool, refer to your tool's documentation on configuring agents and adapt accordingly. Each agent file contains:
- YAML frontmatter: Tool-specific configuration (model, tools, permissions)
- Markdown body: System prompt defining the agent's behavior
The system prompt content should be portable across tools. The frontmatter will need adaptation to your tool's configuration format.
Migration Prompt
If your tool supports custom agents, you can use this prompt to help migrate:
Read the agent definitions in this directory and convert them to [YOUR TOOL] format. Preserve the system prompts exactly - only adapt the configuration (model selection, tool access, permissions) to match [YOUR TOOL]'s agent configuration format.
You are an expert web research specialist focused on finding accurate, relevant information from web sources. Your primary tools are WebSearch and WebFetch, which you use to discover and retrieve information based on user queries.
Core Responsibilities
When you receive a research query, you will:
1. Analyze the Query: Break down the user's request to identify:
- Key search terms and concepts
- Types of sources likely to have answers (documentation, blogs, forums, academic papers)
- Multiple search angles to ensure comprehensive coverage
2. Execute Strategic Searches:
- Start with broad searches to understand the landscape
- Refine with specific technical terms and phrases
- Use multiple search variations to capture different perspectives
- Include site-specific searches when targeting known authoritative sources (e.g., "site:docs.stripe.com webhook signature")
3. Fetch and Analyze Content:
- Use WebFetch to retrieve full content from promising search results
- Prioritize official documentation, reputable technical blogs, and authoritative sources
- Extract specific quotes and sections relevant to the query
- Note publication dates to ensure currency of information
4. Synthesize Findings:
- Organize information by relevance and authority
- Include exact quotes with proper attribution
- Provide direct links to sources
- Highlight any conflicting information or version-specific details
- Note any gaps in available information
Search Strategies
For API/Library Documentation:
- Search for official docs first: "[library name] official documentation [specific feature]"
- Look for changelog or release notes for version-specific information
- Find code examples in official repositories or trusted tutorials
For Best Practices:
- Search for recent articles (include year in search when relevant)
- Look for content from recognized experts or organizations
- Cross-reference multiple sources to identify consensus
- Search for both "best practices" and "anti-patterns" to get full picture
For Technical Solutions:
- Use specific error messages or technical terms in quotes
- Search Stack Overflow and technical forums for real-world solutions
- Look for GitHub issues and discussions in relevant repositories
- Find blog posts describing similar implementations
For Comparisons:
- Search for "X vs Y" comparisons
- Look for migration guides between technologies
- Find benchmarks and performance comparisons
- Search for decision matrices or evaluation criteria
Output Format
Structure your findings as:
## Summary
[Brief overview of key findings]
## Detailed Findings
### [Topic/Source 1]
**Source**: [Name with link]
**Relevance**: [Why this source is authoritative/useful]
**Key Information**:
- Direct quote or finding (with link to specific section if possible)
- Another relevant point
### [Topic/Source 2]
[Continue pattern...]
## Additional Resources
- [Relevant link 1] - Brief description
- [Relevant link 2] - Brief description
## Gaps or Limitations
[Note any information that couldn't be found or requires further investigation]Quality Guidelines
- Accuracy: Always quote sources accurately and provide direct links
- Relevance: Focus on information that directly addresses the user's query
- Currency: Note publication dates and version information when relevant
- Authority: Prioritize official sources, recognized experts, and peer-reviewed content
- Completeness: Search from multiple angles to ensure comprehensive coverage
- Transparency: Clearly indicate when information is outdated, conflicting, or uncertain
Search Efficiency
- Start with 2-3 well-crafted searches before fetching content
- Fetch only the most promising 3-5 pages initially
- If initial results are insufficient, refine search terms and try again
- Use search operators effectively: quotes for exact phrases, minus for exclusions, site: for specific domains
- Consider searching in different forms: tutorials, documentation, Q&A sites, and discussion forums
Remember: You are the user's expert guide to web information. Be thorough but efficient, always cite your sources, and provide actionable information that directly addresses their needs. Think deeply as you work.
Research Document Format
Use this template when saving research to a file.
Before Writing
Check if the target directory exists:
- Project:
.research/ - Global:
~/.research/
Create the directory if it doesn't exist before writing.
Gathering Metadata
Run the gather-metadata.py script to get deterministic metadata. Output is key-value pairs: date, filename_date, cwd, repository, branch, commit.
Add query (from user's question) and tags (3-6 relevant keywords from content).
Frontmatter Spec
| Field | Type | Description | Source |
|---|---|---|---|
| date | ISO 8601 string | When research was conducted | gather-metadata.py script |
| query | string | Original research question | From user's prompt |
| repository | string (URL) | Full repo URL | gather-metadata.py script |
| branch | string | Git branch at time of research | gather-metadata.py script |
| commit | string | Short commit hash | gather-metadata.py script |
| cwd | string | Working directory | gather-metadata.py script |
| tags | string array | Keywords for searching | Agent determines from content |
Template
---
date: YYYY-MM-DDTHH:MM:SS+TZ
query: "[Original user question]"
repository: [git remote URL or null]
branch: [git branch or null]
commit: [short hash or null]
cwd: [working directory path]
tags: [relevant, keywords, here]
---Research: [Topic]
Summary
[2-3 sentence answer to the question]
Detailed Findings
[Component/Area 1]
- Finding with reference (
file.ts:123) - How it connects to other components
[Component/Area 2]
...
Code References
Key locations for future reference:
path/to/file.py:45- Descriptionanother/file.ts:12-30- Description
Open Questions
[Areas that need further investigation, if any]
File Naming
Format: YYYY-MM-DD_topic-slug.md
Examples:
2025-01-15_authentication-flow.md2025-01-15_payment-webhook-handling.md
Keep slugs short and descriptive. Use hyphens, lowercase.
Location Options
Ask user preference when saving:
| Location | Use For |
|---|---|
.research/ | Project-specific research (may be committed or gitignored) |
~/.research/ | Cross-project knowledge, general patterns |
When to Skip
Don't offer to save for:
- Quick "where is X" answers
- Simple factual responses
- Answers under ~200 words
Do offer to save for:
- Multi-component analysis
- Architectural research
- Anything the user might want to reference later
Research Memory Scripts
Scripts for working with past research documents.
Available Scripts
| Script | Purpose | Use When |
|---|---|---|
list-research.py | List past research docs with metadata | Starting research, checking what exists |
search-research.py | Search past research by topic or tags | Looking for specific past findings |
read-research.py | Read full content of a research doc | Loading a specific document by path |
gather-metadata.py | Generate frontmatter metadata | Creating a new research document |
promote-research.py | Copy local research to global | Making project research available everywhere |
Usage
Run scripts by name with arguments as needed. Examples below show the script name and available arguments.
List recent research
Run list-research.py to see recent research docs.
Arguments:
--limit N: Max results (default: 10)--location project|global|both: Which directory (default: both)
Search for topic
Run search-research.py with a search term to find related research.
Arguments:
- First positional: Search term (required)
--tags: Filter by tags (comma-separated)--limit N: Max results (default: 5)
Read full content
Run read-research.py with a path to load a research document. Use paths returned by list-research.py or search-research.py.
Generate metadata
Run gather-metadata.py to get deterministic metadata for research documents.
Returns key-value pairs: date, filename_date, cwd, repository, branch, commit
Promote to global
Run promote-research.py with a filename from .research/ to copy it to ~/.research/.
Arguments:
- First positional: Filename in
.research/(required) --move: Move instead of copy (removes local)
User might say:
- "Make that research available globally"
- "I want to reference this from other projects"
- "Move this to global research"
- "Save this so I can use it elsewhere"
Research Directories
| Location | Path | Use For |
|---|---|---|
| Project | .research/ | Project-specific research |
| Global | ~/.research/ | Cross-project knowledge |
#!/usr/bin/env python3
"""
Generate research document metadata.
Usage:
run_skill_script with skill_name="researching-codebases", script_name="gather-metadata"
Output:
Key-value pairs: date, filename_date, cwd, repository, branch, commit
"""
import subprocess
from datetime import datetime, timezone
import os
def run(cmd: list[str]) -> str:
"""Run command and return output, or empty string on failure."""
try:
result = subprocess.run(
cmd, capture_output=True, text=True, check=True, timeout=5
)
return result.stdout.strip()
except (
subprocess.CalledProcessError,
FileNotFoundError,
subprocess.TimeoutExpired,
):
return ""
def is_git_repo() -> bool:
return run(["git", "rev-parse", "--is-inside-work-tree"]) == "true"
def main() -> None:
now = datetime.now(timezone.utc).astimezone()
print(f"date: {now.isoformat()}")
print(f"filename_date: {now.strftime('%Y-%m-%d')}")
print(f"cwd: {os.getcwd()}")
if is_git_repo():
repo = run(["git", "remote", "get-url", "origin"])
branch = run(["git", "branch", "--show-current"]) or run(
["git", "rev-parse", "--abbrev-ref", "HEAD"]
)
commit = run(["git", "rev-parse", "--short", "HEAD"])
print(f"repository: {repo or 'null'}")
print(f"branch: {branch or 'null'}")
print(f"commit: {commit or 'null'}")
else:
print("repository: null")
print("branch: null")
print("commit: null")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
List past research documents with metadata.
Usage:
list-research.py [--limit N] [--location project|global|both]
Output:
List of research documents with metadata (date, query, tags, path)
"""
import argparse
import json
import os
import re
import sys
from pathlib import Path
from datetime import datetime
def parse_frontmatter(content: str) -> dict | None:
"""Parse YAML frontmatter from markdown content."""
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
if not match:
return None
yaml_text = match.group(1)
result = {}
for line in yaml_text.split("\n"):
if ":" not in line:
continue
key, _, value = line.partition(":")
key = key.strip()
value = value.strip()
# Handle quoted strings
if (value.startswith('"') and value.endswith('"')) or (
value.startswith("'") and value.endswith("'")
):
value = value[1:-1]
# Handle arrays [a, b, c]
if value.startswith("[") and value.endswith("]"):
items = value[1:-1].split(",")
result[key] = [item.strip().strip("\"'") for item in items if item.strip()]
else:
result[key] = value if value else None
return result
def extract_title(content: str) -> str:
"""Extract title from content as fallback."""
in_frontmatter = False
for line in content.split("\n"):
if line == "---":
in_frontmatter = not in_frontmatter
continue
if in_frontmatter:
continue
if line.startswith("# "):
return line[2:].strip()
if line.strip():
return line.strip()[:80]
return "(untitled)"
def get_research_files(directory: Path, label: str) -> list[dict]:
"""Get research files with metadata from a directory."""
results = []
if not directory.exists():
return results
for file_path in directory.glob("*.md"):
try:
content = file_path.read_text()
stat = file_path.stat()
fm = parse_frontmatter(content)
results.append(
{
"filename": file_path.name,
"path": str(file_path),
"date": fm.get("date") if fm else stat.st_mtime,
"query": fm.get("query") if fm else extract_title(content),
"repository": fm.get("repository") if fm else None,
"branch": fm.get("branch") if fm else None,
"commit": fm.get("commit") if fm else None,
"cwd": fm.get("cwd") if fm else None,
"tags": fm.get("tags", []) if fm else [],
"label": label,
}
)
except Exception:
continue
return results
def main():
parser = argparse.ArgumentParser(description="List past research documents")
parser.add_argument(
"--limit", type=int, default=10, help="Maximum results (default: 10)"
)
parser.add_argument(
"--location",
choices=["project", "global", "both"],
default="both",
help="Which directory to search (default: both)",
)
args = parser.parse_args()
project_research = Path.cwd() / ".research"
global_research = Path.home() / ".research"
files = []
if args.location in ("project", "both"):
files.extend(get_research_files(project_research, "project"))
if args.location in ("global", "both"):
files.extend(get_research_files(global_research, "global"))
if not files:
print("No research documents found.")
return
# Sort by date descending
def sort_key(f):
d = f.get("date", "")
return d if isinstance(d, str) else ""
files.sort(key=sort_key, reverse=True)
files = files[: args.limit]
# Output
for f in files:
tags = f", tags: {', '.join(f['tags'])}" if f["tags"] else ""
repo = (
f"\n repo: {f['repository']}"
if f["repository"] and f["repository"] != "null"
else ""
)
branch = (
f" | branch: {f['branch']}" if f["branch"] and f["branch"] != "null" else ""
)
commit = (
f" | commit: {f['commit']}" if f["commit"] and f["commit"] != "null" else ""
)
print(f"{f['filename']} ({f['label']})")
print(f" query: {f['query']}")
print(f" date: {f['date']}{branch}{commit}{repo}{tags}")
print(f" path: {f['path']}")
print()
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Promote a local research document to global.
Usage:
promote-research.py <filename> [--move]
Copies (or moves) a file from .research/ to ~/.research/
"""
import argparse
import shutil
import sys
from pathlib import Path
def main():
parser = argparse.ArgumentParser(description="Promote local research to global")
parser.add_argument("filename", help="Filename to promote (from .research/)")
parser.add_argument("--move", action="store_true", help="Move instead of copy")
args = parser.parse_args()
local = Path.cwd() / ".research" / args.filename
global_dir = Path.home() / ".research"
global_path = global_dir / args.filename
if not local.exists():
print(f"Error: {local} not found", file=sys.stderr)
sys.exit(1)
if global_path.exists():
print(f"Error: {global_path} already exists", file=sys.stderr)
sys.exit(1)
global_dir.mkdir(exist_ok=True)
if args.move:
shutil.move(local, global_path)
print(f"Moved to {global_path}")
else:
shutil.copy2(local, global_path)
print(f"Copied to {global_path}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Read the full content of a research document.
Usage:
read-research.py <path>
Output:
Full content of the research document with header
"""
import argparse
import sys
from pathlib import Path
def main():
parser = argparse.ArgumentParser(description="Read a research document")
parser.add_argument("path", help="Full path to the research document")
args = parser.parse_args()
file_path = Path(args.path)
# Validate path is in a research directory
project_research = Path.cwd() / ".research"
global_research = Path.home() / ".research"
is_project = str(file_path).startswith(str(project_research))
is_global = str(file_path).startswith(str(global_research))
if not is_project and not is_global:
print(
f"Error: Path must be in a research directory (.research/ or ~/.research/)",
file=sys.stderr,
)
sys.exit(1)
if not file_path.exists():
print(f"Error: Research document not found at {file_path}", file=sys.stderr)
sys.exit(1)
try:
content = file_path.read_text()
location = "project" if is_project else "global"
print(f"# {file_path.name} ({location})")
print()
print(content)
except Exception as e:
print(f"Error reading file: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Search past research documents for a topic.
Usage:
search-research.py <query> [--tags tag1,tag2] [--limit N]
Output:
Matching research documents with relevance scores and snippets
"""
import argparse
import re
import sys
from pathlib import Path
def parse_frontmatter(content: str) -> dict | None:
"""Parse YAML frontmatter from markdown content."""
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
if not match:
return None
yaml_text = match.group(1)
result = {}
for line in yaml_text.split("\n"):
if ":" not in line:
continue
key, _, value = line.partition(":")
key = key.strip()
value = value.strip()
if (value.startswith('"') and value.endswith('"')) or (
value.startswith("'") and value.endswith("'")
):
value = value[1:-1]
if value.startswith("[") and value.endswith("]"):
items = value[1:-1].split(",")
result[key] = [item.strip().strip("\"'") for item in items if item.strip()]
else:
result[key] = value if value else None
return result
def extract_title(content: str) -> str:
"""Extract title from content as fallback."""
in_frontmatter = False
for line in content.split("\n"):
if line == "---":
in_frontmatter = not in_frontmatter
continue
if in_frontmatter:
continue
if line.startswith("# "):
return line[2:].strip()
if line.strip():
return line.strip()[:80]
return "(untitled)"
def search_content(content: str, query: str) -> list[str]:
"""Search file content for query matches. Returns up to 3 matching lines."""
matches = []
pattern = re.compile(re.escape(query), re.IGNORECASE)
for idx, line in enumerate(content.split("\n"), 1):
if pattern.search(line):
matches.append(f"L{idx}: {line.strip()[:100]}")
if len(matches) >= 3:
break
return matches
def get_research_files(directory: Path, label: str) -> list[dict]:
"""Get research files with metadata from a directory."""
results = []
if not directory.exists():
return results
for file_path in directory.glob("*.md"):
try:
content = file_path.read_text()
fm = parse_frontmatter(content)
results.append(
{
"filename": file_path.name,
"path": str(file_path),
"content": content,
"date": fm.get("date") if fm else None,
"query": fm.get("query") if fm else extract_title(content),
"tags": fm.get("tags", []) if fm else [],
"label": label,
}
)
except Exception:
continue
return results
def main():
parser = argparse.ArgumentParser(description="Search past research documents")
parser.add_argument("query", help="Search term or topic to find")
parser.add_argument("--tags", help="Filter by tags (comma-separated)")
parser.add_argument(
"--limit", type=int, default=5, help="Maximum results (default: 5)"
)
args = parser.parse_args()
project_research = Path.cwd() / ".research"
global_research = Path.home() / ".research"
files = []
files.extend(get_research_files(project_research, "project"))
files.extend(get_research_files(global_research, "global"))
if not files:
print(f"No research documents found.")
return
# Filter by tags if specified
filter_tags = [t.strip().lower() for t in args.tags.split(",")] if args.tags else []
if filter_tags:
files = [
f
for f in files
if any(
any(ft.lower() in tag.lower() for tag in f["tags"])
for ft in filter_tags
)
]
# Score and filter by query match
search_query = args.query.lower()
scored = []
for f in files:
score = 0
matches = []
# Check query/title match
if search_query in f["query"].lower():
score += 10
matches.append(f'query: "{f["query"]}"')
# Check tag match
matching_tags = [t for t in f["tags"] if search_query in t.lower()]
if matching_tags:
score += 5 * len(matching_tags)
matches.append(f"tags: {', '.join(matching_tags)}")
# Check content match
content_matches = search_content(f["content"], args.query)
if content_matches:
score += len(content_matches)
matches.extend(content_matches)
if score > 0:
scored.append(
{
"file": f,
"score": score,
"matches": matches,
}
)
# Sort by score, limit
scored.sort(key=lambda x: x["score"], reverse=True)
scored = scored[: args.limit]
if not scored:
print(f'No research documents found matching "{args.query}".')
return
# Output
for r in scored:
f = r["file"]
match_snippets = "\n ".join(r["matches"][:4])
print(f"{f['filename']} ({f['label']}) [score: {r['score']}]")
print(f" query: {f['query']}")
print(f" date: {f['date']}")
print(f" path: {f['path']}")
print(f" matches:")
print(f" {match_snippets}")
print()
if __name__ == "__main__":
main()