@usejarvis/brain 0.1.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 (266) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +278 -0
  3. package/bin/jarvis.ts +413 -0
  4. package/package.json +74 -0
  5. package/scripts/ensure-bun.cjs +8 -0
  6. package/src/actions/README.md +421 -0
  7. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  8. package/src/actions/app-control/desktop-controller.ts +438 -0
  9. package/src/actions/app-control/interface.ts +64 -0
  10. package/src/actions/app-control/linux.ts +273 -0
  11. package/src/actions/app-control/macos.ts +54 -0
  12. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  13. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  14. package/src/actions/app-control/windows.ts +44 -0
  15. package/src/actions/browser/cdp.ts +138 -0
  16. package/src/actions/browser/chrome-launcher.ts +252 -0
  17. package/src/actions/browser/session.ts +437 -0
  18. package/src/actions/browser/stealth.ts +49 -0
  19. package/src/actions/index.ts +20 -0
  20. package/src/actions/terminal/executor.ts +157 -0
  21. package/src/actions/terminal/wsl-bridge.ts +126 -0
  22. package/src/actions/test.ts +93 -0
  23. package/src/actions/tools/agents.ts +321 -0
  24. package/src/actions/tools/builtin.ts +846 -0
  25. package/src/actions/tools/commitments.ts +192 -0
  26. package/src/actions/tools/content.ts +217 -0
  27. package/src/actions/tools/delegate.ts +147 -0
  28. package/src/actions/tools/desktop.test.ts +55 -0
  29. package/src/actions/tools/desktop.ts +305 -0
  30. package/src/actions/tools/goals.ts +376 -0
  31. package/src/actions/tools/local-tools-guard.ts +20 -0
  32. package/src/actions/tools/registry.ts +171 -0
  33. package/src/actions/tools/research.ts +111 -0
  34. package/src/actions/tools/sidecar-list.ts +57 -0
  35. package/src/actions/tools/sidecar-route.ts +105 -0
  36. package/src/actions/tools/workflows.ts +216 -0
  37. package/src/agents/agent.ts +132 -0
  38. package/src/agents/delegation.ts +107 -0
  39. package/src/agents/hierarchy.ts +113 -0
  40. package/src/agents/index.ts +19 -0
  41. package/src/agents/messaging.ts +125 -0
  42. package/src/agents/orchestrator.ts +576 -0
  43. package/src/agents/role-discovery.ts +61 -0
  44. package/src/agents/sub-agent-runner.ts +307 -0
  45. package/src/agents/task-manager.ts +151 -0
  46. package/src/authority/approval-delivery.ts +59 -0
  47. package/src/authority/approval.ts +196 -0
  48. package/src/authority/audit.ts +158 -0
  49. package/src/authority/authority.test.ts +519 -0
  50. package/src/authority/deferred-executor.ts +103 -0
  51. package/src/authority/emergency.ts +66 -0
  52. package/src/authority/engine.ts +297 -0
  53. package/src/authority/index.ts +12 -0
  54. package/src/authority/learning.ts +111 -0
  55. package/src/authority/tool-action-map.ts +74 -0
  56. package/src/awareness/analytics.ts +466 -0
  57. package/src/awareness/awareness.test.ts +332 -0
  58. package/src/awareness/capture-engine.ts +305 -0
  59. package/src/awareness/context-graph.ts +130 -0
  60. package/src/awareness/context-tracker.ts +349 -0
  61. package/src/awareness/index.ts +25 -0
  62. package/src/awareness/intelligence.ts +321 -0
  63. package/src/awareness/ocr-engine.ts +88 -0
  64. package/src/awareness/service.ts +528 -0
  65. package/src/awareness/struggle-detector.ts +342 -0
  66. package/src/awareness/suggestion-engine.ts +476 -0
  67. package/src/awareness/types.ts +201 -0
  68. package/src/cli/autostart.ts +241 -0
  69. package/src/cli/deps.ts +449 -0
  70. package/src/cli/doctor.ts +230 -0
  71. package/src/cli/helpers.ts +401 -0
  72. package/src/cli/onboard.ts +580 -0
  73. package/src/comms/README.md +329 -0
  74. package/src/comms/auth-error.html +48 -0
  75. package/src/comms/channels/discord.ts +228 -0
  76. package/src/comms/channels/signal.ts +56 -0
  77. package/src/comms/channels/telegram.ts +316 -0
  78. package/src/comms/channels/whatsapp.ts +60 -0
  79. package/src/comms/channels.test.ts +173 -0
  80. package/src/comms/desktop-notify.ts +114 -0
  81. package/src/comms/example.ts +129 -0
  82. package/src/comms/index.ts +129 -0
  83. package/src/comms/streaming.ts +142 -0
  84. package/src/comms/voice.test.ts +152 -0
  85. package/src/comms/voice.ts +291 -0
  86. package/src/comms/websocket.test.ts +409 -0
  87. package/src/comms/websocket.ts +473 -0
  88. package/src/config/README.md +387 -0
  89. package/src/config/index.ts +6 -0
  90. package/src/config/loader.test.ts +137 -0
  91. package/src/config/loader.ts +142 -0
  92. package/src/config/types.ts +260 -0
  93. package/src/daemon/README.md +232 -0
  94. package/src/daemon/agent-service-interface.ts +9 -0
  95. package/src/daemon/agent-service.ts +600 -0
  96. package/src/daemon/api-routes.ts +2119 -0
  97. package/src/daemon/background-agent-service.ts +396 -0
  98. package/src/daemon/background-agent.test.ts +78 -0
  99. package/src/daemon/channel-service.ts +201 -0
  100. package/src/daemon/commitment-executor.ts +297 -0
  101. package/src/daemon/event-classifier.ts +239 -0
  102. package/src/daemon/event-coalescer.ts +123 -0
  103. package/src/daemon/event-reactor.ts +214 -0
  104. package/src/daemon/health.ts +220 -0
  105. package/src/daemon/index.ts +1004 -0
  106. package/src/daemon/llm-settings.ts +316 -0
  107. package/src/daemon/observer-service.ts +150 -0
  108. package/src/daemon/pid.ts +98 -0
  109. package/src/daemon/research-queue.ts +155 -0
  110. package/src/daemon/services.ts +175 -0
  111. package/src/daemon/ws-service.ts +788 -0
  112. package/src/goals/accountability.ts +240 -0
  113. package/src/goals/awareness-bridge.ts +185 -0
  114. package/src/goals/estimator.ts +185 -0
  115. package/src/goals/events.ts +28 -0
  116. package/src/goals/goals.test.ts +400 -0
  117. package/src/goals/integration.test.ts +329 -0
  118. package/src/goals/nl-builder.test.ts +220 -0
  119. package/src/goals/nl-builder.ts +256 -0
  120. package/src/goals/rhythm.test.ts +177 -0
  121. package/src/goals/rhythm.ts +275 -0
  122. package/src/goals/service.test.ts +135 -0
  123. package/src/goals/service.ts +348 -0
  124. package/src/goals/types.ts +106 -0
  125. package/src/goals/workflow-bridge.ts +96 -0
  126. package/src/integrations/google-api.ts +134 -0
  127. package/src/integrations/google-auth.ts +175 -0
  128. package/src/llm/README.md +291 -0
  129. package/src/llm/anthropic.ts +386 -0
  130. package/src/llm/gemini.ts +371 -0
  131. package/src/llm/index.ts +19 -0
  132. package/src/llm/manager.ts +153 -0
  133. package/src/llm/ollama.ts +307 -0
  134. package/src/llm/openai.ts +350 -0
  135. package/src/llm/provider.test.ts +231 -0
  136. package/src/llm/provider.ts +60 -0
  137. package/src/llm/test.ts +87 -0
  138. package/src/observers/README.md +278 -0
  139. package/src/observers/calendar.ts +113 -0
  140. package/src/observers/clipboard.ts +136 -0
  141. package/src/observers/email.ts +109 -0
  142. package/src/observers/example.ts +58 -0
  143. package/src/observers/file-watcher.ts +124 -0
  144. package/src/observers/index.ts +159 -0
  145. package/src/observers/notifications.ts +197 -0
  146. package/src/observers/observers.test.ts +203 -0
  147. package/src/observers/processes.ts +225 -0
  148. package/src/personality/README.md +61 -0
  149. package/src/personality/adapter.ts +196 -0
  150. package/src/personality/index.ts +20 -0
  151. package/src/personality/learner.ts +209 -0
  152. package/src/personality/model.ts +132 -0
  153. package/src/personality/personality.test.ts +236 -0
  154. package/src/roles/README.md +252 -0
  155. package/src/roles/authority.ts +119 -0
  156. package/src/roles/example-usage.ts +198 -0
  157. package/src/roles/index.ts +42 -0
  158. package/src/roles/loader.ts +143 -0
  159. package/src/roles/prompt-builder.ts +194 -0
  160. package/src/roles/test-multi.ts +102 -0
  161. package/src/roles/test-role.yaml +77 -0
  162. package/src/roles/test-utils.ts +93 -0
  163. package/src/roles/test.ts +106 -0
  164. package/src/roles/tool-guide.ts +190 -0
  165. package/src/roles/types.ts +36 -0
  166. package/src/roles/utils.ts +200 -0
  167. package/src/scripts/google-setup.ts +168 -0
  168. package/src/sidecar/connection.ts +179 -0
  169. package/src/sidecar/index.ts +6 -0
  170. package/src/sidecar/manager.ts +542 -0
  171. package/src/sidecar/protocol.ts +85 -0
  172. package/src/sidecar/rpc.ts +161 -0
  173. package/src/sidecar/scheduler.ts +136 -0
  174. package/src/sidecar/types.ts +112 -0
  175. package/src/sidecar/validator.ts +144 -0
  176. package/src/vault/README.md +110 -0
  177. package/src/vault/awareness.ts +341 -0
  178. package/src/vault/commitments.ts +299 -0
  179. package/src/vault/content-pipeline.ts +260 -0
  180. package/src/vault/conversations.ts +173 -0
  181. package/src/vault/entities.ts +180 -0
  182. package/src/vault/extractor.test.ts +356 -0
  183. package/src/vault/extractor.ts +345 -0
  184. package/src/vault/facts.ts +190 -0
  185. package/src/vault/goals.ts +477 -0
  186. package/src/vault/index.ts +87 -0
  187. package/src/vault/keychain.ts +99 -0
  188. package/src/vault/observations.ts +115 -0
  189. package/src/vault/relationships.ts +178 -0
  190. package/src/vault/retrieval.test.ts +126 -0
  191. package/src/vault/retrieval.ts +227 -0
  192. package/src/vault/schema.ts +658 -0
  193. package/src/vault/settings.ts +38 -0
  194. package/src/vault/vectors.ts +92 -0
  195. package/src/vault/workflows.ts +403 -0
  196. package/src/workflows/auto-suggest.ts +290 -0
  197. package/src/workflows/engine.ts +366 -0
  198. package/src/workflows/events.ts +24 -0
  199. package/src/workflows/executor.ts +207 -0
  200. package/src/workflows/nl-builder.ts +198 -0
  201. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  202. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  203. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  204. package/src/workflows/nodes/actions/discord.ts +77 -0
  205. package/src/workflows/nodes/actions/file-write.ts +73 -0
  206. package/src/workflows/nodes/actions/gmail.ts +69 -0
  207. package/src/workflows/nodes/actions/http-request.ts +117 -0
  208. package/src/workflows/nodes/actions/notification.ts +85 -0
  209. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  210. package/src/workflows/nodes/actions/send-message.ts +82 -0
  211. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  212. package/src/workflows/nodes/actions/telegram.ts +60 -0
  213. package/src/workflows/nodes/builtin.ts +119 -0
  214. package/src/workflows/nodes/error/error-handler.ts +37 -0
  215. package/src/workflows/nodes/error/fallback.ts +47 -0
  216. package/src/workflows/nodes/error/retry.ts +82 -0
  217. package/src/workflows/nodes/logic/delay.ts +42 -0
  218. package/src/workflows/nodes/logic/if-else.ts +41 -0
  219. package/src/workflows/nodes/logic/loop.ts +90 -0
  220. package/src/workflows/nodes/logic/merge.ts +38 -0
  221. package/src/workflows/nodes/logic/race.ts +40 -0
  222. package/src/workflows/nodes/logic/switch.ts +59 -0
  223. package/src/workflows/nodes/logic/template-render.ts +53 -0
  224. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  225. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  226. package/src/workflows/nodes/registry.ts +99 -0
  227. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  228. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  229. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  230. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  231. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  232. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  233. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  234. package/src/workflows/nodes/triggers/cron.ts +40 -0
  235. package/src/workflows/nodes/triggers/email.ts +40 -0
  236. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  237. package/src/workflows/nodes/triggers/git.ts +46 -0
  238. package/src/workflows/nodes/triggers/manual.ts +23 -0
  239. package/src/workflows/nodes/triggers/poll.ts +81 -0
  240. package/src/workflows/nodes/triggers/process.ts +44 -0
  241. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  242. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  243. package/src/workflows/safe-eval.ts +139 -0
  244. package/src/workflows/template.ts +118 -0
  245. package/src/workflows/triggers/cron.ts +311 -0
  246. package/src/workflows/triggers/manager.ts +285 -0
  247. package/src/workflows/triggers/observer-bridge.ts +172 -0
  248. package/src/workflows/triggers/poller.ts +201 -0
  249. package/src/workflows/triggers/screen-condition.ts +218 -0
  250. package/src/workflows/triggers/triggers.test.ts +740 -0
  251. package/src/workflows/triggers/webhook.ts +191 -0
  252. package/src/workflows/types.ts +133 -0
  253. package/src/workflows/variables.ts +72 -0
  254. package/src/workflows/workflows.test.ts +383 -0
  255. package/src/workflows/yaml.ts +104 -0
  256. package/ui/dist/index-j75njzc1.css +1199 -0
  257. package/ui/dist/index-p2zh407q.js +80603 -0
  258. package/ui/dist/index.html +13 -0
  259. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  260. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  261. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  262. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  263. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  264. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  265. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  266. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,190 @@
1
+ import { getDb, generateId } from './schema.ts';
2
+ import { findEntities } from './entities.ts';
3
+
4
+ export type Fact = {
5
+ id: string;
6
+ subject_id: string;
7
+ predicate: string;
8
+ object: string;
9
+ confidence: number;
10
+ source: string | null;
11
+ created_at: number;
12
+ verified_at: number | null;
13
+ };
14
+
15
+ type FactRow = {
16
+ id: string;
17
+ subject_id: string;
18
+ predicate: string;
19
+ object: string;
20
+ confidence: number;
21
+ source: string | null;
22
+ created_at: number;
23
+ verified_at: number | null;
24
+ };
25
+
26
+ /**
27
+ * Parse fact row from database
28
+ */
29
+ function parseFact(row: FactRow): Fact {
30
+ return { ...row };
31
+ }
32
+
33
+ /**
34
+ * Create a new fact in the knowledge graph
35
+ */
36
+ export function createFact(
37
+ subject_id: string,
38
+ predicate: string,
39
+ object: string,
40
+ opts?: { confidence?: number; source?: string }
41
+ ): Fact {
42
+ const db = getDb();
43
+ const id = generateId();
44
+ const now = Date.now();
45
+ const confidence = opts?.confidence ?? 1.0;
46
+ const source = opts?.source ?? null;
47
+
48
+ const stmt = db.prepare(
49
+ 'INSERT INTO facts (id, subject_id, predicate, object, confidence, source, created_at, verified_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
50
+ );
51
+
52
+ const objectStr = typeof object === 'string' ? object : JSON.stringify(object);
53
+ stmt.run(id, subject_id, predicate, objectStr, confidence, source, now, null);
54
+
55
+ return {
56
+ id,
57
+ subject_id,
58
+ predicate,
59
+ object,
60
+ confidence,
61
+ source,
62
+ created_at: now,
63
+ verified_at: null,
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Get a fact by ID
69
+ */
70
+ export function getFact(id: string): Fact | null {
71
+ const db = getDb();
72
+ const stmt = db.prepare('SELECT * FROM facts WHERE id = ?');
73
+ const row = stmt.get(id) as FactRow | null;
74
+
75
+ if (!row) return null;
76
+
77
+ return parseFact(row);
78
+ }
79
+
80
+ /**
81
+ * Find facts matching query criteria
82
+ */
83
+ export function findFacts(query: {
84
+ subject_id?: string;
85
+ predicate?: string;
86
+ object?: string;
87
+ }): Fact[] {
88
+ const db = getDb();
89
+ const conditions: string[] = [];
90
+ const params: unknown[] = [];
91
+
92
+ if (query.subject_id) {
93
+ conditions.push('subject_id = ?');
94
+ params.push(query.subject_id);
95
+ }
96
+
97
+ if (query.predicate) {
98
+ conditions.push('predicate = ?');
99
+ params.push(query.predicate);
100
+ }
101
+
102
+ if (query.object) {
103
+ conditions.push('object = ?');
104
+ params.push(query.object);
105
+ }
106
+
107
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
108
+ const stmt = db.prepare(`SELECT * FROM facts ${where} ORDER BY created_at DESC`);
109
+ const rows = stmt.all(...params as any[]) as FactRow[];
110
+
111
+ return rows.map(parseFact);
112
+ }
113
+
114
+ /**
115
+ * Query a fact by subject name and predicate
116
+ * Example: "What is Anna's birthday?" → queryFact("Anna", "birthday")
117
+ */
118
+ export function queryFact(subjectName: string, predicate: string): Fact | null {
119
+ const entities = findEntities({ name: subjectName });
120
+
121
+ if (entities.length === 0) return null;
122
+
123
+ // Use the first matching entity
124
+ const facts = findFacts({ subject_id: entities[0]!.id, predicate });
125
+
126
+ return facts.length > 0 ? facts[0]! : null;
127
+ }
128
+
129
+ /**
130
+ * Update a fact's properties
131
+ */
132
+ export function updateFact(
133
+ id: string,
134
+ updates: Partial<Pick<Fact, 'predicate' | 'object' | 'confidence' | 'source'>>
135
+ ): Fact | null {
136
+ const db = getDb();
137
+ const fact = getFact(id);
138
+ if (!fact) return null;
139
+
140
+ const fields: string[] = [];
141
+ const params: unknown[] = [];
142
+
143
+ if (updates.predicate !== undefined) {
144
+ fields.push('predicate = ?');
145
+ params.push(updates.predicate);
146
+ }
147
+
148
+ if (updates.object !== undefined) {
149
+ fields.push('object = ?');
150
+ params.push(updates.object);
151
+ }
152
+
153
+ if (updates.confidence !== undefined) {
154
+ fields.push('confidence = ?');
155
+ params.push(updates.confidence);
156
+ }
157
+
158
+ if (updates.source !== undefined) {
159
+ fields.push('source = ?');
160
+ params.push(updates.source);
161
+ }
162
+
163
+ if (fields.length === 0) return fact;
164
+
165
+ params.push(id);
166
+
167
+ const stmt = db.prepare(`UPDATE facts SET ${fields.join(', ')} WHERE id = ?`);
168
+ stmt.run(...params as any[]);
169
+
170
+ return getFact(id);
171
+ }
172
+
173
+ /**
174
+ * Delete a fact
175
+ */
176
+ export function deleteFact(id: string): boolean {
177
+ const db = getDb();
178
+ const stmt = db.prepare('DELETE FROM facts WHERE id = ?');
179
+ const result = stmt.run(id);
180
+ return result.changes > 0;
181
+ }
182
+
183
+ /**
184
+ * Mark a fact as verified
185
+ */
186
+ export function verifyFact(id: string): void {
187
+ const db = getDb();
188
+ const stmt = db.prepare('UPDATE facts SET verified_at = ? WHERE id = ?');
189
+ stmt.run(Date.now(), id);
190
+ }
@@ -0,0 +1,477 @@
1
+ /**
2
+ * Goal Vault — CRUD operations for M16 Autonomous Goal Pursuit
3
+ */
4
+
5
+ import type { SQLQueryBindings } from 'bun:sqlite';
6
+ import { getDb, generateId } from './schema.ts';
7
+ import type {
8
+ Goal, GoalProgressEntry, GoalCheckIn,
9
+ GoalLevel, GoalStatus, GoalHealth, EscalationStage,
10
+ GoalQuery, GoalUpdate,
11
+ } from '../goals/types.ts';
12
+
13
+ // ── Row types (raw DB) ──────────────────────────────────────────────
14
+
15
+ type GoalRow = Omit<Goal, 'tags' | 'dependencies'> & {
16
+ tags: string | null;
17
+ dependencies: string | null;
18
+ };
19
+
20
+ type ProgressRow = GoalProgressEntry;
21
+
22
+ type CheckInRow = Omit<GoalCheckIn, 'goals_reviewed' | 'actions_planned' | 'actions_completed'> & {
23
+ goals_reviewed: string | null;
24
+ actions_planned: string | null;
25
+ actions_completed: string | null;
26
+ };
27
+
28
+ // ── Parsers ─────────────────────────────────────────────────────────
29
+
30
+ function parseGoal(row: GoalRow): Goal {
31
+ return {
32
+ ...row,
33
+ tags: row.tags ? JSON.parse(row.tags) : [],
34
+ dependencies: row.dependencies ? JSON.parse(row.dependencies) : [],
35
+ };
36
+ }
37
+
38
+ function parseCheckIn(row: CheckInRow): GoalCheckIn {
39
+ return {
40
+ ...row,
41
+ goals_reviewed: row.goals_reviewed ? JSON.parse(row.goals_reviewed) : [],
42
+ actions_planned: row.actions_planned ? JSON.parse(row.actions_planned) : [],
43
+ actions_completed: row.actions_completed ? JSON.parse(row.actions_completed) : [],
44
+ };
45
+ }
46
+
47
+ // ── Goals CRUD ──────────────────────────────────────────────────────
48
+
49
+ export function createGoal(
50
+ title: string,
51
+ level: GoalLevel,
52
+ opts?: {
53
+ parent_id?: string;
54
+ description?: string;
55
+ success_criteria?: string;
56
+ time_horizon?: string;
57
+ deadline?: number;
58
+ estimated_hours?: number;
59
+ authority_level?: number;
60
+ tags?: string[];
61
+ dependencies?: string[];
62
+ status?: GoalStatus;
63
+ sort_order?: number;
64
+ },
65
+ ): Goal {
66
+ const db = getDb();
67
+ const id = generateId();
68
+ const now = Date.now();
69
+
70
+ db.prepare(
71
+ `INSERT INTO goals (id, parent_id, level, title, description, success_criteria,
72
+ time_horizon, score, score_reason, status, health, deadline, started_at,
73
+ estimated_hours, actual_hours, authority_level, tags, dependencies,
74
+ escalation_stage, escalation_started_at, sort_order, created_at, updated_at, completed_at)
75
+ VALUES (?, ?, ?, ?, ?, ?, ?, 0.0, NULL, ?, 'on_track', ?, ?, ?, 0, ?, ?, ?, 'none', NULL, ?, ?, ?, NULL)`
76
+ ).run(
77
+ id,
78
+ opts?.parent_id ?? null,
79
+ level,
80
+ title,
81
+ opts?.description ?? '',
82
+ opts?.success_criteria ?? '',
83
+ opts?.time_horizon ?? 'quarterly',
84
+ opts?.status ?? 'draft',
85
+ opts?.deadline ?? null,
86
+ opts?.status === 'active' ? now : null,
87
+ opts?.estimated_hours ?? null,
88
+ opts?.authority_level ?? 3,
89
+ opts?.tags ? JSON.stringify(opts.tags) : null,
90
+ opts?.dependencies ? JSON.stringify(opts.dependencies) : null,
91
+ opts?.sort_order ?? 0,
92
+ now, now,
93
+ );
94
+
95
+ return getGoal(id)!;
96
+ }
97
+
98
+ export function getGoal(id: string): Goal | null {
99
+ const db = getDb();
100
+ const row = db.prepare('SELECT * FROM goals WHERE id = ?').get(id) as GoalRow | null;
101
+ return row ? parseGoal(row) : null;
102
+ }
103
+
104
+ export function findGoals(query: GoalQuery = {}): Goal[] {
105
+ const db = getDb();
106
+ const conditions: string[] = [];
107
+ const params: unknown[] = [];
108
+
109
+ if (query.status) {
110
+ conditions.push('status = ?');
111
+ params.push(query.status);
112
+ }
113
+ if (query.level) {
114
+ conditions.push('level = ?');
115
+ params.push(query.level);
116
+ }
117
+ if (query.parent_id !== undefined) {
118
+ if (query.parent_id === null) {
119
+ conditions.push('parent_id IS NULL');
120
+ } else {
121
+ conditions.push('parent_id = ?');
122
+ params.push(query.parent_id);
123
+ }
124
+ }
125
+ if (query.health) {
126
+ conditions.push('health = ?');
127
+ params.push(query.health);
128
+ }
129
+ if (query.tag) {
130
+ conditions.push("tags LIKE ?");
131
+ params.push(`%"${query.tag}"%`);
132
+ }
133
+ if (query.time_horizon) {
134
+ conditions.push('time_horizon = ?');
135
+ params.push(query.time_horizon);
136
+ }
137
+
138
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
139
+ const limit = Math.max(1, Math.min(parseInt(String(query.limit ?? 100), 10) || 100, 1000));
140
+
141
+ const rows = db.prepare(
142
+ `SELECT * FROM goals ${where} ORDER BY sort_order ASC, created_at ASC LIMIT ?`
143
+ ).all(...(params as SQLQueryBindings[]), limit) as GoalRow[];
144
+
145
+ return rows.map(parseGoal);
146
+ }
147
+
148
+ export function getRootGoals(): Goal[] {
149
+ return findGoals({ parent_id: null });
150
+ }
151
+
152
+ export function getGoalChildren(parentId: string): Goal[] {
153
+ return findGoals({ parent_id: parentId });
154
+ }
155
+
156
+ export function getGoalTree(rootId: string): Goal[] {
157
+ const result: Goal[] = [];
158
+ const root = getGoal(rootId);
159
+ if (!root) return result;
160
+
161
+ result.push(root);
162
+
163
+ const collectChildren = (parentId: string) => {
164
+ const children = getGoalChildren(parentId);
165
+ for (const child of children) {
166
+ result.push(child);
167
+ collectChildren(child.id);
168
+ }
169
+ };
170
+
171
+ collectChildren(rootId);
172
+ return result;
173
+ }
174
+
175
+ export function updateGoal(id: string, updates: GoalUpdate): Goal | null {
176
+ const db = getDb();
177
+ const existing = getGoal(id);
178
+ if (!existing) return null;
179
+
180
+ const sets: string[] = [];
181
+ const params: unknown[] = [];
182
+
183
+ if (updates.title !== undefined) { sets.push('title = ?'); params.push(updates.title); }
184
+ if (updates.description !== undefined) { sets.push('description = ?'); params.push(updates.description); }
185
+ if (updates.success_criteria !== undefined) { sets.push('success_criteria = ?'); params.push(updates.success_criteria); }
186
+ if (updates.time_horizon !== undefined) { sets.push('time_horizon = ?'); params.push(updates.time_horizon); }
187
+ if (updates.deadline !== undefined) { sets.push('deadline = ?'); params.push(updates.deadline); }
188
+ if (updates.estimated_hours !== undefined) { sets.push('estimated_hours = ?'); params.push(updates.estimated_hours); }
189
+ if (updates.authority_level !== undefined) { sets.push('authority_level = ?'); params.push(updates.authority_level); }
190
+ if (updates.tags !== undefined) { sets.push('tags = ?'); params.push(JSON.stringify(updates.tags)); }
191
+ if (updates.dependencies !== undefined) { sets.push('dependencies = ?'); params.push(JSON.stringify(updates.dependencies)); }
192
+ if (updates.sort_order !== undefined) { sets.push('sort_order = ?'); params.push(updates.sort_order); }
193
+
194
+ if (sets.length === 0) return existing;
195
+
196
+ sets.push('updated_at = ?');
197
+ params.push(Date.now());
198
+ params.push(id);
199
+
200
+ db.prepare(`UPDATE goals SET ${sets.join(', ')} WHERE id = ?`).run(...(params as SQLQueryBindings[]));
201
+
202
+ return getGoal(id);
203
+ }
204
+
205
+ export function updateGoalScore(id: string, score: number, reason: string, source = 'user'): Goal | null {
206
+ const db = getDb();
207
+ const existing = getGoal(id);
208
+ if (!existing) return null;
209
+
210
+ const clampedScore = Math.max(0, Math.min(1, score));
211
+ const now = Date.now();
212
+
213
+ // Log progress entry
214
+ addProgressEntry(id, source === 'user' ? 'manual' : 'system', existing.score, clampedScore, reason, source);
215
+
216
+ // Update the goal
217
+ db.prepare(
218
+ `UPDATE goals SET score = ?, score_reason = ?, updated_at = ? WHERE id = ?`
219
+ ).run(clampedScore, reason, now, id);
220
+
221
+ return getGoal(id);
222
+ }
223
+
224
+ export function updateGoalStatus(id: string, status: GoalStatus): Goal | null {
225
+ const db = getDb();
226
+ const existing = getGoal(id);
227
+ if (!existing) return null;
228
+
229
+ const now = Date.now();
230
+ const isTerminal = status === 'completed' || status === 'failed' || status === 'killed';
231
+
232
+ const sets: string[] = ['status = ?', 'updated_at = ?'];
233
+ const params: unknown[] = [status, now];
234
+
235
+ if (status === 'active' && !existing.started_at) {
236
+ sets.push('started_at = ?');
237
+ params.push(now);
238
+ }
239
+
240
+ if (isTerminal) {
241
+ sets.push('completed_at = ?');
242
+ params.push(now);
243
+ }
244
+
245
+ params.push(id);
246
+ db.prepare(`UPDATE goals SET ${sets.join(', ')} WHERE id = ?`).run(...(params as SQLQueryBindings[]));
247
+
248
+ return getGoal(id);
249
+ }
250
+
251
+ export function updateGoalHealth(id: string, health: GoalHealth): Goal | null {
252
+ const db = getDb();
253
+ const now = Date.now();
254
+ db.prepare(`UPDATE goals SET health = ?, updated_at = ? WHERE id = ?`).run(health, now, id);
255
+ return getGoal(id);
256
+ }
257
+
258
+ export function updateGoalEscalation(id: string, stage: EscalationStage): Goal | null {
259
+ const db = getDb();
260
+ const now = Date.now();
261
+
262
+ if (stage === 'none') {
263
+ db.prepare(`UPDATE goals SET escalation_stage = 'none', escalation_started_at = NULL, updated_at = ? WHERE id = ?`).run(now, id);
264
+ } else {
265
+ const existing = getGoal(id);
266
+ if (!existing) return null;
267
+ // Only set escalation_started_at if transitioning from 'none'
268
+ const startedAt = existing.escalation_stage === 'none' ? now : existing.escalation_started_at;
269
+ db.prepare(
270
+ `UPDATE goals SET escalation_stage = ?, escalation_started_at = ?, updated_at = ? WHERE id = ?`
271
+ ).run(stage, startedAt, now, id);
272
+ }
273
+
274
+ return getGoal(id);
275
+ }
276
+
277
+ export function updateGoalActualHours(id: string, hours: number): Goal | null {
278
+ const db = getDb();
279
+ const now = Date.now();
280
+ db.prepare(`UPDATE goals SET actual_hours = ?, updated_at = ? WHERE id = ?`).run(hours, now, id);
281
+ return getGoal(id);
282
+ }
283
+
284
+ export function deleteGoal(id: string): boolean {
285
+ const db = getDb();
286
+ const result = db.prepare('DELETE FROM goals WHERE id = ?').run(id);
287
+ return result.changes > 0;
288
+ }
289
+
290
+ export function reorderGoals(items: { id: string; sort_order: number }[]): void {
291
+ const db = getDb();
292
+ const stmt = db.prepare('UPDATE goals SET sort_order = ?, updated_at = ? WHERE id = ?');
293
+ const now = Date.now();
294
+
295
+ const tx = db.transaction(() => {
296
+ for (const item of items) {
297
+ stmt.run(item.sort_order, now, item.id);
298
+ }
299
+ });
300
+ tx();
301
+ }
302
+
303
+ export function getOverdueGoals(): Goal[] {
304
+ const db = getDb();
305
+ const now = Date.now();
306
+ const rows = db.prepare(
307
+ `SELECT * FROM goals WHERE status = 'active' AND deadline IS NOT NULL AND deadline < ? ORDER BY deadline ASC`
308
+ ).all(now) as GoalRow[];
309
+ return rows.map(parseGoal);
310
+ }
311
+
312
+ export function getGoalsByDependency(goalId: string): Goal[] {
313
+ const db = getDb();
314
+ const rows = db.prepare(
315
+ `SELECT * FROM goals WHERE dependencies LIKE ? AND status IN ('draft', 'active', 'paused')`
316
+ ).all(`%"${goalId}"%`) as GoalRow[];
317
+ return rows.map(parseGoal);
318
+ }
319
+
320
+ export function getGoalsNeedingEscalation(): Goal[] {
321
+ const db = getDb();
322
+ const rows = db.prepare(
323
+ `SELECT * FROM goals WHERE status = 'active' AND health IN ('behind', 'critical') ORDER BY deadline ASC`
324
+ ).all() as GoalRow[];
325
+ return rows.map(parseGoal);
326
+ }
327
+
328
+ export function getActiveGoalsByLevel(level: GoalLevel): Goal[] {
329
+ return findGoals({ status: 'active', level });
330
+ }
331
+
332
+ // ── Progress Entries ────────────────────────────────────────────────
333
+
334
+ export function addProgressEntry(
335
+ goalId: string,
336
+ type: 'manual' | 'auto_detected' | 'review' | 'system',
337
+ scoreBefore: number,
338
+ scoreAfter: number,
339
+ note: string,
340
+ source: string,
341
+ ): GoalProgressEntry {
342
+ const db = getDb();
343
+ const id = generateId();
344
+ const now = Date.now();
345
+
346
+ db.prepare(
347
+ `INSERT INTO goal_progress (id, goal_id, type, score_before, score_after, note, source, created_at)
348
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
349
+ ).run(id, goalId, type, scoreBefore, scoreAfter, note, source, now);
350
+
351
+ return { id, goal_id: goalId, type, score_before: scoreBefore, score_after: scoreAfter, note, source, created_at: now };
352
+ }
353
+
354
+ export function getProgressHistory(goalId: string, limit = 50): GoalProgressEntry[] {
355
+ const db = getDb();
356
+ const safeLimit = Math.max(1, Math.min(limit, 500));
357
+ const rows = db.prepare(
358
+ `SELECT * FROM goal_progress WHERE goal_id = ? ORDER BY created_at DESC LIMIT ?`
359
+ ).all(goalId, safeLimit) as ProgressRow[];
360
+ return rows;
361
+ }
362
+
363
+ // ── Check-Ins ───────────────────────────────────────────────────────
364
+
365
+ export function createCheckIn(
366
+ type: 'morning_plan' | 'evening_review',
367
+ summary: string,
368
+ goalsReviewed: string[],
369
+ actionsPlanned: string[] = [],
370
+ actionsCompleted: string[] = [],
371
+ ): GoalCheckIn {
372
+ const db = getDb();
373
+ const id = generateId();
374
+ const now = Date.now();
375
+
376
+ db.prepare(
377
+ `INSERT INTO goal_check_ins (id, type, summary, goals_reviewed, actions_planned, actions_completed, created_at)
378
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
379
+ ).run(
380
+ id, type, summary,
381
+ JSON.stringify(goalsReviewed),
382
+ JSON.stringify(actionsPlanned),
383
+ JSON.stringify(actionsCompleted),
384
+ now,
385
+ );
386
+
387
+ return {
388
+ id, type, summary,
389
+ goals_reviewed: goalsReviewed,
390
+ actions_planned: actionsPlanned,
391
+ actions_completed: actionsCompleted,
392
+ created_at: now,
393
+ };
394
+ }
395
+
396
+ export function getRecentCheckIns(type?: 'morning_plan' | 'evening_review', limit = 10): GoalCheckIn[] {
397
+ const db = getDb();
398
+ const safeLimit = Math.max(1, Math.min(limit, 100));
399
+
400
+ if (type) {
401
+ const rows = db.prepare(
402
+ `SELECT * FROM goal_check_ins WHERE type = ? ORDER BY created_at DESC LIMIT ?`
403
+ ).all(type, safeLimit) as CheckInRow[];
404
+ return rows.map(parseCheckIn);
405
+ }
406
+
407
+ const rows = db.prepare(
408
+ `SELECT * FROM goal_check_ins ORDER BY created_at DESC LIMIT ?`
409
+ ).all(safeLimit) as CheckInRow[];
410
+ return rows.map(parseCheckIn);
411
+ }
412
+
413
+ export function getTodayCheckIn(type: 'morning_plan' | 'evening_review'): GoalCheckIn | null {
414
+ const db = getDb();
415
+ const startOfDay = new Date();
416
+ startOfDay.setHours(0, 0, 0, 0);
417
+
418
+ const row = db.prepare(
419
+ `SELECT * FROM goal_check_ins WHERE type = ? AND created_at >= ? ORDER BY created_at DESC LIMIT 1`
420
+ ).get(type, startOfDay.getTime()) as CheckInRow | null;
421
+
422
+ return row ? parseCheckIn(row) : null;
423
+ }
424
+
425
+ // ── Metrics ─────────────────────────────────────────────────────────
426
+
427
+ export function getGoalMetrics(): {
428
+ total: number;
429
+ active: number;
430
+ completed: number;
431
+ failed: number;
432
+ killed: number;
433
+ avg_score: number;
434
+ on_track: number;
435
+ at_risk: number;
436
+ behind: number;
437
+ critical: number;
438
+ overdue: number;
439
+ } {
440
+ const db = getDb();
441
+
442
+ const statusCounts = db.prepare(
443
+ `SELECT status, COUNT(*) as count FROM goals GROUP BY status`
444
+ ).all() as { status: string; count: number }[];
445
+
446
+ const healthCounts = db.prepare(
447
+ `SELECT health, COUNT(*) as count FROM goals WHERE status = 'active' GROUP BY health`
448
+ ).all() as { health: string; count: number }[];
449
+
450
+ const avgScore = db.prepare(
451
+ `SELECT AVG(score) as avg FROM goals WHERE status = 'active' AND level = 'objective'`
452
+ ).get() as { avg: number | null };
453
+
454
+ const overdueCount = db.prepare(
455
+ `SELECT COUNT(*) as count FROM goals WHERE status = 'active' AND deadline IS NOT NULL AND deadline < ?`
456
+ ).get(Date.now()) as { count: number };
457
+
458
+ const statusMap: Record<string, number> = {};
459
+ for (const row of statusCounts) statusMap[row.status] = row.count;
460
+
461
+ const healthMap: Record<string, number> = {};
462
+ for (const row of healthCounts) healthMap[row.health] = row.count;
463
+
464
+ return {
465
+ total: Object.values(statusMap).reduce((a, b) => a + b, 0),
466
+ active: statusMap['active'] ?? 0,
467
+ completed: statusMap['completed'] ?? 0,
468
+ failed: statusMap['failed'] ?? 0,
469
+ killed: statusMap['killed'] ?? 0,
470
+ avg_score: avgScore.avg ?? 0,
471
+ on_track: healthMap['on_track'] ?? 0,
472
+ at_risk: healthMap['at_risk'] ?? 0,
473
+ behind: healthMap['behind'] ?? 0,
474
+ critical: healthMap['critical'] ?? 0,
475
+ overdue: overdueCount.count,
476
+ };
477
+ }