Ctrl K
ComunidadGuías…

lib/comment-follow-up.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type { Comment } from "@/lib/types";

/**
 * New comments an MCP agent has not answered yet (MIN-801, MIN-818).
 *
 * The rule works per thread (a root comment and its replies, depth ≤ 1):
 *
 * - A reply in a thread the agent took part in (it wrote the root or one of
 *   the replies) waits until the agent writes in THAT thread after it,
 *   whatever the agent posted elsewhere on the ticket.
 * - Any other comment (a root comment, or a reply in a thread the agent never
 *   joined) waits when it came after the agent's LAST comment on the ticket.
 *
 * Authors are teammates, the user themself, or a forge comment mirrored from
 * a linked pull request (MIN-802). Numo's replies are not counted: they are
 * another agent's voice, not new feedback. A mirrored comment deleted on the
 * forge no longer asks for anything.
 *
 * Mirrored comments carry their forge date in `created_at`, so one ordering
 * covers both sources. The SQL function `issue_comment_follow_ups` applies the
 * same rule for the board cards; keep the two in step.
 */
export interface CommentFollowUp {
  /** Comments still waiting for the agent. */
  count: number;
  /** When the agent last commented on the ticket (ISO timestamp). */
  since: string;
  /** Root comment ids of the agent's threads holding unanswered replies. */
  threads: string[];
}

type FollowUpComment = Pick<
  Comment,
  "id" | "parent_id" | "created_at" | "via_mcp" | "via_assistant" | "github"
>;

export function commentFollowUp(comments: readonly FollowUpComment[]): CommentFollowUp | null {
  let sinceTime = -Infinity;
  // The agent's last message in each thread, keyed by the thread's root id.
  const threadAgentTime = new Map<string, number>();
  for (const comment of comments) {
    if (!comment.via_mcp) continue;
    const time = Date.parse(comment.created_at);
    const thread = comment.parent_id ?? comment.id;
    sinceTime = Math.max(sinceTime, time);
    threadAgentTime.set(thread, Math.max(threadAgentTime.get(thread) ?? -Infinity, time));
  }
  if (sinceTime === -Infinity) return null;

  let count = 0;
  const threads = new Set<string>();
  for (const comment of comments) {
    if (comment.via_mcp || comment.via_assistant || comment.github?.deleted_at) continue;
    const time = Date.parse(comment.created_at);
    const thread = comment.parent_id;
    const threadTime = thread ? threadAgentTime.get(thread) : undefined;
    if (thread && threadTime !== undefined) {
      if (time > threadTime) {
        count++;
        threads.add(thread);
      }
    } else if (time > sinceTime) {
      count++;
    }
  }
  // ISO, as the board's endpoint serves it, whatever form the row carried.
  return count > 0
    ? { count, since: new Date(sinceTime).toISOString(), threads: [...threads] }
    : null;
}