feat: Add threaded review comments + agent comment responses
Introduces GitHub-style threaded comments via parentCommentId self-reference. Users and agents can reply within comment threads, and review agents receive comment IDs so they can post targeted responses via comment-responses.json. - Migration 0032: parentCommentId column + index on review_comments - Repository: createReply() copies parent context, default author 'you' → 'user' - tRPC: replyToReviewComment procedure, requestPhaseChanges passes threaded comments - Orchestrator: formats [comment:ID] tags with full reply threads in task description - Agent IO: readCommentResponses() reads .cw/output/comment-responses.json - OutputHandler: processes agent comment responses (creates replies, resolves threads) - Execute prompt: conditional <review_comments> block when task has [comment:] markers - Frontend: CommentThread renders root+replies with agent-specific styling + reply form - Sidebar/ReviewTab: root-only comment counts, reply mutation plumbing through DiffViewer chain
This commit is contained in:
@@ -23,7 +23,43 @@ export class DrizzleReviewCommentRepository implements ReviewCommentRepository {
|
||||
lineNumber: data.lineNumber,
|
||||
lineType: data.lineType,
|
||||
body: data.body,
|
||||
author: data.author ?? 'you',
|
||||
author: data.author ?? 'user',
|
||||
parentCommentId: data.parentCommentId ?? null,
|
||||
resolved: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
const rows = await this.db
|
||||
.select()
|
||||
.from(reviewComments)
|
||||
.where(eq(reviewComments.id, id))
|
||||
.limit(1);
|
||||
return rows[0]!;
|
||||
}
|
||||
|
||||
async createReply(parentCommentId: string, body: string, author?: string): Promise<ReviewComment> {
|
||||
// Fetch parent comment to copy context fields
|
||||
const parentRows = await this.db
|
||||
.select()
|
||||
.from(reviewComments)
|
||||
.where(eq(reviewComments.id, parentCommentId))
|
||||
.limit(1);
|
||||
const parent = parentRows[0];
|
||||
if (!parent) {
|
||||
throw new Error(`Parent comment not found: ${parentCommentId}`);
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const id = nanoid();
|
||||
await this.db.insert(reviewComments).values({
|
||||
id,
|
||||
phaseId: parent.phaseId,
|
||||
filePath: parent.filePath,
|
||||
lineNumber: parent.lineNumber,
|
||||
lineType: parent.lineType,
|
||||
body,
|
||||
author: author ?? 'user',
|
||||
parentCommentId,
|
||||
resolved: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
@@ -13,10 +13,12 @@ export interface CreateReviewCommentData {
|
||||
lineType: 'added' | 'removed' | 'context';
|
||||
body: string;
|
||||
author?: string;
|
||||
parentCommentId?: string; // for replies
|
||||
}
|
||||
|
||||
export interface ReviewCommentRepository {
|
||||
create(data: CreateReviewCommentData): Promise<ReviewComment>;
|
||||
createReply(parentCommentId: string, body: string, author?: string): Promise<ReviewComment>;
|
||||
findByPhaseId(phaseId: string): Promise<ReviewComment[]>;
|
||||
resolve(id: string): Promise<ReviewComment | null>;
|
||||
unresolve(id: string): Promise<ReviewComment | null>;
|
||||
|
||||
@@ -617,11 +617,13 @@ export const reviewComments = sqliteTable('review_comments', {
|
||||
lineType: text('line_type', { enum: ['added', 'removed', 'context'] }).notNull(),
|
||||
body: text('body').notNull(),
|
||||
author: text('author').notNull().default('you'),
|
||||
parentCommentId: text('parent_comment_id').references((): ReturnType<typeof text> => reviewComments.id, { onDelete: 'cascade' }),
|
||||
resolved: integer('resolved', { mode: 'boolean' }).notNull().default(false),
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
|
||||
}, (table) => [
|
||||
index('review_comments_phase_id_idx').on(table.phaseId),
|
||||
index('review_comments_parent_id_idx').on(table.parentCommentId),
|
||||
]);
|
||||
|
||||
export type ReviewComment = InferSelectModel<typeof reviewComments>;
|
||||
|
||||
Reference in New Issue
Block a user