diff --git a/docs/architecture_report.md b/docs/architecture_report.md new file mode 100644 index 000000000..a2ede048f --- /dev/null +++ b/docs/architecture_report.md @@ -0,0 +1,65 @@ +# Báo cáo Kiến trúc: Tích hợp Plannotator vào MiMo-Code + +## 1. Tổng quan +Mục tiêu của dự án là tích hợp Plannotator vào MiMo-Code như một lớp đánh giá và lập kế hoạch có sự tham gia của con người (human-in-the-loop). Điều này cho phép người dùng xem xét, chú thích và phê duyệt các kế hoạch do MiMo-Code tạo ra trước khi thực thi, cũng như đánh giá mã nguồn sau khi triển khai. + +## 2. Phân tích hiện trạng + +### 2.1. MiMo-Code +MiMo-Code là một trợ lý lập trình AI chạy trên terminal, hỗ trợ nhiều agent (build, plan, compose). +- **Quy trình lập kế hoạch (Planning Pipeline):** MiMo-Code sử dụng agent `plan` và các skill trong `compose` (như `compose:plan`) để tạo ra các kế hoạch triển khai dưới dạng Markdown. Các kế hoạch này thường được lưu trong `docs/compose/plans/`. +- **Quy trình thực thi (Execution Pipeline):** Sau khi kế hoạch được tạo, agent `build` hoặc skill `compose:execute` / `compose:subagent` sẽ thực thi các bước trong kế hoạch. +- **Quy trình kiểm tra/xác minh (Verification Pipeline):** Các skill như `compose:review` và `compose:verify` được sử dụng để kiểm tra mã nguồn sau khi thực thi. + +### 2.2. Plannotator +Plannotator là một công cụ đánh giá dựa trên trình duyệt dành cho các AI coding agents. +- **Quy trình đánh giá kế hoạch:** Nhận đầu vào là nội dung Markdown của kế hoạch, hiển thị giao diện người dùng để chú thích và trả về quyết định (approve/deny) cùng với phản hồi. +- **Quy trình đánh giá mã nguồn:** Nhận đầu vào là git diff hoặc URL của Pull Request, hiển thị giao diện đánh giá mã nguồn và trả về phản hồi. +- **Điểm tích hợp:** Plannotator cung cấp CLI (ví dụ: `plannotator opencode-plan`, `plannotator opencode-review`) có thể được gọi thông qua các lệnh shell. + +## 3. Kiến trúc tích hợp đề xuất + +Để đáp ứng yêu cầu giảm thiểu sửa đổi kiến trúc lõi của MiMo-Code, chúng tôi đề xuất xây dựng một module tích hợp (adapter/wrapper) tại `packages/opencode/src/integrations/plannotator/`. + +### 3.1. Các thành phần chính + +1. **`exporter.ts`**: Chuyển đổi đầu ra kế hoạch của MiMo-Code thành định dạng Markdown tương thích với Plannotator. +2. **`importer.ts`**: Phân tích các chú thích và phản hồi từ Plannotator, chuyển đổi thành cấu trúc dữ liệu mà MiMo-Code có thể hiểu được. +3. **`review_client.ts`**: Wrapper để gọi CLI của Plannotator (sử dụng `ChildProcess` hoặc `CrossSpawnSpawner` của MiMo-Code). +4. **`hooks.ts`**: Cung cấp các điểm móc (hooks) `before_execution(plan)` và `after_implementation(diff)` để chèn vào quy trình làm việc của MiMo-Code. + +### 3.2. Quy trình làm việc (Workflow) + +```text +User Task + ↓ +MiMo Plan Generation (compose:plan) + ↓ +Plannotator Plan Review (hooks.before_execution) + ↓ +Approval / Revision (importer.ts xử lý phản hồi) + ↓ +MiMo Execution (compose:execute / build agent) + ↓ +Tests (compose:verify) + ↓ +Git Diff Generation (Git.diff) + ↓ +Plannotator Code Review (hooks.after_implementation) + ↓ +MiMo Revision Loop (nếu có phản hồi từ review) +``` + +## 4. Đánh giá rủi ro và độ phức tạp + +- **Rủi ro:** Việc gọi CLI của Plannotator có thể bị chặn (block) luồng thực thi của MiMo-Code nếu không được xử lý bất đồng bộ đúng cách. +- **Độ phức tạp:** Trung bình. Việc tạo module tích hợp khá độc lập, nhưng việc chèn hooks vào quy trình `compose` hiện tại đòi hỏi sự cẩn thận để không làm hỏng các luồng hiện có. +- **Giải pháp:** Sử dụng thư viện `effect` (đang được MiMo-Code sử dụng rộng rãi) để quản lý các tác vụ bất đồng bộ và lỗi khi gọi Plannotator CLI. + +## 5. Kế hoạch triển khai + +1. Tạo thư mục `packages/opencode/src/integrations/plannotator/`. +2. Triển khai các tệp `exporter.ts`, `importer.ts`, `review_client.ts`, và `hooks.ts`. +3. Cập nhật `packages/opencode/src/skill/compose/.bundle/execute/SKILL.md` hoặc tạo một workflow mới để tích hợp các hooks. +4. Xây dựng pipeline tạo bộ dữ liệu (Dataset Generation) lưu trữ tại `datasets/plannotator_feedback/`. +5. Viết tài liệu hướng dẫn và kiểm thử. diff --git a/docs/integration_architecture.d2 b/docs/integration_architecture.d2 new file mode 100644 index 000000000..40fef6042 --- /dev/null +++ b/docs/integration_architecture.d2 @@ -0,0 +1,46 @@ +direction: right + +User: "Người dùng" +MiMoCode: "MiMo-Code" +Plannotator: "Plannotator" +IntegrationModule: "Module tích hợp Plannotator" +Dataset: "Bộ dữ liệu phản hồi" + +User -> MiMoCode: "Yêu cầu tác vụ" + +subgraph MiMoCode { + MiMoPlanGeneration: "Sinh kế hoạch MiMo" + MiMoExecution: "Thực thi MiMo" + MiMoTests: "Kiểm thử MiMo" + MiMoGitDiff: "Sinh Git Diff" + MiMoRevisionLoop: "Vòng lặp sửa đổi MiMo" +} + +subgraph IntegrationModule { + Exporter: "exporter.ts" + ReviewClient: "review_client.ts" + Importer: "importer.ts" + Hooks: "hooks.ts" +} + +subgraph Plannotator { + PlannotatorPlanReview: "Đánh giá kế hoạch Plannotator" + PlannotatorCodeReview: "Đánh giá mã nguồn Plannotator" +} + +MiMoPlanGeneration -> Exporter: "Kế hoạch MiMo" +Exporter -> ReviewClient: "Kế hoạch đã xuất" +ReviewClient -> PlannotatorPlanReview: "Gửi kế hoạch" +PlannotatorPlanReview -> Importer: "Phản hồi Plannotator" +Importer -> Hooks: "Phản hồi đã nhập" +Hooks -> MiMoExecution: "Phê duyệt / Từ chối" + +MiMoExecution -> MiMoTests: "Mã đã thực thi" +MiMoTests -> MiMoGitDiff: "Kết quả kiểm thử" +MiMoGitDiff -> ReviewClient: "Git Diff" +ReviewClient -> PlannotatorCodeReview: "Gửi Git Diff" +PlannotatorCodeReview -> Importer: "Phản hồi đánh giá mã nguồn" +Importer -> MiMoRevisionLoop: "Phản hồi đã nhập" +MiMoRevisionLoop -> MiMoPlanGeneration: "Yêu cầu sửa đổi" + +Hooks -> Dataset: "Lưu phản hồi" diff --git a/docs/integration_architecture.png b/docs/integration_architecture.png new file mode 100644 index 000000000..405f53bcb Binary files /dev/null and b/docs/integration_architecture.png differ diff --git a/docs/plannotator_integration_guide.md b/docs/plannotator_integration_guide.md new file mode 100644 index 000000000..c68077070 --- /dev/null +++ b/docs/plannotator_integration_guide.md @@ -0,0 +1,43 @@ +# Hướng dẫn Tích hợp Plannotator vào MiMo-Code + +Tài liệu này hướng dẫn cách sử dụng và cấu trúc của module tích hợp Plannotator trong MiMo-Code. + +## 1. Cấu trúc thư mục +Module tích hợp nằm tại `packages/opencode/src/integrations/plannotator/`: +- `exporter.ts`: Chuyển đổi kế hoạch MiMo sang Markdown. +- `importer.ts`: Phân tích phản hồi từ Plannotator. +- `review_client.ts`: Giao tiếp với CLI Plannotator. +- `hooks.ts`: Các điểm móc cho quy trình lập kế hoạch và thực thi. +- `skill.ts`: Workflow cấp cao để sử dụng trong MiMo-Code. + +## 2. Cách thức hoạt động + +### 2.1. Đánh giá kế hoạch (Plan Review) +Khi một kế hoạch được tạo ra, nó sẽ được gửi đến Plannotator qua lệnh `opencode-plan`. Người dùng sẽ thấy một giao diện web để phê duyệt hoặc yêu cầu thay đổi. +- **Approve**: MiMo-Code tiếp tục thực thi. +- **Reject**: MiMo-Code nhận phản hồi và quay lại bước lập kế hoạch. + +### 2.2. Đánh giá mã nguồn (Code Review) +Sau khi mã nguồn được viết, MiMo-Code có thể gửi `git diff` đến Plannotator qua lệnh `opencode-review`. + +### 2.3. Lưu trữ dữ liệu (Dataset Generation) +Mọi tương tác đều được lưu lại tại `datasets/plannotator_feedback/` dưới dạng JSON để phục vụ việc huấn luyện mô hình trong tương lai. + +## 3. Cài đặt +Đảm bảo `plannotator` đã được cài đặt trong hệ thống và có thể truy cập từ dòng lệnh. +```bash +curl -fsSL https://plannotator.ai/install.sh | bash +``` + +## 4. Sử dụng trong Skill Compose +Bạn có thể gọi `PlannotatorWorkflow.reviewPlan` trong các skill như `compose:plan` để kích hoạt việc đánh giá. + +```typescript +import { PlannotatorWorkflow } from "../../integrations/plannotator/skill" + +// Trong logic lập kế hoạch +const result = yield* PlannotatorWorkflow.reviewPlan(planContent, taskTitle) +if (result.status === "approved") { + // Tiếp tục +} +``` diff --git a/packages/opencode/src/integrations/plannotator/README.md b/packages/opencode/src/integrations/plannotator/README.md new file mode 100644 index 000000000..eff207e73 --- /dev/null +++ b/packages/opencode/src/integrations/plannotator/README.md @@ -0,0 +1,20 @@ +# Plannotator Integration for MiMo-Code + +This module integrates [Plannotator](https://github.com/backnotprop/plannotator) into MiMo-Code as a human-in-the-loop review layer. + +## Components + +- `exporter.ts`: Converts MiMo plans to Plannotator-compatible Markdown. +- `importer.ts`: Parses Plannotator feedback and approval status. +- `review_client.ts`: Handles CLI communication with Plannotator. +- `hooks.ts`: Provides integration points for the planning and execution workflows. + +## Workflow + +1. **Plan Review**: Before executing a plan, it is sent to Plannotator for human approval. +2. **Code Review**: After implementation, the changes (git diff) are sent to Plannotator for review. +3. **Dataset Generation**: All reviews and feedback are stored in `datasets/plannotator_feedback/` for future model fine-tuning. + +## Usage + +The integration is designed to be used within MiMo-Code's `compose` skills or workflows. diff --git a/packages/opencode/src/integrations/plannotator/exporter.ts b/packages/opencode/src/integrations/plannotator/exporter.ts new file mode 100644 index 000000000..9c2204966 --- /dev/null +++ b/packages/opencode/src/integrations/plannotator/exporter.ts @@ -0,0 +1,16 @@ +import { Instance } from "../../project/instance" +import path from "path" + +/** + * Chuyển đổi kế hoạch của MiMo-Code sang định dạng Markdown tương thích với Plannotator. + */ +export function exportPlan(planContent: string, taskTitle: string): string { + const header = `# Task\n\n${taskTitle}\n\n# Plan\n\n` + + // Kiểm tra nếu kế hoạch đã có tiêu đề hoặc định dạng Markdown phù hợp + if (planContent.trim().startsWith("#")) { + return `${header}${planContent}` + } + + return `${header}${planContent}` +} diff --git a/packages/opencode/src/integrations/plannotator/hooks.ts b/packages/opencode/src/integrations/plannotator/hooks.ts new file mode 100644 index 000000000..449fc92ef --- /dev/null +++ b/packages/opencode/src/integrations/plannotator/hooks.ts @@ -0,0 +1,52 @@ +import { Effect } from "effect" +import { makeReviewClient } from "./review_client" +import { exportPlan } from "./exporter" +import { importFeedback, PlannotatorFeedback } from "./importer" +import { AppFileSystem } from "../../file" +import path from "path" + +export const PlannotatorHooks = Effect.gen(function* () { + const client = yield* makeReviewClient + const fsys = yield* AppFileSystem.Service + + const saveToDataset = (data: any) => Effect.gen(function* () { + const datasetDir = "datasets/plannotator_feedback" + yield* fsys.mkdir(datasetDir, { recursive: true }) + const filename = `${Date.now()}.json` + yield* fsys.write(path.join(datasetDir, filename), JSON.stringify(data, null, 2)) + }) + + return { + beforeExecution: (plan: string, taskTitle: string) => + Effect.gen(function* () { + const exportedPlan = exportPlan(plan, taskTitle) + const output = yield* client.submitPlan(exportedPlan) + const feedback = importFeedback(output) + + yield* saveToDataset({ + type: "plan_review", + task: taskTitle, + original_plan: plan, + review_comments: feedback.comments.join("\n"), + approval: feedback.approval_status === "approved" + }) + + return feedback + }), + + afterImplementation: (diff: string, description: string) => + Effect.gen(function* () { + const output = yield* client.submitDiff(diff, description) + const feedback = importFeedback(output) + + yield* saveToDataset({ + type: "code_review", + code_diff: diff, + review_comments: feedback.comments.join("\n"), + approval: feedback.approval_status === "approved" + }) + + return feedback + }) + } +}) diff --git a/packages/opencode/src/integrations/plannotator/importer.ts b/packages/opencode/src/integrations/plannotator/importer.ts new file mode 100644 index 000000000..4b8df52fc --- /dev/null +++ b/packages/opencode/src/integrations/plannotator/importer.ts @@ -0,0 +1,33 @@ +/** + * Phân tích phản hồi từ Plannotator. + */ +export interface PlannotatorFeedback { + comments: string[] + changes_requested: string[] + approval_status: "approved" | "rejected" | "dismissed" +} + +export function importFeedback(plannotatorOutput: any): PlannotatorFeedback { + const decision = plannotatorOutput.decision + const feedback = plannotatorOutput.feedback || "" + + const result: PlannotatorFeedback = { + comments: [], + changes_requested: [], + approval_status: "dismissed" + } + + if (decision === "submitted") { + result.approval_status = plannotatorOutput.result?.approved ? "approved" : "rejected" + if (feedback) { + result.comments.push(feedback) + if (result.approval_status === "rejected") { + result.changes_requested.push(feedback) + } + } + } else if (decision === "dismissed") { + result.approval_status = "dismissed" + } + + return result +} diff --git a/packages/opencode/src/integrations/plannotator/review_client.ts b/packages/opencode/src/integrations/plannotator/review_client.ts new file mode 100644 index 000000000..5d02349fc --- /dev/null +++ b/packages/opencode/src/integrations/plannotator/review_client.ts @@ -0,0 +1,78 @@ +import { Effect } from "effect" +import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" +import { Stream } from "effect" +import { tmpdir } from "node:os" +import path from "node:path" +import { randomUUID } from "node:crypto" +import { AppFileSystem } from "../../file" + +export interface ReviewClient { + submitPlan(plan: string): Effect.Effect + submitDiff(diff: string, description: string): Effect.Effect +} + +export const makeReviewClient = Effect.gen(function* () { + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner + const fsys = yield* AppFileSystem.Service + + const runPlannotator = (mode: string, input: any) => + Effect.gen(function* () { + const plannotatorBin = process.env.PLANNOTATOR_BIN || "plannotator" + const readyFile = path.join( + tmpdir(), + `plannotator-mimo-${process.pid}-${Date.now()}-${randomUUID()}.jsonl` + ) + + const env = { + ...process.env, + PLANNOTATOR_ORIGIN: "mimo-code", + PLANNOTATOR_READY_FILE: readyFile, + } + + const proc = ChildProcess.make(plannotatorBin, [mode], { + stdin: "pipe", + stdout: "pipe", + stderr: "pipe", + env, + }) + + const handle = yield* spawner.spawn(proc) + + // Gửi input qua stdin + const inputStr = JSON.stringify(input) + yield* Stream.fromIterable([Buffer.from(inputStr)]).pipe( + Stream.run(handle.stdin) + ) + + const [stdout, stderr] = yield* Effect.all( + [ + Stream.mkString(Stream.decodeText(handle.stdout)), + Stream.mkString(Stream.decodeText(handle.stderr)) + ], + { concurrency: 2 } + ) + + const exitCode = yield* handle.exitCode + + // Dọn dẹp ready file + if (yield* fsys.existsSafe(readyFile)) { + yield* fsys.remove(readyFile).pipe(Effect.ignore) + } + + if (exitCode !== 0) { + return yield* Effect.fail(new Error(`Plannotator failed (exit ${exitCode}): ${stderr}`)) + } + + try { + return JSON.parse(stdout) + } catch (e) { + return yield* Effect.fail(new Error(`Failed to parse Plannotator output: ${stdout}`)) + } + }).pipe(Effect.scoped) + + return { + submitPlan: (plan: string) => runPlannotator("opencode-plan", { plan }), + submitDiff: (diff: string, description: string) => + runPlannotator("opencode-review", { arguments: "--git", description, diff }), + } +}) diff --git a/packages/opencode/src/integrations/plannotator/skill.ts b/packages/opencode/src/integrations/plannotator/skill.ts new file mode 100644 index 000000000..a1037b0e7 --- /dev/null +++ b/packages/opencode/src/integrations/plannotator/skill.ts @@ -0,0 +1,38 @@ +import { Effect } from "effect" +import { PlannotatorHooks } from "./hooks" +import { Question } from "../../question" + +/** + * Tích hợp Plannotator vào quy trình compose của MiMo-Code. + */ +export const PlannotatorWorkflow = Effect.gen(function* () { + const hooks = yield* PlannotatorHooks + const question = yield* Question.Service + + return { + /** + * Chèn bước review kế hoạch vào quy trình. + */ + reviewPlan: (plan: string, taskTitle: string) => + Effect.gen(function* () { + const feedback = yield* hooks.beforeExecution(plan, taskTitle) + + if (feedback.approval_status === "approved") { + return { status: "approved", feedback } + } else if (feedback.approval_status === "rejected") { + return { status: "rejected", feedback } + } else { + return { status: "dismissed", feedback } + } + }), + + /** + * Chèn bước review mã nguồn vào quy trình. + */ + reviewCode: (diff: string, description: string) => + Effect.gen(function* () { + const feedback = yield* hooks.afterImplementation(diff, description) + return feedback + }) + } +})