@cursor/july 0.1.1 → 0.1.2
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/AGENTS.md +24 -2
- package/README.md +25 -15
- package/dist/bin/agent-serve.js +101 -12
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +31 -14
- package/dist/channels/github/cursor-account.d.ts +43 -0
- package/dist/channels/github/cursor-account.d.ts.map +1 -0
- package/dist/channels/github/cursor-account.js +95 -0
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +46 -9
- package/dist/channels/github/index.d.ts +2 -2
- package/dist/channels/github/index.js +2 -2
- package/dist/channels/github/types.d.ts +17 -0
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.d.ts +8 -2
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +8 -0
- package/dist/channels/slack/types.d.ts +24 -3
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/channels/slack/types.js +15 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +8 -8
- package/dist/docs/assets/{ab.md.COdXkces.js → ab.md.BMCZ6Hd7.js} +3 -3
- package/dist/docs/assets/{ab.md.COdXkces.lean.js → ab.md.BMCZ6Hd7.lean.js} +1 -1
- package/dist/docs/assets/{app.DqfFEmJd.js → app.Oje4vhlk.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.zwQ9RCQ7.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.BaLEdS15.js → VPLocalSearchBox.H5XZ2zCB.js} +1 -1
- package/dist/docs/assets/chunks/{theme.CZRvu_0q.js → theme.CTR_TuaE.js} +2 -2
- package/dist/docs/assets/{deployment.md.Dx1TYNk5.js → deployment.md.DTKwE15Z.js} +3 -3
- package/dist/docs/assets/{deployment.md.Dx1TYNk5.lean.js → deployment.md.DTKwE15Z.lean.js} +1 -1
- package/dist/docs/assets/{evals.md.DPZ_MAnI.js → evals.md.DAgEc_hL.js} +3 -3
- package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.js → guides_agent-to-agent.md.Bpzgq2Pq.js} +1 -1
- package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.js → guides_cloud-runtime.md.gVzabdQL.js} +1 -1
- package/dist/docs/assets/{guides_github.md.DwbKhCeS.js → guides_github.md.DOOCpqsW.js} +11 -4
- package/dist/docs/assets/{guides_github.md.DwbKhCeS.lean.js → guides_github.md.DOOCpqsW.lean.js} +1 -1
- package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.js → guides_human-in-the-loop.md.DlUqsp1S.js} +2 -2
- package/dist/docs/assets/{guides_slack.md.bv41fHfW.js → guides_slack.md.CCwqHvSV.js} +4 -4
- package/dist/docs/assets/{guides_slack.md.bv41fHfW.lean.js → guides_slack.md.CCwqHvSV.lean.js} +1 -1
- package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.js → guides_webhooks.md.B1EswtUu.js} +2 -2
- package/dist/docs/assets/index.md.m81y7TY7.js +20 -0
- package/dist/docs/assets/{index.md.BPKcj5AI.lean.js → index.md.m81y7TY7.lean.js} +1 -1
- package/dist/docs/assets/quickstart.md.CfU8_uTC.js +192 -0
- package/dist/docs/assets/quickstart.md.CfU8_uTC.lean.js +1 -0
- package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.js → reference_agent-config.md.DrW2JUM8.js} +4 -4
- package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.lean.js → reference_agent-config.md.DrW2JUM8.lean.js} +1 -1
- package/dist/docs/assets/{reference_channels.md.D7JTR03W.js → reference_channels.md.DdmiKgqf.js} +4 -4
- package/dist/docs/assets/{reference_channels.md.D7JTR03W.lean.js → reference_channels.md.DdmiKgqf.lean.js} +1 -1
- package/dist/docs/assets/{reference_connections.md.C3vNH_DE.js → reference_connections.md.zaEYCLHT.js} +1 -1
- package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.js → reference_hooks.md.DyLVfE1O.js} +1 -1
- package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.lean.js → reference_hooks.md.DyLVfE1O.lean.js} +1 -1
- package/dist/docs/assets/{reference_http-api.md.DBAahtdz.js → reference_http-api.md.Dx_nmDG6.js} +1 -1
- package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.js → reference_instructions.md.CgoV-YEb.js} +9 -7
- package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.lean.js → reference_instructions.md.CgoV-YEb.lean.js} +1 -1
- package/dist/docs/assets/{reference_schedules.md.D7qijxLk.js → reference_schedules.md.w_F2mXB6.js} +2 -2
- package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.js → reference_skills.md.B_jHN7JL.js} +3 -3
- package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.lean.js → reference_skills.md.B_jHN7JL.lean.js} +1 -1
- package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.js → reference_subagents.md.zWAMNfi1.js} +1 -1
- package/dist/docs/assets/{reference_tools.md.DF5kwlt0.js → reference_tools.md.CqgJroI0.js} +2 -2
- package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.js +1 -0
- package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.lean.js +1 -0
- package/dist/docs/assets/storage.md.CVnInNiN.js +17 -0
- package/dist/docs/assets/storage.md.CVnInNiN.lean.js +1 -0
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +7 -7
- package/dist/docs/evals.html +7 -7
- package/dist/docs/guides/agent-to-agent.html +6 -6
- package/dist/docs/guides/cloud-runtime.html +5 -5
- package/dist/docs/guides/github.html +14 -7
- package/dist/docs/guides/human-in-the-loop.html +6 -6
- package/dist/docs/guides/slack.html +8 -8
- package/dist/docs/guides/webhooks.html +6 -6
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +5 -5
- package/dist/docs/index.html +7 -7
- package/dist/docs/quickstart.html +180 -23
- package/dist/docs/reference/agent-config.html +7 -7
- package/dist/docs/reference/channels.html +8 -8
- package/dist/docs/reference/cli.html +4 -4
- package/dist/docs/reference/connections.html +5 -5
- package/dist/docs/reference/hooks.html +5 -5
- package/dist/docs/reference/http-api.html +6 -6
- package/dist/docs/reference/instructions.html +13 -11
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/schedules.html +7 -7
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +6 -6
- package/dist/docs/reference/subagents.html +6 -6
- package/dist/docs/reference/tools.html +7 -7
- package/dist/docs/scaffolding-agents.html +5 -5
- package/dist/docs/storage.html +41 -0
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/internal/cli-deploy.d.ts +52 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -0
- package/dist/internal/cli-deploy.js +731 -0
- package/dist/internal/cursor/backend-client.d.ts +10 -1
- package/dist/internal/cursor/backend-client.d.ts.map +1 -1
- package/dist/internal/cursor/backend-client.js +79 -1
- package/dist/internal/cursor/github-credentials.d.ts +44 -0
- package/dist/internal/cursor/github-credentials.d.ts.map +1 -0
- package/dist/internal/cursor/github-credentials.js +195 -0
- package/dist/internal/deploy-client.d.ts +176 -0
- package/dist/internal/deploy-client.d.ts.map +1 -0
- package/dist/internal/deploy-client.js +375 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +73 -5
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/eval-run-store.d.ts +22 -3
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +37 -19
- package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -1
- package/dist/internal/handleAgentServeTrigger.js +10 -12
- package/dist/internal/host-platforms.d.ts +7 -2
- package/dist/internal/host-platforms.d.ts.map +1 -1
- package/dist/internal/host-platforms.js +15 -10
- package/dist/internal/hosting.d.ts +37 -0
- package/dist/internal/hosting.d.ts.map +1 -0
- package/dist/internal/hosting.js +67 -0
- package/dist/internal/reminder-runner.d.ts +7 -0
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +50 -6
- package/dist/internal/reminder-store.d.ts +2 -0
- package/dist/internal/reminder-store.d.ts.map +1 -1
- package/dist/internal/reminder-store.js +18 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +173 -42
- package/dist/internal/session-engine.d.ts +49 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +246 -17
- package/dist/internal/storage-coordinator.d.ts +139 -0
- package/dist/internal/storage-coordinator.d.ts.map +1 -0
- package/dist/internal/storage-coordinator.js +499 -0
- package/dist/playground/assets/cursor-icons-outline-BxTT_FVJ.woff2 +0 -0
- package/dist/playground/assets/index-B1Qc9h2u.css +1 -0
- package/dist/playground/assets/{index-FlWjhg3x.js → index-CpDYCj8W.js} +42 -42
- package/dist/playground/index.html +2 -2
- package/dist/storage.d.ts +204 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +153 -0
- package/dist/types.d.ts +44 -3
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +3 -2
- package/docs/guides/github.md +43 -8
- package/docs/guides/slack.md +1 -1
- package/docs/quickstart.md +329 -51
- package/docs/reference/instructions.md +8 -6
- package/docs/scaffolding-agents.md +1 -1
- package/docs/storage.md +98 -0
- package/package.json +8 -1
- package/skills/github/SKILL.md +9 -1
- package/src/bin/agent-serve.ts +139 -0
- package/src/channels/github/api.ts +42 -23
- package/src/channels/github/cursor-account.ts +165 -0
- package/src/channels/github/github-channel.ts +66 -6
- package/src/channels/github/index.ts +2 -2
- package/src/channels/github/types.ts +19 -0
- package/src/channels/slack/slack-channel.ts +17 -3
- package/src/channels/slack/types.ts +44 -3
- package/src/index.ts +13 -0
- package/src/internal/cli-deploy.ts +940 -0
- package/src/internal/cursor/backend-client.ts +103 -1
- package/src/internal/cursor/github-credentials.ts +248 -0
- package/src/internal/deploy-client.ts +591 -0
- package/src/internal/discovery.ts +88 -1
- package/src/internal/distribution.ts +1 -0
- package/src/internal/eval-run-store.ts +48 -19
- package/src/internal/handleAgentServeTrigger.ts +10 -12
- package/src/internal/host-platforms.ts +28 -11
- package/src/internal/hosting.ts +77 -0
- package/src/internal/reminder-runner.ts +50 -6
- package/src/internal/reminder-store.ts +21 -0
- package/src/internal/server.ts +213 -27
- package/src/internal/session-engine.ts +285 -7
- package/src/internal/storage-coordinator.ts +615 -0
- package/src/storage.ts +325 -0
- package/src/types.ts +40 -2
- package/dist/docs/assets/chunks/@localSearchIndexroot.CcVk1uKq.js +0 -1
- package/dist/docs/assets/index.md.BPKcj5AI.js +0 -20
- package/dist/docs/assets/quickstart.md.tVPiGK_L.js +0 -35
- package/dist/docs/assets/quickstart.md.tVPiGK_L.lean.js +0 -1
- package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.js +0 -1
- package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.lean.js +0 -1
- package/dist/playground/assets/cursor-icons-outline-oY2V_mvK.woff2 +0 -0
- package/dist/playground/assets/index-1K-hG-7p.css +0 -1
- /package/dist/docs/assets/{evals.md.DPZ_MAnI.lean.js → evals.md.DAgEc_hL.lean.js} +0 -0
- /package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.lean.js → guides_agent-to-agent.md.Bpzgq2Pq.lean.js} +0 -0
- /package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.lean.js → guides_cloud-runtime.md.gVzabdQL.lean.js} +0 -0
- /package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.lean.js → guides_human-in-the-loop.md.DlUqsp1S.lean.js} +0 -0
- /package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.lean.js → guides_webhooks.md.B1EswtUu.lean.js} +0 -0
- /package/dist/docs/assets/{reference_connections.md.C3vNH_DE.lean.js → reference_connections.md.zaEYCLHT.lean.js} +0 -0
- /package/dist/docs/assets/{reference_http-api.md.DBAahtdz.lean.js → reference_http-api.md.Dx_nmDG6.lean.js} +0 -0
- /package/dist/docs/assets/{reference_schedules.md.D7qijxLk.lean.js → reference_schedules.md.w_F2mXB6.lean.js} +0 -0
- /package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.lean.js → reference_subagents.md.zWAMNfi1.lean.js} +0 -0
- /package/dist/docs/assets/{reference_tools.md.DF5kwlt0.lean.js → reference_tools.md.CqgJroI0.lean.js} +0 -0
package/docs/guides/github.md
CHANGED
|
@@ -18,16 +18,51 @@ The companion skill for coding agents is
|
|
|
18
18
|
Connect GitHub in Cursor for the repositories you care about (Settings or
|
|
19
19
|
[cursor.com/dashboard](https://cursor.com/dashboard)). That gives your
|
|
20
20
|
account access and lets Cursor receive the repo's webhooks. Sign the host
|
|
21
|
-
in (`agentkit login` or `CURSOR_API_KEY`),
|
|
22
|
-
|
|
21
|
+
in (`agentkit login` or `CURSOR_API_KEY`), then opt the channel into the
|
|
22
|
+
Cursor account connection:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
export default githubChannel({
|
|
26
|
+
cursorAccount: {
|
|
27
|
+
repos: ["owner/repo"],
|
|
28
|
+
// permissions?: "read" | "pr-write" | "contents-write"
|
|
29
|
+
// default "pr-write" (comments / PR writes, no contents:write)
|
|
30
|
+
},
|
|
31
|
+
// hooks...
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`cursorAccount` starts the event relay and mints one short-lived GitHub
|
|
36
|
+
credential scoped to those repositories. `ctx.github`, `ctx.host.github`,
|
|
37
|
+
and child `gh` commands share it. Agentkit refreshes the credential before
|
|
38
|
+
expiry. No GitHub App key, PAT, or separate `gh auth login` is needed on
|
|
39
|
+
the host.
|
|
40
|
+
|
|
41
|
+
Choose `permissions` by what the agent needs:
|
|
42
|
+
|
|
43
|
+
| `permissions` | Use when |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `"read"` | Inspect PRs / issues / statuses only |
|
|
46
|
+
| `"pr-write"` (default) | Comment, review, update PR/issue metadata |
|
|
47
|
+
| `"contents-write"` | Push code (`contents:write`) |
|
|
48
|
+
|
|
49
|
+
`contents-write` is an explicit opt-up. The host holds a push-capable
|
|
50
|
+
token shared with model-driven tools and untrusted webhook content —
|
|
51
|
+
prefer `"pr-write"` unless the agent must push.
|
|
52
|
+
|
|
53
|
+
Selected repositories must share one GitHub owner (one App installation).
|
|
54
|
+
Configuration that spans owners fails at startup / mint time.
|
|
55
|
+
|
|
56
|
+
To keep repository scope in deployment config instead, use
|
|
57
|
+
`cursorAccount: true` and pass it at serve time:
|
|
23
58
|
|
|
24
59
|
```bash
|
|
25
60
|
agentkit serve --dir . --cursor-events --repo owner/repo
|
|
26
61
|
```
|
|
27
62
|
|
|
28
|
-
Repeat `--repo` for each repository. The stream
|
|
29
|
-
Cursor
|
|
30
|
-
|
|
63
|
+
Repeat `--repo` for each repository. The stream and credential are
|
|
64
|
+
resolved as the signed-in Cursor principal. `serve` refuses to start
|
|
65
|
+
signed out.
|
|
31
66
|
|
|
32
67
|
Offset and consumer id live under `<state-root>/cursor-events/`.
|
|
33
68
|
`CURSOR_API_BASE_URL` overrides the backend. The stream carries event
|
|
@@ -51,7 +86,7 @@ import { defaultGitHubAuth, githubChannel } from "@cursor/july/channels/github";
|
|
|
51
86
|
|
|
52
87
|
export default githubChannel({
|
|
53
88
|
botName: "my-agent", // or GITHUB_APP_SLUG; used to ignore self-comments
|
|
54
|
-
|
|
89
|
+
cursorAccount: { repos: ["owner/repo"] },
|
|
55
90
|
onPullRequest: (ctx, pr) =>
|
|
56
91
|
pr.action === "opened" ? { auth: defaultGitHubAuth(ctx) } : null,
|
|
57
92
|
onCheckSuite: (ctx, suite) =>
|
|
@@ -72,10 +107,10 @@ things:
|
|
|
72
107
|
|
|
73
108
|
Return `{ task }` when the wake drives deterministic code. A security
|
|
74
109
|
reviewer can run its whole review loop this way and report
|
|
75
|
-
through
|
|
110
|
+
through PR comments. Return `{ auth }` when the model needs to reason
|
|
76
111
|
about the event.
|
|
77
112
|
|
|
78
|
-
|
|
113
|
+
Without `cursorAccount`, outbound GitHub API calls prefer App installation tokens when
|
|
79
114
|
`GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY` are set (with an
|
|
80
115
|
installation id from the event or `GITHUB_APP_INSTALLATION_ID`). On
|
|
81
116
|
serve warmup, App-backed hosts also export a short-lived installation
|
package/docs/guides/slack.md
CHANGED
|
@@ -53,7 +53,7 @@ import { slackChannel } from "@cursor/july/channels/slack";
|
|
|
53
53
|
|
|
54
54
|
export default slackChannel({
|
|
55
55
|
cursorAccount: true,
|
|
56
|
-
agentName: "Weatherbot", //
|
|
56
|
+
agentName: "Weatherbot", // single token — no spaces; defaults from mount slug (PascalCase)
|
|
57
57
|
agentIcon: { emoji: ":robot_face:" },
|
|
58
58
|
});
|
|
59
59
|
```
|
package/docs/quickstart.md
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Build your first
|
|
3
|
-
description: "Create
|
|
2
|
+
title: "Build your first PR approver"
|
|
3
|
+
description: "Create an agent that reviews pull requests by complexity, approves the safe ones, and wakes from GitHub webhooks."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Build your first
|
|
6
|
+
# Build your first PR approver
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Build an agent that reviews GitHub pull requests. It fetches the diff,
|
|
9
|
+
rates the change's complexity in plain TypeScript, approves the safe
|
|
10
|
+
ones, and flags the rest for a human. Then wire it to GitHub webhooks
|
|
11
|
+
and watch a pull request wake it.
|
|
12
|
+
|
|
13
|
+
The split is the point of the exercise: deterministic policy lives in
|
|
14
|
+
typed tools, judgment lives in the model, and every decision is
|
|
15
|
+
inspectable in the playground.
|
|
10
16
|
|
|
11
17
|
## Prerequisites
|
|
12
18
|
|
|
@@ -23,13 +29,18 @@ agentkit login
|
|
|
23
29
|
|
|
24
30
|
You can also set `CURSOR_API_KEY` instead of signing in.
|
|
25
31
|
|
|
32
|
+
- A GitHub credential. `gh auth login` is enough, or set
|
|
33
|
+
`GITHUB_TOKEN`. The tools you write resolve either one
|
|
34
|
+
automatically. Reading pull requests works on any public repo;
|
|
35
|
+
posting reviews needs write access to the repo you review.
|
|
36
|
+
|
|
26
37
|
## Scaffolding Agents
|
|
27
38
|
|
|
28
39
|
Have Cursor read [`skills/create-agent/SKILL.md`](../skills/create-agent/SKILL.md)
|
|
29
40
|
and describe what you want:
|
|
30
41
|
|
|
31
|
-
> Build me a
|
|
32
|
-
>
|
|
42
|
+
> Build me a PR approver for the playground. Start with one tool that
|
|
43
|
+
> inspects a pull request and guide me through the remaining decisions.
|
|
33
44
|
|
|
34
45
|
Cursor asks for missing choices, shows you the plan, then builds and
|
|
35
46
|
verifies the agent. Continue below to do the same by hand.
|
|
@@ -42,14 +53,14 @@ full guided workflow.
|
|
|
42
53
|
Start with the built-in scaffold:
|
|
43
54
|
|
|
44
55
|
```bash
|
|
45
|
-
agentkit init ./
|
|
46
|
-
cd
|
|
56
|
+
agentkit init ./pr-approver
|
|
57
|
+
cd pr-approver
|
|
47
58
|
```
|
|
48
59
|
|
|
49
60
|
The scaffold creates the files agentkit discovers:
|
|
50
61
|
|
|
51
62
|
```text
|
|
52
|
-
|
|
63
|
+
pr-approver/
|
|
53
64
|
├── agent/
|
|
54
65
|
│ ├── agent.ts
|
|
55
66
|
│ ├── instructions.md
|
|
@@ -85,18 +96,32 @@ reply. It prints a JSON trajectory with the response, tool calls, and
|
|
|
85
96
|
token usage. It also writes an NDJSON trace under
|
|
86
97
|
`.agentkit/traces/`.
|
|
87
98
|
|
|
88
|
-
##
|
|
99
|
+
## Teach it to review
|
|
89
100
|
|
|
90
101
|
Replace `agent/instructions.md`:
|
|
91
102
|
|
|
92
103
|
```md
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
-
You
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
# PR approver
|
|
105
|
+
|
|
106
|
+
You review GitHub pull requests. Be specific and brief.
|
|
107
|
+
|
|
108
|
+
For every pull request:
|
|
109
|
+
|
|
110
|
+
1. Call `inspect_pr` first. Never judge a change you haven't fetched.
|
|
111
|
+
2. Match your review to the complexity it reports:
|
|
112
|
+
- `trivial`: read the patches. If the diff does what the title says
|
|
113
|
+
and nothing looks risky, call `submit_review` with verdict
|
|
114
|
+
`approve`.
|
|
115
|
+
- `moderate`: read every patch. Approve only when you understand the
|
|
116
|
+
whole change and see no risk. Otherwise ask for a human review and
|
|
117
|
+
say which files worry you.
|
|
118
|
+
- `large`: call `submit_review` with verdict `request_human_review`
|
|
119
|
+
right away. Use the stats you already have to point the reviewer at
|
|
120
|
+
the biggest files; don't dig further.
|
|
121
|
+
3. Never approve a draft. Point out anything surprising, even when you
|
|
122
|
+
approve.
|
|
123
|
+
|
|
124
|
+
End with one sentence: the verdict and why.
|
|
100
125
|
```
|
|
101
126
|
|
|
102
127
|
Remove the demo echo tool:
|
|
@@ -105,79 +130,332 @@ Remove the demo echo tool:
|
|
|
105
130
|
rm agent/tools/echo.ts
|
|
106
131
|
```
|
|
107
132
|
|
|
108
|
-
## Add a
|
|
133
|
+
## Add a shared helper
|
|
134
|
+
|
|
135
|
+
Both tools need to split a PR URL into its parts. Shared code lives in
|
|
136
|
+
`agent/lib/`, which the framework never loads as tools.
|
|
137
|
+
|
|
138
|
+
Create `agent/lib/github.ts`:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
export interface PullRef {
|
|
142
|
+
owner: string;
|
|
143
|
+
repo: string;
|
|
144
|
+
number: number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Split https://github.com/owner/repo/pull/123 into its parts. */
|
|
148
|
+
export function parsePullUrl(prUrl: string): PullRef {
|
|
149
|
+
const url = new URL(prUrl);
|
|
150
|
+
const [owner, repo, pulls, number] = url.pathname.split("/").filter(Boolean);
|
|
151
|
+
const parsed = Number.parseInt(number ?? "", 10);
|
|
152
|
+
if (pulls !== "pull" || owner === undefined || Number.isNaN(parsed)) {
|
|
153
|
+
throw new Error(`Not a pull request URL: ${prUrl}`);
|
|
154
|
+
}
|
|
155
|
+
return { owner, repo, number: parsed };
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Add the inspect tool
|
|
109
160
|
|
|
110
|
-
Create `agent/tools/
|
|
161
|
+
Create `agent/tools/inspect_pr.ts`:
|
|
111
162
|
|
|
112
163
|
```ts
|
|
113
164
|
import { defineTool } from "@cursor/july/tools";
|
|
114
165
|
import { z } from "zod";
|
|
166
|
+
import { parsePullUrl } from "../lib/github.js";
|
|
167
|
+
|
|
168
|
+
export type Complexity = "trivial" | "moderate" | "large";
|
|
169
|
+
|
|
170
|
+
/** Deterministic policy: the tool rates the change, not the model. */
|
|
171
|
+
function rateComplexity(linesChanged: number, changedFiles: number): Complexity {
|
|
172
|
+
if (linesChanged <= 25 && changedFiles <= 2) {
|
|
173
|
+
return "trivial";
|
|
174
|
+
}
|
|
175
|
+
if (linesChanged <= 400 && changedFiles <= 15) {
|
|
176
|
+
return "moderate";
|
|
177
|
+
}
|
|
178
|
+
return "large";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Keep one oversized file from flooding the model's context. */
|
|
182
|
+
function trimPatch(patch: string | undefined): string | undefined {
|
|
183
|
+
if (patch === undefined || patch.length <= 3000) {
|
|
184
|
+
return patch;
|
|
185
|
+
}
|
|
186
|
+
return `${patch.slice(0, 3000)}\n[... patch trimmed ...]`;
|
|
187
|
+
}
|
|
115
188
|
|
|
116
189
|
export default defineTool({
|
|
117
|
-
description:
|
|
190
|
+
description:
|
|
191
|
+
"Fetch a pull request's title, stats, and per-file patches, plus a deterministic complexity rating (trivial, moderate, or large). Call this before any review decision.",
|
|
118
192
|
inputSchema: z.object({
|
|
119
|
-
|
|
193
|
+
prUrl: z
|
|
194
|
+
.string()
|
|
195
|
+
.describe("Pull request URL: https://github.com/owner/repo/pull/123"),
|
|
120
196
|
}),
|
|
121
|
-
async execute({
|
|
122
|
-
const
|
|
123
|
-
|
|
197
|
+
async execute({ prUrl }, ctx) {
|
|
198
|
+
const { owner, repo, number } = parsePullUrl(prUrl);
|
|
199
|
+
const octokit = await ctx.host.github.getOctokit();
|
|
200
|
+
const { data: pr } = await octokit.rest.pulls.get({
|
|
201
|
+
owner,
|
|
202
|
+
repo,
|
|
203
|
+
pull_number: number,
|
|
204
|
+
});
|
|
205
|
+
const { data: files } = await octokit.rest.pulls.listFiles({
|
|
206
|
+
owner,
|
|
207
|
+
repo,
|
|
208
|
+
pull_number: number,
|
|
209
|
+
per_page: 100,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const complexity = rateComplexity(
|
|
213
|
+
pr.additions + pr.deletions,
|
|
214
|
+
pr.changed_files
|
|
215
|
+
);
|
|
124
216
|
return {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
217
|
+
title: pr.title,
|
|
218
|
+
author: pr.user?.login,
|
|
219
|
+
state: pr.state,
|
|
220
|
+
draft: pr.draft ?? false,
|
|
221
|
+
additions: pr.additions,
|
|
222
|
+
deletions: pr.deletions,
|
|
223
|
+
changedFiles: pr.changed_files,
|
|
224
|
+
complexity,
|
|
225
|
+
files: files.map((file) => ({
|
|
226
|
+
path: file.filename,
|
|
227
|
+
additions: file.additions,
|
|
228
|
+
deletions: file.deletions,
|
|
229
|
+
// Large changes get a stats-only skim; a human reads the code.
|
|
230
|
+
...(complexity === "large" ? {} : { patch: trimPatch(file.patch) }),
|
|
231
|
+
})),
|
|
128
232
|
};
|
|
129
233
|
},
|
|
130
234
|
});
|
|
131
235
|
```
|
|
132
236
|
|
|
133
|
-
The file adds one tool named `
|
|
237
|
+
The file adds one tool named `inspect_pr`:
|
|
134
238
|
|
|
135
239
|
- `description` tells the model when to call it.
|
|
136
240
|
- `inputSchema` defines and validates the arguments.
|
|
137
241
|
- `execute` runs on the server and returns data to the model.
|
|
138
242
|
|
|
139
|
-
|
|
140
|
-
|
|
243
|
+
Two details carry the design. `rateComplexity` is the review policy,
|
|
244
|
+
and it lives in code: the model never decides what counts as a big
|
|
245
|
+
change. And `ctx.host.github` is the shared host GitHub client, so the
|
|
246
|
+
tool inherits whatever credential the host has (a token, `gh auth`, or
|
|
247
|
+
a GitHub App) without parsing any of it.
|
|
141
248
|
|
|
142
|
-
## Try the
|
|
249
|
+
## Try the inspect tool
|
|
143
250
|
|
|
144
|
-
Call the tool directly first:
|
|
251
|
+
Call the tool directly first, on a real merged pull request:
|
|
145
252
|
|
|
146
253
|
```bash
|
|
147
|
-
agentkit call
|
|
148
|
-
--input '{"
|
|
254
|
+
agentkit call inspect_pr --dir . \
|
|
255
|
+
--input '{"prUrl":"https://github.com/react/react/pull/35623"}'
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`call` validates the input and runs `execute` without a model turn.
|
|
259
|
+
This PR is a one-character typo fix, so the result comes back rated
|
|
260
|
+
`trivial` with the whole patch inline:
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"title": "Fix typo: accomodate -> accommodate",
|
|
265
|
+
"additions": 1,
|
|
266
|
+
"deletions": 1,
|
|
267
|
+
"changedFiles": 1,
|
|
268
|
+
"complexity": "trivial",
|
|
269
|
+
"files": [{ "path": "compiler/packages/...", "patch": "@@ -1315,7 ..." }]
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Now call it on the PR that added `experimental_useEvent` to React:
|
|
274
|
+
1,027 additions across 26 files.
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
agentkit call inspect_pr --dir . \
|
|
278
|
+
--input '{"prUrl":"https://github.com/react/react/pull/25229"}'
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The rating flips to `large` and the patches disappear from the result.
|
|
282
|
+
The policy in the tool decides how much the model gets to see, before
|
|
283
|
+
any model turn spends a token on it.
|
|
284
|
+
|
|
285
|
+
## Add the review tool
|
|
286
|
+
|
|
287
|
+
The approver needs a way to act on its verdict. Create
|
|
288
|
+
`agent/tools/submit_review.ts`:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
import { defineTool } from "@cursor/july/tools";
|
|
292
|
+
import { z } from "zod";
|
|
293
|
+
import { parsePullUrl } from "../lib/github.js";
|
|
294
|
+
|
|
295
|
+
export default defineTool({
|
|
296
|
+
description:
|
|
297
|
+
"Post the review decision to GitHub: approve the pull request, or comment asking for a human review.",
|
|
298
|
+
inputSchema: z.object({
|
|
299
|
+
prUrl: z
|
|
300
|
+
.string()
|
|
301
|
+
.describe("Pull request URL: https://github.com/owner/repo/pull/123"),
|
|
302
|
+
verdict: z.enum(["approve", "request_human_review"]),
|
|
303
|
+
summary: z
|
|
304
|
+
.string()
|
|
305
|
+
.describe("One or two sentences explaining the verdict."),
|
|
306
|
+
}),
|
|
307
|
+
async execute({ prUrl, verdict, summary }, ctx) {
|
|
308
|
+
const { owner, repo, number } = parsePullUrl(prUrl);
|
|
309
|
+
const review =
|
|
310
|
+
verdict === "approve"
|
|
311
|
+
? { event: "APPROVE" as const, body: `PR approver: ${summary}` }
|
|
312
|
+
: {
|
|
313
|
+
event: "COMMENT" as const,
|
|
314
|
+
body: `PR approver: this change needs a human review. ${summary}`,
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const octokit = await ctx.host.github.getOctokit();
|
|
318
|
+
await octokit.rest.pulls.createReview({
|
|
319
|
+
owner,
|
|
320
|
+
repo,
|
|
321
|
+
pull_number: number,
|
|
322
|
+
event: review.event,
|
|
323
|
+
body: review.body,
|
|
324
|
+
});
|
|
325
|
+
return { posted: true, ...review };
|
|
326
|
+
},
|
|
327
|
+
});
|
|
149
328
|
```
|
|
150
329
|
|
|
151
|
-
|
|
152
|
-
|
|
330
|
+
The tool posts a real review: an APPROVE when the agent approves, a
|
|
331
|
+
comment asking for a human otherwise. Two GitHub rules shape how you
|
|
332
|
+
test it. Your credential needs write access to the repo it reviews,
|
|
333
|
+
and GitHub rejects approving your own pull request, so hand the agent
|
|
334
|
+
a teammate's PR rather than one you authored. When a post fails, the
|
|
335
|
+
tool call reports the GitHub error to the model and the turn keeps
|
|
336
|
+
going.
|
|
337
|
+
|
|
338
|
+
Want a person to sign off before the review lands? Set
|
|
339
|
+
`needsApproval: true` on the tool and the call parks until someone
|
|
340
|
+
approves it from the playground or Slack.
|
|
341
|
+
[Human-in-the-loop approvals](./guides/human-in-the-loop.md) shows the
|
|
342
|
+
flow.
|
|
343
|
+
|
|
344
|
+
## Review a pull request
|
|
345
|
+
|
|
346
|
+
Run the whole loop on a pull request your credential can review. A
|
|
347
|
+
teammate's open PR is the right pick: write access to the repo, and
|
|
348
|
+
not authored by you.
|
|
153
349
|
|
|
154
350
|
```bash
|
|
155
351
|
agentkit run --dir . \
|
|
156
|
-
--message "
|
|
352
|
+
--message "Review https://github.com/acme/checkout/pull/42"
|
|
157
353
|
```
|
|
158
354
|
|
|
159
|
-
The
|
|
160
|
-
|
|
355
|
+
The trajectory shows two tool calls. The agent inspects the PR, reads
|
|
356
|
+
the patches, and submits its verdict. A small, clean change gets an
|
|
357
|
+
APPROVE review on the spot, with a one-line summary of what it checked.
|
|
358
|
+
A large one gets a comment asking for a human review, pointing at the
|
|
359
|
+
files a reviewer should start with. Same instructions, different
|
|
360
|
+
behavior, because the policy in the tool decided how much the model got
|
|
361
|
+
to see.
|
|
161
362
|
|
|
162
|
-
|
|
363
|
+
Open the PR on GitHub: the review is on the timeline, posted by
|
|
364
|
+
whatever identity your credential belongs to.
|
|
365
|
+
|
|
366
|
+
## Wake it from GitHub
|
|
163
367
|
|
|
164
|
-
|
|
368
|
+
A reviewer you have to prompt is only half useful. Give the agent a
|
|
369
|
+
GitHub channel so pull requests wake it. Create
|
|
370
|
+
`agent/channels/github.ts`:
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
import {
|
|
374
|
+
defaultGitHubAuth,
|
|
375
|
+
githubChannel,
|
|
376
|
+
} from "@cursor/july/channels/github";
|
|
377
|
+
|
|
378
|
+
const REVIEW_ACTIONS = new Set(["opened", "reopened", "ready_for_review"]);
|
|
379
|
+
|
|
380
|
+
export default githubChannel({
|
|
381
|
+
botName: "pr-approver",
|
|
382
|
+
webhookEvents: ["pull_request"],
|
|
383
|
+
// submit_review owns every GitHub write. Without these flags the channel
|
|
384
|
+
// also posts chat replies and reactions to the PR when the token allows it.
|
|
385
|
+
deliverReplies: false,
|
|
386
|
+
progress: { reactions: false },
|
|
387
|
+
onPullRequest: (ctx, pr) => {
|
|
388
|
+
if (!REVIEW_ACTIONS.has(pr.action) || pr.draft) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
auth: defaultGitHubAuth(ctx),
|
|
393
|
+
title: `Review ${ctx.repository.fullName}#${pr.number}`,
|
|
394
|
+
context: [
|
|
395
|
+
"",
|
|
396
|
+
`Review ${pr.url}. Inspect it first, then submit your verdict with submit_review.`,
|
|
397
|
+
],
|
|
398
|
+
};
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The channel mounts `POST /v1/channels/github` and dispatches on the
|
|
404
|
+
`pull_request` events you declared. Opened, reopened, and undrafted PRs
|
|
405
|
+
start a model turn; everything else returns `null` and is skipped.
|
|
406
|
+
|
|
407
|
+
Serve the agent, then replay a real PR at it from a second terminal.
|
|
408
|
+
`replay` reads the PR through `gh api`, synthesizes a GitHub-shaped
|
|
409
|
+
webhook delivery, and POSTs it to the channel. No repo admin, no
|
|
410
|
+
tunnel:
|
|
165
411
|
|
|
166
412
|
```bash
|
|
167
413
|
agentkit serve --dir . --dev
|
|
414
|
+
# second terminal:
|
|
415
|
+
agentkit github replay https://github.com/acme/checkout/pull/42 \
|
|
416
|
+
--dir . --action opened
|
|
168
417
|
```
|
|
169
418
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
419
|
+
The replay prints the delivery, and the serve terminal shows the wake:
|
|
420
|
+
|
|
421
|
+
```text
|
|
422
|
+
[agentkit] replaying acme/checkout#42 (pull_request) → 1 channel
|
|
423
|
+
[agentkit] pull_request.opened → pr-approver/github 200
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
The agent runs the same inspect-then-submit loop, unprompted this time.
|
|
427
|
+
Replay the same PR again and the channel resumes that PR's session
|
|
428
|
+
instead of starting a new one: each pull request keeps one running
|
|
429
|
+
conversation.
|
|
430
|
+
|
|
431
|
+
## Open the playground
|
|
432
|
+
|
|
433
|
+
Keep `serve --dev` running and open the playground URL it printed. The
|
|
434
|
+
webhook session is in the session list, titled
|
|
435
|
+
`Review acme/checkout#42`, with the trigger message, both tool calls,
|
|
436
|
+
and the verdict laid out. Start a new chat there and ask for another
|
|
437
|
+
review to watch a turn stream live.
|
|
438
|
+
|
|
439
|
+
## Go live
|
|
440
|
+
|
|
441
|
+
Replay is for development. For real deliveries, serve with
|
|
442
|
+
`--cursor-events --repo owner/repo` to pull events for repositories
|
|
443
|
+
connected to Cursor with no public URL, or run
|
|
444
|
+
`agentkit github forward` to relay webhooks to your dev server. The
|
|
445
|
+
[GitHub guide](./guides/github.md) compares the options. In production,
|
|
446
|
+
give the host GitHub App credentials so reviews post as your app's bot
|
|
447
|
+
identity instead of a personal account.
|
|
173
448
|
|
|
174
449
|
## Where to go next
|
|
175
450
|
|
|
176
|
-
- [
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
451
|
+
- [`examples/approval-buddy`](../examples/approval-buddy/): the
|
|
452
|
+
production-shaped sibling, with commit statuses, review subagents,
|
|
453
|
+
and a deterministic stamp policy
|
|
454
|
+
- [Evals](./evals.md): freeze these two PRs as regression checks so
|
|
455
|
+
prompt changes can't flip a verdict
|
|
456
|
+
- [Tools](./reference/tools.md): more on typed tools, approvals, and
|
|
457
|
+
direct calls
|
|
458
|
+
- [GitHub](./guides/github.md): fixtures, forwarding, and pulling
|
|
459
|
+
events from Cursor
|
|
182
460
|
- [Building agents with agents](./building-with-agents.md): have a
|
|
183
461
|
coding agent extend the project for you
|
|
@@ -43,16 +43,18 @@ covers controlling that.
|
|
|
43
43
|
## Best Practices
|
|
44
44
|
|
|
45
45
|
Keep them a few lines: identity, when to use which tool, output shape.
|
|
46
|
-
The [quickstart
|
|
46
|
+
The [quickstart PR approver](../quickstart.md) is the pattern:
|
|
47
47
|
|
|
48
48
|
```md
|
|
49
|
-
#
|
|
49
|
+
# PR approver
|
|
50
50
|
|
|
51
|
-
You
|
|
51
|
+
You review GitHub pull requests. Be specific and brief.
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
1. Call `inspect_pr` first. Never judge a change you haven't fetched.
|
|
54
|
+
2. Match your review to the complexity it reports.
|
|
55
|
+
3. Never approve a draft.
|
|
56
|
+
|
|
57
|
+
End with one sentence: the verdict and why.
|
|
56
58
|
```
|
|
57
59
|
|
|
58
60
|
- Name the tools and the decision rule ("use X before answering about
|
|
@@ -106,7 +106,7 @@ inputs again, and adds an eval for each improvement you keep.
|
|
|
106
106
|
|
|
107
107
|
## Related
|
|
108
108
|
|
|
109
|
-
- [Build your first
|
|
109
|
+
- [Build your first PR approver](./quickstart.md)
|
|
110
110
|
- [Building agents with agents](./building-with-agents.md)
|
|
111
111
|
- [Evals](./evals.md)
|
|
112
112
|
- [Hillclimbing](./hillclimbing.md)
|