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
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:** {이유}

---
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

피해야 할 것.
126 changes: 126 additions & 0 deletions internal/daemon/notify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package daemon

import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"strings"
"time"
)

// NotifyPayload is the JSON body sent to DALCENTER_NOTIFY_URL.
type NotifyPayload struct {
Event string `json:"event"` // "task_done", "task_failed"
Dal string `json:"dal"`
TaskID string `json:"task_id"`
Task string `json:"task"`
Status string `json:"status"`
PRUrl string `json:"pr_url,omitempty"`
Error string `json:"error,omitempty"`
Output string `json:"output,omitempty"`
Changes int `json:"git_changes"`
Verified string `json:"verified,omitempty"`
Timestamp string `json:"timestamp"`
}

// notifyTaskComplete sends a notification when a task finishes.
// It tries three channels in order:
// 1. DALCENTER_NOTIFY_URL — HTTP POST with JSON payload
// 2. notify-dalroot CLI — if CallbackPane is set
// 3. Neither — log only
func notifyTaskComplete(dalName string, tr *taskResult, repo string) {
payload := buildNotifyPayload(dalName, tr)

// 1. HTTP notification via DALCENTER_NOTIFY_URL
if url := os.Getenv("DALCENTER_NOTIFY_URL"); url != "" {
go sendNotifyHTTP(url, payload)
}

// 2. CLI notification via notify-dalroot (backward compat)
if tr.CallbackPane != "" {
go sendNotifyCLI(dalName, tr, repo)
}
}

// buildNotifyPayload constructs the notification payload from a task result.
func buildNotifyPayload(dalName string, tr *taskResult) NotifyPayload {
event := "task_done"
if tr.Status == "failed" || tr.Status == "blocked" {
event = "task_failed"
}

p := NotifyPayload{
Event: event,
Dal: dalName,
TaskID: tr.ID,
Task: truncateStr(tr.Task, 200),
Status: tr.Status,
Changes: tr.GitChanges,
Verified: tr.Verified,
Timestamp: time.Now().UTC().Format(time.RFC3339),
}

if tr.Status == "failed" || tr.Status == "blocked" {
p.Error = truncateStr(tr.Error, 500)
}

// Extract PR URL from output if present
if prURL := extractPRUrl(tr.Output); prURL != "" {
p.PRUrl = prURL
}

return p
}

// sendNotifyHTTP posts the payload to the given URL.
func sendNotifyHTTP(url string, payload NotifyPayload) {
data, err := json.Marshal(payload)
if err != nil {
log.Printf("[notify] marshal error: %v", err)
return
}

client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Post(url, "application/json", bytes.NewReader(data))
if err != nil {
log.Printf("[notify] HTTP POST to %s failed: %v", url, err)
return
}
resp.Body.Close()
log.Printf("[notify] HTTP POST %s → %d (%s %s)", url, resp.StatusCode, payload.Event, payload.Dal)
}

// sendNotifyCLI calls the notify-dalroot CLI tool for pane-based notification.
func sendNotifyCLI(dalName string, tr *taskResult, repo string) {
msg := fmt.Sprintf("[%s] task %s: %s", dalName, tr.Status, truncateStr(tr.Task, 80))
if prURL := extractPRUrl(tr.Output); prURL != "" {
msg += " → " + prURL
}
if tr.Status == "failed" && tr.Error != "" {
msg += " | error: " + truncateStr(tr.Error, 100)
}
cmd := exec.Command("notify-dalroot", repo, msg, tr.CallbackPane)
if err := cmd.Run(); err != nil {
log.Printf("[notify] dalroot CLI failed: %v", err)
}
}

// extractPRUrl scans output for a GitHub PR URL.
func extractPRUrl(output string) string {
for _, line := range strings.Split(output, "\n") {
line = strings.TrimSpace(line)
if strings.Contains(line, "github.com/") && strings.Contains(line, "/pull/") {
// Find the URL within the line
for _, word := range strings.Fields(line) {
if strings.Contains(word, "github.com/") && strings.Contains(word, "/pull/") {
return strings.TrimRight(word, ".,;:!?\"'`)")
}
}
}
}
return ""
}
Loading
Loading