feat(KB-341): unify steeringComments and comments into single comments system

- Consolidate steeringComments and comments into unified comments field using TaskComment type

- Remove SteeringComment type, migrate all comments to TaskComment format

- Update addComment to delegate to addTaskComment with auto-refinement for done tasks

- Merge legacy steeringComments data into comments during database migration

- Update dashboard API endpoint from /steer to /comments

- Update executor to use unified comments field for real-time injection

- Update PR comment handler to use addTaskComment method
This commit is contained in:
gsxdsm
2026-04-01 07:44:04 -07:00
parent c1ca8a4316
commit 021055c82a
38 changed files with 1198 additions and 143 deletions

View File

@@ -541,8 +541,9 @@ describe("migrateFromLegacy", () => {
expect(JSON.parse(row.steps)).toHaveLength(2);
expect(JSON.parse(row.log)).toHaveLength(1);
expect(JSON.parse(row.attachments)).toHaveLength(1);
// steeringComments should be merged into comments
expect(JSON.parse(row.comments)).toHaveLength(1); // 1 steering comment migrated
// steeringComments are merged into comments during migration
expect(JSON.parse(row.steeringComments)).toEqual([]); // Now empty - merged into comments
expect(JSON.parse(row.comments)).toHaveLength(1); // Migrated from steeringComments
expect(JSON.parse(row.workflowStepResults)).toHaveLength(1);
expect(JSON.parse(row.prInfo).number).toBe(1);
expect(JSON.parse(row.issueInfo).number).toBe(10);

View File

@@ -224,7 +224,10 @@ async function migrateTasks(kbDir: string, db: Database): Promise<void> {
toJson(task.steps || []),
toJson(task.log || []),
toJson(task.attachments || []),
toJson(mergedComments),
toJson(task.attachments || []),
"[]", // steeringComments column - no longer used, write empty array
// Merge legacy steeringComments into unified comments field during migration
toJson([...((task as any).steeringComments || []), ...(task.comments || [])]),
toJson(task.workflowStepResults || []),
toJsonNullable(task.prInfo),
toJsonNullable(task.issueInfo),

View File

@@ -1276,13 +1276,13 @@ describe("TaskStore", () => {
);
});
it("unifies task comments and steering comments into single comments field", async () => {
it("persists all comments in unified comments field", async () => {
const task = await createTestTask();
await store.addTaskComment(task.id, "General note", "alice");
await store.addComment(task.id, "Execution note");
const reopened = await store.getTask(task.id);
// Both comments should now be in the unified comments field
// Both comments should be in the unified comments array
expect(reopened.comments).toHaveLength(2);
expect(reopened.comments![0].text).toBe("General note");
expect(reopened.comments![1].text).toBe("Execution note");
@@ -1367,8 +1367,7 @@ describe("TaskStore", () => {
const task = await createTestTask();
const updated = await store.addComment(task.id, "Comment with log");
expect(updated.log.some((l) => l.action === "Comment added")).toBe(true);
expect(updated.log.some((l) => l.outcome === "by user")).toBe(true);
expect(updated.log.some((l) => l.action === "Comment added by user")).toBe(true);
});
it("updates updatedAt timestamp", async () => {
@@ -1499,12 +1498,11 @@ describe("TaskStore", () => {
});
describe("task comments and merge details types", () => {
it("keeps task comments distinct from steering comments on new tasks", async () => {
it("has undefined comments on new tasks", async () => {
const task = await createTestTask();
const reopened = await store.getTask(task.id);
expect(reopened.comments).toBeUndefined();
expect(reopened.comments).toBeUndefined();
});
it("supports the task comment and merge details shapes", async () => {
@@ -2244,6 +2242,7 @@ describe("TaskStore", () => {
const duplicated = await store.duplicateTask(task.id);
// Comments should not be copied when duplicating
expect(duplicated.comments).toBeUndefined();
});

View File

@@ -150,7 +150,13 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
steps: fromJson<import("./types.js").TaskStep[]>(row.steps) || [],
log: fromJson<import("./types.js").TaskLogEntry[]>(row.log) || [],
attachments: (() => { const a = fromJson<TaskAttachment[]>(row.attachments); return a && a.length > 0 ? a : undefined; })(),
comments: (() => { const c = fromJson<import("./types.js").TaskComment[]>(row.comments); return c && c.length > 0 ? c : undefined; })(),
comments: (() => {
// Merge legacy steeringComments and comments into unified comments field
const legacySteering = fromJson<Array<{ id: string; text: string; createdAt: string; author: string }>>(row.steeringComments) || [];
const regularComments = fromJson<import("./types.js").TaskComment[]>(row.comments) || [];
const merged = [...legacySteering, ...regularComments];
return merged.length > 0 ? merged : undefined;
})(),
workflowStepResults: (() => { const w = fromJson<import("./types.js").WorkflowStepResult[]>(row.workflowStepResults); return w && w.length > 0 ? w : undefined; })(),
prInfo: fromJson<import("./types.js").PrInfo>(row.prInfo),
issueInfo: fromJson<import("./types.js").IssueInfo>(row.issueInfo),
@@ -208,6 +214,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
toJson(task.steps || []),
toJson(task.log || []),
toJson(task.attachments || []),
"[]", // steeringComments column - no longer used, write empty array
toJson(task.comments || []),
toJson(task.workflowStepResults || []),
toJsonNullable(task.prInfo),
@@ -1997,6 +2004,8 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
* Comments are injected into the AI execution context.
* When a comment is added to a task in the "done" column by a user,
* automatically creates a refinement task with the comment text as feedback.
*
* Note: Now uses the unified comments system (TaskComment).
*/
async addComment(
id: string,