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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
bin/
tmp/
prism
/prism
CLAUDE.md
.claude/
38 changes: 32 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ prism prompt <PR_URL> --template my.tmpl # Custom template
# Debug
prism fetch <PR_URL> --format json # Raw PR data
prism fetch <PR_URL> --format text # Raw PR data as text

# Provider selection (available on all commands)
prism analyze <PR_URL> --provider github # Explicit provider
```

</details>
Expand All @@ -226,12 +229,35 @@ prism fetch <PR_URL> --format text # Raw PR data as text

---

## Supported Providers
## Providers

### Supported providers

| Provider | Type | Status |
|----------|------|--------|
| GitHub | Built-in | Supported |
| AWS CodeCommit | Plugin | In progress (v0.2.0) |

### Provider selection

By default, prism auto-detects the provider from the PR URL. Use `--provider` to specify explicitly:

```bash
prism analyze https://github.com/owner/repo/pull/123 # auto-detected as GitHub
prism analyze <PR_URL> --provider github # explicit GitHub
prism analyze <PR_URL> --provider codecommit # explicit CodeCommit (requires plugin)
```

### Plugin providers

External providers are distributed as separate binaries named `prism-provider-<name>` and discovered on PATH. Plugins receive a PR URL and return structured JSON to stdout.

```bash
# Plugin invocation (called by prism internally):
prism-provider-codecommit fetch <PR_URL>
```

| Provider | Status |
|----------|--------|
| GitHub | Supported |
| AWS CodeCommit | Planned (v0.2.0) |
See [ADR-0001](docs/adr/0001-provider-plugin-architecture.md) for design details.

---

Expand Down Expand Up @@ -324,7 +350,7 @@ make clean # Remove bin/
## Roadmap

- **v0.1.0** — GitHub provider, analyze/prompt/fetch commands, JSON/Markdown/text output, light/detailed/cross modes, config/lang/template support, exit codes
- **v0.2.0** — AWS CodeCommit provider
- **v0.2.0** — Provider plugin architecture, `--provider` flag, AWS CodeCommit provider
- **v0.3.0** — Policy files, custom review axes, project-specific rules
- **v0.4.0+** — Review policy as code, SARIF output, metrics, IDE/CI integration

Expand Down
16 changes: 14 additions & 2 deletions cmd/prism/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,20 @@ func TestCLIAnalyzeInvalidURL(t *testing.T) {
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "invalid PR URL") {
t.Errorf("error = %q, want 'invalid PR URL'", err.Error())
if !strings.Contains(err.Error(), "cannot auto-detect provider") {
t.Errorf("error = %q, want 'cannot auto-detect provider'", err.Error())
}
}

func TestCLIAnalyzeWithUnknownProvider(t *testing.T) {
cmd := rootCmd()
cmd.SetArgs([]string{"analyze", "https://example.com/pr/1", "--provider", "nonexistent"})
err := cmd.Execute()
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "not found") {
t.Errorf("error = %q, want 'not found'", err.Error())
}
}

Expand Down
40 changes: 24 additions & 16 deletions cmd/prism/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (

"github.com/hidetzu/prism/internal/config"
"github.com/hidetzu/prism/internal/domain"
ghprovider "github.com/hidetzu/prism/internal/provider/github"
"github.com/hidetzu/prism/internal/provider"
"github.com/hidetzu/prism/internal/usecase"
)

const version = "0.1.0-dev"
const version = "0.2.0-alpha.1"

// Exit codes as defined in docs/spec.md.
const (
Expand Down Expand Up @@ -53,6 +53,8 @@ func rootCmd() *cobra.Command {
SilenceErrors: true,
}

cmd.PersistentFlags().String("provider", "", "Provider name (e.g. github, codecommit); auto-detected from URL if omitted")

cmd.AddCommand(
analyzeCmd(),
promptCmd(),
Expand Down Expand Up @@ -107,6 +109,20 @@ func fetchCmd() *cobra.Command {
return cmd
}

func resolveProvider(cmd *cobra.Command, cfg config.Config, prURL string) (provider.Provider, domain.PRRef, error) {
providerName, _ := cmd.Flags().GetString("provider")
reg := provider.NewRegistry(cfg.GitHubToken)
p, err := reg.Resolve(providerName, prURL)
if err != nil {
return nil, domain.PRRef{}, fmt.Errorf("%w: %v", domain.ErrInvalidArgs, err)
}
ref, err := p.Parse(prURL)
if err != nil {
return nil, domain.PRRef{}, fmt.Errorf("%w: invalid PR URL: %v", domain.ErrInvalidArgs, err)
}
return p, ref, nil
}

func runAnalyze(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("%w: PR URL is required", domain.ErrInvalidArgs)
Expand All @@ -118,10 +134,9 @@ func runAnalyze(cmd *cobra.Command, args []string) error {
return fmt.Errorf("load config: %w", err)
}

p := newProvider(cfg)
ref, err := p.Parse(args[0])
p, ref, err := resolveProvider(cmd, cfg, args[0])
if err != nil {
return fmt.Errorf("%w: invalid PR URL: %v", domain.ErrInvalidArgs, err)
return err
}

format, _ := cmd.Flags().GetString("format")
Expand All @@ -145,10 +160,9 @@ func runPrompt(cmd *cobra.Command, args []string) error {
return fmt.Errorf("load config: %w", err)
}

p := newProvider(cfg)
ref, err := p.Parse(args[0])
p, ref, err := resolveProvider(cmd, cfg, args[0])
if err != nil {
return fmt.Errorf("%w: invalid PR URL: %v", domain.ErrInvalidArgs, err)
return err
}

mode, _ := cmd.Flags().GetString("mode")
Expand Down Expand Up @@ -182,10 +196,9 @@ func runFetch(cmd *cobra.Command, args []string) error {
return fmt.Errorf("load config: %w", err)
}

p := newProvider(cfg)
ref, err := p.Parse(args[0])
p, ref, err := resolveProvider(cmd, cfg, args[0])
if err != nil {
return fmt.Errorf("%w: invalid PR URL: %v", domain.ErrInvalidArgs, err)
return err
}

format, _ := cmd.Flags().GetString("format")
Expand All @@ -194,8 +207,3 @@ func runFetch(cmd *cobra.Command, args []string) error {
Format: format,
}, os.Stdout)
}

func newProvider(cfg config.Config) *ghprovider.Provider {
token := cfg.GitHubToken
return ghprovider.NewProvider(token)
}
7 changes: 7 additions & 0 deletions cmd/prism/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,10 @@ func TestFetchRequiresURL(t *testing.T) {
t.Fatal("expected error, got nil")
}
}

func TestProviderFlag(t *testing.T) {
cmd := rootCmd()
if cmd.PersistentFlags().Lookup("provider") == nil {
t.Error("missing persistent flag --provider")
}
}
142 changes: 142 additions & 0 deletions docs/adr/0001-provider-plugin-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# ADR-0001: Provider Plugin Architecture for v0.2.0

## Status
Accepted

## Date
2026-04-11

## Context

`prism` v0.2.0 では AWS CodeCommit の Pull Request 取得を追加したい。
ただし、`prism` 本体の責務は **Pull Request を構造化コンテキストへ分解すること** であり、
各ホスティングサービス固有の API 実装や認証処理を大量に抱え込むことではない。

現状の懸念は次の2点である。

1. **プロバイダー検出の設計**
- 現在は GitHub 前提で実装されている
- GitHub / CodeCommit / 将来の Bitbucket などにどう拡張するかを整理する必要がある

2. **認証と依存の違い**
- GitHub は `GITHUB_TOKEN` を用いた Bearer 認証
- CodeCommit は AWS credential chain / IAM / region を前提とする
- Go 本体に AWS SDK を直接組み込むと、依存と責務が膨らむ

また、CodeCommit については既存資産として `ccpr` が存在している。
この資産を活用しつつ、`prism` 本体の責務を守る必要がある。

---

## Decision

v0.2.0 では、**Provider を外部バイナリプラグイン方式で拡張する**。

`prism` 本体は provider interface と plugin execution layer のみを持ち、
CodeCommit 対応は `ccpr` 系の別バイナリとして実装・連携する。

### 採用する方針

- Provider 拡張は **外部バイナリ方式** を採用する
- `prism-provider-codecommit` のような実行可能バイナリを想定する
- `prism` はサブプロセスとして provider plugin を呼び出す
- provider plugin は PR データを共通 JSON 形式で返す
- `prism` は返却された共通 JSON を `PullRequest` ドメインモデルへ変換して解析を続行する

### GitHub provider の扱い

- v0.2.0 では **GitHub は本体内蔵(built-in)** とする
- `go install` だけで GitHub に対して即座に使えるユーザー体験を維持する
- ただし、内部的には plugin と同等の境界を保ち、将来的に plugin として外出し可能な構造にする
- CodeCommit 以降の外部 provider は plugin 方式とする

### Provider 検出方針

- デフォルトは **URL からの自動判定**
- `--provider` が明示指定された場合は **URL による自動判定を行わない**
- URL 自動判定が難しいケースや GitHub Enterprise などでは `--provider` で明示できる

### 初期ルール

- `github.com` を含む URL は GitHub provider
- CodeCommit URL パターンに一致するものは CodeCommit provider
- `--provider github` などが指定された場合はその provider を強制利用する
- 自動判定できない場合は、分かりやすいエラーメッセージで `--provider` の指定を促す

---

## Rationale

この判断の理由は次の通り。

### 1. `prism` の責務を保てる
`prism` の本質は PR の取得ではなく、**取得済み PR をレビュー用コンテキストへ変換すること** にある。
プロバイダー固有の実装を本体に抱え込むと、この責務が曖昧になる。

### 2. `ccpr` を活かしやすい
CodeCommit 対応については既存の `ccpr` を活かせる。
`prism` に AWS SDK を直接入れず、外部 provider としてラップすることで、資産再利用と責務分離を両立できる。

### 3. 将来の拡張に強い
Bitbucket、GitHub Enterprise、GitLab など将来的な provider を想定したとき、
プラグイン方式のほうが `prism` 本体の変更を最小化しやすい。

### 4. 依存分離が明確
GitHub provider は軽量に保てる一方、CodeCommit provider は AWS 認証や SDK を独立して持てる。
これにより本体のビルド、保守、配布がシンプルになる。

---

## Alternatives Considered

## A. 外部バイナリ方式
採用。

### メリット
- 依存が完全に分離できる
- `prism` 本体の責務が明確
- `ccpr` を活かしやすい
- 将来 provider を増やしやすい

### デメリット
- プラグインとの入出力仕様を安定させる必要がある
- サブプロセス実行とエラー処理が必要
- 配布方法を設計する必要がある

## B. Go internal 方式
不採用。

### 理由
- `prism` 本体に provider 固有依存が入りやすい
- AWS SDK などの依存が本体に混ざる
- `prism` の思想である責務分離が崩れやすい

---

## Consequences

### Positive
- v0.2.0 で CodeCommit 対応を追加しつつ、本体をシンプルに保てる
- `ccpr` を provider plugin として再利用する道が開ける
- 将来の provider 拡張戦略が明確になる

### Negative
- plugin protocol を設計する必要がある
- plugin discovery の仕様が必要になる
- ユーザー向けにはインストール説明が少し増える

---

## Plugin Protocol

plugin の呼び出し規約、JSON schema、discovery 仕様の詳細は [Provider Plugin Protocol](../provider-plugin-protocol.md) を参照。

---

## Implementation Status

- [x] plugin protocol の JSON schema を定義する
- [x] provider registry を実装する
- [x] provider auto detection ロジックを実装する
- [x] `--provider` 優先ルールを実装する
- [ ] CodeCommit plugin の第一候補として `ccpr` ラップ方式を検討する
15 changes: 12 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ graph TD
FMT --> DOM
PMT --> DOM

PRV --- GH["GitHub"]
PRV --- CC["CodeCommit<br/><i>planned</i>"]
PRV --- REG["Registry"]
REG --- GH["GitHub<br/><i>built-in</i>"]
REG --- PLG["Plugin Executor"]
PLG --- CC["prism-provider-codecommit"]
PLG --- ETC["prism-provider-..."]

FMT --- JSON["JSON"]
FMT --- MD["Markdown"]
Expand Down Expand Up @@ -87,6 +90,12 @@ type Provider interface {

All provider-specific data is normalized into domain models at the provider boundary.

The provider layer consists of:

- **Registry** — resolves a provider by name or auto-detects from URL. GitHub is built-in; other providers are discovered as external plugin binaries (`prism-provider-<name>`) on PATH.
- **Plugin Executor** — runs an external provider binary as a subprocess, parses its stdout JSON into domain models, and enriches files with language/test/config classification.
- **Plugin Protocol** — plugins are invoked as `prism-provider-<name> fetch <PR_URL>` and return a JSON object to stdout. See [ADR-0001](adr/0001-provider-plugin-architecture.md) for details.

### `internal/classifier`

Determines change type based on PR title, description, file paths, and diff content.
Expand All @@ -111,7 +120,7 @@ Orchestrates the pipeline: fetch → classify → analyze → format/render. Eac

## Design Principles

1. **Provider abstraction first** — New PR sources should only require implementing the `Provider` interface
1. **Provider abstraction first** — New PR sources are added as external plugin binaries without modifying prism itself
2. **Domain models are the contract** — All packages communicate through domain types
3. **Output stability** — JSON schema must remain backward-compatible within a major version
4. **Testability** — All external dependencies are behind interfaces; use fixtures and golden files for output verification
Expand Down
Loading
Loading