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.
- tapps_agents/__init__.py +2 -2
- tapps_agents/agents/cleanup/__init__.py +7 -0
- tapps_agents/agents/cleanup/agent.py +445 -0
- tapps_agents/agents/enhancer/agent.py +2 -2
- tapps_agents/agents/implementer/agent.py +35 -13
- tapps_agents/agents/reviewer/agent.py +43 -10
- tapps_agents/agents/reviewer/scoring.py +59 -68
- tapps_agents/agents/reviewer/tools/__init__.py +24 -0
- tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -0
- tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -0
- tapps_agents/beads/__init__.py +11 -0
- tapps_agents/beads/hydration.py +213 -0
- tapps_agents/beads/specs.py +206 -0
- tapps_agents/cli/commands/cleanup_agent.py +92 -0
- tapps_agents/cli/commands/health.py +19 -3
- tapps_agents/cli/commands/simple_mode.py +842 -676
- tapps_agents/cli/commands/task.py +219 -0
- tapps_agents/cli/commands/top_level.py +13 -0
- tapps_agents/cli/main.py +15 -2
- tapps_agents/cli/parsers/cleanup_agent.py +228 -0
- tapps_agents/cli/parsers/top_level.py +1978 -1881
- tapps_agents/core/config.py +43 -0
- tapps_agents/core/init_project.py +3012 -2896
- tapps_agents/epic/markdown_sync.py +105 -0
- tapps_agents/epic/orchestrator.py +1 -2
- tapps_agents/epic/parser.py +427 -423
- tapps_agents/experts/adaptive_domain_detector.py +0 -2
- tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +15 -15
- tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +19 -44
- tapps_agents/health/checks/outcomes.backup_20260204_064058.py +324 -0
- tapps_agents/health/checks/outcomes.backup_20260204_064256.py +324 -0
- tapps_agents/health/checks/outcomes.backup_20260204_064600.py +324 -0
- tapps_agents/health/checks/outcomes.py +134 -46
- tapps_agents/health/orchestrator.py +12 -4
- tapps_agents/hooks/__init__.py +33 -0
- tapps_agents/hooks/config.py +140 -0
- tapps_agents/hooks/events.py +135 -0
- tapps_agents/hooks/executor.py +128 -0
- tapps_agents/hooks/manager.py +143 -0
- tapps_agents/session/__init__.py +19 -0
- tapps_agents/session/manager.py +256 -0
- tapps_agents/simple_mode/code_snippet_handler.py +382 -0
- tapps_agents/simple_mode/intent_parser.py +29 -4
- tapps_agents/simple_mode/orchestrators/base.py +185 -59
- tapps_agents/simple_mode/orchestrators/build_orchestrator.py +2667 -2642
- tapps_agents/simple_mode/orchestrators/fix_orchestrator.py +723 -723
- tapps_agents/simple_mode/workflow_suggester.py +37 -3
- tapps_agents/workflow/agent_handlers/implementer_handler.py +18 -3
- tapps_agents/workflow/cursor_executor.py +2196 -2118
- tapps_agents/workflow/direct_execution_fallback.py +16 -3
- tapps_agents/workflow/enforcer.py +36 -23
- tapps_agents/workflow/message_formatter.py +188 -0
- tapps_agents/workflow/parallel_executor.py +43 -4
- tapps_agents/workflow/parser.py +375 -357
- tapps_agents/workflow/rules_generator.py +337 -331
- tapps_agents/workflow/skill_invoker.py +9 -3
- {tapps_agents-3.5.38.dist-info → tapps_agents-3.5.40.dist-info}/METADATA +9 -5
- {tapps_agents-3.5.38.dist-info → tapps_agents-3.5.40.dist-info}/RECORD +62 -53
- tapps_agents/agents/analyst/SKILL.md +0 -85
- tapps_agents/agents/architect/SKILL.md +0 -80
- tapps_agents/agents/debugger/SKILL.md +0 -66
- tapps_agents/agents/designer/SKILL.md +0 -78
- tapps_agents/agents/documenter/SKILL.md +0 -95
- tapps_agents/agents/enhancer/SKILL.md +0 -189
- tapps_agents/agents/implementer/SKILL.md +0 -117
- tapps_agents/agents/improver/SKILL.md +0 -55
- tapps_agents/agents/ops/SKILL.md +0 -64
- tapps_agents/agents/orchestrator/SKILL.md +0 -238
- tapps_agents/agents/planner/story_template.md +0 -37
- tapps_agents/agents/reviewer/templates/quality-dashboard.html.j2 +0 -150
- tapps_agents/agents/tester/SKILL.md +0 -71
- {tapps_agents-3.5.38.dist-info → tapps_agents-3.5.40.dist-info}/WHEEL +0 -0
- {tapps_agents-3.5.38.dist-info → tapps_agents-3.5.40.dist-info}/entry_points.txt +0 -0
- {tapps_agents-3.5.38.dist-info → tapps_agents-3.5.40.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
578
|
+
fallback = _usage_data_from_execution_metrics(project_root)
|
|
579
|
+
if fallback:
|
|
580
|
+
fallback_used = True
|
|
581
|
+
usage_data = fallback
|
|
576
582
|
else:
|
|
577
|
-
|
|
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()
|