Exact reviewed source / 34 of 64
prototype/src/core.mjs
SHA-256 of original bytes
0090c8143ae3a4fe53194a564457d99983025f5dc7b009ef9e4f28f902709a9b
import { createHash } from 'node:crypto';
export const LABELS = Object.freeze(['bug','feature','documentation','question']);
export const THRESHOLDS = Object.freeze({minimumSimilarity:0.40, minimumMargin:0.04, duplicateSimilarity:0.83});
export function normalizeIssues(value) {
const raw = Array.isArray(value) ? value : value?.issues;
if (!Array.isArray(raw) || raw.length === 0 || raw.length > 100) throw new Error('Provide between 1 and 100 issues.');
const seen = new Set();
const issues = raw.map((item) => {
if (!item || typeof item !== 'object' || Array.isArray(item)) throw new Error('Each issue must be an object.');
const id = item.id ?? item.number;
if (!Number.isSafeInteger(id) || id < 1 || seen.has(id)) throw new Error('Issue IDs must be unique positive safe integers.');
seen.add(id);
if (typeof item.title !== 'string' || !item.title.trim() || item.title.length > 300) throw new Error('Every issue needs a title of 1–300 characters.');
if (item.body != null && typeof item.body !== 'string') throw new Error('Issue bodies must be text or null.');
const body = item.body ?? '';
if (body.length > 12000) throw new Error('Issue bodies must be at most 12,000 characters.');
if (item.labels != null && !Array.isArray(item.labels)) throw new Error('Labels must be an array.');
const labels = (item.labels ?? []).map(label => typeof label === 'string' ? label : label?.name);
if (labels.length > 30 || labels.some(label => typeof label !== 'string' || label.length > 100)) throw new Error('Invalid existing labels.');
return {id,title:item.title.trim(),body,labels:[...new Set(labels)]};
});
if (JSON.stringify(issues).length > 256000) throw new Error('The complete export must be at most 256,000 characters.');
return issues;
}
export function cosine(a,b) {
if (!a.length || a.length !== b.length) throw new Error('Mismatched embedding dimensions.');
let dot=0,na=0,nb=0;
for(let i=0;i<a.length;i++){ if(!Number.isFinite(a[i])||!Number.isFinite(b[i]))throw new Error('Invalid embedding.');dot+=a[i]*b[i];na+=a[i]**2;nb+=b[i]**2;}
return na && nb ? dot / Math.sqrt(na*nb) : 0;
}
export function rankIssue(vector, references, referenceVectors) {
const scores=references.map((reference,index)=>({...reference,similarity:cosine(vector,referenceVectors[index])}));
const ranked=LABELS.map(label=>{
const matches=scores.filter(x=>x.label===label).sort((a,b)=>b.similarity-a.similarity);
return {label,score:(matches[0].similarity+matches[1].similarity)/2,evidence:matches[0]};
}).sort((a,b)=>b.score-a.score || a.label.localeCompare(b.label));
const top=ranked[0],margin=top.score-ranked[1].score;
const needsReview=top.score<THRESHOLDS.minimumSimilarity||margin<THRESHOLDS.minimumMargin;
return {suggestedLabel:top.label,similarity:top.score,margin,needsReview,evidence:{title:top.evidence.title,label:top.evidence.label,similarity:top.evidence.similarity},scores:ranked.map(({label,score})=>({label,score}))};
}
export function suggest(issues,vectors,references,referenceVectors) {
return issues.map((issue,index)=>{
let duplicateOf=null,duplicateSimilarity=0,nearestCandidateId=null,nearestCandidateSimilarity=-1;
for(let other=0;other<issues.length;other++){
if(other===index)continue;
const similarity=cosine(vectors[index],vectors[other]);
if(similarity>nearestCandidateSimilarity){nearestCandidateId=issues[other].id;nearestCandidateSimilarity=similarity;}
}
// Suggest only an earlier canonical issue to avoid cycles and duplicate pair counts.
for(let other=0;other<index;other++){
const similarity=cosine(vectors[index],vectors[other]);
if(similarity>=THRESHOLDS.duplicateSimilarity&&similarity>duplicateSimilarity){duplicateOf=issues[other].id;duplicateSimilarity=similarity;}
}
return {...issue,...rankIssue(vectors[index],references,referenceVectors),duplicateOf,duplicateSimilarity,nearestCandidateId,nearestCandidateSimilarity:nearestCandidateId===null?null:nearestCandidateSimilarity};
});
}
export function digest(value) {return createHash('sha256').update(JSON.stringify(value)).digest('hex');}
export function applyDecisions(issues,decisions) {
if(!Array.isArray(decisions)||!decisions.length||decisions.length>issues.length)throw new Error('Select at least one issue.');
const ids=new Set(issues.map(x=>x.id)),seen=new Set();
for(const decision of decisions){
if(!decision||!ids.has(decision.id)||seen.has(decision.id)||!LABELS.includes(decision.label))throw new Error('Every decision needs a unique current issue and a supported label.');
seen.add(decision.id);
}
const selected=new Map(decisions.map(x=>[x.id,x.label]));
return issues.map(issue=>({...issue,labels:selected.has(issue.id)?[...new Set([...issue.labels,selected.get(issue.id)])]:[...issue.labels]}));
}
export function keywordBaseline(issue) {
const text=(issue.title+' '+issue.body).toLowerCase();
const words={bug:['crash','error','exception','broken','fail','wrong','incorrect','regression'],feature:['add','feature','enhancement','implement','support','new'],documentation:['readme','document','guide','tutorial','manual','typo'],question:['how','where','which','what','help','?']};
const scores=LABELS.map(label=>({label,score:words[label].reduce((n,word)=>n+Number(text.includes(word)),0)})).sort((a,b)=>b.score-a.score);
return scores[0].score ? scores[0].label : 'unsure';
}