tech debt review
This commit is contained in:
parent
053a70c602
commit
14abec9735
|
|
@ -4,6 +4,8 @@ import glob
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
|
from rich.panel import Panel
|
||||||
|
from rich.console import Console
|
||||||
from langchain_anthropic import ChatAnthropic
|
from langchain_anthropic import ChatAnthropic
|
||||||
from langchain_core.messages import HumanMessage
|
from langchain_core.messages import HumanMessage
|
||||||
from langgraph.checkpoint.memory import MemorySaver
|
from langgraph.checkpoint.memory import MemorySaver
|
||||||
|
|
@ -16,6 +18,7 @@ from ra_aid.tools import (
|
||||||
request_implementation, read_file_tool, emit_research_subtask,
|
request_implementation, read_file_tool, emit_research_subtask,
|
||||||
fuzzy_find_project_files, ripgrep_search, list_directory_tree
|
fuzzy_find_project_files, ripgrep_search, list_directory_tree
|
||||||
)
|
)
|
||||||
|
from ra_aid.tools.note_tech_debt import BORDER_STYLE, TECH_DEBT_NOTE_EMOJI
|
||||||
from ra_aid.tools.memory import _global_memory
|
from ra_aid.tools.memory import _global_memory
|
||||||
from ra_aid import print_agent_output, print_stage_header, print_task_header
|
from ra_aid import print_agent_output, print_stage_header, print_task_header
|
||||||
from ra_aid.tools.programmer import related_files
|
from ra_aid.tools.programmer import related_files
|
||||||
|
|
@ -40,7 +43,6 @@ Examples:
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-m', '--message',
|
'-m', '--message',
|
||||||
type=str,
|
type=str,
|
||||||
required=True,
|
|
||||||
help='The task or query to be executed by the agent'
|
help='The task or query to be executed by the agent'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|
@ -53,8 +55,21 @@ Examples:
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Skip interactive approval for shell commands'
|
help='Skip interactive approval for shell commands'
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--review-tech-debt',
|
||||||
|
action='store_true',
|
||||||
|
help='Review existing technical debt notes'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--clear-tech-debt',
|
||||||
|
action='store_true',
|
||||||
|
help='Clear all technical debt notes'
|
||||||
|
)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
# Create console instance
|
||||||
|
console = Console()
|
||||||
|
|
||||||
# Create the base model
|
# Create the base model
|
||||||
model = ChatAnthropic(model_name="claude-3-5-sonnet-20241022")
|
model = ChatAnthropic(model_name="claude-3-5-sonnet-20241022")
|
||||||
|
|
||||||
|
|
@ -217,6 +232,23 @@ def run_research_subtasks(base_task: str, config: dict):
|
||||||
print(f"Encountered Anthropic Internal Server Error: {e}. Retrying...")
|
print(f"Encountered Anthropic Internal Server Error: {e}. Retrying...")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
def check_tech_debt_notes() -> bool:
|
||||||
|
"""Check if any tech debt notes exist.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if tech debt notes exist, False otherwise
|
||||||
|
"""
|
||||||
|
tech_debt_dir = '.ra-aid/tech-debt'
|
||||||
|
tech_debt_files = glob.glob(os.path.join(tech_debt_dir, '*.md'))
|
||||||
|
return len(tech_debt_files) > 0
|
||||||
|
|
||||||
|
def clear_tech_debt_notes() -> None:
|
||||||
|
"""Clear all technical debt notes."""
|
||||||
|
tech_debt_dir = '.ra-aid/tech-debt'
|
||||||
|
if os.path.exists(tech_debt_dir):
|
||||||
|
shutil.rmtree(tech_debt_dir)
|
||||||
|
os.makedirs(tech_debt_dir) # Recreate empty directory
|
||||||
|
|
||||||
def validate_environment():
|
def validate_environment():
|
||||||
"""Validate required environment variables and dependencies."""
|
"""Validate required environment variables and dependencies."""
|
||||||
missing = []
|
missing = []
|
||||||
|
|
@ -237,12 +269,17 @@ def validate_environment():
|
||||||
print(f"- {error}", file=sys.stderr)
|
print(f"- {error}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def review_tech_debt(model) -> None:
|
def review_tech_debt() -> None:
|
||||||
"""Review any technical debt notes collected during execution."""
|
"""Review any technical debt notes collected during execution."""
|
||||||
tech_debt_dir = '.ra-aid/tech-debt'
|
tech_debt_dir = '.ra-aid/tech-debt'
|
||||||
tech_debt_files = glob.glob(os.path.join(tech_debt_dir, '*.md'))
|
tech_debt_files = glob.glob(os.path.join(tech_debt_dir, '*.md'))
|
||||||
|
|
||||||
if not tech_debt_files:
|
if not tech_debt_files:
|
||||||
|
console.print(Panel(
|
||||||
|
"[bold]No technical debt notes found.[/]",
|
||||||
|
border_style=BORDER_STYLE,
|
||||||
|
title=f"{TECH_DEBT_NOTE_EMOJI} Tech Debt"
|
||||||
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
print_stage_header("Technical Debt Review")
|
print_stage_header("Technical Debt Review")
|
||||||
|
|
@ -252,8 +289,26 @@ def review_tech_debt(model) -> None:
|
||||||
for file_path in tech_debt_files:
|
for file_path in tech_debt_files:
|
||||||
with open(file_path, 'r') as file:
|
with open(file_path, 'r') as file:
|
||||||
content = file.read()
|
content = file.read()
|
||||||
|
tech_debt_contents.append("\n")
|
||||||
tech_debt_contents.append(content)
|
tech_debt_contents.append(content)
|
||||||
|
|
||||||
|
# Create dedicated memory and agent for tech debt review
|
||||||
|
tech_debt_memory = MemorySaver()
|
||||||
|
|
||||||
|
# Define tools for tech debt review agent - minimal set needed for analysis
|
||||||
|
# tech_debt_tools = [
|
||||||
|
# emit_expert_context, ask_expert, read_file_tool,
|
||||||
|
# list_directory_tree, fuzzy_find_project_files, ripgrep_search,
|
||||||
|
# ]
|
||||||
|
tech_debt_tools = []
|
||||||
|
|
||||||
|
# Create fresh agent for tech debt review
|
||||||
|
tech_debt_agent = create_react_agent(
|
||||||
|
model,
|
||||||
|
tech_debt_tools,
|
||||||
|
checkpointer=tech_debt_memory
|
||||||
|
)
|
||||||
|
|
||||||
# Analyze the tech debt notes
|
# Analyze the tech debt notes
|
||||||
prompt = f"""Review the following technical debt notes collected during program execution:
|
prompt = f"""Review the following technical debt notes collected during program execution:
|
||||||
|
|
||||||
|
|
@ -264,11 +319,15 @@ Please provide a brief, focused analysis:
|
||||||
2. Highlight high-impact items
|
2. Highlight high-impact items
|
||||||
3. Suggest a rough priority order
|
3. Suggest a rough priority order
|
||||||
Keep the response concise and actionable.
|
Keep the response concise and actionable.
|
||||||
|
|
||||||
|
Remember that the user doesn't know the note ids. You'll have to reiterate the key information of the issues in whole.
|
||||||
|
|
||||||
|
We want to prioritize items that are the highest impact relative to the level of effort required to fix them.
|
||||||
"""
|
"""
|
||||||
# Stream the analysis
|
# Stream and print the analysis
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
for chunk in model.stream(
|
for chunk in tech_debt_agent.stream(
|
||||||
{"messages": [HumanMessage(content=prompt)]},
|
{"messages": [HumanMessage(content=prompt)]},
|
||||||
{"configurable": {"thread_id": "tech-debt"}, "recursion_limit": 100}
|
{"configurable": {"thread_id": "tech-debt"}, "recursion_limit": 100}
|
||||||
):
|
):
|
||||||
|
|
@ -278,88 +337,128 @@ Keep the response concise and actionable.
|
||||||
print(f"Encountered Anthropic Internal Server Error: {e}. Retrying...")
|
print(f"Encountered Anthropic Internal Server Error: {e}. Retrying...")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Exit immediately after tech debt review
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Main entry point for the ra-aid command line tool."""
|
"""Main entry point for the ra-aid command line tool."""
|
||||||
try:
|
try:
|
||||||
validate_environment()
|
try:
|
||||||
args = parse_arguments()
|
validate_environment()
|
||||||
base_task = args.message
|
args = parse_arguments()
|
||||||
config = {
|
|
||||||
"configurable": {
|
|
||||||
"thread_id": "abc123"
|
|
||||||
},
|
|
||||||
"recursion_limit": 100,
|
|
||||||
"research_only": args.research_only,
|
|
||||||
"cowboy_mode": args.cowboy_mode
|
|
||||||
}
|
|
||||||
|
|
||||||
# Store config in global memory for access by is_informational_query
|
# Validate message is provided when needed
|
||||||
_global_memory['config'] = config
|
if not (args.message or args.review_tech_debt or args.clear_tech_debt):
|
||||||
|
print("Error: --message is required unless reviewing or clearing tech debt", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Run research stage
|
# Handle clear tech debt request early
|
||||||
print_stage_header("Research Stage")
|
if args.clear_tech_debt:
|
||||||
research_prompt = f"""User query: {base_task} --keep it simple
|
clear_tech_debt_notes()
|
||||||
|
console.print(Panel(
|
||||||
|
"[bold]Technical debt notes cleared.[/]",
|
||||||
|
border_style="bright_blue",
|
||||||
|
title="📝 Tech Debt"
|
||||||
|
))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle tech debt review request
|
||||||
|
if args.review_tech_debt:
|
||||||
|
if check_tech_debt_notes():
|
||||||
|
review_tech_debt()
|
||||||
|
else:
|
||||||
|
console.print(Panel(
|
||||||
|
"[bold]No technical debt notes found.[/]",
|
||||||
|
border_style="bright_blue",
|
||||||
|
title="📝 Tech Debt"
|
||||||
|
))
|
||||||
|
return
|
||||||
|
base_task = args.message
|
||||||
|
config = {
|
||||||
|
"configurable": {
|
||||||
|
"thread_id": "abc123"
|
||||||
|
},
|
||||||
|
"recursion_limit": 100,
|
||||||
|
"research_only": args.research_only,
|
||||||
|
"cowboy_mode": args.cowboy_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
# Store config in global memory for access by is_informational_query
|
||||||
|
_global_memory['config'] = config
|
||||||
|
|
||||||
|
# Run research stage
|
||||||
|
print_stage_header("Research Stage")
|
||||||
|
research_prompt = f"""User query: {base_task} --keep it simple
|
||||||
|
|
||||||
{RESEARCH_PROMPT}
|
{RESEARCH_PROMPT}
|
||||||
|
|
||||||
Be very thorough in your research and emit lots of snippets, key facts. If you take more than a few steps, be eager to emit research subtasks.{'' if args.research_only else ' Only request implementation if the user explicitly asked for changes to be made.'}"""
|
Be very thorough in your research and emit lots of snippets, key facts. If you take more than a few steps, be eager to emit research subtasks.{'' if args.research_only else ' Only request implementation if the user explicitly asked for changes to be made.'}"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
for chunk in research_agent.stream(
|
for chunk in research_agent.stream(
|
||||||
{"messages": [HumanMessage(content=research_prompt)]},
|
{"messages": [HumanMessage(content=research_prompt)]},
|
||||||
config
|
config
|
||||||
):
|
):
|
||||||
print_agent_output(chunk)
|
print_agent_output(chunk)
|
||||||
break
|
break
|
||||||
except ChatAnthropic.InternalServerError as e:
|
except ChatAnthropic.InternalServerError as e:
|
||||||
print(f"Encountered Anthropic Internal Server Error: {e}. Retrying...")
|
print(f"Encountered Anthropic Internal Server Error: {e}. Retrying...")
|
||||||
continue
|
continue
|
||||||
except TaskCompletedException as e:
|
except TaskCompletedException as e:
|
||||||
print_stage_header("Task Completed")
|
print_stage_header("Task Completed")
|
||||||
raise # Re-raise to be caught by outer handler
|
raise # Re-raise to be caught by outer handler
|
||||||
|
|
||||||
# Run any research subtasks
|
# Run any research subtasks
|
||||||
run_research_subtasks(base_task, config)
|
run_research_subtasks(base_task, config)
|
||||||
|
|
||||||
# For informational queries, summarize findings
|
# For informational queries, summarize findings
|
||||||
if is_informational_query():
|
if is_informational_query():
|
||||||
summarize_research_findings(base_task, config)
|
summarize_research_findings(base_task, config)
|
||||||
else:
|
else:
|
||||||
# Only proceed with planning and implementation if not an informational query
|
# Only proceed with planning and implementation if not an informational query
|
||||||
print_stage_header("Planning Stage")
|
print_stage_header("Planning Stage")
|
||||||
planning_prompt = PLANNING_PROMPT.format(
|
planning_prompt = PLANNING_PROMPT.format(
|
||||||
research_notes=get_memory_value('research_notes'),
|
research_notes=get_memory_value('research_notes'),
|
||||||
key_facts=get_memory_value('key_facts'),
|
key_facts=get_memory_value('key_facts'),
|
||||||
key_snippets=get_memory_value('key_snippets'),
|
key_snippets=get_memory_value('key_snippets'),
|
||||||
base_task=base_task,
|
base_task=base_task,
|
||||||
related_files="\n".join(related_files)
|
related_files="\n".join(related_files)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run planning agent
|
# Run planning agent
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
for chunk in planning_agent.stream(
|
for chunk in planning_agent.stream(
|
||||||
{"messages": [HumanMessage(content=planning_prompt)]},
|
{"messages": [HumanMessage(content=planning_prompt)]},
|
||||||
config
|
config
|
||||||
):
|
):
|
||||||
print_agent_output(chunk)
|
print_agent_output(chunk)
|
||||||
break
|
break
|
||||||
except ChatAnthropic.InternalServerError as e:
|
except ChatAnthropic.InternalServerError as e:
|
||||||
print(f"Encountered Anthropic Internal Server Error: {e}. Retrying...")
|
print(f"Encountered Anthropic Internal Server Error: {e}. Retrying...")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Run implementation stage with task-specific agents
|
# Run implementation stage with task-specific agents
|
||||||
run_implementation_stage(
|
run_implementation_stage(
|
||||||
base_task,
|
base_task,
|
||||||
get_memory_value('tasks'),
|
get_memory_value('tasks'),
|
||||||
get_memory_value('plan'),
|
get_memory_value('plan'),
|
||||||
related_files
|
related_files
|
||||||
)
|
)
|
||||||
except TaskCompletedException:
|
except TaskCompletedException:
|
||||||
review_tech_debt(model)
|
sys.exit(0)
|
||||||
sys.exit(0)
|
finally:
|
||||||
|
# Show tech debt notification only when appropriate
|
||||||
|
if (check_tech_debt_notes() and
|
||||||
|
not getattr(args, 'review_tech_debt', False) and
|
||||||
|
not getattr(args, 'clear_tech_debt', False)):
|
||||||
|
console.print(Panel(
|
||||||
|
"[bold]Technical debt notes exist.[/]\n[dim italic]Use --review-tech-debt to review them.[/]",
|
||||||
|
border_style=BORDER_STYLE,
|
||||||
|
title=f"{TECH_DEBT_NOTE_EMOJI} Tech Debt"
|
||||||
|
))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ def emit_expert_context(context: str) -> str:
|
||||||
|
|
||||||
Err on the side of adding more context rather than less.
|
Err on the side of adding more context rather than less.
|
||||||
|
|
||||||
|
You must give the complete contents.
|
||||||
|
|
||||||
Expert context will be reset after the ask_expert tool is called.
|
Expert context will be reset after the ask_expert tool is called.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -29,9 +31,13 @@ def emit_expert_context(context: str) -> str:
|
||||||
Returns:
|
Returns:
|
||||||
Confirmation message
|
Confirmation message
|
||||||
"""
|
"""
|
||||||
global expert_context
|
|
||||||
expert_context.append(context)
|
expert_context.append(context)
|
||||||
return f"Added context: {context}"
|
|
||||||
|
# Create and display status panel
|
||||||
|
panel_content = f"Added expert context ({len(context)} characters)"
|
||||||
|
console.print(Panel(panel_content, title="Expert Context", border_style="blue"))
|
||||||
|
|
||||||
|
return f"Context added."
|
||||||
|
|
||||||
@tool("ask_expert")
|
@tool("ask_expert")
|
||||||
def ask_expert(question: str) -> str:
|
def ask_expert(question: str) -> str:
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,11 @@ from langchain_core.tools import tool
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
|
|
||||||
|
# Rich styling constants for tech debt UI
|
||||||
|
BORDER_STYLE = "bright_blue"
|
||||||
|
TECH_DEBT_NOTE_EMOJI = "📝"
|
||||||
|
TECH_DEBT_CLEANUP_EMOJI = "🧹"
|
||||||
|
|
||||||
MAX_NOTES = 10 # Maximum number of tech debt notes before cleanup warning
|
MAX_NOTES = 10 # Maximum number of tech debt notes before cleanup warning
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
@ -60,8 +65,8 @@ def note_tech_debt(
|
||||||
|
|
||||||
[dim italic]The cleanup agent will analyze note contents and suggest which ones to purge.[/dim italic]
|
[dim italic]The cleanup agent will analyze note contents and suggest which ones to purge.[/dim italic]
|
||||||
""",
|
""",
|
||||||
title="🧹 Tech Debt Cleanup",
|
title=f"{TECH_DEBT_CLEANUP_EMOJI} Tech Debt Cleanup",
|
||||||
border_style="bright_blue"
|
border_style=BORDER_STYLE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -83,8 +88,8 @@ def note_tech_debt(
|
||||||
console.print(
|
console.print(
|
||||||
Panel(
|
Panel(
|
||||||
f"Created Tech Debt Note #{next_num} at {note_path}",
|
f"Created Tech Debt Note #{next_num} at {note_path}",
|
||||||
title="📝 Tech Debt Note",
|
title=f"{TECH_DEBT_NOTE_EMOJI} Tech Debt Note",
|
||||||
border_style="bright_blue"
|
border_style=BORDER_STYLE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue