devloop 0.3.1__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {devloop-0.3.1 → devloop-0.3.3}/PKG-INFO +1 -1
  2. {devloop-0.3.1 → devloop-0.3.3}/pyproject.toml +1 -1
  3. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/main.py +85 -4
  4. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/templates/claude_commands/agent-summary.md +1 -1
  5. devloop-0.3.3/src/devloop/cli/templates/git_hooks/pre-commit +70 -0
  6. devloop-0.3.3/src/devloop/cli/templates/git_hooks/pre-commit-checks +72 -0
  7. devloop-0.3.3/src/devloop/cli/templates/git_hooks/pre-push +114 -0
  8. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/agent.py +29 -15
  9. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/auto_fix.py +151 -10
  10. devloop-0.3.3/src/devloop/core/backup_manager.py +409 -0
  11. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/config.py +93 -2
  12. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/manager.py +121 -3
  13. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/performance.py +114 -0
  14. {devloop-0.3.1 → devloop-0.3.3}/LICENSE +0 -0
  15. {devloop-0.3.1 → devloop-0.3.3}/README.md +0 -0
  16. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/__init__.py +0 -0
  17. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/__init__.py +0 -0
  18. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/agent_health_monitor.py +0 -0
  19. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/ci_monitor.py +0 -0
  20. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/code_rabbit.py +0 -0
  21. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/doc_lifecycle.py +0 -0
  22. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/echo.py +0 -0
  23. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/file_logger.py +0 -0
  24. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/formatter.py +0 -0
  25. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/git_commit_assistant.py +0 -0
  26. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/linter.py +0 -0
  27. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/performance_profiler.py +0 -0
  28. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/sandbox_helper.py +0 -0
  29. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/security_scanner.py +0 -0
  30. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/snyk.py +0 -0
  31. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/test_runner.py +0 -0
  32. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/agents/type_checker.py +0 -0
  33. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/__init__.py +0 -0
  34. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/commands/__init__.py +0 -0
  35. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/commands/custom_agents.py +0 -0
  36. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/commands/feedback.py +0 -0
  37. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/commands/summary.py +0 -0
  38. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/main_v1.py +0 -0
  39. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/pyodide_installer.py +0 -0
  40. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/templates/claude_commands/README.md +0 -0
  41. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/templates/claude_commands/devloop-findings.md +0 -0
  42. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/cli/templates/claude_commands/devloop-status.md +0 -0
  43. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/collectors/__init__.py +0 -0
  44. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/collectors/base.py +0 -0
  45. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/collectors/filesystem.py +0 -0
  46. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/collectors/git.py +0 -0
  47. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/collectors/manager.py +0 -0
  48. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/collectors/process.py +0 -0
  49. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/collectors/system.py +0 -0
  50. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/__init__.py +0 -0
  51. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/agent_template.py +0 -0
  52. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/amp_integration.py +0 -0
  53. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/context.py +0 -0
  54. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/context_store.py +0 -0
  55. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/contextual_feedback.py +0 -0
  56. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/custom_agent.py +0 -0
  57. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/debug_trace.py +0 -0
  58. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/event.py +0 -0
  59. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/event_store.py +0 -0
  60. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/feedback.py +0 -0
  61. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/learning.py +0 -0
  62. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/operational_health.py +0 -0
  63. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/proactive_feedback.py +0 -0
  64. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/summary_formatter.py +0 -0
  65. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/core/summary_generator.py +0 -0
  66. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/security/__init__.py +0 -0
  67. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/security/audit_logger.py +0 -0
  68. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/security/bubblewrap_sandbox.py +0 -0
  69. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/security/cgroups_helper.py +0 -0
  70. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/security/factory.py +0 -0
  71. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/security/no_sandbox.py +0 -0
  72. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/security/package.json +0 -0
  73. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/security/pyodide_runner.js +0 -0
  74. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/security/pyodide_sandbox.py +0 -0
  75. {devloop-0.3.1 → devloop-0.3.3}/src/devloop/security/sandbox.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devloop
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: Intelligent background agents for development workflow automation
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "devloop"
3
- version = "0.3.1"
3
+ version = "0.3.3"
4
4
  description = "Intelligent background agents for development workflow automation"
5
5
  authors = ["DevLoop Contributors <devloop@example.com>"]
6
6
  license = "MIT"
@@ -69,7 +69,7 @@ def amp_context():
69
69
  from pathlib import Path
70
70
 
71
71
  # Try to read the context index
72
- context_dir = Path(".devloop/context")
72
+ context_dir = Path.cwd() / ".devloop/context"
73
73
  index_file = context_dir / "index.json"
74
74
 
75
75
  if index_file.exists():
@@ -289,8 +289,13 @@ async def watch_async(path: Path, config_path: Path | None):
289
289
  console.print(f"[dim]Context store: {context_store.context_dir}[/dim]")
290
290
  console.print(f"[dim]Event store: {event_store.db_path}[/dim]")
291
291
 
292
- # Create agent manager with project directory
293
- agent_manager = AgentManager(event_bus, project_dir=path)
292
+ # Get global config for resource limits
293
+ global_config = config.get_global_config()
294
+
295
+ # Create agent manager with project directory and resource limits
296
+ agent_manager = AgentManager(
297
+ event_bus, project_dir=path, resource_limits=global_config.resource_limits
298
+ )
294
299
 
295
300
  # Create filesystem collector
296
301
  fs_config = {"watch_paths": [str(path)]}
@@ -558,10 +563,37 @@ This project uses background agents and Beads for task management.
558
563
  commands_copied.append(template_file.stem)
559
564
 
560
565
  if commands_copied:
561
- console.print(f"\n[green]✓[/green] Created Claude Code slash commands:")
566
+ console.print("\n[green]✓[/green] Created Claude Code slash commands:")
562
567
  for cmd in commands_copied:
563
568
  console.print(f" • /{cmd}")
564
569
 
570
+ # Install git hooks if this is a git repository
571
+ git_dir = path / ".git"
572
+ if git_dir.exists() and git_dir.is_dir():
573
+ hooks_template_dir = Path(__file__).parent / "templates" / "git_hooks"
574
+ hooks_dest_dir = git_dir / "hooks"
575
+
576
+ if hooks_template_dir.exists():
577
+ hooks_installed = []
578
+ for template_file in hooks_template_dir.iterdir():
579
+ if template_file.is_file():
580
+ dest_file = hooks_dest_dir / template_file.name
581
+
582
+ # Backup existing hook if present
583
+ if dest_file.exists():
584
+ backup_file = hooks_dest_dir / f"{template_file.name}.backup"
585
+ shutil.copy2(dest_file, backup_file)
586
+
587
+ # Install new hook
588
+ shutil.copy2(template_file, dest_file)
589
+ dest_file.chmod(0o755) # Make executable
590
+ hooks_installed.append(template_file.name)
591
+
592
+ if hooks_installed:
593
+ console.print("\n[green]✓[/green] Installed git hooks:")
594
+ for hook in hooks_installed:
595
+ console.print(f" • {hook}")
596
+
565
597
  console.print("\n[green]✓[/green] Initialized!")
566
598
  console.print("\nNext steps:")
567
599
  console.print(f" 1. Review/edit: [cyan]{claude_dir / 'agents.json'}[/cyan]")
@@ -633,5 +665,54 @@ def version():
633
665
  console.print(f"DevLoop v{__version__}")
634
666
 
635
667
 
668
+ @app.command()
669
+ def update_hooks(path: Path = typer.Argument(Path.cwd(), help="Project directory")):
670
+ """Update git hooks from latest templates."""
671
+ import shutil
672
+
673
+ git_dir = path / ".git"
674
+
675
+ if not git_dir.exists() or not git_dir.is_dir():
676
+ console.print(
677
+ f"[red]✗[/red] Not a git repository: {path}\n"
678
+ "[yellow]Git hooks can only be installed in git repositories.[/yellow]"
679
+ )
680
+ return
681
+
682
+ hooks_template_dir = Path(__file__).parent / "templates" / "git_hooks"
683
+ hooks_dest_dir = git_dir / "hooks"
684
+
685
+ if not hooks_template_dir.exists():
686
+ console.print(f"[red]✗[/red] Hook templates not found at: {hooks_template_dir}")
687
+ return
688
+
689
+ hooks_dest_dir.mkdir(parents=True, exist_ok=True)
690
+ hooks_updated = []
691
+
692
+ for template_file in hooks_template_dir.iterdir():
693
+ if template_file.is_file():
694
+ dest_file = hooks_dest_dir / template_file.name
695
+
696
+ # Backup existing hook if present
697
+ if dest_file.exists():
698
+ backup_file = hooks_dest_dir / f"{template_file.name}.backup"
699
+ shutil.copy2(dest_file, backup_file)
700
+ console.print(
701
+ f"[dim] Backed up existing hook: {template_file.name} -> {template_file.name}.backup[/dim]"
702
+ )
703
+
704
+ # Install new hook
705
+ shutil.copy2(template_file, dest_file)
706
+ dest_file.chmod(0o755) # Make executable
707
+ hooks_updated.append(template_file.name)
708
+
709
+ if hooks_updated:
710
+ console.print("\n[green]✓[/green] Updated git hooks:")
711
+ for hook in hooks_updated:
712
+ console.print(f" • {hook}")
713
+ else:
714
+ console.print("[yellow]No hooks found to update[/yellow]")
715
+
716
+
636
717
  if __name__ == "__main__":
637
718
  app()
@@ -4,7 +4,7 @@ description: Show intelligent summary of recent dev-agent findings
4
4
 
5
5
  Generate a summary of recent devloop agent findings by running:
6
6
  ```bash
7
- poetry run devloop summary agent-summary recent
7
+ devloop summary agent-summary recent
8
8
  ```
9
9
 
10
10
  The summary should include:
@@ -0,0 +1,70 @@
1
+ #!/bin/sh
2
+ #
3
+ # Combined pre-commit hook
4
+ #
5
+ # Checks:
6
+ # 1. Poetry lock file sync with pyproject.toml
7
+ # 2. Code quality checks (Black, Ruff, mypy, pytest)
8
+ # 3. bd (beads) pre-commit flush
9
+ # 4. Any existing pre-commit.old hook
10
+ #
11
+
12
+ # Check if pyproject.toml changed but poetry.lock didn't (only in Poetry projects)
13
+ if [ -f "poetry.lock" ]; then
14
+ if git diff --cached --name-only | grep -q "pyproject.toml"; then
15
+ if ! git diff --cached --name-only | grep -q "poetry.lock"; then
16
+ echo "ERROR: pyproject.toml changed but poetry.lock not updated"
17
+ echo "This will cause CI failure: 'poetry.lock was last generated' error"
18
+ echo ""
19
+ echo "Fix: Run 'poetry lock' and stage both files"
20
+ echo " poetry lock"
21
+ echo " git add poetry.lock"
22
+ exit 1
23
+ fi
24
+ fi
25
+ fi
26
+
27
+ # Run type checks and tests (only if there are Python changes)
28
+ if git diff --cached --name-only | grep -qE '\.py$'; then
29
+ if [ -x ".git/hooks/pre-commit-checks" ]; then
30
+ ".git/hooks/pre-commit-checks"
31
+ EXIT_CODE=$?
32
+ if [ $EXIT_CODE -ne 0 ]; then
33
+ exit $EXIT_CODE
34
+ fi
35
+ fi
36
+ fi
37
+
38
+ # Run existing hook if present
39
+ if [ -x ".git/hooks/pre-commit.old" ]; then
40
+ ".git/hooks/pre-commit.old" "$@"
41
+ EXIT_CODE=$?
42
+ if [ $EXIT_CODE -ne 0 ]; then
43
+ exit $EXIT_CODE
44
+ fi
45
+ fi
46
+
47
+ # Check if bd is available
48
+ if ! command -v bd >/dev/null 2>&1; then
49
+ echo "Warning: bd command not found, skipping pre-commit flush" >&2
50
+ exit 0
51
+ fi
52
+
53
+ # Check if we're in a bd workspace
54
+ if [ ! -d .beads ]; then
55
+ exit 0
56
+ fi
57
+
58
+ # Flush pending changes to JSONL
59
+ if ! bd sync --flush-only >/dev/null 2>&1; then
60
+ echo "Error: Failed to flush bd changes to JSONL" >&2
61
+ echo "Run 'bd sync --flush-only' manually to diagnose" >&2
62
+ exit 1
63
+ fi
64
+
65
+ # If the JSONL file was modified, stage it
66
+ if [ -f .beads/issues.jsonl ]; then
67
+ git add .beads/issues.jsonl 2>/dev/null || true
68
+ fi
69
+
70
+ exit 0
@@ -0,0 +1,72 @@
1
+ #!/bin/bash
2
+ #
3
+ # Pre-commit checks: Formatting, linting, type checking, and tests
4
+ # Prevents committing code that has formatting/linting/type errors or failing tests
5
+ #
6
+ # This is called from the main pre-commit hook after lock file checks
7
+ #
8
+
9
+ set -e
10
+
11
+ RED='\033[0;31m'
12
+ GREEN='\033[0;32m'
13
+ YELLOW='\033[1;33m'
14
+ NC='\033[0m'
15
+
16
+ echo -e "${YELLOW}[Pre-commit] Running code quality checks and tests...${NC}"
17
+
18
+ # Check if poetry is available
19
+ if ! command -v poetry &> /dev/null; then
20
+ echo -e "${YELLOW}[Pre-commit] poetry not found, skipping checks${NC}"
21
+ exit 0
22
+ fi
23
+
24
+ # Navigate to src directory where pyproject.toml is located
25
+ if [ ! -d "src" ]; then
26
+ echo -e "${YELLOW}[Pre-commit] src directory not found, skipping checks${NC}"
27
+ exit 0
28
+ fi
29
+
30
+ # Run Black formatter check
31
+ echo -e "${YELLOW}[Pre-commit] Checking code formatting (Black)...${NC}"
32
+ if ! poetry run black --check src/ > /tmp/black-output.txt 2>&1; then
33
+ echo -e "${RED}[Pre-commit] ❌ Black formatting check failed:${NC}"
34
+ cat /tmp/black-output.txt
35
+ echo -e "${YELLOW}Fix with: poetry run black src/${NC}"
36
+ exit 1
37
+ fi
38
+ echo -e "${GREEN}[Pre-commit] ✅ Code formatting passed${NC}"
39
+
40
+ # Run Ruff linting
41
+ echo -e "${YELLOW}[Pre-commit] Running linter (Ruff)...${NC}"
42
+ if ! poetry run ruff check src/ > /tmp/ruff-output.txt 2>&1; then
43
+ echo -e "${RED}[Pre-commit] ❌ Ruff linting failed:${NC}"
44
+ cat /tmp/ruff-output.txt
45
+ echo -e "${YELLOW}Fix with: poetry run ruff check src/ --fix${NC}"
46
+ exit 1
47
+ fi
48
+ echo -e "${GREEN}[Pre-commit] ✅ Linting passed${NC}"
49
+
50
+ cd src
51
+
52
+ # Run mypy
53
+ echo -e "${YELLOW}[Pre-commit] Type checking (mypy)...${NC}"
54
+ if ! poetry run mypy devloop/core/ devloop/agents/ > /tmp/mypy-output.txt 2>&1; then
55
+ echo -e "${RED}[Pre-commit] ❌ Type check failed:${NC}"
56
+ cat /tmp/mypy-output.txt
57
+ exit 1
58
+ fi
59
+ echo -e "${GREEN}[Pre-commit] ✅ Type checks passed${NC}"
60
+
61
+ # Run pytest (from parent directory where tests/ exists)
62
+ echo -e "${YELLOW}[Pre-commit] Running tests (pytest)...${NC}"
63
+ cd ..
64
+ if ! poetry run pytest tests/ -q > /tmp/pytest-output.txt 2>&1; then
65
+ echo -e "${RED}[Pre-commit] ❌ Tests failed:${NC}"
66
+ # Show last 50 lines of output
67
+ tail -50 /tmp/pytest-output.txt
68
+ exit 1
69
+ fi
70
+ echo -e "${GREEN}[Pre-commit] ✅ All tests passed${NC}"
71
+
72
+ exit 0
@@ -0,0 +1,114 @@
1
+ #!/bin/bash
2
+ # Pre-push hook: Verify CI passes before pushing to remote
3
+ # Prevents pushing code that fails local checks
4
+
5
+ set -e
6
+
7
+ # Colors for output
8
+ RED='\033[0;31m'
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ NC='\033[0m' # No Color
12
+
13
+ echo -e "${YELLOW}[CI Check] Running pre-push verification...${NC}"
14
+
15
+ # Check if gh CLI is available
16
+ if ! command -v gh &> /dev/null; then
17
+ echo -e "${YELLOW}[CI Check] gh CLI not found, skipping remote CI check${NC}"
18
+ echo -e "${YELLOW}[CI Check] Install GitHub CLI to enable CI verification: https://cli.github.com${NC}"
19
+ exit 0
20
+ fi
21
+
22
+ # Check if jq is available
23
+ if ! command -v jq &> /dev/null; then
24
+ echo -e "${YELLOW}[CI Check] jq not found, skipping remote CI check${NC}"
25
+ echo -e "${YELLOW}[CI Check] Install jq to enable CI verification: https://jqlang.github.io/jq/download/${NC}"
26
+ exit 0
27
+ fi
28
+
29
+ # Get the branch being pushed
30
+ BRANCH=$(git rev-parse --abbrev-ref HEAD)
31
+
32
+ echo -e "${YELLOW}[CI Check] Checking CI status for branch: $BRANCH${NC}"
33
+
34
+ # Check recent CI runs on this branch (get last completed run)
35
+ LAST_RUN=$(gh run list --branch "$BRANCH" --limit 1 --json status,conclusion --jq '.[0]' 2>/dev/null || echo "")
36
+
37
+ if [ -z "$LAST_RUN" ]; then
38
+ echo -e "${YELLOW}[CI Check] No previous CI runs found${NC}"
39
+ exit 0
40
+ fi
41
+
42
+ STATUS=$(echo "$LAST_RUN" | jq -r '.status' 2>/dev/null || echo "")
43
+ CONCLUSION=$(echo "$LAST_RUN" | jq -r '.conclusion' 2>/dev/null || echo "")
44
+
45
+ # If run is in progress, wait for completion (up to 2 minutes)
46
+ if [ "$STATUS" != "completed" ]; then
47
+ echo -e "${YELLOW}[CI Check] Previous CI run in progress, waiting for completion...${NC}"
48
+
49
+ WAIT_TIME=0
50
+ MAX_WAIT=120 # 2 minutes
51
+ POLL_INTERVAL=5
52
+
53
+ while [ $WAIT_TIME -lt $MAX_WAIT ]; do
54
+ sleep $POLL_INTERVAL
55
+ WAIT_TIME=$((WAIT_TIME + POLL_INTERVAL))
56
+
57
+ LAST_RUN=$(gh run list --branch "$BRANCH" --limit 1 --json status,conclusion --jq '.[0]' 2>/dev/null || echo "")
58
+ STATUS=$(echo "$LAST_RUN" | jq -r '.status' 2>/dev/null || echo "")
59
+ CONCLUSION=$(echo "$LAST_RUN" | jq -r '.conclusion' 2>/dev/null || echo "")
60
+
61
+ if [ "$STATUS" = "completed" ]; then
62
+ echo -e "${YELLOW}[CI Check] CI completed with status: $CONCLUSION${NC}"
63
+ break
64
+ fi
65
+
66
+ echo -e "${YELLOW}[CI Check] Still waiting... ($WAIT_TIME/$MAX_WAIT seconds)${NC}"
67
+ done
68
+
69
+ # If still not completed, inform user
70
+ if [ "$STATUS" != "completed" ]; then
71
+ echo -e "${YELLOW}[CI Check] CI still running after 2 minutes${NC}"
72
+ echo -e "${YELLOW}[CI Check] You can push and monitor at: https://github.com/$(git config --get remote.origin.url | sed 's/.*github.com[:/]\(.*\)\.git/\1/')/actions${NC}"
73
+ exit 0
74
+ fi
75
+ fi
76
+
77
+ # Check conclusion
78
+ if [ "$CONCLUSION" = "failure" ] || [ "$CONCLUSION" = "timed_out" ] || [ "$CONCLUSION" = "cancelled" ]; then
79
+ echo -e "${RED}[CI Check] ❌ Last CI run failed with status: $CONCLUSION${NC}"
80
+ echo -e "${RED}[CI Check] View the failed run: gh run list --branch $BRANCH${NC}"
81
+ echo -e "${RED}[CI Check] Please fix CI issues before pushing${NC}"
82
+ exit 1
83
+ fi
84
+
85
+ if [ "$CONCLUSION" = "success" ]; then
86
+ echo -e "${GREEN}[CI Check] ✅ Latest CI run passed${NC}"
87
+
88
+ # After CI passes, extract DevLoop findings and create Beads issues
89
+ echo -e "${YELLOW}[Findings] Extracting DevLoop findings...${NC}"
90
+
91
+ # Find the extract-findings-to-beads script
92
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
93
+ FINDINGS_SCRIPT="$SCRIPT_DIR/../../.agents/hooks/extract-findings-to-beads"
94
+
95
+ if [ -x "$FINDINGS_SCRIPT" ]; then
96
+ # Get current git commit hash to link findings
97
+ CURRENT_COMMIT=$(git rev-parse HEAD)
98
+
99
+ # Extract findings and create Beads issues
100
+ if "$FINDINGS_SCRIPT"; then
101
+ echo -e "${GREEN}[Findings] ✅ DevLoop findings processed${NC}"
102
+ else
103
+ echo -e "${YELLOW}[Findings] ⚠️ Could not process DevLoop findings (non-blocking)${NC}"
104
+ fi
105
+ else
106
+ echo -e "${YELLOW}[Findings] 📝 Extract-findings script not found (optional)${NC}"
107
+ fi
108
+
109
+ exit 0
110
+ fi
111
+
112
+ # For any other status, allow push
113
+ echo -e "${YELLOW}[CI Check] Unable to verify CI status (status: $CONCLUSION), allowing push${NC}"
114
+ exit 0
@@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional
11
11
 
12
12
  from .event import Event, EventBus
13
13
  from .feedback import FeedbackAPI
14
- from .performance import PerformanceMonitor
14
+ from .performance import AgentResourceTracker, PerformanceMonitor
15
15
 
16
16
 
17
17
  @dataclass
@@ -79,12 +79,14 @@ class Agent(ABC):
79
79
  event_bus: EventBus,
80
80
  feedback_api: Optional[FeedbackAPI] = None,
81
81
  performance_monitor: Optional[PerformanceMonitor] = None,
82
+ resource_tracker: Optional[AgentResourceTracker] = None,
82
83
  ):
83
84
  self.name = name
84
85
  self.triggers = triggers
85
86
  self.event_bus = event_bus
86
87
  self.feedback_api = feedback_api
87
88
  self.performance_monitor = performance_monitor
89
+ self.resource_tracker = resource_tracker
88
90
  self.enabled = True
89
91
  self.logger = logging.getLogger(f"agent.{name}")
90
92
  self._running = False
@@ -139,21 +141,33 @@ class Agent(ABC):
139
141
  try:
140
142
  operation_name = f"agent.{self.name}.handle"
141
143
 
142
- if self.performance_monitor:
143
- async with self.performance_monitor.monitor_operation(
144
- operation_name,
145
- metadata={"event_type": event.type, "agent_name": self.name},
146
- ) as metrics:
144
+ # Mark agent as active for resource tracking
145
+ if self.resource_tracker:
146
+ self.resource_tracker.mark_agent_active(self.name)
147
+
148
+ try:
149
+ if self.performance_monitor:
150
+ async with self.performance_monitor.monitor_operation(
151
+ operation_name,
152
+ metadata={
153
+ "event_type": event.type,
154
+ "agent_name": self.name,
155
+ },
156
+ ) as metrics:
157
+ result = await self.handle(event)
158
+ metrics.complete(result.success, result.error)
159
+
160
+ # Update result duration from metrics
161
+ if metrics.duration:
162
+ result.duration = metrics.duration
163
+ else:
164
+ start_time = time.time()
147
165
  result = await self.handle(event)
148
- metrics.complete(result.success, result.error)
149
-
150
- # Update result duration from metrics
151
- if metrics.duration:
152
- result.duration = metrics.duration
153
- else:
154
- start_time = time.time()
155
- result = await self.handle(event)
156
- result.duration = time.time() - start_time
166
+ result.duration = time.time() - start_time
167
+ finally:
168
+ # Mark agent as inactive after handling
169
+ if self.resource_tracker:
170
+ self.resource_tracker.mark_agent_inactive(self.name)
157
171
 
158
172
  # Update performance store if available
159
173
  if self.feedback_api: