feat(FN-5154): complete Step 0 — add done-range attribution helper

Fusion-Task-Id: FN-5154
Fusion-Task-Lineage: 1ca8d162-343b-4d55-bcba-c34efa331ac4
This commit is contained in:
Fusion (runfusion.ai)
2026-05-19 08:41:22 -07:00
committed by gsxdsm
parent 012fb17db1
commit 4e3a4731aa

View File

@@ -0,0 +1,108 @@
type AttributionSource = "trailer" | "subject-prefix" | "bracketed-prefix" | "none";
export interface DoneRangeAttributionOptions {
worktreePath: string;
baseRef: string;
taskId: string;
runGit: (args: string[]) => Promise<string>;
}
export interface DoneRangeAttributionResult {
files: string[];
ownCommitShas: string[];
foreignCommitCount: number;
}
function extractAttributedTaskId(body: string): string | null {
const trailerPattern = /(?:^|\n)(?:Fusion-Task-Id|Task-Id):\s*(\S+)\s*(?:\n|$)/gim;
let match: RegExpExecArray | null = null;
let last: RegExpExecArray | null = null;
while (true) {
match = trailerPattern.exec(body);
if (!match) break;
last = match;
}
return last?.[1] ?? null;
}
function extractTaskIdFromSubject(subject: string): {
attributedTaskId: string | null;
source: Extract<AttributionSource, "subject-prefix" | "bracketed-prefix" | "none">;
} {
if (!subject) {
return { attributedTaskId: null, source: "none" };
}
const conventional =
/^(?:feat|fix|test|chore|docs|refactor|perf|build|ci|style|revert)\s*\(([A-Z]+-\d+)\)!?:/i.exec(subject);
if (conventional?.[1]) {
return { attributedTaskId: conventional[1].toUpperCase(), source: "subject-prefix" };
}
const bracketed = /^\s*\[([A-Z]+-\d+)\]/i.exec(subject);
if (bracketed?.[1]) {
return { attributedTaskId: bracketed[1].toUpperCase(), source: "bracketed-prefix" };
}
const colon = /^\s*([A-Z]+-\d+):/i.exec(subject);
if (colon?.[1]) {
return { attributedTaskId: colon[1].toUpperCase(), source: "subject-prefix" };
}
return { attributedTaskId: null, source: "none" };
}
function taskIdsMatch(a: string | null, b: string): boolean {
return Boolean(a) && a.toUpperCase() === b.toUpperCase();
}
export async function filterFilesToOwnTaskCommits(opts: DoneRangeAttributionOptions): Promise<DoneRangeAttributionResult> {
const logOutput = await opts.runGit(["log", "--format=%H%x00%s%x00%B%x1e", `${opts.baseRef}..HEAD`]);
if (!logOutput.trim()) {
return { files: [], ownCommitShas: [], foreignCommitCount: 0 };
}
const fileSet = new Set<string>();
const ownCommitShas: string[] = [];
let foreignCommitCount = 0;
const records = logOutput
.split("\x1e")
.map((record) => record.trim())
.filter(Boolean);
for (const record of records) {
const [sha = "", subject = "", ...bodyParts] = record.split("\x00");
if (!sha) continue;
const body = bodyParts.join("\x00");
const trailerAttributedTaskId = extractAttributedTaskId(body);
const subjectAttribution = trailerAttributedTaskId
? { attributedTaskId: null, source: "none" as const }
: extractTaskIdFromSubject(subject);
const attributedTaskId = trailerAttributedTaskId ?? subjectAttribution.attributedTaskId;
if (taskIdsMatch(attributedTaskId, opts.taskId)) {
ownCommitShas.push(sha);
} else {
foreignCommitCount += 1;
}
}
for (const sha of ownCommitShas) {
const diffTreeOutput = await opts.runGit(["diff-tree", "--no-commit-id", "--name-only", "-r", sha]);
for (const file of diffTreeOutput
.split("\n")
.map((line) => line.trim())
.filter(Boolean)) {
fileSet.add(file);
}
}
return {
files: [...fileSet].sort((a, b) => a.localeCompare(b)),
ownCommitShas,
foreignCommitCount,
};
}