create-arete-workspace 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/bin/arete.js +156 -0
- package/bin/create.js +111 -0
- package/lib/install-openclaw.js +50 -0
- package/lib/scaffold.js +213 -0
- package/lib/setup-wizard.js +88 -0
- package/lib/updater.js +130 -0
- package/package.json +34 -0
- package/packages/gatsaeng-os/README.md +36 -0
- package/packages/gatsaeng-os/components.json +23 -0
- package/packages/gatsaeng-os/eslint.config.mjs +18 -0
- package/packages/gatsaeng-os/next.config.ts +7 -0
- package/packages/gatsaeng-os/package.json +59 -0
- package/packages/gatsaeng-os/postcss.config.mjs +7 -0
- package/packages/gatsaeng-os/public/file.svg +1 -0
- package/packages/gatsaeng-os/public/globe.svg +1 -0
- package/packages/gatsaeng-os/public/next.svg +1 -0
- package/packages/gatsaeng-os/public/vercel.svg +1 -0
- package/packages/gatsaeng-os/public/window.svg +1 -0
- package/packages/gatsaeng-os/python/api_server.py +248 -0
- package/packages/gatsaeng-os/python/briefing.py +145 -0
- package/packages/gatsaeng-os/python/config.py +55 -0
- package/packages/gatsaeng-os/python/goal_context_agent.py +193 -0
- package/packages/gatsaeng-os/python/gyeokguk.py +171 -0
- package/packages/gatsaeng-os/python/proactive.py +158 -0
- package/packages/gatsaeng-os/python/requirements.txt +11 -0
- package/packages/gatsaeng-os/python/run.py +28 -0
- package/packages/gatsaeng-os/python/scoring.py +44 -0
- package/packages/gatsaeng-os/python/streak.py +70 -0
- package/packages/gatsaeng-os/python/telegram_bot.py +331 -0
- package/packages/gatsaeng-os/python/timing_engine.py +117 -0
- package/packages/gatsaeng-os/python/vault_io.py +423 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/areas/[id]/page.tsx +215 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/areas/page.tsx +161 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/books/[id]/page.tsx +215 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/books/page.tsx +268 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/calendar/page.tsx +379 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/error.tsx +30 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/focus/page.tsx +293 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/goals/[id]/page.tsx +426 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/goals/page.tsx +178 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/layout.tsx +29 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/notes/[id]/page.tsx +147 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/notes/page.tsx +254 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/page.tsx +26 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/projects/[id]/page.tsx +86 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/projects/page.tsx +215 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/review/page.tsx +475 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/routines/page.tsx +436 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/tasks/[id]/page.tsx +210 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/tasks/page.tsx +307 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/voice/page.tsx +212 -0
- package/packages/gatsaeng-os/src/app/api/areas/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/areas/route.ts +22 -0
- package/packages/gatsaeng-os/src/app/api/auth/login/route.ts +52 -0
- package/packages/gatsaeng-os/src/app/api/auth/logout/route.ts +8 -0
- package/packages/gatsaeng-os/src/app/api/books/[id]/route.ts +27 -0
- package/packages/gatsaeng-os/src/app/api/books/route.ts +20 -0
- package/packages/gatsaeng-os/src/app/api/calendar/[id]/route.ts +24 -0
- package/packages/gatsaeng-os/src/app/api/calendar/import/route.ts +52 -0
- package/packages/gatsaeng-os/src/app/api/calendar/route.ts +37 -0
- package/packages/gatsaeng-os/src/app/api/daily/route.ts +51 -0
- package/packages/gatsaeng-os/src/app/api/goals/[id]/route.ts +34 -0
- package/packages/gatsaeng-os/src/app/api/goals/route.ts +30 -0
- package/packages/gatsaeng-os/src/app/api/logs/energy/route.ts +40 -0
- package/packages/gatsaeng-os/src/app/api/logs/focus/route.ts +22 -0
- package/packages/gatsaeng-os/src/app/api/logs/routine/route.ts +54 -0
- package/packages/gatsaeng-os/src/app/api/milestones/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/milestones/route.ts +47 -0
- package/packages/gatsaeng-os/src/app/api/notes/[id]/route.ts +29 -0
- package/packages/gatsaeng-os/src/app/api/notes/route.ts +37 -0
- package/packages/gatsaeng-os/src/app/api/profile/route.ts +17 -0
- package/packages/gatsaeng-os/src/app/api/projects/[id]/route.ts +27 -0
- package/packages/gatsaeng-os/src/app/api/projects/route.ts +25 -0
- package/packages/gatsaeng-os/src/app/api/reviews/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/reviews/route.ts +29 -0
- package/packages/gatsaeng-os/src/app/api/routines/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/routines/route.ts +28 -0
- package/packages/gatsaeng-os/src/app/api/tasks/[id]/route.ts +28 -0
- package/packages/gatsaeng-os/src/app/api/tasks/route.ts +66 -0
- package/packages/gatsaeng-os/src/app/api/timing/current/route.ts +63 -0
- package/packages/gatsaeng-os/src/app/api/voice/chat/route.ts +50 -0
- package/packages/gatsaeng-os/src/app/api/voice/transcribe/route.ts +25 -0
- package/packages/gatsaeng-os/src/app/api/voice/tts/route.ts +36 -0
- package/packages/gatsaeng-os/src/app/error.tsx +30 -0
- package/packages/gatsaeng-os/src/app/favicon.ico +0 -0
- package/packages/gatsaeng-os/src/app/globals.css +208 -0
- package/packages/gatsaeng-os/src/app/layout.tsx +33 -0
- package/packages/gatsaeng-os/src/app/login/page.tsx +87 -0
- package/packages/gatsaeng-os/src/app/providers.tsx +27 -0
- package/packages/gatsaeng-os/src/components/ErrorBoundary.tsx +46 -0
- package/packages/gatsaeng-os/src/components/dashboard/DashboardGrid.tsx +86 -0
- package/packages/gatsaeng-os/src/components/dashboard/DdayWidget.tsx +88 -0
- package/packages/gatsaeng-os/src/components/dashboard/EnergyTracker.tsx +87 -0
- package/packages/gatsaeng-os/src/components/dashboard/FocusTimer.tsx +139 -0
- package/packages/gatsaeng-os/src/components/dashboard/GatsaengScore.tsx +30 -0
- package/packages/gatsaeng-os/src/components/dashboard/GoalRings.tsx +107 -0
- package/packages/gatsaeng-os/src/components/dashboard/ProactiveBar.tsx +98 -0
- package/packages/gatsaeng-os/src/components/dashboard/RoutineChecklist.tsx +81 -0
- package/packages/gatsaeng-os/src/components/dashboard/TimingWidget.tsx +86 -0
- package/packages/gatsaeng-os/src/components/dashboard/WidgetCustomizer.tsx +95 -0
- package/packages/gatsaeng-os/src/components/dashboard/WidgetWrapper.tsx +33 -0
- package/packages/gatsaeng-os/src/components/dashboard/ZeigarnikPanel.tsx +43 -0
- package/packages/gatsaeng-os/src/components/editor/EditorToolbar.tsx +186 -0
- package/packages/gatsaeng-os/src/components/editor/TiptapEditor.tsx +114 -0
- package/packages/gatsaeng-os/src/components/layout/Header.tsx +47 -0
- package/packages/gatsaeng-os/src/components/layout/MobileBottomNav.tsx +122 -0
- package/packages/gatsaeng-os/src/components/layout/MobileSidebar.tsx +29 -0
- package/packages/gatsaeng-os/src/components/layout/Sidebar.tsx +142 -0
- package/packages/gatsaeng-os/src/components/onboarding/OnboardingFlow.tsx +229 -0
- package/packages/gatsaeng-os/src/components/onboarding/OnboardingGate.tsx +78 -0
- package/packages/gatsaeng-os/src/components/projects/CalendarView.tsx +152 -0
- package/packages/gatsaeng-os/src/components/projects/KanbanView.tsx +180 -0
- package/packages/gatsaeng-os/src/components/projects/ListView.tsx +82 -0
- package/packages/gatsaeng-os/src/components/projects/TableView.tsx +206 -0
- package/packages/gatsaeng-os/src/components/projects/TaskCard.tsx +154 -0
- package/packages/gatsaeng-os/src/components/projects/TaskForm.tsx +128 -0
- package/packages/gatsaeng-os/src/components/projects/ViewSwitcher.tsx +40 -0
- package/packages/gatsaeng-os/src/components/search/GlobalSearch.tsx +179 -0
- package/packages/gatsaeng-os/src/components/shared/InlineEdit.tsx +77 -0
- package/packages/gatsaeng-os/src/components/shared/PinButton.tsx +42 -0
- package/packages/gatsaeng-os/src/components/tasks/DDayBadge.tsx +34 -0
- package/packages/gatsaeng-os/src/components/ui/badge.tsx +48 -0
- package/packages/gatsaeng-os/src/components/ui/button.tsx +64 -0
- package/packages/gatsaeng-os/src/components/ui/card.tsx +92 -0
- package/packages/gatsaeng-os/src/components/ui/checkbox.tsx +32 -0
- package/packages/gatsaeng-os/src/components/ui/command.tsx +184 -0
- package/packages/gatsaeng-os/src/components/ui/dialog.tsx +158 -0
- package/packages/gatsaeng-os/src/components/ui/input.tsx +21 -0
- package/packages/gatsaeng-os/src/components/ui/label.tsx +24 -0
- package/packages/gatsaeng-os/src/components/ui/popover.tsx +89 -0
- package/packages/gatsaeng-os/src/components/ui/progress.tsx +31 -0
- package/packages/gatsaeng-os/src/components/ui/select.tsx +190 -0
- package/packages/gatsaeng-os/src/components/ui/sheet.tsx +143 -0
- package/packages/gatsaeng-os/src/components/ui/tabs.tsx +91 -0
- package/packages/gatsaeng-os/src/components/ui/toggle-group.tsx +83 -0
- package/packages/gatsaeng-os/src/components/ui/toggle.tsx +47 -0
- package/packages/gatsaeng-os/src/components/ui/tooltip.tsx +57 -0
- package/packages/gatsaeng-os/src/hooks/useAreas.ts +53 -0
- package/packages/gatsaeng-os/src/hooks/useBooks.ts +62 -0
- package/packages/gatsaeng-os/src/hooks/useCalendar.ts +59 -0
- package/packages/gatsaeng-os/src/hooks/useDaily.ts +15 -0
- package/packages/gatsaeng-os/src/hooks/useGlobalTasks.ts +45 -0
- package/packages/gatsaeng-os/src/hooks/useGoals.ts +53 -0
- package/packages/gatsaeng-os/src/hooks/useMilestones.ts +75 -0
- package/packages/gatsaeng-os/src/hooks/useNotes.ts +65 -0
- package/packages/gatsaeng-os/src/hooks/useProjects.ts +102 -0
- package/packages/gatsaeng-os/src/hooks/useRoutines.ts +76 -0
- package/packages/gatsaeng-os/src/hooks/useTiming.ts +27 -0
- package/packages/gatsaeng-os/src/lib/apiFetch.ts +14 -0
- package/packages/gatsaeng-os/src/lib/auth.ts +32 -0
- package/packages/gatsaeng-os/src/lib/date.ts +7 -0
- package/packages/gatsaeng-os/src/lib/editor/markdown.ts +35 -0
- package/packages/gatsaeng-os/src/lib/llm-governor.ts +167 -0
- package/packages/gatsaeng-os/src/lib/neuroscience/energyCycle.ts +35 -0
- package/packages/gatsaeng-os/src/lib/neuroscience/habitStack.ts +22 -0
- package/packages/gatsaeng-os/src/lib/neuroscience/scoring.ts +32 -0
- package/packages/gatsaeng-os/src/lib/routes.ts +15 -0
- package/packages/gatsaeng-os/src/lib/utils.ts +6 -0
- package/packages/gatsaeng-os/src/lib/vault/config.ts +29 -0
- package/packages/gatsaeng-os/src/lib/vault/frontmatter.ts +84 -0
- package/packages/gatsaeng-os/src/lib/vault/index.ts +180 -0
- package/packages/gatsaeng-os/src/lib/vault/schemas.ts +274 -0
- package/packages/gatsaeng-os/src/middleware.ts +34 -0
- package/packages/gatsaeng-os/src/stores/dashboardStore.ts +26 -0
- package/packages/gatsaeng-os/src/stores/favoritesStore.ts +47 -0
- package/packages/gatsaeng-os/src/stores/timerStore.ts +65 -0
- package/packages/gatsaeng-os/src/types/index.ts +320 -0
- package/packages/gatsaeng-os/tsconfig.json +34 -0
- package/templates/scripts/forge_qa.sh.tmpl +237 -0
- package/templates/scripts/forge_ship.sh.tmpl +183 -0
- package/templates/scripts/session_indexer.py.tmpl +420 -0
- package/templates/scripts/tracer.py.tmpl +266 -0
- package/templates/workspace/AGENTS.md.tmpl +190 -0
- package/templates/workspace/BOOTSTRAP.md.tmpl +27 -0
- package/templates/workspace/HEARTBEAT.md.tmpl +23 -0
- package/templates/workspace/MEMORY.md.tmpl +35 -0
- package/templates/workspace/SOUL.md.tmpl +258 -0
- package/templates/workspace/TOOLS.md.tmpl +28 -0
- package/templates/workspace/USER.md.tmpl +43 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Morning Briefing & Evening Check-in Engine."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, timedelta
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import vault_io
|
|
7
|
+
import timing_engine
|
|
8
|
+
import scoring
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_morning_briefing() -> str:
|
|
12
|
+
"""아침 브리핑 생성 — 오늘의 갓생 계획."""
|
|
13
|
+
today = date.today()
|
|
14
|
+
parts = [f"## 🧭 {today.isoformat()} 모닝 브리핑\n"]
|
|
15
|
+
|
|
16
|
+
# 1. Timing context
|
|
17
|
+
timing_text = timing_engine.get_monthly_briefing()
|
|
18
|
+
if timing_text:
|
|
19
|
+
parts.append(f"### 이달 운기\n{timing_text}\n")
|
|
20
|
+
|
|
21
|
+
# 2. Yesterday summary
|
|
22
|
+
yesterday_score = vault_io.get_yesterday_score()
|
|
23
|
+
streaks = vault_io.get_active_streaks()
|
|
24
|
+
parts.append(f"### 어제 결산")
|
|
25
|
+
parts.append(f"- 스코어: {yesterday_score}점")
|
|
26
|
+
parts.append(f"- 연속: {streaks['current']}일 (최장: {streaks['longest']}일)\n")
|
|
27
|
+
|
|
28
|
+
# 3. Today's routines
|
|
29
|
+
routines = vault_io.get_routines_with_status()
|
|
30
|
+
today_dow = today.isoweekday()
|
|
31
|
+
scheduled = [r for r in routines if today_dow in r.get('scheduled_days', [1,2,3,4,5,6,7])]
|
|
32
|
+
if scheduled:
|
|
33
|
+
parts.append(f"### 오늘 루틴 ({len(scheduled)}개)")
|
|
34
|
+
for r in scheduled:
|
|
35
|
+
parts.append(f"- ⬜ {r.get('title', '?')}")
|
|
36
|
+
parts.append('')
|
|
37
|
+
|
|
38
|
+
# 4. D-day alerts
|
|
39
|
+
ddays = vault_io.get_upcoming_ddays(30)
|
|
40
|
+
urgent = [m for m in ddays if m.get('d_day', 999) <= 7]
|
|
41
|
+
if urgent:
|
|
42
|
+
parts.append("### D-day 경고")
|
|
43
|
+
for m in urgent:
|
|
44
|
+
parts.append(f"- D-{m.get('d_day', '?')}: {m.get('title', '?')}")
|
|
45
|
+
parts.append('')
|
|
46
|
+
|
|
47
|
+
# 5. Skipped routine alerts
|
|
48
|
+
skips = vault_io.get_consecutive_skips()
|
|
49
|
+
critical = {rid: count for rid, count in skips.items() if count >= 2}
|
|
50
|
+
if critical:
|
|
51
|
+
parts.append("### 주의 — 연속 미실행")
|
|
52
|
+
for rid, count in critical.items():
|
|
53
|
+
routine = vault_io.get_entity('routines', rid)
|
|
54
|
+
name = routine['data'].get('title', rid) if routine else rid
|
|
55
|
+
parts.append(f"- {name}: {count}일 연속 스킵")
|
|
56
|
+
parts.append('')
|
|
57
|
+
|
|
58
|
+
# 6. Focus suggestion based on energy
|
|
59
|
+
profile = vault_io.read_profile()
|
|
60
|
+
peak_hours = profile.get('peak_hours', [9, 10, 11])
|
|
61
|
+
parts.append(f"### 오늘 포커스 타임")
|
|
62
|
+
parts.append(f"피크 시간대: {', '.join(str(h) + '시' for h in peak_hours)}")
|
|
63
|
+
|
|
64
|
+
return '\n'.join(parts)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def generate_evening_checkin() -> str:
|
|
68
|
+
"""저녁 체크인 — 오늘의 성과 리뷰."""
|
|
69
|
+
parts = ["## 저녁 체크인\n"]
|
|
70
|
+
|
|
71
|
+
# Today's score
|
|
72
|
+
today_score = vault_io.get_today_score()
|
|
73
|
+
parts.append(f"### 오늘 스코어: {today_score}점\n")
|
|
74
|
+
|
|
75
|
+
# Routine completion
|
|
76
|
+
routines = vault_io.get_routines_with_status()
|
|
77
|
+
done = [r for r in routines if r.get('completed_today')]
|
|
78
|
+
not_done = [r for r in routines if not r.get('completed_today')]
|
|
79
|
+
|
|
80
|
+
if done:
|
|
81
|
+
parts.append(f"### 완료 ({len(done)}개)")
|
|
82
|
+
for r in done:
|
|
83
|
+
parts.append(f"- ✅ {r.get('title', '?')}")
|
|
84
|
+
parts.append('')
|
|
85
|
+
|
|
86
|
+
if not_done:
|
|
87
|
+
parts.append(f"### 미완료 ({len(not_done)}개)")
|
|
88
|
+
for r in not_done:
|
|
89
|
+
parts.append(f"- ⬜ {r.get('title', '?')}")
|
|
90
|
+
parts.append('')
|
|
91
|
+
|
|
92
|
+
# Streak status
|
|
93
|
+
streaks = vault_io.get_active_streaks()
|
|
94
|
+
total_routines = len(routines)
|
|
95
|
+
done_count = len(done)
|
|
96
|
+
|
|
97
|
+
if total_routines > 0 and done_count == total_routines:
|
|
98
|
+
parts.append(f"전체 루틴 완료! 연속 {streaks['current']}일 🔥")
|
|
99
|
+
elif done_count >= total_routines * 0.5:
|
|
100
|
+
parts.append(f"절반 이상 완료. 내일은 전체 클리어를 목표로.")
|
|
101
|
+
else:
|
|
102
|
+
parts.append(f"오늘 실행률 낮음. 내일은 최소 {min(3, total_routines)}개 목표.")
|
|
103
|
+
|
|
104
|
+
return '\n'.join(parts)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def generate_weekly_summary() -> str:
|
|
108
|
+
"""주간 요약 리포트."""
|
|
109
|
+
today = date.today()
|
|
110
|
+
parts = [f"## 주간 리포트 ({(today - timedelta(days=6)).isoformat()} ~ {today.isoformat()})\n"]
|
|
111
|
+
|
|
112
|
+
# Weekly scores
|
|
113
|
+
total = 0
|
|
114
|
+
daily_scores = []
|
|
115
|
+
for i in range(7):
|
|
116
|
+
d = (today - timedelta(days=i)).isoformat()
|
|
117
|
+
manifest = vault_io.get_entity_by_date('tasks', d)
|
|
118
|
+
score = manifest['data'].get('gatsaeng_score', 0) if manifest else 0
|
|
119
|
+
total += score
|
|
120
|
+
daily_scores.append((d, score))
|
|
121
|
+
|
|
122
|
+
parts.append(f"### 총 스코어: {total}점")
|
|
123
|
+
parts.append("일별:")
|
|
124
|
+
for d, s in reversed(daily_scores):
|
|
125
|
+
bar = '█' * min(s // 10, 20)
|
|
126
|
+
parts.append(f" {d[-5:]}: {bar} {s}")
|
|
127
|
+
parts.append('')
|
|
128
|
+
|
|
129
|
+
# Goals progress
|
|
130
|
+
goals = vault_io.get_goals_with_milestones()
|
|
131
|
+
if goals:
|
|
132
|
+
parts.append("### 목표 진행")
|
|
133
|
+
for g in goals:
|
|
134
|
+
tv = g.get('target_value', 0)
|
|
135
|
+
cv = g.get('current_value', 0)
|
|
136
|
+
if tv > 0:
|
|
137
|
+
pct = round(cv / tv * 100)
|
|
138
|
+
parts.append(f"- {g.get('title', '?')}: {pct}% ({cv}/{tv})")
|
|
139
|
+
parts.append('')
|
|
140
|
+
|
|
141
|
+
# Streaks
|
|
142
|
+
streaks = vault_io.get_active_streaks()
|
|
143
|
+
parts.append(f"### 스트릭: {streaks['current']}일 (최장: {streaks['longest']}일)")
|
|
144
|
+
|
|
145
|
+
return '\n'.join(parts)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
|
|
5
|
+
load_dotenv()
|
|
6
|
+
|
|
7
|
+
# Vault
|
|
8
|
+
VAULT_PATH = Path(os.getenv('VAULT_PATH', os.path.expanduser('~/Documents/EVE-obsidian/EVE/GatsaengOS')))
|
|
9
|
+
|
|
10
|
+
# Folders
|
|
11
|
+
FOLDERS = {
|
|
12
|
+
'areas': VAULT_PATH / 'areas',
|
|
13
|
+
'goals': VAULT_PATH / 'goals',
|
|
14
|
+
'milestones': VAULT_PATH / 'milestones',
|
|
15
|
+
'projects': VAULT_PATH / 'projects',
|
|
16
|
+
'tasks': VAULT_PATH / 'tasks',
|
|
17
|
+
'routines': VAULT_PATH / 'routines',
|
|
18
|
+
'reviews': VAULT_PATH / 'reviews',
|
|
19
|
+
'sessions': VAULT_PATH / 'sessions',
|
|
20
|
+
'timing': VAULT_PATH / 'timing',
|
|
21
|
+
'routine_logs': VAULT_PATH / 'logs' / 'routine',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
PROFILE_PATH = VAULT_PATH / 'profile.md'
|
|
25
|
+
|
|
26
|
+
# API
|
|
27
|
+
ANTHROPIC_API_KEY = os.getenv('ANTHROPIC_API_KEY', '')
|
|
28
|
+
AGENT_MODEL = os.getenv('AGENT_MODEL', 'claude-sonnet-4-6')
|
|
29
|
+
|
|
30
|
+
# Telegram
|
|
31
|
+
TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN', '')
|
|
32
|
+
TELEGRAM_CHAT_ID = os.getenv('TELEGRAM_CHAT_ID', '')
|
|
33
|
+
|
|
34
|
+
# Server
|
|
35
|
+
DASHBOARD_PORT = int(os.getenv('DASHBOARD_PORT', '8765'))
|
|
36
|
+
|
|
37
|
+
# Schedule
|
|
38
|
+
MORNING_BRIEFING = os.getenv('MORNING_BRIEFING', '07:30')
|
|
39
|
+
EVENING_CHECKIN = os.getenv('EVENING_CHECKIN', '22:00')
|
|
40
|
+
WEEKLY_REVIEW_DAY = os.getenv('WEEKLY_REVIEW_DAY', 'sunday')
|
|
41
|
+
PROACTIVE_CHECK_INTERVAL = int(os.getenv('PROACTIVE_CHECK_INTERVAL_HOURS', '6'))
|
|
42
|
+
|
|
43
|
+
# Constraints
|
|
44
|
+
LIMITS = {
|
|
45
|
+
'MAX_ACTIVE_GOALS': 5,
|
|
46
|
+
'MAX_ACTIVE_ROUTINES': 6,
|
|
47
|
+
'MAX_DAILY_TASKS': 6,
|
|
48
|
+
'MAX_ACTIVE_PROJECTS': 3,
|
|
49
|
+
'MAX_MILESTONES_PER_GOAL': 4,
|
|
50
|
+
'MIN_REANALYSIS_WEEKS': 2,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# saju-helper workspace (for KB references)
|
|
54
|
+
SAJU_HELPER_WORKSPACE = Path(os.path.expanduser('~/.openclaw/workspace-saju-helper'))
|
|
55
|
+
SAJU_KB_PATH = Path(os.path.expanduser('~/Documents/saju-kb'))
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Goal Context Agent — Claude API를 사용한 목표 진단 + 마일스톤 제안."""
|
|
2
|
+
|
|
3
|
+
from anthropic import Anthropic
|
|
4
|
+
from typing import Optional
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import vault_io
|
|
8
|
+
from config import ANTHROPIC_API_KEY, AGENT_MODEL, LIMITS
|
|
9
|
+
|
|
10
|
+
client = Anthropic(api_key=ANTHROPIC_API_KEY) if ANTHROPIC_API_KEY else None
|
|
11
|
+
|
|
12
|
+
SYSTEM_PROMPT = """당신은 갓생 OS의 Goal Context Agent입니다.
|
|
13
|
+
사용자의 목표 데이터, 마일스톤 진행률, 루틴 실행 기록, 사주 타이밍을 분석하여:
|
|
14
|
+
|
|
15
|
+
1. **진단 (ai_diagnosis)**: 현재 목표 상태를 2-3문장으로 평가
|
|
16
|
+
2. **방향 (ai_direction)**: 다음 2주간 구체적 행동 제안 (1-2문장)
|
|
17
|
+
3. **마일스톤 제안 (suggested_milestones)**: 필요시 새 마일스톤 제안 (max 4개/목표)
|
|
18
|
+
|
|
19
|
+
규칙:
|
|
20
|
+
- 반말, 직설적, 숫자 기반
|
|
21
|
+
- "잘 될 거야" 같은 위로 금지
|
|
22
|
+
- 사주 타이밍이 있으면 "이 시기에 ~에 유리/불리" 형태만 사용
|
|
23
|
+
- 마일스톤은 측정 가능한 수치 목표만
|
|
24
|
+
- 전체 답변은 JSON으로
|
|
25
|
+
|
|
26
|
+
응답 형식:
|
|
27
|
+
{
|
|
28
|
+
"ai_diagnosis": "...",
|
|
29
|
+
"ai_direction": "...",
|
|
30
|
+
"ai_next_review": "YYYY-MM-DD",
|
|
31
|
+
"suggested_milestones": [
|
|
32
|
+
{"title": "...", "target_value": N, "unit": "...", "due_date": "YYYY-MM-DD"}
|
|
33
|
+
]
|
|
34
|
+
}"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_context(goal_id: str) -> str:
|
|
38
|
+
"""Build context string for the agent."""
|
|
39
|
+
goal = vault_io.read_goal(goal_id)
|
|
40
|
+
if not goal:
|
|
41
|
+
return ''
|
|
42
|
+
|
|
43
|
+
parts = [f"## 목표: {goal['data'].get('title', '')}"]
|
|
44
|
+
|
|
45
|
+
# Goal details
|
|
46
|
+
gd = goal['data']
|
|
47
|
+
parts.append(f"- 유형: {gd.get('type', '?')}")
|
|
48
|
+
parts.append(f"- 상태: {gd.get('status', '?')}")
|
|
49
|
+
if gd.get('target_value'):
|
|
50
|
+
parts.append(f"- 진행: {gd.get('current_value', 0)}/{gd.get('target_value', 0)} {gd.get('unit', '')}")
|
|
51
|
+
if gd.get('due_date'):
|
|
52
|
+
parts.append(f"- 마감: {gd.get('due_date')}")
|
|
53
|
+
if gd.get('why_statement'):
|
|
54
|
+
parts.append(f"- Why: {gd.get('why_statement')}")
|
|
55
|
+
if gd.get('identity_statement'):
|
|
56
|
+
parts.append(f"- Identity: {gd.get('identity_statement')}")
|
|
57
|
+
if goal.get('content'):
|
|
58
|
+
parts.append(f"\n### 노트:\n{goal['content']}")
|
|
59
|
+
|
|
60
|
+
# Key metrics
|
|
61
|
+
metrics = gd.get('key_metrics', [])
|
|
62
|
+
if metrics:
|
|
63
|
+
parts.append('\n### 핵심 지표:')
|
|
64
|
+
for m in metrics:
|
|
65
|
+
parts.append(f"- {m.get('name', '?')}: {m.get('current', 0)}/{m.get('target', 0)} {m.get('unit', '')}")
|
|
66
|
+
|
|
67
|
+
# Milestones
|
|
68
|
+
milestones = vault_io.get_active_milestones()
|
|
69
|
+
goal_milestones = [m for m in milestones if m.get('goal_id') == goal_id]
|
|
70
|
+
if goal_milestones:
|
|
71
|
+
parts.append(f'\n### 마일스톤 ({len(goal_milestones)}/{LIMITS["MAX_MILESTONES_PER_GOAL"]}):')
|
|
72
|
+
for m in goal_milestones:
|
|
73
|
+
progress = round(m.get('current_value', 0) / max(m.get('target_value', 1), 1) * 100)
|
|
74
|
+
parts.append(f"- {m.get('title', '?')}: {progress}% (D{'-' if m.get('d_day', 0) >= 0 else '+'}{abs(m.get('d_day', 0))})")
|
|
75
|
+
|
|
76
|
+
# Linked routines
|
|
77
|
+
routines = vault_io.get_routines_with_status()
|
|
78
|
+
linked = [r for r in routines if r.get('goal_id') == goal_id]
|
|
79
|
+
if linked:
|
|
80
|
+
parts.append('\n### 연결된 루틴:')
|
|
81
|
+
for r in linked:
|
|
82
|
+
status = '✅' if r.get('completed_today') else '⬜'
|
|
83
|
+
parts.append(f"- {status} {r.get('title', '?')} (연속 {r.get('streak', 0)}일)")
|
|
84
|
+
|
|
85
|
+
# Timing context
|
|
86
|
+
timing = vault_io.get_current_timing()
|
|
87
|
+
if timing:
|
|
88
|
+
parts.append(f"\n### 이달 운기:")
|
|
89
|
+
parts.append(f"- 주기: {timing.get('pillar', '?')} ({timing.get('rating', '?')}/5)")
|
|
90
|
+
parts.append(f"- 테마: {timing.get('theme', '?')}")
|
|
91
|
+
parts.append(f"- 인사이트: {timing.get('insight', '?')}")
|
|
92
|
+
|
|
93
|
+
# Recent scores
|
|
94
|
+
today_score = vault_io.get_today_score()
|
|
95
|
+
yesterday_score = vault_io.get_yesterday_score()
|
|
96
|
+
parts.append(f"\n### 스코어: 오늘 {today_score}점 / 어제 {yesterday_score}점")
|
|
97
|
+
|
|
98
|
+
# Context data files
|
|
99
|
+
context_files = vault_io.read_context_data(goal_id)
|
|
100
|
+
if context_files:
|
|
101
|
+
parts.append(f"\n### 업로드된 컨텍스트 데이터: {len(context_files)}개")
|
|
102
|
+
for cf in context_files:
|
|
103
|
+
parts.append(f"- {cf['name']} ({cf['size']} bytes)")
|
|
104
|
+
|
|
105
|
+
return '\n'.join(parts)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def analyze_goal(goal_id: str) -> Optional[dict]:
|
|
109
|
+
"""Run Goal Context Agent analysis."""
|
|
110
|
+
if not client:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
context = build_context(goal_id)
|
|
114
|
+
if not context:
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
response = client.messages.create(
|
|
118
|
+
model=AGENT_MODEL,
|
|
119
|
+
max_tokens=1024,
|
|
120
|
+
system=SYSTEM_PROMPT,
|
|
121
|
+
messages=[
|
|
122
|
+
{'role': 'user', 'content': context}
|
|
123
|
+
],
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if not response.content:
|
|
127
|
+
return None
|
|
128
|
+
text = response.content[0].text
|
|
129
|
+
|
|
130
|
+
# Parse JSON from response
|
|
131
|
+
try:
|
|
132
|
+
# Handle potential markdown code blocks
|
|
133
|
+
if '```json' in text:
|
|
134
|
+
text = text.split('```json')[1].split('```')[0]
|
|
135
|
+
elif '```' in text:
|
|
136
|
+
text = text.split('```')[1].split('```')[0]
|
|
137
|
+
result = json.loads(text.strip())
|
|
138
|
+
except (json.JSONDecodeError, IndexError):
|
|
139
|
+
result = {
|
|
140
|
+
'ai_diagnosis': text[:200],
|
|
141
|
+
'ai_direction': '',
|
|
142
|
+
'ai_next_review': '',
|
|
143
|
+
'suggested_milestones': [],
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def run(goal_id: str) -> dict:
|
|
150
|
+
"""Full agent run: analyze + update goal + create milestones."""
|
|
151
|
+
result = analyze_goal(goal_id)
|
|
152
|
+
if not result:
|
|
153
|
+
return {'error': 'Analysis failed or API key missing'}
|
|
154
|
+
|
|
155
|
+
# Update goal with AI fields
|
|
156
|
+
updates = {}
|
|
157
|
+
if result.get('ai_diagnosis'):
|
|
158
|
+
updates['ai_diagnosis'] = result['ai_diagnosis']
|
|
159
|
+
if result.get('ai_direction'):
|
|
160
|
+
updates['ai_direction'] = result['ai_direction']
|
|
161
|
+
if result.get('ai_next_review'):
|
|
162
|
+
updates['ai_next_review'] = result['ai_next_review']
|
|
163
|
+
|
|
164
|
+
if updates:
|
|
165
|
+
vault_io.update_entity('goals', goal_id, updates)
|
|
166
|
+
|
|
167
|
+
# Create suggested milestones (respecting limit)
|
|
168
|
+
created_milestones = []
|
|
169
|
+
existing = vault_io.get_active_milestones()
|
|
170
|
+
goal_ms_count = len([m for m in existing if m.get('goal_id') == goal_id])
|
|
171
|
+
|
|
172
|
+
for ms in result.get('suggested_milestones', []):
|
|
173
|
+
if goal_ms_count >= LIMITS['MAX_MILESTONES_PER_GOAL']:
|
|
174
|
+
break
|
|
175
|
+
new_ms = vault_io.create_entity('milestones', {
|
|
176
|
+
'goal_id': goal_id,
|
|
177
|
+
'title': ms.get('title', ''),
|
|
178
|
+
'target_value': ms.get('target_value', 0),
|
|
179
|
+
'current_value': 0,
|
|
180
|
+
'unit': ms.get('unit', ''),
|
|
181
|
+
'due_date': ms.get('due_date', ''),
|
|
182
|
+
'status': 'active',
|
|
183
|
+
'created_by': 'ai',
|
|
184
|
+
})
|
|
185
|
+
created_milestones.append(new_ms)
|
|
186
|
+
goal_ms_count += 1
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
'diagnosis': result.get('ai_diagnosis'),
|
|
190
|
+
'direction': result.get('ai_direction'),
|
|
191
|
+
'next_review': result.get('ai_next_review'),
|
|
192
|
+
'milestones_created': len(created_milestones),
|
|
193
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""격국 기여도 판정 — 행동이 격국 방향 전진인지 판단."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import vault_io
|
|
6
|
+
import timing_engine
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# DK의 격국 핵심 요소 (memory/helper/profile.md 기반)
|
|
10
|
+
# 편재격: 확장/실행/결과 중심
|
|
11
|
+
GYEOKGUK_KEYWORDS = {
|
|
12
|
+
'forward': [
|
|
13
|
+
'실행', '완료', '도전', '확장', '성장', '네트워킹', '협업',
|
|
14
|
+
'마케팅', '세일즈', '사업', '투자', '결과물', '런칭',
|
|
15
|
+
'결정', '추진', '시도', '테스트', '제안', '계약',
|
|
16
|
+
],
|
|
17
|
+
'backward': [
|
|
18
|
+
'관망', '미루기', '회피', '불안', '과분석', '완벽주의',
|
|
19
|
+
'소극적', '거절', '포기', '삭제', '취소',
|
|
20
|
+
],
|
|
21
|
+
'neutral': [
|
|
22
|
+
'학습', '연구', '정리', '계획', '분석', '리팩토링',
|
|
23
|
+
'유지', '모니터링', '기록',
|
|
24
|
+
],
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def judge_action(action_text: str) -> dict:
|
|
29
|
+
"""행동 텍스트가 격국 방향 전진인지 판정.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
{
|
|
33
|
+
'direction': 'forward' | 'backward' | 'neutral',
|
|
34
|
+
'score': 1 | -1 | 0,
|
|
35
|
+
'reason': str
|
|
36
|
+
}
|
|
37
|
+
"""
|
|
38
|
+
text_lower = action_text.lower()
|
|
39
|
+
|
|
40
|
+
forward_hits = [kw for kw in GYEOKGUK_KEYWORDS['forward'] if kw in text_lower]
|
|
41
|
+
backward_hits = [kw for kw in GYEOKGUK_KEYWORDS['backward'] if kw in text_lower]
|
|
42
|
+
|
|
43
|
+
if forward_hits and not backward_hits:
|
|
44
|
+
return {
|
|
45
|
+
'direction': 'forward',
|
|
46
|
+
'score': 1,
|
|
47
|
+
'reason': f"격국 전진: {', '.join(forward_hits[:3])}",
|
|
48
|
+
}
|
|
49
|
+
elif backward_hits and not forward_hits:
|
|
50
|
+
return {
|
|
51
|
+
'direction': 'backward',
|
|
52
|
+
'score': -1,
|
|
53
|
+
'reason': f"격국 후퇴: {', '.join(backward_hits[:3])}",
|
|
54
|
+
}
|
|
55
|
+
elif forward_hits and backward_hits:
|
|
56
|
+
# 둘 다 포함 → 비율로 판정
|
|
57
|
+
if len(forward_hits) > len(backward_hits):
|
|
58
|
+
return {
|
|
59
|
+
'direction': 'forward',
|
|
60
|
+
'score': 1,
|
|
61
|
+
'reason': f"전진 우세: {', '.join(forward_hits[:2])} vs {', '.join(backward_hits[:1])}",
|
|
62
|
+
}
|
|
63
|
+
else:
|
|
64
|
+
return {
|
|
65
|
+
'direction': 'neutral',
|
|
66
|
+
'score': 0,
|
|
67
|
+
'reason': "전진/후퇴 혼재. 행동 방향 명확히 할 것.",
|
|
68
|
+
}
|
|
69
|
+
else:
|
|
70
|
+
return {
|
|
71
|
+
'direction': 'neutral',
|
|
72
|
+
'score': 0,
|
|
73
|
+
'reason': "격국 방향성 미감지. 행동이 구체적이어야 판정 가능.",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def judge_daily_actions() -> dict:
|
|
78
|
+
"""오늘의 완료된 행동들의 격국 기여도 합산."""
|
|
79
|
+
routines = vault_io.get_routines_with_status()
|
|
80
|
+
done_routines = [r for r in routines if r.get('completed_today')]
|
|
81
|
+
|
|
82
|
+
forward = 0
|
|
83
|
+
backward = 0
|
|
84
|
+
neutral = 0
|
|
85
|
+
details = []
|
|
86
|
+
|
|
87
|
+
for r in done_routines:
|
|
88
|
+
result = judge_action(r.get('title', ''))
|
|
89
|
+
details.append({
|
|
90
|
+
'action': r.get('title', ''),
|
|
91
|
+
**result,
|
|
92
|
+
})
|
|
93
|
+
if result['score'] > 0:
|
|
94
|
+
forward += 1
|
|
95
|
+
elif result['score'] < 0:
|
|
96
|
+
backward += 1
|
|
97
|
+
else:
|
|
98
|
+
neutral += 1
|
|
99
|
+
|
|
100
|
+
total = forward + backward + neutral
|
|
101
|
+
net_score = forward - backward
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
'date': vault_io.read_today_tasks()['data'].get('date', ''),
|
|
105
|
+
'forward': forward,
|
|
106
|
+
'backward': backward,
|
|
107
|
+
'neutral': neutral,
|
|
108
|
+
'net_score': net_score,
|
|
109
|
+
'verdict': _verdict(net_score, total),
|
|
110
|
+
'details': details,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _verdict(net_score: int, total: int) -> str:
|
|
115
|
+
"""판정 문구."""
|
|
116
|
+
if total == 0:
|
|
117
|
+
return "행동 없음. 0은 0이다."
|
|
118
|
+
if net_score > 0:
|
|
119
|
+
return f"오늘 격국 방향 전진 (+{net_score}). 이 방향 유지."
|
|
120
|
+
elif net_score < 0:
|
|
121
|
+
return f"오늘 격국 방향 후퇴 ({net_score}). 관망 본능 감지. 내일 실행 1개라도 추가."
|
|
122
|
+
else:
|
|
123
|
+
return "중립. '했다'는 성장이 아니다. 방향성 있는 행동을 추가하라."
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_growth_scorecard() -> dict:
|
|
127
|
+
"""성장 스코어카드 — 최근 7일 격국 기여도."""
|
|
128
|
+
from datetime import date, timedelta
|
|
129
|
+
|
|
130
|
+
today = date.today()
|
|
131
|
+
weekly_forward = 0
|
|
132
|
+
weekly_backward = 0
|
|
133
|
+
daily_results = []
|
|
134
|
+
|
|
135
|
+
for i in range(7):
|
|
136
|
+
d = (today - timedelta(days=i)).isoformat()
|
|
137
|
+
log = vault_io.get_entity_by_date('routine_logs', d)
|
|
138
|
+
if not log:
|
|
139
|
+
daily_results.append({'date': d, 'net_score': 0, 'actions': 0})
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
completions = log['data'].get('completions', [])
|
|
143
|
+
day_forward = 0
|
|
144
|
+
day_backward = 0
|
|
145
|
+
|
|
146
|
+
for c in completions:
|
|
147
|
+
rid = c.get('routine_id', '')
|
|
148
|
+
routine = vault_io.get_entity('routines', rid)
|
|
149
|
+
if routine:
|
|
150
|
+
result = judge_action(routine['data'].get('title', ''))
|
|
151
|
+
if result['score'] > 0:
|
|
152
|
+
day_forward += 1
|
|
153
|
+
elif result['score'] < 0:
|
|
154
|
+
day_backward += 1
|
|
155
|
+
|
|
156
|
+
weekly_forward += day_forward
|
|
157
|
+
weekly_backward += day_backward
|
|
158
|
+
daily_results.append({
|
|
159
|
+
'date': d,
|
|
160
|
+
'net_score': day_forward - day_backward,
|
|
161
|
+
'actions': len(completions),
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
'period': f"{(today - timedelta(days=6)).isoformat()} ~ {today.isoformat()}",
|
|
166
|
+
'weekly_forward': weekly_forward,
|
|
167
|
+
'weekly_backward': weekly_backward,
|
|
168
|
+
'weekly_net': weekly_forward - weekly_backward,
|
|
169
|
+
'daily': list(reversed(daily_results)),
|
|
170
|
+
'verdict': _verdict(weekly_forward - weekly_backward, weekly_forward + weekly_backward),
|
|
171
|
+
}
|