Files

74 lines
2.7 KiB
JavaScript
Raw Permalink Normal View History

import { isGeneratedArtifact } from './generated.js';
import { parseDiffFiles } from './hunks.js';
// The digest is what the model actually reads. Within what it covers there is
// no truncation: a diff that does not fit the model's context is refused
// upstream so the user can pick a roomier model, because a walkthrough written
// against a silently clipped diff is confidently wrong in a way nobody can see.
//
// The one thing it does not cover is tool-produced files (lockfiles, minified
// bundles, codegen). Those are excluded by name, not by size, and they are not
// hidden — they carry no hunk aliases, so nothing can anchor to them, and they
// surface in the uncovered tail like any other unreviewed change.
/**
* Parse sections into files and build the model-facing digest.
*
* Hunks are exposed to the model as request-local aliases (`h1`, `h2`, …)
* rather than their real ids: the aliases are far cheaper in tokens, and a
* model cannot invent a plausible-looking id for a hunk that does not exist.
*/
export function buildDigest(sections) {
const files = [];
for (const section of sections) {
const parsed = parseDiffFiles(section.patch, section.scope);
for (const file of parsed.files) {
files.push({ ...file, scope: section.scope, generated: isGeneratedArtifact(file.path) });
}
}
const idByAlias = new Map();
const aliasById = new Map();
let counter = 0;
const digestFiles = files
.filter((file) => !file.generated)
.map((file) => ({
path: file.path,
...(file.oldPath ? { oldPath: file.oldPath } : {}),
status: file.status,
...(file.scope !== 'branch' && !file.scope.startsWith('pr:') ? { scope: file.scope } : {}),
...(file.binary ? { binary: true } : {}),
hunks: file.hunks.map((hunk) => {
counter += 1;
const alias = `h${counter}`;
idByAlias.set(alias, hunk.id);
aliasById.set(hunk.id, alias);
return {
alias,
header: hunk.header,
oldLines: `${hunk.oldStart}-${hunk.oldStart + Math.max(0, hunk.oldLines - 1)}`,
newLines: `${hunk.newStart}-${hunk.newStart + Math.max(0, hunk.newLines - 1)}`,
added: hunk.added,
deleted: hunk.deleted,
patch: hunk.body,
};
}),
}));
const generatedFiles = files.filter((file) => file.generated);
return {
digest: { files: digestFiles },
files,
idByAlias,
aliasById,
// Reviewable counts: what the model is actually asked about. The excluded
// files still reach the client through `files`.
hunkCount: counter,
fileCount: digestFiles.length,
generatedFileCount: generatedFiles.length,
generatedPaths: generatedFiles.map((file) => file.path),
};
}