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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,6 @@ vite.config.ts.timestamp-*

# MCP
.mcp.json

# AIに渡すプロンプトファイルを置く場所
user-prompt
2,187 changes: 2,187 additions & 0 deletions design-docs-for-ai/issue3-codex-review-loop-implementation-plan.md

Large diffs are not rendered by default.

32 changes: 30 additions & 2 deletions package-lock.json

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

6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,11 @@
"typescript": "6.0.2",
"ultracite": "7.3.2",
"vitest": "4.1.2"
},
"dependencies": {
"commander": "14.0.3",
"picocolors": "1.1.1",
"yaml": "2.8.3",
"zod": "4.3.6"
}
}
3 changes: 3 additions & 0 deletions src/__fixtures__/codex-output-malformed.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"type": "message", "role": "assistant", "content": "Starting review..."}
{this is not valid json}
{"type": "message", "role": "assistant", "content": "Here are my findings: some unstructured text"}
4 changes: 4 additions & 0 deletions src/__fixtures__/codex-output-valid.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{"type": "message", "role": "assistant", "content": "レビューを開始します。"}
{"type": "command_execution", "command": "gh issue view 3 --repo nekochans/planloop", "exit_code": 0}
{"type": "mcp_tool_call", "tool": "context7_query", "input": {"query": "commander.js"}}
{"type": "message", "role": "assistant", "content": "{\"findings\":[{\"id\":\"finding-1\",\"summary\":\"API endpoint path mismatch\",\"detail\":\"The plan references /api/v2/users but the spec defines /api/v1/users.\",\"severity\":\"high\",\"category\":\"correctness\"},{\"id\":\"finding-2\",\"summary\":\"Missing error handling\",\"detail\":\"Network timeout scenarios are not covered.\",\"severity\":\"medium\",\"category\":\"spec_mismatch\"}]}"}
57 changes: 57 additions & 0 deletions src/__fixtures__/sample-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { PlanloopConfig } from "../types/index.js";

type DeepPartial<T> = {
[K in keyof T]?: T[K] extends Array<infer _U>
? T[K]
: T[K] extends object
? DeepPartial<T[K]>
: T[K];
};

const defaults: PlanloopConfig = {
version: 1,
paths: {
reviewDir: "design-docs-for-ai",
runDir: ".planloop/runs",
},
policy: {
requireHumanOnFirstRound: true,
requireHumanOnNewHighSeverity: true,
maxRounds: 8,
stagnationRounds: 2,
blockingCategories: [
"correctness",
"spec_mismatch",
"missing_acceptance_criteria",
"migration_risk",
],
autoWaiveCategories: ["speculative_future", "unnecessary_fallback"],
},
review: {
perspectives: [
"correctness: 実装計画の内容が要件と一致しているか",
"spec_mismatch: 仕様との不一致がないか",
"missing_acceptance_criteria: 受け入れ基準の漏れがないか",
"migration_risk: マイグレーションリスクがないか",
"security: セキュリティ上の懸念がないか",
"performance: パフォーマンス上の懸念がないか",
],
},
engines: {
claude: {
mode: "inherited",
},
},
};

export const createSampleConfig = (
overrides: DeepPartial<PlanloopConfig> = {}
): PlanloopConfig => ({
version: 1,
paths: { ...defaults.paths, ...overrides.paths },
policy: { ...defaults.policy, ...overrides.policy },
review: { ...defaults.review, ...overrides.review },
engines: {
claude: { ...defaults.engines.claude, ...overrides.engines?.claude },
},
});
48 changes: 48 additions & 0 deletions src/__fixtures__/sample-findings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Finding, RawFinding } from "../types/index.js";

export const createRawFinding = (
overrides: Partial<RawFinding> = {}
): RawFinding => ({
id: "finding-1",
summary: "Test finding summary",
detail: "Test finding detail",
severity: "high",
category: "correctness",
...overrides,
});

export const createFinding = (overrides: Partial<Finding> = {}): Finding => ({
id: "finding-1",
summary: "Test finding summary",
detail: "Test finding detail",
severity: "high",
category: "correctness",
fingerprint: "abc123",
...overrides,
});

export const sampleFindings: RawFinding[] = [
createRawFinding({
id: "finding-1",
summary: "API endpoint path mismatch",
detail:
"The plan references /api/v2/users but the spec defines /api/v1/users.",
severity: "high",
category: "correctness",
}),
createRawFinding({
id: "finding-2",
summary: "Missing error handling for network timeout",
detail:
"The error handling strategy doesn't cover network timeout scenarios.",
severity: "medium",
category: "spec_mismatch",
}),
createRawFinding({
id: "finding-3",
summary: "Consider adding pagination support",
detail: "Consider adding pagination support for future scaling needs.",
severity: "low",
category: "speculative_future",
}),
];
15 changes: 15 additions & 0 deletions src/__fixtures__/sample-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# サンプル実装計画

## 概要

このドキュメントはテスト用のサンプル実装計画です。

## 実装内容

1. APIエンドポイント `/api/v1/users` の実装
2. エラーハンドリングの追加
3. テストコードの作成

## 関連Issue

https://github.com/nekochans/planloop/issues/3
11 changes: 11 additions & 0 deletions src/__fixtures__/sample-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# サンプルプロンプト

## 要件

ユーザー管理APIの実装計画を作成してください。

## 制約

- REST APIとして実装
- 認証にはJWTを使用
- エラーレスポンスはRFC 7807準拠
34 changes: 34 additions & 0 deletions src/adapters/claude-cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, expect, it } from "vitest";
import { extractRevisionResult } from "./claude-cli.js";

describe("extractRevisionResult", () => {
it("should parse result from stream-json output", () => {
const output = `{"type":"message","role":"assistant","content":"Analyzing..."}
{"type":"tool_use","tool":"Read","path":"plan.md"}
{"type":"result","result":"{\\"reflectedFindings\\":[\\"finding-1\\",\\"finding-2\\"],\\"summary\\":\\"APIパスを修正しました\\"}"}`;

const result = extractRevisionResult(output);
expect(result.reflectedFindings).toEqual(["finding-1", "finding-2"]);
expect(result.summary).toBe("APIパスを修正しました");
});

it("should parse direct JSON result", () => {
const output = `{"reflectedFindings":["finding-1"],"summary":"修正完了"}`;
const result = extractRevisionResult(output);
expect(result.reflectedFindings).toEqual(["finding-1"]);
expect(result.summary).toBe("修正完了");
});

it("should return empty result for unparseable output", () => {
const output = "Some non-JSON text\nAnother line";
const result = extractRevisionResult(output);
expect(result.reflectedFindings).toEqual([]);
expect(result.summary).toBe("");
});

it("should handle empty output", () => {
const result = extractRevisionResult("");
expect(result.reflectedFindings).toEqual([]);
expect(result.summary).toBe("");
});
});
Loading