taskboy 0.1.1__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 (95) hide show
  1. taskboy/__init__.py +0 -0
  2. taskboy/adapters/__init__.py +0 -0
  3. taskboy/adapters/_util.py +30 -0
  4. taskboy/adapters/aws_read.py +107 -0
  5. taskboy/adapters/confluence.py +88 -0
  6. taskboy/adapters/github_api.py +564 -0
  7. taskboy/adapters/issues.py +435 -0
  8. taskboy/adapters/jira.py +263 -0
  9. taskboy/adapters/sentry.py +82 -0
  10. taskboy/adapters/slack_history.py +204 -0
  11. taskboy/assets.py +27 -0
  12. taskboy/audit.py +78 -0
  13. taskboy/broker.py +266 -0
  14. taskboy/classifier.py +219 -0
  15. taskboy/cli.py +161 -0
  16. taskboy/config.py +539 -0
  17. taskboy/dashboard/__init__.py +5 -0
  18. taskboy/dashboard/api.py +963 -0
  19. taskboy/dashboard/app.py +78 -0
  20. taskboy/dashboard/auth.py +117 -0
  21. taskboy/dashboard/editors.py +140 -0
  22. taskboy/dashboard/gitops.py +60 -0
  23. taskboy/dashboard/render.py +48 -0
  24. taskboy/debug_feed.py +170 -0
  25. taskboy/deploy/env.example +17 -0
  26. taskboy/deploy/git-cred-helper.py +36 -0
  27. taskboy/deploy/install.sh +101 -0
  28. taskboy/deploy/remote-update.sh +52 -0
  29. taskboy/deploy/taskboy-restart.path +10 -0
  30. taskboy/deploy/taskboy-restart.service +8 -0
  31. taskboy/deploy/taskboy.service +22 -0
  32. taskboy/hooks.py +208 -0
  33. taskboy/issue_runs.py +203 -0
  34. taskboy/llm.py +125 -0
  35. taskboy/main.py +269 -0
  36. taskboy/memory.py +59 -0
  37. taskboy/models.py +103 -0
  38. taskboy/mrkdwn.py +19 -0
  39. taskboy/notify.py +41 -0
  40. taskboy/orchestrator.py +401 -0
  41. taskboy/personality.py +17 -0
  42. taskboy/prompts.py +288 -0
  43. taskboy/quick.py +234 -0
  44. taskboy/redact.py +49 -0
  45. taskboy/repocache.py +117 -0
  46. taskboy/review_requests.py +388 -0
  47. taskboy/router.py +146 -0
  48. taskboy/runner.py +581 -0
  49. taskboy/scheduler.py +301 -0
  50. taskboy/secrets.py +83 -0
  51. taskboy/settings.py +19 -0
  52. taskboy/setup_checks.py +184 -0
  53. taskboy/setup_wizard.py +699 -0
  54. taskboy/skills.py +95 -0
  55. taskboy/slack.py +666 -0
  56. taskboy/slack_users.py +32 -0
  57. taskboy/started_messages.py +33 -0
  58. taskboy/store.py +1393 -0
  59. taskboy/task_actions.py +86 -0
  60. taskboy/templates/config.example.yaml +149 -0
  61. taskboy/templates/conventions.md +60 -0
  62. taskboy/templates/help.example.md +29 -0
  63. taskboy/templates/personality_agent.example.md +5 -0
  64. taskboy/templates/personality_reviewer.example.md +5 -0
  65. taskboy/templates/services/aws.yaml +7 -0
  66. taskboy/templates/services/confluence.yaml +4 -0
  67. taskboy/templates/services/github.yaml +14 -0
  68. taskboy/templates/services/jira.yaml +6 -0
  69. taskboy/templates/services/sentry.yaml +4 -0
  70. taskboy/templates/services/slack.yaml +9 -0
  71. taskboy/templates/skills/README.md +33 -0
  72. taskboy/templates/skills/discoverissues/SKILL.md +46 -0
  73. taskboy/templates/skills/implementapprovedissues/SKILL.md +41 -0
  74. taskboy/templates/skills/jira2pr/SKILL.md +66 -0
  75. taskboy/templates/skills/monitor/SKILL.md +32 -0
  76. taskboy/templates/skills/monitornew/SKILL.md +27 -0
  77. taskboy/templates/skills/refineissue/SKILL.md +35 -0
  78. taskboy/templates/skills/release/SKILL.md +36 -0
  79. taskboy/templates/skills/review/SKILL.md +89 -0
  80. taskboy/templates/skills/reviewandmonitor/SKILL.md +22 -0
  81. taskboy/templates/skills/reviews/SKILL.md +26 -0
  82. taskboy/templates/skills/slack2jira/SKILL.md +47 -0
  83. taskboy/templates/skills/slack2pr/SKILL.md +64 -0
  84. taskboy/templates/skills/spec2pr/SKILL.md +35 -0
  85. taskboy/templates/slack_app_manifest.yaml +48 -0
  86. taskboy/templates/task_started_messages.yaml +14 -0
  87. taskboy/ui_dist/assets/index-DmYxR9Qy.css +1 -0
  88. taskboy/ui_dist/assets/index-LDDO4iT2.js +15 -0
  89. taskboy/ui_dist/index.html +15 -0
  90. taskboy/workspace.py +86 -0
  91. taskboy-0.1.1.dist-info/METADATA +120 -0
  92. taskboy-0.1.1.dist-info/RECORD +95 -0
  93. taskboy-0.1.1.dist-info/WHEEL +5 -0
  94. taskboy-0.1.1.dist-info/entry_points.txt +2 -0
  95. taskboy-0.1.1.dist-info/top_level.txt +1 -0
taskboy/skills.py ADDED
@@ -0,0 +1,95 @@
1
+ """repository-shipped skill discovery, parsing, and prompt rendering."""
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+ import yaml
8
+
9
+
10
+ class SkillError(ValueError):
11
+ pass
12
+
13
+
14
+ # in-process capability servers a skill may opt into via `internal_tools:` frontmatter (wired by the runner)
15
+ KNOWN_INTERNAL_TOOLS = {"issues", "enqueue"}
16
+
17
+
18
+ @dataclass
19
+ class Skill:
20
+ name: str
21
+ description: str
22
+ body: str
23
+ requires: list[str]
24
+ model: str | None = None # model alias this skill runs on (e.g. fable); None uses the config skills.tier default
25
+ profile: str | None = None # execution profile override; None uses the config skills.profile default
26
+ internal_tools: list[str] = None # type: ignore[assignment] # normalized to a list in load()
27
+
28
+
29
+ def parse_invocation(text: str) -> tuple[str, str] | None:
30
+ match = re.match(r"^/([a-z0-9_-]+)\s*(.*)$", text, flags=re.DOTALL)
31
+ if not match:
32
+ return None
33
+ return match.group(1), match.group(2)
34
+
35
+
36
+ def available(root: str | Path) -> list[str]:
37
+ path = Path(root)
38
+ if not path.is_dir():
39
+ return []
40
+ return sorted(child.name for child in path.iterdir() if child.is_dir() and (child / "SKILL.md").is_file())
41
+
42
+
43
+ def load(root: str | Path, name: str) -> Skill:
44
+ path = Path(root) / name / "SKILL.md"
45
+ try:
46
+ text = path.read_text()
47
+ except FileNotFoundError as e:
48
+ raise SkillError(f"skill /{name} is not installed") from e
49
+ match = re.match(r"\A---\s*\n(.*?)\n---\s*\n?(.*)\Z", text, flags=re.DOTALL)
50
+ if not match:
51
+ raise SkillError(f"skill /{name} has invalid frontmatter")
52
+ try:
53
+ metadata = yaml.safe_load(match.group(1))
54
+ except yaml.YAMLError as e:
55
+ raise SkillError(f"skill /{name} has invalid frontmatter: {e}") from e
56
+ if not isinstance(metadata, dict):
57
+ raise SkillError(f"skill /{name} has invalid frontmatter")
58
+ skill_name = metadata.get("name")
59
+ description = metadata.get("description")
60
+ requires = metadata.get("requires") or []
61
+ model = metadata.get("model")
62
+ profile = metadata.get("profile")
63
+ internal_tools = metadata.get("internal_tools") or []
64
+ if not isinstance(skill_name, str) or not isinstance(description, str):
65
+ raise SkillError(f"skill /{name} frontmatter needs name and description")
66
+ if skill_name != name:
67
+ raise SkillError(f"skill /{name} frontmatter name does not match its directory")
68
+ if not isinstance(requires, list) or not all(isinstance(item, str) for item in requires):
69
+ raise SkillError(f"skill /{name} requires must be a list of skill names")
70
+ if model is not None and not isinstance(model, str):
71
+ raise SkillError(f"skill /{name} model must be a model alias string")
72
+ if profile is not None and not isinstance(profile, str):
73
+ raise SkillError(f"skill /{name} profile must be a profile name string")
74
+ if not isinstance(internal_tools, list) or not all(isinstance(item, str) for item in internal_tools):
75
+ raise SkillError(f"skill /{name} internal_tools must be a list of strings")
76
+ unknown = set(internal_tools) - KNOWN_INTERNAL_TOOLS
77
+ if unknown:
78
+ raise SkillError(f"skill /{name} internal_tools contains unknown entries: {sorted(unknown)}")
79
+ return Skill(name=skill_name, description=description, body=match.group(2).rstrip(), requires=requires, model=model or None, profile=profile or None, internal_tools=internal_tools)
80
+
81
+
82
+ def render(root: str | Path, name: str) -> str:
83
+ first = load(root, name)
84
+ rendered = [first.body]
85
+ seen = {name}
86
+ queue = list(first.requires)
87
+ while queue:
88
+ required_name = queue.pop(0)
89
+ if required_name in seen:
90
+ continue
91
+ required = load(root, required_name)
92
+ seen.add(required_name)
93
+ rendered.append(f"### Included skill: /{required_name}\n{required.body}")
94
+ queue.extend(required.requires)
95
+ return "\n\n".join(rendered)