Files
kandev/apps/web/lib/diff/walkthrough-match.ts
T
José Almeida 3e237e177a feat: add agent-authored code walkthroughs (#1647)
* feat: add code-walkthrough backend (MCP tool, service, persistence, WS events)

* feat: render agent code walkthroughs as line-anchored cards + editor window

* test: add code-walkthrough re-emit mock-agent scenario

* fix: rework code-walkthrough UX (floating card, line reveal, minimize, clickable file)

* fix: improve code walkthrough editor UX

* fix: backfill walkthrough launcher after websocket connect

* fix: improve walkthrough requests and editor anchors

* fix: clear walkthrough highlights on close

* feat: add walkthrough discard and prompt customization

* fix: send walkthrough requests through saved prompt

* fix: support postgres built-in prompt seeding

* fix: address walkthrough review feedback

* fix: address walkthrough review follow-ups

* fix: harden walkthrough lifecycle races

* fix: reuse walkthrough notification cleanup

* fix: guard stale walkthrough file opens

* fix: abort stale file opens before paint

* fix: stabilize github settings e2e readiness

* fix: use stable github settings e2e selector
2026-07-10 08:34:57 +01:00

71 lines
2.3 KiB
TypeScript

export type WalkthroughTargetFile = {
path: string;
repository_name?: string;
};
export type WalkthroughTargetStep = {
file: string;
repo?: string;
};
function normalizePath(path: string): string[] {
return path.trim().replaceAll("\\", "/").replace(/^\.\//, "").split("/").filter(Boolean);
}
function normalizeRepo(repo: string | undefined): string {
return (repo ?? "").trim();
}
function segmentsMatchSuffix(left: string[], right: string[]): boolean {
if (left.length === 0 || right.length === 0) return false;
const shorter = left.length <= right.length ? left : right;
const longer = left.length <= right.length ? right : left;
const offset = longer.length - shorter.length;
return shorter.every((segment, index) => longer[offset + index] === segment);
}
/**
* Best-effort match between a walkthrough step's `file` and a diff/review file
* path. Exact match wins; suffix matching is allowed only on path-segment
* boundaries so `bar.ts` cannot match `foobar.ts`.
*/
export function walkthroughFileMatches(diffPath: string, stepFile: string): boolean {
const diffSegments = normalizePath(diffPath);
const stepSegments = normalizePath(stepFile);
return segmentsMatchSuffix(diffSegments, stepSegments);
}
function repositoryMatches(file: WalkthroughTargetFile, step: WalkthroughTargetStep): boolean {
const stepRepo = normalizeRepo(step.repo);
const fileRepo = normalizeRepo(file.repository_name);
if (!stepRepo) return !fileRepo;
if (!fileRepo) return false;
return fileRepo === stepRepo;
}
function isRepoAmbiguous(
file: WalkthroughTargetFile,
step: WalkthroughTargetStep,
allFiles: WalkthroughTargetFile[] | undefined,
): boolean {
if (step.repo || !allFiles || !file.repository_name) return false;
const repos = new Set(
allFiles
.filter((candidate) => walkthroughFileMatches(candidate.path, step.file))
.map((candidate) => normalizeRepo(candidate.repository_name))
.filter(Boolean),
);
return repos.size > 1;
}
export function walkthroughStepMatchesFile(
file: WalkthroughTargetFile,
step: WalkthroughTargetStep,
allFiles?: WalkthroughTargetFile[],
): boolean {
if (!repositoryMatches(file, step)) return false;
if (!walkthroughFileMatches(file.path, step.file)) return false;
if (isRepoAmbiguous(file, step, allFiles)) return false;
return true;
}