import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { createHash } from "node:crypto"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { Readable, Writable } from "node:stream"; import { runCommand } from "./command"; import { repoRoot } from "./config"; import { runApplyPatchV2, type ApplyPatchV2Executor, type ApplyPatchV2RemoteResult } from "./apply-patch-v2"; interface GhContentRoute { repo: string; area: "root" | "pr-list" | "issue-list" | "pr-item" | "issue-item" | "legacy-item"; number: number | null; floor: number | null; raw: string; } interface GhRouteDocument { kind: "pr" | "issue"; repo: string; number: number; floor: number; title: string; url: string; state: string; commentCount: number; body: string; bodySha: string | null; } interface CommandResult { ok: boolean; exitCode: number | null; stdout: string; stderr: string; parsed: Record; } type GhListState = "open" | "closed" | "all"; interface GhListOptions { limit: number; full: boolean; state: GhListState; search: string | null; labels: string[]; } function record(value: unknown): Record { return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record : {}; } function nested(value: unknown, keys: string[]): unknown { let current = value; for (const key of keys) current = record(current)[key]; return current; } function cliJson(args: string[], timeoutMs = 80_000): CommandResult { const result = runCommand(["bun", "scripts/cli.ts", ...args], repoRoot, { timeoutMs }); let parsed: Record = {}; if (result.stdout.trim().length > 0) { try { parsed = record(JSON.parse(result.stdout) as unknown); } catch { parsed = {}; } } const ok = result.exitCode === 0 && parsed.ok !== false && nested(parsed, ["data", "ok"]) !== false; return { ok, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr, parsed }; } function decodeSegment(segment: string): string { try { return decodeURIComponent(segment); } catch { throw new Error(`invalid percent-encoded gh route segment: ${segment}`); } } export function isGhContentRoute(target: string | undefined): boolean { return typeof target === "string" && target.startsWith("gh:"); } export function parseGhContentRoute(target: string): GhContentRoute { if (!target.startsWith("gh:/")) throw new Error("gh route must use gh:/owner/repo, for example: gh:/pikasTech/HWLAB/pr ls or gh:/pikasTech/HWLAB/pr/503/1 cat"); const parts = target.slice("gh:/".length).split("/").filter((part) => part.length > 0).map(decodeSegment); if (parts.length < 2 || parts.length > 5) throw new Error("gh route supports gh:/owner/repo, gh:/owner/repo/pr, gh:/owner/repo/issue, gh:/owner/repo/pr/number[/floor], gh:/owner/repo/issue/number[/floor], and legacy gh:/owner/repo/number/floor"); const [owner, repoName, areaRaw, numberRaw, floorRaw] = parts; if (!owner || !repoName) throw new Error("gh route requires non-empty owner and repo"); const repo = `${owner}/${repoName}`; if (areaRaw === undefined) return { repo, area: "root", number: null, floor: null, raw: target }; if (areaRaw === "pr" || areaRaw === "prs" || areaRaw === "pull" || areaRaw === "pulls") { if (numberRaw === undefined) return { repo, area: "pr-list", number: null, floor: null, raw: target }; const number = positiveRouteNumber(numberRaw, "PR number"); const floor = floorRaw === undefined ? null : positiveRouteNumber(floorRaw, "PR floor"); return { repo, area: "pr-item", number, floor, raw: target }; } if (areaRaw === "issue" || areaRaw === "issues") { if (numberRaw === undefined) return { repo, area: "issue-list", number: null, floor: null, raw: target }; const number = positiveRouteNumber(numberRaw, "issue number"); const floor = floorRaw === undefined ? null : positiveRouteNumber(floorRaw, "issue floor"); return { repo, area: "issue-item", number, floor, raw: target }; } if (parts.length === 4) { const number = positiveRouteNumber(areaRaw, "legacy issue/PR number"); const floor = positiveRouteNumber(numberRaw ?? "", "legacy floor"); return { repo, area: "legacy-item", number, floor, raw: target }; } throw new Error("unsupported gh route; use gh:/owner/repo/pr[/number[/floor]] or gh:/owner/repo/issue[/number[/floor]]"); } function positiveRouteNumber(raw: string, label: string): number { const value = Number(raw); if (!Number.isInteger(value) || value <= 0) throw new Error(`gh route ${label} must be a positive integer`); return value; } function commandData(result: CommandResult): Record { return record(expandedParsedRoot(result).data); } function expandedParsedRoot(result: CommandResult): Record { const dumpPath = nested(result.parsed, ["data", "dump", "path"]); if (typeof dumpPath === "string" && existsSync(dumpPath)) { try { return record(JSON.parse(readFileSync(dumpPath, "utf8")) as unknown); } catch { return result.parsed; } } return result.parsed; } function readDocument(route: GhContentRoute): GhRouteDocument { if (route.number === null) throw new Error("gh route document requires a PR or issue number"); if (route.floor !== 1) { throw new Error("only floor 1 is currently supported; floor 1 maps to the issue/PR body"); } const shouldReadPr = route.area === "pr-item" || route.area === "legacy-item"; const shouldReadIssue = route.area === "issue-item" || route.area === "legacy-item"; const pr = shouldReadPr ? cliJson(["gh", "pr", "read", String(route.number), "--repo", route.repo, "--json", "title,body,url,stateDetail,merged,mergedAt,mergeCommit,headRefName,baseRefName", "--raw"]) : null; if (pr?.ok) { const data = commandData(pr); const prRecord = record(data.pullRequest); const json = record(data.json); const issueMeta = readIssueMetadata(route.repo, route.number); return { kind: "pr", repo: route.repo, number: route.number, floor: route.floor, title: String(json.title ?? prRecord.title ?? ""), url: String(json.url ?? prRecord.url ?? ""), state: issueMeta.state ?? String(json.stateDetail ?? prRecord.stateDetail ?? ""), commentCount: issueMeta.commentCount ?? 0, body: String(json.body ?? prRecord.body ?? ""), bodySha: null, }; } if (!shouldReadIssue) { throw new Error(`failed to read ${route.repo}#${route.number} as PR: ${pr?.stderr || pr?.stdout}`); } const issue = cliJson(["gh", "issue", "read", String(route.number), "--repo", route.repo, "--json", "title,body,state,updatedAt", "--raw"]); if (!issue.ok) { throw new Error(`failed to read ${route.repo}#${route.number} as ${route.area === "issue-item" ? "issue" : "PR or issue"}: pr=${pr?.stderr || pr?.stdout || "skipped"}; issue=${issue.stderr || issue.stdout}`); } const issueRecord = record(commandData(issue).issue); const json = record(commandData(issue).json); const commentCount = numberFromUnknown(issueRecord.commentCount) ?? commentsCount(commandData(issue).comments) ?? commentsCount(json.comments) ?? 0; return { kind: "issue", repo: route.repo, number: route.number, floor: route.floor, title: String(json.title ?? issueRecord.title ?? ""), url: String(issueRecord.url ?? ""), state: String(json.state ?? issueRecord.state ?? ""), commentCount, body: String(json.body ?? issueRecord.body ?? ""), bodySha: typeof issueRecord.bodySha === "string" ? issueRecord.bodySha : null, }; } function documentRoute(route: GhContentRoute): GhContentRoute { if ((route.area === "pr-item" || route.area === "issue-item" || route.area === "legacy-item") && route.floor === null) { return { ...route, floor: 1 }; } return route; } function isPatchOperation(operation: string): boolean { return operation === "patch-apply" || operation === "apply-patch" || operation === "apply_patch"; } function numberFromUnknown(value: unknown): number | null { return typeof value === "number" && Number.isFinite(value) ? value : null; } function commentsCount(value: unknown): number | null { return Array.isArray(value) ? value.length : null; } function readIssueMetadata(repo: string, number: number): { state: string | null; commentCount: number | null; bodyChars: number | null } { const issue = cliJson(["gh", "issue", "read", String(number), "--repo", repo, "--json", "title,body,state,updatedAt,comments", "--raw"], 80_000); if (!issue.ok) return { state: null, commentCount: null, bodyChars: null }; const data = commandData(issue); const issueRecord = record(data.issue); const json = record(data.json); const body = String(json.body ?? issueRecord.body ?? ""); return { state: typeof json.state === "string" ? json.state : typeof issueRecord.state === "string" ? issueRecord.state : null, commentCount: numberFromUnknown(issueRecord.commentCount) ?? commentsCount(data.comments) ?? commentsCount(json.comments), bodyChars: body.length, }; } function parseListState(raw: string): GhListState { if (raw === "open" || raw === "closed" || raw === "all") return raw; throw new Error("gh route list --state must be open, closed, or all"); } function optionValue(args: string[], index: number, name: string): { value: string; nextIndex: number } { const arg = args[index] ?? ""; if (arg.startsWith(`${name}=`)) return { value: arg.slice(name.length + 1), nextIndex: index }; const value = args[index + 1]; if (value === undefined || value.length === 0) throw new Error(`gh route list ${name} requires a value`); return { value, nextIndex: index + 1 }; } function parseLabels(raw: string): string[] { const labels = raw.split(",").map((item) => item.trim()).filter((item) => item.length > 0); if (labels.length === 0) throw new Error("gh route issue ls --label requires at least one non-empty label"); return labels; } function parseListOptions(args: string[], kind: "pr" | "issue"): GhListOptions { const options: GhListOptions = { limit: 30, full: false, state: "all", search: null, labels: [] }; for (let index = 0; index < args.length; index += 1) { const arg = args[index] ?? ""; if (arg === "--full") { options.full = true; continue; } if (arg === "--limit" || arg.startsWith("--limit=")) { const parsed = optionValue(args, index, "--limit"); options.limit = positiveRouteNumber(parsed.value, "--limit"); index = parsed.nextIndex; continue; } if (arg === "--state" || arg.startsWith("--state=")) { const parsed = optionValue(args, index, "--state"); options.state = parseListState(parsed.value); index = parsed.nextIndex; continue; } if (arg === "--search" || arg.startsWith("--search=")) { if (kind !== "issue") throw new Error("gh route --search is only supported for issue ls"); const parsed = optionValue(args, index, "--search"); options.search = parsed.value; index = parsed.nextIndex; continue; } if (arg === "--label" || arg.startsWith("--label=")) { if (kind !== "issue") throw new Error("gh route --label is only supported for issue ls"); const parsed = optionValue(args, index, "--label"); options.labels.push(...parseLabels(parsed.value)); index = parsed.nextIndex; continue; } throw new Error(`unsupported gh route ${kind} ls arg: ${arg}`); } return options; } function listRoute(route: GhContentRoute, args: string[]): number { if (route.area === "root") { printDirectory([ { name: "pr/", type: "dir", description: "pull request directory" }, { name: "issue/", type: "dir", description: "issue directory" }, ]); return 0; } if (route.area === "pr-list") { const options = parseListOptions(args, "pr"); const result = cliJson(["gh", "pr", "list", "--repo", route.repo, "--state", options.state, "--limit", String(options.limit), "--json", "number,title,state,url,updatedAt,createdAt,author,headRefName,baseRefName,draft"], 80_000); if (!result.ok) throw new Error(result.stderr || result.stdout || "gh pr list failed"); const prs = commandData(result).pullRequests; const rows = Array.isArray(prs) ? prs.map((item) => prDirectoryEntry(route.repo, record(item), options.full)) : []; printDirectory(rows); return 0; } if (route.area === "issue-list") { const options = parseListOptions(args, "issue"); const issueArgs = ["gh", "issue", "list", "--repo", route.repo, "--state", options.state, "--limit", String(options.limit), "--json", "number,title,state,url,updatedAt,createdAt,author"]; if (options.search !== null) issueArgs.push("--search", options.search); for (const label of options.labels) issueArgs.push("--label", label); const result = cliJson(issueArgs, 80_000); if (!result.ok) throw new Error(result.stderr || result.stdout || "gh issue list failed"); const issues = commandData(result).issues; const rows = Array.isArray(issues) ? issues.map((item) => issueDirectoryEntry(route.repo, record(item), options.full)) : []; printDirectory(rows); return 0; } const document = readDocument({ ...route, floor: 1 }); printDirectory([ { name: "1", type: "file", kind: document.kind, state: document.state, floors: 1 + document.commentCount, commentCount: document.commentCount, bodyChars: document.body.length, title: document.title, url: document.url, }, ]); return 0; } function prDirectoryEntry(repo: string, item: Record, full: boolean): Record { const number = Number(item.number); const meta = full && Number.isInteger(number) ? readIssueMetadata(repo, number) : null; const commentCount = meta?.commentCount ?? null; return { name: `${item.number}/`, type: "dir", kind: "pr", state: meta?.state ?? item.state ?? null, draft: item.draft === true, floors: commentCount === null ? 1 : 1 + commentCount, commentCount, bodyChars: meta?.bodyChars ?? null, title: item.title ?? "", url: item.url ?? `https://github.com/${repo}/pull/${item.number}`, headRefName: item.headRefName ?? null, baseRefName: item.baseRefName ?? null, updatedAt: item.updatedAt ?? null, }; } function issueDirectoryEntry(repo: string, item: Record, full: boolean): Record { const number = Number(item.number); const meta = full && Number.isInteger(number) ? readIssueMetadata(repo, number) : null; const commentCount = meta?.commentCount ?? null; return { name: `${item.number}/`, type: "dir", kind: "issue", state: meta?.state ?? item.state ?? null, floors: commentCount === null ? 1 : 1 + commentCount, commentCount, bodyChars: meta?.bodyChars ?? null, title: item.title ?? "", url: item.url ?? `https://github.com/${repo}/issues/${item.number}`, updatedAt: item.updatedAt ?? null, }; } function printDirectory(rows: Array>): void { for (const row of rows) { const fields = [ String(row.name ?? ""), `type=${String(row.type ?? "")}`, row.kind === undefined ? null : `kind=${String(row.kind)}`, row.state === undefined || row.state === null ? null : `state=${String(row.state)}`, row.draft === undefined ? null : `draft=${String(row.draft)}`, row.floors === undefined || row.floors === null ? null : `floors=${String(row.floors)}`, row.commentCount === undefined || row.commentCount === null ? null : `commentCount=${String(row.commentCount)}`, row.bodyChars === undefined || row.bodyChars === null ? null : `bodyChars=${String(row.bodyChars)}`, row.updatedAt === undefined || row.updatedAt === null ? null : `updatedAt=${String(row.updatedAt)}`, row.url === undefined || row.url === null || row.url === "" ? null : `url=${String(row.url)}`, row.title === undefined || row.title === "" ? null : `title=${String(row.title)}`, ].filter((item): item is string => item !== null); process.stdout.write(`${fields.join("\t")}\n`); } } function readStdin(): Promise { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; process.stdin.on("data", (chunk: Buffer) => chunks.push(chunk)); process.stdin.on("error", reject); process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); }); } function sha256Hex(value: Buffer): string { return createHash("sha256").update(value).digest("hex"); } function bodyVirtualPath(path: string): string | null { const normalized = path.replace(/\\/gu, "/").replace(/^\.\/+/u, ""); if (normalized === "body.md" || normalized === "body" || normalized === "1.md") return "body.md"; return null; } function verifyVirtualWrite(target: string, buffer: Buffer, expectedBytes: string, expectedSha256: string): ApplyPatchV2RemoteResult | null { const expectedByteCount = Number(expectedBytes); if (!Number.isSafeInteger(expectedByteCount) || expectedByteCount < 0) { return { exitCode: 2, stdout: "", stderr: `invalid expected byte count for ${target}\n` }; } if (buffer.length !== expectedByteCount) { return { exitCode: 23, stdout: "", stderr: `v2 virtual byte count mismatch for ${target}: expected=${expectedByteCount} actual=${buffer.length}\n` }; } const actualSha256 = sha256Hex(buffer); if (actualSha256 !== expectedSha256) { return { exitCode: 24, stdout: "", stderr: `v2 virtual sha256 mismatch for ${target}: expected=${expectedSha256} actual=${actualSha256}\n` }; } return null; } export class GhVirtualFileExecutor implements ApplyPatchV2Executor { private readonly files = new Map(); private readonly temp = new Map(); constructor(body: string) { this.files.set("body.md", Buffer.from(body, "utf8")); } body(): string { const body = this.files.get("body.md"); return body === undefined ? "" : body.toString("utf8"); } async run(command: string[], input?: string): Promise { const operation = command[4] ?? ""; const args = command.slice(5); try { return this.runOperation(operation, args, input); } catch (error) { return { exitCode: 1, stdout: "", stderr: `${error instanceof Error ? error.message : String(error)}\n` }; } } private canonical(rawPath: string): string { const path = bodyVirtualPath(rawPath); if (path === null) throw new Error(`unsupported gh virtual file path: ${rawPath}; use body.md for floor 1`); return path; } private file(path: string): Buffer { const body = this.files.get(path); if (body === undefined) throw new Error(`virtual file not found: ${path}`); return body; } private runOperation(operation: string, args: string[], input?: string): ApplyPatchV2RemoteResult { if (operation === "stat") { const path = this.canonical(args[0] ?? ""); const body = this.file(path); return { exitCode: 0, stdout: `${body.length} ${sha256Hex(body)}\n`, stderr: "" }; } if (operation === "read-b64-block") { const path = this.canonical(args[0] ?? ""); const blockIndex = Number(args[1] ?? ""); const blockSize = Number(args[2] ?? ""); if (!Number.isSafeInteger(blockIndex) || blockIndex < 0 || !Number.isSafeInteger(blockSize) || blockSize <= 0) { return { exitCode: 2, stdout: "", stderr: "invalid read block args\n" }; } const body = this.file(path); const offset = blockIndex * blockSize; return { exitCode: 0, stdout: body.subarray(offset, offset + blockSize).toString("base64"), stderr: "" }; } if (operation === "write-b64-argv") { const path = this.canonical(args[0] ?? ""); const expectedBytes = args[1] ?? ""; const expectedSha256 = args[2] ?? ""; const buffer = Buffer.from(args.slice(3).join(""), "base64"); const failure = verifyVirtualWrite(path, buffer, expectedBytes, expectedSha256); if (failure !== null) return failure; this.files.set(path, buffer); return { exitCode: 0, stdout: "", stderr: "" }; } if (operation === "write-b64-stdin") { const path = this.canonical(args[0] ?? ""); const expectedBytes = args[1] ?? ""; const expectedSha256 = args[2] ?? ""; const buffer = Buffer.from((input ?? "").replace(/\s+/gu, ""), "base64"); const failure = verifyVirtualWrite(path, buffer, expectedBytes, expectedSha256); if (failure !== null) return failure; this.files.set(path, buffer); return { exitCode: 0, stdout: "", stderr: "" }; } if (operation === "write-b64-begin") { this.canonical(args[0] ?? ""); const token = args[1] ?? ""; this.temp.set(token, ""); return { exitCode: 0, stdout: "", stderr: "" }; } if (operation === "write-b64-append") { this.canonical(args[0] ?? ""); const token = args[1] ?? ""; if (!this.temp.has(token)) return { exitCode: 2, stdout: "", stderr: "unknown virtual write token\n" }; this.temp.set(token, `${this.temp.get(token) ?? ""}${args[2] ?? ""}`); return { exitCode: 0, stdout: "", stderr: "" }; } if (operation === "write-b64-commit") { const path = this.canonical(args[0] ?? ""); const token = args[1] ?? ""; const expectedBytes = args[2] ?? ""; const expectedSha256 = args[3] ?? ""; const encoded = this.temp.get(token); if (encoded === undefined) return { exitCode: 2, stdout: "", stderr: "unknown virtual write token\n" }; const buffer = Buffer.from(encoded, "base64"); const failure = verifyVirtualWrite(path, buffer, expectedBytes, expectedSha256); if (failure !== null) return failure; this.temp.delete(token); this.files.set(path, buffer); return { exitCode: 0, stdout: "", stderr: "" }; } if (operation === "delete") { const path = this.canonical(args[0] ?? ""); if (!this.files.has(path)) return { exitCode: 1, stdout: "", stderr: `virtual file not found: ${path}\n` }; this.files.delete(path); return { exitCode: 0, stdout: "", stderr: "" }; } if (operation === "move") { const from = this.canonical(args[0] ?? ""); const to = this.canonical(args[1] ?? ""); const body = this.file(from); this.files.set(to, body); this.files.delete(from); return { exitCode: 0, stdout: "", stderr: "" }; } return { exitCode: 2, stdout: "", stderr: `unsupported virtual apply-patch operation: ${operation}\n` }; } } function writableCapture(): { stream: Writable; text: () => string } { const chunks: Buffer[] = []; return { stream: new Writable({ write(chunk, _encoding, callback) { chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))); callback(); }, }), text: () => Buffer.concat(chunks).toString("utf8"), }; } export async function applyPatchToGhBody(currentBody: string, patchText: string): Promise<{ body: string; applyPatchOutput: string; executor: GhVirtualFileExecutor }> { const executor = new GhVirtualFileExecutor(currentBody); const output = writableCapture(); const exitCode = await runApplyPatchV2({ executor, stdin: Readable.from([patchText]), stdout: output.stream, argv: [], }); if (exitCode !== 0) throw new Error(`apply-patch-v2 failed for gh virtual body: ${output.text()}`); return { body: executor.body(), applyPatchOutput: output.text(), executor }; } function writeTempBody(body: string): string { const dir = mkdtempSync(join(tmpdir(), "unidesk-gh-route-")); const path = join(dir, "body.md"); writeFileSync(path, body, "utf8"); return path; } function updateDocument(document: GhRouteDocument, nextBody: string, dryRun: boolean): CommandResult { const bodyPath = writeTempBody(nextBody); try { if (document.kind === "pr") { return cliJson(["gh", "pr", "update", String(document.number), "--repo", document.repo, "--mode", "replace", "--body-file", bodyPath, ...(dryRun ? ["--dry-run"] : [])], 100_000); } return cliJson([ "gh", "issue", "update", String(document.number), "--repo", document.repo, "--mode", "replace", "--body-file", bodyPath, ...(document.bodySha !== null ? ["--expect-body-sha", document.bodySha] : []), ...(dryRun ? ["--dry-run"] : []), ], 100_000); } finally { rmSync(join(bodyPath, ".."), { recursive: true, force: true }); } } function printJson(payload: unknown): void { process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`); } function runRg(body: string, args: string[]): number { if (args.length === 0) throw new Error("gh route rg requires a pattern"); const dir = mkdtempSync(join(tmpdir(), "unidesk-gh-route-rg-")); try { const bodyPath = join(dir, "body.md"); writeFileSync(bodyPath, body, "utf8"); const result = runCommand(["rg", "--color=never", ...args, bodyPath], repoRoot, { timeoutMs: 30_000 }); process.stdout.write(result.stdout.replaceAll(`${bodyPath}:`, "")); process.stderr.write(result.stderr.replaceAll(`${bodyPath}:`, "")); return result.exitCode ?? 1; } finally { rmSync(dir, { recursive: true, force: true }); } } export async function runGhContentRoute(target: string, args: string[]): Promise { const route = parseGhContentRoute(target); const operation = args[0] ?? "cat"; const opArgs = args.slice(1); if (operation === "ls" || operation === "dir") { return listRoute(route, opArgs); } const document = readDocument(documentRoute(route)); if (operation === "cat") { process.stdout.write(document.body); return 0; } if (operation === "rg") { return runRg(document.body, opArgs); } if (isPatchOperation(operation)) { const dryRun = opArgs.includes("--dry-run"); const unsupported = opArgs.filter((arg) => arg !== "--dry-run"); if (unsupported.length > 0) throw new Error(`unsupported gh ${operation} args: ${unsupported.join(" ")}`); const patchText = await readStdin(); const patchResult = await applyPatchToGhBody(document.body, patchText); const nextBody = patchResult.body; const update = updateDocument(document, nextBody, dryRun); printJson({ ok: update.ok, route, target: { kind: document.kind, repo: document.repo, number: document.number, floor: document.floor, title: document.title, url: document.url, }, dryRun, bodyCharsBefore: document.body.length, bodyCharsAfter: nextBody.length, applyPatch: { engine: "apply-patch-v2", virtualFiles: ["body.md"], output: patchResult.applyPatchOutput.trim(), }, update: commandData(update), }); return update.ok ? 0 : 1; } if (operation === "rm") { printJson({ ok: false, degradedReason: "unsupported-operation", route, message: "gh route floor 1 is the issue/PR body and cannot be removed safely; use apply-patch to delete reviewed sections.", }); return 2; } printJson({ ok: false, degradedReason: "unsupported-command", route, supported: ["cat", "rg", "patch-apply", "apply-patch", "apply_patch", "rm"], }); return 2; }