源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
This commit is contained in:
+334
@@ -0,0 +1,334 @@
|
||||
import {
|
||||
extractAgentPatchPath,
|
||||
inferAgentPatchChangeType,
|
||||
normalizeAgentPatchText,
|
||||
type AgentPatchChangeType
|
||||
} from "../../../rules/agentPatchMetadata";
|
||||
import { isAgentUnifiedDiffText } from "../../../rules/agentUnifiedDiffValidation";
|
||||
|
||||
export { extractAgentPatchPath, inferAgentPatchChangeType };
|
||||
export type AgentUnifiedDiffChangeType = AgentPatchChangeType;
|
||||
|
||||
export interface ParsedAgentUnifiedDiff {
|
||||
oldString: string;
|
||||
newString: string;
|
||||
}
|
||||
|
||||
export interface ParsedAgentUnifiedDiffLine {
|
||||
kind: "add" | "remove" | "context";
|
||||
oldLineNumber: number | null;
|
||||
newLineNumber: number | null;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export type AgentFileChangeType =
|
||||
| "created"
|
||||
| "modified"
|
||||
| "deleted"
|
||||
| "unknown";
|
||||
|
||||
const DIFF_META_PREFIXES = [
|
||||
"diff --git ",
|
||||
"index ",
|
||||
"--- ",
|
||||
"+++ ",
|
||||
"*** "
|
||||
] as const;
|
||||
|
||||
export function parseAgentUnifiedDiff(
|
||||
diffText: string
|
||||
): ParsedAgentUnifiedDiff | null {
|
||||
if (!isAgentUnifiedDiff(diffText)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedText = normalizeAgentPatchText(diffText);
|
||||
const oldLines: string[] = [];
|
||||
const newLines: string[] = [];
|
||||
let sawChangeLine = false;
|
||||
let sawHunkHeader = false;
|
||||
let hunkActive = false;
|
||||
|
||||
for (const line of normalizedText.replace(/\r\n/g, "\n").split("\n")) {
|
||||
if (/^@@(?: -\d|$)/.test(line)) {
|
||||
if (sawHunkHeader && (oldLines.length > 0 || newLines.length > 0)) {
|
||||
oldLines.push("");
|
||||
newLines.push("");
|
||||
}
|
||||
sawHunkHeader = true;
|
||||
hunkActive = true;
|
||||
continue;
|
||||
}
|
||||
if (line === "@@" && isApplyPatch(normalizedText)) {
|
||||
if (sawHunkHeader && (oldLines.length > 0 || newLines.length > 0)) {
|
||||
oldLines.push("");
|
||||
newLines.push("");
|
||||
}
|
||||
sawHunkHeader = true;
|
||||
hunkActive = true;
|
||||
continue;
|
||||
}
|
||||
if (hunkActive && line.startsWith("diff --git ")) {
|
||||
hunkActive = false;
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
(!hunkActive &&
|
||||
DIFF_META_PREFIXES.some((prefix) => line.startsWith(prefix))) ||
|
||||
(hunkActive && isApplyPatch(normalizedText) && line.startsWith("*** "))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (line === "\\ No newline at end of file") {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("+")) {
|
||||
newLines.push(line.slice(1));
|
||||
sawChangeLine = true;
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("-")) {
|
||||
oldLines.push(line.slice(1));
|
||||
sawChangeLine = true;
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith(" ")) {
|
||||
const content = line.slice(1);
|
||||
oldLines.push(content);
|
||||
newLines.push(content);
|
||||
}
|
||||
}
|
||||
|
||||
if (!sawChangeLine) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
oldString: oldLines.join("\n"),
|
||||
newString: newLines.join("\n")
|
||||
};
|
||||
}
|
||||
|
||||
export function parseAgentUnifiedDiffLines(
|
||||
diffText: string
|
||||
): ParsedAgentUnifiedDiffLine[] {
|
||||
if (!isAgentUnifiedDiff(diffText)) {
|
||||
return [];
|
||||
}
|
||||
const normalized = normalizeAgentPatchText(diffText);
|
||||
if (!normalized.trim()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const lines: ParsedAgentUnifiedDiffLine[] = [];
|
||||
let oldLineNumber = 0;
|
||||
let newLineNumber = 0;
|
||||
let hunkActive = false;
|
||||
|
||||
for (const line of normalized.replace(/\r\n/g, "\n").split("\n")) {
|
||||
const match = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
||||
if (match) {
|
||||
oldLineNumber = Number.parseInt(match[1] ?? "0", 10);
|
||||
newLineNumber = Number.parseInt(match[2] ?? "0", 10);
|
||||
hunkActive = true;
|
||||
continue;
|
||||
}
|
||||
if (line === "@@" && isApplyPatch(normalized)) {
|
||||
hunkActive = true;
|
||||
continue;
|
||||
}
|
||||
if (hunkActive && line.startsWith("diff --git ")) {
|
||||
hunkActive = false;
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
(!hunkActive &&
|
||||
DIFF_META_PREFIXES.some((prefix) => line.startsWith(prefix))) ||
|
||||
(hunkActive && isApplyPatch(normalized) && line.startsWith("*** "))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (line === "\\ No newline at end of file") {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("+")) {
|
||||
lines.push({
|
||||
kind: "add",
|
||||
oldLineNumber: null,
|
||||
newLineNumber,
|
||||
text: line.slice(1)
|
||||
});
|
||||
newLineNumber += 1;
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("-")) {
|
||||
lines.push({
|
||||
kind: "remove",
|
||||
oldLineNumber,
|
||||
newLineNumber: null,
|
||||
text: line.slice(1)
|
||||
});
|
||||
oldLineNumber += 1;
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith(" ")) {
|
||||
lines.push({
|
||||
kind: "context",
|
||||
oldLineNumber,
|
||||
newLineNumber,
|
||||
text: line.slice(1)
|
||||
});
|
||||
oldLineNumber += 1;
|
||||
newLineNumber += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
export function isAgentUnifiedDiff(diffText: string): boolean {
|
||||
return isAgentUnifiedDiffText(diffText);
|
||||
}
|
||||
|
||||
export function countAgentTextLines(value: string | null | undefined): number {
|
||||
if (typeof value !== "string" || value.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const lines = value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
||||
if (lines.at(-1) === "") {
|
||||
lines.pop();
|
||||
}
|
||||
return lines.length;
|
||||
}
|
||||
|
||||
export function agentFileChangeStats({
|
||||
changeType,
|
||||
unifiedDiff,
|
||||
content,
|
||||
oldString,
|
||||
newString
|
||||
}: {
|
||||
changeType: AgentFileChangeType;
|
||||
unifiedDiff: string | null | undefined;
|
||||
content: string | null | undefined;
|
||||
oldString: string | null | undefined;
|
||||
newString: string | null | undefined;
|
||||
}): { added: number; removed: number } {
|
||||
const diff = typeof unifiedDiff === "string" ? unifiedDiff : null;
|
||||
if (diff && isAgentUnifiedDiff(diff)) {
|
||||
return parseAgentUnifiedDiffStats(diff);
|
||||
}
|
||||
|
||||
if (changeType === "deleted") {
|
||||
return {
|
||||
added: 0,
|
||||
removed: countAgentTextLines(oldString ?? content ?? diff)
|
||||
};
|
||||
}
|
||||
if (changeType === "created") {
|
||||
return {
|
||||
added: countAgentTextLines(content ?? newString ?? diff),
|
||||
removed: 0
|
||||
};
|
||||
}
|
||||
if (typeof oldString === "string" && typeof newString === "string") {
|
||||
return agentTextDiffStats(oldString, newString);
|
||||
}
|
||||
return { added: 0, removed: 0 };
|
||||
}
|
||||
|
||||
export function parseAgentUnifiedDiffStats(diffText: string): {
|
||||
added: number;
|
||||
removed: number;
|
||||
} {
|
||||
if (!isAgentUnifiedDiff(diffText)) {
|
||||
return { added: 0, removed: 0 };
|
||||
}
|
||||
|
||||
let added = 0;
|
||||
let removed = 0;
|
||||
let hunkActive = false;
|
||||
const normalized = normalizeAgentPatchText(diffText);
|
||||
for (const line of normalized.replace(/\r\n/g, "\n").split("\n")) {
|
||||
if (/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.test(line)) {
|
||||
hunkActive = true;
|
||||
continue;
|
||||
}
|
||||
if (line === "@@" && isApplyPatch(normalized)) {
|
||||
hunkActive = true;
|
||||
continue;
|
||||
}
|
||||
if (hunkActive && line.startsWith("diff --git ")) {
|
||||
hunkActive = false;
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
(!hunkActive &&
|
||||
DIFF_META_PREFIXES.some((prefix) => line.startsWith(prefix))) ||
|
||||
(hunkActive && isApplyPatch(normalized) && line.startsWith("*** "))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (line === "\\ No newline at end of file" || !hunkActive) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("+")) {
|
||||
added += 1;
|
||||
} else if (line.startsWith("-")) {
|
||||
removed += 1;
|
||||
}
|
||||
}
|
||||
return { added, removed };
|
||||
}
|
||||
|
||||
function agentTextDiffStats(
|
||||
oldString: string,
|
||||
newString: string
|
||||
): { added: number; removed: number } {
|
||||
const oldLines = splitAgentTextLines(oldString);
|
||||
const newLines = splitAgentTextLines(newString);
|
||||
if (oldLines.length === 0 && newLines.length === 0) {
|
||||
return { added: 0, removed: 0 };
|
||||
}
|
||||
if (oldLines.length * newLines.length > 2_000_000) {
|
||||
return { added: 0, removed: 0 };
|
||||
}
|
||||
|
||||
let previous = Array.from({ length: newLines.length + 1 }, () => 0);
|
||||
for (const oldLine of oldLines) {
|
||||
const current = Array.from({ length: newLines.length + 1 }, () => 0);
|
||||
for (let newIndex = 1; newIndex <= newLines.length; newIndex += 1) {
|
||||
current[newIndex] =
|
||||
oldLine === newLines[newIndex - 1]
|
||||
? previous[newIndex - 1]! + 1
|
||||
: Math.max(previous[newIndex]!, current[newIndex - 1]!);
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
|
||||
const commonLines = previous[newLines.length] ?? 0;
|
||||
return {
|
||||
added: newLines.length - commonLines,
|
||||
removed: oldLines.length - commonLines
|
||||
};
|
||||
}
|
||||
|
||||
function splitAgentTextLines(value: string): string[] {
|
||||
if (value.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const lines = value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
||||
if (lines.at(-1) === "") {
|
||||
lines.pop();
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function isApplyPatch(value: string): boolean {
|
||||
return (
|
||||
value.includes("*** Begin Patch") &&
|
||||
(value.includes("*** Add File:") ||
|
||||
value.includes("*** Delete File:") ||
|
||||
value.includes("*** Update File:"))
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user