UNPKG

autoheal

Version:

GPT Test driven development. Automatically fix tests and guide GPT to write and fix code using your tests.

264 lines (236 loc) • 7.03 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import chalk from "chalk"; import { prompt } from "./prompt.js"; import { readFileSync, writeFileSync } from "fs"; export function healFile(filePath, otherFiles, testDetails, hint, model) { return __awaiter(this, void 0, void 0, function* () { const fileContent = readFileSync(filePath, { encoding: "utf-8" }); try { const rawFile = yield prompt([ ...promptMessages, { role: "user", content: [ hint ? `${hint}\n\n` : "", otherFiles.length ? `Other files fixed:\n\`\`\`\n${otherFiles.join("\n")}\n\`\`\`\n\n` : "", `Test results:\n\`\`\`\n${testDetails}\n\`\`\`\`\n\n`, `File to fix: ${filePath}\n\`\`\`\n${fileContent}\n\`\`\``, ].join(""), }, ], model); if (!rawFile) { console.log(chalk.red("failed to heal, raw text", rawFile)); return { filePath, healDescription: "Unable to heal file", }; } const [healDescription, newFileRaw] = rawFile.split("```"); let newFile = newFileRaw.replace("\n", ""); // Replace first newline newFile = removeMarkdown(newFile); if (newFile) { writeFileSync(filePath, newFile || ""); } return { filePath, healDescription, }; } catch (e) { console.log(chalk.red(e.message)); return { filePath, healDescription: "Unable to heal file", }; } }); } /** * Remove markdown syntax highlighting */ function removeMarkdown(rawFile) { const fileTypes = [ "jsx", "tsx", "typescript", "javascript", "ts", "php", "python", "ruby", "json", "js", ]; // Remove the first set of characters that match a file type for (const type of fileTypes) { if (rawFile.startsWith(type)) { return rawFile.replace(type, ""); } } return rawFile; } const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const promptMessages = [ { role: "system", content: "You are an expert typescript programming assistant.", }, { role: "user", content: "I will give you the results of a unit test run and a suspected file" + " that may be contributing to the failed test. The file may only be contributing" + " to some or none of the failing tests which you will take into account when fixing the file. " + "You may be given a list of other files already fixed by other agents, take this into account when deciding whether to fix the file or not. " + "Add a comment explaining each change in the fixed file prefixed with @autoheal" + "You will only reply with a very brief" + " description of the possible issue (in past tense) and the content of the fixed file between the triple backticks." + "The fixed file should conform to style and format of the file and should try to ac.", }, { role: "assistant", content: "Acknowledged.", }, { role: "user", content: `Test results: \`\`\` Order.test.ts: 7 | test("Order can add products", () => { 8 | const order = new Order(); 9 | 10 | expect(order.getLineItems()).toStrictEqual([]); 11 | order.addLineItem(testLineItem); 12 | expect(order.getLineItems()).toStrictEqual([testLineItem]); ^ error: expect(received).toStrictEqual(expected) + [] - [ - { - "id": 1, - "product": { - "id": 1, - "name": "Product 1", - "price": 10 - }, - "quantity": 1 - } - ] - Expected - 11 + Received + 1 at /Users/dion/Projects/codeai/Order.test.ts:12:2 āœ— Order can add products 0 pass 1 fail 2 expect() calls Ran 1 tests across 1 files [24.00ms] \`\`\` File: ./src/Order.ts \`\`\` export class Customer {} export class Product { id: number; name: string; price: number; constructor(id: number, name: string, price: number) { this.id = id; this.name = name; this.price = price; } } export class LineItem { id?: number; product: Product; quantity: number; constructor(id: number, product: Product, quantity: number) { this.id = id; this.product = product; this.quantity = quantity; } } export class Order { id?: number; customer?: Customer; private lineItems: LineItem[] = []; constructor(id?: number) { this.id = id; } async load() {} setLineItems(lineItems: LineItem[]) { this.lineItems = lineItems; } addLineItem(lineItem: LineItem) {} getLineItems() { return this.lineItems; } getTotal() { return this.lineItems.reduce((total, lineItem) => { return total + lineItem.product?.price || 0; }, 0); } } \`\`\` `, }, { role: "assistant", content: `Fixed \`Order.addLineItem\` implementation \`\`\` export class Customer {} export class Product { id: number; name: string; price: number; constructor(id: number, name: string, price: number) { this.id = id; this.name = name; this.price = price; } } export class LineItem { id?: number; product: Product; quantity: number; constructor(id: number, product: Product, quantity: number) { this.id = id; this.product = product; this.quantity = quantity; } } export class Order { id?: number; customer?: Customer; private lineItems: LineItem[] = []; constructor(id?: number) { this.id = id; } async load() {} setLineItems(lineItems: LineItem[]) { this.lineItems = lineItems; } /* @autoheal Fixed \`Order.addLineItem\` implementation */ addLineItem(lineItem: LineItem) { this.lineItems.push(lineItem); } getLineItems() { return this.lineItems; } getTotal() { return this.lineItems.reduce((total, lineItem) => { return total + (lineItem.product?.price || 0) * lineItem.quantity; }, 0); } } \`\`\` `, }, ];