tapps-agents 3.5.38__py3-none-any.whl → 3.5.40__py3-none-any.whl

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. tapps_agents/__init__.py +2 -2
  2. tapps_agents/agents/cleanup/__init__.py +7 -0
  3. tapps_agents/agents/cleanup/agent.py +445 -0
  4. tapps_agents/agents/enhancer/agent.py +2 -2
  5. tapps_agents/agents/implementer/agent.py +35 -13
  6. tapps_agents/agents/reviewer/agent.py +43 -10
  7. tapps_agents/agents/reviewer/scoring.py +59 -68
  8. tapps_agents/agents/reviewer/tools/__init__.py +24 -0
  9. tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -0
  10. tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -0
  11. tapps_agents/beads/__init__.py +11 -0
  12. tapps_agents/beads/hydration.py +213 -0
  13. tapps_agents/beads/specs.py +206 -0
  14. tapps_agents/cli/commands/cleanup_agent.py +92 -0
  15. tapps_agents/cli/commands/health.py +19 -3
  16. tapps_agents/cli/commands/simple_mode.py +842 -676
  17. tapps_agents/cli/commands/task.py +219 -0
  18. tapps_agents/cli/commands/top_level.py +13 -0
  19. tapps_agents/cli/main.py +15 -2
  20. tapps_agents/cli/parsers/cleanup_agent.py +228 -0
  21. tapps_agents/cli/parsers/top_level.py +1978 -1881
  22. tapps_agents/core/config.py +43 -0
  23. tapps_agents/core/init_project.py +3012 -2896
  24. tapps_agents/epic/markdown_sync.py +105 -0
  25. tapps_agents/epic/orchestrator.py +1 -2
  26. tapps_agents/epic/parser.py +427 -423
  27. tapps_agents/experts/adaptive_domain_detector.py +0 -2
  28. tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +15 -15
  29. tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +19 -44
  30. tapps_agents/health/checks/outcomes.backup_20260204_064058.py +324 -0
  31. tapps_agents/health/checks/outcomes.backup_20260204_064256.py +324 -0
  32. tapps_agents/health/checks/outcomes.backup_20260204_064600.py +324 -0
  33. tapps_agents/health/checks/outcomes.py +134 -46
  34. tapps_agents/health/orchestrator.py +12 -4
  35. tapps_agents/hooks/__init__.py +33 -0
  36. tapps_agents/hooks/config.py +140 -0
  37. tapps_agents/hooks/events.py +135 -0
  38. tapps_agents/hooks/executor.py +128 -0
  39. tapps_agents/hooks/manager.py +143 -0
  40. tapps_agents/session/__init__.py +19 -0
  41. tapps_agents/session/manager.py +256 -0
  42. tapps_agents/simple_mode/code_snippet_handler.py +382 -0
  43. tapps_agents/simple_mode/intent_parser.py +29 -4
  44. tapps_agents/simple_mode/orchestrators/base.py +185 -59
  45. tapps_agents/simple_mode/orchestrators/build_orchestrator.py +2667 -2642
  46. tapps_agents/simple_mode/orchestrators/fix_orchestrator.py +723 -723
  47. tapps_agents/simple_mode/workflow_suggester.py +37 -3
  48. tapps_agents/workflow/agent_handlers/implementer_handler.py +18 -3
  49. tapps_agents/workflow/cursor_executor.py +2196 -2118
  50. tapps_agents/workflow/direct_execution_fallback.py +16 -3
  51. tapps_agents/workflow/enforcer.py +36 -23
  52. tapps_agents/workflow/message_formatter.py +188 -0
  53. tapps_agents/workflow/parallel_executor.py +43 -4
  54. tapps_agents/workflow/parser.py +375 -357
  55. tapps_agents/workflow/rules_generator.py +337 -331
  56. tapps_agents/workflow/skill_invoker.py +9 -3
  57. {tapps_agents-3.5.38.dist-info → tapps_agents-3.5.40.dist-info}/METADATA +9 -5
  58. {tapps_agents-3.5.38.dist-info → tapps_agents-3.5.40.dist-info}/RECORD +62 -53
  59. tapps_agents/agents/analyst/SKILL.md +0 -85
  60. tapps_agents/agents/architect/SKILL.md +0 -80
  61. tapps_agents/agents/debugger/SKILL.md +0 -66
  62. tapps_agents/agents/designer/SKILL.md +0 -78
  63. tapps_agents/agents/documenter/SKILL.md +0 -95
  64. tapps_agents/agents/enhancer/SKILL.md +0 -189
  65. tapps_agents/agents/implementer/SKILL.md +0 -117
  66. tapps_agents/agents/improver/SKILL.md +0 -55
  67. tapps_agents/agents/ops/SKILL.md +0 -64
  68. tapps_agents/agents/orchestrator/SKILL.md +0 -238
  69. tapps_agents/agents/planner/story_template.md +0 -37
  70. tapps_agents/agents/reviewer/templates/quality-dashboard.html.j2 +0 -150
  71. tapps_agents/agents/tester/SKILL.md +0 -71
  72. {tapps_agents-3.5.38.dist-info → tapps_agents-3.5.40.dist-info}/WHEEL +0 -0
  73. {tapps_agents-3.5.38.dist-info → tapps_agents-3.5.40.dist-info}/entry_points.txt +0 -0
  74. {tapps_agents-3.5.38.dist-info → tapps_agents-3.5.40.dist-info}/licenses/LICENSE +0 -0
  75. {tapps_agents-3.5.38.dist-info → tapps_agents-3.5.40.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,206 @@
1
+ """
2
+ Task specification schema and loader.
3
+
4
+ Loads and saves task specification YAML files from .tapps-agents/task-specs/
5
+ with validation. Supports hydration/dehydration pattern for multi-session workflows.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from pathlib import Path
12
+ from typing import Literal
13
+
14
+ import yaml
15
+
16
+ logger = logging.getLogger(__name__)
17
+ from pydantic import BaseModel, Field, field_validator
18
+
19
+
20
+ class TaskSpec(BaseModel):
21
+ """Task specification schema for .tapps-agents/task-specs/ YAML files."""
22
+
23
+ id: str = Field(..., min_length=1, description="Unique task ID (e.g. enh-002-s1)")
24
+ title: str = Field(..., min_length=1, description="Task title")
25
+ description: str = Field(default="", description="Task description")
26
+ type: Literal["story", "epic", "task"] = Field(
27
+ default="story",
28
+ description="Task type",
29
+ )
30
+ priority: int = Field(default=0, ge=0, description="Priority (0=highest)")
31
+ story_points: int | None = Field(default=None, ge=0, description="Story points estimate")
32
+ epic: str | None = Field(default=None, description="Epic ID this task belongs to")
33
+ dependencies: list[str] = Field(
34
+ default_factory=list,
35
+ description="IDs of tasks this depends on",
36
+ )
37
+ github_issue: str | int | None = Field(default=None, description="GitHub issue number or ID")
38
+ beads_issue: str | None = Field(default=None, description="Beads issue ID (populated after create)")
39
+ status: Literal["todo", "in-progress", "done", "blocked"] = Field(
40
+ default="todo",
41
+ description="Current status",
42
+ )
43
+ workflow: str | None = Field(
44
+ default=None,
45
+ description="Workflow to run (build, fix, review, test, full)",
46
+ )
47
+ files: list[str] = Field(default_factory=list, description="Files or paths affected")
48
+ tests: list[str] = Field(default_factory=list, description="Test paths")
49
+
50
+ model_config = {"extra": "forbid"}
51
+
52
+ @field_validator("id", mode="before")
53
+ @classmethod
54
+ def id_stripped(cls, v: object) -> str:
55
+ """Strip whitespace from id."""
56
+ if isinstance(v, str):
57
+ return v.strip()
58
+ return str(v)
59
+
60
+
61
+ def _task_specs_dir(project_root: Path) -> Path:
62
+ """Return path to .tapps-agents/task-specs/."""
63
+ return project_root / ".tapps-agents" / "task-specs"
64
+
65
+
66
+ def load_task_specs(project_root: Path | None = None) -> list[TaskSpec]:
67
+ """
68
+ Load all task specs from .tapps-agents/task-specs/.
69
+
70
+ Scans the directory for YAML files, parses valid ones, and returns a list
71
+ of validated TaskSpec. Validation errors are reported with file/field;
72
+ invalid files are skipped (not raised).
73
+
74
+ Args:
75
+ project_root: Project root (default: cwd).
76
+
77
+ Returns:
78
+ List of successfully loaded TaskSpec. Empty if directory missing or no valid files.
79
+ """
80
+ project_root = project_root or Path.cwd()
81
+ specs_dir = _task_specs_dir(project_root)
82
+ if not specs_dir.exists():
83
+ return []
84
+
85
+ result: list[TaskSpec] = []
86
+ for path in sorted(specs_dir.glob("*.yaml")):
87
+ try:
88
+ spec = _load_single_spec(path)
89
+ if spec:
90
+ result.append(spec)
91
+ except Exception as e:
92
+ logger.warning("Task spec validation failed %s: %s", path, e)
93
+ continue
94
+ return result
95
+
96
+
97
+ def _load_single_spec(path: Path) -> TaskSpec | None:
98
+ """Load and validate a single task spec file."""
99
+ content = path.read_text(encoding="utf-8")
100
+ raw = yaml.safe_load(content)
101
+ if raw is None:
102
+ return None
103
+
104
+ if not isinstance(raw, dict):
105
+ raise ValueError(f"Expected YAML object at {path}, got {type(raw).__name__}")
106
+
107
+ # Support both top-level "task" key and flat structure
108
+ data = raw.get("task", raw)
109
+ if not isinstance(data, dict):
110
+ raise ValueError(f"Expected task object at {path}")
111
+
112
+ return TaskSpec.model_validate(data)
113
+
114
+
115
+ def load_task_spec(
116
+ spec_id: str,
117
+ project_root: Path | None = None,
118
+ ) -> TaskSpec | None:
119
+ """
120
+ Load a single task spec by ID.
121
+
122
+ Searches .tapps-agents/task-specs/ for a file containing the given id.
123
+ Naming convention: <epic-id>-<story-id>.yaml (e.g. enh-002-s1.yaml).
124
+
125
+ Args:
126
+ spec_id: Task ID (e.g. enh-002-s1).
127
+ project_root: Project root (default: cwd).
128
+
129
+ Returns:
130
+ TaskSpec if found and valid, else None.
131
+ """
132
+ project_root = project_root or Path.cwd()
133
+ specs_dir = _task_specs_dir(project_root)
134
+ if not specs_dir.exists():
135
+ return None
136
+
137
+ # Try direct filename: spec_id.yaml
138
+ candidate = specs_dir / f"{spec_id}.yaml"
139
+ if candidate.exists():
140
+ try:
141
+ return _load_single_spec(candidate)
142
+ except Exception:
143
+ return None
144
+
145
+ # Scan files for matching id
146
+ for path in specs_dir.glob("*.yaml"):
147
+ try:
148
+ spec = _load_single_spec(path)
149
+ if spec and spec.id == spec_id:
150
+ return spec
151
+ except Exception:
152
+ continue
153
+ return None
154
+
155
+
156
+ def save_task_spec(
157
+ spec: TaskSpec,
158
+ project_root: Path | None = None,
159
+ ) -> Path:
160
+ """
161
+ Save task spec to .tapps-agents/task-specs/.
162
+
163
+ Uses naming convention <epic-id>-<story-id>.yaml when epic can be derived,
164
+ otherwise <spec.id>.yaml. Creates directory if needed.
165
+
166
+ Args:
167
+ spec: TaskSpec to save.
168
+ project_root: Project root (default: cwd).
169
+
170
+ Returns:
171
+ Path where spec was written.
172
+
173
+ Raises:
174
+ ValueError: On validation failure (should not occur for valid TaskSpec).
175
+ """
176
+ project_root = project_root or Path.cwd()
177
+ specs_dir = _task_specs_dir(project_root)
178
+ specs_dir.mkdir(parents=True, exist_ok=True)
179
+
180
+ # Naming: epic-id-story-id or spec.id
181
+ filename = f"{spec.id}.yaml"
182
+ out_path = specs_dir / filename
183
+
184
+ payload = {"task": spec.model_dump()}
185
+ out_path.write_text(
186
+ yaml.dump(payload, default_flow_style=False, sort_keys=False, allow_unicode=True),
187
+ encoding="utf-8",
188
+ )
189
+ return out_path
190
+
191
+
192
+ def validate_task_spec_file(path: Path) -> tuple[TaskSpec | None, str | None]:
193
+ """
194
+ Validate a task spec file without loading into global list.
195
+
196
+ Args:
197
+ path: Path to YAML file.
198
+
199
+ Returns:
200
+ (TaskSpec, None) if valid, (None, error_message) if invalid.
201
+ """
202
+ try:
203
+ spec = _load_single_spec(path)
204
+ return (spec, None)
205
+ except Exception as e:
206
+ return (None, f"{path}: {e}")
@@ -0,0 +1,92 @@
1
+ """
2
+ Cleanup Agent command handlers
3
+ """
4
+ import asyncio
5
+
6
+ from ...agents.cleanup.agent import CleanupAgent
7
+ from ..base import normalize_command
8
+ from ..feedback import get_feedback
9
+ from ..help.static_help import get_static_help
10
+ from ..utils.agent_lifecycle import safe_close_agent_sync
11
+ from .common import check_result_error
12
+
13
+
14
+ def handle_cleanup_agent_command(args: object) -> None:
15
+ """Handle cleanup-agent commands."""
16
+ feedback = get_feedback()
17
+ command = normalize_command(getattr(args, "command", None))
18
+ output_format = getattr(args, "format", "json")
19
+ feedback.format_type = output_format
20
+
21
+ # Help commands first - no activation needed
22
+ if command == "help" or command is None:
23
+ help_text = get_static_help("cleanup-agent")
24
+ feedback.output_result(help_text)
25
+ return
26
+
27
+ # Cleanup agent runs offline (no network needed for file operations)
28
+ offline_mode = True
29
+
30
+ # Create and activate agent
31
+ cleanup = CleanupAgent()
32
+ try:
33
+ asyncio.run(cleanup.activate(offline_mode=offline_mode))
34
+
35
+ if command == "analyze":
36
+ result = asyncio.run(
37
+ cleanup.run(
38
+ "analyze",
39
+ path=getattr(args, "path", None),
40
+ pattern=getattr(args, "pattern", "*.md"),
41
+ output=getattr(args, "output", None),
42
+ )
43
+ )
44
+ elif command == "plan":
45
+ result = asyncio.run(
46
+ cleanup.run(
47
+ "plan",
48
+ analysis_file=getattr(args, "analysis_file", None),
49
+ path=getattr(args, "path", None),
50
+ pattern=getattr(args, "pattern", "*.md"),
51
+ output=getattr(args, "output", None),
52
+ )
53
+ )
54
+ elif command == "execute":
55
+ # Handle dry-run flag (--no-dry-run disables dry-run)
56
+ dry_run = not getattr(args, "no_dry_run", False)
57
+ backup = not getattr(args, "no_backup", False)
58
+
59
+ result = asyncio.run(
60
+ cleanup.run(
61
+ "execute",
62
+ plan_file=getattr(args, "plan_file", None),
63
+ path=getattr(args, "path", None),
64
+ pattern=getattr(args, "pattern", "*.md"),
65
+ dry_run=dry_run,
66
+ backup=backup,
67
+ )
68
+ )
69
+ elif command == "run":
70
+ # Handle dry-run flag (--no-dry-run disables dry-run)
71
+ dry_run = not getattr(args, "no_dry_run", False)
72
+ backup = not getattr(args, "no_backup", False)
73
+
74
+ result = asyncio.run(
75
+ cleanup.run(
76
+ "run",
77
+ path=getattr(args, "path", None),
78
+ pattern=getattr(args, "pattern", "*.md"),
79
+ dry_run=dry_run,
80
+ backup=backup,
81
+ )
82
+ )
83
+ else:
84
+ # Invalid command - show help
85
+ help_text = get_static_help("cleanup-agent")
86
+ feedback.output_result(help_text)
87
+ return
88
+
89
+ check_result_error(result)
90
+ feedback.output_result(result, message="Cleanup operation completed successfully")
91
+ finally:
92
+ safe_close_agent_sync(cleanup)
@@ -5,6 +5,7 @@ Health command handlers.
5
5
  from __future__ import annotations
6
6
 
7
7
  import json
8
+ import logging
8
9
  import sys
9
10
  from collections import defaultdict
10
11
  from datetime import UTC, datetime, timedelta
@@ -557,7 +558,8 @@ def handle_health_overview_command(
557
558
  health_results = orchestrator.run_all_checks(save_metrics=True)
558
559
  overall = orchestrator.get_overall_health(health_results)
559
560
 
560
- # 2. Usage (best-effort; prefer analytics, fallback to execution metrics)
561
+ # 2. Usage (best-effort; prefer analytics, fallback to execution metrics — HM-001-S1)
562
+ _log = logging.getLogger(__name__)
561
563
  usage_data = None
562
564
  try:
563
565
  usage_dashboard = AnalyticsDashboard()
@@ -565,6 +567,7 @@ def handle_health_overview_command(
565
567
  except Exception:
566
568
  pass
567
569
  # If analytics has no agent/workflow data, derive from execution metrics
570
+ fallback_used = False
568
571
  if usage_data:
569
572
  agents = usage_data.get("agents") or []
570
573
  workflows = usage_data.get("workflows") or []
@@ -572,9 +575,22 @@ def handle_health_overview_command(
572
575
  w.get("total_executions", 0) for w in workflows
573
576
  )
574
577
  if total_runs == 0:
575
- usage_data = _usage_data_from_execution_metrics(project_root) or usage_data
578
+ fallback = _usage_data_from_execution_metrics(project_root)
579
+ if fallback:
580
+ fallback_used = True
581
+ usage_data = fallback
576
582
  else:
577
- usage_data = _usage_data_from_execution_metrics(project_root) or usage_data
583
+ fallback = _usage_data_from_execution_metrics(project_root)
584
+ if fallback:
585
+ fallback_used = True
586
+ usage_data = fallback
587
+ if fallback_used and usage_data:
588
+ n_agents = len(usage_data.get("agents") or [])
589
+ n_workflows = len(usage_data.get("workflows") or [])
590
+ _log.info(
591
+ "Health overview: using execution metrics fallback (%s agents, %s workflows)",
592
+ n_agents, n_workflows,
593
+ )
578
594
 
579
595
  # 3. Build output
580
596
  feedback = get_feedback()