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
5 changes: 2 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ Follow the global prompt rules first. This file adds project-specific skill rout

## MCP 서버

`.mcp.json`에 등록된 ccg MCP 서버가 26개 도구를 제공합니다:
`.mcp.json`에 등록된 ccg MCP 서버가 24개 도구를 제공합니다:

- `parse_project`, `build_or_update_graph`, `run_postprocess`
- `get_postprocess_policy`, `reset_postprocess_policy`
- `get_node`, `search`, `query_graph`, `list_graph_stats`, `get_minimal_context`
- `get_impact_radius`, `trace_flow`, `find_suspect_fallback_edges`
- `detect_changes`, `get_affected_flows`, `list_flows`
Expand Down Expand Up @@ -49,7 +48,7 @@ Graceful shutdown: SIGINT/SIGTERM 시 진행 중인 clone/build에 context cance
상세 문서는 `guide/` 디렉토리를 참조하세요:

- [CLI Reference](guide/cli-reference.md) — 전체 명령어, 플래그, 설정 파일
- [MCP Tools](guide/mcp-tools.md) — 26개 MCP 도구, Skills, AI-Driven Annotation
- [MCP Tools](guide/mcp-tools.md) — 24개 MCP 도구, Skills, AI-Driven Annotation
- [Annotations](guide/annotations.md) — 어노테이션 태그, 예시, 검색
- [Webhook](guide/webhook.md) — Webhook sync, 브랜치 필터링, HMAC, graceful shutdown
- [Docker](guide/docker.md) — Docker 빌드, MCP 서버, PostgreSQL 배포
Expand Down
54 changes: 9 additions & 45 deletions cmd/ccg/health_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import (
"testing"
"time"

postprocesspolicy "github.com/tae2089/code-context-graph/internal/postprocess/policy"
ccgserver "github.com/tae2089/code-context-graph/internal/server"
ccgdb "github.com/tae2089/code-context-graph/internal/db"
ccgserver "github.com/tae2089/code-context-graph/internal/server"
"github.com/tae2089/code-context-graph/internal/webhook"
)

Expand Down Expand Up @@ -113,7 +112,7 @@ func TestStatusHandler_ReportsWebhookQueueFull(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()

ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }, nil).ServeHTTP(rec, req)
ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }).ServeHTTP(rec, req)

if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("expected 503, got %d body=%s", rec.Code, rec.Body.String())
Expand All @@ -136,7 +135,7 @@ func TestStatusHandler_ReportsWebhookQueueAges(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()

ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }, nil).ServeHTTP(rec, req)
ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }).ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
Expand Down Expand Up @@ -207,7 +206,7 @@ func TestStatusHandler_ReportsUnresolvedWebhookFailure(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()

ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }, nil).ServeHTTP(rec, req)
ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }).ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
Expand Down Expand Up @@ -283,7 +282,7 @@ func TestStatusHandler_DegradedWhenRecentRepoFailureUnresolved(t *testing.T) {

req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()
ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }, nil).ServeHTTP(rec, req)
ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }).ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
Expand Down Expand Up @@ -326,7 +325,7 @@ func TestStatusHandler_DBNotReadyTakesPrecedenceOverWebhookDegraded(t *testing.T

req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()
ccgserver.StatusHandler(func(r *http.Request) error { return errors.New("db down") }, time.Minute, func() *webhook.SyncQueue { return q }, nil).ServeHTTP(rec, req)
ccgserver.StatusHandler(func(r *http.Request) error { return errors.New("db down") }, time.Minute, func() *webhook.SyncQueue { return q }).ServeHTTP(rec, req)

if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("expected 503, got %d body=%s", rec.Code, rec.Body.String())
Expand Down Expand Up @@ -383,7 +382,7 @@ func TestStatusHandler_ReportsPerRepoRecentRepos(t *testing.T) {

req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()
ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }, nil).ServeHTTP(rec, req)
ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }).ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
Expand Down Expand Up @@ -444,7 +443,7 @@ func TestStatusHandler_RecentRepos_FailureHasErrorFields(t *testing.T) {

req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()
ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }, nil).ServeHTTP(rec, req)
ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }).ServeHTTP(rec, req)

var body map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
Expand Down Expand Up @@ -476,7 +475,7 @@ func TestStatusHandler_ExistingAggregateFieldsPreserved(t *testing.T) {

req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()
ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }, nil).ServeHTTP(rec, req)
ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, func() *webhook.SyncQueue { return q }).ServeHTTP(rec, req)

var body map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
Expand All @@ -493,41 +492,6 @@ func TestStatusHandler_ExistingAggregateFieldsPreserved(t *testing.T) {
}
}

func TestStatusHandler_ReportsPostprocessSummary(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/status", nil)
rec := httptest.NewRecorder()

ccgserver.StatusHandler(func(r *http.Request) error { return nil }, time.Minute, nil, func(context.Context) (*postprocesspolicy.StatusSummary, error) {
return &postprocesspolicy.StatusSummary{
Status: postprocesspolicy.StatusDegraded,
FailClosed: []postprocesspolicy.StateSnapshot{{
Namespace: "default",
Tool: postprocesspolicy.ToolRunPostprocess,
Policy: postprocesspolicy.PolicyFailClosed,
ConsecutiveFailures: 3,
}},
}, nil
}).ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
}
var body map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatalf("decode status: %v", err)
}
if got := body["status"]; got != "degraded" {
t.Fatalf("status = %v, want degraded", got)
}
postprocessBody, ok := body["postprocess"].(map[string]any)
if !ok {
t.Fatalf("missing postprocess body: %v", body)
}
if got := postprocessBody["status"]; got != "degraded" {
t.Fatalf("postprocess.status = %v, want degraded", got)
}
}

type recordingPool struct {
maxOpen int
maxIdle int
Expand Down
36 changes: 9 additions & 27 deletions cmd/ccg/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestRunMigrations_AllowsRuntimeSchemaCheck(t *testing.T) {
}
}

func TestRunMigrations_SqliteAppliesPolicyTables(t *testing.T) {
func TestRunMigrations_SqliteDropsPolicyTablesAtHead(t *testing.T) {
requireSQLiteFTS5(t)

dbPath := filepath.Join(t.TempDir(), "ccg.db")
Expand All @@ -128,29 +128,11 @@ func TestRunMigrations_SqliteAppliesPolicyTables(t *testing.T) {
t.Fatalf("run migrations: %v", err)
}

if !db.Migrator().HasTable("ccg_postprocess_policy_state") {
t.Fatal("expected ccg_postprocess_policy_state after migrations")
}
if !db.Migrator().HasTable("ccg_postprocess_run_logs") {
t.Fatal("expected ccg_postprocess_run_logs after migrations")
}

var version migration.MigrationSchemaVersion
if err := db.Table("schema_migrations").First(&version).Error; err != nil {
t.Fatalf("load schema version: %v", err)
}
if int(version.Version) != migration.RequiredSchemaVersion {
t.Fatalf("schema version = %d, want %d", version.Version, migration.RequiredSchemaVersion)
}
if version.Dirty {
t.Fatal("schema version is dirty")
}

if !db.Migrator().HasColumn("ccg_postprocess_policy_state", "namespace") {
t.Fatal("expected namespace column on ccg_postprocess_policy_state")
if db.Migrator().HasTable("ccg_postprocess_policy_state") {
t.Fatal("expected ccg_postprocess_policy_state to be dropped at head schema")
}
if !db.Migrator().HasColumn("ccg_postprocess_policy_state", "tool") {
t.Fatal("expected tool column on ccg_postprocess_policy_state")
if db.Migrator().HasTable("ccg_postprocess_run_logs") {
t.Fatal("expected ccg_postprocess_run_logs to be dropped at head schema")
}
}

Expand Down Expand Up @@ -268,7 +250,7 @@ func TestRunMigrations_SQLiteDownRestoresNullableColumns(t *testing.T) {
if err != nil {
t.Fatalf("create migrator: %v", err)
}
if err := migrator.Steps(-5); err != nil {
if err := migrator.Steps(-6); err != nil {
t.Fatalf("run down migration: %v", err)
}

Expand Down Expand Up @@ -313,7 +295,7 @@ func TestRunMigrations_SQLiteDownFromVersionThreeDropsPolicyTables(t *testing.T)
if err != nil {
t.Fatalf("create migrator: %v", err)
}
if err := migrator.Steps(-4); err != nil {
if err := migrator.Steps(-5); err != nil {
t.Fatalf("run down migration: %v", err)
}

Expand Down Expand Up @@ -427,7 +409,7 @@ func TestEnsureSchemaVersion_LogsRuntimeSchemaPassAndFail(t *testing.T) {
t.Fatalf("ensure schema version: %v", err)
}
passLog := logs.String()
for _, want := range []string{"database runtime schema check passed", "driver=sqlite", "required_version=6", "auto_migrated=true"} {
for _, want := range []string{"database runtime schema check passed", "driver=sqlite", "required_version=7", "auto_migrated=true"} {
if !strings.Contains(passLog, want) {
t.Fatalf("expected runtime schema pass log to contain %q, got %q", want, passLog)
}
Expand All @@ -441,7 +423,7 @@ func TestEnsureSchemaVersion_LogsRuntimeSchemaPassAndFail(t *testing.T) {
t.Fatal("expected parity failure")
}
failLog := logs.String()
for _, want := range []string{"database runtime schema check failed", "driver=sqlite", "required_version=6", "auto_migrated=false", "error.message="} {
for _, want := range []string{"database runtime schema check failed", "driver=sqlite", "required_version=7", "auto_migrated=false", "error.message="} {
if !strings.Contains(failLog, want) {
t.Fatalf("expected runtime schema failure log to contain %q, got %q", want, failLog)
}
Expand Down
1 change: 0 additions & 1 deletion guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ explore graph edges.
| [Webhook](webhook.md) | GitHub / Gitea webhook sync, branch filtering, graceful shutdown |
| [Docker](docker.md) | Docker image build, MCP server setup, Wiki UI deployment, PostgreSQL integration |
| [Operations](operations.md) | Deployment profiles, database choice, readiness, webhook operations, troubleshooting |
| [Postprocess Failure Policy](postprocess-failure-policy.md) | Status rules, failure causes, and automatic degraded/fail_closed policy for build and postprocess tools |
| [Runtime Layout](runtime-layout.md) | `ccg`, `ccg-server`, Wiki serving, and shared `ccg-core` ownership boundaries |
| [Architecture](architecture.md) | System architecture, data flow, DB schema |
| [Development](development.md) | Build, test, integration test (Gitea + PostgreSQL) |
Expand Down
4 changes: 1 addition & 3 deletions guide/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ ccg update ./backend --namespace backend
| `ccg build --fallback-calls` | Enable best-effort fallback call resolution (strict by default) |
| `ccg update [dir]` | Incremental sync |
| `ccg update --fallback-calls` | Enable best-effort fallback call resolution for incremental sync |
| `ccg status` | Graph statistics and postprocess error summary |
| `ccg status --errors` | Include recent postprocess failure details |
| `ccg status --recent <n>` | Number of recent postprocess failures to inspect (default `5`) |
| `ccg status` | Graph statistics |
| `ccg search <query>` | Full-text search |
| `ccg search --path <prefix> <query>` | Scoped search by path prefix |
| `ccg docs [--out dir]` | Generate Markdown documentation, the `wiki-index.json` compatibility snapshot, and the default RAG index (prunes stale generator-managed docs by default) |
Expand Down
1 change: 0 additions & 1 deletion guide/ko/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ matched fields, evidence node로 빠르게 경로를 좁힌 뒤 graph/search 도
| [웹훅(Webhook)](webhook.md) | GitHub / Gitea 웹훅 동기화, 브랜치 필터링, Graceful Shutdown |
| [Docker](docker.md) | Docker 이미지 빌드, MCP 서버 설정, Wiki UI 배포, PostgreSQL 연동 |
| [운영(Operations)](operations.md) | 배포 프로필, 데이터베이스 선택, 준비성 체크, 웹훅 운영 및 문제 해결 |
| [사후 처리 실패 정책](postprocess-failure-policy.md) | 상태 규칙, 실패 원인, 빌드 및 사후 처리 도구의 자동 성능 저하(degraded)/폐쇄형 실패(fail_closed) 정책 |
| [런타임 구조](runtime-layout.md) | `ccg`, `ccg-server`, Wiki serving, 공용 `ccg-core` 소유권 경계 |
| [아키텍처](architecture.md) | 시스템 아키텍처, 데이터 흐름, DB 스키마 |
| [개발(Development)](development.md) | 빌드, 테스트, 통합 테스트(Gitea + PostgreSQL) |
Expand Down
4 changes: 1 addition & 3 deletions guide/ko/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ ccg update ./backend --namespace backend
| `ccg build --fallback-calls` | (기본 off) best-effort fallback 호출 해상도 활성화 |
| `ccg update [dir]` | 증분 동기화(Incremental sync) |
| `ccg update --fallback-calls` | 증분 동기화에서 best-effort fallback 호출 해상도 활성화 |
| `ccg status` | 그래프 통계 및 사후 처리 오류 요약 출력 |
| `ccg status --errors` | 최근 사후 처리 실패 상세 포함 |
| `ccg status --recent <n>` | 확인할 최근 사후 처리 실패 개수 (기본값 `5`) |
| `ccg status` | 그래프 통계 출력 |
| `ccg search <query>` | 전체 텍스트 검색 |
| `ccg search --path <prefix> <query>` | 경로 접두사로 검색 범위 제한 |
| `ccg docs [--out dir]` | 마크다운 문서, `wiki-index.json` 호환 snapshot, 기본 RAG 인덱스 생성 (기본적으로 그래프에 없는 generator-managed 문서를 prune) |
Expand Down
1 change: 0 additions & 1 deletion guide/ko/lint.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,3 @@ rules:
- [CLI 레퍼런스](cli-reference.md#lint-categories)
- [커스텀 어노테이션](annotations.md)
- [개발](development.md)
- [사후 처리 실패 정책](postprocess-failure-policy.md)
12 changes: 3 additions & 9 deletions guide/ko/mcp-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,14 @@ Codex 또는 Claude Code 같은 MCP 지원 코딩 에이전트가 연결할 수
|------|-------------|
| `parse_project` | 소스 파일 파싱 |
| `build_or_update_graph` | 사후 처리를 포함한 전체/증분 빌드 |
| `run_postprocess` | 저장된 흐름(flow), 커뮤니티 및 전체 텍스트 검색 파생 상태 재생성 |
| `get_postprocess_policy` | 자동 사후 처리 정책 상태 및 최근 실패 내역 확인 |
| `reset_postprocess_policy` | 특정 도구에 대한 fail-closed 연속 기록을 지우기 위한 리셋 마커 기록 |
| `run_postprocess` | 저장된 흐름(flow) 및 전체 텍스트 검색 파생 상태 재생성 |
| `get_node` | 정규화된 이름으로 노드 조회 |
| `search` | 전체 텍스트 검색 |
| `query_graph` | 정의된 그래프 쿼리(callers, callees, imports 등) |
| `list_graph_stats` | 노드/엣지/파일 수 확인 |
| `get_minimal_context` | AI 에이전트 진입점을 위한 경량 요약(~100 토큰) — 그래프 통계, 리스크, 주요 커뮤니티/흐름, 도구 추천 포함 |
| `get_minimal_context` | AI 에이전트 진입점을 위한 경량 요약(~100 토큰) — 그래프 통계, 리스크, 주요 흐름, 도구 추천 포함 |

`build_or_update_graph`와 `run_postprocess`는 모두 자동 사후 처리 실패 정책(postprocess failure policy)을 지원합니다. 명시적인 `postprocess_policy`가 제공되지 않으면 CCG는 기본적으로 `degraded` 모드를 사용하며, 동일한 `(namespace, tool)` 쌍에 대해 3회 연속 `degraded` 실행 시 자동으로 `fail_closed`로 격상합니다.

상태 표, 실패 원인, 건너뛰기 동작 및 `build_or_update_graph`와 `run_postprocess`에 대한 정책 격상 규칙 등 자세한 내용은 [사후 처리 실패 정책](postprocess-failure-policy.md)을 참조하십시오.

CCG는 아직 Prometheus `/metrics` 엔드포인트를 제공하지 않습니다. 사후 처리 작업에 대해서는 현재 기계가 읽을 수 있는 운영 인터페이스인 `get_postprocess_policy`와 HTTP `/status` 요약을 사용하십시오.
`build_or_update_graph`와 `run_postprocess`는 그래프 기록 후 파생 상태 단계(저장된 흐름, 전체 텍스트 검색)를 실행합니다. 단계가 실패하면 도구는 여전히 `status: "degraded"`와 실패한 단계를 담은 `failed_steps`가 포함된 구조화된 결과를 반환하며, 실패는 로그에도 남습니다. 이 응답(또는 HTTP `/status` 요약)으로 성능 저하된 사후 처리를 감지하십시오.

### 분석 (Analysis)

Expand Down
Loading
Loading