agent-devkit 0.0.4 → 0.1.0
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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/runtime/README.md +3 -0
- package/runtime/agent +29 -13
- package/runtime/agents/README.md +3 -0
- package/runtime/agents/github-pr-reviewer/AGENTS.md +10 -0
- package/runtime/agents/github-pr-reviewer/README.md +7 -0
- package/runtime/agents/github-pr-reviewer/agent.yaml +33 -0
- package/runtime/agents/github-pr-reviewer/capabilities/create-review-automation/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/create-review-automation/decision-rules.md +8 -0
- package/runtime/agents/github-pr-reviewer/capabilities/create-review-automation/workflow.md +9 -0
- package/runtime/agents/github-pr-reviewer/capabilities/inspect-pr/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/inspect-pr/decision-rules.md +7 -0
- package/runtime/agents/github-pr-reviewer/capabilities/inspect-pr/workflow.md +8 -0
- package/runtime/agents/github-pr-reviewer/capabilities/list-review-requests/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/list-review-requests/decision-rules.md +7 -0
- package/runtime/agents/github-pr-reviewer/capabilities/list-review-requests/workflow.md +8 -0
- package/runtime/agents/github-pr-reviewer/capabilities/review-pr-diff/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/review-pr-diff/decision-rules.md +9 -0
- package/runtime/agents/github-pr-reviewer/capabilities/review-pr-diff/workflow.md +10 -0
- package/runtime/agents/github-pr-reviewer/infra/README.md +7 -0
- package/runtime/agents/github-pr-reviewer/knowledge/context.md +7 -0
- package/runtime/agents/github-pr-reviewer/knowledge/system.md +17 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-automation-output.md +10 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-inspection-output.md +9 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-list-output.md +9 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-review-output.md +17 -0
- package/runtime/cli/aikit/__init__.py +1 -1
- package/runtime/cli/aikit/aliases.py +196 -0
- package/runtime/cli/aikit/app_home.py +78 -0
- package/runtime/cli/aikit/audit.py +344 -0
- package/runtime/cli/aikit/calendar.py +163 -0
- package/runtime/cli/aikit/diagnostics.py +9 -0
- package/runtime/cli/aikit/github_pr.py +254 -0
- package/runtime/cli/aikit/identity.py +75 -0
- package/runtime/cli/aikit/llm.py +240 -48
- package/runtime/cli/aikit/main.py +924 -24
- package/runtime/cli/aikit/memory.py +148 -8
- package/runtime/cli/aikit/notifications.py +9 -0
- package/runtime/cli/aikit/permissions.py +345 -0
- package/runtime/cli/aikit/personality.py +123 -0
- package/runtime/cli/aikit/providers.py +4 -5
- package/runtime/cli/aikit/scheduler.py +18 -0
- package/runtime/cli/aikit/sessions.py +396 -0
- package/runtime/cli/aikit/setup_wizard.py +12 -0
- package/runtime/cli/aikit/tasks.py +351 -0
- package/runtime/cli/aikit/toolchain.py +274 -0
- package/runtime/providers/calendar.yaml +28 -0
- package/runtime/providers/github.yaml +42 -0
- package/runtime/providers/local-notification.yaml +19 -0
- package/runtime/providers/local-scheduler.yaml +24 -0
- package/runtime/vendor/skills/napkin/napkin.md +13 -3
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""GitHub PR reviewer MVP using the GitHub CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from cli.aikit.memory import redact_secrets
|
|
11
|
+
from cli.aikit.permissions import permission_check
|
|
12
|
+
from cli.aikit.tasks import create_task
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
GH_TIMEOUT_SECONDS = 120
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def pr_list_review_requests() -> dict[str, Any]:
|
|
19
|
+
check = gh_check()
|
|
20
|
+
if check["status"] != "ok":
|
|
21
|
+
return check
|
|
22
|
+
result = run_gh(["pr", "list", "--review-requested", "@me", "--json", "number,title,url,author,headRefName,baseRefName,isDraft"])
|
|
23
|
+
if result["status"] != "ok":
|
|
24
|
+
return result
|
|
25
|
+
prs = parse_json_list(result["stdout"])
|
|
26
|
+
return {"kind": "pr", "status": "ok", "mode": "report-only", "items": prs, "count": len(prs), "provider": "github"}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def pr_inspect(pr_ref: str) -> dict[str, Any]:
|
|
30
|
+
check = gh_check()
|
|
31
|
+
if check["status"] != "ok":
|
|
32
|
+
return check
|
|
33
|
+
result = run_gh(["pr", "view", pr_ref, "--json", "number,title,url,author,body,headRefName,baseRefName,state,isDraft,reviewDecision,mergeable"])
|
|
34
|
+
if result["status"] != "ok":
|
|
35
|
+
return result
|
|
36
|
+
return {"kind": "pr", "status": "ok", "mode": "report-only", "item": parse_json_object(result["stdout"]), "provider": "github"}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def pr_review(
|
|
40
|
+
pr_ref: str,
|
|
41
|
+
*,
|
|
42
|
+
approve: bool = False,
|
|
43
|
+
request_changes: bool = False,
|
|
44
|
+
comment: str | None = None,
|
|
45
|
+
allow_write: bool = False,
|
|
46
|
+
dry_run: bool = False,
|
|
47
|
+
) -> dict[str, Any]:
|
|
48
|
+
action = pr_write_action(approve=approve, request_changes=request_changes, comment=comment)
|
|
49
|
+
if (approve or request_changes or comment) and not allow_write:
|
|
50
|
+
return {
|
|
51
|
+
"kind": "pr-review",
|
|
52
|
+
"status": "blocked",
|
|
53
|
+
"ok": False,
|
|
54
|
+
"mode": "report-only",
|
|
55
|
+
"requires_permission": True,
|
|
56
|
+
"message": "PR write actions require explicit opt-in with --allow-write.",
|
|
57
|
+
"exit_code": 2,
|
|
58
|
+
}
|
|
59
|
+
permission = None
|
|
60
|
+
if action:
|
|
61
|
+
permission = permission_check(agent="github-pr-reviewer", provider="github", action=action)
|
|
62
|
+
if dry_run:
|
|
63
|
+
return {
|
|
64
|
+
"kind": "pr-review",
|
|
65
|
+
"status": "planned",
|
|
66
|
+
"ok": True,
|
|
67
|
+
"dry_run": True,
|
|
68
|
+
"mode": "write-plan" if allow_write else "report-only",
|
|
69
|
+
"provider": "github",
|
|
70
|
+
"pr_ref": pr_ref,
|
|
71
|
+
"permission": permission,
|
|
72
|
+
"external_action": pr_external_action(pr_ref, approve=approve, request_changes=request_changes, comment=comment),
|
|
73
|
+
"summary": "Dry-run only. No PR comments, approvals or request-changes were submitted.",
|
|
74
|
+
}
|
|
75
|
+
if not permission["ok"]:
|
|
76
|
+
return {
|
|
77
|
+
"kind": "pr-review",
|
|
78
|
+
"status": "blocked",
|
|
79
|
+
"ok": False,
|
|
80
|
+
"mode": "write-opt-in",
|
|
81
|
+
"requires_permission": True,
|
|
82
|
+
"permission": permission,
|
|
83
|
+
"message": "PR write action is above the configured permission level.",
|
|
84
|
+
"exit_code": 2,
|
|
85
|
+
}
|
|
86
|
+
elif dry_run:
|
|
87
|
+
return {
|
|
88
|
+
"kind": "pr-review",
|
|
89
|
+
"status": "planned",
|
|
90
|
+
"ok": True,
|
|
91
|
+
"dry_run": True,
|
|
92
|
+
"mode": "report-only",
|
|
93
|
+
"provider": "github",
|
|
94
|
+
"pr_ref": pr_ref,
|
|
95
|
+
"summary": "Dry-run only. PR diff would be loaded for report-only review.",
|
|
96
|
+
}
|
|
97
|
+
if action and not allow_write:
|
|
98
|
+
return {
|
|
99
|
+
"kind": "pr-review",
|
|
100
|
+
"status": "blocked",
|
|
101
|
+
"ok": False,
|
|
102
|
+
"mode": "write-opt-in",
|
|
103
|
+
"requires_permission": True,
|
|
104
|
+
"message": "PR write execution is reserved for the permissions layer. No comments, approvals or request-changes were submitted.",
|
|
105
|
+
"exit_code": 2,
|
|
106
|
+
}
|
|
107
|
+
check = gh_check()
|
|
108
|
+
if check["status"] != "ok":
|
|
109
|
+
return check
|
|
110
|
+
if action:
|
|
111
|
+
review_result = run_gh(pr_review_args(pr_ref, approve=approve, request_changes=request_changes, comment=comment))
|
|
112
|
+
if review_result["status"] != "ok":
|
|
113
|
+
return review_result
|
|
114
|
+
return {
|
|
115
|
+
"kind": "pr-review",
|
|
116
|
+
"status": "ok",
|
|
117
|
+
"ok": True,
|
|
118
|
+
"mode": "write-opt-in",
|
|
119
|
+
"provider": "github",
|
|
120
|
+
"pr_ref": pr_ref,
|
|
121
|
+
"permission": permission,
|
|
122
|
+
"external_action": pr_external_action(pr_ref, approve=approve, request_changes=request_changes, comment=comment),
|
|
123
|
+
"summary": "PR review action submitted through gh.",
|
|
124
|
+
}
|
|
125
|
+
view = pr_inspect(pr_ref)
|
|
126
|
+
if view.get("status") != "ok":
|
|
127
|
+
return view
|
|
128
|
+
diff_result = run_gh(["pr", "diff", pr_ref])
|
|
129
|
+
if diff_result["status"] != "ok":
|
|
130
|
+
return diff_result
|
|
131
|
+
return {
|
|
132
|
+
"kind": "pr-review",
|
|
133
|
+
"status": "ok",
|
|
134
|
+
"ok": True,
|
|
135
|
+
"mode": "report-only" if not allow_write else "write-opt-in",
|
|
136
|
+
"provider": "github",
|
|
137
|
+
"pr": view.get("item"),
|
|
138
|
+
"diff_available": bool(diff_result.get("stdout")),
|
|
139
|
+
"summary": "PR diff loaded for report-only review. No comments, approvals or request-changes were submitted.",
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def pr_write_action(*, approve: bool, request_changes: bool, comment: str | None) -> str | None:
|
|
144
|
+
if approve or request_changes:
|
|
145
|
+
return "approve" if approve else "request-changes"
|
|
146
|
+
if comment:
|
|
147
|
+
return "comment"
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def pr_review_args(pr_ref: str, *, approve: bool, request_changes: bool, comment: str | None) -> list[str]:
|
|
152
|
+
args = ["pr", "review", pr_ref]
|
|
153
|
+
if approve:
|
|
154
|
+
args.append("--approve")
|
|
155
|
+
elif request_changes:
|
|
156
|
+
args.append("--request-changes")
|
|
157
|
+
else:
|
|
158
|
+
args.append("--comment")
|
|
159
|
+
if comment:
|
|
160
|
+
args.extend(["--body", comment])
|
|
161
|
+
return args
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def pr_external_action(pr_ref: str, *, approve: bool, request_changes: bool, comment: str | None) -> dict[str, Any]:
|
|
165
|
+
return {
|
|
166
|
+
"provider": "github",
|
|
167
|
+
"agent": "github-pr-reviewer",
|
|
168
|
+
"pr_ref": pr_ref,
|
|
169
|
+
"action": pr_write_action(approve=approve, request_changes=request_changes, comment=comment),
|
|
170
|
+
"comment": bool(comment),
|
|
171
|
+
"approve": approve,
|
|
172
|
+
"request_changes": request_changes,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def pr_create_automation(*, task_id: str | None = None, title: str | None = None, time: str = "09:00") -> dict[str, Any]:
|
|
177
|
+
task = create_task(
|
|
178
|
+
task_id=task_id or "daily-pr-review",
|
|
179
|
+
title=title or "Revisar PRs pendentes diariamente",
|
|
180
|
+
prompt="revise todas as prs que recebo diariamente",
|
|
181
|
+
schedule={"type": "daily", "time": time},
|
|
182
|
+
action={"type": "capability", "agent": "github-pr-reviewer", "capability": "list-review-requests", "external_writes": False},
|
|
183
|
+
permissions={"mode": "report-only", "comment": False, "approve": False, "request_changes": False},
|
|
184
|
+
notifications=[{"type": "terminal"}],
|
|
185
|
+
)
|
|
186
|
+
task["kind"] = "pr-automation"
|
|
187
|
+
task["mode"] = "report-only"
|
|
188
|
+
return task
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def gh_check() -> dict[str, Any]:
|
|
192
|
+
binary = shutil.which("gh")
|
|
193
|
+
if not binary:
|
|
194
|
+
return {
|
|
195
|
+
"kind": "pr",
|
|
196
|
+
"status": "needs-setup",
|
|
197
|
+
"ok": False,
|
|
198
|
+
"provider": "github",
|
|
199
|
+
"message": "GitHub CLI `gh` was not found in PATH.",
|
|
200
|
+
"next_steps": ["Run `agent toolchain install gh-cli --dry-run`.", "Authenticate with `gh auth login`."],
|
|
201
|
+
"exit_code": 2,
|
|
202
|
+
}
|
|
203
|
+
return {"kind": "pr", "status": "ok", "binary": binary}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def run_gh(args: list[str]) -> dict[str, Any]:
|
|
207
|
+
try:
|
|
208
|
+
process = subprocess.run(
|
|
209
|
+
["gh", *args],
|
|
210
|
+
check=False,
|
|
211
|
+
text=True,
|
|
212
|
+
stdout=subprocess.PIPE,
|
|
213
|
+
stderr=subprocess.PIPE,
|
|
214
|
+
timeout=GH_TIMEOUT_SECONDS,
|
|
215
|
+
)
|
|
216
|
+
except subprocess.TimeoutExpired as exc:
|
|
217
|
+
stdout = exc.stdout.decode(errors="replace") if isinstance(exc.stdout, bytes) else (exc.stdout or "")
|
|
218
|
+
stderr = exc.stderr.decode(errors="replace") if isinstance(exc.stderr, bytes) else (exc.stderr or "")
|
|
219
|
+
return {
|
|
220
|
+
"kind": "pr",
|
|
221
|
+
"status": "failed",
|
|
222
|
+
"ok": False,
|
|
223
|
+
"provider": "github",
|
|
224
|
+
"command": ["gh", *args],
|
|
225
|
+
"message": redact_secrets((stderr or stdout or f"gh command timed out after {GH_TIMEOUT_SECONDS}s").strip()),
|
|
226
|
+
"exit_code": 124,
|
|
227
|
+
}
|
|
228
|
+
if process.returncode != 0:
|
|
229
|
+
return {
|
|
230
|
+
"kind": "pr",
|
|
231
|
+
"status": "failed",
|
|
232
|
+
"ok": False,
|
|
233
|
+
"provider": "github",
|
|
234
|
+
"command": ["gh", *args],
|
|
235
|
+
"message": redact_secrets((process.stderr or process.stdout or "gh command failed").strip()),
|
|
236
|
+
"exit_code": process.returncode,
|
|
237
|
+
}
|
|
238
|
+
return {"kind": "pr", "status": "ok", "stdout": process.stdout}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def parse_json_list(value: str) -> list[dict[str, Any]]:
|
|
242
|
+
try:
|
|
243
|
+
payload = json.loads(value or "[]")
|
|
244
|
+
except json.JSONDecodeError:
|
|
245
|
+
return []
|
|
246
|
+
return payload if isinstance(payload, list) else []
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def parse_json_object(value: str) -> dict[str, Any]:
|
|
250
|
+
try:
|
|
251
|
+
payload = json.loads(value or "{}")
|
|
252
|
+
except json.JSONDecodeError:
|
|
253
|
+
return {}
|
|
254
|
+
return payload if isinstance(payload, dict) else {}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Agent DevKit public identity helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
BACKEND_IDENTITY_PATTERN = re.compile(
|
|
10
|
+
r"(?i)\b("
|
|
11
|
+
r"sou\s+(?:o\s+|a\s+)?(?:claude|codex|chatgpt)|"
|
|
12
|
+
r"meu\s+nome\s+(?:e|é)\s+(?:claude|codex|chatgpt)|"
|
|
13
|
+
r"i\s*am\s+(?:claude|codex|chatgpt)|"
|
|
14
|
+
r"i'm\s+(?:claude|codex|chatgpt)|"
|
|
15
|
+
r"my\s+name\s+is\s+(?:claude|codex|chatgpt)|"
|
|
16
|
+
r"(?:da|from)\s+(?:anthropic|openai)"
|
|
17
|
+
r")\b"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
IDENTITY_QUESTION_PATTERN = re.compile(
|
|
21
|
+
r"(?i)\b("
|
|
22
|
+
r"qual\s+(?:e|é)\s+(?:o\s+)?seu\s+nome|"
|
|
23
|
+
r"quem\s+(?:e|é)\s+voce|"
|
|
24
|
+
r"quem\s+(?:e|é)\s+você|"
|
|
25
|
+
r"voce\s+esta\s+vivo|"
|
|
26
|
+
r"você\s+est[aá]\s+vivo|"
|
|
27
|
+
r"vc\s+est[aá]\s+vivo|"
|
|
28
|
+
r"seu\s+nome"
|
|
29
|
+
r")\b"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
DEFAULT_PUBLIC_NAME = "Agent DevKit"
|
|
33
|
+
CANONICAL_PROGRAM_NAMES = {"agent", "aikit", "ai-devkit"}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def display_name(value: str) -> str:
|
|
37
|
+
return " ".join(part.capitalize() for part in re.split(r"[-_\s]+", value) if part) or DEFAULT_PUBLIC_NAME
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def public_name(*, personality: dict[str, Any] | None = None, invoked_as: str | None = None) -> str:
|
|
41
|
+
invoked = (invoked_as or "").strip()
|
|
42
|
+
if invoked and invoked not in CANONICAL_PROGRAM_NAMES:
|
|
43
|
+
return display_name(invoked)
|
|
44
|
+
configured = str((personality or {}).get("agent_name") or "").strip()
|
|
45
|
+
return configured or DEFAULT_PUBLIC_NAME
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def is_identity_question(prompt: str) -> bool:
|
|
49
|
+
return bool(IDENTITY_QUESTION_PATTERN.search(prompt))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def local_identity_response(prompt: str, *, name: str) -> str:
|
|
53
|
+
if re.search(r"(?i)vivo|viva", prompt):
|
|
54
|
+
return f"Estou disponivel. Meu nome e {name}, seu agente local do Agent DevKit."
|
|
55
|
+
return f"Meu nome e {name}."
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def identity_system_prompt(*, name: str) -> str:
|
|
59
|
+
return (
|
|
60
|
+
f"Voce e {name}, o agente local do Agent DevKit. "
|
|
61
|
+
"A LLM conectada e apenas o motor de raciocinio, nao sua identidade publica. "
|
|
62
|
+
"Nunca responda que voce e Claude, Codex, ChatGPT, OpenAI ou Anthropic. "
|
|
63
|
+
"Se perguntarem seu nome, responda usando apenas a identidade publica configurada. "
|
|
64
|
+
"Seja direto, operacional e nao peca todas as credenciais de uma vez."
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def host_cli_prompt(prompt: str, *, name: str) -> str:
|
|
69
|
+
return f"{identity_system_prompt(name=name)}\n\nPedido do usuario:\n{prompt}"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def enforce_identity_response(response: str, prompt: str, *, name: str) -> str:
|
|
73
|
+
if BACKEND_IDENTITY_PATTERN.search(response):
|
|
74
|
+
return local_identity_response(prompt, name=name)
|
|
75
|
+
return response
|