74 lines
2.7 KiB
JavaScript
74 lines
2.7 KiB
JavaScript
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),
|
||
|
|
};
|
||
|
|
}
|