Skip to content
Closed
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
4 changes: 4 additions & 0 deletions .dal/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.dal/decisions.md merge=union
.dal/decisions-archive.md merge=union
.dal/*/history.md merge=union
.dal/wisdom.md merge=union
38 changes: 38 additions & 0 deletions .dal/template/dal.spec.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// dal.spec.cue — localdal schema

#Player: "claude" | "codex" | "gemini"
#Role: "leader" | "member" | "ops"

#BranchConfig: {
base?: string | *"main"
}

#SetupConfig: {
packages?: [...string]
commands?: [...string]
timeout?: string | *"5m"
}

#DalProfile: {
uuid!: string & != ""
name!: string & != ""
version!: string
player!: #Player
fallback_player?: #Player
role!: #Role
channel_only?: bool
skills?: [...string]
hooks?: [...string]
model?: string
player_version?: string
auto_task?: string
auto_interval?: string
workspace?: string
branch?: #BranchConfig
setup?: #SetupConfig
git?: {
user?: string
email?: string
github_token?: string
}
}
25 changes: 25 additions & 0 deletions .dal/template/dal/charter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# dal — 문서 관리자

## Role
팀 공유 기억의 유일한 writer + committer. 백그라운드 자동 실행.

## Responsibilities
1. decisions/inbox/ → decisions.md 병합 (중복 제거: By + What 기준)
2. wisdom-inbox/ → wisdom.md 병합
3. history-buffer/{name}.md → .dal/{name}/history.md 병합
4. history.md 12KB 초과 시 Core Context 압축
5. decisions.md 50KB 초과 시 30일+ 항목 → decisions-archive.md
6. README.md / CLAUDE.md 갱신 (ccw tool update_module_claude)
7. 변경 시 git add + commit + push

## Tools
- ccw tool update_module_claude — 모듈 문서 자동 생성
- ccw tool detect_changed_modules — 변경 모듈 탐지
- ccw memory — 컨텍스트 메모리 관리
- dalcli status / report

## Rules
- push 실패 시 재시도만. force push, reset 금지.
- 병합 후 inbox 파일 삭제.
- history에는 최종 결과만. 중간 상태 금지.
- 코드, 리뷰, 테스트 금지 — 문서만 담당.
15 changes: 15 additions & 0 deletions .dal/template/dal/dal.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
uuid: "99ab0934-07fb-423f-84e6-1ce8aef91919"
name: "dal"
version: "1.0.0"
player: "claude"
model: "haiku"
role: "member"
skills: ["skills/inbox-protocol", "skills/history-hygiene"]
hooks: []
auto_task: "1. /workspace/decisions/inbox/ → decisions.md 병합 (중복 제거 후 삭제). 2. /workspace/history-buffer/ → .dal/{name}/history.md 병합. 3. /workspace/wisdom-inbox/ → wisdom.md 병합. 4. history.md 12KB 초과 시 압축. 5. decisions.md 50KB 초과 시 30일+ 아카이브. 6. README.md, CLAUDE.md 갱신 필요 시 ccw tool update_module_claude로 자동 생성. 7. 변경 시 git add + commit + push."
auto_interval: "30m"
git: {
user: "dal-docs"
email: "dal-docs@dalcenter.local"
github_token: "env:GITHUB_TOKEN"
}
28 changes: 28 additions & 0 deletions .dal/template/dalops/charter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# dalops — 운영자

## Role
CCW 기반 오케스트레이터. 코드 구현, 리뷰, 테스트를 워크플로우로 실행한다.

## Tools
- ccw cli --tool codex --mode review — Codex 코드 리뷰
- ccw cli --tool codex --mode analysis — Codex 분석
- ccw cli --tool gemini — Gemini 분석
- dalcli status / ps / report

## Workflows
- workflow-lite-plan — 단일 모듈 기능 구현
- workflow-tdd-plan — 테스트 주도 개발
- workflow-multi-cli-plan — 멀티 CLI 협업 분석/리뷰
- workflow-test-fix — 테스트 생성 및 수정 루프

## Process
1. 이슈/작업 수신
2. CCW 워크플로우 선택 및 실행
3. codex 리뷰 통과 확인
5. 브랜치 → PR 생성
6. dalcli report로 결과 보고

## Rules
- main 직접 커밋 금지
- PR 생성 전 반드시 테스트 통과
- ccw session으로 작업 컨텍스트 유지
13 changes: 13 additions & 0 deletions .dal/template/dalops/dal.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
uuid: "c9380496-8203-44e4-9025-6e624e95cb67"
name: "dalops"
version: "1.0.0"
player: "claude"
role: "ops"
channel_only: true
skills: ["skills/git-workflow", "skills/pre-flight"]
hooks: []
git: {
user: "dal-ops"
email: "dal-ops@dalcenter.local"
github_token: "env:GITHUB_TOKEN"
}
3 changes: 3 additions & 0 deletions .dal/template/decisions-archive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Decisions Archive

아카이브된 결정. 읽기 전용 참조.
13 changes: 13 additions & 0 deletions .dal/template/decisions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Decisions

팀 아키텍처 결정 로그. 모든 dal은 작업 전에 이 파일을 읽는다.
scribe dal이 inbox에서 승인된 제안을 병합한다. 직접 수정 금지.

## 포맷

### {날짜}: {주제}
**By:** {dal name}
**What:** {결정 내용}
**Why:** {이유}

---
15 changes: 15 additions & 0 deletions .dal/template/reviewer/dal.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
uuid: "a1b2c3d4-e5f6-7890-abcd-reviewer00001"
name: "reviewer"
version: "1.0.0"
player: "claude"
role: "member"
channel_only: false
skills: ["skills/git-workflow", "skills/pre-flight"]
hooks: []
auto_task: "1. gh pr list --state open 으로 리뷰 대기 PR 확인. 2. 각 PR에 대해: checkout → go build → go test → 코드 리뷰 (보안, 로직, 테스트 커버리지). 3. 문제 없으면 gh pr review --approve. 4. 문제 있으면 gh pr review --request-changes + 코멘트. 5. 리뷰 결과를 /workspace/review-log.md에 기록."
auto_interval: "15m"
git: {
user: "dal-reviewer"
email: "dal-reviewer@dalcenter.local"
github_token: "env:GITHUB_TOKEN"
}
3 changes: 3 additions & 0 deletions .dal/template/skills/escalation/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Escalation

report: 완료 보고. claim: 진행 불가 에스컬레이션.
3 changes: 3 additions & 0 deletions .dal/template/skills/git-workflow/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Git Workflow

main 직접 커밋 금지. 브랜치 → PR → 리뷰 → 머지.
3 changes: 3 additions & 0 deletions .dal/template/skills/history-hygiene/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# History Hygiene

최종 결과만 기록. 중간 시도 금지. 12KB 제한.
3 changes: 3 additions & 0 deletions .dal/template/skills/inbox-protocol/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Inbox Protocol

decisions.md, wisdom.md 직접 수정 금지. inbox에 드롭.
4 changes: 4 additions & 0 deletions .dal/template/skills/leader-protocol/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Leader Protocol

나는 중개자. 직접 수정 안 함. 소환+읽기+판단+라우팅만.
Write/Edit/commit 금지. dalcli-leader assign으로 멤버에게 위임.
3 changes: 3 additions & 0 deletions .dal/template/skills/pre-flight/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Pre-Flight

작업 전 필수: now.md → decisions.md → wisdom.md → ps.
3 changes: 3 additions & 0 deletions .dal/template/skills/reviewer-protocol/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Reviewer Protocol

작성자 ≠ 리뷰어. 리뷰어 본인 수정 금지.
11 changes: 11 additions & 0 deletions .dal/template/wisdom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Wisdom

팀 공유 교훈. 모든 dal은 작업 전에 이 파일을 읽는다.

## Patterns

검증된 접근 방식.

## Anti-Patterns

피해야 할 것.
5 changes: 3 additions & 2 deletions cmd/dalcenter/cmd_localdal.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func localdalRoot() string {
// --- serve ---

func newServeCmd() *cobra.Command {
var addr, serviceRepo, bridgeURL, bridgeConf string
var addr, serviceRepo, bridgeURL, bridgeConf, githubRepo string
cmd := &cobra.Command{
Use: "serve",
Short: "Run dalcenter daemon (HTTP API + Docker management)",
Expand All @@ -39,7 +39,7 @@ func newServeCmd() *cobra.Command {
root = repoRoot
}
}
d := daemon.New(addr, root, serviceRepo, bridgeURL, bridgeConf)
d := daemon.New(addr, root, serviceRepo, bridgeURL, bridgeConf, githubRepo)

ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
Expand All @@ -51,6 +51,7 @@ func newServeCmd() *cobra.Command {
cmd.Flags().StringVar(&serviceRepo, "repo", os.Getenv("DALCENTER_REPO"), "Service repository path")
cmd.Flags().StringVar(&bridgeURL, "bridge-url", os.Getenv("DALCENTER_BRIDGE_URL"), "Matterbridge API URL (auto-set if --bridge-conf provided)")
cmd.Flags().StringVar(&bridgeConf, "bridge-conf", os.Getenv("DALCENTER_BRIDGE_CONF"), "Matterbridge config path (starts as child process)")
cmd.Flags().StringVar(&githubRepo, "github-repo", os.Getenv("DALCENTER_GITHUB_REPO"), "GitHub repo for issue polling (e.g., owner/repo)")
return cmd
}

Expand Down
6 changes: 3 additions & 3 deletions internal/daemon/claim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestClaimStore_FilterByStatus(t *testing.T) {
}

func TestHandleClaim_Post(t *testing.T) {
d := New(":0", "/tmp/test", t.TempDir(), "", "")
d := New(":0", "/tmp/test", t.TempDir(), "", "", "")
body := `{"dal":"dev","type":"bug","title":"cargo missing","detail":"command not found"}`
req := httptest.NewRequest("POST", "/api/claim", strings.NewReader(body))
w := httptest.NewRecorder()
Expand All @@ -93,7 +93,7 @@ func TestHandleClaim_Post(t *testing.T) {
}

func TestHandleClaims_Empty(t *testing.T) {
d := New(":0", "/tmp/test", t.TempDir(), "", "")
d := New(":0", "/tmp/test", t.TempDir(), "", "", "")
req := httptest.NewRequest("GET", "/api/claims", nil)
w := httptest.NewRecorder()
d.handleClaims(w, req)
Expand All @@ -103,7 +103,7 @@ func TestHandleClaims_Empty(t *testing.T) {
}

func TestHandleClaim_MissingFields(t *testing.T) {
d := New(":0", "/tmp/test", t.TempDir(), "", "")
d := New(":0", "/tmp/test", t.TempDir(), "", "", "")
body := `{"dal":"","title":""}`
req := httptest.NewRequest("POST", "/api/claim", strings.NewReader(body))
w := httptest.NewRecorder()
Expand Down
11 changes: 10 additions & 1 deletion internal/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Daemon struct {
serviceRepo string // service repo path to mount as /workspace
bridgeURL string // matterbridge API URL
bridgeConf string // matterbridge config path (child process)
githubRepo string // GitHub repo for issue polling (e.g., "owner/repo")
apiToken string // Bearer token for write endpoints (empty = no auth)
containers map[string]*Container // dal name -> container
mu sync.RWMutex
Expand All @@ -44,6 +45,7 @@ type Daemon struct {
tasks *taskStore
feedback *feedbackStore
costs *costStore
issues *issueStore
registry *Registry
startTime time.Time
}
Expand All @@ -63,7 +65,7 @@ type Container struct {
}

// New creates a daemon.
func New(addr, localdalRoot, serviceRepo, bridgeURL, bridgeConf string) *Daemon {
func New(addr, localdalRoot, serviceRepo, bridgeURL, bridgeConf, githubRepo string) *Daemon {
token := os.Getenv("DALCENTER_TOKEN")
if token != "" {
log.Println("[daemon] API token auth enabled for write endpoints")
Expand All @@ -77,13 +79,15 @@ func New(addr, localdalRoot, serviceRepo, bridgeURL, bridgeConf string) *Daemon
serviceRepo: serviceRepo,
bridgeURL: strings.TrimRight(bridgeURL, "/"),
bridgeConf: bridgeConf,
githubRepo: githubRepo,
apiToken: token,
containers: make(map[string]*Container),
escalations: newEscalationStoreWithFile(filepath.Join(dataDir(serviceRepo), "escalations.json")),
claims: newClaimStoreWithFile(filepath.Join(dataDir(serviceRepo), "claims.json")),
tasks: newTaskStoreWithFile(filepath.Join(dataDir(serviceRepo), "tasks.json")),
feedback: newFeedbackStoreWithFile(filepath.Join(dataDir(serviceRepo), "feedback.json")),
costs: newCostStoreWithFile(filepath.Join(dataDir(serviceRepo), "costs.json"), orchestrationLogDir(serviceRepo)),
issues: newIssueStore(filepath.Join(dataDir(serviceRepo), "issues_seen.json")),
registry: newRegistry(serviceRepo),
credSyncLast: newCredentialSyncMap(),
startTime: time.Now(),
Expand Down Expand Up @@ -171,6 +175,9 @@ func (d *Daemon) Run(ctx context.Context) error {
// Start leader health watcher (auto-recovery)
go d.startLeaderWatcher(ctx)

// Start GitHub issue watcher
go d.startIssueWatcher(ctx, d.githubRepo, defaultIssuePollInterval)

if d.bridgeURL != "" {
log.Printf("[daemon] matterbridge URL: %s", d.bridgeURL)
}
Expand Down Expand Up @@ -222,6 +229,8 @@ func (d *Daemon) Run(ctx context.Context) error {
mux.HandleFunc("POST /api/escalate", d.requireAuth(d.handleEscalate))
mux.HandleFunc("GET /api/escalations", d.handleEscalations)
mux.HandleFunc("POST /api/escalations/{id}/resolve", d.requireAuth(d.handleResolveEscalation))
// GitHub issue tracking
mux.HandleFunc("GET /api/issues", d.handleIssues)
// A2A protocol endpoints
mux.HandleFunc("GET /api/provider-status", d.handleProviderStatus)
mux.HandleFunc("POST /api/provider-trip", d.handleProviderTrip)
Expand Down
2 changes: 1 addition & 1 deletion internal/daemon/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ hooks: []
`), 0644)
os.WriteFile(filepath.Join(devDir, "charter.md"), []byte("# Dev\n"), 0644)

d := New(":0", root, "", "", "")
d := New(":0", root, "", "", "", "")
return d, root
}

Expand Down
Loading
Loading