← back to korosuke613__zenn-articles

Function bodies 11 total

All specs Real LLM only Function bodies
AiReviewer class · typescript · L26-L395 (370 LOC)
tools/libs/AiReviewer.ts
export class AiReviewer {
  private readonly openai: OpenAI;
  private static readonly SYSTEM_PROMPT = `
あなたは日本語技術記事を校正するアシスタントです。
与えられたマークダウン形式の文章で、誤字・脱字、文法誤り、論理的・技術的な誤りのある行を抜き出し、修正した行を出力してください。
その際、確実に修正すべき誤りのみを出力してください。
また、以下のルールに従って修正を行ってください。
- 句読点の追加や削除はしない
- 英単語のキャピタライゼーションはしない
- 半角・全角の違いは修正しない
- 見出しの誤字脱字は修正しない
- 伸ばし棒の有無は修正しない
- スペースの有無は修正しない
- リストのインデントは修正しない
- セルフホストランナーは修正しない
- 箇条書きの場合、文の終わりに句読点は不要
- 文体の統一は指摘しないで良い
- 読点の修正はしない
- '_本項の執筆者: [@ajfAfg](https://zenn.dev/arjef)_' に誤りはない
- 'GPT 4o' は誤字ではない

出力は、$review_num$個以下とし、より優先的に修正すべきものを出力してください。
`;
  private static readonly pricing: Record<string, OpenAIPricingData> = {
    // ref: https://openai.com/api/pricing/
    "gpt-4o": {
      input: 2.5 / (1 * 1000000),
      output: 10 / (1 * 1000000),
    },
    "o1-mini": {
      input: 3 / (1 * 1000000),
      output: 12 / (1 * 1000000),
    },
    "o1-preview": {
      input: 15 / (1 * 1000000),
      output: 60 / (1 * 1000000),
    },
    "o1": {
      input: 15 / (1 * 1000000)
constructor method · typescript · L80-L117 (38 LOC)
tools/libs/AiReviewer.ts
  constructor(openai: OpenAI, options?: Partial<AiReviewerOptions>) {
    this.openai = openai;
    this.options = { ...AiReviewer.defaultOptions, ...options };
    const formatter = (record: LogRecord) =>
      `[${record.datetime.toISOString()} ${record.levelName}] ${record.msg}`;

    const logSetting: log.LogConfig = {
      handlers: {
        console: new log.ConsoleHandler("DEBUG", {
          formatter,
        }),
      },
      loggers: {
        default: {
          level: AiReviewer.getLogLevel(Deno.env.get("LOG_LEVEL")),
          handlers: ["console"],
        },
      },
    };

    if (
      this.options.logFilePath !== undefined && logSetting.handlers &&
      logSetting.loggers
    ) {
      logSetting.handlers["file"] = new log.FileHandler("DEBUG", {
        filename: this.options.logFilePath,
        formatter,
        mode: "w",
      });
      logSetting.loggers.default.handlers = ["console", "file"];
    }

    log.setup(logSetting);

    if (this.options.loggin
Git class · typescript · L1-L99 (99 LOC)
tools/libs/Git.ts
export class Git {
  async diffExecutor(
    filePath: string,
    baseRef: string,
    targetRef: string,
  ): Promise<string> {
    const cmd = new Deno.Command("git", {
      args: [
        "--no-pager",
        "diff",
        "--unified=0",
        `${baseRef}...${targetRef}`,
        filePath,
      ],
      stdout: "piped",
      stderr: "piped",
    });

    const { code, stdout, stderr } = await cmd.output();
    if (code !== 0) {
      const errorString = new TextDecoder().decode(stderr);
      throw new Error(`git: ${code}, ${errorString}`);
    }

    return new TextDecoder().decode(stdout);
  }

  async getDiff(
    filePath: string,
    baseRef: string,
    targetRef: string,
    diffExecutor: (
      filePath: string,
      baseRef: string,
      targetRef: string,
    ) => Promise<string> = this.diffExecutor,
  ) {
    const output = await diffExecutor(filePath, baseRef, targetRef);

    // remove the first 5 lines
    const removedHeader = output.split("\n").slice(5);
diffExecutor method · typescript · L2-L26 (25 LOC)
tools/libs/Git.ts
  async diffExecutor(
    filePath: string,
    baseRef: string,
    targetRef: string,
  ): Promise<string> {
    const cmd = new Deno.Command("git", {
      args: [
        "--no-pager",
        "diff",
        "--unified=0",
        `${baseRef}...${targetRef}`,
        filePath,
      ],
      stdout: "piped",
      stderr: "piped",
    });

    const { code, stdout, stderr } = await cmd.output();
    if (code !== 0) {
      const errorString = new TextDecoder().decode(stderr);
      throw new Error(`git: ${code}, ${errorString}`);
    }

    return new TextDecoder().decode(stdout);
  }
getDiff method · typescript · L28-L54 (27 LOC)
tools/libs/Git.ts
  async getDiff(
    filePath: string,
    baseRef: string,
    targetRef: string,
    diffExecutor: (
      filePath: string,
      baseRef: string,
      targetRef: string,
    ) => Promise<string> = this.diffExecutor,
  ) {
    const output = await diffExecutor(filePath, baseRef, targetRef);

    // remove the first 5 lines
    const removedHeader = output.split("\n").slice(5);

    // extract lines starting with '+'
    const filteredAddedLines = removedHeader.filter((line) =>
      line.startsWith("+")
    );

    // remove the first character '+'
    const removedPlus = filteredAddedLines.map((line) => line.slice(1)).join(
      "\n",
    );

    return removedPlus;
  }
diffNameOnlyExecutor method · typescript · L56-L78 (23 LOC)
tools/libs/Git.ts
  async diffNameOnlyExecutor(
    baseRef: string,
    targetRef: string,
  ): Promise<string> {
    const cmd = new Deno.Command("git", {
      args: [
        "--no-pager",
        "diff",
        "--name-only",
        `${baseRef}...${targetRef}`,
      ],
      stdout: "piped",
      stderr: "piped",
    });

    const { code, stdout, stderr } = await cmd.output();
    if (code !== 0) {
      const errorString = new TextDecoder().decode(stderr);
      throw new Error(`git: ${code}, ${errorString}`);
    }

    return new TextDecoder().decode(stdout);
  }
getDiffNameOnly method · typescript · L80-L98 (19 LOC)
tools/libs/Git.ts
  async getDiffNameOnly(
    baseRef: string,
    targetRef: string,
    diffNameOnlyExecutor: (
      baseRef: string,
      targetRef: string,
    ) => Promise<string> = this.diffNameOnlyExecutor,
  ): Promise<string | undefined> {
    const output = await diffNameOnlyExecutor(baseRef, targetRef);
    const gitDiffFiles = output.split("\n").filter((f) => f !== "");

    // Weekly の変更は複数個無いという前提
    const diffWeekly: string | undefined =
      gitDiffFiles.filter((a) =>
        a.endsWith(".md") && a.includes("productivity-weekly")
      )[0];

    return diffWeekly;
  }
Same scanner, your repo: https://repobility.com — Repobility
TocBuilder class · typescript · L13-L112 (100 LOC)
tools/libs/Toc.ts
export class TocBuilder {
  toc: Toc = {
    "news 📺": [],
    "know-how 🎓": [],
    "tool 🔨": [],
    other: [],
  };

  /**
   * マークダウンドキュメントから見出しを登録します。
   * 見出しの深さが1の場合は、主要カテゴリとして扱い、その他の場合は "other" として扱います。
   * 見出しの深さが2の場合は、現在のカテゴリに見出しを追加します。
   * @param tokens マークダウンドキュメントのトークンリスト
   */
  registerHeadings(tokens: Token[]) {
    const headings: Tokens.Heading[] = tokens.filter((token) =>
      token.type === "heading" && token.depth !== undefined &&
      token.text !== undefined
    ) as Tokens.Heading[];

    let nowCategory: Category = "other";

    headings.forEach((heading) => {
      if (heading.depth === 1) {
        // 主要カテゴリじゃないものは other に入れる
        if (!isCategory(heading.text)) {
          nowCategory = "other";
          return;
        }
        nowCategory = heading.text as Category;
        this.toc[nowCategory] = [];
      }
      if (heading.depth === 2) {
        this.toc[nowCategory].push(heading.text);
      }
    });
  }

  /**
   * マークダウンドキュメントからリストを登録します。
 
registerHeadings method · typescript · L27-L49 (23 LOC)
tools/libs/Toc.ts
  registerHeadings(tokens: Token[]) {
    const headings: Tokens.Heading[] = tokens.filter((token) =>
      token.type === "heading" && token.depth !== undefined &&
      token.text !== undefined
    ) as Tokens.Heading[];

    let nowCategory: Category = "other";

    headings.forEach((heading) => {
      if (heading.depth === 1) {
        // 主要カテゴリじゃないものは other に入れる
        if (!isCategory(heading.text)) {
          nowCategory = "other";
          return;
        }
        nowCategory = heading.text as Category;
        this.toc[nowCategory] = [];
      }
      if (heading.depth === 2) {
        this.toc[nowCategory].push(heading.text);
      }
    });
  }
registerLists method · typescript · L56-L95 (40 LOC)
tools/libs/Toc.ts
  registerLists(tokens: Token[]) {
    const lists: Tokens.List[] = tokens.filter((token) =>
      token.type === "list" &&
      token.items !== undefined &&
      token.ordered !== undefined &&
      token.start !== undefined &&
      token.raw !== undefined &&
      token.loose !== undefined
    ) as Tokens.List[];

    if (lists.length === 0) return;

    const readMoreList = lists.filter((list) => isCategory(list.raw))[0].raw;
    const readMoreTitleRegExp = new RegExp(/^\s\s-\s\[(.*)\]/);
    const readMoreArray = readMoreList.split("\n");

    const getCategoryFromString = (str: string): Category => {
      const result = Category.filter((category) => str.includes(category));
      if (result.length > 0) {
        return result[0];
      }
      return "other";
    };

    let nowCategory: Category = "other";
    readMoreArray.forEach((item) => {
      if (isCategory(item)) {
        const category = getCategoryFromString(item);
        nowCategory = category;
        return;
  
build method · typescript · L102-L111 (10 LOC)
tools/libs/Toc.ts
  build() {
    const tocList = Object.entries(this.toc).map(([category, headings]) => {
      const categoryList = headings.map((heading) => {
        return `    - ${heading}`;
      });
      return [`- ${category}`, ...categoryList].join("\n");
    }).join("\n");

    return tocList;
  }