@cursor/july 0.1.53 → 0.1.55
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 +12 -0
- package/dist/channels/slack/manifest.d.ts +5 -3
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +7 -3
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.DAKcDclS.js → app.CVQxhV85.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BFpWSo6D.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.BNIaCSfG.js → VPLocalSearchBox.QAPzJjql.js} +1 -1
- package/dist/docs/assets/chunks/{theme.B47GzJjc.js → theme.CSxTCOFu.js} +2 -2
- package/dist/docs/assets/{reference_cli.md.-V6J7Cl8.js → reference_cli.md.D_CCfG8R.js} +2 -0
- package/dist/docs/assets/templates_triage.md.DWuQ1bZz.js +9 -0
- package/dist/docs/assets/templates_triage.md.DWuQ1bZz.lean.js +1 -0
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/opentelemetry.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +6 -4
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/templates/triage.html +14 -16
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +10 -1
- package/dist/internal/cli-deploy.js +1 -1
- package/dist/internal/cli-slack.d.ts +2 -0
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +35 -18
- package/dist/internal/init-project.d.ts +3 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +77 -45
- package/dist/internal/slack-provision-client.js +1 -1
- package/dist/playground/assets/{index-BHAPd9KT.js → index-0oqXUkrR.js} +44 -44
- package/dist/playground/assets/index-tHCjJxv4.css +1 -0
- package/dist/playground/index.html +2 -2
- package/docs/reference/cli.md +2 -0
- package/docs/templates/triage.md +59 -101
- package/package.json +1 -1
- package/src/channels/slack/manifest.ts +9 -3
- package/src/internal/cli-ax.ts +10 -1
- package/src/internal/cli-deploy.ts +2 -2
- package/src/internal/cli-slack.ts +57 -18
- package/src/internal/init-project.ts +93 -48
- package/src/internal/slack-provision-client.ts +1 -1
- package/templates/triage/README.md +27 -29
- package/templates/triage/agent/agent.ts +1 -0
- package/templates/triage/agent/channels/queue.ts +47 -0
- package/templates/triage/agent/channels/webhook.ts +50 -0
- package/templates/triage/agent/instructions.md +24 -13
- package/templates/triage/agent/lib/actuate.ts +759 -0
- package/templates/triage/agent/lib/parse-queue-event.ts +168 -0
- package/templates/triage/agent/lib/tracker.ts +5 -0
- package/templates/triage/agent/lib/types.ts +52 -0
- package/templates/triage/agent/lib/verify-webhook.ts +81 -0
- package/templates/triage/agent/skills/triage-rubric.md +6 -5
- package/templates/triage/agent/tools/record_triage.ts +24 -5
- package/templates/triage/evals/triage.eval.ts +25 -3
- package/templates/triage/overlays/jira/README.md +21 -0
- package/templates/triage/overlays/jira/agent/lib/tracker.ts +8 -0
- package/templates/triage/overlays/jira/agent/mcp-connections/tracker.ts +7 -0
- package/templates/triage/overlays/linear/README.md +21 -0
- package/templates/triage/overlays/linear/agent/lib/tracker.ts +8 -0
- package/templates/triage/overlays/linear/agent/mcp-connections/tracker.ts +7 -0
- package/templates/triage/package.json +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.C4uxzYEU.js +0 -1
- package/dist/docs/assets/templates_triage.md.CConZt1z.js +0 -11
- package/dist/docs/assets/templates_triage.md.CConZt1z.lean.js +0 -1
- package/dist/playground/assets/index-DpKH0HcT.css +0 -1
- package/templates/triage/agent/channels/intake.ts +0 -20
- package/templates/triage/agent/channels/slack.ts +0 -3
- /package/dist/docs/assets/{reference_cli.md.-V6J7Cl8.lean.js → reference_cli.md.D_CCfG8R.lean.js} +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { QueueIssue } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export const ISSUE_ID: RegExp = /\b[A-Z][A-Z0-9]{0,9}-\d+\b/;
|
|
4
|
+
export const LINEAR_URL: RegExp =
|
|
5
|
+
/https:\/\/linear\.app\/(?:[^/\s"'<>]+\/)?issue\/[A-Z][A-Z0-9]{0,9}-\d+[^\s"'<>]*/i;
|
|
6
|
+
export const JIRA_URL: RegExp =
|
|
7
|
+
/https:\/\/\S+\/(?:browse\/[A-Z][A-Z0-9]{0,9}-\d+|jira\/software\/[^\s"'<>]*selectedIssue=[A-Z][A-Z0-9]{0,9}-\d+)/i;
|
|
8
|
+
|
|
9
|
+
export type ParseResult =
|
|
10
|
+
| { ok: true; issue: QueueIssue }
|
|
11
|
+
| { ok: false; ignored: true }
|
|
12
|
+
| { ok: false; error: string };
|
|
13
|
+
|
|
14
|
+
export function isIssueId(value: string): boolean {
|
|
15
|
+
const match = value.match(ISSUE_ID);
|
|
16
|
+
return match !== null && match[0] === value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function pinIssueIdentifier(
|
|
20
|
+
continuationKey: string | null | undefined,
|
|
21
|
+
fallback: string
|
|
22
|
+
): string {
|
|
23
|
+
if (typeof continuationKey === "string" && isIssueId(continuationKey)) {
|
|
24
|
+
return continuationKey;
|
|
25
|
+
}
|
|
26
|
+
return fallback;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isTrackerWebhook(body: unknown): boolean {
|
|
30
|
+
return (
|
|
31
|
+
isRecord(body) &&
|
|
32
|
+
(typeof body["type"] === "string" || typeof body["webhookEvent"] === "string")
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function parseQueueEvent(body: unknown): ParseResult {
|
|
37
|
+
if (!isRecord(body)) {
|
|
38
|
+
return { ok: false, error: "unsupported queue event" };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof body["webhookEvent"] === "string") {
|
|
42
|
+
if (body["webhookEvent"] !== "jira:issue_created") {
|
|
43
|
+
return { ok: false, ignored: true };
|
|
44
|
+
}
|
|
45
|
+
return parseJiraIssue(body);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof body["type"] === "string") {
|
|
49
|
+
if (body["type"] !== "Issue" || body["action"] !== "create") {
|
|
50
|
+
return { ok: false, ignored: true };
|
|
51
|
+
}
|
|
52
|
+
return parseLinearIssue(body);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof body["identifier"] === "string") {
|
|
56
|
+
return queueIssue({
|
|
57
|
+
identifier: body["identifier"],
|
|
58
|
+
title: stringValue(body["title"]),
|
|
59
|
+
description: descriptionValue(body["description"]),
|
|
60
|
+
url: stringValue(body["url"]),
|
|
61
|
+
reporter: reporterValue(body["reporter"]),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { ok: false, error: "identifier is required" };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function formatQueuePrompt(issue: QueueIssue): string {
|
|
69
|
+
return [
|
|
70
|
+
"Triage this existing issue.",
|
|
71
|
+
"",
|
|
72
|
+
`identifier: ${issue.identifier}`,
|
|
73
|
+
`title: ${issue.title}`,
|
|
74
|
+
`description: ${issue.description}`,
|
|
75
|
+
`url: ${issue.url}`,
|
|
76
|
+
`reporter: ${issue.reporter ?? ""}`,
|
|
77
|
+
].join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseLinearIssue(body: Record<string, unknown>): ParseResult {
|
|
81
|
+
const data = body["data"];
|
|
82
|
+
if (!isRecord(data)) {
|
|
83
|
+
return { ok: false, ignored: true };
|
|
84
|
+
}
|
|
85
|
+
const identifier =
|
|
86
|
+
stringValue(data["identifier"]) || stringValue(data["key"]);
|
|
87
|
+
if (identifier.match(ISSUE_ID)?.[0] === undefined) {
|
|
88
|
+
return { ok: false, ignored: true };
|
|
89
|
+
}
|
|
90
|
+
return queueIssue({
|
|
91
|
+
identifier,
|
|
92
|
+
title: stringValue(data["title"]),
|
|
93
|
+
description: descriptionValue(data["description"]),
|
|
94
|
+
url: stringValue(data["url"]),
|
|
95
|
+
reporter: reporterValue(data["creator"]),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function parseJiraIssue(body: Record<string, unknown>): ParseResult {
|
|
100
|
+
const issue = body["issue"];
|
|
101
|
+
if (!isRecord(issue)) {
|
|
102
|
+
return { ok: false, ignored: true };
|
|
103
|
+
}
|
|
104
|
+
const fields = isRecord(issue["fields"]) ? issue["fields"] : {};
|
|
105
|
+
const identifier = stringValue(issue["key"]);
|
|
106
|
+
if (identifier.match(ISSUE_ID)?.[0] === undefined) {
|
|
107
|
+
return { ok: false, ignored: true };
|
|
108
|
+
}
|
|
109
|
+
return queueIssue({
|
|
110
|
+
identifier,
|
|
111
|
+
title: stringValue(fields["summary"]),
|
|
112
|
+
description: descriptionValue(fields["description"]),
|
|
113
|
+
url: stringValue(issue["self"]),
|
|
114
|
+
reporter: reporterValue(fields["reporter"]),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function queueIssue(input: QueueIssue): ParseResult {
|
|
119
|
+
const identifier = input.identifier.match(ISSUE_ID)?.[0];
|
|
120
|
+
if (identifier === undefined) {
|
|
121
|
+
return { ok: false, error: "identifier is required" };
|
|
122
|
+
}
|
|
123
|
+
return { ok: true, issue: { ...input, identifier } };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
127
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function stringValue(value: unknown): string {
|
|
131
|
+
return typeof value === "string" ? value : "";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function richTextValue(value: unknown): string {
|
|
135
|
+
if (typeof value === "string") {
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
if (Array.isArray(value)) {
|
|
139
|
+
return value.map(richTextValue).filter(Boolean).join("\n");
|
|
140
|
+
}
|
|
141
|
+
if (!isRecord(value)) {
|
|
142
|
+
return "";
|
|
143
|
+
}
|
|
144
|
+
if (typeof value["text"] === "string") {
|
|
145
|
+
return value["text"];
|
|
146
|
+
}
|
|
147
|
+
return richTextValue(value["content"]);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function descriptionValue(value: unknown): string {
|
|
151
|
+
return richTextValue(value);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function reporterValue(value: unknown): string | undefined {
|
|
155
|
+
if (typeof value === "string" && value !== "") {
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
if (!isRecord(value)) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
for (const key of ["displayName", "name", "email", "emailAddress"]) {
|
|
162
|
+
const candidate = value[key];
|
|
163
|
+
if (typeof candidate === "string" && candidate !== "") {
|
|
164
|
+
return candidate;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type TriageKind = "bug" | "question" | "feedback";
|
|
2
|
+
|
|
3
|
+
export type TriageSeverity = "low" | "medium" | "high" | "urgent";
|
|
4
|
+
|
|
5
|
+
export interface QueueIssue {
|
|
6
|
+
identifier: string;
|
|
7
|
+
url: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description: string;
|
|
10
|
+
reporter?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TriageVerdict {
|
|
14
|
+
identifier: string;
|
|
15
|
+
kind: TriageKind;
|
|
16
|
+
severity: TriageSeverity;
|
|
17
|
+
title: string;
|
|
18
|
+
summary: string;
|
|
19
|
+
nextStep: string;
|
|
20
|
+
reporter?: string;
|
|
21
|
+
duplicateOf?: string;
|
|
22
|
+
needsInfo?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TrackerIssue {
|
|
26
|
+
identifier: string;
|
|
27
|
+
url: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type QueueAction =
|
|
31
|
+
| { op: "commented" }
|
|
32
|
+
| { op: "prioritized"; severity: TriageSeverity }
|
|
33
|
+
| { op: "duplicate_of"; of: string }
|
|
34
|
+
| { op: "needs_info" };
|
|
35
|
+
|
|
36
|
+
export type TrackerResult =
|
|
37
|
+
| { status: "not_connected" }
|
|
38
|
+
| { status: "skipped"; reason: "eval" }
|
|
39
|
+
| { status: "acted"; issue: TrackerIssue; actions: QueueAction[] }
|
|
40
|
+
| { status: "failed"; message: string };
|
|
41
|
+
|
|
42
|
+
export interface RecordTriageResult {
|
|
43
|
+
verdict: TriageVerdict;
|
|
44
|
+
evaluatedAt: string;
|
|
45
|
+
tracker: TrackerResult;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type TrackerKind = "none" | "linear" | "jira";
|
|
49
|
+
|
|
50
|
+
export type TrackerConfig =
|
|
51
|
+
| { kind: "none" }
|
|
52
|
+
| { kind: "linear" | "jira"; connectionName: string };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const REPLAY_WINDOW_MS = 60_000;
|
|
4
|
+
|
|
5
|
+
export type VerifyWebhookResult =
|
|
6
|
+
| { ok: true }
|
|
7
|
+
| { ok: false; status: 401 };
|
|
8
|
+
|
|
9
|
+
export function verifyTrackerWebhook(input: {
|
|
10
|
+
rawBody: string;
|
|
11
|
+
linearSignature: string | null;
|
|
12
|
+
hubSignature: string | null;
|
|
13
|
+
linearSecret: string | undefined;
|
|
14
|
+
jiraSecret: string | undefined;
|
|
15
|
+
nowMs: number;
|
|
16
|
+
}): VerifyWebhookResult {
|
|
17
|
+
if (input.linearSignature !== null && input.linearSignature !== "") {
|
|
18
|
+
if (
|
|
19
|
+
input.linearSecret === undefined ||
|
|
20
|
+
input.linearSecret === "" ||
|
|
21
|
+
!hmacHexEqual(input.linearSecret, input.rawBody, input.linearSignature)
|
|
22
|
+
) {
|
|
23
|
+
return { ok: false, status: 401 };
|
|
24
|
+
}
|
|
25
|
+
return linearReplayFresh(input.rawBody, input.nowMs);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (input.hubSignature !== null && input.hubSignature !== "") {
|
|
29
|
+
if (
|
|
30
|
+
input.jiraSecret === undefined ||
|
|
31
|
+
input.jiraSecret === "" ||
|
|
32
|
+
!hmacHexEqual(input.jiraSecret, input.rawBody, input.hubSignature)
|
|
33
|
+
) {
|
|
34
|
+
return { ok: false, status: 401 };
|
|
35
|
+
}
|
|
36
|
+
return { ok: true };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { ok: false, status: 401 };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function linearReplayFresh(
|
|
43
|
+
rawBody: string,
|
|
44
|
+
nowMs: number
|
|
45
|
+
): VerifyWebhookResult {
|
|
46
|
+
let body: unknown;
|
|
47
|
+
try {
|
|
48
|
+
body = JSON.parse(rawBody);
|
|
49
|
+
} catch {
|
|
50
|
+
return { ok: false, status: 401 };
|
|
51
|
+
}
|
|
52
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
53
|
+
return { ok: false, status: 401 };
|
|
54
|
+
}
|
|
55
|
+
const timestamp = (body as { webhookTimestamp?: unknown }).webhookTimestamp;
|
|
56
|
+
if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) {
|
|
57
|
+
return { ok: false, status: 401 };
|
|
58
|
+
}
|
|
59
|
+
if (Math.abs(nowMs - timestamp) > REPLAY_WINDOW_MS) {
|
|
60
|
+
return { ok: false, status: 401 };
|
|
61
|
+
}
|
|
62
|
+
return { ok: true };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function hmacHexEqual(secret: string, rawBody: string, header: string): boolean {
|
|
66
|
+
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
|
|
67
|
+
const received = header.replace(/^sha256=/i, "").trim().toLowerCase();
|
|
68
|
+
let expectedBytes: Buffer;
|
|
69
|
+
let receivedBytes: Buffer;
|
|
70
|
+
try {
|
|
71
|
+
expectedBytes = Buffer.from(expected, "hex");
|
|
72
|
+
receivedBytes = Buffer.from(received, "hex");
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return (
|
|
77
|
+
expectedBytes.length > 0 &&
|
|
78
|
+
expectedBytes.length === receivedBytes.length &&
|
|
79
|
+
timingSafeEqual(expectedBytes, receivedBytes)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Definitions and tie-breakers for classifying
|
|
2
|
+
description: Definitions and tie-breakers for classifying an issue as bug, question, or feedback, plus the severity ladder.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Triage rubric
|
|
6
6
|
|
|
7
7
|
## Kinds
|
|
8
8
|
|
|
9
|
-
- **bug**: something is broken, erroring, slow, or
|
|
10
|
-
now, and the reporter wants it diagnosed or fixed.
|
|
9
|
+
- **bug**: the issue says something is broken, erroring, slow, or
|
|
10
|
+
behaving wrong now, and the reporter wants it diagnosed or fixed. It
|
|
11
|
+
usually has a
|
|
11
12
|
symptom, an error, an id, a timestamp, or steps.
|
|
12
13
|
- **question**: the reporter wants to know something that has an answer
|
|
13
14
|
(how does X work, is Y expected, who owns Z). Someone has to look it
|
|
@@ -23,7 +24,7 @@ description: Definitions and tie-breakers for classifying a report as bug, quest
|
|
|
23
24
|
- A question about a specific failure that already happened is a bug.
|
|
24
25
|
"Why did my export fail yesterday?" needs an investigation, not a
|
|
25
26
|
lookup.
|
|
26
|
-
-
|
|
27
|
+
- An issue that reports a bug and suggests a fix is a bug.
|
|
27
28
|
- A known bug with a workaround that only works sometimes is a bug. The
|
|
28
29
|
unreliable workaround is the thing to explain.
|
|
29
30
|
- A feature idea phrased as a question ("could we have dark mode?") is
|
|
@@ -39,5 +40,5 @@ description: Definitions and tie-breakers for classifying a report as bug, quest
|
|
|
39
40
|
costs time.
|
|
40
41
|
- **low**: cosmetic, minor friction, questions, and most feedback.
|
|
41
42
|
|
|
42
|
-
Questions and feedback default to low unless the
|
|
43
|
+
Questions and feedback default to low unless the issue shows real
|
|
43
44
|
user impact.
|
|
@@ -1,23 +1,42 @@
|
|
|
1
1
|
import { defineTool } from "@cursor/july/tools";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { actuate } from "../lib/actuate.js";
|
|
4
|
+
import tracker from "../lib/tracker.js";
|
|
3
5
|
|
|
4
6
|
export default defineTool({
|
|
5
7
|
description:
|
|
6
|
-
"Record the
|
|
8
|
+
"Record the verdict for the current tracker issue and enrich it in place with a comment, priority, or duplicate link. Call exactly once per issue. Never create a ticket.",
|
|
7
9
|
inputSchema: z.object({
|
|
10
|
+
identifier: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("Identifier of the existing tracker issue."),
|
|
8
13
|
kind: z.enum(["bug", "question", "feedback"]),
|
|
9
14
|
severity: z.enum(["low", "medium", "high", "urgent"]),
|
|
10
15
|
title: z
|
|
11
16
|
.string()
|
|
12
17
|
.max(80)
|
|
13
|
-
.describe("Short
|
|
18
|
+
.describe("Short title for the issue."),
|
|
14
19
|
summary: z
|
|
15
20
|
.string()
|
|
16
|
-
.describe("What the
|
|
21
|
+
.describe("What the issue says, in two sentences or less."),
|
|
17
22
|
nextStep: z.string().describe("The single next action for the team."),
|
|
18
23
|
reporter: z.string().optional().describe("Who reported it, when known."),
|
|
24
|
+
duplicateOf: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Identifier of a matching existing issue."),
|
|
28
|
+
needsInfo: z
|
|
29
|
+
.boolean()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Whether the issue needs more information."),
|
|
19
32
|
}),
|
|
20
|
-
async execute(verdict) {
|
|
21
|
-
return {
|
|
33
|
+
async execute(verdict, ctx) {
|
|
34
|
+
return actuate({
|
|
35
|
+
verdict,
|
|
36
|
+
mcp: ctx.host.mcp,
|
|
37
|
+
tracker,
|
|
38
|
+
purpose: ctx.session.purpose,
|
|
39
|
+
issueKey: ctx.session.continuationKey,
|
|
40
|
+
});
|
|
22
41
|
},
|
|
23
42
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineEval, includes } from "@cursor/july/evals";
|
|
2
|
+
import { formatQueuePrompt } from "../agent/lib/parse-queue-event.js";
|
|
2
3
|
|
|
3
4
|
export default defineEval({
|
|
4
5
|
tags: ["smoke"],
|
|
@@ -8,7 +9,14 @@ export default defineEval({
|
|
|
8
9
|
description: "A concrete failure is recorded as a bug.",
|
|
9
10
|
async test(t) {
|
|
10
11
|
await t.send(
|
|
11
|
-
|
|
12
|
+
formatQueuePrompt({
|
|
13
|
+
identifier: "TRI-101",
|
|
14
|
+
title: "CSV export returns 500",
|
|
15
|
+
description:
|
|
16
|
+
"Export to CSV has failed with a 500 since this morning. Request id 41ac. Retrying does not help.",
|
|
17
|
+
url: "https://linear.app/acme/issue/TRI-101",
|
|
18
|
+
reporter: "Sam",
|
|
19
|
+
})
|
|
12
20
|
);
|
|
13
21
|
t.succeeded();
|
|
14
22
|
t.calledTool("record_triage");
|
|
@@ -20,7 +28,14 @@ export default defineEval({
|
|
|
20
28
|
description: "A how-does-it-work ask is recorded as a question.",
|
|
21
29
|
async test(t) {
|
|
22
30
|
await t.send(
|
|
23
|
-
|
|
31
|
+
formatQueuePrompt({
|
|
32
|
+
identifier: "TRI-102",
|
|
33
|
+
title: "Rotate staging API keys",
|
|
34
|
+
description:
|
|
35
|
+
"How do I rotate the API keys for the staging environment?",
|
|
36
|
+
url: "https://linear.app/acme/issue/TRI-102",
|
|
37
|
+
reporter: "Morgan",
|
|
38
|
+
})
|
|
24
39
|
);
|
|
25
40
|
t.succeeded();
|
|
26
41
|
t.calledTool("record_triage");
|
|
@@ -32,7 +47,14 @@ export default defineEval({
|
|
|
32
47
|
description: "A suggestion is recorded as feedback.",
|
|
33
48
|
async test(t) {
|
|
34
49
|
await t.send(
|
|
35
|
-
|
|
50
|
+
formatQueuePrompt({
|
|
51
|
+
identifier: "TRI-103",
|
|
52
|
+
title: "Collapse onboarding profile pages",
|
|
53
|
+
description:
|
|
54
|
+
"The onboarding wizard has too many steps. You should collapse the profile pages into one.",
|
|
55
|
+
url: "https://linear.app/acme/issue/TRI-103",
|
|
56
|
+
reporter: "Riley",
|
|
57
|
+
})
|
|
36
58
|
);
|
|
37
59
|
t.succeeded();
|
|
38
60
|
t.calledTool("record_triage");
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Triage agent for Jira
|
|
2
|
+
|
|
3
|
+
Comments on existing Jira issues. Scaffold with:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @cursor/july init ./my-triage-agent --template triage-jira
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Connect Jira in Cursor, then:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
agent-sdk login
|
|
13
|
+
agent-sdk dev
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Paste an issue identifier in the playground. To receive Jira
|
|
17
|
+
`issue_created` webhooks, self-host, set `JIRA_WEBHOOK_SECRET`, and
|
|
18
|
+
point the webhook at `POST /<slug>/v1/channels/webhook`.
|
|
19
|
+
|
|
20
|
+
Edit `agent/skills/triage-rubric.md` for your severity ladder.
|
|
21
|
+
`agent-sdk eval` skips Jira writes.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Triage agent for Linear
|
|
2
|
+
|
|
3
|
+
Comments on existing Linear issues. Scaffold with:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @cursor/july init ./my-triage-agent --template triage-linear
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Connect Linear in Cursor, then:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
agent-sdk login
|
|
13
|
+
agent-sdk dev
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Paste an issue identifier in the playground. To receive Linear Issue
|
|
17
|
+
create webhooks, self-host, set `LINEAR_WEBHOOK_SECRET`, and point the
|
|
18
|
+
webhook at `POST /<slug>/v1/channels/webhook`.
|
|
19
|
+
|
|
20
|
+
Edit `agent/skills/triage-rubric.md` for your severity ladder.
|
|
21
|
+
`agent-sdk eval` skips Linear writes.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "triage-agent",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Comments on existing Linear or Jira issues.",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"check": "tsc --noEmit",
|
|
8
8
|
"dev": "agent-sdk dev"
|