Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 139 additions & 1 deletion ai-code-review/ADR/getAdrs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Repository } from "../repository";
import tl = require("azure-pipelines-task-lib/task");
import { DevOpsWikiService, DevOpsWikiOptions } from "../devOpsWikiService";

/**
* Retrieves Architecture Decision Records (ADRs) from the repository.
Expand All @@ -13,7 +15,7 @@ export async function getAdrs(
adrsFolderPath: string,
adrsExtensions: string[] = []
): Promise<string[]> {
let fileExtensions = ['.md', '.txt', '.html'];
let fileExtensions = [".md", ".txt", ".html"];
if (adrsExtensions.length) {
fileExtensions = adrsExtensions;
}
Expand All @@ -25,3 +27,139 @@ export async function getAdrs(
);
return adrContent;
}

export async function getAllAdrs(localRepo: Repository): Promise<string[]> {
let adrContent: string[] = [];

// Read settings from task inputs
// ADRs from local repository
const adrsLocalFolderPath = tl.getInput("adrsLocalFolderPath", false) || "adrs";
const adrsLocalFileExtensions = tl.getInput("adrsLocalFileExtensions") || "";
const reviewWithLocalADRs = tl.getBoolInput("reviewWithLocalADRs", false);
const adrRemoteFolderPath = tl.getInput("adrsRemoteFolderPath", false) || "adrs";

// ADRs from remote repository
const adrRemoteFileExtensions = tl.getInput("adrsRemoteFileExtensions") || "";
const reviewWithRemoteADRs = tl.getBoolInput("reviewWithRemoteADRs", false);
const adrRemoteRepositoryUrl = tl.getInput("adrRemoteRepository", false) || "";

// ADRs from local DevOps Wiki
const reviewWithLocalWikiADRs = tl.getBoolInput("reviewWithLocalWikiADRs", false);
const adrsLocalWikiPath = tl.getInput("adrsLocalWikiPath", false) || "/";
const adrsLocalWikiToken = tl.getInput("adrsLocalWikiToken", false) || "";
const adrsLocalWikiId = tl.getInput("adrsLocalWikiId", false) || "";
const adrsLocalProjectId = tl.getInput("adrsLocalProjectId", false) || "";

// ADRs from remote DevOps Wiki
const adrsRemoteWikiUrl = tl.getInput("adrsRemoteWikiUrl", false) || "";
const reviewWithRemoteWikiADRs = tl.getBoolInput("reviewWithRemoteWikiADRs", false);
const adrsRemoteWikiPath = tl.getInput("adrsRemoteWikiPath", false) || "/";
const adrsRemoteWikiToken = tl.getInput("adrsRemoteWikiToken", false) || "";
const adrsRemoteWikiId = tl.getInput("adrsRemoteWikiId", false) || "";
const adrsRemoteProjectId = tl.getInput("adrsRemoteProjectId", false) || "";

// Get ADRs from local repository
if (reviewWithLocalADRs) {
const adrsExtensions = getArrayFromCSV(adrsLocalFileExtensions);
adrContent = await getAdrs(localRepo, adrsLocalFolderPath, adrsExtensions);
console.info(`Found ${adrContent.length} ADRs to use in the review.`);
}

if (reviewWithRemoteADRs) {
if (adrRemoteRepositoryUrl.trim() === "") {
tl.setResult(
tl.TaskResult.Failed,
"ADR Remote Repository URL must be provided when 'Review with Remote ADRs' is enabled."
);
return adrContent;
}

let remoteRepo = new Repository(undefined, adrRemoteRepositoryUrl);
try {
await remoteRepo.Clone();
const adrsExtensions = getArrayFromCSV(adrRemoteFileExtensions);
const remoteAdrs = await getAdrs(remoteRepo, adrRemoteFolderPath, adrsExtensions);
adrContent = [...adrContent, ...remoteAdrs];
console.info(`Found ${remoteAdrs.length} remote ADRs to use in the review.`);
} catch (e) {
tl.setResult(tl.TaskResult.Failed, `Failed to read ADRs from remote repository: ${e}`);
return adrContent;
}
}

if (reviewWithLocalWikiADRs) {
if (adrsLocalWikiToken.trim() === "") {
tl.setResult(
tl.TaskResult.Failed,
"ADR Local Wiki Token must be provided when 'Review with Local Wiki ADRs' is enabled."
);
return adrContent;
}
const options: DevOpsWikiOptions = {
token: adrsLocalWikiToken,
wikiId: adrsLocalWikiId && adrsLocalWikiId.trim().length > 0 ? adrsLocalWikiId : undefined,
projectId:
adrsLocalProjectId && adrsLocalProjectId.trim().length > 0 ? adrsLocalProjectId : undefined
};
const devOpsWikiService = new DevOpsWikiService(options);
try {
const wikiAdrsContent = await devOpsWikiService.getPages(`${adrsLocalWikiPath}`);
console.info(`Found ${wikiAdrsContent.length} ADR pages in the wiki.`);
adrContent = [...adrContent, ...wikiAdrsContent];
} catch (e) {
tl.setResult(tl.TaskResult.Failed, `Failed to read ADRs from DevOps Wiki: ${e}`);
return adrContent;
}
}

if (reviewWithRemoteWikiADRs) {
if (adrsRemoteWikiUrl.trim() === "") {
tl.setResult(
tl.TaskResult.Failed,
"ADR Remote Wiki URL must be provided when 'Review with Remote Wiki ADRs' is enabled."
);
return adrContent;
}

if (adrsRemoteWikiToken.trim() === "") {
tl.setResult(
tl.TaskResult.Failed,
"ADR Remote Wiki Token must be provided when 'Review with Remote Wiki ADRs' is enabled."
);
return adrContent;
}

if (adrsRemoteProjectId.trim() === "") {
tl.setResult(
tl.TaskResult.Failed,
"ADR Remote Project ID must be provided when 'Review with Remote Wiki ADRs' is enabled."
);
return adrContent;
}

const options: DevOpsWikiOptions = {
collectionUri: adrsRemoteWikiUrl,
token: adrsRemoteWikiToken,
wikiId: adrsRemoteWikiId,
projectId: adrsRemoteProjectId
};
const devOpsWikiService = new DevOpsWikiService(options);
try {
const wikiAdrsContent = await devOpsWikiService.getPages(`${adrsRemoteWikiPath}`);
console.info(`Found ${wikiAdrsContent.length} ADR pages in the remote wiki.`);
adrContent = [...adrContent, ...wikiAdrsContent];
} catch (e) {
tl.setResult(tl.TaskResult.Failed, `Failed to read ADRs from remote DevOps Wiki: ${e}`);
return adrContent;
}
}

return adrContent;
}

function getArrayFromCSV(csv: string) {
if (!csv.trim()) {
return [];
}
return csv.split(",");
}
4 changes: 3 additions & 1 deletion ai-code-review/chatCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class ChatCompletion {
adrsContent.length > 0
? `- Consider the following Architecture Decision Records (ADRs) in your review. \n
- Create a summary of each ADR and how it impacts the code changes, in table format.\n
- You ALWAYS have to provide an ADR review table even if there are no comments related to ADRs.\n
- You ALWAYS have to provide an ADR REVIEW TABLE even if there are no comments related to ADRs, without exceptions.\n
-ADRs review table example:\n
| ADR Name | Comments | Files diff related | ADR validation |
| --- | --- | --- | --- |
Expand All @@ -81,6 +81,8 @@ export class ChatCompletion {
If it is not possible to determine the relation between ADRs and code changes, respond with '⁉️ Unknown relation between ADRs and code changes.'\n
All rows should be related to one ADR only. Cant be related to multiple ADRs. But can have multiple comments related to same ADR in the same row.\n
A row cant be related to a unkown or none ADR. A row is always related to an ADR, and have an ADR name. The ADR name cant be empty, n/a or unkown\n
The ADR REVIEW TABLE is MANDATORY in every review, no matter what.\n
ALL ADRs provided must be included in the ADR REVIEW TABLE,excluding ONLY the templates used to create ADRs.\n



Expand Down
4 changes: 2 additions & 2 deletions ai-code-review/devOpsWikiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface DevOpsWikiOptions {
collectionUri?: string; // e.g. https://dev.azure.com/{organization}/
projectId?: string; // Team Project Id or name
wikiId?: string; // Wiki identifier (name or id). Defaults to repo name + ".wiki"
token?: string; // Personal access token or System.AccessToken
token: string; // Personal access token
}

export class DevOpsWikiService {
Expand All @@ -17,7 +17,7 @@ export class DevOpsWikiService {
private _httpsAgent: Agent;
private _headers: HeadersInit;

constructor(options?: DevOpsWikiOptions) {
constructor(options: DevOpsWikiOptions) {
this._collectionUri =
options?.collectionUri || tl.getVariable("System.TeamFoundationCollectionUri") || "";
this._projectId =
Expand Down
68 changes: 4 additions & 64 deletions ai-code-review/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { AzureOpenAI } from "openai";
import { ChatCompletion } from "./chatCompletion";
import { Repository } from "./repository";
import { PullRequest } from "./pullrequest";
import { getAdrs } from "./ADR/getAdrs";
import { DevOpsWikiService, DevOpsWikiOptions } from "./devOpsWikiService";
import { getAllAdrs } from "./ADR/getAdrs";
import "@azure/openai/types";

export class Main {
Expand Down Expand Up @@ -33,16 +32,7 @@ export class Main {
const deploymentName = tl.getInput("azureOpenAiDeploymentName", true)!;
const apiKey = tl.getInput("azureOpenAiApiKey", true)!;
const apiVersion = tl.getInput("azureOpenAiApiVersion", true)!;
const adrsLocalFolderPath = tl.getInput("adrsLocalFolderPath", false) || "adrs";
const adrsLocalFileExtensions = tl.getInput("adrsLocalFileExtensions") || "";
const reviewWithLocalADRs = tl.getBoolInput("reviewWithLocalADRs", false);
const adrRemoteFolderPath = tl.getInput("adrsRemoteFolderPath", false) || "adrs";
const adrRemoteFileExtensions = tl.getInput("adrsRemoteFileExtensions") || "";
const reviewWithRemoteADRs = tl.getBoolInput("reviewWithRemoteADRs", false);
const adrRemoteRepositoryUrl = tl.getInput("adrRemoteRepository", false) || "";
const reviewWithLocalWikiADRs = tl.getBoolInput("reviewWithLocalWikiADRs", false);
const adrsLocalWikiPath = tl.getInput("adrsLocalWikiPath", false) || "/";
const adrsLocalWikiToken = tl.getInput("adrsLocalWikiToken", false) || "";

const fileExtensions = tl.getInput("fileExtensions", false);
const filesToExclude = tl.getInput("fileExcludes", false);
const additionalPrompts = tl.getInput("additionalPrompts", false)?.split(",");
Expand Down Expand Up @@ -76,55 +66,12 @@ export class Main {
this._pullRequest = new PullRequest();
let filesToReview = await this._repository.GetChangedFiles(fileExtensions, filesToExclude);
console.info(`Found ${filesToReview.length} changed files to review.`);
let adrContent: string[] = [];
if (reviewWithLocalADRs) {
const adrsExtensions = this.getArrayFromCSV(adrsLocalFileExtensions);
adrContent = await getAdrs(this._repository, adrsLocalFolderPath, adrsExtensions);
console.info(`Found ${adrContent.length} ADRs to use in the review.`);
}
if (reviewWithRemoteADRs) {
if (adrRemoteRepositoryUrl.trim() === "") {
tl.setResult(
tl.TaskResult.Failed,
"ADR Remote Repository URL must be provided when 'Review with Remote ADRs' is enabled."
);
return;
}

let remoteRepo = new Repository(undefined, adrRemoteRepositoryUrl);
try {
await remoteRepo.Clone();
const adrsExtensions = this.getArrayFromCSV(adrRemoteFileExtensions);
const remoteAdrs = await getAdrs(remoteRepo, adrRemoteFolderPath, adrsExtensions);
adrContent = [...adrContent, ...remoteAdrs];
console.info(`Found ${remoteAdrs.length} remote ADRs to use in the review.`);
} catch (e) {
tl.setResult(tl.TaskResult.Failed, `Failed to read ADRs from remote repository: ${e}`);
return;
}
}

if (reviewWithLocalWikiADRs) {
const options: DevOpsWikiOptions = {
token:
adrsLocalWikiToken && adrsLocalWikiToken.trim().length > 0
? adrsLocalWikiToken
: undefined
};
const devOpsWikiService = new DevOpsWikiService(options);
try {
const wikiAdrsContent = await devOpsWikiService.getPages(`${adrsLocalWikiPath}`);
console.info(`Found ${wikiAdrsContent.length} ADR pages in the wiki.`);
adrContent = [...adrContent, ...wikiAdrsContent];
} catch (e) {
tl.setResult(tl.TaskResult.Failed, `Failed to read ADRs from DevOps Wiki: ${e}`);
return;
}
}
const adrsContent = await getAllAdrs(this._repository);

this._chatCompletion = new ChatCompletion(
client,
adrContent,
adrsContent,
tl.getBoolInput("reviewBugs", true),
tl.getBoolInput("reviewPerformance", true),
tl.getBoolInput("reviewBestPractices", true),
Expand Down Expand Up @@ -197,13 +144,6 @@ export class Main {
}
tl.setResult(tl.TaskResult.Succeeded, "Pull Request reviewed.");
}

static getArrayFromCSV(csv: string) {
if (!csv.trim()) {
return [];
}
return csv.split(",");
}
}

Main.Main();
4 changes: 2 additions & 2 deletions ai-code-review/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ai-code-review/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "swdevflow-code-review",
"version": "1.0.36",
"version": "1.0.39",
"description": "",
"main": "index.js",
"scripts": {
Expand Down
Loading