mirror of
https://github.com/wassname/pi-lgtm.git
synced 2026-06-27 17:01:35 +08:00
simplify /lgtm back to viewer-only
This commit is contained in:
+72
-28
@@ -20,7 +20,7 @@
|
||||
* /tasks — Interactive task management menu
|
||||
* /lgtm <id...> — View the proof log for one or more tasks
|
||||
* /lgtm * — View all open task proof logs
|
||||
* /lgtm — Pick from open tasks to inspect proof logs
|
||||
* /lgtm — Pick a task to inspect proof logs
|
||||
*/
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
@@ -58,6 +58,25 @@ function textResult(msg: string) {
|
||||
return { content: [{ type: "text" as const, text: msg }], details: undefined as any };
|
||||
}
|
||||
|
||||
export type LgtmCommandSpec =
|
||||
| { kind: "menu" }
|
||||
| { kind: "view_all" }
|
||||
| { kind: "view"; ids: string[] }
|
||||
| { kind: "error"; message: string };
|
||||
|
||||
export function parseLgtmArgs(args: string): LgtmCommandSpec {
|
||||
const trimmed = args.trim();
|
||||
if (!trimmed) return { kind: "menu" };
|
||||
if (trimmed === "*") return { kind: "view_all" };
|
||||
|
||||
const tokens = trimmed.split(/[\s,]+/).map(token => token.trim()).filter(Boolean);
|
||||
if (tokens[0] === "clear") {
|
||||
return { kind: "error", message: "Task clearing lives in /tasks now. /lgtm is viewer-only." };
|
||||
}
|
||||
|
||||
return { kind: "view", ids: tokens.map(token => token.replace(/^#/, "")).filter(Boolean) };
|
||||
}
|
||||
|
||||
const TASK_TOOL_NAMES = new Set(["TaskCreate", "TaskList", "TaskGet", "TaskUpdate", "TaskClaimDone", "lgtm_supersede", "robot_review_ask", "robot_review_run"]);
|
||||
const REMINDER_INTERVAL = 4;
|
||||
const AUTO_CLEAR_DELAY = 4;
|
||||
@@ -1555,48 +1574,73 @@ This appends a new robot-review iteration. If accepted for a top-level proof tas
|
||||
return renderProofLog(task);
|
||||
}
|
||||
|
||||
function showProofLog(task: Task) {
|
||||
pi.sendMessage({
|
||||
customType: "proof-log",
|
||||
content: renderTaskEvidenceForHuman(task),
|
||||
display: true,
|
||||
details: { taskId: task.id },
|
||||
});
|
||||
}
|
||||
|
||||
function getLgtmTaskLabel(task: Task): string {
|
||||
const tag = task.status === "completed"
|
||||
? "[DONE] "
|
||||
: task.status === "in_progress"
|
||||
? "[ACTIVE] "
|
||||
: "[PENDING] ";
|
||||
return `${tag}#${task.id} ${task.subject}`;
|
||||
}
|
||||
|
||||
async function viewEvidence(taskId: string, ctx: ExtensionCommandContext): Promise<void> {
|
||||
const task = store.get(taskId);
|
||||
if (!task) { ctx.ui.notify(`Task #${taskId} not found`, "error"); return; }
|
||||
ctx.ui.notify(renderTaskEvidenceForHuman(task), "info");
|
||||
if (!task) {
|
||||
ctx.ui.notify(`Task #${taskId} not found`, "error");
|
||||
return;
|
||||
}
|
||||
showProofLog(task);
|
||||
}
|
||||
|
||||
async function viewAllOpenProofLogs(ctx: ExtensionCommandContext): Promise<void> {
|
||||
const open = store.list().filter(t => t.status !== "completed");
|
||||
if (open.length === 0) {
|
||||
ctx.ui.notify("No open tasks to inspect.", "info");
|
||||
return;
|
||||
}
|
||||
for (const task of open) showProofLog(task);
|
||||
}
|
||||
|
||||
pi.registerCommand("lgtm", {
|
||||
description:
|
||||
"View the proof log and judge notes. /lgtm <id> [<id>...] shows specific tasks; /lgtm * shows all open tasks. It does not complete tasks.",
|
||||
"View the proof log and judge notes. /lgtm <id> [<id>...] shows specific tasks; /lgtm * shows all open tasks; task management lives in /tasks.",
|
||||
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
||||
const trimmed = args.trim();
|
||||
if (trimmed === "*") {
|
||||
const open = store.list().filter(t => t.status !== "completed");
|
||||
if (open.length === 0) {
|
||||
ctx.ui.notify("No open tasks to inspect.", "info");
|
||||
return;
|
||||
}
|
||||
ctx.ui.notify(open.map(renderTaskEvidenceForHuman).join("\n\n---\n\n"), "info");
|
||||
const parsed = parseLgtmArgs(args);
|
||||
if (parsed.kind === "error") {
|
||||
ctx.ui.notify(parsed.message, "error");
|
||||
return;
|
||||
}
|
||||
if (!trimmed) {
|
||||
const open = store.list();
|
||||
if (open.length === 0) {
|
||||
ctx.ui.notify("No tasks to inspect.", "info");
|
||||
return;
|
||||
}
|
||||
const tag = (t: typeof open[number]) => {
|
||||
if (t.status === "completed") return "[DONE] ";
|
||||
if (t.status === "in_progress") return "[ACTIVE] ";
|
||||
return "[PENDING] ";
|
||||
};
|
||||
if (parsed.kind === "menu") {
|
||||
const tasks = store.list();
|
||||
const choice = await ctx.ui.select(
|
||||
"View proof log:",
|
||||
open.map(t => `${tag(t)}#${t.id} ${t.subject}`).concat(["← Cancel"]),
|
||||
"LGTM",
|
||||
["View all open proof logs", ...tasks.map(getLgtmTaskLabel), "← Cancel"],
|
||||
);
|
||||
if (!choice || choice === "← Cancel") return;
|
||||
if (choice === "View all open proof logs") return viewAllOpenProofLogs(ctx);
|
||||
const match = choice.match(/#(\d+)/);
|
||||
if (match) await viewEvidence(match[1], ctx);
|
||||
if (match) return viewEvidence(match[1], ctx);
|
||||
return;
|
||||
}
|
||||
const ids = trimmed.split(/[\s,]+/).map(t => t.replace(/^#/, "")).filter(Boolean);
|
||||
for (const id of ids) await viewEvidence(id, ctx);
|
||||
if (parsed.kind === "view_all") return viewAllOpenProofLogs(ctx);
|
||||
for (const id of parsed.ids) await viewEvidence(id, ctx);
|
||||
},
|
||||
getArgumentCompletions: (args: string) => {
|
||||
const trimmed = args.trim();
|
||||
const tasks = store.list();
|
||||
if (!trimmed) return [{ value: "*", label: "*" }];
|
||||
const prefix = trimmed.replace(/^#/, "");
|
||||
return ["*", ...tasks.filter(task => task.id.startsWith(prefix)).map(task => task.id)]
|
||||
.map(value => ({ value, label: value }));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import proofTasksExtension, { parseLgtmArgs } from "../src/index.js";
|
||||
|
||||
type RegisteredTool = {
|
||||
name: string;
|
||||
execute: (...args: any[]) => Promise<any>;
|
||||
};
|
||||
|
||||
type RegisteredCommand = {
|
||||
handler: (args: string, ctx: any) => Promise<void>;
|
||||
getArgumentCompletions?: (args: string) => Promise<string[]>;
|
||||
};
|
||||
|
||||
function makeHarness() {
|
||||
const tools = new Map<string, RegisteredTool>();
|
||||
const commands = new Map<string, RegisteredCommand>();
|
||||
const sentMessages: any[] = [];
|
||||
|
||||
const pi = {
|
||||
on: vi.fn(),
|
||||
registerTool: vi.fn((tool: RegisteredTool) => tools.set(tool.name, tool)),
|
||||
registerCommand: vi.fn((name: string, command: RegisteredCommand) => commands.set(name, command)),
|
||||
sendMessage: vi.fn((message: any) => sentMessages.push(message)),
|
||||
};
|
||||
|
||||
proofTasksExtension(pi as any);
|
||||
|
||||
async function execTool(name: string, params: Record<string, unknown>) {
|
||||
const tool = tools.get(name);
|
||||
if (!tool) throw new Error(`Tool ${name} not registered`);
|
||||
return tool.execute("tool-call", params, undefined, undefined, {});
|
||||
}
|
||||
|
||||
function makeUi(overrides: {
|
||||
select?: Array<string | undefined>;
|
||||
confirm?: Array<boolean>;
|
||||
} = {}) {
|
||||
const selectQueue = [...(overrides.select ?? [])];
|
||||
const confirmQueue = [...(overrides.confirm ?? [])];
|
||||
return {
|
||||
notify: vi.fn(),
|
||||
select: vi.fn(async () => selectQueue.shift()),
|
||||
confirm: vi.fn(async () => confirmQueue.shift() ?? false),
|
||||
};
|
||||
}
|
||||
|
||||
return { tools, commands, sentMessages, execTool, makeUi };
|
||||
}
|
||||
|
||||
describe("parseLgtmArgs", () => {
|
||||
it("parses menu and view forms", () => {
|
||||
expect(parseLgtmArgs("")).toEqual({ kind: "menu" });
|
||||
expect(parseLgtmArgs("*")).toEqual({ kind: "view_all" });
|
||||
expect(parseLgtmArgs("1 #2")).toEqual({ kind: "view", ids: ["1", "2"] });
|
||||
});
|
||||
|
||||
it("rejects task-management forms", () => {
|
||||
expect(parseLgtmArgs("clear")).toEqual({ kind: "error", message: "Task clearing lives in /tasks now. /lgtm is viewer-only." });
|
||||
expect(parseLgtmArgs("clear *")).toEqual({ kind: "error", message: "Task clearing lives in /tasks now. /lgtm is viewer-only." });
|
||||
expect(parseLgtmArgs("clear #7")).toEqual({ kind: "error", message: "Task clearing lives in /tasks now. /lgtm is viewer-only." });
|
||||
});
|
||||
});
|
||||
|
||||
describe("/lgtm command", () => {
|
||||
it("shows all open proof logs from the picker", async () => {
|
||||
const harness = makeHarness();
|
||||
await harness.execTool("TaskCreate", { subject: "Task A", description: "Desc", done_criterion: "done" });
|
||||
await harness.execTool("TaskCreate", { subject: "Task B", description: "Desc", done_criterion: "done" });
|
||||
|
||||
const ui = harness.makeUi({ select: ["View all open proof logs"] });
|
||||
const command = harness.commands.get("lgtm");
|
||||
if (!command) throw new Error("/lgtm not registered");
|
||||
|
||||
await command.handler("", { ui });
|
||||
|
||||
expect(harness.sentMessages).toHaveLength(2);
|
||||
expect(harness.sentMessages[0].customType).toBe("proof-log");
|
||||
expect(harness.sentMessages[0].content).toContain("Task #1");
|
||||
expect(harness.sentMessages[1].content).toContain("Task #2");
|
||||
});
|
||||
|
||||
it("shows one proof log from the picker", async () => {
|
||||
const harness = makeHarness();
|
||||
await harness.execTool("TaskCreate", { subject: "Task A", description: "Desc", done_criterion: "done" });
|
||||
|
||||
const ui = harness.makeUi({ select: ["[PENDING] #1 Task A"] });
|
||||
const command = harness.commands.get("lgtm");
|
||||
if (!command) throw new Error("/lgtm not registered");
|
||||
|
||||
await command.handler("", { ui });
|
||||
|
||||
expect(harness.sentMessages).toHaveLength(1);
|
||||
expect(harness.sentMessages[0].content).toContain("Task #1");
|
||||
});
|
||||
|
||||
it("rejects /lgtm clear and points task management back to /tasks", async () => {
|
||||
const harness = makeHarness();
|
||||
await harness.execTool("TaskCreate", { subject: "Task A", description: "Desc", done_criterion: "done" });
|
||||
|
||||
const ui = harness.makeUi();
|
||||
const command = harness.commands.get("lgtm");
|
||||
if (!command) throw new Error("/lgtm not registered");
|
||||
|
||||
await command.handler("clear 1", { ui });
|
||||
|
||||
expect(harness.sentMessages).toHaveLength(0);
|
||||
expect(ui.notify).toHaveBeenCalledWith("Task clearing lives in /tasks now. /lgtm is viewer-only.", "error");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user