Q149: editing a task can change everything creating it can

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-09 01:04:26 -04:00
1 parent 766d4ab8cd
commit 7ab46bb2de
14 files changed
+150 -16

No files matched your search

+12
View File
@@ -83,6 +83,18 @@ test("the pinned CEO conversation is the Messages front door", async () => {
await expect(page.getByRole("button", { name: "Open task Operationalize goal: Ship the FlowPilot MVP." })).toBeVisible();
await page.getByRole("button", { name: "Open task Operationalize goal: Ship the FlowPilot MVP." }).click();
await expect(taskDialog).toBeVisible();
// A wrong link is corrected in the same task's editor, as at creation.
await taskDialog.getByRole("button", { name: "Edit" }).click();
await taskDialog.getByLabel("Writing").selectOption("technical");
await taskDialog.getByLabel("Requires workspace access").check();
await taskDialog.getByRole("button", { name: "Save" }).click();
await expect(taskDialog.getByRole("heading", { name: "Edit task" })).toHaveCount(0);
const edited = await page.evaluate(async () => {
const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: { tasks: Array<{ title: string; writingMode: string; requiresFilesystem: boolean }> } }> } }).agentCompany;
const task = (await api.loadSnapshot()).data.tasks.find((item) => item.title === "Operationalize goal: Ship the FlowPilot MVP.")!;
return [task.writingMode, task.requiresFilesystem];
});
expect(edited).toEqual(["technical", true]);
await taskDialog.getByRole("button", { name: "Close" }).click();
await page.getByRole("button", { name: "Messages" }).click();
+10
View File
@@ -0,0 +1,10 @@
import type { DatabaseSync } from "node:sqlite";
import type { WritingMode } from "../../src/shared/domain";
/** A task's current links, for edits in tests that change other fields and keep these. */
export function currentLinks(db: DatabaseSync, taskId: string) {
const row = db.prepare("select project_id, goal_id, workspace_id, requires_filesystem, writing_mode from tasks where id = ?").get(taskId) as {
project_id: string | null; goal_id: string | null; workspace_id: string | null; requires_filesystem: number; writing_mode: WritingMode;
};
return { projectId: row.project_id, goalId: row.goal_id, workspaceId: row.workspace_id, requiresFilesystem: row.requires_filesystem === 1, writingMode: row.writing_mode };
}
+2
View File
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { currentLinks } from "../helpers/task-links";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
@@ -302,6 +303,7 @@ describe("execution modes", () => {
const taskApprovals = () => db.prepare("select id, state, details from approvals where source_entity_id = ? order by rowid").all(taskId) as Array<{ id: string; state: string; details: string }>;
const edit = (description: string) => h.connection.transaction(() => h.deps.organization.updateTask({
orgId: h.orgId, taskId, title: "Wipe staging", description, assigneeAgentId: h.ceoId, risk: "high", priority: TASK_PRIORITY.NORMAL, parentTaskId: null,
...currentLinks(db, taskId),
}));
const decide = (id: string) => h.connection.transaction(() => approvals.decide({ orgId: h.orgId, approvalId: id, state: "approved", decidedByEmployeeId: employeeId }));
+44
View File
@@ -0,0 +1,44 @@
import { randomUUID } from "node:crypto";
import { mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS, WRITING_MODE } from "../../src/shared/domain";
describe("editing a task", () => {
test("can correct its project, goal, workspace, access and writing mode like creating it can", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-task-edit-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
const ids: Record<string, string> = {};
connection.transaction(() => {
ids.org = organization.createOrganization({ organizationName: "Acme", employeeName: "Yang", mission: "m", goal: "g" });
ids.goalA = organization.createGoal({ orgId: ids.org, title: "Goal A", description: "d", ownerAgentId: null });
ids.goalB = organization.createGoal({ orgId: ids.org, title: "Goal B", description: "d", ownerAgentId: null });
ids.projectA = organization.createProject({ orgId: ids.org, goalId: ids.goalA, name: "Project A", description: "d", leadAgentId: null });
ids.projectB = organization.createProject({ orgId: ids.org, goalId: ids.goalB, name: "Project B", description: "d", leadAgentId: null });
for (const name of ["wsA", "wsB"]) {
const path = join(tmpdir(), `ac-task-edit-${name}-${randomUUID()}`);
mkdirSync(path, { recursive: true });
ids[name] = organization.createWorkspace({ orgId: ids.org, name, localPath: path, projectId: null });
}
ids.task = organization.createTask({
orgId: ids.org, title: "Ship", description: "d", status: TASK_STATUS.BACKLOG, assigneeAgentId: null, risk: RISK_LEVEL.LOW,
projectId: ids.projectA, goalId: ids.goalA, workspaceId: ids.wsA, priority: TASK_PRIORITY.NORMAL, requiresFilesystem: false,
});
});
const edit = (change: { projectId: string | null; goalId: string | null; workspaceId: string | null }) => connection.transaction(() => organization.updateTask({
orgId: ids.org, taskId: ids.task, title: "Ship", description: "d", assigneeAgentId: null, risk: RISK_LEVEL.LOW, priority: TASK_PRIORITY.NORMAL,
parentTaskId: null, requiresFilesystem: true, writingMode: WRITING_MODE.TECHNICAL, ...change,
}));
edit({ projectId: ids.projectB, goalId: ids.goalB, workspaceId: ids.wsB });
expect(connection.db.prepare("select project_id, goal_id, workspace_id, requires_filesystem, writing_mode from tasks where id = ?").get(ids.task))
.toEqual({ project_id: ids.projectB, goal_id: ids.goalB, workspace_id: ids.wsB, requires_filesystem: 1, writing_mode: "technical" });
expect(() => edit({ projectId: ids.projectA, goalId: ids.goalB, workspaceId: ids.wsB })).toThrow("Project is linked to a different goal.");
expect(() => edit({ projectId: null, goalId: null, workspaceId: "wsp_missing" })).toThrow("Workspace was not found.");
expect(() => edit({ projectId: null, goalId: "gol_missing", workspaceId: null })).toThrow("Goal was not found.");
connection.close();
});
});
+2 -1
View File
@@ -2,6 +2,7 @@ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { currentLinks } from "../helpers/task-links";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { ApprovalService } from "../../src/main/services/approval-service";
@@ -733,7 +734,7 @@ describe("task execution service", () => {
expect(connection.db.prepare("select count(*) as n from audit_events where entity_id = ? and action = 'stalled'").get(taskId)).toEqual({ n: 1 });
expect(notified).toBe(1);
organization.updateTask({ orgId, taskId, title: "Ship it", description: "Narrower scope.", assigneeAgentId: agentId, risk: RISK_LEVEL.LOW, priority: TASK_PRIORITY.NORMAL, parentTaskId: null });
organization.updateTask({ orgId, taskId, title: "Ship it", description: "Narrower scope.", assigneeAgentId: agentId, risk: RISK_LEVEL.LOW, priority: TASK_PRIORITY.NORMAL, parentTaskId: null, ...currentLinks(connection.db, taskId) });
expect(connection.db.prepare("select stalled_reason from tasks where id = ?").get(taskId)).toEqual({ stalled_reason: null });
expect(connection.transaction(() => execution.enqueueHeartbeatRuns())).toHaveLength(1);
+2
View File
@@ -1,6 +1,7 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { currentLinks } from "../helpers/task-links";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
@@ -23,6 +24,7 @@ describe("task hierarchy", () => {
});
const reparent = (title: string, parent: string | null) => organization.updateTask({
orgId, taskId: ids[title], title, description: "d", assigneeAgentId: null, risk: RISK_LEVEL.LOW, priority: TASK_PRIORITY.NORMAL, parentTaskId: parent,
...currentLinks(connection.db, ids[title]),
});
reparent("B", ids.A);
reparent("C", ids.B);
+2 -1
View File
@@ -1,6 +1,7 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { currentLinks } from "../helpers/task-links";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
@@ -57,7 +58,7 @@ describe("task status authority", () => {
expect(h.as("dev", { status: "backlog", evidence: 123 })).toBe('ERROR: "evidence" is required.');
h.as("dev", { status: "done", evidence: "file_id=fil_1" });
const reviewId = (h.connection.db.prepare("select id from tasks where review_of_task_id = ?").get(h.ids.task) as { id: string }).id;
h.organization.updateTask({ orgId: h.orgId, taskId: h.ids.task, title: "Login page", description: "Build it.", assigneeAgentId: h.ids.cto, risk: RISK_LEVEL.LOW, priority: TASK_PRIORITY.NORMAL, parentTaskId: null });
h.organization.updateTask({ orgId: h.orgId, taskId: h.ids.task, title: "Login page", description: "Build it.", assigneeAgentId: h.ids.cto, risk: RISK_LEVEL.LOW, priority: TASK_PRIORITY.NORMAL, parentTaskId: null, ...currentLinks(h.connection.db, h.ids.task) });
expect(h.status()).toBe("in_progress");
expect(h.connection.db.prepare("select status from tasks where id = ?").get(reviewId)).toEqual({ status: "done" });
expect(h.as("cto", { status: "done", evidence: "Rebuilt the form." })).toBe(`Task ${h.ids.task} moved to review: done needs sign-off from your manager. Cleo will review it.`);