Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion src/aggregate.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { entityKey, groupByEntity, type EntityRow } from "./aggregate.js";
import { entityKey, groupByEntity, markRecency, type EntityRow } from "./aggregate.js";

/**
* Binding-independent unit tests for entity aggregation (node pool).
Expand Down Expand Up @@ -243,3 +243,32 @@ describe("duplicate rate regression (issue #189 measurements, 2026-08-01)", () =
expect(entityKey(pool[2])).not.toBe(entityKey(pool[3])); // issue #1317 vs PR #1318
});
});

describe("markRecency (issue #267)", () => {
it("marks older rows superseded and newest latest", () => {
const m = markRecency([
{ id: "old", updatedAt: "2026-09-15T00:00:00Z" },
{ id: "new", updatedAt: "2026-09-29T00:00:00Z" },
{ id: "mid", updatedAt: "2026-09-23T00:00:00Z" },
]);
expect(m.get("old")).toBe("superseded");
expect(m.get("mid")).toBe("superseded");
expect(m.get("new")).toBe("latest");
});
it("marks nothing for a single row or equal timestamps", () => {
expect(markRecency([{ id: "a", updatedAt: "2026-09-15T00:00:00Z" }]).size).toBe(0);
expect(markRecency([
{ id: "a", updatedAt: "2026-09-15T00:00:00Z" },
{ id: "b", updatedAt: "2026-09-15T00:00:00Z" },
]).size).toBe(0);
});
it("leaves unparsable timestamps unmarked", () => {
const m = markRecency([
{ id: "a", updatedAt: "" },
{ id: "b", updatedAt: "2026-09-15T00:00:00Z" },
{ id: "c", updatedAt: "2026-09-16T00:00:00Z" },
]);
expect(m.has("a")).toBe(false);
expect(m.get("c")).toBe("latest");
});
});
21 changes: 21 additions & 0 deletions src/aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,24 @@ export function groupByEntity<T>(
}
return ordered;
}

/** Recency marker for one row of an entity group (issue #267). */
export type Recency = "latest" | "superseded";

/**
* Compare `updated_at` across the representative and its folded rows and
* mark each one: "superseded" when another row in the group is newer,
* "latest" for the newest. Returns an empty map when the group holds fewer
* than two distinct timestamps — the pool proves nothing about recency then.
* Rows with an unparsable timestamp are left unmarked.
*/
export function markRecency(rows: Array<{ id: string; updatedAt: string | null | undefined }>): Map<string, Recency> {
const out = new Map<string, Recency>();
const times = rows
.map((r) => ({ id: r.id, t: r.updatedAt ? Date.parse(r.updatedAt) : NaN }))
.filter((r) => !Number.isNaN(r.t));
if (new Set(times.map((r) => r.t)).size < 2) return out;
const max = Math.max(...times.map((r) => r.t));
for (const r of times) out.set(r.id, r.t === max ? "latest" : "superseded");
return out;
}
24 changes: 22 additions & 2 deletions src/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import {
FETCH_CONTENT_MAX_CHARS,
FETCH_MAX_VECTOR_IDS,
} from "./fetch.js";
import { entityKey, groupByEntity } from "./aggregate.js";
import { entityKey, groupByEntity, markRecency } from "./aggregate.js";
import { pathPrefixRange, validateDocPathPrefix } from "./path-prefix.js";

const GITHUB_API = "https://api.github.com";
Expand Down Expand Up @@ -337,7 +337,10 @@ export function createRagMcpServer(env: Env): McpServer {
"(Master's feedback, AI responses, self-review now/later/accepted classifications).\n" +
"Results are aggregated per underlying entity: a file's doc row and its commit diffs are one result, " +
"an issue or PR and its comments / reviews are one result. top_k therefore counts distinct entities, " +
"and a result that absorbed others carries same_entity { count, others[] } with links to them.\n" +
"and a result that absorbed others carries same_entity { count, others[] } with links to them. " +
"When those rows hold more than one distinct updated_at, the representative and each others entry carry recency: " +
"\"superseded\" = a newer row of the same entity is in the pool, \"latest\" = the newest row in the pool. " +
"Absent recency is not a claim of being latest, and recency never affects ranking or scores.\n" +
"Every result row — and every same_entity.others entry — carries vector_id, the handle mode 4 takes. " +
"It is a handle for reaching a row you just found, not a durable identifier: the id scheme has been " +
"migrated before and may be again, so do not store one for later use.\n" +
Expand Down Expand Up @@ -917,8 +920,19 @@ export function createRagMcpServer(env: Env): McpServer {
updated_at: string;
score: number;
commit_sha?: string;
/** See ResultItem.recency (issue #267). */
recency?: "latest" | "superseded";
}>;
};
/**
* Additive recency marker (issue #267), set only when the
* representative and its same_entity.others hold more than one
* distinct updated_at: "superseded" = a newer row of the same entity
* is in the pool, "latest" = newest row in the pool. Never affects
* ranking or scores. Absent = no newer version in the pool, which is
* not a claim of being latest.
*/
recency?: "latest" | "superseded";
};

const items: ResultItem[] = filtered.map((f) => {
Expand All @@ -928,6 +942,10 @@ export function createRagMcpServer(env: Env): McpServer {
// Rows folded into this representative. Kept as references (never
// dropped) so the caller can still reach every version / comment.
const folded = collapsedInto.get(f.vectorId) ?? [];
const recency = markRecency([
{ id: f.vectorId, updatedAt: r.updatedAt },
...folded.map((o) => ({ id: o.vectorId, updatedAt: resolveRow(payload.get(o.vectorId)).updatedAt })),
]);
const sameEntity =
folded.length > 0
? {
Expand All @@ -942,6 +960,7 @@ export function createRagMcpServer(env: Env): McpServer {
updated_at: or.updatedAt,
score: o.fusedScore,
...(or.type === "diff" ? { commit_sha: or.commitSha } : {}),
...(recency.has(o.vectorId) ? { recency: recency.get(o.vectorId) } : {}),
};
}),
}
Expand Down Expand Up @@ -1008,6 +1027,7 @@ export function createRagMcpServer(env: Env): McpServer {
}
: {}),
...(sameEntity ? { same_entity: sameEntity } : {}),
...(recency.has(f.vectorId) ? { recency: recency.get(f.vectorId) } : {}),
};
});

Expand Down
Loading