Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ describe("normalizeNodeId", () => {
).toBe("file:src/foo.ts");
});

it("keeps a real prefix when a different reserved word is a middle segment", () => {
// Regression: "endpoint:service:x" is a valid prefix followed by a real
// path segment that happens to be a reserved word. The outer "endpoint"
// prefix must be preserved, not dropped in favour of "service".
expect(
normalizeNodeId("endpoint:service:getUser", { type: "endpoint" }),
).toBe("endpoint:service:getUser");
});

it("is idempotent for IDs whose middle segment is a reserved word", () => {
const once = normalizeNodeId("endpoint:service:getUser", {
type: "endpoint",
});
expect(normalizeNodeId(once, { type: "endpoint" })).toBe(once);
});

it("strips project-name prefix when valid prefix follows", () => {
expect(
normalizeNodeId("my-project:file:src/foo.ts", { type: "file" }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,13 @@ function stripToValidPrefix(id: string): { prefix: string | null; path: string }

const segment = remaining.slice(0, colonIdx);
if (VALID_PREFIXES.has(segment)) {
// Check for double valid prefix (e.g., "file:file:src/foo.ts")
// Check for a true duplicate prefix (e.g., "file:file:src/foo.ts").
// Only collapse when the next segment is the SAME prefix — a different
// reserved word in the middle (e.g. "endpoint:service:x") is a real
// path segment, not a duplicate, and must not be stripped.
const rest = remaining.slice(colonIdx + 1);
const innerColonIdx = rest.indexOf(":");
if (innerColonIdx > 0 && VALID_PREFIXES.has(rest.slice(0, innerColonIdx))) {
if (innerColonIdx > 0 && rest.slice(0, innerColonIdx) === segment) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use expected type when collapsing prefixed IDs

When an LLM emits a project-prefixed ID and the project name is also a reserved prefix (for example a file node with service:file:src/foo.ts, or any bad outer valid prefix before the expected prefix), this condition no longer recurses because the inner prefix differs from the outer one. normalizeNodeId then returns service:file:src/foo.ts for a node whose type is file, so the graph no longer uses the canonical type:path ID and edges that reference the canonical file:src/foo.ts form are dropped as dangling. The duplicate-prefix decision needs the expected node prefix to distinguish this case from legitimate middle path segments like endpoint:service:x.

Useful? React with 👍 / 👎.

// Double-prefixed — skip the outer, recurse on inner
remaining = rest;
continue;
Expand Down