@gethmy/mcp 2.4.7 → 2.5.1

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.
@@ -14,870 +14,22 @@ var __export = (target, all) => {
14
14
  };
15
15
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
16
16
 
17
- // ../memory/dist/schema.js
18
- var init_schema = () => {};
19
-
20
- // ../memory/dist/constraints.js
21
- var init_constraints = __esm(() => {
22
- init_schema();
23
- });
24
- // ../memory/dist/client.js
25
- var init_client = __esm(() => {
26
- init_constraints();
27
- });
28
-
29
- // ../memory/dist/graph-walk.js
30
- async function discoverRelatedContext(client, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
31
- const visited = new Set;
32
- const collectedEntities = [];
33
- const collectedRelations = [];
34
- let truncated = false;
35
- const queue = startIds.map((id) => [id, 0]);
36
- for (const id of startIds) {
37
- visited.add(id);
38
- }
39
- while (queue.length > 0) {
40
- const [entityId, depth] = queue.shift();
41
- if (collectedEntities.length >= maxEntities) {
42
- truncated = true;
43
- break;
44
- }
45
- if (depth > maxDepth)
46
- continue;
47
- try {
48
- const entityResult = await client.getMemoryEntity(entityId);
49
- const entity = entityResult.entity;
50
- if (entity) {
51
- collectedEntities.push({
52
- id: entity.id,
53
- type: entity.type,
54
- title: entity.title,
55
- confidence: entity.confidence ?? 1,
56
- memory_tier: entity.memory_tier || "reference"
57
- });
58
- }
59
- if (depth >= maxDepth)
60
- continue;
61
- const related = await client.getRelatedEntities(entityId);
62
- for (const raw of related.outgoing || []) {
63
- const rel = raw;
64
- const relConfidence = rel.confidence ?? 1;
65
- if (relConfidence < minConfidence)
66
- continue;
67
- const target = rel.target;
68
- const targetId = target?.id ?? rel.target_id;
69
- if (targetId && !visited.has(targetId)) {
70
- visited.add(targetId);
71
- queue.push([targetId, depth + 1]);
72
- collectedRelations.push({
73
- id: rel.id,
74
- source_id: entityId,
75
- target_id: targetId,
76
- relation_type: rel.relation_type,
77
- confidence: relConfidence
78
- });
79
- }
80
- }
81
- for (const raw of related.incoming || []) {
82
- const rel = raw;
83
- const relConfidence = rel.confidence ?? 1;
84
- if (relConfidence < minConfidence)
85
- continue;
86
- const source = rel.source;
87
- const sourceId = source?.id ?? rel.source_id;
88
- if (sourceId && !visited.has(sourceId)) {
89
- visited.add(sourceId);
90
- queue.push([sourceId, depth + 1]);
91
- collectedRelations.push({
92
- id: rel.id,
93
- source_id: sourceId,
94
- target_id: entityId,
95
- relation_type: rel.relation_type,
96
- confidence: relConfidence
97
- });
98
- }
99
- }
100
- } catch {}
101
- }
102
- return {
103
- entities: collectedEntities,
104
- relations: collectedRelations,
105
- depth: maxDepth,
106
- truncated
107
- };
108
- }
109
-
110
- // ../memory/dist/lifecycle.js
111
- function checkPromotion(currentTier, accessCount, confidence, createdAt) {
112
- const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
113
- const base = {
114
- eligible: false,
115
- targetTier: null,
116
- reason: null,
117
- currentTier,
118
- accessCount,
119
- confidence,
120
- ageDays
121
- };
122
- if (currentTier === "draft") {
123
- const rules = PROMOTION_RULES.draftToEpisode;
124
- if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
125
- return {
126
- ...base,
127
- eligible: true,
128
- targetTier: "episode",
129
- reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
130
- };
131
- }
132
- }
133
- if (currentTier === "episode") {
134
- const rules = PROMOTION_RULES.episodeToReference;
135
- if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
136
- return {
137
- ...base,
138
- eligible: true,
139
- targetTier: "reference",
140
- reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
141
- };
142
- }
143
- }
144
- return base;
145
- }
146
- var PROMOTION_RULES;
147
- var init_lifecycle = __esm(() => {
148
- PROMOTION_RULES = {
149
- draftToEpisode: {
150
- minAccessCount: 5,
151
- minConfidence: 0.8,
152
- minAgeDays: 1
153
- },
154
- episodeToReference: {
155
- minAccessCount: 10,
156
- minConfidence: 0.9,
157
- minAgeDays: 7
158
- }
159
- };
160
- });
161
- // ../memory/dist/sync.js
162
- var init_sync = () => {};
163
-
164
- // ../memory/dist/index.js
165
- var init_dist = __esm(() => {
166
- init_client();
167
- init_constraints();
168
- init_lifecycle();
169
- init_schema();
170
- init_sync();
171
- });
172
-
173
- // src/context-assembly.ts
174
- var exports_context_assembly = {};
175
- __export(exports_context_assembly, {
176
- trackSessionAssembly: () => trackSessionAssembly,
177
- recordContextFeedback: () => recordContextFeedback,
178
- mapToContextEntity: () => mapToContextEntity,
179
- getSessionAssemblyId: () => getSessionAssemblyId,
180
- getCachedManifest: () => getCachedManifest,
181
- expandQuery: () => expandQuery,
182
- computeRelevanceScore: () => computeRelevanceScore,
183
- cacheManifest: () => cacheManifest,
184
- assembleContext: () => assembleContext
185
- });
186
- function estimateTokens(text) {
187
- return Math.ceil(text.length / 4);
188
- }
189
- function passesQualityGate(entity) {
190
- const content = entity.content.trim();
191
- if (content.length < 50)
192
- return false;
193
- const normalizedTitle = entity.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
194
- const normalizedContent = content.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
195
- if (normalizedContent.length < normalizedTitle.length * 1.5) {
196
- return false;
197
- }
198
- if (entity.type === "pattern" && /recurring .+ \(\d+ instances\)/i.test(entity.title)) {
199
- const lines = content.split(`
200
- `).filter((l) => l.trim().length > 0);
201
- const bulletLines = lines.filter((l) => l.trim().startsWith("- "));
202
- if (bulletLines.length > lines.length * 0.6)
203
- return false;
204
- }
205
- if (entity.type === "procedure") {
206
- const stepCount = (content.match(/^\d+\.\s/gm) || []).length;
207
- if (stepCount < 3)
208
- return false;
209
- }
210
- return true;
211
- }
212
- function generateAssemblyId() {
213
- return `ctx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
214
- }
215
- function truncateContent(content, maxTokens) {
216
- const currentTokens = estimateTokens(content);
217
- if (currentTokens <= maxTokens) {
218
- return { text: content, truncated: false };
219
- }
220
- const paragraphs = content.split(/\n\n+/);
221
- let result = paragraphs[0];
222
- for (let i = 1;i < paragraphs.length; i++) {
223
- const lines = paragraphs[i].split(`
224
- `).filter((l) => l.startsWith("- ") || l.startsWith("* "));
225
- if (lines.length > 0) {
226
- const bulletSection = lines.join(`
227
- `);
228
- if (estimateTokens(result + `
229
-
230
- ` + bulletSection) <= maxTokens) {
231
- result += `
232
-
233
- ` + bulletSection;
234
- }
235
- }
236
- }
237
- if (estimateTokens(result) > maxTokens) {
238
- const maxChars = maxTokens * 4;
239
- result = result.slice(0, maxChars - 3) + "...";
240
- }
241
- return { text: result, truncated: true };
242
- }
243
- function escapeRegex(str) {
244
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
245
- }
246
- function expandQuery(taskContext) {
247
- const queries = [taskContext];
248
- const lowerQueries = [taskContext.toLowerCase()];
249
- const words = taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
250
- const expandableWords = words.filter((w) => QUERY_SYNONYMS[w]);
251
- for (const word of expandableWords) {
252
- const synonyms = QUERY_SYNONYMS[word];
253
- if (!synonyms)
254
- continue;
255
- const variation = taskContext.replace(new RegExp(`\\b${escapeRegex(word)}\\b`, "gi"), synonyms[0]);
256
- const lowerVariation = variation.toLowerCase();
257
- if (lowerVariation !== taskContext.toLowerCase() && !lowerQueries.includes(lowerVariation)) {
258
- queries.push(variation);
259
- lowerQueries.push(lowerVariation);
260
- }
261
- if (queries.length >= MAX_QUERY_VARIATIONS)
262
- break;
263
- }
264
- if (words.length >= 3) {
265
- const keyPhrases = words.filter((w) => ![
266
- "the",
267
- "and",
268
- "for",
269
- "with",
270
- "this",
271
- "that",
272
- "from",
273
- "into"
274
- ].includes(w)).slice(0, 4).join(" ");
275
- if (!lowerQueries.includes(keyPhrases)) {
276
- queries.push(keyPhrases);
277
- }
278
- }
279
- return queries.slice(0, MAX_QUERY_VARIATIONS);
280
- }
281
- function computeRelevanceScore(entity, taskContext, cardLabels, graphRelations) {
282
- const reasons = [];
283
- let score = 0;
284
- const hasRrfScore = entity.rrf_score !== undefined && entity.rrf_score > 0;
285
- if (hasRrfScore) {
286
- const normalizedRrf = Math.min(entity.rrf_score / 0.04, 1);
287
- const rrfContribution = normalizedRrf * 0.3;
288
- score += rrfContribution;
289
- reasons.push(`hybrid_search(rrf=${entity.rrf_score.toFixed(4)})`);
290
- }
291
- const textMatchWeight = hasRrfScore ? 0.15 : 0.4;
292
- const taskWords = new Set(taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
293
- const entityWords = new Set(`${entity.title} ${entity.content}`.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
294
- const overlap = [...taskWords].filter((w) => entityWords.has(w));
295
- if (overlap.length > 0) {
296
- const textScore = Math.min(overlap.length / Math.max(taskWords.size, 1), 1) * textMatchWeight;
297
- score += textScore;
298
- reasons.push(`text_match(${overlap.length} words)`);
299
- }
300
- if (cardLabels.length > 0 && entity.tags.length > 0) {
301
- const labelSet = new Set(cardLabels.map((l) => l.toLowerCase()));
302
- const tagOverlap = entity.tags.filter((t) => labelSet.has(t.toLowerCase()));
303
- if (tagOverlap.length > 0) {
304
- const tagScore = tagOverlap.length / cardLabels.length * 0.3;
305
- score += tagScore;
306
- reasons.push(`tag_match(${tagOverlap.join(",")})`);
307
- }
308
- }
309
- score += entity.confidence * 0.15;
310
- if (entity.confidence >= 0.9) {
311
- reasons.push("high_confidence");
312
- }
313
- if (entity.last_accessed_at) {
314
- const daysSinceAccess = (Date.now() - new Date(entity.last_accessed_at).getTime()) / (1000 * 60 * 60 * 24);
315
- const halfLife = { draft: 7, episode: 30, reference: 180 }[entity.memory_tier];
316
- const recencyScore = 0.5 ** (daysSinceAccess / halfLife) * 0.1;
317
- score += recencyScore;
318
- if (daysSinceAccess < 7)
319
- reasons.push("recently_accessed");
320
- }
321
- if (entity.access_count > 0) {
322
- const freqScore = Math.log10(entity.access_count + 1) * 0.05;
323
- score += Math.min(freqScore, 0.1);
324
- if (entity.access_count >= 5)
325
- reasons.push(`frequently_used(${entity.access_count})`);
326
- }
327
- const usefulnessScore = entity.metadata?.usefulness_score ?? 0;
328
- if (usefulnessScore >= 3) {
329
- const usefulnessBoost = Math.min(usefulnessScore / 20, 0.15);
330
- score += usefulnessBoost;
331
- reasons.push(`useful(${usefulnessScore})`);
332
- } else if (usefulnessScore === 0 && entity.access_count >= 5) {
333
- score -= 0.02;
334
- reasons.push("low_usefulness");
335
- }
336
- if (entity.type === "procedure") {
337
- score += 0.1;
338
- reasons.push("procedure_boost");
339
- }
340
- if (graphRelations && graphRelations.length > 0) {
341
- const entityRelations = graphRelations.filter((r) => r.source_id === entity.id || r.target_id === entity.id);
342
- if (entityRelations.length > 0) {
343
- let bestBonus = 0;
344
- let bestRelType = "";
345
- for (const rel of entityRelations) {
346
- const bonus = RELATION_BONUSES[rel.relation_type] ?? 0.1;
347
- if (bonus > bestBonus) {
348
- bestBonus = bonus;
349
- bestRelType = rel.relation_type;
350
- }
351
- }
352
- score += bestBonus;
353
- reasons.push(`graph_walk(${bestRelType})`);
354
- }
355
- }
356
- score = Math.max(0, Math.min(score, 1));
357
- const tierWeight = TIER_WEIGHTS[entity.memory_tier];
358
- score *= tierWeight;
359
- return { score, reasons };
360
- }
361
- async function assembleContext(options) {
362
- const {
363
- workspaceId,
364
- projectId,
365
- taskContext,
366
- cardLabels = [],
367
- tokenBudget = DEFAULT_TOKEN_BUDGET,
368
- client: client2,
369
- graphWalkEnabled = true,
370
- queryExpansionEnabled = true,
371
- enableLlmReranking = false,
372
- rerankFn
373
- } = options;
374
- const assemblyId = generateAssemblyId();
375
- const manifest = {
376
- assemblyId,
377
- timestamp: new Date().toISOString(),
378
- included: [],
379
- excluded: [],
380
- budgetUsed: 0,
381
- budgetTotal: tokenBudget,
382
- tierBreakdown: {
383
- draft: { count: 0, tokens: 0 },
384
- episode: { count: 0, tokens: 0 },
385
- reference: { count: 0, tokens: 0 }
386
- }
387
- };
388
- const candidates = [];
389
- const queries = queryExpansionEnabled ? expandQuery(taskContext) : [taskContext];
390
- const searchResults = await Promise.allSettled(queries.map((query) => client2.searchMemoryEntities(workspaceId, query, {
391
- project_id: projectId,
392
- limit: 30
393
- })));
394
- const candidateIds = new Set;
395
- for (const result of searchResults) {
396
- if (result.status !== "fulfilled")
397
- continue;
398
- if (result.value.entities?.length > 0) {
399
- for (const raw of result.value.entities) {
400
- const entity = mapToContextEntity(raw);
401
- if (!candidateIds.has(entity.id)) {
402
- candidateIds.add(entity.id);
403
- candidates.push(entity);
404
- }
405
- }
406
- }
407
- }
408
- if (candidates.length < 10 && projectId) {
409
- try {
410
- const listResult = await client2.listMemoryEntities({
411
- workspace_id: workspaceId,
412
- project_id: projectId,
413
- limit: 30
414
- });
415
- if (listResult.entities?.length > 0) {
416
- for (const raw of listResult.entities) {
417
- const entity = mapToContextEntity(raw);
418
- if (!candidateIds.has(entity.id)) {
419
- candidateIds.add(entity.id);
420
- candidates.push(entity);
421
- }
422
- }
423
- }
424
- } catch {}
425
- }
426
- if (candidates.length < 20) {
427
- try {
428
- const wsResult = await client2.listMemoryEntities({
429
- workspace_id: workspaceId,
430
- scope: "workspace",
431
- limit: 20
432
- });
433
- if (wsResult.entities?.length > 0) {
434
- for (const raw of wsResult.entities) {
435
- const entity = mapToContextEntity(raw);
436
- if (!candidateIds.has(entity.id)) {
437
- candidateIds.add(entity.id);
438
- candidates.push(entity);
439
- }
440
- }
441
- }
442
- } catch {}
443
- }
444
- let graphRelations = [];
445
- if (graphWalkEnabled && candidates.length > 0) {
446
- try {
447
- const seedCandidates = [...candidates].sort((a, b) => (b.rrf_score ?? 0) - (a.rrf_score ?? 0)).slice(0, GRAPH_WALK_SEED_COUNT);
448
- const seedIds = seedCandidates.map((c) => c.id);
449
- const walkResult = await discoverRelatedContext(client2, seedIds, GRAPH_WALK_MAX_DEPTH, GRAPH_WALK_MAX_ENTITIES, GRAPH_WALK_MIN_CONFIDENCE);
450
- graphRelations = walkResult.relations;
451
- const newEntityIds = walkResult.entities.filter((e) => !candidateIds.has(e.id)).map((e) => e.id);
452
- if (newEntityIds.length > 0) {
453
- const fetchResults = await Promise.allSettled(newEntityIds.map((id) => client2.getMemoryEntity(id)));
454
- for (const result of fetchResults) {
455
- if (result.status !== "fulfilled" || !result.value.entity)
456
- continue;
457
- const mapped = mapToContextEntity(result.value.entity);
458
- candidateIds.add(mapped.id);
459
- candidates.push(mapped);
460
- }
461
- }
462
- } catch {}
463
- }
464
- if (candidates.length === 0) {
465
- return {
466
- context: "",
467
- manifest,
468
- memories: []
469
- };
470
- }
471
- const qualityCandidates = candidates.filter((entity) => {
472
- if (passesQualityGate(entity))
473
- return true;
474
- manifest.excluded.push({
475
- entityId: entity.id,
476
- title: entity.title,
477
- type: entity.type,
478
- tier: entity.memory_tier,
479
- relevanceScore: 0,
480
- reason: "failed_quality_gate"
481
- });
482
- return false;
483
- });
484
- if (qualityCandidates.length === 0) {
485
- return {
486
- context: "",
487
- manifest,
488
- memories: []
489
- };
490
- }
491
- const scored = qualityCandidates.map((entity) => {
492
- const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels, graphRelations.length > 0 ? graphRelations : undefined);
493
- return { entity, score, reasons };
494
- });
495
- scored.sort((a, b) => b.score - a.score);
496
- if (enableLlmReranking && rerankFn && scored.length >= RERANK_MIN_CANDIDATES) {
497
- const topN = scored.slice(0, RERANK_TOP_N);
498
- const scoreRange = topN[0].score - topN[topN.length - 1].score;
499
- if (scoreRange <= RERANK_CLUSTER_THRESHOLD) {
500
- try {
501
- const rerankCandidates = topN.map((s) => ({
502
- id: s.entity.id,
503
- title: s.entity.title,
504
- snippet: s.entity.content.slice(0, 200)
505
- }));
506
- const rerankedIds = await rerankFn(taskContext, rerankCandidates);
507
- const idOrder = new Map(rerankedIds.map((id, i) => [id, i]));
508
- topN.sort((a, b) => {
509
- const aIdx = idOrder.get(a.entity.id) ?? 999;
510
- const bIdx = idOrder.get(b.entity.id) ?? 999;
511
- return aIdx - bIdx;
512
- });
513
- scored.splice(0, topN.length, ...topN);
514
- } catch {}
515
- }
516
- }
517
- const procedureBudget = Math.floor(tokenBudget * PROCEDURE_BUDGET_FRACTION);
518
- const remainingBudget = tokenBudget - procedureBudget;
519
- const tierBudgets = {
520
- reference: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.reference),
521
- episode: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.episode),
522
- draft: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.draft)
523
- };
524
- const tierUsed = {
525
- reference: 0,
526
- episode: 0,
527
- draft: 0
528
- };
529
- let procedureUsed = 0;
530
- const included = [];
531
- let totalUsed = 0;
532
- let referenceCount = 0;
533
- for (const item of scored) {
534
- if (item.entity.memory_tier === "reference" && item.entity.type !== "procedure" && referenceCount < MIN_REFERENCE_SLOTS) {
535
- const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
536
- const tokens = estimateTokens(`### ${item.entity.title}
537
- ${text}`);
538
- if (totalUsed + tokens <= tokenBudget) {
539
- included.push({ ...item, tokens, truncated });
540
- item.entity.content = text;
541
- totalUsed += tokens;
542
- tierUsed.reference += tokens;
543
- referenceCount++;
544
- }
545
- }
546
- }
547
- const includedIds = new Set(included.map((i) => i.entity.id));
548
- const procedureCandidates = scored.filter((item) => item.entity.type === "procedure" && !includedIds.has(item.entity.id));
549
- for (const item of procedureCandidates) {
550
- if (item.score < MIN_RELEVANCE_THRESHOLD) {
551
- manifest.excluded.push({
552
- entityId: item.entity.id,
553
- title: item.entity.title,
554
- type: item.entity.type,
555
- tier: item.entity.memory_tier,
556
- relevanceScore: item.score,
557
- reason: "below_relevance_threshold"
558
- });
559
- continue;
560
- }
561
- const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
562
- const tokens = estimateTokens(`### ${item.entity.title}
563
- ${text}`);
564
- if (procedureUsed + tokens > procedureBudget) {
565
- const totalRemaining = tokenBudget - totalUsed;
566
- if (tokens > totalRemaining) {
567
- manifest.excluded.push({
568
- entityId: item.entity.id,
569
- title: item.entity.title,
570
- type: item.entity.type,
571
- tier: item.entity.memory_tier,
572
- relevanceScore: item.score,
573
- reason: "procedure_budget_exceeded"
574
- });
575
- continue;
576
- }
577
- }
578
- if (totalUsed + tokens > tokenBudget) {
579
- manifest.excluded.push({
580
- entityId: item.entity.id,
581
- title: item.entity.title,
582
- type: item.entity.type,
583
- tier: item.entity.memory_tier,
584
- relevanceScore: item.score,
585
- reason: "total_budget_exceeded"
586
- });
587
- continue;
588
- }
589
- included.push({ ...item, tokens, truncated });
590
- item.entity.content = text;
591
- totalUsed += tokens;
592
- procedureUsed += tokens;
593
- includedIds.add(item.entity.id);
594
- }
595
- for (const item of scored) {
596
- if (includedIds.has(item.entity.id))
597
- continue;
598
- if (item.entity.type === "procedure")
599
- continue;
600
- if (item.score < MIN_RELEVANCE_THRESHOLD) {
601
- manifest.excluded.push({
602
- entityId: item.entity.id,
603
- title: item.entity.title,
604
- type: item.entity.type,
605
- tier: item.entity.memory_tier,
606
- relevanceScore: item.score,
607
- reason: "below_relevance_threshold"
608
- });
609
- continue;
610
- }
611
- const tier = item.entity.memory_tier;
612
- const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
613
- const tokens = estimateTokens(`### ${item.entity.title}
614
- ${text}`);
615
- if (tierUsed[tier] + tokens > tierBudgets[tier]) {
616
- const totalRemaining = tokenBudget - totalUsed;
617
- if (tokens > totalRemaining) {
618
- manifest.excluded.push({
619
- entityId: item.entity.id,
620
- title: item.entity.title,
621
- type: item.entity.type,
622
- tier,
623
- relevanceScore: item.score,
624
- reason: "budget_exceeded"
625
- });
626
- continue;
627
- }
628
- }
629
- if (totalUsed + tokens > tokenBudget) {
630
- manifest.excluded.push({
631
- entityId: item.entity.id,
632
- title: item.entity.title,
633
- type: item.entity.type,
634
- tier,
635
- relevanceScore: item.score,
636
- reason: "total_budget_exceeded"
637
- });
638
- continue;
639
- }
640
- included.push({ ...item, tokens, truncated });
641
- item.entity.content = text;
642
- totalUsed += tokens;
643
- tierUsed[tier] += tokens;
644
- includedIds.add(item.entity.id);
645
- }
646
- manifest.budgetUsed = totalUsed;
647
- const procedureItems = included.filter((i) => i.entity.type === "procedure");
648
- manifest.tierBreakdown = {
649
- reference: {
650
- count: included.filter((i) => i.entity.memory_tier === "reference" && i.entity.type !== "procedure").length,
651
- tokens: tierUsed.reference
652
- },
653
- episode: {
654
- count: included.filter((i) => i.entity.memory_tier === "episode" && i.entity.type !== "procedure").length,
655
- tokens: tierUsed.episode
656
- },
657
- draft: {
658
- count: included.filter((i) => i.entity.memory_tier === "draft" && i.entity.type !== "procedure").length,
659
- tokens: tierUsed.draft
660
- }
661
- };
662
- manifest.procedureBreakdown = {
663
- count: procedureItems.length,
664
- tokens: procedureUsed,
665
- budget: procedureBudget
666
- };
667
- for (const item of included) {
668
- manifest.included.push({
669
- entityId: item.entity.id,
670
- title: item.entity.title,
671
- type: item.entity.type,
672
- tier: item.entity.memory_tier,
673
- relevanceScore: item.score,
674
- reasons: item.reasons,
675
- tokenCount: item.tokens,
676
- truncated: item.truncated
677
- });
678
- }
679
- const contextSections = [];
680
- const nonProcedureItems = included.filter((i) => i.entity.type !== "procedure");
681
- if (included.length > 0) {
682
- if (procedureItems.length > 0) {
683
- contextSections.push(`## Procedures (${procedureItems.length} loaded, ${procedureUsed}/${procedureBudget} tokens)`);
684
- for (const item of procedureItems) {
685
- const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
686
- const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
687
- contextSections.push(`
688
- ### ${item.entity.title} (confidence: ${item.entity.confidence})${tierLabel}${tags}`);
689
- contextSections.push(item.entity.content);
690
- }
691
- }
692
- if (nonProcedureItems.length > 0) {
693
- contextSections.push(`
694
- ## Relevant Memories (${nonProcedureItems.length} loaded, ${manifest.excluded.length} excluded)`);
695
- contextSections.push(`*Assembly: ${assemblyId} | Budget: ${totalUsed}/${tokenBudget} tokens*`);
696
- for (const item of nonProcedureItems) {
697
- const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
698
- const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
699
- contextSections.push(`
700
- ### ${item.entity.title} (${item.entity.type}, confidence: ${item.entity.confidence})${tierLabel}${tags}`);
701
- contextSections.push(item.entity.content);
702
- }
703
- }
704
- }
705
- incrementAccessCounts(client2, included.map((i) => i.entity.id)).catch(() => {});
706
- promoteEligibleEntities(client2, included.map((i) => i.entity)).catch(() => {});
707
- return {
708
- context: contextSections.join(`
709
- `),
710
- manifest,
711
- memories: included.map((i) => i.entity)
712
- };
713
- }
714
- function mapToContextEntity(raw) {
715
- const e = raw;
716
- return {
717
- id: e.id,
718
- type: e.type,
719
- title: e.title,
720
- content: e.content,
721
- confidence: e.confidence ?? 1,
722
- tags: e.tags || [],
723
- memory_tier: e.memory_tier || "reference",
724
- access_count: e.access_count || 0,
725
- last_accessed_at: e.last_accessed_at || null,
726
- created_at: e.created_at || "",
727
- updated_at: e.updated_at || "",
728
- metadata: e.metadata ?? undefined,
729
- rrf_score: e.rrf_score ?? undefined,
730
- fts_rank: e.fts_rank ?? undefined,
731
- semantic_rank: e.semantic_rank ?? undefined
732
- };
733
- }
734
- async function incrementAccessCounts(client2, entityIds) {
735
- if (entityIds.length === 0)
736
- return;
737
- try {
738
- await client2.batchTouchMemoryEntities(entityIds);
739
- } catch {
740
- await Promise.allSettled(entityIds.map((id) => client2.touchMemoryEntity(id)));
741
- }
742
- }
743
- async function promoteEligibleEntities(client2, entities) {
744
- for (const entity of entities) {
745
- if (entity.memory_tier === "reference")
746
- continue;
747
- if (!entity.created_at)
748
- continue;
749
- const promotion = checkPromotion(entity.memory_tier, entity.access_count + 1, entity.confidence, entity.created_at);
750
- if (promotion.eligible && promotion.targetTier) {
751
- try {
752
- await client2.updateMemoryEntity(entity.id, {
753
- memory_tier: promotion.targetTier,
754
- metadata: {
755
- ...entity.metadata || {},
756
- promoted_at: new Date().toISOString(),
757
- promotion_reason: promotion.reason,
758
- promoted_from: entity.memory_tier
759
- }
760
- });
761
- } catch {}
762
- }
763
- }
764
- }
765
- function cacheManifest(manifest) {
766
- if (manifestCache.size >= MAX_CACHE_SIZE) {
767
- const firstKey = manifestCache.keys().next().value;
768
- if (firstKey)
769
- manifestCache.delete(firstKey);
770
- }
771
- manifestCache.set(manifest.assemblyId, manifest);
772
- }
773
- function getCachedManifest(assemblyId) {
774
- return manifestCache.get(assemblyId);
775
- }
776
- function trackSessionAssembly(cardId, assemblyId) {
777
- if (sessionAssemblyMap.size >= MAX_SESSION_MAP_SIZE) {
778
- const firstKey = sessionAssemblyMap.keys().next().value;
779
- if (firstKey)
780
- sessionAssemblyMap.delete(firstKey);
781
- }
782
- sessionAssemblyMap.set(cardId, assemblyId);
783
- }
784
- function getSessionAssemblyId(cardId) {
785
- return sessionAssemblyMap.get(cardId);
786
- }
787
- async function recordContextFeedback(client2, cardId, sessionStatus, progressPercent, hadBlockers) {
788
- const assemblyId = sessionAssemblyMap.get(cardId);
789
- if (!assemblyId)
790
- return { adjusted: 0 };
791
- const manifest = manifestCache.get(assemblyId);
792
- if (!manifest || manifest.included.length === 0)
793
- return { adjusted: 0 };
794
- let adjusted = 0;
795
- const isSuccess = sessionStatus === "completed" && (progressPercent ?? 0) >= 100;
796
- for (const entry of manifest.included) {
797
- try {
798
- if (isSuccess) {
799
- const { entity } = await client2.getMemoryEntity(entry.entityId);
800
- const e = entity;
801
- const currentUsefulness = e.metadata?.usefulness_score ?? 0;
802
- const newConfidence = Math.min((e.confidence ?? 0.5) + 0.05, 1);
803
- await client2.updateMemoryEntity(entry.entityId, {
804
- confidence: newConfidence,
805
- metadata: {
806
- usefulness_score: currentUsefulness + 1,
807
- last_feedback_at: new Date().toISOString()
808
- }
809
- });
810
- adjusted++;
811
- } else if (hadBlockers) {
812
- const { entity } = await client2.getMemoryEntity(entry.entityId);
813
- const e = entity;
814
- const newConfidence = Math.max((e.confidence ?? 0.5) - 0.02, 0.1);
815
- await client2.updateMemoryEntity(entry.entityId, {
816
- confidence: newConfidence,
817
- metadata: {
818
- last_feedback_at: new Date().toISOString()
819
- }
820
- });
821
- adjusted++;
822
- }
823
- } catch {}
824
- }
825
- sessionAssemblyMap.delete(cardId);
826
- return { adjusted };
827
- }
828
- var DEFAULT_TOKEN_BUDGET = 4000, MAX_TOKENS_PER_ENTITY = 500, MIN_RELEVANCE_THRESHOLD = 0.15, TIER_WEIGHTS, PROCEDURE_BUDGET_FRACTION = 0.15, TIER_BUDGET_ALLOCATION, MIN_REFERENCE_SLOTS = 1, GRAPH_WALK_MAX_DEPTH = 1, GRAPH_WALK_MAX_ENTITIES = 10, GRAPH_WALK_MIN_CONFIDENCE = 0.5, GRAPH_WALK_SEED_COUNT = 5, MAX_QUERY_VARIATIONS = 4, RERANK_CLUSTER_THRESHOLD = 0.05, RERANK_TOP_N = 10, RERANK_MIN_CANDIDATES = 5, RELATION_BONUSES, QUERY_SYNONYMS, manifestCache, MAX_CACHE_SIZE = 50, sessionAssemblyMap, MAX_SESSION_MAP_SIZE = 100;
829
- var init_context_assembly = __esm(() => {
830
- init_dist();
831
- TIER_WEIGHTS = {
832
- reference: 1,
833
- episode: 0.7,
834
- draft: 0.4
835
- };
836
- TIER_BUDGET_ALLOCATION = {
837
- reference: 0.6,
838
- episode: 0.3,
839
- draft: 0.1
840
- };
841
- RELATION_BONUSES = {
842
- depends_on: 0.15,
843
- resolved_by: 0.2,
844
- relates_to: 0.1,
845
- implements: 0.15,
846
- blocks: 0.15,
847
- references: 0.1,
848
- extends: 0.1,
849
- caused_by: 0.15
850
- };
851
- QUERY_SYNONYMS = {
852
- auth: ["authentication", "authorization", "session"],
853
- authentication: ["auth", "session", "sign-in"],
854
- login: ["sign-in", "authentication", "session"],
855
- bug: ["error", "issue", "defect", "problem"],
856
- error: ["exception", "failure", "issue"],
857
- fix: ["resolve", "patch", "repair", "correct"],
858
- deploy: ["deployment", "release", "ship", "publish"],
859
- test: ["testing", "spec", "assertion", "verify"],
860
- config: ["configuration", "settings", "setup"],
861
- db: ["database", "storage", "persistence"],
862
- database: ["storage", "persistence", "data store"],
863
- api: ["endpoint", "route", "service"],
864
- ui: ["frontend", "component", "view"],
865
- perf: ["performance", "speed", "latency"],
866
- performance: ["speed", "latency", "optimization"]
867
- };
868
- manifestCache = new Map;
869
- sessionAssemblyMap = new Map;
870
- });
871
-
872
17
  // src/prompt-builder.ts
873
18
  var exports_prompt_builder = {};
874
19
  __export(exports_prompt_builder, {
20
+ proposePromptVariant: () => proposePromptVariant,
875
21
  inferCategoryFromLabels: () => inferCategoryFromLabels,
876
22
  getRoleFraming: () => getRoleFraming,
877
23
  getAvailableVariants: () => getAvailableVariants,
878
24
  getAvailableCategories: () => getAvailableCategories,
879
- generatePrompt: () => generatePrompt
25
+ generatePrompt: () => generatePrompt,
26
+ computeContentHash: () => computeContentHash,
27
+ PROMPT_TEMPLATE_VERSION: () => PROMPT_TEMPLATE_VERSION
880
28
  });
29
+ import { createHash, randomUUID } from "node:crypto";
30
+ function computeContentHash(prompt) {
31
+ return createHash("sha256").update(prompt).digest("hex");
32
+ }
881
33
  function inferCategoryFromLabels(labels) {
882
34
  for (const label of labels) {
883
35
  const normalizedName = label.name.toLowerCase().trim();
@@ -895,7 +47,7 @@ function inferCategoryFromLabels(labels) {
895
47
  function getRoleFraming(category) {
896
48
  return DEFAULT_ROLE_FRAMINGS[category];
897
49
  }
898
- function estimateTokens2(text) {
50
+ function estimateTokens(text) {
899
51
  return Math.ceil(text.length / 4);
900
52
  }
901
53
  function formatSubtasks(subtasks) {
@@ -1058,8 +210,11 @@ ${customConstraints}`);
1058
210
  linkedCardCount: links.length,
1059
211
  memoryCount
1060
212
  },
1061
- tokenEstimate: estimateTokens2(prompt),
1062
- ...assemblyId && { assemblyId }
213
+ tokenEstimate: estimateTokens(prompt),
214
+ ...assemblyId && { assemblyId },
215
+ promptId: randomUUID(),
216
+ contentHash: computeContentHash(prompt),
217
+ version: PROMPT_TEMPLATE_VERSION
1063
218
  };
1064
219
  }
1065
220
  function extractSessionInsights(assembledContext) {
@@ -1141,7 +296,26 @@ function getAvailableCategories() {
1141
296
  function getAvailableVariants() {
1142
297
  return ["analysis", "draft", "execute"];
1143
298
  }
1144
- var LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS;
299
+ async function proposePromptVariant(contentHash, fetchCohort) {
300
+ if (!contentHash)
301
+ return null;
302
+ const cohort = await fetchCohort(contentHash);
303
+ if (!cohort || cohort.length < VARIANT_MIN_COHORT)
304
+ return null;
305
+ const completed = cohort.filter((r) => r.status === "completed" && (r.progressPercent ?? 0) >= 100 && !r.hadBlockers).length;
306
+ const completionRate = completed / cohort.length;
307
+ if (completionRate >= VARIANT_COMPLETION_THRESHOLD)
308
+ return null;
309
+ const blockerRate = cohort.filter((r) => r.hadBlockers).length / cohort.length;
310
+ const framingHint = blockerRate >= 0.4 ? "Cohort hits frequent blockers — try a more diagnostic framing (require root-cause + repro before any fix)." : "Cohort frequently stalls without finishing — try a more action-forcing framing (smaller subtasks, explicit DoD checklist).";
311
+ return {
312
+ contentHash,
313
+ cohortSize: cohort.length,
314
+ completionRate,
315
+ framingHint
316
+ };
317
+ }
318
+ var PROMPT_TEMPLATE_VERSION = 1, LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS, VARIANT_MIN_COHORT = 10, VARIANT_COMPLETION_THRESHOLD = 0.4;
1145
319
  var init_prompt_builder = __esm(() => {
1146
320
  LABEL_CATEGORY_MAP = {
1147
321
  bug: "bug",
@@ -1500,6 +674,26 @@ function getMemoryDir() {
1500
674
  return join(homedir(), ".harmony", "memory");
1501
675
  }
1502
676
 
677
+ // ../harmony-shared/dist/cardLinks.js
678
+ var LINK_TYPE_INVERSES = {
679
+ relates_to: "relates_to",
680
+ blocks: "is_blocked_by",
681
+ duplicates: "is_duplicated_by",
682
+ is_part_of: "has_part"
683
+ };
684
+ function getDisplayLinkType(linkType, direction) {
685
+ if (direction === "outgoing")
686
+ return linkType;
687
+ return LINK_TYPE_INVERSES[linkType];
688
+ }
689
+ // ../harmony-shared/dist/constants.js
690
+ var TIMINGS = {
691
+ SEARCH_DEBOUNCE: 300,
692
+ AUTOSAVE_DEBOUNCE: 1000,
693
+ TOAST_DURATION: 3000,
694
+ QUERY_STALE_TIME: 1000 * 60 * 5,
695
+ QUERY_GC_TIME: 1000 * 60 * 60 * 24
696
+ };
1503
697
  // src/api-client.ts
1504
698
  var RETRY_CONFIG = {
1505
699
  maxRetries: 3,
@@ -1848,22 +1042,6 @@ class HarmonyApiClient {
1848
1042
  const query = params.toString() ? `?${params.toString()}` : "";
1849
1043
  return this.request("GET", `/cards/${cardId}/agent-context${query}`);
1850
1044
  }
1851
- async getAgentProfile(workspaceId, agentIdentifier) {
1852
- const params = new URLSearchParams({
1853
- workspace_id: workspaceId,
1854
- agent_identifier: agentIdentifier
1855
- });
1856
- return this.request("GET", `/agent-profiles?${params.toString()}`);
1857
- }
1858
- async listAgentProfiles(workspaceId) {
1859
- const params = new URLSearchParams({ workspace_id: workspaceId });
1860
- return this.request("GET", `/agent-profiles?${params.toString()}`);
1861
- }
1862
- async refreshAgentProfiles(workspaceId) {
1863
- return this.request("POST", "/agent-profiles/refresh", {
1864
- workspace_id: workspaceId
1865
- });
1866
- }
1867
1045
  async createMemoryEntity(data) {
1868
1046
  return this.request("POST", "/memory/entities", data);
1869
1047
  }
@@ -1888,6 +1066,8 @@ class HarmonyApiClient {
1888
1066
  params.set("limit", String(options.limit));
1889
1067
  if (options.offset !== undefined)
1890
1068
  params.set("offset", String(options.offset));
1069
+ if (options.include_superseded)
1070
+ params.set("include_superseded", "true");
1891
1071
  return this.request("GET", `/memory/entities?${params.toString()}`);
1892
1072
  }
1893
1073
  async getMemoryEntity(entityId) {
@@ -2053,10 +1233,35 @@ class HarmonyApiClient {
2053
1233
  async generateApiKey(name) {
2054
1234
  return this.request("POST", "/api-keys", { name });
2055
1235
  }
1236
+ async recordPromptHistory(data) {
1237
+ return this.request("POST", "/prompt-history", data);
1238
+ }
1239
+ async recordPromptHistoryFeedback(sessionId, outcome) {
1240
+ return this.request("POST", "/prompt-history/feedback", {
1241
+ sessionId,
1242
+ outcome
1243
+ });
1244
+ }
1245
+ async getPromptHistoryCohort(contentHash) {
1246
+ const params = new URLSearchParams({ content_hash: contentHash });
1247
+ return this.request("GET", `/prompt-history/cohort?${params.toString()}`);
1248
+ }
2056
1249
  async generateCardPrompt(options) {
2057
- const { assembleContext: assembleContext2, cacheManifest: cacheManifest2, generatePrompt: generatePrompt2 } = await loadPromptModules();
1250
+ const { generatePrompt: generatePrompt2 } = await loadPromptModules();
2058
1251
  const cardResult = await this.getCard(options.cardId);
2059
1252
  const cardData = cardResult.card;
1253
+ try {
1254
+ const linksResult = await this.getCardLinks(options.cardId);
1255
+ const rawLinks = linksResult.links || [];
1256
+ cardData.links = rawLinks.filter((l) => l.target_card).map((l) => ({
1257
+ target_card: l.target_card,
1258
+ direction: l.direction,
1259
+ display_type: getDisplayLinkType(l.link_type, l.direction)
1260
+ }));
1261
+ } catch (err) {
1262
+ const msg = err instanceof Error ? err.message : String(err);
1263
+ console.debug(`[generateCardPrompt] getCardLinks failed: ${msg}`);
1264
+ }
2060
1265
  let columnData = null;
2061
1266
  const projectIdForBoard = options.projectId || cardData.project_id;
2062
1267
  if (projectIdForBoard) {
@@ -2069,51 +1274,29 @@ class HarmonyApiClient {
2069
1274
  } catch {}
2070
1275
  }
2071
1276
  const variant = options.variant || "execute";
2072
- let assembledContextStr;
2073
- let assemblyId;
1277
+ const assembledContextStr = undefined;
1278
+ const assemblyId = undefined;
2074
1279
  let memories;
2075
1280
  try {
2076
1281
  if (options.workspaceId && cardData.title) {
2077
- const cardLabels = (cardData.labels || []).map((l) => l.name);
2078
- const taskContext = [cardData.title, cardData.description || ""].filter(Boolean).join(" ");
2079
- const assembled = await assembleContext2({
2080
- workspaceId: options.workspaceId,
2081
- projectId: options.projectId,
2082
- taskContext,
2083
- cardLabels,
2084
- cardId: cardData.id,
2085
- client: this
1282
+ const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
1283
+ project_id: options.projectId,
1284
+ limit: 5
2086
1285
  });
2087
- if (assembled.context) {
2088
- assembledContextStr = assembled.context;
2089
- assemblyId = assembled.manifest.assemblyId;
2090
- cacheManifest2(assembled.manifest);
1286
+ if (memoryResult.entities?.length > 0) {
1287
+ memories = memoryResult.entities.map((e) => ({
1288
+ id: e.id,
1289
+ type: e.type,
1290
+ title: e.title,
1291
+ content: e.content,
1292
+ confidence: e.confidence,
1293
+ tags: e.tags || []
1294
+ }));
2091
1295
  }
2092
1296
  }
2093
1297
  } catch (err) {
2094
1298
  const msg = err instanceof Error ? err.message : String(err);
2095
- console.debug(`[generateCardPrompt] Context assembly failed: ${msg}`);
2096
- try {
2097
- if (options.workspaceId && cardData.title) {
2098
- const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
2099
- project_id: options.projectId,
2100
- limit: 5
2101
- });
2102
- if (memoryResult.entities?.length > 0) {
2103
- memories = memoryResult.entities.map((e) => ({
2104
- id: e.id,
2105
- type: e.type,
2106
- title: e.title,
2107
- content: e.content,
2108
- confidence: e.confidence,
2109
- tags: e.tags || []
2110
- }));
2111
- }
2112
- }
2113
- } catch (fallbackErr) {
2114
- const fallbackMsg = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
2115
- console.debug(`[generateCardPrompt] Memory fallback also failed: ${fallbackMsg}`);
2116
- }
1299
+ console.debug(`[generateCardPrompt] Memory search failed: ${msg}`);
2117
1300
  }
2118
1301
  const result = generatePrompt2({
2119
1302
  card: cardData,
@@ -2125,6 +1308,25 @@ class HarmonyApiClient {
2125
1308
  assembledContext: assembledContextStr,
2126
1309
  assemblyId
2127
1310
  });
1311
+ try {
1312
+ await this.recordPromptHistory({
1313
+ cardId: cardData.id,
1314
+ generatedPrompt: result.prompt,
1315
+ variant,
1316
+ contextIncluded: {
1317
+ assemblyId: result.assemblyId ?? null,
1318
+ tokenEstimate: result.tokenEstimate,
1319
+ contextSummary: result.contextSummary
1320
+ },
1321
+ sessionId: options.sessionId ?? null,
1322
+ contentHash: result.contentHash,
1323
+ templateVersion: result.version,
1324
+ confidence: 0.5
1325
+ });
1326
+ } catch (err) {
1327
+ const msg = err instanceof Error ? err.message : String(err);
1328
+ console.debug(`[generateCardPrompt] prompt_history persistence failed: ${msg}`);
1329
+ }
2128
1330
  return {
2129
1331
  ...result,
2130
1332
  cardId: cardData.id,
@@ -2136,27 +1338,22 @@ class HarmonyApiClient {
2136
1338
  var _promptModules = null;
2137
1339
  async function loadPromptModules() {
2138
1340
  if (!_promptModules) {
2139
- const [ca, pb] = await Promise.all([
2140
- Promise.resolve().then(() => (init_context_assembly(), exports_context_assembly)),
2141
- Promise.resolve().then(() => (init_prompt_builder(), exports_prompt_builder))
2142
- ]);
1341
+ const pb = await Promise.resolve().then(() => (init_prompt_builder(), exports_prompt_builder));
2143
1342
  _promptModules = {
2144
- assembleContext: ca.assembleContext,
2145
- cacheManifest: ca.cacheManifest,
2146
1343
  generatePrompt: pb.generatePrompt
2147
1344
  };
2148
1345
  }
2149
1346
  return _promptModules;
2150
1347
  }
2151
- var client2 = null;
1348
+ var client = null;
2152
1349
  function getClient() {
2153
- if (!client2) {
2154
- client2 = new HarmonyApiClient;
1350
+ if (!client) {
1351
+ client = new HarmonyApiClient;
2155
1352
  }
2156
- return client2;
1353
+ return client;
2157
1354
  }
2158
1355
  function resetClient() {
2159
- client2 = null;
1356
+ client = null;
2160
1357
  }
2161
1358
  export {
2162
1359
  signupUser,