import { z } from "zod";
import { all, first, run } from "../db";
import { forbidden, notFoundError } from "../errors";
import { applyLabels, indexIssue, issueUrl, loadIssue } from "../issues";
import type { IssueRow } from "../issues";
import { addIssueEvent } from "../pulls";
import { limitWrite, limitBrowse } from "../limits";
import { assertNotArchived, hasPermission } from "../repos";
import { changeIssueState, insertComment, insertIssue } from "../mcp/issue-writes";
import { markdownBody, title } from "~/lib/validation";
import type { RepoRow } from "~/lib/types";
import { readJson, requireScopedRepo, unprocessable } from "./http";
import type { ApiContext, ScopedRepo } from "./http";
const LIST_LIMIT = 100;
const MAX_COMMENTS = 100;
/** Opening issues and commenting need only read access (as on the web), but a fine-grained token must opt in to issue writes. */
function requireIssueWriteGrant(ctx: ApiContext): void {
const grant = ctx.credential.grant;
if (grant?.kind === "fine" && grant.permissions.issues !== "write") {
throw forbidden("This token cannot write issues");
}
if (grant?.kind === "deploy") throw forbidden("Deploy keys cannot write issues");
}
function canMaintain(scoped: ScopedRepo): boolean {
return hasPermission(scoped.permission, "write");
}
async function loadPlainIssue(repo: RepoRow, number: number): Promise<IssueRow> {
const issue = await loadIssue(repo.id, number);
if (!issue || issue.is_pull === 1) throw notFoundError("Issue not found");
return issue;
}
async function issueJson(repo: RepoRow, issue: IssueRow) {
const [author, labels] = await Promise.all([
first<{ name: string }>("SELECT name FROM accounts WHERE id = ?", issue.author_id),
all<{ name: string }>(