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.
Files changed (180) hide show
  1. package/README.md +77 -0
  2. package/bin/arete.js +156 -0
  3. package/bin/create.js +111 -0
  4. package/lib/install-openclaw.js +50 -0
  5. package/lib/scaffold.js +213 -0
  6. package/lib/setup-wizard.js +88 -0
  7. package/lib/updater.js +130 -0
  8. package/package.json +34 -0
  9. package/packages/gatsaeng-os/README.md +36 -0
  10. package/packages/gatsaeng-os/components.json +23 -0
  11. package/packages/gatsaeng-os/eslint.config.mjs +18 -0
  12. package/packages/gatsaeng-os/next.config.ts +7 -0
  13. package/packages/gatsaeng-os/package.json +59 -0
  14. package/packages/gatsaeng-os/postcss.config.mjs +7 -0
  15. package/packages/gatsaeng-os/public/file.svg +1 -0
  16. package/packages/gatsaeng-os/public/globe.svg +1 -0
  17. package/packages/gatsaeng-os/public/next.svg +1 -0
  18. package/packages/gatsaeng-os/public/vercel.svg +1 -0
  19. package/packages/gatsaeng-os/public/window.svg +1 -0
  20. package/packages/gatsaeng-os/python/api_server.py +248 -0
  21. package/packages/gatsaeng-os/python/briefing.py +145 -0
  22. package/packages/gatsaeng-os/python/config.py +55 -0
  23. package/packages/gatsaeng-os/python/goal_context_agent.py +193 -0
  24. package/packages/gatsaeng-os/python/gyeokguk.py +171 -0
  25. package/packages/gatsaeng-os/python/proactive.py +158 -0
  26. package/packages/gatsaeng-os/python/requirements.txt +11 -0
  27. package/packages/gatsaeng-os/python/run.py +28 -0
  28. package/packages/gatsaeng-os/python/scoring.py +44 -0
  29. package/packages/gatsaeng-os/python/streak.py +70 -0
  30. package/packages/gatsaeng-os/python/telegram_bot.py +331 -0
  31. package/packages/gatsaeng-os/python/timing_engine.py +117 -0
  32. package/packages/gatsaeng-os/python/vault_io.py +423 -0
  33. package/packages/gatsaeng-os/src/app/(dashboard)/areas/[id]/page.tsx +215 -0
  34. package/packages/gatsaeng-os/src/app/(dashboard)/areas/page.tsx +161 -0
  35. package/packages/gatsaeng-os/src/app/(dashboard)/books/[id]/page.tsx +215 -0
  36. package/packages/gatsaeng-os/src/app/(dashboard)/books/page.tsx +268 -0
  37. package/packages/gatsaeng-os/src/app/(dashboard)/calendar/page.tsx +379 -0
  38. package/packages/gatsaeng-os/src/app/(dashboard)/error.tsx +30 -0
  39. package/packages/gatsaeng-os/src/app/(dashboard)/focus/page.tsx +293 -0
  40. package/packages/gatsaeng-os/src/app/(dashboard)/goals/[id]/page.tsx +426 -0
  41. package/packages/gatsaeng-os/src/app/(dashboard)/goals/page.tsx +178 -0
  42. package/packages/gatsaeng-os/src/app/(dashboard)/layout.tsx +29 -0
  43. package/packages/gatsaeng-os/src/app/(dashboard)/notes/[id]/page.tsx +147 -0
  44. package/packages/gatsaeng-os/src/app/(dashboard)/notes/page.tsx +254 -0
  45. package/packages/gatsaeng-os/src/app/(dashboard)/page.tsx +26 -0
  46. package/packages/gatsaeng-os/src/app/(dashboard)/projects/[id]/page.tsx +86 -0
  47. package/packages/gatsaeng-os/src/app/(dashboard)/projects/page.tsx +215 -0
  48. package/packages/gatsaeng-os/src/app/(dashboard)/review/page.tsx +475 -0
  49. package/packages/gatsaeng-os/src/app/(dashboard)/routines/page.tsx +436 -0
  50. package/packages/gatsaeng-os/src/app/(dashboard)/tasks/[id]/page.tsx +210 -0
  51. package/packages/gatsaeng-os/src/app/(dashboard)/tasks/page.tsx +307 -0
  52. package/packages/gatsaeng-os/src/app/(dashboard)/voice/page.tsx +212 -0
  53. package/packages/gatsaeng-os/src/app/api/areas/[id]/route.ts +26 -0
  54. package/packages/gatsaeng-os/src/app/api/areas/route.ts +22 -0
  55. package/packages/gatsaeng-os/src/app/api/auth/login/route.ts +52 -0
  56. package/packages/gatsaeng-os/src/app/api/auth/logout/route.ts +8 -0
  57. package/packages/gatsaeng-os/src/app/api/books/[id]/route.ts +27 -0
  58. package/packages/gatsaeng-os/src/app/api/books/route.ts +20 -0
  59. package/packages/gatsaeng-os/src/app/api/calendar/[id]/route.ts +24 -0
  60. package/packages/gatsaeng-os/src/app/api/calendar/import/route.ts +52 -0
  61. package/packages/gatsaeng-os/src/app/api/calendar/route.ts +37 -0
  62. package/packages/gatsaeng-os/src/app/api/daily/route.ts +51 -0
  63. package/packages/gatsaeng-os/src/app/api/goals/[id]/route.ts +34 -0
  64. package/packages/gatsaeng-os/src/app/api/goals/route.ts +30 -0
  65. package/packages/gatsaeng-os/src/app/api/logs/energy/route.ts +40 -0
  66. package/packages/gatsaeng-os/src/app/api/logs/focus/route.ts +22 -0
  67. package/packages/gatsaeng-os/src/app/api/logs/routine/route.ts +54 -0
  68. package/packages/gatsaeng-os/src/app/api/milestones/[id]/route.ts +26 -0
  69. package/packages/gatsaeng-os/src/app/api/milestones/route.ts +47 -0
  70. package/packages/gatsaeng-os/src/app/api/notes/[id]/route.ts +29 -0
  71. package/packages/gatsaeng-os/src/app/api/notes/route.ts +37 -0
  72. package/packages/gatsaeng-os/src/app/api/profile/route.ts +17 -0
  73. package/packages/gatsaeng-os/src/app/api/projects/[id]/route.ts +27 -0
  74. package/packages/gatsaeng-os/src/app/api/projects/route.ts +25 -0
  75. package/packages/gatsaeng-os/src/app/api/reviews/[id]/route.ts +26 -0
  76. package/packages/gatsaeng-os/src/app/api/reviews/route.ts +29 -0
  77. package/packages/gatsaeng-os/src/app/api/routines/[id]/route.ts +26 -0
  78. package/packages/gatsaeng-os/src/app/api/routines/route.ts +28 -0
  79. package/packages/gatsaeng-os/src/app/api/tasks/[id]/route.ts +28 -0
  80. package/packages/gatsaeng-os/src/app/api/tasks/route.ts +66 -0
  81. package/packages/gatsaeng-os/src/app/api/timing/current/route.ts +63 -0
  82. package/packages/gatsaeng-os/src/app/api/voice/chat/route.ts +50 -0
  83. package/packages/gatsaeng-os/src/app/api/voice/transcribe/route.ts +25 -0
  84. package/packages/gatsaeng-os/src/app/api/voice/tts/route.ts +36 -0
  85. package/packages/gatsaeng-os/src/app/error.tsx +30 -0
  86. package/packages/gatsaeng-os/src/app/favicon.ico +0 -0
  87. package/packages/gatsaeng-os/src/app/globals.css +208 -0
  88. package/packages/gatsaeng-os/src/app/layout.tsx +33 -0
  89. package/packages/gatsaeng-os/src/app/login/page.tsx +87 -0
  90. package/packages/gatsaeng-os/src/app/providers.tsx +27 -0
  91. package/packages/gatsaeng-os/src/components/ErrorBoundary.tsx +46 -0
  92. package/packages/gatsaeng-os/src/components/dashboard/DashboardGrid.tsx +86 -0
  93. package/packages/gatsaeng-os/src/components/dashboard/DdayWidget.tsx +88 -0
  94. package/packages/gatsaeng-os/src/components/dashboard/EnergyTracker.tsx +87 -0
  95. package/packages/gatsaeng-os/src/components/dashboard/FocusTimer.tsx +139 -0
  96. package/packages/gatsaeng-os/src/components/dashboard/GatsaengScore.tsx +30 -0
  97. package/packages/gatsaeng-os/src/components/dashboard/GoalRings.tsx +107 -0
  98. package/packages/gatsaeng-os/src/components/dashboard/ProactiveBar.tsx +98 -0
  99. package/packages/gatsaeng-os/src/components/dashboard/RoutineChecklist.tsx +81 -0
  100. package/packages/gatsaeng-os/src/components/dashboard/TimingWidget.tsx +86 -0
  101. package/packages/gatsaeng-os/src/components/dashboard/WidgetCustomizer.tsx +95 -0
  102. package/packages/gatsaeng-os/src/components/dashboard/WidgetWrapper.tsx +33 -0
  103. package/packages/gatsaeng-os/src/components/dashboard/ZeigarnikPanel.tsx +43 -0
  104. package/packages/gatsaeng-os/src/components/editor/EditorToolbar.tsx +186 -0
  105. package/packages/gatsaeng-os/src/components/editor/TiptapEditor.tsx +114 -0
  106. package/packages/gatsaeng-os/src/components/layout/Header.tsx +47 -0
  107. package/packages/gatsaeng-os/src/components/layout/MobileBottomNav.tsx +122 -0
  108. package/packages/gatsaeng-os/src/components/layout/MobileSidebar.tsx +29 -0
  109. package/packages/gatsaeng-os/src/components/layout/Sidebar.tsx +142 -0
  110. package/packages/gatsaeng-os/src/components/onboarding/OnboardingFlow.tsx +229 -0
  111. package/packages/gatsaeng-os/src/components/onboarding/OnboardingGate.tsx +78 -0
  112. package/packages/gatsaeng-os/src/components/projects/CalendarView.tsx +152 -0
  113. package/packages/gatsaeng-os/src/components/projects/KanbanView.tsx +180 -0
  114. package/packages/gatsaeng-os/src/components/projects/ListView.tsx +82 -0
  115. package/packages/gatsaeng-os/src/components/projects/TableView.tsx +206 -0
  116. package/packages/gatsaeng-os/src/components/projects/TaskCard.tsx +154 -0
  117. package/packages/gatsaeng-os/src/components/projects/TaskForm.tsx +128 -0
  118. package/packages/gatsaeng-os/src/components/projects/ViewSwitcher.tsx +40 -0
  119. package/packages/gatsaeng-os/src/components/search/GlobalSearch.tsx +179 -0
  120. package/packages/gatsaeng-os/src/components/shared/InlineEdit.tsx +77 -0
  121. package/packages/gatsaeng-os/src/components/shared/PinButton.tsx +42 -0
  122. package/packages/gatsaeng-os/src/components/tasks/DDayBadge.tsx +34 -0
  123. package/packages/gatsaeng-os/src/components/ui/badge.tsx +48 -0
  124. package/packages/gatsaeng-os/src/components/ui/button.tsx +64 -0
  125. package/packages/gatsaeng-os/src/components/ui/card.tsx +92 -0
  126. package/packages/gatsaeng-os/src/components/ui/checkbox.tsx +32 -0
  127. package/packages/gatsaeng-os/src/components/ui/command.tsx +184 -0
  128. package/packages/gatsaeng-os/src/components/ui/dialog.tsx +158 -0
  129. package/packages/gatsaeng-os/src/components/ui/input.tsx +21 -0
  130. package/packages/gatsaeng-os/src/components/ui/label.tsx +24 -0
  131. package/packages/gatsaeng-os/src/components/ui/popover.tsx +89 -0
  132. package/packages/gatsaeng-os/src/components/ui/progress.tsx +31 -0
  133. package/packages/gatsaeng-os/src/components/ui/select.tsx +190 -0
  134. package/packages/gatsaeng-os/src/components/ui/sheet.tsx +143 -0
  135. package/packages/gatsaeng-os/src/components/ui/tabs.tsx +91 -0
  136. package/packages/gatsaeng-os/src/components/ui/toggle-group.tsx +83 -0
  137. package/packages/gatsaeng-os/src/components/ui/toggle.tsx +47 -0
  138. package/packages/gatsaeng-os/src/components/ui/tooltip.tsx +57 -0
  139. package/packages/gatsaeng-os/src/hooks/useAreas.ts +53 -0
  140. package/packages/gatsaeng-os/src/hooks/useBooks.ts +62 -0
  141. package/packages/gatsaeng-os/src/hooks/useCalendar.ts +59 -0
  142. package/packages/gatsaeng-os/src/hooks/useDaily.ts +15 -0
  143. package/packages/gatsaeng-os/src/hooks/useGlobalTasks.ts +45 -0
  144. package/packages/gatsaeng-os/src/hooks/useGoals.ts +53 -0
  145. package/packages/gatsaeng-os/src/hooks/useMilestones.ts +75 -0
  146. package/packages/gatsaeng-os/src/hooks/useNotes.ts +65 -0
  147. package/packages/gatsaeng-os/src/hooks/useProjects.ts +102 -0
  148. package/packages/gatsaeng-os/src/hooks/useRoutines.ts +76 -0
  149. package/packages/gatsaeng-os/src/hooks/useTiming.ts +27 -0
  150. package/packages/gatsaeng-os/src/lib/apiFetch.ts +14 -0
  151. package/packages/gatsaeng-os/src/lib/auth.ts +32 -0
  152. package/packages/gatsaeng-os/src/lib/date.ts +7 -0
  153. package/packages/gatsaeng-os/src/lib/editor/markdown.ts +35 -0
  154. package/packages/gatsaeng-os/src/lib/llm-governor.ts +167 -0
  155. package/packages/gatsaeng-os/src/lib/neuroscience/energyCycle.ts +35 -0
  156. package/packages/gatsaeng-os/src/lib/neuroscience/habitStack.ts +22 -0
  157. package/packages/gatsaeng-os/src/lib/neuroscience/scoring.ts +32 -0
  158. package/packages/gatsaeng-os/src/lib/routes.ts +15 -0
  159. package/packages/gatsaeng-os/src/lib/utils.ts +6 -0
  160. package/packages/gatsaeng-os/src/lib/vault/config.ts +29 -0
  161. package/packages/gatsaeng-os/src/lib/vault/frontmatter.ts +84 -0
  162. package/packages/gatsaeng-os/src/lib/vault/index.ts +180 -0
  163. package/packages/gatsaeng-os/src/lib/vault/schemas.ts +274 -0
  164. package/packages/gatsaeng-os/src/middleware.ts +34 -0
  165. package/packages/gatsaeng-os/src/stores/dashboardStore.ts +26 -0
  166. package/packages/gatsaeng-os/src/stores/favoritesStore.ts +47 -0
  167. package/packages/gatsaeng-os/src/stores/timerStore.ts +65 -0
  168. package/packages/gatsaeng-os/src/types/index.ts +320 -0
  169. package/packages/gatsaeng-os/tsconfig.json +34 -0
  170. package/templates/scripts/forge_qa.sh.tmpl +237 -0
  171. package/templates/scripts/forge_ship.sh.tmpl +183 -0
  172. package/templates/scripts/session_indexer.py.tmpl +420 -0
  173. package/templates/scripts/tracer.py.tmpl +266 -0
  174. package/templates/workspace/AGENTS.md.tmpl +190 -0
  175. package/templates/workspace/BOOTSTRAP.md.tmpl +27 -0
  176. package/templates/workspace/HEARTBEAT.md.tmpl +23 -0
  177. package/templates/workspace/MEMORY.md.tmpl +35 -0
  178. package/templates/workspace/SOUL.md.tmpl +258 -0
  179. package/templates/workspace/TOOLS.md.tmpl +28 -0
  180. 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
+ }