nexo-brain 5.3.19 → 5.3.21

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 (211) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/nexo-brain.js +52 -10
  3. package/package.json +1 -1
  4. package/src/auto_update.py +11 -8
  5. package/src/dashboard/static/favicon 2.svg +32 -0
  6. package/src/dashboard/static/nexo-logo 2.png +0 -0
  7. package/src/dashboard/static/nexo-logo 2.svg +40 -0
  8. package/src/dashboard/static/style 2.css +2458 -0
  9. package/src/dashboard/templates/adaptive 2.html +118 -0
  10. package/src/dashboard/templates/artifacts 2.html +133 -0
  11. package/src/dashboard/templates/backups 2.html +136 -0
  12. package/src/dashboard/templates/base 2.html +417 -0
  13. package/src/dashboard/templates/calendar 2.html +591 -0
  14. package/src/dashboard/templates/chat 2.html +356 -0
  15. package/src/dashboard/templates/claims 2.html +259 -0
  16. package/src/dashboard/templates/cortex 2.html +321 -0
  17. package/src/dashboard/templates/credentials 2.html +128 -0
  18. package/src/dashboard/templates/crons 2.html +370 -0
  19. package/src/dashboard/templates/dashboard 2.html +494 -0
  20. package/src/dashboard/templates/dreams 2.html +252 -0
  21. package/src/dashboard/templates/email 2.html +160 -0
  22. package/src/dashboard/templates/evolution 2.html +189 -0
  23. package/src/dashboard/templates/feed 2.html +249 -0
  24. package/src/dashboard/templates/followup_health 2.html +170 -0
  25. package/src/dashboard/templates/graph 2.html +201 -0
  26. package/src/dashboard/templates/guard 2.html +259 -0
  27. package/src/dashboard/templates/inbox 2.html +251 -0
  28. package/src/dashboard/templates/memory 2.html +420 -0
  29. package/src/dashboard/templates/operations 2.html +608 -0
  30. package/src/dashboard/templates/plugins 2.html +185 -0
  31. package/src/dashboard/templates/protocol 2.html +199 -0
  32. package/src/dashboard/templates/rules 2.html +246 -0
  33. package/src/dashboard/templates/sentiment 2.html +247 -0
  34. package/src/dashboard/templates/sessions 2.html +218 -0
  35. package/src/dashboard/templates/skills 2.html +329 -0
  36. package/src/dashboard/templates/somatic 2.html +73 -0
  37. package/src/dashboard/templates/triggers 2.html +133 -0
  38. package/src/dashboard/templates/trust 2.html +360 -0
  39. package/src/db/__init__ 2.py +259 -0
  40. package/src/db/_core 2.py +437 -0
  41. package/src/db/_credentials 2.py +124 -0
  42. package/src/db/_episodic 2.py +762 -0
  43. package/src/db/_evolution 2.py +54 -0
  44. package/src/db/_fts 2.py +406 -0
  45. package/src/db/_goal_profiles 2.py +376 -0
  46. package/src/db/_hot_context 2.py +660 -0
  47. package/src/db/_outcomes 2.py +800 -0
  48. package/src/db/_personal_scripts 2.py +582 -0
  49. package/src/db/_sessions 2.py +330 -0
  50. package/src/db/_tasks 2.py +91 -0
  51. package/src/db/_watchers 2.py +173 -0
  52. package/src/doctor/formatters 2.py +52 -0
  53. package/src/doctor/models 2.py +69 -0
  54. package/src/doctor/planes 2.py +87 -0
  55. package/src/doctor/providers/__init__ 2.py +1 -0
  56. package/src/doctor/providers/deep 2.py +367 -0
  57. package/src/evolution_cycle 2.py +519 -0
  58. package/src/hooks/auto_capture 2.py +208 -0
  59. package/src/hooks/caffeinate-guard 2.sh +8 -0
  60. package/src/hooks/capture-session 2.sh +21 -0
  61. package/src/hooks/capture-tool-logs 2.sh +158 -0
  62. package/src/hooks/daily-briefing-check 2.sh +33 -0
  63. package/src/hooks/heartbeat-enforcement 2.py +90 -0
  64. package/src/hooks/heartbeat-posttool 2.sh +18 -0
  65. package/src/hooks/inbox-hook 2.sh +76 -0
  66. package/src/hooks/post-compact 2.sh +152 -0
  67. package/src/hooks/pre-compact 2.sh +169 -0
  68. package/src/hooks/protocol-guardrail 2.sh +10 -0
  69. package/src/hooks/protocol-pretool-guardrail 2.sh +9 -0
  70. package/src/hooks/session-stop 2.sh +52 -0
  71. package/src/kg_populate 2.py +292 -0
  72. package/src/maintenance 2.py +53 -0
  73. package/src/memory_backends 2.py +71 -0
  74. package/src/migrate_embeddings 2.py +124 -0
  75. package/src/nexo_sdk 2.py +103 -0
  76. package/src/observability 2.py +199 -0
  77. package/src/plugin_loader 2.py +217 -0
  78. package/src/plugins/__init__ 2.py +0 -0
  79. package/src/plugins/artifact_registry 2.py +450 -0
  80. package/src/plugins/backup 2.py +127 -0
  81. package/src/plugins/claims_tools 2.py +119 -0
  82. package/src/plugins/cognitive_memory 2.py +609 -0
  83. package/src/plugins/core_rules 2.py +252 -0
  84. package/src/plugins/cortex 2.py +1155 -0
  85. package/src/plugins/entities 2.py +67 -0
  86. package/src/plugins/episodic_memory 2.py +560 -0
  87. package/src/plugins/evolution 2.py +167 -0
  88. package/src/plugins/goal_engine 2.py +142 -0
  89. package/src/plugins/guard 2.py +862 -0
  90. package/src/plugins/impact 2.py +29 -0
  91. package/src/plugins/knowledge_graph_tools 2.py +137 -0
  92. package/src/plugins/media_memory_tools 2.py +98 -0
  93. package/src/plugins/memory_export 2.py +196 -0
  94. package/src/plugins/outcomes 2.py +130 -0
  95. package/src/plugins/personal_scripts 2.py +117 -0
  96. package/src/plugins/preferences 2.py +47 -0
  97. package/src/plugins/protocol 2.py +1449 -0
  98. package/src/plugins/simple_api 2.py +106 -0
  99. package/src/plugins/skills 2.py +341 -0
  100. package/src/plugins/state_watchers 2.py +79 -0
  101. package/src/plugins/update 2.py +986 -0
  102. package/src/plugins/user_state_tools 2.py +43 -0
  103. package/src/plugins/workflow 2.py +588 -0
  104. package/src/protocol_settings 2.py +59 -0
  105. package/src/public_contribution 2.py +466 -0
  106. package/src/public_evolution_queue 2.py +241 -0
  107. package/src/requirements 2.txt +14 -0
  108. package/src/retroactive_learnings 2.py +373 -0
  109. package/src/rules/__init__ 2.py +0 -0
  110. package/src/rules/core-rules 2.json +331 -0
  111. package/src/rules/migrate 2.py +207 -0
  112. package/src/runtime_power 2.py +874 -0
  113. package/src/script_registry 2.py +1559 -0
  114. package/src/scripts/check-context 2.py +272 -0
  115. package/src/scripts/deep-sleep/apply_findings 2.py +2327 -0
  116. package/src/scripts/deep-sleep/collect 2.py +928 -0
  117. package/src/scripts/deep-sleep/extract 2.py +330 -0
  118. package/src/scripts/deep-sleep/extract-prompt 2.md +285 -0
  119. package/src/scripts/deep-sleep/synthesize 2.py +312 -0
  120. package/src/scripts/deep-sleep/synthesize-prompt 2.md +336 -0
  121. package/src/scripts/nexo-agent-run 2.py +75 -0
  122. package/src/scripts/nexo-auto-update 2.py +6 -0
  123. package/src/scripts/nexo-backup 2.sh +25 -0
  124. package/src/scripts/nexo-brain-activation 2.sh +140 -0
  125. package/src/scripts/nexo-catchup 2.py +300 -0
  126. package/src/scripts/nexo-cognitive-decay 2.py +257 -0
  127. package/src/scripts/nexo-cortex-cycle 2.py +293 -0
  128. package/src/scripts/nexo-cron-wrapper 2.sh +53 -0
  129. package/src/scripts/nexo-daily-self-audit 2.py +2161 -0
  130. package/src/scripts/nexo-dashboard 2.sh +29 -0
  131. package/src/scripts/nexo-deep-sleep 2.sh +86 -0
  132. package/src/scripts/nexo-evolution-run 2.py +1664 -0
  133. package/src/scripts/nexo-followup-hygiene 2.py +139 -0
  134. package/src/scripts/nexo-hook-record 2.py +42 -0
  135. package/src/scripts/nexo-immune 2.py +936 -0
  136. package/src/scripts/nexo-impact-scorer 2.py +117 -0
  137. package/src/scripts/nexo-inbox-hook 2.sh +74 -0
  138. package/src/scripts/nexo-install 2.py +6 -0
  139. package/src/scripts/nexo-learning-housekeep 2.py +401 -0
  140. package/src/scripts/nexo-learning-validator 2.py +266 -0
  141. package/src/scripts/nexo-migrate 2.py +260 -0
  142. package/src/scripts/nexo-outcome-checker 2.py +127 -0
  143. package/src/scripts/nexo-postmortem-consolidator 2.py +456 -0
  144. package/src/scripts/nexo-pre-commit 2.py +120 -0
  145. package/src/scripts/nexo-prevent-sleep 2.sh +35 -0
  146. package/src/scripts/nexo-proactive-dashboard 2.py +354 -0
  147. package/src/scripts/nexo-reflection 2.py +256 -0
  148. package/src/scripts/nexo-runtime-preflight 2.py +274 -0
  149. package/src/scripts/nexo-sleep 2.py +631 -0
  150. package/src/scripts/nexo-snapshot-restore 2.sh +35 -0
  151. package/src/scripts/nexo-sync-clients 2.py +16 -0
  152. package/src/scripts/nexo-synthesis 2.py +475 -0
  153. package/src/scripts/nexo-tcc-approve 2.sh +79 -0
  154. package/src/scripts/nexo-update 2.sh +306 -0
  155. package/src/scripts/nexo-watchdog 2.sh +1207 -0
  156. package/src/scripts/nexo-watchdog-smoke 2.py +119 -0
  157. package/src/scripts/rehydrate_learnings_from_archive 2.py +245 -0
  158. package/src/server 2.py +1296 -0
  159. package/src/skills/run-nexo-audit-phase/guide 2.md +43 -0
  160. package/src/skills/run-nexo-audit-phase/skill 2.json +59 -0
  161. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +17 -0
  162. package/src/skills/run-nexo-core-fix-cycle/script 2.py +276 -0
  163. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +58 -0
  164. package/src/skills/run-release-final-audit/guide 2.md +16 -0
  165. package/src/skills/run-release-final-audit/script 2.py +259 -0
  166. package/src/skills/run-release-final-audit/skill 2.json +77 -0
  167. package/src/skills/run-runtime-doctor/guide 2.md +12 -0
  168. package/src/skills/run-runtime-doctor/script 2.py +21 -0
  169. package/src/skills/run-runtime-doctor/skill 2.json +25 -0
  170. package/src/skills_runtime 2.py +932 -0
  171. package/src/state_watchers_runtime 2.py +475 -0
  172. package/src/storage_router 2.py +32 -0
  173. package/src/system_catalog 2.py +786 -0
  174. package/src/tools_coordination 2.py +103 -0
  175. package/src/tools_credentials 2.py +68 -0
  176. package/src/tools_drive 2.py +487 -0
  177. package/src/tools_hot_context 2.py +163 -0
  178. package/src/tools_learnings 2.py +612 -0
  179. package/src/tools_menu 2.py +229 -0
  180. package/src/tools_reminders 2.py +88 -0
  181. package/src/tools_reminders_crud 2.py +363 -0
  182. package/src/tools_sessions 2.py +1054 -0
  183. package/src/tools_system_catalog 2.py +19 -0
  184. package/src/tools_task_history 2.py +57 -0
  185. package/src/tools_transcripts 2.py +98 -0
  186. package/src/transcript_utils 2.py +412 -0
  187. package/src/user_context 2.py +46 -0
  188. package/src/user_data_portability 2.py +328 -0
  189. package/src/user_state_model 2.py +170 -0
  190. package/templates/CLAUDE.md 2.template +108 -0
  191. package/templates/CODEX.AGENTS.md 2.template +66 -0
  192. package/templates/launchagents/README 2.md +132 -0
  193. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +39 -0
  194. package/templates/launchagents/com.nexo.catchup 2.plist +39 -0
  195. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +40 -0
  196. package/templates/launchagents/com.nexo.dashboard 2.plist +43 -0
  197. package/templates/launchagents/com.nexo.deep-sleep 2.plist +43 -0
  198. package/templates/launchagents/com.nexo.evolution 2.plist +44 -0
  199. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +45 -0
  200. package/templates/launchagents/com.nexo.immune 2.plist +41 -0
  201. package/templates/launchagents/com.nexo.postmortem 2.plist +45 -0
  202. package/templates/launchagents/com.nexo.self-audit 2.plist +47 -0
  203. package/templates/launchagents/com.nexo.synthesis 2.plist +45 -0
  204. package/templates/launchagents/com.nexo.watchdog 2.plist +37 -0
  205. package/templates/nexo_helper 2.py +301 -0
  206. package/templates/openclaw 2.json +13 -0
  207. package/templates/plugin-template 2.py +40 -0
  208. package/templates/script-template 2.py +59 -0
  209. package/templates/script-template 2.sh +13 -0
  210. package/templates/skill-script-template 2.py +48 -0
  211. package/templates/skill-template 2.md +33 -0
@@ -0,0 +1,376 @@
1
+ from __future__ import annotations
2
+ """Goal Engine v1 — explicit optimization profiles for durable goals and decisions."""
3
+
4
+ import json
5
+
6
+ from db._core import get_db
7
+ from db._workflow import get_workflow_goal
8
+
9
+ VALID_SCOPE_TYPES = {"default", "area", "task_type", "goal_id"}
10
+ VALID_STATUSES = {"active", "disabled"}
11
+ WEIGHT_KEYS = ("impact", "success", "risk", "somatic")
12
+ DEFAULT_WEIGHTS = {
13
+ "impact": 0.35,
14
+ "success": 0.30,
15
+ "risk": 0.20,
16
+ "somatic": 0.15,
17
+ }
18
+ DEFAULT_GOAL_PROFILES = (
19
+ {
20
+ "profile_id": "default_balanced",
21
+ "profile_name": "Balanced default",
22
+ "description": "Balancea impacto, exito, riesgo y huella somatica para decisiones generales.",
23
+ "scope_type": "default",
24
+ "scope_value": "",
25
+ "goal_labels": ["maximise_success", "minimise_risk", "preserve_trust"],
26
+ "weights": DEFAULT_WEIGHTS,
27
+ "status": "active",
28
+ "source": "system",
29
+ },
30
+ {
31
+ "profile_id": "release_safety",
32
+ "profile_name": "Release safety",
33
+ "description": "Favorece decisiones reversibles y verificadas en release, deploy y cambios publicos.",
34
+ "scope_type": "area",
35
+ "scope_value": "release",
36
+ "goal_labels": ["minimise_risk", "preserve_trust", "maximise_success"],
37
+ "weights": {
38
+ "impact": 0.24,
39
+ "success": 0.28,
40
+ "risk": 0.30,
41
+ "somatic": 0.18,
42
+ },
43
+ "status": "active",
44
+ "source": "system",
45
+ },
46
+ {
47
+ "profile_id": "customer_trust",
48
+ "profile_name": "Customer trust",
49
+ "description": "Favorece decisiones que preservan confianza y reducen friccion con clientes.",
50
+ "scope_type": "area",
51
+ "scope_value": "customer",
52
+ "goal_labels": ["preserve_trust", "maximise_success", "minimise_risk"],
53
+ "weights": {
54
+ "impact": 0.25,
55
+ "success": 0.31,
56
+ "risk": 0.26,
57
+ "somatic": 0.18,
58
+ },
59
+ "status": "active",
60
+ "source": "system",
61
+ },
62
+ {
63
+ "profile_id": "ops_efficiency",
64
+ "profile_name": "Operations efficiency",
65
+ "description": "Favorece throughput operativo manteniendo riesgo contenido en ejecucion rutinaria.",
66
+ "scope_type": "task_type",
67
+ "scope_value": "execute",
68
+ "goal_labels": ["maximise_efficiency", "maximise_success", "minimise_risk"],
69
+ "weights": {
70
+ "impact": 0.38,
71
+ "success": 0.28,
72
+ "risk": 0.20,
73
+ "somatic": 0.14,
74
+ },
75
+ "status": "active",
76
+ "source": "system",
77
+ },
78
+ {
79
+ "profile_id": "business_growth",
80
+ "profile_name": "Business growth",
81
+ "description": "Da mas peso a impacto y exito cuando el contexto busca crecimiento o revenue.",
82
+ "scope_type": "area",
83
+ "scope_value": "business",
84
+ "goal_labels": ["maximise_business_impact", "maximise_success"],
85
+ "weights": {
86
+ "impact": 0.56,
87
+ "success": 0.22,
88
+ "risk": 0.14,
89
+ "somatic": 0.08,
90
+ },
91
+ "status": "active",
92
+ "source": "system",
93
+ },
94
+ )
95
+
96
+
97
+ def _parse_json(value, default):
98
+ if value in (None, ""):
99
+ return default
100
+ if isinstance(value, (dict, list)):
101
+ return value
102
+ try:
103
+ return json.loads(value)
104
+ except Exception:
105
+ return default
106
+
107
+
108
+ def _normalize_goal_labels(labels) -> list[str]:
109
+ parsed = _parse_json(labels, labels if isinstance(labels, list) else [])
110
+ if not isinstance(parsed, list):
111
+ return []
112
+ seen: set[str] = set()
113
+ result: list[str] = []
114
+ for item in parsed:
115
+ clean = str(item or "").strip()
116
+ if not clean or clean in seen:
117
+ continue
118
+ seen.add(clean)
119
+ result.append(clean)
120
+ return result
121
+
122
+
123
+ def _normalize_weights(weights) -> dict:
124
+ parsed = _parse_json(weights, weights if isinstance(weights, dict) else {})
125
+ if not isinstance(parsed, dict):
126
+ parsed = {}
127
+ collected: dict[str, float] = {}
128
+ for key in WEIGHT_KEYS:
129
+ try:
130
+ value = float(parsed.get(key, DEFAULT_WEIGHTS[key]))
131
+ except (TypeError, ValueError):
132
+ value = DEFAULT_WEIGHTS[key]
133
+ collected[key] = max(0.01, value)
134
+ total = sum(collected.values())
135
+ if total <= 0:
136
+ collected = dict(DEFAULT_WEIGHTS)
137
+ total = sum(collected.values())
138
+ return {key: round(collected[key] / total, 4) for key in WEIGHT_KEYS}
139
+
140
+
141
+ def _row_to_goal_profile(row, *, resolved_by: str = "") -> dict | None:
142
+ if not row:
143
+ return None
144
+ profile = dict(row)
145
+ profile["goal_labels"] = _normalize_goal_labels(profile.get("goal_labels"))
146
+ profile["weights"] = _normalize_weights(profile.get("weights"))
147
+ if resolved_by:
148
+ profile["resolved_by"] = resolved_by
149
+ return profile
150
+
151
+
152
+ def ensure_default_goal_profiles() -> None:
153
+ conn = get_db()
154
+ for profile in DEFAULT_GOAL_PROFILES:
155
+ conn.execute(
156
+ """INSERT OR IGNORE INTO goal_profiles (
157
+ profile_id, profile_name, description, scope_type, scope_value,
158
+ goal_labels, weights, status, source
159
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
160
+ (
161
+ profile["profile_id"],
162
+ profile["profile_name"],
163
+ profile["description"],
164
+ profile["scope_type"],
165
+ profile["scope_value"],
166
+ json.dumps(profile["goal_labels"], ensure_ascii=False),
167
+ json.dumps(_normalize_weights(profile["weights"]), ensure_ascii=False),
168
+ profile["status"],
169
+ profile["source"],
170
+ ),
171
+ )
172
+ conn.commit()
173
+
174
+
175
+ def get_goal_profile(profile_id: str) -> dict | None:
176
+ ensure_default_goal_profiles()
177
+ conn = get_db()
178
+ row = conn.execute(
179
+ "SELECT * FROM goal_profiles WHERE profile_id = ?",
180
+ ((profile_id or "").strip(),),
181
+ ).fetchone()
182
+ return _row_to_goal_profile(row)
183
+
184
+
185
+ def list_goal_profiles(*, scope_type: str = "", status: str = "active", limit: int = 50) -> list[dict]:
186
+ ensure_default_goal_profiles()
187
+ conn = get_db()
188
+ clauses = []
189
+ params: list[object] = []
190
+ clean_scope = (scope_type or "").strip()
191
+ clean_status = (status or "").strip()
192
+ if clean_scope:
193
+ clauses.append("scope_type = ?")
194
+ params.append(clean_scope)
195
+ if clean_status:
196
+ clauses.append("status = ?")
197
+ params.append(clean_status)
198
+ where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
199
+ rows = conn.execute(
200
+ f"""SELECT * FROM goal_profiles
201
+ {where}
202
+ ORDER BY
203
+ CASE scope_type
204
+ WHEN 'default' THEN 0
205
+ WHEN 'area' THEN 1
206
+ WHEN 'task_type' THEN 2
207
+ WHEN 'goal_id' THEN 3
208
+ ELSE 9
209
+ END,
210
+ profile_id ASC
211
+ LIMIT ?""",
212
+ params + [max(1, int(limit))],
213
+ ).fetchall()
214
+ return [_row_to_goal_profile(row) for row in rows if row]
215
+
216
+
217
+ def upsert_goal_profile(
218
+ *,
219
+ profile_id: str,
220
+ profile_name: str = "",
221
+ description: str = "",
222
+ scope_type: str = "default",
223
+ scope_value: str = "",
224
+ goal_labels=None,
225
+ weights=None,
226
+ status: str = "active",
227
+ source: str = "manual",
228
+ ) -> dict:
229
+ ensure_default_goal_profiles()
230
+ clean_id = (profile_id or "").strip()
231
+ if not clean_id:
232
+ raise ValueError("profile_id is required")
233
+ clean_scope = (scope_type or "default").strip()
234
+ if clean_scope not in VALID_SCOPE_TYPES:
235
+ raise ValueError(f"scope_type must be one of: {', '.join(sorted(VALID_SCOPE_TYPES))}")
236
+ clean_status = (status or "active").strip().lower()
237
+ if clean_status not in VALID_STATUSES:
238
+ raise ValueError(f"status must be one of: {', '.join(sorted(VALID_STATUSES))}")
239
+
240
+ normalized_weights = _normalize_weights(weights)
241
+ normalized_labels = _normalize_goal_labels(goal_labels)
242
+ conn = get_db()
243
+ existing = conn.execute(
244
+ "SELECT * FROM goal_profiles WHERE profile_id = ?",
245
+ (clean_id,),
246
+ ).fetchone()
247
+ if existing:
248
+ current = dict(existing)
249
+ conn.execute(
250
+ """UPDATE goal_profiles
251
+ SET profile_name = ?,
252
+ description = ?,
253
+ scope_type = ?,
254
+ scope_value = ?,
255
+ goal_labels = ?,
256
+ weights = ?,
257
+ status = ?,
258
+ source = ?,
259
+ updated_at = datetime('now')
260
+ WHERE profile_id = ?""",
261
+ (
262
+ (profile_name or current.get("profile_name") or clean_id).strip(),
263
+ (description or current.get("description") or "").strip(),
264
+ clean_scope,
265
+ (scope_value or current.get("scope_value") or "").strip().lower(),
266
+ json.dumps(normalized_labels or _normalize_goal_labels(current.get("goal_labels")), ensure_ascii=False),
267
+ json.dumps(normalized_weights, ensure_ascii=False),
268
+ clean_status,
269
+ (source or current.get("source") or "manual").strip(),
270
+ clean_id,
271
+ ),
272
+ )
273
+ else:
274
+ conn.execute(
275
+ """INSERT INTO goal_profiles (
276
+ profile_id, profile_name, description, scope_type, scope_value,
277
+ goal_labels, weights, status, source
278
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
279
+ (
280
+ clean_id,
281
+ (profile_name or clean_id).strip(),
282
+ (description or "").strip(),
283
+ clean_scope,
284
+ (scope_value or "").strip().lower(),
285
+ json.dumps(normalized_labels, ensure_ascii=False),
286
+ json.dumps(normalized_weights, ensure_ascii=False),
287
+ clean_status,
288
+ (source or "manual").strip(),
289
+ ),
290
+ )
291
+ conn.commit()
292
+ return get_goal_profile(clean_id) or {}
293
+
294
+
295
+ def resolve_goal_profile(
296
+ *,
297
+ profile_id: str = "",
298
+ area: str = "",
299
+ task_type: str = "",
300
+ goal_id: str = "",
301
+ ) -> dict:
302
+ ensure_default_goal_profiles()
303
+ conn = get_db()
304
+ explicit_id = (profile_id or "").strip()
305
+ if explicit_id:
306
+ explicit = get_goal_profile(explicit_id)
307
+ if not explicit:
308
+ raise ValueError(f"Unknown goal profile: {explicit_id}")
309
+ if explicit.get("status") != "active":
310
+ raise ValueError(f"Goal profile {explicit_id} is not active")
311
+ explicit["resolved_by"] = "explicit"
312
+ return explicit
313
+
314
+ clean_goal_id = (goal_id or "").strip()
315
+ if clean_goal_id:
316
+ workflow_goal = get_workflow_goal(clean_goal_id)
317
+ if workflow_goal:
318
+ shared_state = workflow_goal.get("shared_state") or {}
319
+ shared_profile_id = str(shared_state.get("goal_profile_id", "")).strip()
320
+ if shared_profile_id:
321
+ linked = get_goal_profile(shared_profile_id)
322
+ if linked and linked.get("status") == "active":
323
+ linked["resolved_by"] = "workflow_goal.shared_state"
324
+ return linked
325
+ row = conn.execute(
326
+ """SELECT * FROM goal_profiles
327
+ WHERE scope_type = 'goal_id' AND scope_value = ? AND status = 'active'
328
+ ORDER BY updated_at DESC, profile_id ASC
329
+ LIMIT 1""",
330
+ (clean_goal_id,),
331
+ ).fetchone()
332
+ if row:
333
+ return _row_to_goal_profile(row, resolved_by="goal_id") or {}
334
+
335
+ clean_area = (area or "").strip().lower()
336
+ if clean_area:
337
+ row = conn.execute(
338
+ """SELECT * FROM goal_profiles
339
+ WHERE scope_type = 'area' AND scope_value = ? AND status = 'active'
340
+ ORDER BY updated_at DESC, profile_id ASC
341
+ LIMIT 1""",
342
+ (clean_area,),
343
+ ).fetchone()
344
+ if row:
345
+ return _row_to_goal_profile(row, resolved_by="area") or {}
346
+
347
+ clean_type = (task_type or "").strip().lower()
348
+ if clean_type:
349
+ row = conn.execute(
350
+ """SELECT * FROM goal_profiles
351
+ WHERE scope_type = 'task_type' AND scope_value = ? AND status = 'active'
352
+ ORDER BY updated_at DESC, profile_id ASC
353
+ LIMIT 1""",
354
+ (clean_type,),
355
+ ).fetchone()
356
+ if row:
357
+ return _row_to_goal_profile(row, resolved_by="task_type") or {}
358
+
359
+ row = conn.execute(
360
+ """SELECT * FROM goal_profiles
361
+ WHERE scope_type = 'default' AND status = 'active'
362
+ ORDER BY updated_at DESC, profile_id ASC
363
+ LIMIT 1"""
364
+ ).fetchone()
365
+ return _row_to_goal_profile(row, resolved_by="default") or {
366
+ "profile_id": "default_balanced",
367
+ "profile_name": "Balanced default",
368
+ "description": DEFAULT_GOAL_PROFILES[0]["description"],
369
+ "scope_type": "default",
370
+ "scope_value": "",
371
+ "goal_labels": list(DEFAULT_GOAL_PROFILES[0]["goal_labels"]),
372
+ "weights": dict(DEFAULT_WEIGHTS),
373
+ "status": "active",
374
+ "source": "system",
375
+ "resolved_by": "fallback_default",
376
+ }