@vantageos/backend-doctor 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 +88 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +94 -0
- package/dist/contract.d.ts +18 -0
- package/dist/contract.js +29 -0
- package/dist/coverage-cli.d.ts +2 -0
- package/dist/coverage-cli.js +17 -0
- package/dist/coverage.d.ts +32 -0
- package/dist/coverage.js +77 -0
- package/dist/detectors/oracle-axis1.d.ts +86 -0
- package/dist/detectors/oracle-axis1.js +221 -0
- package/dist/detectors/oracle-axis2.d.ts +22 -0
- package/dist/detectors/oracle-axis2.js +149 -0
- package/dist/detectors/predicates.d.ts +19 -0
- package/dist/detectors/predicates.js +334 -0
- package/dist/detectors/shared.d.ts +56 -0
- package/dist/detectors/shared.js +103 -0
- package/dist/detectors/source/_scan.d.ts +43 -0
- package/dist/detectors/source/_scan.js +82 -0
- package/dist/detectors/source/backfill.d.ts +27 -0
- package/dist/detectors/source/backfill.js +211 -0
- package/dist/detectors/source/contract.d.ts +33 -0
- package/dist/detectors/source/contract.js +385 -0
- package/dist/detectors/source/identity.d.ts +47 -0
- package/dist/detectors/source/identity.js +410 -0
- package/dist/detectors/source/jobs.d.ts +10 -0
- package/dist/detectors/source/jobs.js +209 -0
- package/dist/detectors/source/lifecycle.d.ts +6 -0
- package/dist/detectors/source/lifecycle.js +147 -0
- package/dist/detectors/source/readbudget.d.ts +6 -0
- package/dist/detectors/source/readbudget.js +133 -0
- package/dist/detectors/source/refusal.d.ts +6 -0
- package/dist/detectors/source/refusal.js +125 -0
- package/dist/detectors/source/security.d.ts +37 -0
- package/dist/detectors/source/security.js +392 -0
- package/dist/detectors/source/writesafety.d.ts +14 -0
- package/dist/detectors/source/writesafety.js +345 -0
- package/dist/detectors/standard/_doc.d.ts +66 -0
- package/dist/detectors/standard/_doc.js +152 -0
- package/dist/detectors/standard/r41-frontier.d.ts +58 -0
- package/dist/detectors/standard/r41-frontier.js +176 -0
- package/dist/detectors/standard/r46-returns-validator.d.ts +61 -0
- package/dist/detectors/standard/r46-returns-validator.js +474 -0
- package/dist/detectors/standard/standard-doc.d.ts +12 -0
- package/dist/detectors/standard/standard-doc.js +271 -0
- package/dist/detectors/structural.d.ts +14 -0
- package/dist/detectors/structural.js +83 -0
- package/dist/engine-types.d.ts +10 -0
- package/dist/engine-types.js +1 -0
- package/dist/not-checkable.d.ts +22 -0
- package/dist/not-checkable.js +37 -0
- package/dist/oracle.d.ts +33 -0
- package/dist/oracle.js +133 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.js +140 -0
- package/dist/report.d.ts +35 -0
- package/dist/report.js +138 -0
- package/dist/severity.d.ts +30 -0
- package/dist/severity.js +121 -0
- package/dist/tally.d.ts +57 -0
- package/dist/tally.js +152 -0
- package/dist/trigger.d.ts +47 -0
- package/dist/trigger.js +143 -0
- package/fixtures/backend-standard-51ec7af1.md +648 -0
- package/fixtures/backend-standard-a9d7877b.md +629 -0
- package/fixtures/mcp-app-standard-a74a042b.md +279 -0
- package/fixtures/mcp-extension-standard-a74a042b.md +259 -0
- package/fixtures/mcp-server-standard-a74a042b.md +434 -0
- package/fixtures/mcp-standard-a74a042b.md +354 -0
- package/fixtures/vp-1200-green/.pin +1 -0
- package/fixtures/vp-1200-green/convex/migrations.ts +350 -0
- package/fixtures/vp-1200-green/convex/schema.ts +1243 -0
- package/fixtures/vp-1200-red/.pin +1 -0
- package/fixtures/vp-1200-red/convex/migrations.ts +239 -0
- package/fixtures/vp-1200-red/convex/schema.ts +1243 -0
- package/fixtures/vp-by-tool-day158.csv +190 -0
- package/fixtures/vp-convex/.github/workflows/npm-publish.yml +163 -0
- package/fixtures/vp-convex/.pin +1 -0
- package/fixtures/vp-convex/bun.lock +783 -0
- package/fixtures/vp-convex/convex/auth.config.ts +8 -0
- package/fixtures/vp-convex/convex/briefingNotes.ts +321 -0
- package/fixtures/vp-convex/convex/convex.config.ts +14 -0
- package/fixtures/vp-convex/convex/credentials.ts +591 -0
- package/fixtures/vp-convex/convex/crons.ts +72 -0
- package/fixtures/vp-convex/convex/dashboard.ts +199 -0
- package/fixtures/vp-convex/convex/gumroadWebhook.ts +815 -0
- package/fixtures/vp-convex/convex/issues.ts +544 -0
- package/fixtures/vp-convex/convex/kb.ts +305 -0
- package/fixtures/vp-convex/convex/kbMutations.ts +192 -0
- package/fixtures/vp-convex/convex/kbShared.ts +56 -0
- package/fixtures/vp-convex/convex/licenses.ts +443 -0
- package/fixtures/vp-convex/convex/memories.ts +406 -0
- package/fixtures/vp-convex/convex/memoriesScoped.ts +220 -0
- package/fixtures/vp-convex/convex/messages.ts +971 -0
- package/fixtures/vp-convex/convex/migrations.ts +239 -0
- package/fixtures/vp-convex/convex/missionTemplates.ts +369 -0
- package/fixtures/vp-convex/convex/oauth.ts +1608 -0
- package/fixtures/vp-convex/convex/oauthMigrations.ts +96 -0
- package/fixtures/vp-convex/convex/okfBundleNode.ts +1031 -0
- package/fixtures/vp-convex/convex/recurringTasks.ts +386 -0
- package/fixtures/vp-convex/convex/schema.ts +1229 -0
- package/fixtures/vp-convex/convex/tasks.ts +2782 -0
- package/fixtures/vp-r31-green/.pin +1 -0
- package/fixtures/vp-r31-green/convex/migrations.ts +350 -0
- package/fixtures/vp-r31-red/.pin +1 -0
- package/fixtures/vp-r31-red/convex/migrations.ts +286 -0
- package/fixtures/vr-by-tool-day159.csv +89 -0
- package/package.json +39 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query } from "./_generated/server";
|
|
3
|
+
import { api } from "./_generated/api";
|
|
4
|
+
// convex-strict-mode-doc-type-import-needed-when-refactoring-list-query-from-early-return-to-accumulator-post-filter
|
|
5
|
+
import type { Doc } from "./_generated/dataModel";
|
|
6
|
+
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
// Shared validators
|
|
9
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
const issueStatusValidator = v.union(
|
|
12
|
+
v.literal("open"),
|
|
13
|
+
v.literal("in_progress"),
|
|
14
|
+
v.literal("fixed"),
|
|
15
|
+
v.literal("verified"),
|
|
16
|
+
v.literal("closed"),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const issuePriorityValidator = v.union(
|
|
20
|
+
v.literal("urgent"),
|
|
21
|
+
v.literal("high"),
|
|
22
|
+
v.literal("medium"),
|
|
23
|
+
v.literal("low"),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
|
+
// Helper: derive priority from GitHub labels (case-insensitive)
|
|
28
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
function derivePriority(labels: string[]): "urgent" | "high" | "medium" | "low" {
|
|
31
|
+
const lower = labels.map((l) => l.toLowerCase());
|
|
32
|
+
if (lower.some((l) => l.includes("p0") || l.includes("urgent") || l.includes("critical"))) {
|
|
33
|
+
return "urgent";
|
|
34
|
+
}
|
|
35
|
+
if (lower.some((l) => l.includes("p1") || l.includes("high") || l.includes("important"))) {
|
|
36
|
+
return "high";
|
|
37
|
+
}
|
|
38
|
+
if (lower.some((l) => l.includes("p2") || l.includes("medium"))) {
|
|
39
|
+
return "medium";
|
|
40
|
+
}
|
|
41
|
+
return "low";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
// upsertFromGitHub — upsert by repo+issueNumber
|
|
46
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
export const upsertFromGitHub = mutation({
|
|
49
|
+
args: {
|
|
50
|
+
repo: v.string(),
|
|
51
|
+
issueNumber: v.number(),
|
|
52
|
+
title: v.string(),
|
|
53
|
+
body: v.string(),
|
|
54
|
+
htmlUrl: v.string(),
|
|
55
|
+
labels: v.array(v.string()),
|
|
56
|
+
status: issueStatusValidator,
|
|
57
|
+
githubCreatedAt: v.number(),
|
|
58
|
+
githubUpdatedAt: v.number(),
|
|
59
|
+
},
|
|
60
|
+
handler: async (ctx, args): Promise<string> => {
|
|
61
|
+
// Truncate body to 2000 chars
|
|
62
|
+
const body = args.body.slice(0, 2000);
|
|
63
|
+
|
|
64
|
+
// Get assignedOrchestrator + project from repo mapping
|
|
65
|
+
const mapping = await ctx.runQuery(api.githubRepoMapping.getByRepo, {
|
|
66
|
+
repo: args.repo,
|
|
67
|
+
});
|
|
68
|
+
const assignedOrchestrator: string = mapping?.orchestrator ?? "sigma";
|
|
69
|
+
const project: string = mapping?.project ?? args.repo;
|
|
70
|
+
|
|
71
|
+
// Derive priority from labels
|
|
72
|
+
const priority = derivePriority(args.labels);
|
|
73
|
+
|
|
74
|
+
// Check if issue already exists
|
|
75
|
+
const existing = await ctx.db
|
|
76
|
+
.query("issues")
|
|
77
|
+
.withIndex("by_repo_number", (q) =>
|
|
78
|
+
q.eq("repo", args.repo).eq("issueNumber", args.issueNumber),
|
|
79
|
+
)
|
|
80
|
+
.unique();
|
|
81
|
+
|
|
82
|
+
if (existing) {
|
|
83
|
+
await ctx.db.patch(existing._id, {
|
|
84
|
+
title: args.title,
|
|
85
|
+
body,
|
|
86
|
+
htmlUrl: args.htmlUrl,
|
|
87
|
+
labels: args.labels,
|
|
88
|
+
status: args.status,
|
|
89
|
+
priority,
|
|
90
|
+
assignedOrchestrator,
|
|
91
|
+
project,
|
|
92
|
+
githubUpdatedAt: args.githubUpdatedAt,
|
|
93
|
+
});
|
|
94
|
+
return existing._id;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return await ctx.db.insert("issues", {
|
|
98
|
+
repo: args.repo,
|
|
99
|
+
issueNumber: args.issueNumber,
|
|
100
|
+
title: args.title,
|
|
101
|
+
body,
|
|
102
|
+
htmlUrl: args.htmlUrl,
|
|
103
|
+
labels: args.labels,
|
|
104
|
+
status: args.status,
|
|
105
|
+
priority,
|
|
106
|
+
assignedOrchestrator,
|
|
107
|
+
project,
|
|
108
|
+
githubCreatedAt: args.githubCreatedAt,
|
|
109
|
+
githubUpdatedAt: args.githubUpdatedAt,
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
115
|
+
// updateStatus — update issue status by repo+issueNumber
|
|
116
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
export const updateStatus = mutation({
|
|
119
|
+
args: {
|
|
120
|
+
repo: v.string(),
|
|
121
|
+
issueNumber: v.number(),
|
|
122
|
+
status: issueStatusValidator,
|
|
123
|
+
},
|
|
124
|
+
handler: async (ctx, args) => {
|
|
125
|
+
const existing = await ctx.db
|
|
126
|
+
.query("issues")
|
|
127
|
+
.withIndex("by_repo_number", (q) =>
|
|
128
|
+
q.eq("repo", args.repo).eq("issueNumber", args.issueNumber),
|
|
129
|
+
)
|
|
130
|
+
.unique();
|
|
131
|
+
if (!existing) throw new Error(`Issue ${args.repo}#${args.issueNumber} not found`);
|
|
132
|
+
await ctx.db.patch(existing._id, { status: args.status });
|
|
133
|
+
return null;
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
138
|
+
// linkCommit — append a commit SHA to fixCommits, set fixedBy + fixedAt
|
|
139
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
export const linkCommit = mutation({
|
|
142
|
+
args: {
|
|
143
|
+
repo: v.string(),
|
|
144
|
+
issueNumber: v.number(),
|
|
145
|
+
commitSha: v.string(),
|
|
146
|
+
fixedBy: v.string(),
|
|
147
|
+
},
|
|
148
|
+
handler: async (ctx, args) => {
|
|
149
|
+
const existing = await ctx.db
|
|
150
|
+
.query("issues")
|
|
151
|
+
.withIndex("by_repo_number", (q) =>
|
|
152
|
+
q.eq("repo", args.repo).eq("issueNumber", args.issueNumber),
|
|
153
|
+
)
|
|
154
|
+
.unique();
|
|
155
|
+
if (!existing) throw new Error(`Issue ${args.repo}#${args.issueNumber} not found`);
|
|
156
|
+
const fixCommits = [...(existing.fixCommits ?? []), args.commitSha];
|
|
157
|
+
await ctx.db.patch(existing._id, {
|
|
158
|
+
fixCommits,
|
|
159
|
+
fixedBy: args.fixedBy,
|
|
160
|
+
fixedAt: Date.now(),
|
|
161
|
+
});
|
|
162
|
+
return null;
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
167
|
+
// linkTask — append a taskId to linkedTaskIds
|
|
168
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
export const linkTask = mutation({
|
|
171
|
+
args: {
|
|
172
|
+
repo: v.string(),
|
|
173
|
+
issueNumber: v.number(),
|
|
174
|
+
taskId: v.string(),
|
|
175
|
+
},
|
|
176
|
+
handler: async (ctx, args) => {
|
|
177
|
+
const existing = await ctx.db
|
|
178
|
+
.query("issues")
|
|
179
|
+
.withIndex("by_repo_number", (q) =>
|
|
180
|
+
q.eq("repo", args.repo).eq("issueNumber", args.issueNumber),
|
|
181
|
+
)
|
|
182
|
+
.unique();
|
|
183
|
+
if (!existing) throw new Error(`Issue ${args.repo}#${args.issueNumber} not found`);
|
|
184
|
+
const linkedTaskIds = [...(existing.linkedTaskIds ?? []), args.taskId];
|
|
185
|
+
await ctx.db.patch(existing._id, { linkedTaskIds });
|
|
186
|
+
return null;
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
191
|
+
// verify — set verifiedBy + verifiedAt, change status to "verified"
|
|
192
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
export const verify = mutation({
|
|
195
|
+
args: {
|
|
196
|
+
repo: v.string(),
|
|
197
|
+
issueNumber: v.number(),
|
|
198
|
+
verifiedBy: v.string(),
|
|
199
|
+
},
|
|
200
|
+
handler: async (ctx, args) => {
|
|
201
|
+
const existing = await ctx.db
|
|
202
|
+
.query("issues")
|
|
203
|
+
.withIndex("by_repo_number", (q) =>
|
|
204
|
+
q.eq("repo", args.repo).eq("issueNumber", args.issueNumber),
|
|
205
|
+
)
|
|
206
|
+
.unique();
|
|
207
|
+
if (!existing) throw new Error(`Issue ${args.repo}#${args.issueNumber} not found`);
|
|
208
|
+
await ctx.db.patch(existing._id, {
|
|
209
|
+
status: "verified",
|
|
210
|
+
verifiedBy: args.verifiedBy,
|
|
211
|
+
verifiedAt: Date.now(),
|
|
212
|
+
});
|
|
213
|
+
return null;
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
218
|
+
// close — set status to "closed"
|
|
219
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
export const close = mutation({
|
|
222
|
+
args: {
|
|
223
|
+
repo: v.string(),
|
|
224
|
+
issueNumber: v.number(),
|
|
225
|
+
},
|
|
226
|
+
handler: async (ctx, args) => {
|
|
227
|
+
const existing = await ctx.db
|
|
228
|
+
.query("issues")
|
|
229
|
+
.withIndex("by_repo_number", (q) =>
|
|
230
|
+
q.eq("repo", args.repo).eq("issueNumber", args.issueNumber),
|
|
231
|
+
)
|
|
232
|
+
.unique();
|
|
233
|
+
if (!existing) throw new Error(`Issue ${args.repo}#${args.issueNumber} not found`);
|
|
234
|
+
await ctx.db.patch(existing._id, { status: "closed" });
|
|
235
|
+
return null;
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
240
|
+
// getByRepoNumber — fetch a single issue by repo+issueNumber
|
|
241
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
export const getByRepoNumber = query({
|
|
244
|
+
args: {
|
|
245
|
+
repo: v.string(),
|
|
246
|
+
issueNumber: v.number(),
|
|
247
|
+
},
|
|
248
|
+
handler: async (ctx, args) => {
|
|
249
|
+
return await ctx.db
|
|
250
|
+
.query("issues")
|
|
251
|
+
.withIndex("by_repo_number", (q) =>
|
|
252
|
+
q.eq("repo", args.repo).eq("issueNumber", args.issueNumber),
|
|
253
|
+
)
|
|
254
|
+
.unique();
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
259
|
+
// listByProject — list issues by project with optional status filter
|
|
260
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
// PR #635 wide-scan-cap pattern (see convex/tasks.ts TASK_LIST_SCAN_CAP,
|
|
263
|
+
// convex/profiles.ts PROFILES_LIST_SCAN_CAP, lot 1 mission k574p02m). When
|
|
264
|
+
// paginating via `createdBefore`, the post-take filter only finds rows
|
|
265
|
+
// older than the cursor if the FETCH is wide enough to include them —
|
|
266
|
+
// mission k574p02m DEFECT 2, lot 2.
|
|
267
|
+
export const ISSUES_LIST_BY_PROJECT_SCAN_CAP = 2000;
|
|
268
|
+
|
|
269
|
+
export const listByProject = query({
|
|
270
|
+
args: {
|
|
271
|
+
fields: v.optional(v.union(v.literal("lite"), v.literal("full"))), // v2.4.12 accept (no-op for now) — closes ArgumentValidationError from MCP wrappers passing fields
|
|
272
|
+
project: v.string(),
|
|
273
|
+
status: v.optional(issueStatusValidator),
|
|
274
|
+
limit: v.optional(v.number()),
|
|
275
|
+
// S3.3 B8 follow-up batch 2 — cursor paging anchor (newest-first).
|
|
276
|
+
createdBefore: v.optional(v.number()),
|
|
277
|
+
},
|
|
278
|
+
handler: async (ctx, args) => {
|
|
279
|
+
const limit = args.limit ?? 50;
|
|
280
|
+
const needsWideScan = args.createdBefore !== undefined;
|
|
281
|
+
const fetchCap = needsWideScan
|
|
282
|
+
? ISSUES_LIST_BY_PROJECT_SCAN_CAP + 1
|
|
283
|
+
: limit;
|
|
284
|
+
// The by_project index only has ["project"], so we query by project
|
|
285
|
+
// and filter status in-memory if needed
|
|
286
|
+
let results = await ctx.db
|
|
287
|
+
.query("issues")
|
|
288
|
+
.withIndex("by_project", (q) => q.eq("project", args.project))
|
|
289
|
+
.order("desc")
|
|
290
|
+
.take(fetchCap);
|
|
291
|
+
|
|
292
|
+
if (args.status !== undefined) {
|
|
293
|
+
results = results.filter((r) => r.status === args.status);
|
|
294
|
+
}
|
|
295
|
+
if (args.createdBefore !== undefined) {
|
|
296
|
+
const before = args.createdBefore;
|
|
297
|
+
results = results.filter((r) => r._creationTime < before);
|
|
298
|
+
}
|
|
299
|
+
return results.slice(0, limit);
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
304
|
+
// listByOrchestrator — list issues by assignedOrchestrator with optional status
|
|
305
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
export const ISSUES_LIST_BY_ORCHESTRATOR_SCAN_CAP = 2000;
|
|
308
|
+
|
|
309
|
+
export const listByOrchestrator = query({
|
|
310
|
+
args: {
|
|
311
|
+
fields: v.optional(v.union(v.literal("lite"), v.literal("full"))), // v2.4.12 accept (no-op for now) — closes ArgumentValidationError from MCP wrappers passing fields
|
|
312
|
+
assignedOrchestrator: v.string(),
|
|
313
|
+
status: v.optional(issueStatusValidator),
|
|
314
|
+
limit: v.optional(v.number()),
|
|
315
|
+
// S3.3 B8 follow-up batch 2 — cursor paging anchor (newest-first).
|
|
316
|
+
createdBefore: v.optional(v.number()),
|
|
317
|
+
},
|
|
318
|
+
handler: async (ctx, args) => {
|
|
319
|
+
const limit = args.limit ?? 50;
|
|
320
|
+
const needsWideScan = args.createdBefore !== undefined;
|
|
321
|
+
const fetchCap = needsWideScan
|
|
322
|
+
? ISSUES_LIST_BY_ORCHESTRATOR_SCAN_CAP + 1
|
|
323
|
+
: limit;
|
|
324
|
+
let rows: Doc<"issues">[];
|
|
325
|
+
if (args.status !== undefined) {
|
|
326
|
+
const statusFilter = args.status;
|
|
327
|
+
rows = await ctx.db
|
|
328
|
+
.query("issues")
|
|
329
|
+
.withIndex("by_assigned", (q) =>
|
|
330
|
+
q.eq("assignedOrchestrator", args.assignedOrchestrator).eq("status", statusFilter),
|
|
331
|
+
)
|
|
332
|
+
.order("desc")
|
|
333
|
+
.take(fetchCap);
|
|
334
|
+
} else {
|
|
335
|
+
rows = await ctx.db
|
|
336
|
+
.query("issues")
|
|
337
|
+
.withIndex("by_assigned", (q) =>
|
|
338
|
+
q.eq("assignedOrchestrator", args.assignedOrchestrator),
|
|
339
|
+
)
|
|
340
|
+
.order("desc")
|
|
341
|
+
.take(fetchCap);
|
|
342
|
+
}
|
|
343
|
+
if (args.createdBefore !== undefined) {
|
|
344
|
+
const before = args.createdBefore;
|
|
345
|
+
rows = rows.filter((r) => r._creationTime < before);
|
|
346
|
+
}
|
|
347
|
+
return rows.slice(0, limit);
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
352
|
+
// listByStatus — list all issues matching a given status (no orchestrator filter)
|
|
353
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
export const ISSUES_LIST_BY_STATUS_SCAN_CAP = 2000;
|
|
356
|
+
|
|
357
|
+
export const listByStatus = query({
|
|
358
|
+
args: {
|
|
359
|
+
fields: v.optional(v.union(v.literal("lite"), v.literal("full"))), // v2.4.12 accept (no-op for now) — closes ArgumentValidationError from MCP wrappers passing fields
|
|
360
|
+
status: issueStatusValidator,
|
|
361
|
+
limit: v.optional(v.number()),
|
|
362
|
+
// S3.3 B8 follow-up batch 2 — cursor paging anchor (newest-first).
|
|
363
|
+
createdBefore: v.optional(v.number()),
|
|
364
|
+
},
|
|
365
|
+
returns: v.array(v.any()),
|
|
366
|
+
handler: async (ctx, args) => {
|
|
367
|
+
const limit = args.limit ?? 50;
|
|
368
|
+
const needsWideScan = args.createdBefore !== undefined;
|
|
369
|
+
const fetchCap = needsWideScan
|
|
370
|
+
? ISSUES_LIST_BY_STATUS_SCAN_CAP + 1
|
|
371
|
+
: limit;
|
|
372
|
+
let rows: Doc<"issues">[] = await ctx.db
|
|
373
|
+
.query("issues")
|
|
374
|
+
.withIndex("by_status", (q) => q.eq("status", args.status))
|
|
375
|
+
.order("desc")
|
|
376
|
+
.take(fetchCap);
|
|
377
|
+
if (args.createdBefore !== undefined) {
|
|
378
|
+
const before = args.createdBefore;
|
|
379
|
+
rows = rows.filter((r) => r._creationTime < before);
|
|
380
|
+
}
|
|
381
|
+
return rows.slice(0, limit);
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
386
|
+
// getStats — count issues per status, optionally filtered by project
|
|
387
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
export const getStats = query({
|
|
390
|
+
args: {
|
|
391
|
+
project: v.optional(v.string()),
|
|
392
|
+
},
|
|
393
|
+
handler: async (ctx, args) => {
|
|
394
|
+
const stats = { open: 0, in_progress: 0, fixed: 0, verified: 0, closed: 0, total: 0 };
|
|
395
|
+
|
|
396
|
+
let issues;
|
|
397
|
+
if (args.project) {
|
|
398
|
+
issues = await ctx.db
|
|
399
|
+
.query("issues")
|
|
400
|
+
.withIndex("by_project", (q) => q.eq("project", args.project!))
|
|
401
|
+
.take(1000);
|
|
402
|
+
} else {
|
|
403
|
+
issues = await ctx.db.query("issues").take(1000);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
for (const issue of issues) {
|
|
407
|
+
stats[issue.status as keyof typeof stats]++;
|
|
408
|
+
stats.total++;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return stats;
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
416
|
+
// External issue tracking (for Zeta contributions to third-party repos)
|
|
417
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
418
|
+
|
|
419
|
+
export const createExternal = mutation({
|
|
420
|
+
args: {
|
|
421
|
+
externalRepo: v.string(),
|
|
422
|
+
externalIssueNumber: v.number(),
|
|
423
|
+
externalIssueUrl: v.string(),
|
|
424
|
+
title: v.string(),
|
|
425
|
+
body: v.string(),
|
|
426
|
+
assignedOrchestrator: v.string(),
|
|
427
|
+
project: v.optional(v.string()),
|
|
428
|
+
priority: v.optional(v.union(v.literal("urgent"), v.literal("high"), v.literal("medium"), v.literal("low"))),
|
|
429
|
+
forkRepo: v.optional(v.string()),
|
|
430
|
+
},
|
|
431
|
+
returns: v.id("issues"),
|
|
432
|
+
handler: async (ctx, args) => {
|
|
433
|
+
const now = Date.now();
|
|
434
|
+
return await ctx.db.insert("issues", {
|
|
435
|
+
repo: args.externalRepo,
|
|
436
|
+
issueNumber: args.externalIssueNumber,
|
|
437
|
+
title: args.title,
|
|
438
|
+
body: args.body.slice(0, 2000),
|
|
439
|
+
htmlUrl: args.externalIssueUrl,
|
|
440
|
+
labels: [],
|
|
441
|
+
status: "open",
|
|
442
|
+
priority: args.priority ?? "medium",
|
|
443
|
+
assignedOrchestrator: args.assignedOrchestrator,
|
|
444
|
+
project: args.project ?? "external",
|
|
445
|
+
githubCreatedAt: now,
|
|
446
|
+
githubUpdatedAt: now,
|
|
447
|
+
externalRepo: args.externalRepo,
|
|
448
|
+
externalIssueNumber: args.externalIssueNumber,
|
|
449
|
+
externalIssueUrl: args.externalIssueUrl,
|
|
450
|
+
forkRepo: args.forkRepo,
|
|
451
|
+
});
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
export const updatePrStatus = mutation({
|
|
456
|
+
args: {
|
|
457
|
+
repo: v.string(),
|
|
458
|
+
issueNumber: v.number(),
|
|
459
|
+
prUrl: v.string(),
|
|
460
|
+
prStatus: v.union(
|
|
461
|
+
v.literal("draft"),
|
|
462
|
+
v.literal("open"),
|
|
463
|
+
v.literal("merged"),
|
|
464
|
+
v.literal("closed"),
|
|
465
|
+
),
|
|
466
|
+
},
|
|
467
|
+
returns: v.null(),
|
|
468
|
+
handler: async (ctx, args) => {
|
|
469
|
+
const issue = await ctx.db
|
|
470
|
+
.query("issues")
|
|
471
|
+
.withIndex("by_repo_number", (q) =>
|
|
472
|
+
q.eq("repo", args.repo).eq("issueNumber", args.issueNumber),
|
|
473
|
+
)
|
|
474
|
+
.unique();
|
|
475
|
+
if (!issue) return null;
|
|
476
|
+
|
|
477
|
+
await ctx.db.patch(issue._id, {
|
|
478
|
+
prUrl: args.prUrl,
|
|
479
|
+
prStatus: args.prStatus,
|
|
480
|
+
githubUpdatedAt: Date.now(),
|
|
481
|
+
});
|
|
482
|
+
return null;
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
export const listExternalOpen = query({
|
|
487
|
+
args: {
|
|
488
|
+
prStatus: v.optional(v.union(
|
|
489
|
+
v.literal("draft"),
|
|
490
|
+
v.literal("open"),
|
|
491
|
+
v.literal("merged"),
|
|
492
|
+
v.literal("closed"),
|
|
493
|
+
)),
|
|
494
|
+
limit: v.optional(v.number()),
|
|
495
|
+
paginationToken: v.optional(v.union(v.string(), v.null())),
|
|
496
|
+
},
|
|
497
|
+
returns: v.object({
|
|
498
|
+
issues: v.array(v.object({
|
|
499
|
+
_id: v.id("issues"),
|
|
500
|
+
_creationTime: v.number(),
|
|
501
|
+
repo: v.string(),
|
|
502
|
+
issueNumber: v.number(),
|
|
503
|
+
title: v.string(),
|
|
504
|
+
status: v.string(),
|
|
505
|
+
externalRepo: v.optional(v.string()),
|
|
506
|
+
externalIssueUrl: v.optional(v.string()),
|
|
507
|
+
prUrl: v.optional(v.string()),
|
|
508
|
+
prStatus: v.optional(v.string()),
|
|
509
|
+
assignedOrchestrator: v.string(),
|
|
510
|
+
})),
|
|
511
|
+
nextPageToken: v.union(v.string(), v.null()),
|
|
512
|
+
}),
|
|
513
|
+
handler: async (ctx, args) => {
|
|
514
|
+
const numItems = args.limit ?? 50;
|
|
515
|
+
|
|
516
|
+
const result = await ctx.db
|
|
517
|
+
.query("issues")
|
|
518
|
+
.filter((q) => {
|
|
519
|
+
let expr = q.neq(q.field("externalRepo"), undefined);
|
|
520
|
+
if (args.prStatus !== undefined) {
|
|
521
|
+
expr = q.and(expr, q.eq(q.field("prStatus"), args.prStatus));
|
|
522
|
+
}
|
|
523
|
+
return expr;
|
|
524
|
+
})
|
|
525
|
+
.paginate({ numItems, cursor: args.paginationToken ?? null });
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
issues: result.page.map((i) => ({
|
|
529
|
+
_id: i._id,
|
|
530
|
+
_creationTime: i._creationTime,
|
|
531
|
+
repo: i.repo,
|
|
532
|
+
issueNumber: i.issueNumber,
|
|
533
|
+
title: i.title,
|
|
534
|
+
status: i.status,
|
|
535
|
+
externalRepo: i.externalRepo,
|
|
536
|
+
externalIssueUrl: i.externalIssueUrl,
|
|
537
|
+
prUrl: i.prUrl,
|
|
538
|
+
prStatus: i.prStatus as string | undefined,
|
|
539
|
+
assignedOrchestrator: i.assignedOrchestrator,
|
|
540
|
+
})),
|
|
541
|
+
nextPageToken: result.isDone ? null : result.continueCursor,
|
|
542
|
+
};
|
|
543
|
+
},
|
|
544
|
+
});
|