tapps-agents 3.5.39__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 (70) hide show
  1. tapps_agents/__init__.py +2 -2
  2. tapps_agents/agents/enhancer/agent.py +2728 -2728
  3. tapps_agents/agents/implementer/agent.py +35 -13
  4. tapps_agents/agents/reviewer/agent.py +43 -10
  5. tapps_agents/agents/reviewer/scoring.py +59 -68
  6. tapps_agents/agents/reviewer/tools/__init__.py +24 -0
  7. tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -0
  8. tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -0
  9. tapps_agents/beads/__init__.py +11 -0
  10. tapps_agents/beads/hydration.py +213 -0
  11. tapps_agents/beads/specs.py +206 -0
  12. tapps_agents/cli/commands/health.py +19 -3
  13. tapps_agents/cli/commands/simple_mode.py +842 -676
  14. tapps_agents/cli/commands/task.py +219 -0
  15. tapps_agents/cli/commands/top_level.py +13 -0
  16. tapps_agents/cli/main.py +658 -651
  17. tapps_agents/cli/parsers/top_level.py +1978 -1881
  18. tapps_agents/core/config.py +1622 -1622
  19. tapps_agents/core/init_project.py +3012 -2897
  20. tapps_agents/epic/markdown_sync.py +105 -0
  21. tapps_agents/epic/orchestrator.py +1 -2
  22. tapps_agents/epic/parser.py +427 -423
  23. tapps_agents/experts/adaptive_domain_detector.py +0 -2
  24. tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +15 -15
  25. tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +19 -44
  26. tapps_agents/health/checks/outcomes.backup_20260204_064058.py +324 -0
  27. tapps_agents/health/checks/outcomes.backup_20260204_064256.py +324 -0
  28. tapps_agents/health/checks/outcomes.backup_20260204_064600.py +324 -0
  29. tapps_agents/health/checks/outcomes.py +134 -46
  30. tapps_agents/health/orchestrator.py +12 -4
  31. tapps_agents/hooks/__init__.py +33 -0
  32. tapps_agents/hooks/config.py +140 -0
  33. tapps_agents/hooks/events.py +135 -0
  34. tapps_agents/hooks/executor.py +128 -0
  35. tapps_agents/hooks/manager.py +143 -0
  36. tapps_agents/session/__init__.py +19 -0
  37. tapps_agents/session/manager.py +256 -0
  38. tapps_agents/simple_mode/code_snippet_handler.py +382 -0
  39. tapps_agents/simple_mode/intent_parser.py +29 -4
  40. tapps_agents/simple_mode/orchestrators/base.py +185 -59
  41. tapps_agents/simple_mode/orchestrators/build_orchestrator.py +2667 -2642
  42. tapps_agents/simple_mode/orchestrators/fix_orchestrator.py +2 -2
  43. tapps_agents/simple_mode/workflow_suggester.py +37 -3
  44. tapps_agents/workflow/agent_handlers/implementer_handler.py +18 -3
  45. tapps_agents/workflow/cursor_executor.py +2196 -2118
  46. tapps_agents/workflow/direct_execution_fallback.py +16 -3
  47. tapps_agents/workflow/message_formatter.py +2 -1
  48. tapps_agents/workflow/parallel_executor.py +43 -4
  49. tapps_agents/workflow/parser.py +375 -357
  50. tapps_agents/workflow/rules_generator.py +337 -337
  51. tapps_agents/workflow/skill_invoker.py +9 -3
  52. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/METADATA +5 -1
  53. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/RECORD +57 -53
  54. tapps_agents/agents/analyst/SKILL.md +0 -85
  55. tapps_agents/agents/architect/SKILL.md +0 -80
  56. tapps_agents/agents/debugger/SKILL.md +0 -66
  57. tapps_agents/agents/designer/SKILL.md +0 -78
  58. tapps_agents/agents/documenter/SKILL.md +0 -95
  59. tapps_agents/agents/enhancer/SKILL.md +0 -189
  60. tapps_agents/agents/implementer/SKILL.md +0 -117
  61. tapps_agents/agents/improver/SKILL.md +0 -55
  62. tapps_agents/agents/ops/SKILL.md +0 -64
  63. tapps_agents/agents/orchestrator/SKILL.md +0 -238
  64. tapps_agents/agents/planner/story_template.md +0 -37
  65. tapps_agents/agents/reviewer/templates/quality-dashboard.html.j2 +0 -150
  66. tapps_agents/agents/tester/SKILL.md +0 -71
  67. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/WHEEL +0 -0
  68. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/entry_points.txt +0 -0
  69. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/licenses/LICENSE +0 -0
  70. {tapps_agents-3.5.39.dist-info → tapps_agents-3.5.40.dist-info}/top_level.txt +0 -0
@@ -625,16 +625,16 @@ class ImplementerAgent(BaseAgent, ExpertSupportMixin):
625
625
  async def _detect_api_client_pattern(self, specification: str, context: str | None = None) -> bool:
626
626
  """
627
627
  Detect if specification/context indicates an HTTP/API client implementation.
628
-
628
+
629
629
  Checks for common patterns that indicate API client code:
630
630
  - Keywords: "API client", "OAuth2", "refresh token", "external API", "HTTP client"
631
- - Authentication patterns: "Bearer", "Zoho", "token", "authentication"
631
+ - Authentication patterns: "Bearer", "token", "authentication"
632
632
  - API patterns: "REST API", "API integration", "third-party API"
633
-
633
+
634
634
  Args:
635
635
  specification: Code specification/description
636
636
  context: Optional context code
637
-
637
+
638
638
  Returns:
639
639
  True if specification appears to be for an API client, False otherwise
640
640
  """
@@ -646,13 +646,14 @@ class ImplementerAgent(BaseAgent, ExpertSupportMixin):
646
646
  if context:
647
647
  text_to_analyze += " " + context.lower()
648
648
 
649
- # API client keywords
649
+ # API client keywords (enhanced)
650
650
  api_client_keywords = [
651
651
  "api client",
652
652
  "http client",
653
653
  "rest client",
654
654
  "oauth2",
655
655
  "oauth 2",
656
+ "oauth",
656
657
  "refresh token",
657
658
  "access token",
658
659
  "external api",
@@ -660,31 +661,52 @@ class ImplementerAgent(BaseAgent, ExpertSupportMixin):
660
661
  "api integration",
661
662
  "rest api",
662
663
  "api wrapper",
664
+ "graphql client",
665
+ "graphql api",
666
+ "websocket client",
667
+ "mqtt client",
668
+ "grpc client",
663
669
  ]
664
-
665
- # Authentication keywords
670
+
671
+ # Authentication keywords (enhanced with OAuth2 patterns)
666
672
  auth_keywords = [
667
673
  "bearer",
668
- "zoho",
669
- "site24x7",
670
- "okta",
671
- "salesforce",
674
+ "token", # General token (covers access, refresh, bearer, etc.)
672
675
  "authentication",
673
676
  "authorization",
674
677
  "api key",
678
+ "api_key",
679
+ "apikey",
675
680
  "client_id",
676
681
  "client_secret",
677
682
  "token_url",
678
683
  "api_base_url",
684
+ "jwt",
685
+ "id_token",
686
+ "grant_type",
687
+ "authorization_code",
688
+ "client_credentials",
689
+ "credentials", # General credentials
690
+ "auth", # Short form
679
691
  ]
680
-
681
- # Structure keywords
692
+
693
+ # Structure keywords (enhanced with framework patterns)
682
694
  structure_keywords = [
683
695
  "class.*client",
684
696
  "get method",
685
697
  "post method",
698
+ "put method",
699
+ "delete method",
700
+ "patch method",
686
701
  "api endpoint",
702
+ "endpoint", # General endpoint
703
+ "rest endpoint",
687
704
  "make request",
705
+ "http request",
706
+ "fastapi",
707
+ "django rest",
708
+ "api route",
709
+ "router", # General router
688
710
  ]
689
711
 
690
712
  # Check for API client keywords
@@ -3218,16 +3218,16 @@ class ReviewerAgent(BaseAgent, ExpertSupportMixin):
3218
3218
  async def _detect_api_client_pattern(self, code: str) -> bool:
3219
3219
  """
3220
3220
  Detect if code appears to be an HTTP/API client.
3221
-
3221
+
3222
3222
  Checks for common patterns that indicate API client code:
3223
3223
  - HTTP client libraries (requests, httpx)
3224
- - Authentication headers (Authorization, Bearer, Zoho-oauthtoken, X-API-Key)
3224
+ - Authentication headers (Authorization, Bearer, X-API-Key)
3225
3225
  - Token management (refresh_token, access_token, token_url)
3226
3226
  - API client structure (class Client, get/post methods, api_base_url)
3227
-
3227
+
3228
3228
  Args:
3229
3229
  code: Code content to analyze
3230
-
3230
+
3231
3231
  Returns:
3232
3232
  True if code appears to be an API client, False otherwise
3233
3233
  """
@@ -3243,18 +3243,20 @@ class ReviewerAgent(BaseAgent, ExpertSupportMixin):
3243
3243
  "requests.put",
3244
3244
  "requests.delete",
3245
3245
  "httpx.client",
3246
- "httpx.asynccclient",
3246
+ "httpx.asyncclient",
3247
3247
  "httpx.get",
3248
3248
  "httpx.post",
3249
3249
  "urllib.request",
3250
3250
  "urllib3",
3251
+ "aiohttp",
3252
+ "fetch(", # JavaScript/TypeScript
3253
+ "axios", # JavaScript/TypeScript
3251
3254
  ]
3252
-
3253
- # Authentication indicators
3255
+
3256
+ # Authentication indicators (including OAuth2)
3254
3257
  auth_indicators = [
3255
3258
  "authorization:",
3256
3259
  "bearer",
3257
- "zoho-oauthtoken",
3258
3260
  "x-api-key",
3259
3261
  "api_key",
3260
3262
  "api-key",
@@ -3263,6 +3265,13 @@ class ReviewerAgent(BaseAgent, ExpertSupportMixin):
3263
3265
  "token_url",
3264
3266
  "client_id",
3265
3267
  "client_secret",
3268
+ "oauth2",
3269
+ "oauth",
3270
+ "grant_type",
3271
+ "authorization_code",
3272
+ "client_credentials",
3273
+ "jwt",
3274
+ "id_token",
3266
3275
  ]
3267
3276
 
3268
3277
  # API client structure indicators
@@ -3270,14 +3279,25 @@ class ReviewerAgent(BaseAgent, ExpertSupportMixin):
3270
3279
  "api_base_url",
3271
3280
  "base_url",
3272
3281
  "api_url",
3282
+ "endpoint",
3283
+ "/api/",
3284
+ "rest",
3285
+ "graphql",
3273
3286
  "class.*client",
3287
+ "class.*api",
3274
3288
  "def get(",
3275
3289
  "def post(",
3276
3290
  "def put(",
3277
3291
  "def delete(",
3292
+ "def patch(",
3278
3293
  "def _headers",
3279
3294
  "def _get_access_token",
3280
3295
  "def _refresh",
3296
+ "@app.get", # FastAPI
3297
+ "@app.post", # FastAPI
3298
+ "@router", # FastAPI router
3299
+ "apiview", # Django REST
3300
+ "viewset", # Django REST
3281
3301
  ]
3282
3302
 
3283
3303
  # Check for HTTP client usage
@@ -3289,8 +3309,21 @@ class ReviewerAgent(BaseAgent, ExpertSupportMixin):
3289
3309
  # Check for API client structure
3290
3310
  has_structure = any(indicator in code_lower for indicator in structure_indicators)
3291
3311
 
3292
- # Code is likely an API client if it has HTTP client usage AND (auth OR structure)
3293
- return has_http_client and (has_auth or has_structure)
3312
+ # Server-side REST framework indicators (FastAPI, Django REST, etc.)
3313
+ server_api_indicators = [
3314
+ "@app.get",
3315
+ "@app.post",
3316
+ "@router",
3317
+ "apiview",
3318
+ "viewset",
3319
+ ]
3320
+ has_server_api = any(indicator in code_lower for indicator in server_api_indicators)
3321
+
3322
+ # Code is likely an API client/server if:
3323
+ # 1. Has HTTP client usage AND (auth OR structure), OR
3324
+ # 2. Has auth AND structure (e.g., OAuth2 client without explicit http calls yet), OR
3325
+ # 3. Has server-side REST framework patterns (FastAPI, Django REST)
3326
+ return (has_http_client and (has_auth or has_structure)) or (has_auth and has_structure) or has_server_api
3294
3327
 
3295
3328
  def _score_yaml_file(self, file_path: Path, code: str) -> dict[str, Any]:
3296
3329
  """
@@ -964,9 +964,7 @@ class CodeScorer(BaseScorer):
964
964
 
965
965
  Phase 6.2: Modern Quality Analysis - mypy Integration
966
966
  Phase 5 (P1): Fixed to actually run mypy and return real scores (not static 5.0).
967
-
968
- Returns:
969
- Type checking score (0-10), where 10 = no issues, 0 = many issues
967
+ ENH-002-S2: Prefer ScopedMypyExecutor (--follow-imports=skip, <10s) with fallback to full mypy.
970
968
  """
971
969
  if not self.has_mypy:
972
970
  logger.debug("mypy not available - returning neutral score")
@@ -976,10 +974,25 @@ class CodeScorer(BaseScorer):
976
974
  if file_path.suffix != ".py":
977
975
  return 10.0 # Perfect score for non-Python files (can't type check)
978
976
 
977
+ # ENH-002-S2: Try scoped mypy first (faster)
978
+ try:
979
+ from .tools.scoped_mypy import ScopedMypyExecutor
980
+ executor = ScopedMypyExecutor()
981
+ result = executor.run_scoped_sync(file_path, timeout=10)
982
+ if result.files_checked == 1 or result.issues:
983
+ error_count = len(result.issues)
984
+ if error_count == 0:
985
+ return 10.0
986
+ score = 10.0 - (error_count * 0.5)
987
+ logger.debug(
988
+ "mypy (scoped) found %s errors for %s, score: %s/10",
989
+ error_count, file_path, score,
990
+ )
991
+ return max(0.0, min(10.0, score))
992
+ except Exception as e:
993
+ logger.debug("scoped mypy not used, falling back to full mypy: %s", e)
994
+
979
995
  try:
980
- # Phase 5 fix: Actually run mypy and parse errors
981
- # Run mypy with show-error-codes for better error parsing
982
- # Optimization (ENH-002): Scoped to single file with --no-incremental for 6x speedup
983
996
  result = subprocess.run( # nosec B603
984
997
  [
985
998
  sys.executable,
@@ -988,63 +1001,44 @@ class CodeScorer(BaseScorer):
988
1001
  "--show-error-codes",
989
1002
  "--no-error-summary",
990
1003
  "--no-color-output",
991
- "--no-incremental", # Skip cache for single-file check (faster)
1004
+ "--no-incremental",
992
1005
  str(file_path),
993
1006
  ],
994
1007
  capture_output=True,
995
1008
  text=True,
996
1009
  encoding="utf-8",
997
1010
  errors="replace",
998
- timeout=30, # 30 second timeout (optimized from 60s for single file)
1011
+ timeout=30,
999
1012
  cwd=file_path.parent if file_path.parent.exists() else None,
1000
1013
  )
1001
-
1002
- # Phase 5 fix: Parse mypy output correctly
1003
1014
  if result.returncode == 0:
1004
- # No type errors found - return perfect score
1005
- logger.debug(f"mypy found no errors for {file_path}")
1015
+ logger.debug("mypy found no errors for %s", file_path)
1006
1016
  return 10.0
1007
-
1008
- # mypy returns non-zero exit code when errors found
1009
- # mypy outputs errors to stdout (not stderr)
1010
1017
  output = result.stdout.strip()
1011
1018
  if not output:
1012
- # No output but non-zero return code - assume success
1013
- logger.debug(f"mypy returned non-zero but no output for {file_path}")
1019
+ logger.debug("mypy returned non-zero but no output for %s", file_path)
1014
1020
  return 10.0
1015
-
1016
- # Parse mypy error output
1017
- # Format: filename:line: error: message [error-code]
1018
- # Or: filename:line:column: error: message [error-code]
1019
1021
  error_lines = [
1020
1022
  line
1021
1023
  for line in output.split("\n")
1022
1024
  if "error:" in line.lower() and file_path.name in line
1023
1025
  ]
1024
1026
  error_count = len(error_lines)
1025
-
1026
1027
  if error_count == 0:
1027
- # No errors found despite non-zero return code (e.g., config issues)
1028
- logger.debug(f"mypy returned non-zero but no parseable errors for {file_path}")
1028
+ logger.debug("mypy returned non-zero but no parseable errors for %s", file_path)
1029
1029
  return 10.0
1030
-
1031
- # Phase 5 fix: Calculate score based on actual error count
1032
- # Formula: 10 - (errors * 0.5), but cap at 0.0
1033
1030
  score = 10.0 - (error_count * 0.5)
1034
- logger.debug(f"mypy found {error_count} errors for {file_path}, score: {score:.1f}/10")
1031
+ logger.debug("mypy found %s errors for %s, score: %s/10", error_count, file_path, score)
1035
1032
  return max(0.0, min(10.0, score))
1036
-
1037
1033
  except subprocess.TimeoutExpired:
1038
- logger.warning(f"mypy timed out for {file_path}")
1039
- return 5.0 # Neutral on timeout
1034
+ logger.warning("mypy timed out for %s", file_path)
1035
+ return 5.0
1040
1036
  except FileNotFoundError:
1041
- # mypy not found in PATH
1042
- logger.debug(f"mypy not found in PATH for {file_path}")
1043
- self.has_mypy = False # Update availability flag
1037
+ logger.debug("mypy not found in PATH for %s", file_path)
1038
+ self.has_mypy = False
1044
1039
  return 5.0
1045
1040
  except Exception as e:
1046
- # Any other error
1047
- logger.warning(f"mypy failed for {file_path}: {e}", exc_info=True)
1041
+ logger.warning("mypy failed for %s: %s", file_path, e, exc_info=True)
1048
1042
  return 5.0
1049
1043
 
1050
1044
  def get_mypy_errors(self, file_path: Path) -> list[dict[str, Any]]:
@@ -1052,15 +1046,30 @@ class CodeScorer(BaseScorer):
1052
1046
  Get detailed mypy type checking errors for a file.
1053
1047
 
1054
1048
  Phase 6.2: Modern Quality Analysis - mypy Integration
1055
-
1056
- Returns:
1057
- List of error dictionaries with line, message, error_code, etc.
1049
+ ENH-002-S2: Prefer ScopedMypyExecutor with fallback to full mypy.
1058
1050
  """
1059
1051
  if not self.has_mypy or file_path.suffix != ".py":
1060
1052
  return []
1061
1053
 
1062
1054
  try:
1063
- # Optimization (ENH-002): Scoped to single file with --no-incremental for 6x speedup
1055
+ from .tools.scoped_mypy import ScopedMypyExecutor
1056
+ executor = ScopedMypyExecutor()
1057
+ result = executor.run_scoped_sync(file_path, timeout=10)
1058
+ if result.files_checked == 1 or result.issues:
1059
+ return [
1060
+ {
1061
+ "filename": str(i.file_path),
1062
+ "line": i.line,
1063
+ "message": i.message,
1064
+ "error_code": i.error_code,
1065
+ "severity": i.severity,
1066
+ }
1067
+ for i in result.issues
1068
+ ]
1069
+ except Exception:
1070
+ pass
1071
+
1072
+ try:
1064
1073
  result = subprocess.run( # nosec B603
1065
1074
  [
1066
1075
  sys.executable,
@@ -1068,32 +1077,22 @@ class CodeScorer(BaseScorer):
1068
1077
  "mypy",
1069
1078
  "--show-error-codes",
1070
1079
  "--no-error-summary",
1071
- "--no-incremental", # Skip cache for single-file check (faster)
1080
+ "--no-incremental",
1072
1081
  str(file_path),
1073
1082
  ],
1074
1083
  capture_output=True,
1075
1084
  text=True,
1076
1085
  encoding="utf-8",
1077
1086
  errors="replace",
1078
- timeout=30, # Optimized from 60s for single file
1087
+ timeout=30,
1079
1088
  cwd=file_path.parent if file_path.parent.exists() else None,
1080
1089
  )
1081
-
1082
- if result.returncode == 0:
1083
- # No errors
1084
- return []
1085
-
1086
- if not result.stdout.strip():
1090
+ if result.returncode == 0 or not result.stdout.strip():
1087
1091
  return []
1088
-
1089
- # Parse mypy error output
1090
- # Format: filename:line: error: message [error-code]
1091
1092
  errors = []
1092
1093
  for line in result.stdout.strip().split("\n"):
1093
1094
  if "error:" not in line.lower():
1094
1095
  continue
1095
-
1096
- # Parse line: "file.py:12: error: Missing return type [func-returns]"
1097
1096
  parts = line.split(":", 3)
1098
1097
  if len(parts) >= 4:
1099
1098
  filename = parts[0]
@@ -1101,10 +1100,7 @@ class CodeScorer(BaseScorer):
1101
1100
  line_num = int(parts[1])
1102
1101
  except ValueError:
1103
1102
  continue
1104
-
1105
1103
  error_msg = parts[3].strip()
1106
-
1107
- # Extract error code (if present)
1108
1104
  error_code = None
1109
1105
  if "[" in error_msg and "]" in error_msg:
1110
1106
  start = error_msg.rfind("[")
@@ -1112,19 +1108,14 @@ class CodeScorer(BaseScorer):
1112
1108
  if start < end:
1113
1109
  error_code = error_msg[start + 1 : end]
1114
1110
  error_msg = error_msg[:start].strip()
1115
-
1116
- errors.append(
1117
- {
1118
- "filename": filename,
1119
- "line": line_num,
1120
- "message": error_msg,
1121
- "error_code": error_code,
1122
- "severity": "error",
1123
- }
1124
- )
1125
-
1111
+ errors.append({
1112
+ "filename": filename,
1113
+ "line": line_num,
1114
+ "message": error_msg,
1115
+ "error_code": error_code,
1116
+ "severity": "error",
1117
+ })
1126
1118
  return errors
1127
-
1128
1119
  except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
1129
1120
  return []
1130
1121
 
@@ -8,9 +8,33 @@ from .parallel_executor import (
8
8
  ToolResult,
9
9
  ToolStatus,
10
10
  )
11
+ from .ruff_grouping import (
12
+ GroupedRuffIssues,
13
+ RuffGroupingConfig,
14
+ RuffGroupingParser,
15
+ RuffIssue,
16
+ RuffParsingError,
17
+ )
18
+ from .scoped_mypy import (
19
+ MypyIssue,
20
+ MypyResult,
21
+ MypyTimeoutError,
22
+ ScopedMypyConfig,
23
+ ScopedMypyExecutor,
24
+ )
11
25
 
12
26
  __all__ = [
27
+ "GroupedRuffIssues",
28
+ "MypyIssue",
29
+ "MypyResult",
30
+ "MypyTimeoutError",
13
31
  "ParallelToolExecutor",
32
+ "RuffGroupingConfig",
33
+ "RuffGroupingParser",
34
+ "RuffIssue",
35
+ "RuffParsingError",
36
+ "ScopedMypyConfig",
37
+ "ScopedMypyExecutor",
14
38
  "ToolExecutionConfig",
15
39
  "ToolResult",
16
40
  "ToolStatus",