briar-cli 1.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.
- briar/__init__.py +8 -0
- briar/__main__.py +11 -0
- briar/_registry.py +36 -0
- briar/agent/__init__.py +16 -0
- briar/agent/_llm.py +90 -0
- briar/agent/_llms/__init__.py +40 -0
- briar/agent/_llms/anthropic_llm.py +185 -0
- briar/agent/_llms/bedrock.py +154 -0
- briar/agent/_llms/gemini.py +152 -0
- briar/agent/_llms/openai_llm.py +129 -0
- briar/agent/runner.py +364 -0
- briar/agent/tools.py +355 -0
- briar/auth/__init__.py +42 -0
- briar/auth/_acquirer.py +121 -0
- briar/auth/_acquirers/__init__.py +62 -0
- briar/auth/_acquirers/aws_sso.py +185 -0
- briar/auth/_acquirers/aws_static.py +54 -0
- briar/auth/_acquirers/bitbucket.py +60 -0
- briar/auth/_acquirers/github_device.py +129 -0
- briar/auth/_acquirers/github_pat.py +44 -0
- briar/auth/_acquirers/infisical.py +80 -0
- briar/auth/_acquirers/jira_session.py +102 -0
- briar/auth/_acquirers/jira_token.py +61 -0
- briar/auth/_acquirers/linear.py +46 -0
- briar/auth/_prompt.py +155 -0
- briar/cli.py +149 -0
- briar/commands/__init__.py +48 -0
- briar/commands/agent.py +692 -0
- briar/commands/auth.py +281 -0
- briar/commands/base.py +46 -0
- briar/commands/context.py +143 -0
- briar/commands/dashboard.py +61 -0
- briar/commands/extract.py +79 -0
- briar/commands/iac.py +44 -0
- briar/commands/runbook.py +110 -0
- briar/commands/secrets.py +165 -0
- briar/commands/version.py +17 -0
- briar/credentials/__init__.py +48 -0
- briar/credentials/_bootstrap.py +93 -0
- briar/credentials/_bootstraps/__init__.py +80 -0
- briar/credentials/_bootstraps/infisical.py +115 -0
- briar/credentials/_store.py +63 -0
- briar/credentials/aws_secrets.py +102 -0
- briar/credentials/envfile.py +171 -0
- briar/credentials/infisical.py +183 -0
- briar/credentials/ssm.py +83 -0
- briar/credentials/vault.py +109 -0
- briar/dashboard/__init__.py +13 -0
- briar/dashboard/collectors.py +1354 -0
- briar/dashboard/server.py +154 -0
- briar/dashboard/templates/index.html +678 -0
- briar/decorators.py +48 -0
- briar/env_vars.py +92 -0
- briar/error_policy.py +273 -0
- briar/errors.py +48 -0
- briar/extract/__init__.py +52 -0
- briar/extract/_cloud.py +157 -0
- briar/extract/_clouds/__init__.py +39 -0
- briar/extract/_clouds/aws.py +190 -0
- briar/extract/_clouds/azure.py +152 -0
- briar/extract/_clouds/gcp.py +135 -0
- briar/extract/_gh.py +159 -0
- briar/extract/_provider.py +218 -0
- briar/extract/_providers/__init__.py +60 -0
- briar/extract/_providers/bitbucket.py +276 -0
- briar/extract/_providers/github.py +277 -0
- briar/extract/_tracker.py +124 -0
- briar/extract/_trackers/__init__.py +44 -0
- briar/extract/_trackers/_jira_auth.py +258 -0
- briar/extract/_trackers/bitbucket.py +131 -0
- briar/extract/_trackers/github_issues.py +139 -0
- briar/extract/_trackers/jira.py +191 -0
- briar/extract/_trackers/linear.py +172 -0
- briar/extract/_user_filter.py +150 -0
- briar/extract/active_tickets.py +71 -0
- briar/extract/active_work.py +87 -0
- briar/extract/aws_infra.py +79 -0
- briar/extract/aws_services/__init__.py +31 -0
- briar/extract/aws_services/base.py +25 -0
- briar/extract/aws_services/ecs.py +43 -0
- briar/extract/aws_services/lambda_.py +38 -0
- briar/extract/aws_services/logs.py +39 -0
- briar/extract/aws_services/rds.py +35 -0
- briar/extract/aws_services/sqs.py +25 -0
- briar/extract/base.py +290 -0
- briar/extract/code_hotspots.py +134 -0
- briar/extract/codebase_conventions.py +72 -0
- briar/extract/composer.py +85 -0
- briar/extract/github_deployments.py +106 -0
- briar/extract/language_detectors/__init__.py +24 -0
- briar/extract/language_detectors/base.py +29 -0
- briar/extract/language_detectors/go.py +26 -0
- briar/extract/language_detectors/node.py +42 -0
- briar/extract/language_detectors/python.py +41 -0
- briar/extract/pr_archaeology.py +131 -0
- briar/extract/pr_review_context.py +123 -0
- briar/extract/reviewer_profile.py +141 -0
- briar/extract/ticket_archaeology.py +115 -0
- briar/extract/ticket_context.py +95 -0
- briar/formatting/__init__.py +67 -0
- briar/formatting/base.py +25 -0
- briar/formatting/csv.py +34 -0
- briar/formatting/json.py +19 -0
- briar/formatting/quiet.py +29 -0
- briar/formatting/table.py +97 -0
- briar/formatting/yaml.py +35 -0
- briar/iac/__init__.py +18 -0
- briar/iac/config_file.py +114 -0
- briar/iac/models.py +232 -0
- briar/iac/reference_map.py +33 -0
- briar/iac/runbook/__init__.py +32 -0
- briar/iac/runbook/executor.py +365 -0
- briar/iac/runbook/models.py +156 -0
- briar/iac/runbook/scheduler.py +187 -0
- briar/iac/scaffold/__init__.py +25 -0
- briar/iac/scaffold/_composer.py +308 -0
- briar/iac/scaffold/_knowledge.py +119 -0
- briar/iac/scaffold/archetypes/__init__.py +26 -0
- briar/iac/scaffold/archetypes/base.py +85 -0
- briar/iac/scaffold/archetypes/engineer.py +64 -0
- briar/iac/scaffold/archetypes/pr_ci_fixer.py +100 -0
- briar/iac/scaffold/archetypes/pr_conflict_resolver.py +83 -0
- briar/iac/scaffold/archetypes/pr_fixer.py +62 -0
- briar/iac/scaffold/archetypes/triager.py +50 -0
- briar/iac/scaffold/base.py +19 -0
- briar/iac/scaffold/implementation.py +59 -0
- briar/iac/scaffold/pr_fixes.py +52 -0
- briar/iac/scaffold/rules/__init__.py +64 -0
- briar/iac/scaffold/rules/base.py +121 -0
- briar/iac/scaffold/rules/commit_as_human.md +22 -0
- briar/iac/scaffold/rules/minimum_correct_fix.md +20 -0
- briar/iac/scaffold/rules/no_force_push.md +19 -0
- briar/iac/scaffold/rules/no_new_pr_creation.md +16 -0
- briar/iac/scaffold/rules/no_workflow_file_edits.md +21 -0
- briar/iac/scaffold/rules/read_all_comments_first.md +20 -0
- briar/iac/scaffold/rules/skip_approved_green_prs.md +17 -0
- briar/iac/scaffold/shapes/__init__.py +38 -0
- briar/iac/scaffold/shapes/base.py +17 -0
- briar/iac/scaffold/shapes/one_shot.py +48 -0
- briar/iac/scaffold/shapes/plan_approve_act.py +134 -0
- briar/iac/scaffold/shapes/triage.py +49 -0
- briar/iac/scaffold/sources/__init__.py +26 -0
- briar/iac/scaffold/sources/aws.py +69 -0
- briar/iac/scaffold/sources/base.py +61 -0
- briar/iac/scaffold/sources/bitbucket.py +164 -0
- briar/iac/scaffold/sources/github.py +155 -0
- briar/iac/scaffold/sources/jira.py +147 -0
- briar/iac/scaffold/triggers/__init__.py +23 -0
- briar/iac/scaffold/triggers/base.py +24 -0
- briar/iac/scaffold/triggers/bitbucket_webhook.py +54 -0
- briar/iac/scaffold/triggers/github_webhook.py +52 -0
- briar/iac/scaffold/triggers/manual.py +16 -0
- briar/iac/scaffold/triggers/schedule_cron.py +37 -0
- briar/log_context.py +73 -0
- briar/logging.py +68 -0
- briar/messaging/__init__.py +66 -0
- briar/messaging/_writer.py +90 -0
- briar/messaging/bitbucket_pr_comment.py +93 -0
- briar/messaging/github_pr_comment.py +90 -0
- briar/messaging/jira_comment.py +63 -0
- briar/messaging/jira_transition.py +71 -0
- briar/messaging/slack_channel.py +73 -0
- briar/messaging/telegram_chat.py +68 -0
- briar/notify/__init__.py +44 -0
- briar/notify/_sink.py +27 -0
- briar/notify/email.py +59 -0
- briar/notify/pagerduty.py +67 -0
- briar/notify/slack.py +49 -0
- briar/notify/telegram.py +48 -0
- briar/pagination.py +37 -0
- briar/settings.py +3 -0
- briar/storage/__init__.py +73 -0
- briar/storage/base.py +137 -0
- briar/storage/file.py +111 -0
- briar/storage/postgres.py +353 -0
- briar_cli-1.1.1.dist-info/METADATA +1031 -0
- briar_cli-1.1.1.dist-info/RECORD +179 -0
- briar_cli-1.1.1.dist-info/WHEEL +4 -0
- briar_cli-1.1.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""SourceTemplate contract — Strategy pattern shared by every source kind.
|
|
2
|
+
|
|
3
|
+
A source template knows:
|
|
4
|
+
- its ``kind`` (e.g. ``"github"``, ``"jira"``, ``"aws"``)
|
|
5
|
+
- its ``family`` (``"tracker"`` or ``"cloud"``) — used by the scaffold to
|
|
6
|
+
decide whether the agent should be given action tools for it
|
|
7
|
+
- how to build a `Source` dict (``build_source``)
|
|
8
|
+
- how to build the matching action `Tool` dicts (``build_tools``) — for a
|
|
9
|
+
cloud source this is typically empty (read-only context); for a tracker
|
|
10
|
+
it's a list of comment/transition tools
|
|
11
|
+
|
|
12
|
+
Implementations live in sibling modules and self-register into
|
|
13
|
+
`SOURCE_TEMPLATES` via the package `__init__`."""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import Any, ClassVar, Dict, List, Literal
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SourceTemplate(ABC):
|
|
23
|
+
"""Abstract base. Subclasses set the class attributes + implement
|
|
24
|
+
`build_source`. `build_tools` defaults to an empty list (read-only
|
|
25
|
+
source); override for trackers."""
|
|
26
|
+
|
|
27
|
+
kind: ClassVar[str] = ""
|
|
28
|
+
family: ClassVar[Literal["tracker", "cloud", ""]] = ""
|
|
29
|
+
auth_secret_arg: ClassVar[str] = ""
|
|
30
|
+
default_provider_for_oauth: ClassVar[str] = ""
|
|
31
|
+
|
|
32
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
33
|
+
"""Subclasses add their own flags (e.g. `--jira-secret-id`)."""
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def build_source(
|
|
37
|
+
self,
|
|
38
|
+
args: argparse.Namespace,
|
|
39
|
+
key_prefix: str,
|
|
40
|
+
) -> Dict[str, Any]:
|
|
41
|
+
"""Emit the source dict the scaffold composer will include."""
|
|
42
|
+
|
|
43
|
+
def build_tools(
|
|
44
|
+
self,
|
|
45
|
+
args: argparse.Namespace,
|
|
46
|
+
key_prefix: str,
|
|
47
|
+
) -> List[Dict[str, Any]]:
|
|
48
|
+
"""Default: source contributes no action tools. Override for
|
|
49
|
+
trackers to expose comment/transition/etc. tools."""
|
|
50
|
+
return []
|
|
51
|
+
|
|
52
|
+
def target(self, args: argparse.Namespace) -> str:
|
|
53
|
+
"""Human-readable identifier for this source (e.g. ``owner/repo``).
|
|
54
|
+
|
|
55
|
+
Used by ``ScaffoldComposer`` to interpolate ``{target}`` into the
|
|
56
|
+
archetype's role/goal/backstory. The default returns ``""`` because
|
|
57
|
+
cloud sources (AWS) don't carry a single target string. Tracker
|
|
58
|
+
sources (GitHub, Bitbucket, Jira) override this to return their
|
|
59
|
+
repo / project identifier; the scaffold template picks the first
|
|
60
|
+
non-empty result among the selected sources."""
|
|
61
|
+
return ""
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Bitbucket source template.
|
|
2
|
+
|
|
3
|
+
Family: `tracker`. Reads issues + pull requests from a single
|
|
4
|
+
``<workspace>/<repo_slug>`` Bitbucket Cloud repository. Brings the same
|
|
5
|
+
three action tools as the GitHub source — `bitbucket.comment_on_issue`,
|
|
6
|
+
`bitbucket.open_pr`, `bitbucket.commit_files` — so an archetype's
|
|
7
|
+
substring `tool_filter` matches on `commit`, `comment_on_issue`, and
|
|
8
|
+
`open_pr` works unchanged.
|
|
9
|
+
|
|
10
|
+
Auth: stored Bitbucket app-password (basic auth: username + app
|
|
11
|
+
password) by default, or an OAuth connection via
|
|
12
|
+
``--auth-mode oauth`` (the downstream runtime resolves the binding to
|
|
13
|
+
the operator's Atlassian/Bitbucket OAuth grant).
|
|
14
|
+
|
|
15
|
+
User filters (`--bitbucket-authors-allow` / `-block`,
|
|
16
|
+
`--bitbucket-assignees-allow` / `-block`) restrict which issues the
|
|
17
|
+
agent sees. Filters compose: ``allow ∩ ¬block``."""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
from typing import Any, Dict, List
|
|
23
|
+
|
|
24
|
+
from briar.iac.scaffold.sources.base import SourceTemplate
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SourceBitbucket(SourceTemplate):
|
|
28
|
+
kind = "bitbucket"
|
|
29
|
+
family = "tracker"
|
|
30
|
+
default_provider_for_oauth = "bitbucket"
|
|
31
|
+
|
|
32
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--bitbucket-workspace",
|
|
35
|
+
help="Bitbucket workspace slug (the part before `/` in a repo URL)",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--bitbucket-repo",
|
|
39
|
+
help="Bitbucket repository slug (the part after `/`)",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--bitbucket-secret-id",
|
|
43
|
+
help="Secret UUID holding a Bitbucket app-password " "(username + app_password stored together); required with --auth-mode pat",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--bitbucket-authors-allow",
|
|
47
|
+
action="append",
|
|
48
|
+
default=[],
|
|
49
|
+
help="only include issues whose reporter is in this list (repeatable)",
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"--bitbucket-authors-block",
|
|
53
|
+
action="append",
|
|
54
|
+
default=[],
|
|
55
|
+
help="exclude issues whose reporter is in this list (repeatable)",
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--bitbucket-assignees-allow",
|
|
59
|
+
action="append",
|
|
60
|
+
default=[],
|
|
61
|
+
help="only include issues with an assignee in this list (repeatable)",
|
|
62
|
+
)
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"--bitbucket-assignees-block",
|
|
65
|
+
action="append",
|
|
66
|
+
default=[],
|
|
67
|
+
help="exclude issues with an assignee in this list (repeatable)",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def build_source(
|
|
71
|
+
self,
|
|
72
|
+
args: argparse.Namespace,
|
|
73
|
+
key_prefix: str,
|
|
74
|
+
) -> Dict[str, Any]:
|
|
75
|
+
ns = vars(args)
|
|
76
|
+
workspace = ns.get("bitbucket_workspace")
|
|
77
|
+
repo = ns.get("bitbucket_repo")
|
|
78
|
+
if not workspace or not repo:
|
|
79
|
+
raise SystemExit("--source bitbucket requires --bitbucket-workspace AND --bitbucket-repo")
|
|
80
|
+
|
|
81
|
+
config: Dict[str, Any] = {
|
|
82
|
+
"workspace": workspace,
|
|
83
|
+
"repo": f"{workspace}/{repo}",
|
|
84
|
+
"include": "open",
|
|
85
|
+
}
|
|
86
|
+
for key, values in self._user_filters(args).items():
|
|
87
|
+
if values:
|
|
88
|
+
config[key] = values
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"key": f"{key_prefix}-bb-issues",
|
|
92
|
+
"name": f"{key_prefix}-bb-issues",
|
|
93
|
+
"kind": "bitbucket",
|
|
94
|
+
"config": config,
|
|
95
|
+
**self._auth(args),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
def build_tools(
|
|
99
|
+
self,
|
|
100
|
+
args: argparse.Namespace,
|
|
101
|
+
key_prefix: str,
|
|
102
|
+
) -> List[Dict[str, Any]]:
|
|
103
|
+
auth = self._auth(args)
|
|
104
|
+
return [
|
|
105
|
+
{
|
|
106
|
+
"key": f"{key_prefix}-bb-comment",
|
|
107
|
+
"name": f"{key_prefix}-bb-comment",
|
|
108
|
+
"description": "Post a comment on a Bitbucket issue/PR",
|
|
109
|
+
"implementation_ref": "bitbucket.comment_on_issue",
|
|
110
|
+
"side_effect": "mutate",
|
|
111
|
+
**auth,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"key": f"{key_prefix}-bb-open-pr",
|
|
115
|
+
"name": f"{key_prefix}-bb-open-pr",
|
|
116
|
+
"description": "Open a draft pull request",
|
|
117
|
+
"implementation_ref": "bitbucket.open_pr",
|
|
118
|
+
"side_effect": "mutate",
|
|
119
|
+
**auth,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"key": f"{key_prefix}-bb-commit",
|
|
123
|
+
"name": f"{key_prefix}-bb-commit",
|
|
124
|
+
"description": "Commit files to a branch",
|
|
125
|
+
"implementation_ref": "bitbucket.commit_files",
|
|
126
|
+
"side_effect": "mutate",
|
|
127
|
+
**auth,
|
|
128
|
+
},
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
def target(self, args: argparse.Namespace) -> str:
|
|
132
|
+
ns = vars(args)
|
|
133
|
+
workspace = ns.get("bitbucket_workspace") or ""
|
|
134
|
+
repo = ns.get("bitbucket_repo") or ""
|
|
135
|
+
if not workspace or not repo:
|
|
136
|
+
return ""
|
|
137
|
+
return f"{workspace}/{repo}"
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def _auth(args: argparse.Namespace) -> Dict[str, Any]:
|
|
141
|
+
ns = vars(args)
|
|
142
|
+
mode = ns.get("auth_mode") or "oauth"
|
|
143
|
+
if mode == "pat":
|
|
144
|
+
secret_id = ns.get("bitbucket_secret_id")
|
|
145
|
+
if not secret_id:
|
|
146
|
+
raise SystemExit("--source bitbucket with --auth-mode pat requires " "--bitbucket-secret-id <secret-uuid>")
|
|
147
|
+
return {"credentials_ref": secret_id, "credential_binding": None}
|
|
148
|
+
return {
|
|
149
|
+
"credentials_ref": None,
|
|
150
|
+
"credential_binding": {
|
|
151
|
+
"kind": "oauth_connection",
|
|
152
|
+
"provider": "bitbucket",
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def _user_filters(args: argparse.Namespace) -> Dict[str, List[str]]:
|
|
158
|
+
ns = vars(args)
|
|
159
|
+
return {
|
|
160
|
+
"authors_allow": list(ns.get("bitbucket_authors_allow") or []),
|
|
161
|
+
"authors_block": list(ns.get("bitbucket_authors_block") or []),
|
|
162
|
+
"assignees_allow": list(ns.get("bitbucket_assignees_allow") or []),
|
|
163
|
+
"assignees_block": list(ns.get("bitbucket_assignees_block") or []),
|
|
164
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""GitHub source template.
|
|
2
|
+
|
|
3
|
+
Family: `tracker`. Reads issues from `<owner>/<repo>`. Brings action
|
|
4
|
+
tools for commenting on issues, opening PRs, and committing files.
|
|
5
|
+
|
|
6
|
+
User filters (`--github-authors-allow`, `--github-authors-block`,
|
|
7
|
+
`--github-assignees-allow`, `--github-assignees-block`) restrict which
|
|
8
|
+
issues the agent sees. Filters compose: allow ∩ ¬block."""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
from typing import Any, Dict, List
|
|
14
|
+
|
|
15
|
+
from briar.iac.scaffold.sources.base import SourceTemplate
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SourceGithub(SourceTemplate):
|
|
19
|
+
kind = "github"
|
|
20
|
+
family = "tracker"
|
|
21
|
+
default_provider_for_oauth = "github"
|
|
22
|
+
|
|
23
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
24
|
+
# Identity flags for the GitHub source. Registered here (not on
|
|
25
|
+
# the scaffold templates) so a Bitbucket-only or AWS-only scaffold
|
|
26
|
+
# doesn't have GitHub-shaped required flags. Validation that they
|
|
27
|
+
# are set happens in `build_source` when `--source github` is
|
|
28
|
+
# actually selected.
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--owner",
|
|
31
|
+
help="GitHub org/user (required when --source includes github)",
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--repo",
|
|
35
|
+
help="GitHub repo name (required when --source includes github)",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--github-authors-allow",
|
|
39
|
+
action="append",
|
|
40
|
+
default=[],
|
|
41
|
+
help="only include issues whose creator is in this list (repeatable)",
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--github-authors-block",
|
|
45
|
+
action="append",
|
|
46
|
+
default=[],
|
|
47
|
+
help="exclude issues whose creator is in this list (repeatable)",
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"--github-assignees-allow",
|
|
51
|
+
action="append",
|
|
52
|
+
default=[],
|
|
53
|
+
help="only include issues with an assignee in this list (repeatable)",
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--github-assignees-block",
|
|
57
|
+
action="append",
|
|
58
|
+
default=[],
|
|
59
|
+
help="exclude issues with an assignee in this list (repeatable)",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def build_source(
|
|
63
|
+
self,
|
|
64
|
+
args: argparse.Namespace,
|
|
65
|
+
key_prefix: str,
|
|
66
|
+
) -> Dict[str, Any]:
|
|
67
|
+
ns = vars(args)
|
|
68
|
+
owner = ns.get("owner")
|
|
69
|
+
repo = ns.get("repo")
|
|
70
|
+
if not owner or not repo:
|
|
71
|
+
raise SystemExit("--source github requires --owner AND --repo")
|
|
72
|
+
config: Dict[str, Any] = {
|
|
73
|
+
"owner": owner,
|
|
74
|
+
"repo": f"{owner}/{repo}",
|
|
75
|
+
"include": "open",
|
|
76
|
+
}
|
|
77
|
+
for key, values in self._user_filters(args).items():
|
|
78
|
+
if values:
|
|
79
|
+
config[key] = values
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
"key": f"{key_prefix}-gh-issues",
|
|
83
|
+
"name": f"{key_prefix}-gh-issues",
|
|
84
|
+
"kind": "github",
|
|
85
|
+
"config": config,
|
|
86
|
+
**self._auth(args),
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
def build_tools(
|
|
90
|
+
self,
|
|
91
|
+
args: argparse.Namespace,
|
|
92
|
+
key_prefix: str,
|
|
93
|
+
) -> List[Dict[str, Any]]:
|
|
94
|
+
auth = self._auth(args)
|
|
95
|
+
return [
|
|
96
|
+
{
|
|
97
|
+
"key": f"{key_prefix}-gh-comment",
|
|
98
|
+
"name": f"{key_prefix}-gh-comment",
|
|
99
|
+
"description": "Post a comment on a GitHub issue/PR",
|
|
100
|
+
"implementation_ref": "github.comment_on_issue",
|
|
101
|
+
"side_effect": "mutate",
|
|
102
|
+
**auth,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"key": f"{key_prefix}-gh-open-pr",
|
|
106
|
+
"name": f"{key_prefix}-gh-open-pr",
|
|
107
|
+
"description": "Open a draft pull request",
|
|
108
|
+
"implementation_ref": "github.open_pr",
|
|
109
|
+
"side_effect": "mutate",
|
|
110
|
+
**auth,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"key": f"{key_prefix}-gh-commit",
|
|
114
|
+
"name": f"{key_prefix}-gh-commit",
|
|
115
|
+
"description": "Commit files to a branch",
|
|
116
|
+
"implementation_ref": "github.commit_files",
|
|
117
|
+
"side_effect": "mutate",
|
|
118
|
+
**auth,
|
|
119
|
+
},
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
def target(self, args: argparse.Namespace) -> str:
|
|
123
|
+
ns = vars(args)
|
|
124
|
+
owner = ns.get("owner") or ""
|
|
125
|
+
repo = ns.get("repo") or ""
|
|
126
|
+
if not owner or not repo:
|
|
127
|
+
return ""
|
|
128
|
+
return f"{owner}/{repo}"
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def _auth(args: argparse.Namespace) -> Dict[str, Any]:
|
|
132
|
+
ns = vars(args)
|
|
133
|
+
mode = ns.get("auth_mode") or "oauth"
|
|
134
|
+
if mode == "pat":
|
|
135
|
+
secret_id = ns.get("github_secret_id")
|
|
136
|
+
if not secret_id:
|
|
137
|
+
raise SystemExit("--source github with --auth-mode pat requires " "--github-secret-id <secret-uuid>")
|
|
138
|
+
return {"credentials_ref": secret_id, "credential_binding": None}
|
|
139
|
+
return {
|
|
140
|
+
"credentials_ref": None,
|
|
141
|
+
"credential_binding": {
|
|
142
|
+
"kind": "oauth_connection",
|
|
143
|
+
"provider": "github",
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def _user_filters(args: argparse.Namespace) -> Dict[str, List[str]]:
|
|
149
|
+
ns = vars(args)
|
|
150
|
+
return {
|
|
151
|
+
"authors_allow": list(ns.get("github_authors_allow") or []),
|
|
152
|
+
"authors_block": list(ns.get("github_authors_block") or []),
|
|
153
|
+
"assignees_allow": list(ns.get("github_assignees_allow") or []),
|
|
154
|
+
"assignees_block": list(ns.get("github_assignees_block") or []),
|
|
155
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Jira source template.
|
|
2
|
+
|
|
3
|
+
Family: `tracker`. Reads issues from one or more Jira projects.
|
|
4
|
+
Brings action tools: `jira.comment`, `jira.transition`, `jira.update_issue`.
|
|
5
|
+
|
|
6
|
+
Auth: Atlassian OAuth connection by default, or a stored Atlassian PAT
|
|
7
|
+
via `--jira-secret-id`."""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
from typing import Any, Dict, List
|
|
13
|
+
|
|
14
|
+
from briar.iac.scaffold.sources.base import SourceTemplate
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SourceJira(SourceTemplate):
|
|
18
|
+
kind = "jira"
|
|
19
|
+
family = "tracker"
|
|
20
|
+
default_provider_for_oauth = "atlassian"
|
|
21
|
+
|
|
22
|
+
_FILTER_FIELDS = (
|
|
23
|
+
("authors_allow", "jira_authors_allow"),
|
|
24
|
+
("authors_block", "jira_authors_block"),
|
|
25
|
+
("assignees_allow", "jira_assignees_allow"),
|
|
26
|
+
("assignees_block", "jira_assignees_block"),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--jira-project",
|
|
32
|
+
action="append",
|
|
33
|
+
default=[],
|
|
34
|
+
help="Jira project key to include (repeatable; defaults to all)",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--jira-jql",
|
|
38
|
+
help="Optional JQL filter applied on top of the project list",
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--jira-secret-id",
|
|
42
|
+
help="Secret UUID holding an Atlassian PAT (skip OAuth)",
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--jira-authors-allow",
|
|
46
|
+
action="append",
|
|
47
|
+
default=[],
|
|
48
|
+
help="reporter allowlist (repeatable; folds into JQL)",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--jira-authors-block",
|
|
52
|
+
action="append",
|
|
53
|
+
default=[],
|
|
54
|
+
help="reporter blocklist (repeatable; folds into JQL)",
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--jira-assignees-allow",
|
|
58
|
+
action="append",
|
|
59
|
+
default=[],
|
|
60
|
+
help="assignee allowlist (repeatable; folds into JQL)",
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"--jira-assignees-block",
|
|
64
|
+
action="append",
|
|
65
|
+
default=[],
|
|
66
|
+
help="assignee blocklist (repeatable; folds into JQL)",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def build_source(
|
|
70
|
+
self,
|
|
71
|
+
args: argparse.Namespace,
|
|
72
|
+
key_prefix: str,
|
|
73
|
+
) -> Dict[str, Any]:
|
|
74
|
+
ns = vars(args)
|
|
75
|
+
config: Dict[str, Any] = {"include": "open"}
|
|
76
|
+
projects = ns.get("jira_project") or []
|
|
77
|
+
if projects:
|
|
78
|
+
config["projects"] = projects
|
|
79
|
+
jql = ns.get("jira_jql")
|
|
80
|
+
if jql:
|
|
81
|
+
config["jql"] = jql
|
|
82
|
+
for field, attr in self._FILTER_FIELDS:
|
|
83
|
+
values = list(ns.get(attr) or [])
|
|
84
|
+
if values:
|
|
85
|
+
config[field] = values
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
"key": f"{key_prefix}-jira",
|
|
89
|
+
"name": f"{key_prefix}-jira",
|
|
90
|
+
"kind": "jira",
|
|
91
|
+
"config": config,
|
|
92
|
+
**self._auth(args),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
def build_tools(
|
|
96
|
+
self,
|
|
97
|
+
args: argparse.Namespace,
|
|
98
|
+
key_prefix: str,
|
|
99
|
+
) -> List[Dict[str, Any]]:
|
|
100
|
+
auth = self._auth(args)
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
"key": f"{key_prefix}-jira-comment",
|
|
104
|
+
"name": f"{key_prefix}-jira-comment",
|
|
105
|
+
"description": "Add a comment to a Jira issue",
|
|
106
|
+
"implementation_ref": "jira.comment",
|
|
107
|
+
"side_effect": "mutate",
|
|
108
|
+
**auth,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"key": f"{key_prefix}-jira-transition",
|
|
112
|
+
"name": f"{key_prefix}-jira-transition",
|
|
113
|
+
"description": "Transition a Jira issue to a new status",
|
|
114
|
+
"implementation_ref": "jira.transition",
|
|
115
|
+
"side_effect": "mutate",
|
|
116
|
+
**auth,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"key": f"{key_prefix}-jira-update",
|
|
120
|
+
"name": f"{key_prefix}-jira-update",
|
|
121
|
+
"description": "Update fields on a Jira issue",
|
|
122
|
+
"implementation_ref": "jira.update_issue",
|
|
123
|
+
"side_effect": "mutate",
|
|
124
|
+
**auth,
|
|
125
|
+
},
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
def target(self, args: argparse.Namespace) -> str:
|
|
129
|
+
ns = vars(args)
|
|
130
|
+
projects = ns.get("jira_project") or []
|
|
131
|
+
if not projects:
|
|
132
|
+
return ""
|
|
133
|
+
return projects[0]
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _auth(args: argparse.Namespace) -> Dict[str, Any]:
|
|
137
|
+
ns = vars(args)
|
|
138
|
+
secret_id = ns.get("jira_secret_id")
|
|
139
|
+
if secret_id:
|
|
140
|
+
return {"credentials_ref": secret_id, "credential_binding": None}
|
|
141
|
+
return {
|
|
142
|
+
"credentials_ref": None,
|
|
143
|
+
"credential_binding": {
|
|
144
|
+
"kind": "oauth_connection",
|
|
145
|
+
"provider": "atlassian",
|
|
146
|
+
},
|
|
147
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Trigger kind registry. One file per kind; this module is the
|
|
2
|
+
assembly point."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Dict
|
|
7
|
+
|
|
8
|
+
from briar._registry import build_registry
|
|
9
|
+
from briar.iac.scaffold.triggers.base import TriggerTemplate
|
|
10
|
+
from briar.iac.scaffold.triggers.bitbucket_webhook import TriggerBitbucketWebhook
|
|
11
|
+
from briar.iac.scaffold.triggers.github_webhook import TriggerGithubWebhook
|
|
12
|
+
from briar.iac.scaffold.triggers.manual import TriggerManual
|
|
13
|
+
from briar.iac.scaffold.triggers.schedule_cron import TriggerScheduleCron
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
TRIGGER_TEMPLATES: Dict[str, TriggerTemplate] = build_registry(
|
|
17
|
+
(TriggerGithubWebhook(), TriggerBitbucketWebhook(), TriggerScheduleCron(), TriggerManual()),
|
|
18
|
+
kind="scaffold trigger",
|
|
19
|
+
name_attr="kind",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__all__ = ["TriggerTemplate", "TRIGGER_TEMPLATES"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""`TriggerTemplate` contract — Strategy shared by every trigger kind."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Any, ClassVar, Dict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TriggerTemplate(ABC):
|
|
11
|
+
"""Subclasses set `kind` + implement `build_trigger`.
|
|
12
|
+
|
|
13
|
+
`build_trigger` returns the trigger dict or `{}` to signal "no
|
|
14
|
+
trigger row — invocation is manual". Callers check truthiness."""
|
|
15
|
+
|
|
16
|
+
kind: ClassVar[str] = ""
|
|
17
|
+
description: ClassVar[str] = ""
|
|
18
|
+
|
|
19
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
20
|
+
"""Default: no extra flags."""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def build_trigger(self, args: argparse.Namespace, key_prefix: str, workflow_key: str) -> Dict[str, Any]:
|
|
24
|
+
"""Emit the trigger dict, or `{}` when invocation is manual."""
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Bitbucket webhook trigger — fires on `issue:created` / `issue:updated`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
from briar.iac.scaffold.triggers.base import TriggerTemplate
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TriggerBitbucketWebhook(TriggerTemplate):
|
|
12
|
+
kind = "bitbucket_webhook"
|
|
13
|
+
description = "Fires on Bitbucket webhook events (issue:created, …)"
|
|
14
|
+
|
|
15
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
16
|
+
parser.add_argument(
|
|
17
|
+
"--bitbucket-webhook-events",
|
|
18
|
+
action="append",
|
|
19
|
+
default=[],
|
|
20
|
+
help="Bitbucket event names (default: issue:created, issue:updated)",
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--bitbucket-webhook-labels",
|
|
24
|
+
action="append",
|
|
25
|
+
default=["briar"],
|
|
26
|
+
help="Restrict to issues with any of these labels (default: briar)",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def build_trigger(
|
|
30
|
+
self,
|
|
31
|
+
args: argparse.Namespace,
|
|
32
|
+
key_prefix: str,
|
|
33
|
+
workflow_key: str,
|
|
34
|
+
) -> Dict[str, Any]:
|
|
35
|
+
ns = vars(args)
|
|
36
|
+
events = ns.get("bitbucket_webhook_events") or ["issue:created", "issue:updated"]
|
|
37
|
+
labels = ns.get("bitbucket_webhook_labels") or ["briar"]
|
|
38
|
+
return {
|
|
39
|
+
"key": f"{key_prefix}-trigger",
|
|
40
|
+
"name": f"{key_prefix}-trigger",
|
|
41
|
+
"kind": "bitbucket_webhook",
|
|
42
|
+
"workflow_key": workflow_key,
|
|
43
|
+
"filter_rules": {
|
|
44
|
+
"events": events,
|
|
45
|
+
"labels_any": labels,
|
|
46
|
+
},
|
|
47
|
+
"payload_to_context_mapping": {
|
|
48
|
+
"issue_number": "$.issue.id",
|
|
49
|
+
"issue_title": "$.issue.title",
|
|
50
|
+
"issue_body": "$.issue.content.raw",
|
|
51
|
+
"repo": "$.repository.full_name",
|
|
52
|
+
},
|
|
53
|
+
"is_enabled": True,
|
|
54
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""GitHub webhook trigger — fires on `issues.opened` / `issues.labeled`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
from briar.iac.scaffold.triggers.base import TriggerTemplate
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TriggerGithubWebhook(TriggerTemplate):
|
|
12
|
+
kind = "github_webhook"
|
|
13
|
+
description = "Fires on GitHub webhook events (issues.opened, …)"
|
|
14
|
+
|
|
15
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
16
|
+
parser.add_argument(
|
|
17
|
+
"--webhook-events",
|
|
18
|
+
action="append",
|
|
19
|
+
default=[],
|
|
20
|
+
help="Github event names (default: issues.opened, issues.labeled)",
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--webhook-labels",
|
|
24
|
+
action="append",
|
|
25
|
+
default=["briar"],
|
|
26
|
+
help="Restrict to issues with any of these labels (default: briar)",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def build_trigger(
|
|
30
|
+
self,
|
|
31
|
+
args: argparse.Namespace,
|
|
32
|
+
key_prefix: str,
|
|
33
|
+
workflow_key: str,
|
|
34
|
+
) -> Dict[str, Any]:
|
|
35
|
+
events = args.webhook_events or ["issues.opened", "issues.labeled"]
|
|
36
|
+
return {
|
|
37
|
+
"key": f"{key_prefix}-trigger",
|
|
38
|
+
"name": f"{key_prefix}-trigger",
|
|
39
|
+
"kind": "github_webhook",
|
|
40
|
+
"workflow_key": workflow_key,
|
|
41
|
+
"filter_rules": {
|
|
42
|
+
"events": events,
|
|
43
|
+
"labels_any": args.webhook_labels,
|
|
44
|
+
},
|
|
45
|
+
"payload_to_context_mapping": {
|
|
46
|
+
"issue_number": "$.issue.number",
|
|
47
|
+
"issue_title": "$.issue.title",
|
|
48
|
+
"issue_body": "$.issue.body",
|
|
49
|
+
"repo": "$.repository.full_name",
|
|
50
|
+
},
|
|
51
|
+
"is_enabled": True,
|
|
52
|
+
}
|