level-up-mcp-server-cn 0.4.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 (137) hide show
  1. package/.claude/projects/c--Users-klexi-OneDrive-Desktop-Levelup-level-up-mcp-server/memory/project_testing_service.md +11 -0
  2. package/.claude/settings.local.json +10 -0
  3. package/.env.example +19 -0
  4. package/CLAUDE.md +222 -0
  5. package/CODE_REVIEW.md +282 -0
  6. package/LICENSE +64 -0
  7. package/README.md +198 -0
  8. package/dist/constants.d.ts +33 -0
  9. package/dist/constants.js +78 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/data/quest-seeds.d.ts +18 -0
  12. package/dist/data/quest-seeds.js +380 -0
  13. package/dist/data/quest-seeds.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +260 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/schemas/common.d.ts +33 -0
  18. package/dist/schemas/common.js +96 -0
  19. package/dist/schemas/common.js.map +1 -0
  20. package/dist/services/dispatcher.d.ts +27 -0
  21. package/dist/services/dispatcher.js +47 -0
  22. package/dist/services/dispatcher.js.map +1 -0
  23. package/dist/services/errors.d.ts +56 -0
  24. package/dist/services/errors.js +99 -0
  25. package/dist/services/errors.js.map +1 -0
  26. package/dist/services/format.d.ts +74 -0
  27. package/dist/services/format.js +144 -0
  28. package/dist/services/format.js.map +1 -0
  29. package/dist/services/ownership.d.ts +19 -0
  30. package/dist/services/ownership.js +79 -0
  31. package/dist/services/ownership.js.map +1 -0
  32. package/dist/services/quality-gate.d.ts +45 -0
  33. package/dist/services/quality-gate.js +131 -0
  34. package/dist/services/quality-gate.js.map +1 -0
  35. package/dist/services/rate-limit.d.ts +12 -0
  36. package/dist/services/rate-limit.js +49 -0
  37. package/dist/services/rate-limit.js.map +1 -0
  38. package/dist/services/register.d.ts +49 -0
  39. package/dist/services/register.js +63 -0
  40. package/dist/services/register.js.map +1 -0
  41. package/dist/services/supabase.d.ts +10 -0
  42. package/dist/services/supabase.js +79 -0
  43. package/dist/services/supabase.js.map +1 -0
  44. package/dist/tools/achievements.d.ts +6 -0
  45. package/dist/tools/achievements.js +242 -0
  46. package/dist/tools/achievements.js.map +1 -0
  47. package/dist/tools/admin.d.ts +16 -0
  48. package/dist/tools/admin.js +328 -0
  49. package/dist/tools/admin.js.map +1 -0
  50. package/dist/tools/agents.d.ts +3 -0
  51. package/dist/tools/agents.js +400 -0
  52. package/dist/tools/agents.js.map +1 -0
  53. package/dist/tools/bootstrap.d.ts +17 -0
  54. package/dist/tools/bootstrap.js +565 -0
  55. package/dist/tools/bootstrap.js.map +1 -0
  56. package/dist/tools/dispatchers/admin.d.ts +3 -0
  57. package/dist/tools/dispatchers/admin.js +50 -0
  58. package/dist/tools/dispatchers/admin.js.map +1 -0
  59. package/dist/tools/dispatchers/eval.d.ts +3 -0
  60. package/dist/tools/dispatchers/eval.js +40 -0
  61. package/dist/tools/dispatchers/eval.js.map +1 -0
  62. package/dist/tools/dispatchers/quests.d.ts +3 -0
  63. package/dist/tools/dispatchers/quests.js +60 -0
  64. package/dist/tools/dispatchers/quests.js.map +1 -0
  65. package/dist/tools/dispatchers/session.d.ts +3 -0
  66. package/dist/tools/dispatchers/session.js +38 -0
  67. package/dist/tools/dispatchers/session.js.map +1 -0
  68. package/dist/tools/dispatchers/skills.d.ts +3 -0
  69. package/dist/tools/dispatchers/skills.js +49 -0
  70. package/dist/tools/dispatchers/skills.js.map +1 -0
  71. package/dist/tools/dispatchers/tasks.d.ts +3 -0
  72. package/dist/tools/dispatchers/tasks.js +53 -0
  73. package/dist/tools/dispatchers/tasks.js.map +1 -0
  74. package/dist/tools/dispatchers/users.d.ts +3 -0
  75. package/dist/tools/dispatchers/users.js +65 -0
  76. package/dist/tools/dispatchers/users.js.map +1 -0
  77. package/dist/tools/dispatchers/xp.d.ts +3 -0
  78. package/dist/tools/dispatchers/xp.js +51 -0
  79. package/dist/tools/dispatchers/xp.js.map +1 -0
  80. package/dist/tools/growth-plan.d.ts +5 -0
  81. package/dist/tools/growth-plan.js +791 -0
  82. package/dist/tools/growth-plan.js.map +1 -0
  83. package/dist/tools/leaderboards.d.ts +10 -0
  84. package/dist/tools/leaderboards.js +279 -0
  85. package/dist/tools/leaderboards.js.map +1 -0
  86. package/dist/tools/leveling.d.ts +24 -0
  87. package/dist/tools/leveling.js +356 -0
  88. package/dist/tools/leveling.js.map +1 -0
  89. package/dist/tools/metrics.d.ts +3 -0
  90. package/dist/tools/metrics.js +247 -0
  91. package/dist/tools/metrics.js.map +1 -0
  92. package/dist/tools/quests.d.ts +5 -0
  93. package/dist/tools/quests.js +586 -0
  94. package/dist/tools/quests.js.map +1 -0
  95. package/dist/tools/ratings.d.ts +11 -0
  96. package/dist/tools/ratings.js +564 -0
  97. package/dist/tools/ratings.js.map +1 -0
  98. package/dist/tools/skills.d.ts +66 -0
  99. package/dist/tools/skills.js +1112 -0
  100. package/dist/tools/skills.js.map +1 -0
  101. package/dist/tools/system.d.ts +31 -0
  102. package/dist/tools/system.js +605 -0
  103. package/dist/tools/system.js.map +1 -0
  104. package/dist/tools/tasks.d.ts +73 -0
  105. package/dist/tools/tasks.js +1572 -0
  106. package/dist/tools/tasks.js.map +1 -0
  107. package/dist/tools/users.d.ts +97 -0
  108. package/dist/tools/users.js +1306 -0
  109. package/dist/tools/users.js.map +1 -0
  110. package/dist/tools/xp.d.ts +38 -0
  111. package/dist/tools/xp.js +670 -0
  112. package/dist/tools/xp.js.map +1 -0
  113. package/dist/types.d.ts +178 -0
  114. package/dist/types.js +12 -0
  115. package/dist/types.js.map +1 -0
  116. package/docs/recommended-skillsets.md +622 -0
  117. package/docs/skills-and-abilities-review.md +672 -0
  118. package/docs/v0.3-roadmap.md +191 -0
  119. package/package.json +35 -0
  120. package/sql/agent_pending_installs.sql +28 -0
  121. package/sql/award_class_xp.sql +81 -0
  122. package/supabase/.temp/cli-latest +1 -0
  123. package/supabase/.temp/gotrue-version +1 -0
  124. package/supabase/.temp/pooler-url +1 -0
  125. package/supabase/.temp/postgres-version +1 -0
  126. package/supabase/.temp/project-ref +1 -0
  127. package/supabase/.temp/rest-version +1 -0
  128. package/supabase/.temp/storage-migration +1 -0
  129. package/supabase/.temp/storage-version +1 -0
  130. package/supabase/migrations/20260314000000_anon_rls_policies.sql +311 -0
  131. package/supabase/migrations/20260314000001_ownership_rpcs.sql +382 -0
  132. package/supabase/migrations/20260314000002_evidence_and_growth_plan.sql +97 -0
  133. package/supabase/migrations/20260317000000_seed_quests.sql +62 -0
  134. package/supabase/migrations/20260317000001_star_cooldown_and_fixes.sql +16 -0
  135. package/supabase/migrations/20260318000000_restore_rank_names.sql +25 -0
  136. package/supabase/migrations/20260320000000_chinese_rank_names.sql +24 -0
  137. package/vitest.config.ts +11 -0
@@ -0,0 +1,382 @@
1
+ -- ============================================================
2
+ -- Migration: Ownership-checking RPC functions
3
+ -- ============================================================
4
+ -- These SECURITY DEFINER functions replace direct UPDATE calls.
5
+ -- Anon role has no UPDATE policies — all modifications go through
6
+ -- these RPCs which verify ownership before making changes.
7
+ --
8
+ -- Depends on: 20260314000000_anon_rls_policies.sql
9
+ -- ============================================================
10
+
11
+ -- ============================================================
12
+ -- RPC 1: update_user_profile
13
+ -- Updates safe user fields. Cannot modify XP, level, or timestamps.
14
+ -- ============================================================
15
+ CREATE OR REPLACE FUNCTION update_user_profile(
16
+ p_user_id UUID,
17
+ p_updates JSONB
18
+ ) RETURNS JSONB AS $$
19
+ DECLARE
20
+ v_user RECORD;
21
+ v_sql TEXT := 'UPDATE users SET ';
22
+ v_sets TEXT[] := ARRAY[]::TEXT[];
23
+ v_key TEXT;
24
+ v_allowed TEXT[] := ARRAY[
25
+ 'username', 'handle', 'email',
26
+ 'is_searchable_by_email', 'wallet_address', 'metadata'
27
+ ];
28
+ BEGIN
29
+ -- Verify user exists
30
+ SELECT id INTO v_user FROM users WHERE id = p_user_id;
31
+ IF NOT FOUND THEN
32
+ RETURN jsonb_build_object('success', false, 'error', 'User not found');
33
+ END IF;
34
+
35
+ -- Build dynamic update from allowed fields only
36
+ FOR v_key IN SELECT jsonb_object_keys(p_updates) LOOP
37
+ IF v_key = ANY(v_allowed) THEN
38
+ v_sets := array_append(v_sets, format('%I = %L', v_key, p_updates->>v_key));
39
+ END IF;
40
+ END LOOP;
41
+
42
+ IF array_length(v_sets, 1) IS NULL THEN
43
+ RETURN jsonb_build_object('success', false, 'error', 'No valid fields to update');
44
+ END IF;
45
+
46
+ v_sql := v_sql || array_to_string(v_sets, ', ') || format(' WHERE id = %L', p_user_id);
47
+ EXECUTE v_sql;
48
+
49
+ RETURN jsonb_build_object('success', true, 'user_id', p_user_id);
50
+ END;
51
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
52
+
53
+ -- ============================================================
54
+ -- RPC 2: update_agent_profile
55
+ -- Updates safe agent fields. Verifies owner_user_id matches.
56
+ -- Cannot modify XP, level, integrity_score, or timestamps.
57
+ -- ============================================================
58
+ CREATE OR REPLACE FUNCTION update_agent_profile(
59
+ p_agent_id UUID,
60
+ p_owner_user_id UUID,
61
+ p_updates JSONB
62
+ ) RETURNS JSONB AS $$
63
+ DECLARE
64
+ v_agent RECORD;
65
+ v_sql TEXT := 'UPDATE agents SET ';
66
+ v_sets TEXT[] := ARRAY[]::TEXT[];
67
+ v_key TEXT;
68
+ v_allowed TEXT[] := ARRAY[
69
+ 'name', 'description', 'agent_class', 'platform',
70
+ 'is_public', 'status', 'metadata'
71
+ ];
72
+ BEGIN
73
+ -- Verify agent exists and caller is the owner
74
+ SELECT id, owner_user_id INTO v_agent FROM agents WHERE id = p_agent_id;
75
+ IF NOT FOUND THEN
76
+ RETURN jsonb_build_object('success', false, 'error', 'Agent not found');
77
+ END IF;
78
+ IF v_agent.owner_user_id != p_owner_user_id THEN
79
+ RETURN jsonb_build_object('success', false, 'error', 'Not the agent owner');
80
+ END IF;
81
+
82
+ -- Build dynamic update from allowed fields only
83
+ FOR v_key IN SELECT jsonb_object_keys(p_updates) LOOP
84
+ IF v_key = ANY(v_allowed) THEN
85
+ v_sets := array_append(v_sets, format('%I = %L', v_key, p_updates->>v_key));
86
+ END IF;
87
+ END LOOP;
88
+
89
+ IF array_length(v_sets, 1) IS NULL THEN
90
+ RETURN jsonb_build_object('success', false, 'error', 'No valid fields to update');
91
+ END IF;
92
+
93
+ v_sql := v_sql || array_to_string(v_sets, ', ') || format(' WHERE id = %L', p_agent_id);
94
+ EXECUTE v_sql;
95
+
96
+ RETURN jsonb_build_object('success', true, 'agent_id', p_agent_id);
97
+ END;
98
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
99
+
100
+ -- ============================================================
101
+ -- RPC 3: update_task_status
102
+ -- Updates task fields. Verifies the caller owns the task
103
+ -- (matches user_id or agent's owner_user_id).
104
+ -- ============================================================
105
+ CREATE OR REPLACE FUNCTION update_task_status(
106
+ p_task_id UUID,
107
+ p_caller_id UUID,
108
+ p_updates JSONB
109
+ ) RETURNS JSONB AS $$
110
+ DECLARE
111
+ v_task RECORD;
112
+ v_is_owner BOOLEAN := false;
113
+ v_sql TEXT := 'UPDATE tasks SET ';
114
+ v_sets TEXT[] := ARRAY[]::TEXT[];
115
+ v_key TEXT;
116
+ v_allowed TEXT[] := ARRAY[
117
+ 'status', 'completion_pct', 'milestones_completed',
118
+ 'output_type', 'completed_at', 'duration_seconds', 'metadata'
119
+ ];
120
+ BEGIN
121
+ -- Fetch task
122
+ SELECT id, user_id, agent_id INTO v_task FROM tasks WHERE id = p_task_id;
123
+ IF NOT FOUND THEN
124
+ RETURN jsonb_build_object('success', false, 'error', 'Task not found');
125
+ END IF;
126
+
127
+ -- Check ownership: caller is the user, or caller owns the agent
128
+ IF v_task.user_id = p_caller_id THEN
129
+ v_is_owner := true;
130
+ ELSIF v_task.agent_id IS NOT NULL THEN
131
+ IF EXISTS (SELECT 1 FROM agents WHERE id = v_task.agent_id AND owner_user_id = p_caller_id) THEN
132
+ v_is_owner := true;
133
+ END IF;
134
+ END IF;
135
+
136
+ IF NOT v_is_owner THEN
137
+ RETURN jsonb_build_object('success', false, 'error', 'Not the task owner');
138
+ END IF;
139
+
140
+ -- Build dynamic update from allowed fields only
141
+ FOR v_key IN SELECT jsonb_object_keys(p_updates) LOOP
142
+ IF v_key = ANY(v_allowed) THEN
143
+ IF v_key IN ('completion_pct', 'milestones_completed', 'duration_seconds') THEN
144
+ v_sets := array_append(v_sets, format('%I = %s', v_key, (p_updates->>v_key)::numeric));
145
+ ELSE
146
+ v_sets := array_append(v_sets, format('%I = %L', v_key, p_updates->>v_key));
147
+ END IF;
148
+ END IF;
149
+ END LOOP;
150
+
151
+ IF array_length(v_sets, 1) IS NULL THEN
152
+ RETURN jsonb_build_object('success', false, 'error', 'No valid fields to update');
153
+ END IF;
154
+
155
+ v_sql := v_sql || array_to_string(v_sets, ', ') || format(' WHERE id = %L', p_task_id);
156
+ EXECUTE v_sql;
157
+
158
+ RETURN jsonb_build_object('success', true, 'task_id', p_task_id);
159
+ END;
160
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
161
+
162
+ -- ============================================================
163
+ -- RPC 4: award_xp_direct
164
+ -- Awards XP through the ledger — used by quests and weekly bonuses.
165
+ -- Replaces awardXpWithLock() in TypeScript.
166
+ -- ============================================================
167
+ CREATE OR REPLACE FUNCTION award_xp_direct(
168
+ p_entity_type TEXT,
169
+ p_entity_id UUID,
170
+ p_amount INTEGER,
171
+ p_source_type TEXT,
172
+ p_description TEXT,
173
+ p_source_id UUID DEFAULT NULL
174
+ ) RETURNS JSONB AS $$
175
+ DECLARE
176
+ v_running_total BIGINT;
177
+ BEGIN
178
+ -- Validate entity exists
179
+ IF p_entity_type = 'user' THEN
180
+ IF NOT EXISTS (SELECT 1 FROM users WHERE id = p_entity_id) THEN
181
+ RETURN jsonb_build_object('success', false, 'error', 'User not found');
182
+ END IF;
183
+ ELSIF p_entity_type = 'agent' THEN
184
+ IF NOT EXISTS (SELECT 1 FROM agents WHERE id = p_entity_id) THEN
185
+ RETURN jsonb_build_object('success', false, 'error', 'Agent not found');
186
+ END IF;
187
+ ELSE
188
+ RETURN jsonb_build_object('success', false, 'error', 'Invalid entity_type');
189
+ END IF;
190
+
191
+ -- Validate amount is positive
192
+ IF p_amount <= 0 THEN
193
+ RETURN jsonb_build_object('success', false, 'error', 'Amount must be positive');
194
+ END IF;
195
+
196
+ -- Get current running total
197
+ SELECT COALESCE(running_total, 0) INTO v_running_total
198
+ FROM xp_ledger
199
+ WHERE entity_type = p_entity_type AND entity_id = p_entity_id
200
+ ORDER BY created_at DESC
201
+ LIMIT 1;
202
+
203
+ IF NOT FOUND THEN
204
+ v_running_total := 0;
205
+ END IF;
206
+
207
+ v_running_total := v_running_total + p_amount;
208
+
209
+ -- Insert ledger entry
210
+ INSERT INTO xp_ledger (
211
+ entity_type, entity_id, xp_type, amount,
212
+ source_type, source_id, description, running_total
213
+ ) VALUES (
214
+ p_entity_type, p_entity_id, 'main', p_amount,
215
+ p_source_type, p_source_id, p_description, v_running_total
216
+ );
217
+
218
+ -- Update entity XP column
219
+ IF p_entity_type = 'user' THEN
220
+ UPDATE users SET main_xp = v_running_total WHERE id = p_entity_id;
221
+ ELSE
222
+ UPDATE agents SET xp = v_running_total WHERE id = p_entity_id;
223
+ END IF;
224
+
225
+ RETURN jsonb_build_object(
226
+ 'success', true,
227
+ 'running_total', v_running_total,
228
+ 'amount', p_amount
229
+ );
230
+ END;
231
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
232
+
233
+ -- ============================================================
234
+ -- RPC 5: update_integrity_score
235
+ -- Updates agent integrity_score from rating events.
236
+ -- ============================================================
237
+ CREATE OR REPLACE FUNCTION update_integrity_score(
238
+ p_agent_id UUID,
239
+ p_delta INTEGER
240
+ ) RETURNS JSONB AS $$
241
+ DECLARE
242
+ v_current INTEGER;
243
+ v_new INTEGER;
244
+ BEGIN
245
+ SELECT integrity_score INTO v_current FROM agents WHERE id = p_agent_id;
246
+ IF NOT FOUND THEN
247
+ RETURN jsonb_build_object('success', false, 'error', 'Agent not found');
248
+ END IF;
249
+
250
+ v_new := GREATEST(0, LEAST(100, v_current + p_delta));
251
+ UPDATE agents SET integrity_score = v_new WHERE id = p_agent_id;
252
+
253
+ RETURN jsonb_build_object(
254
+ 'success', true,
255
+ 'agent_id', p_agent_id,
256
+ 'old_score', v_current,
257
+ 'new_score', v_new
258
+ );
259
+ END;
260
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
261
+
262
+ -- ============================================================
263
+ -- RPC 6: record_level_up_timestamp
264
+ -- Records last_leveled_at after a level-up. Called by check_level_up.
265
+ -- ============================================================
266
+ CREATE OR REPLACE FUNCTION record_level_up_timestamp(
267
+ p_entity_type TEXT,
268
+ p_entity_id UUID
269
+ ) RETURNS JSONB AS $$
270
+ BEGIN
271
+ IF p_entity_type = 'user' THEN
272
+ UPDATE users SET last_leveled_at = now() WHERE id = p_entity_id;
273
+ ELSIF p_entity_type = 'agent' THEN
274
+ UPDATE agents SET last_leveled_at = now() WHERE id = p_entity_id;
275
+ ELSE
276
+ RETURN jsonb_build_object('success', false, 'error', 'Invalid entity_type');
277
+ END IF;
278
+
279
+ RETURN jsonb_build_object('success', true);
280
+ END;
281
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
282
+
283
+ -- ============================================================
284
+ -- RPC 7: merge_user_profiles
285
+ -- Handles the profile merge operation atomically.
286
+ -- Moves agents, identities, XP ledger, quest participations,
287
+ -- recalculates XP, and soft-deletes the secondary user.
288
+ -- ============================================================
289
+ CREATE OR REPLACE FUNCTION merge_user_profiles(
290
+ p_primary_user_id UUID,
291
+ p_secondary_user_id UUID
292
+ ) RETURNS JSONB AS $$
293
+ DECLARE
294
+ v_agents_moved INTEGER;
295
+ v_identities_moved INTEGER;
296
+ v_total_xp BIGINT;
297
+ v_sc RECORD;
298
+ v_pc RECORD;
299
+ BEGIN
300
+ -- Verify both users exist
301
+ IF NOT EXISTS (SELECT 1 FROM users WHERE id = p_primary_user_id) THEN
302
+ RETURN jsonb_build_object('success', false, 'error', 'Primary user not found');
303
+ END IF;
304
+ IF NOT EXISTS (SELECT 1 FROM users WHERE id = p_secondary_user_id) THEN
305
+ RETURN jsonb_build_object('success', false, 'error', 'Secondary user not found');
306
+ END IF;
307
+
308
+ -- Move agents
309
+ WITH moved AS (
310
+ UPDATE agents SET owner_user_id = p_primary_user_id
311
+ WHERE owner_user_id = p_secondary_user_id
312
+ RETURNING id
313
+ ) SELECT count(*) INTO v_agents_moved FROM moved;
314
+
315
+ -- Move identities
316
+ WITH moved AS (
317
+ UPDATE user_identities SET user_id = p_primary_user_id
318
+ WHERE user_id = p_secondary_user_id
319
+ RETURNING id
320
+ ) SELECT count(*) INTO v_identities_moved FROM moved;
321
+
322
+ -- Move XP ledger
323
+ UPDATE xp_ledger SET entity_id = p_primary_user_id
324
+ WHERE entity_type = 'user' AND entity_id = p_secondary_user_id;
325
+
326
+ -- Move quest participations
327
+ UPDATE quest_participants SET user_id = p_primary_user_id
328
+ WHERE user_id = p_secondary_user_id;
329
+
330
+ -- Recalculate XP
331
+ SELECT COALESCE(SUM(amount), 0) INTO v_total_xp
332
+ FROM xp_ledger
333
+ WHERE entity_type = 'user' AND entity_id = p_primary_user_id;
334
+
335
+ UPDATE users SET main_xp = v_total_xp WHERE id = p_primary_user_id;
336
+
337
+ -- Merge class XP
338
+ FOR v_sc IN
339
+ SELECT class_name, class_xp FROM user_class_progress
340
+ WHERE user_id = p_secondary_user_id
341
+ LOOP
342
+ SELECT id, class_xp INTO v_pc FROM user_class_progress
343
+ WHERE user_id = p_primary_user_id AND class_name = v_sc.class_name;
344
+ IF FOUND THEN
345
+ UPDATE user_class_progress SET class_xp = v_pc.class_xp + v_sc.class_xp
346
+ WHERE id = v_pc.id;
347
+ END IF;
348
+ END LOOP;
349
+
350
+ -- Soft-delete secondary user
351
+ UPDATE users SET
352
+ username = '[MERGED] ' || left(p_secondary_user_id::text, 8),
353
+ handle = NULL,
354
+ metadata = jsonb_build_object(
355
+ 'merged_into', p_primary_user_id,
356
+ 'merged_at', now()
357
+ )
358
+ WHERE id = p_secondary_user_id;
359
+
360
+ DELETE FROM user_class_progress WHERE user_id = p_secondary_user_id;
361
+
362
+ RETURN jsonb_build_object(
363
+ 'success', true,
364
+ 'primary_user_id', p_primary_user_id,
365
+ 'secondary_user_id', p_secondary_user_id,
366
+ 'agents_moved', v_agents_moved,
367
+ 'identities_moved', v_identities_moved,
368
+ 'total_xp', v_total_xp
369
+ );
370
+ END;
371
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
372
+
373
+ -- ============================================================
374
+ -- Grant EXECUTE to anon role for all RPCs
375
+ -- ============================================================
376
+ GRANT EXECUTE ON FUNCTION update_user_profile(UUID, JSONB) TO anon;
377
+ GRANT EXECUTE ON FUNCTION update_agent_profile(UUID, UUID, JSONB) TO anon;
378
+ GRANT EXECUTE ON FUNCTION update_task_status(UUID, UUID, JSONB) TO anon;
379
+ GRANT EXECUTE ON FUNCTION award_xp_direct(TEXT, UUID, INTEGER, TEXT, TEXT, UUID) TO anon;
380
+ GRANT EXECUTE ON FUNCTION update_integrity_score(UUID, INTEGER) TO anon;
381
+ GRANT EXECUTE ON FUNCTION record_level_up_timestamp(TEXT, UUID) TO anon;
382
+ GRANT EXECUTE ON FUNCTION merge_user_profiles(UUID, UUID) TO anon;
@@ -0,0 +1,97 @@
1
+ -- ============================================================
2
+ -- Migration: Evidence system + Growth plan cache
3
+ -- ============================================================
4
+ -- Adds:
5
+ -- 1. task_evidence table (Table 47) — verifiable output per task
6
+ -- 2. growth_plan_cache table (Table 46) — cached growth plans
7
+ -- 3. Two new columns on tasks: evidence_required, evidence_quality
8
+ -- 4. RLS policies for anon access
9
+ -- ============================================================
10
+
11
+ -- ============================================================
12
+ -- 1. NEW COLUMNS ON tasks
13
+ -- ============================================================
14
+
15
+ ALTER TABLE tasks
16
+ ADD COLUMN IF NOT EXISTS evidence_required boolean NOT NULL DEFAULT true,
17
+ ADD COLUMN IF NOT EXISTS evidence_quality text;
18
+
19
+ COMMENT ON COLUMN tasks.evidence_required IS 'Whether evidence must be attached. Default true for agent_reported tasks.';
20
+ COMMENT ON COLUMN tasks.evidence_quality IS 'Computed at complete_task: strong, medium, low, none.';
21
+
22
+ -- ============================================================
23
+ -- 2. task_evidence TABLE (Table 47)
24
+ -- ============================================================
25
+
26
+ CREATE TABLE IF NOT EXISTS task_evidence (
27
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
28
+ task_id uuid NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
29
+ evidence_type text NOT NULL CHECK (evidence_type IN (
30
+ 'deliverable_url', 'platform_reference', 'screenshot',
31
+ 'code_output', 'api_response', 'text_summary', 'external_link'
32
+ )),
33
+ content text NOT NULL,
34
+ label text,
35
+ verified boolean NOT NULL DEFAULT false,
36
+ verified_at timestamptz,
37
+ added_at timestamptz NOT NULL DEFAULT now(),
38
+ metadata jsonb
39
+ );
40
+
41
+ CREATE INDEX IF NOT EXISTS idx_task_evidence_task_id ON task_evidence(task_id);
42
+ CREATE INDEX IF NOT EXISTS idx_task_evidence_type_verified ON task_evidence(evidence_type, verified);
43
+
44
+ COMMENT ON TABLE task_evidence IS 'Verifiable evidence attached to completed tasks. One or more items per task.';
45
+
46
+ -- ============================================================
47
+ -- 3. growth_plan_cache TABLE (Table 46)
48
+ -- ============================================================
49
+
50
+ CREATE TABLE IF NOT EXISTS growth_plan_cache (
51
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
52
+ agent_id uuid NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
53
+ target_hash text NOT NULL,
54
+ plan jsonb NOT NULL,
55
+ generated_at timestamptz NOT NULL DEFAULT now(),
56
+ expires_at timestamptz NOT NULL DEFAULT (now() + interval '7 days'),
57
+ invalidated boolean NOT NULL DEFAULT false
58
+ );
59
+
60
+ CREATE INDEX IF NOT EXISTS idx_growth_plan_cache_lookup
61
+ ON growth_plan_cache(agent_id, target_hash, invalidated, expires_at);
62
+
63
+ COMMENT ON TABLE growth_plan_cache IS 'Cached growth plans per agent+target. Invalidated on agent state changes.';
64
+
65
+ -- ============================================================
66
+ -- 4. RLS POLICIES
67
+ -- ============================================================
68
+
69
+ ALTER TABLE task_evidence ENABLE ROW LEVEL SECURITY;
70
+ ALTER TABLE growth_plan_cache ENABLE ROW LEVEL SECURITY;
71
+
72
+ -- task_evidence: anon can read and insert (MCP server enforces rules)
73
+ CREATE POLICY "anon_select_task_evidence" ON task_evidence
74
+ FOR SELECT TO anon USING (true);
75
+
76
+ CREATE POLICY "anon_insert_task_evidence" ON task_evidence
77
+ FOR INSERT TO anon WITH CHECK (true);
78
+
79
+ CREATE POLICY "anon_update_task_evidence" ON task_evidence
80
+ FOR UPDATE TO anon USING (true) WITH CHECK (true);
81
+
82
+ -- growth_plan_cache: anon can read, insert, and update (invalidation)
83
+ CREATE POLICY "anon_select_growth_plan_cache" ON growth_plan_cache
84
+ FOR SELECT TO anon USING (true);
85
+
86
+ CREATE POLICY "anon_insert_growth_plan_cache" ON growth_plan_cache
87
+ FOR INSERT TO anon WITH CHECK (true);
88
+
89
+ CREATE POLICY "anon_update_growth_plan_cache" ON growth_plan_cache
90
+ FOR UPDATE TO anon USING (true) WITH CHECK (true);
91
+
92
+ -- ============================================================
93
+ -- 5. UPDATE tasks RLS (allow updating new columns)
94
+ -- ============================================================
95
+ -- The existing anon UPDATE policy on tasks should already cover
96
+ -- evidence_required and evidence_quality since they're new columns
97
+ -- on the same table. No additional policy needed.
@@ -0,0 +1,62 @@
1
+ -- ============================================================
2
+ -- Seed 35 quests across 6 categories
3
+ -- ============================================================
4
+
5
+ -- Onboarding Track (sequential, order 1-7)
6
+ INSERT INTO quests (title, description, source, quest_type, target_type, base_xp_reward, requirements, safe_for_autonomous, status, metadata)
7
+ VALUES
8
+ ('First Steps', 'Register your Level-Up profile to start tracking XP and progress.', 'internal', 'default', 'user', 5, '{"type":"registration","target":1}', true, 'active', '{"category":"onboarding","order":1}'),
9
+ ('Equip Your Agent', 'Add 3 skills to your agent. Skills improve task quality and reduce leveling costs.', 'internal', 'default', 'both', 15, '{"type":"skill_count","target":3}', true, 'active', '{"category":"onboarding","order":2}'),
10
+ ('Tool Collector', 'Install 3 MCP tools. Each install awards +20 XP and unlocks new skill categories.', 'internal', 'default', 'both', 20, '{"type":"mcp_install","target":3}', true, 'active', '{"category":"onboarding","order":3}'),
11
+ ('First Delivery', 'Complete your first task with evidence. This is where real XP starts flowing.', 'internal', 'default', 'both', 15, '{"type":"task_count","target":1,"filter":{"status":"completed"}}', true, 'active', '{"category":"onboarding","order":4}'),
12
+ ('Quality Work', 'Complete a task with a quality score above 3.5. Evidence and proper difficulty help.', 'internal', 'default', 'both', 20, '{"type":"quality_score","target":3.5}', true, 'active', '{"category":"onboarding","order":5}'),
13
+ ('Evidence Keeper', 'Complete a standard or higher task with strong evidence (URL, code output, or platform reference).', 'internal', 'default', 'both', 15, '{"type":"evidence_quality","target":1,"filter":{"quality":"strong"}}', true, 'active', '{"category":"onboarding","order":6}'),
14
+ ('Rising Star', 'Reach Level 5. You''re no longer a beginner — complex tasks are now unlocked.', 'internal', 'default', 'agent', 30, '{"type":"level","target":5}', true, 'active', '{"category":"onboarding","order":7}');
15
+
16
+ -- Skill Development Track (unlocks at level 3)
17
+ INSERT INTO quests (title, description, source, quest_type, target_type, base_xp_reward, requirements, safe_for_autonomous, min_user_level, status, metadata)
18
+ VALUES
19
+ ('Code Warrior', 'Complete 5 coding tasks (Bug Fix, Feature Build, or Code Review).', 'internal', 'default', 'both', 25, '{"type":"task_count","target":5,"filter":{"task_types":["Bug Fix","Feature Build","Code Review"]}}', true, 3, 'active', '{"category":"skill"}'),
20
+ ('Wordsmith', 'Complete 5 writing tasks (Documentation, Email Draft, or Content Writing).', 'internal', 'default', 'both', 25, '{"type":"task_count","target":5,"filter":{"task_types":["Process Documentation","Email Draft","Content Writing"]}}', true, 3, 'active', '{"category":"skill"}'),
21
+ ('Researcher', 'Complete 3 research tasks (Research Report or Competitive Analysis).', 'internal', 'default', 'both', 20, '{"type":"task_count","target":3,"filter":{"task_types":["Research Report","Competitive Analysis"]}}', true, 3, 'active', '{"category":"skill"}'),
22
+ ('Ops Master', 'Complete 3 infrastructure tasks (Deployment or Pipeline Build).', 'internal', 'default', 'both', 30, '{"type":"task_count","target":3,"filter":{"task_types":["Deployment","Pipeline Build"]}}', true, 3, 'active', '{"category":"skill"}'),
23
+ ('Multi-Talented', 'Have 5+ skills on your agent at proficiency 2 or higher.', 'internal', 'default', 'agent', 25, '{"type":"skill_proficiency_count","target":5,"filter":{"min_proficiency":2}}', true, 3, 'active', '{"category":"skill"}'),
24
+ ('Specialist', 'Reach proficiency 5 in any single skill. Deep expertise pays off.', 'internal', 'default', 'agent', 35, '{"type":"skill_proficiency_max","target":5}', true, 3, 'active', '{"category":"skill"}');
25
+
26
+ -- Automation Track (unlocks at level 5)
27
+ INSERT INTO quests (title, description, source, quest_type, target_type, base_xp_reward, requirements, safe_for_autonomous, min_user_level, status, metadata)
28
+ VALUES
29
+ ('Hands Off', 'Complete a task with agent_solo performer type. Let the agent fly solo.', 'internal', 'default', 'agent', 20, '{"type":"performer_type","target":1,"filter":{"performer_type":"agent_solo"}}', true, 5, 'active', '{"category":"automation"}'),
30
+ ('Dynamic Duo', 'Complete 10 tasks together as user_with_agent. Teamwork makes the dream work.', 'internal', 'default', 'both', 30, '{"type":"performer_type","target":10,"filter":{"performer_type":"user_with_agent"}}', true, 5, 'active', '{"category":"automation"}'),
31
+ ('Orchestrator', 'Complete a task with orchestrated performer type involving multiple agents.', 'internal', 'default', 'both', 50, '{"type":"performer_type","target":1,"filter":{"performer_type":"orchestrated"}}', false, 5, 'active', '{"category":"automation"}'),
32
+ ('Deployer', 'Complete a task with output_type deployed. Ship something live.', 'internal', 'default', 'both', 40, '{"type":"output_type","target":1,"filter":{"output_type":"deployed"}}', true, 5, 'active', '{"category":"automation"}'),
33
+ ('Chain Reaction', 'Complete 3 different task types in a single day. Variety is the spice of XP.', 'internal', 'default', 'both', 25, '{"type":"daily_task_diversity","target":3}', true, 5, 'active', '{"category":"automation"}');
34
+
35
+ -- Weekly Challenges (recurring)
36
+ INSERT INTO quests (title, description, source, quest_type, target_type, base_xp_reward, requirements, safe_for_autonomous, status, metadata)
37
+ VALUES
38
+ ('Productive Week', 'Complete 5 tasks in 7 days. Consistency beats intensity.', 'internal', 'recurring', 'both', 25, '{"type":"task_count","target":5,"filter":{"period":"week"}}', true, 'active', '{"category":"weekly"}'),
39
+ ('Skill Builder', 'Increase any skill proficiency by 1 level this week.', 'internal', 'recurring', 'agent', 15, '{"type":"skill_proficiency_gain","target":1,"filter":{"period":"week"}}', true, 'active', '{"category":"weekly"}'),
40
+ ('Streak Keeper', 'Complete at least 1 task per day for 5 consecutive days.', 'internal', 'recurring', 'both', 30, '{"type":"streak","target":5}', true, 'active', '{"category":"weekly"}'),
41
+ ('Diversity Bonus', 'Complete 3 different task types this week. Breadth earns bonus XP.', 'internal', 'recurring', 'both', 20, '{"type":"weekly_task_diversity","target":3}', true, 'active', '{"category":"weekly"}'),
42
+ ('High Standards', 'Achieve an average quality score above 4.0 across all tasks this week.', 'internal', 'recurring', 'both', 25, '{"type":"quality_score_avg","target":4.0,"filter":{"period":"week"}}', true, 'active', '{"category":"weekly"}');
43
+
44
+ -- Achievement Quests (one-off milestones)
45
+ INSERT INTO quests (title, description, source, quest_type, target_type, base_xp_reward, requirements, safe_for_autonomous, status, metadata)
46
+ VALUES
47
+ ('Century', 'Earn 100 total XP. Your first major milestone.', 'internal', 'one_off', 'both', 10, '{"type":"total_xp","target":100}', true, 'active', '{"category":"achievement"}'),
48
+ ('Rank D Achiever', 'Reach Rank D — Pathfinder. You''re finding your way.', 'internal', 'one_off', 'agent', 20, '{"type":"rank","target":1,"filter":{"rank":"D"}}', true, 'active', '{"category":"achievement"}'),
49
+ ('Rank C Achiever', 'Reach Rank C — Specialist. Real expertise recognized.', 'internal', 'one_off', 'agent', 40, '{"type":"rank","target":1,"filter":{"rank":"C"}}', true, 'active', '{"category":"achievement"}'),
50
+ ('Half Century Tasks', 'Complete 50 tasks total. Dedication pays off.', 'internal', 'one_off', 'both', 30, '{"type":"task_count","target":50}', true, 'active', '{"category":"achievement"}'),
51
+ ('Century Tasks', 'Complete 100 tasks total. You''re a machine (or working with one).', 'internal', 'one_off', 'both', 50, '{"type":"task_count","target":100}', true, 'active', '{"category":"achievement"}'),
52
+ ('Integrity Champion', 'Maintain integrity score above 80 for 30 consecutive days.', 'internal', 'one_off', 'agent', 40, '{"type":"integrity_duration","target":30,"filter":{"min_score":80}}', true, 'active', '{"category":"achievement"}'),
53
+ ('Community Builder', 'Have your work reviewed by another agent. Collaboration earns respect.', 'internal', 'one_off', 'both', 25, '{"type":"peer_review","target":1}', false, 'active', '{"category":"achievement"}');
54
+
55
+ -- Exploration Quests (discover features)
56
+ INSERT INTO quests (title, description, source, quest_type, target_type, base_xp_reward, requirements, safe_for_autonomous, status, metadata)
57
+ VALUES
58
+ ('Growth Minded', 'Check your growth plan to see the path to the next rank.', 'internal', 'one_off', 'both', 5, '{"type":"tool_call","target":1,"filter":{"tool":"levelup_get_growth_plan"}}', true, 'active', '{"category":"exploration"}'),
59
+ ('Skill Browser', 'Browse recommended skillsets for 3 or more ability categories.', 'internal', 'one_off', 'both', 10, '{"type":"tool_call","target":3,"filter":{"tool":"levelup_browse_skillsets"}}', true, 'active', '{"category":"exploration"}'),
60
+ ('Leaderboard Climber', 'Check the leaderboard to see where you stand.', 'internal', 'one_off', 'both', 5, '{"type":"tool_call","target":1,"filter":{"tool":"levelup_get_leaderboard"}}', true, 'active', '{"category":"exploration"}'),
61
+ ('Self Aware', 'Submit an agent self-evaluation after completing a task.', 'internal', 'one_off', 'agent', 10, '{"type":"tool_call","target":1,"filter":{"tool":"levelup_submit_agent_evaluation"}}', true, 'active', '{"category":"exploration"}'),
62
+ ('Profile Complete', 'Set a vanity handle and link an identity to your profile.', 'internal', 'one_off', 'user', 10, '{"type":"profile_complete","target":1}', true, 'active', '{"category":"exploration"}');
@@ -0,0 +1,16 @@
1
+ -- ============================================================
2
+ -- Add last_leveled_at for star cooldown enforcement
3
+ -- Fix quest_completions split_pct default
4
+ -- ============================================================
5
+
6
+ -- Add last_leveled_at columns for star cooldown tracking
7
+ ALTER TABLE users ADD COLUMN IF NOT EXISTS last_leveled_at timestamptz;
8
+ ALTER TABLE agents ADD COLUMN IF NOT EXISTS last_leveled_at timestamptz;
9
+
10
+ -- Set default for quest_completions.split_pct so it doesn't require explicit value
11
+ ALTER TABLE quest_completions ALTER COLUMN split_pct SET DEFAULT 100;
12
+
13
+ -- Add user_star_cooldown_hours config if missing (matches agent version)
14
+ INSERT INTO xp_config (config_key, config_value, description)
15
+ VALUES ('user_star_cooldown_hours', '{"E":0,"D":12,"C":72,"B":168,"A":504,"S":504}', 'Hours between level-ups per rank for users')
16
+ ON CONFLICT (config_key) DO NOTHING;
@@ -0,0 +1,25 @@
1
+ -- ============================================================
2
+ -- Restore canonical rank names for users and agents
3
+ -- ============================================================
4
+ -- User ranks: Analyst → Tactician → Strategist → Commander → Grandmaster → Supreme
5
+ -- Agent ranks: Novice → Pathfinder → Enlightened → Paragon → Transcendent → Celestial
6
+ --
7
+ -- These are the original rank names designed for the Level-Up system.
8
+ -- This migration ensures they are correct regardless of prior DB state.
9
+ -- ============================================================
10
+
11
+ -- User ranks (entity_type = 'user')
12
+ UPDATE rank_definitions SET rank_name = 'Analyst' WHERE rank_letter = 'E' AND entity_type = 'user';
13
+ UPDATE rank_definitions SET rank_name = 'Tactician' WHERE rank_letter = 'D' AND entity_type = 'user';
14
+ UPDATE rank_definitions SET rank_name = 'Strategist' WHERE rank_letter = 'C' AND entity_type = 'user';
15
+ UPDATE rank_definitions SET rank_name = 'Commander' WHERE rank_letter = 'B' AND entity_type = 'user';
16
+ UPDATE rank_definitions SET rank_name = 'Grandmaster' WHERE rank_letter = 'A' AND entity_type = 'user';
17
+ UPDATE rank_definitions SET rank_name = 'Supreme' WHERE rank_letter = 'S' AND entity_type = 'user';
18
+
19
+ -- Agent ranks (entity_type = 'agent')
20
+ UPDATE rank_definitions SET rank_name = 'Novice' WHERE rank_letter = 'E' AND entity_type = 'agent';
21
+ UPDATE rank_definitions SET rank_name = 'Pathfinder' WHERE rank_letter = 'D' AND entity_type = 'agent';
22
+ UPDATE rank_definitions SET rank_name = 'Enlightened' WHERE rank_letter = 'C' AND entity_type = 'agent';
23
+ UPDATE rank_definitions SET rank_name = 'Paragon' WHERE rank_letter = 'B' AND entity_type = 'agent';
24
+ UPDATE rank_definitions SET rank_name = 'Transcendent' WHERE rank_letter = 'A' AND entity_type = 'agent';
25
+ UPDATE rank_definitions SET rank_name = 'Celestial' WHERE rank_letter = 'S' AND entity_type = 'agent';
@@ -0,0 +1,24 @@
1
+ -- ============================================================
2
+ -- Migration: Chinese (zh) rank names for Level-Up CN
3
+ -- ============================================================
4
+ -- User ranks: 分析师 → 战术师 → 策略师 → 指挥官 → 大师 → 至尊
5
+ -- Agent ranks (xianxia cultivation): 练器境 → 筑基期 → 金丹期 → 元婴期 → 大乘期 → 人工大能
6
+ -- ============================================================
7
+
8
+ INSERT INTO rank_definitions (rank_letter, entity_type, rank_name, min_level, max_level, locale)
9
+ VALUES
10
+ -- User ranks (zh)
11
+ ('E', 'user', '分析师', 1, 5, 'zh'),
12
+ ('D', 'user', '战术师', 6, 10, 'zh'),
13
+ ('C', 'user', '策略师', 11, 15, 'zh'),
14
+ ('B', 'user', '指挥官', 16, 20, 'zh'),
15
+ ('A', 'user', '大师', 21, 25, 'zh'),
16
+ ('S', 'user', '至尊', 26, 30, 'zh'),
17
+ -- Agent ranks (zh) — xianxia cultivation realms
18
+ ('E', 'agent', '练器境', 1, 5, 'zh'),
19
+ ('D', 'agent', '筑基期', 6, 10, 'zh'),
20
+ ('C', 'agent', '金丹期', 11, 15, 'zh'),
21
+ ('B', 'agent', '元婴期', 16, 20, 'zh'),
22
+ ('A', 'agent', '大乘期', 21, 25, 'zh'),
23
+ ('S', 'agent', '人工大能', 26, 30, 'zh')
24
+ ON CONFLICT DO NOTHING;
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: "node",
7
+ include: ["src/**/*.test.ts"],
8
+ testTimeout: 15000,
9
+ pool: "threads",
10
+ },
11
+ });