@gethmy/mcp 2.3.1 → 2.3.3

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 (34) hide show
  1. package/dist/lib/api-client.js +2099 -648
  2. package/dist/lib/config.js +217 -201
  3. package/package.json +9 -5
  4. package/src/memory-cleanup.ts +2 -4
  5. package/dist/lib/__tests__/active-learning.test.js +0 -386
  6. package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
  7. package/dist/lib/__tests__/auto-session.test.js +0 -661
  8. package/dist/lib/__tests__/context-assembly.test.js +0 -362
  9. package/dist/lib/__tests__/graph-expansion.test.js +0 -150
  10. package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
  11. package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
  12. package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
  13. package/dist/lib/__tests__/pattern-detection.test.js +0 -295
  14. package/dist/lib/__tests__/prompt-builder.test.js +0 -418
  15. package/dist/lib/active-learning.js +0 -822
  16. package/dist/lib/auto-session.js +0 -214
  17. package/dist/lib/cli.js +0 -138
  18. package/dist/lib/consolidation.js +0 -303
  19. package/dist/lib/context-assembly.js +0 -884
  20. package/dist/lib/graph-expansion.js +0 -163
  21. package/dist/lib/http.js +0 -175
  22. package/dist/lib/index.js +0 -7
  23. package/dist/lib/lifecycle-maintenance.js +0 -88
  24. package/dist/lib/memory-cleanup.js +0 -455
  25. package/dist/lib/onboard.js +0 -36
  26. package/dist/lib/prompt-builder.js +0 -488
  27. package/dist/lib/remote.js +0 -166
  28. package/dist/lib/server.js +0 -3365
  29. package/dist/lib/skills.js +0 -593
  30. package/dist/lib/tui/agents.js +0 -116
  31. package/dist/lib/tui/docs.js +0 -744
  32. package/dist/lib/tui/setup.js +0 -934
  33. package/dist/lib/tui/theme.js +0 -95
  34. package/dist/lib/tui/writer.js +0 -200
@@ -1,672 +1,2123 @@
1
- import { getApiKey, getApiUrl } from "./config.js";
2
- // Retry configuration
3
- const RETRY_CONFIG = {
4
- maxRetries: 3,
5
- baseDelayMs: 1000,
6
- maxDelayMs: 10000,
7
- retryableStatusCodes: [408, 429, 500, 502, 503, 504],
8
- };
9
- // Check if error is retryable
10
- function isRetryableError(error, status) {
11
- if (status && RETRY_CONFIG.retryableStatusCodes.includes(status)) {
12
- return true;
13
- }
14
- if (error instanceof TypeError)
15
- return true; // Network errors
16
- const msg = error instanceof Error ? error.message : String(error);
17
- return (msg.includes("ECONNRESET") ||
18
- msg.includes("ETIMEDOUT") ||
19
- msg.includes("fetch failed"));
20
- }
21
- // Exponential backoff with jitter
22
- function getRetryDelay(attempt) {
23
- const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
24
- return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
25
- }
26
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
27
- // Semaphore for concurrency control
28
- class Semaphore {
29
- permits;
30
- queue = [];
31
- constructor(permits) {
32
- this.permits = permits;
33
- }
34
- async acquire() {
35
- if (this.permits > 0) {
36
- this.permits--;
37
- return;
38
- }
39
- return new Promise((resolve) => this.queue.push(resolve));
40
- }
41
- release() {
42
- const next = this.queue.shift();
43
- if (next)
44
- next();
45
- else
46
- this.permits++;
47
- }
48
- }
49
- // Limit concurrent requests to 3
50
- const requestSemaphore = new Semaphore(3);
51
- // ============ PRE-AUTH FREE FUNCTIONS ============
52
- // These bypass the singleton + semaphore for unauthenticated/JWT-based requests
53
- export async function signupUser(apiUrl, data) {
54
- const url = `${apiUrl}/v1/auth/signup`;
55
- const response = await fetch(url, {
56
- method: "POST",
57
- headers: { "Content-Type": "application/json" },
58
- body: JSON.stringify(data),
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
59
9
  });
60
- const result = await response.json();
61
- if (!response.ok) {
62
- throw new Error(result.error || `Signup failed: ${response.status}`);
63
- }
64
- return result;
65
- }
66
- export async function requestWithBearer(apiUrl, bearerToken, method, path, body) {
67
- const url = `${apiUrl}/v1${path}`;
68
- const response = await fetch(url, {
69
- method,
70
- headers: {
71
- "Content-Type": "application/json",
72
- Authorization: `Bearer ${bearerToken}`,
73
- },
74
- body: body ? JSON.stringify(body) : undefined,
75
- });
76
- const result = await response.json();
77
- if (!response.ok) {
78
- throw new Error(result.error || `API error: ${response.status}`);
79
- }
80
- return result;
81
- }
82
- export class HarmonyApiClient {
83
- apiKey;
84
- apiUrl;
85
- constructor(options) {
86
- this.apiKey = options?.apiKey ?? getApiKey();
87
- this.apiUrl = options?.apiUrl ?? getApiUrl();
88
- }
89
- getApiUrl() {
90
- return this.apiUrl;
91
- }
92
- async request(method, path, body, options) {
93
- await requestSemaphore.acquire();
94
- try {
95
- return await this.requestWithRetry(method, path, body, options);
10
+ };
11
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
+
13
+ // ../memory/dist/schema.js
14
+ var init_schema = () => {};
15
+
16
+ // ../memory/dist/constraints.js
17
+ var init_constraints = __esm(() => {
18
+ init_schema();
19
+ });
20
+ // ../memory/dist/client.js
21
+ var init_client = __esm(() => {
22
+ init_constraints();
23
+ });
24
+
25
+ // ../memory/dist/graph-walk.js
26
+ async function discoverRelatedContext(client, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
27
+ const visited = new Set;
28
+ const collectedEntities = [];
29
+ const collectedRelations = [];
30
+ let truncated = false;
31
+ const queue = startIds.map((id) => [id, 0]);
32
+ for (const id of startIds) {
33
+ visited.add(id);
34
+ }
35
+ while (queue.length > 0) {
36
+ const [entityId, depth] = queue.shift();
37
+ if (collectedEntities.length >= maxEntities) {
38
+ truncated = true;
39
+ break;
40
+ }
41
+ if (depth > maxDepth)
42
+ continue;
43
+ try {
44
+ const entityResult = await client.getMemoryEntity(entityId);
45
+ const entity = entityResult.entity;
46
+ if (entity) {
47
+ collectedEntities.push({
48
+ id: entity.id,
49
+ type: entity.type,
50
+ title: entity.title,
51
+ confidence: entity.confidence ?? 1,
52
+ memory_tier: entity.memory_tier || "reference"
53
+ });
54
+ }
55
+ if (depth >= maxDepth)
56
+ continue;
57
+ const related = await client.getRelatedEntities(entityId);
58
+ for (const raw of related.outgoing || []) {
59
+ const rel = raw;
60
+ const relConfidence = rel.confidence ?? 1;
61
+ if (relConfidence < minConfidence)
62
+ continue;
63
+ const target = rel.target;
64
+ const targetId = target?.id ?? rel.target_id;
65
+ if (targetId && !visited.has(targetId)) {
66
+ visited.add(targetId);
67
+ queue.push([targetId, depth + 1]);
68
+ collectedRelations.push({
69
+ id: rel.id,
70
+ source_id: entityId,
71
+ target_id: targetId,
72
+ relation_type: rel.relation_type,
73
+ confidence: relConfidence
74
+ });
96
75
  }
97
- finally {
98
- requestSemaphore.release();
76
+ }
77
+ for (const raw of related.incoming || []) {
78
+ const rel = raw;
79
+ const relConfidence = rel.confidence ?? 1;
80
+ if (relConfidence < minConfidence)
81
+ continue;
82
+ const source = rel.source;
83
+ const sourceId = source?.id ?? rel.source_id;
84
+ if (sourceId && !visited.has(sourceId)) {
85
+ visited.add(sourceId);
86
+ queue.push([sourceId, depth + 1]);
87
+ collectedRelations.push({
88
+ id: rel.id,
89
+ source_id: sourceId,
90
+ target_id: entityId,
91
+ relation_type: rel.relation_type,
92
+ confidence: relConfidence
93
+ });
99
94
  }
100
- }
101
- async requestRaw(method, path, body, options) {
102
- await requestSemaphore.acquire();
103
- try {
104
- return await this.requestRawWithRetry(method, path, body, options);
95
+ }
96
+ } catch {}
97
+ }
98
+ return {
99
+ entities: collectedEntities,
100
+ relations: collectedRelations,
101
+ depth: maxDepth,
102
+ truncated
103
+ };
104
+ }
105
+
106
+ // ../memory/dist/lifecycle.js
107
+ function checkPromotion(currentTier, accessCount, confidence, createdAt) {
108
+ const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
109
+ const base = {
110
+ eligible: false,
111
+ targetTier: null,
112
+ reason: null,
113
+ currentTier,
114
+ accessCount,
115
+ confidence,
116
+ ageDays
117
+ };
118
+ if (currentTier === "draft") {
119
+ const rules = PROMOTION_RULES.draftToEpisode;
120
+ if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
121
+ return {
122
+ ...base,
123
+ eligible: true,
124
+ targetTier: "episode",
125
+ reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
126
+ };
127
+ }
128
+ }
129
+ if (currentTier === "episode") {
130
+ const rules = PROMOTION_RULES.episodeToReference;
131
+ if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
132
+ return {
133
+ ...base,
134
+ eligible: true,
135
+ targetTier: "reference",
136
+ reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
137
+ };
138
+ }
139
+ }
140
+ return base;
141
+ }
142
+ var PROMOTION_RULES;
143
+ var init_lifecycle = __esm(() => {
144
+ PROMOTION_RULES = {
145
+ draftToEpisode: {
146
+ minAccessCount: 5,
147
+ minConfidence: 0.8,
148
+ minAgeDays: 1
149
+ },
150
+ episodeToReference: {
151
+ minAccessCount: 10,
152
+ minConfidence: 0.9,
153
+ minAgeDays: 7
154
+ }
155
+ };
156
+ });
157
+ // ../memory/dist/sync.js
158
+ var init_sync = () => {};
159
+
160
+ // ../memory/dist/index.js
161
+ var init_dist = __esm(() => {
162
+ init_client();
163
+ init_constraints();
164
+ init_lifecycle();
165
+ init_schema();
166
+ init_sync();
167
+ });
168
+
169
+ // src/context-assembly.ts
170
+ var exports_context_assembly = {};
171
+ __export(exports_context_assembly, {
172
+ trackSessionAssembly: () => trackSessionAssembly,
173
+ recordContextFeedback: () => recordContextFeedback,
174
+ mapToContextEntity: () => mapToContextEntity,
175
+ getSessionAssemblyId: () => getSessionAssemblyId,
176
+ getCachedManifest: () => getCachedManifest,
177
+ expandQuery: () => expandQuery,
178
+ computeRelevanceScore: () => computeRelevanceScore,
179
+ cacheManifest: () => cacheManifest,
180
+ assembleContext: () => assembleContext
181
+ });
182
+ function estimateTokens(text) {
183
+ return Math.ceil(text.length / 4);
184
+ }
185
+ function passesQualityGate(entity) {
186
+ const content = entity.content.trim();
187
+ if (content.length < 50)
188
+ return false;
189
+ const normalizedTitle = entity.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
190
+ const normalizedContent = content.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
191
+ if (normalizedContent.length < normalizedTitle.length * 1.5) {
192
+ return false;
193
+ }
194
+ if (entity.type === "pattern" && /recurring .+ \(\d+ instances\)/i.test(entity.title)) {
195
+ const lines = content.split(`
196
+ `).filter((l) => l.trim().length > 0);
197
+ const bulletLines = lines.filter((l) => l.trim().startsWith("- "));
198
+ if (bulletLines.length > lines.length * 0.6)
199
+ return false;
200
+ }
201
+ if (entity.type === "procedure") {
202
+ const stepCount = (content.match(/^\d+\.\s/gm) || []).length;
203
+ if (stepCount < 3)
204
+ return false;
205
+ }
206
+ return true;
207
+ }
208
+ function generateAssemblyId() {
209
+ return `ctx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
210
+ }
211
+ function truncateContent(content, maxTokens) {
212
+ const currentTokens = estimateTokens(content);
213
+ if (currentTokens <= maxTokens) {
214
+ return { text: content, truncated: false };
215
+ }
216
+ const paragraphs = content.split(/\n\n+/);
217
+ let result = paragraphs[0];
218
+ for (let i = 1;i < paragraphs.length; i++) {
219
+ const lines = paragraphs[i].split(`
220
+ `).filter((l) => l.startsWith("- ") || l.startsWith("* "));
221
+ if (lines.length > 0) {
222
+ const bulletSection = lines.join(`
223
+ `);
224
+ if (estimateTokens(result + `
225
+
226
+ ` + bulletSection) <= maxTokens) {
227
+ result += `
228
+
229
+ ` + bulletSection;
230
+ }
231
+ }
232
+ }
233
+ if (estimateTokens(result) > maxTokens) {
234
+ const maxChars = maxTokens * 4;
235
+ result = result.slice(0, maxChars - 3) + "...";
236
+ }
237
+ return { text: result, truncated: true };
238
+ }
239
+ function escapeRegex(str) {
240
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
241
+ }
242
+ function expandQuery(taskContext) {
243
+ const queries = [taskContext];
244
+ const lowerQueries = [taskContext.toLowerCase()];
245
+ const words = taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
246
+ const expandableWords = words.filter((w) => QUERY_SYNONYMS[w]);
247
+ for (const word of expandableWords) {
248
+ const synonyms = QUERY_SYNONYMS[word];
249
+ if (!synonyms)
250
+ continue;
251
+ const variation = taskContext.replace(new RegExp(`\\b${escapeRegex(word)}\\b`, "gi"), synonyms[0]);
252
+ const lowerVariation = variation.toLowerCase();
253
+ if (lowerVariation !== taskContext.toLowerCase() && !lowerQueries.includes(lowerVariation)) {
254
+ queries.push(variation);
255
+ lowerQueries.push(lowerVariation);
256
+ }
257
+ if (queries.length >= MAX_QUERY_VARIATIONS)
258
+ break;
259
+ }
260
+ if (words.length >= 3) {
261
+ const keyPhrases = words.filter((w) => ![
262
+ "the",
263
+ "and",
264
+ "for",
265
+ "with",
266
+ "this",
267
+ "that",
268
+ "from",
269
+ "into"
270
+ ].includes(w)).slice(0, 4).join(" ");
271
+ if (!lowerQueries.includes(keyPhrases)) {
272
+ queries.push(keyPhrases);
273
+ }
274
+ }
275
+ return queries.slice(0, MAX_QUERY_VARIATIONS);
276
+ }
277
+ function computeRelevanceScore(entity, taskContext, cardLabels, graphRelations) {
278
+ const reasons = [];
279
+ let score = 0;
280
+ const hasRrfScore = entity.rrf_score !== undefined && entity.rrf_score > 0;
281
+ if (hasRrfScore) {
282
+ const normalizedRrf = Math.min(entity.rrf_score / 0.04, 1);
283
+ const rrfContribution = normalizedRrf * 0.3;
284
+ score += rrfContribution;
285
+ reasons.push(`hybrid_search(rrf=${entity.rrf_score.toFixed(4)})`);
286
+ }
287
+ const textMatchWeight = hasRrfScore ? 0.15 : 0.4;
288
+ const taskWords = new Set(taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
289
+ const entityWords = new Set(`${entity.title} ${entity.content}`.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
290
+ const overlap = [...taskWords].filter((w) => entityWords.has(w));
291
+ if (overlap.length > 0) {
292
+ const textScore = Math.min(overlap.length / Math.max(taskWords.size, 1), 1) * textMatchWeight;
293
+ score += textScore;
294
+ reasons.push(`text_match(${overlap.length} words)`);
295
+ }
296
+ if (cardLabels.length > 0 && entity.tags.length > 0) {
297
+ const labelSet = new Set(cardLabels.map((l) => l.toLowerCase()));
298
+ const tagOverlap = entity.tags.filter((t) => labelSet.has(t.toLowerCase()));
299
+ if (tagOverlap.length > 0) {
300
+ const tagScore = tagOverlap.length / cardLabels.length * 0.3;
301
+ score += tagScore;
302
+ reasons.push(`tag_match(${tagOverlap.join(",")})`);
303
+ }
304
+ }
305
+ score += entity.confidence * 0.15;
306
+ if (entity.confidence >= 0.9) {
307
+ reasons.push("high_confidence");
308
+ }
309
+ if (entity.last_accessed_at) {
310
+ const daysSinceAccess = (Date.now() - new Date(entity.last_accessed_at).getTime()) / (1000 * 60 * 60 * 24);
311
+ const halfLife = { draft: 7, episode: 30, reference: 180 }[entity.memory_tier];
312
+ const recencyScore = 0.5 ** (daysSinceAccess / halfLife) * 0.1;
313
+ score += recencyScore;
314
+ if (daysSinceAccess < 7)
315
+ reasons.push("recently_accessed");
316
+ }
317
+ if (entity.access_count > 0) {
318
+ const freqScore = Math.log10(entity.access_count + 1) * 0.05;
319
+ score += Math.min(freqScore, 0.1);
320
+ if (entity.access_count >= 5)
321
+ reasons.push(`frequently_used(${entity.access_count})`);
322
+ }
323
+ const usefulnessScore = entity.metadata?.usefulness_score ?? 0;
324
+ if (usefulnessScore >= 3) {
325
+ const usefulnessBoost = Math.min(usefulnessScore / 20, 0.15);
326
+ score += usefulnessBoost;
327
+ reasons.push(`useful(${usefulnessScore})`);
328
+ } else if (usefulnessScore === 0 && entity.access_count >= 5) {
329
+ score -= 0.02;
330
+ reasons.push("low_usefulness");
331
+ }
332
+ if (entity.type === "procedure") {
333
+ score += 0.1;
334
+ reasons.push("procedure_boost");
335
+ }
336
+ if (graphRelations && graphRelations.length > 0) {
337
+ const entityRelations = graphRelations.filter((r) => r.source_id === entity.id || r.target_id === entity.id);
338
+ if (entityRelations.length > 0) {
339
+ let bestBonus = 0;
340
+ let bestRelType = "";
341
+ for (const rel of entityRelations) {
342
+ const bonus = RELATION_BONUSES[rel.relation_type] ?? 0.1;
343
+ if (bonus > bestBonus) {
344
+ bestBonus = bonus;
345
+ bestRelType = rel.relation_type;
105
346
  }
106
- finally {
107
- requestSemaphore.release();
347
+ }
348
+ score += bestBonus;
349
+ reasons.push(`graph_walk(${bestRelType})`);
350
+ }
351
+ }
352
+ score = Math.max(0, Math.min(score, 1));
353
+ const tierWeight = TIER_WEIGHTS[entity.memory_tier];
354
+ score *= tierWeight;
355
+ return { score, reasons };
356
+ }
357
+ async function assembleContext(options) {
358
+ const {
359
+ workspaceId,
360
+ projectId,
361
+ taskContext,
362
+ cardLabels = [],
363
+ tokenBudget = DEFAULT_TOKEN_BUDGET,
364
+ client: client2,
365
+ graphWalkEnabled = true,
366
+ queryExpansionEnabled = true,
367
+ enableLlmReranking = false,
368
+ rerankFn
369
+ } = options;
370
+ const assemblyId = generateAssemblyId();
371
+ const manifest = {
372
+ assemblyId,
373
+ timestamp: new Date().toISOString(),
374
+ included: [],
375
+ excluded: [],
376
+ budgetUsed: 0,
377
+ budgetTotal: tokenBudget,
378
+ tierBreakdown: {
379
+ draft: { count: 0, tokens: 0 },
380
+ episode: { count: 0, tokens: 0 },
381
+ reference: { count: 0, tokens: 0 }
382
+ }
383
+ };
384
+ const candidates = [];
385
+ const queries = queryExpansionEnabled ? expandQuery(taskContext) : [taskContext];
386
+ const searchResults = await Promise.allSettled(queries.map((query) => client2.searchMemoryEntities(workspaceId, query, {
387
+ project_id: projectId,
388
+ limit: 30
389
+ })));
390
+ const candidateIds = new Set;
391
+ for (const result of searchResults) {
392
+ if (result.status !== "fulfilled")
393
+ continue;
394
+ if (result.value.entities?.length > 0) {
395
+ for (const raw of result.value.entities) {
396
+ const entity = mapToContextEntity(raw);
397
+ if (!candidateIds.has(entity.id)) {
398
+ candidateIds.add(entity.id);
399
+ candidates.push(entity);
108
400
  }
109
- }
110
- async requestWithRetry(method, path, body, options) {
111
- const url = `${this.apiUrl}/v1${path}`;
112
- let lastError = null;
113
- const contentType = options?.contentType || "application/json";
114
- const accept = options?.accept || "application/json";
115
- for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
116
- try {
117
- const response = await fetch(url, {
118
- method,
119
- headers: {
120
- "Content-Type": contentType,
121
- Accept: accept,
122
- "X-API-Key": this.apiKey,
123
- },
124
- body: options?.rawBody ?? (body ? JSON.stringify(body) : undefined),
125
- });
126
- const data = await response.json();
127
- if (!response.ok) {
128
- const errorMsg = data.error || `API error: ${response.status}`;
129
- if (!isRetryableError(null, response.status)) {
130
- throw new Error(errorMsg);
131
- }
132
- lastError = new Error(errorMsg);
133
- if (attempt < RETRY_CONFIG.maxRetries) {
134
- await sleep(getRetryDelay(attempt));
135
- continue;
136
- }
137
- throw lastError;
138
- }
139
- return data;
140
- }
141
- catch (error) {
142
- lastError = error instanceof Error ? error : new Error(String(error));
143
- if (!isRetryableError(error))
144
- throw lastError;
145
- if (attempt < RETRY_CONFIG.maxRetries) {
146
- await sleep(getRetryDelay(attempt));
147
- }
148
- }
401
+ }
402
+ }
403
+ }
404
+ if (candidates.length < 10 && projectId) {
405
+ try {
406
+ const listResult = await client2.listMemoryEntities({
407
+ workspace_id: workspaceId,
408
+ project_id: projectId,
409
+ limit: 30
410
+ });
411
+ if (listResult.entities?.length > 0) {
412
+ for (const raw of listResult.entities) {
413
+ const entity = mapToContextEntity(raw);
414
+ if (!candidateIds.has(entity.id)) {
415
+ candidateIds.add(entity.id);
416
+ candidates.push(entity);
417
+ }
149
418
  }
150
- throw lastError || new Error("Request failed after retries");
151
- }
152
- async requestRawWithRetry(method, path, body, options) {
153
- const url = `${this.apiUrl}/v1${path}`;
154
- let lastError = null;
155
- const contentType = options?.contentType || "application/json";
156
- const accept = options?.accept || "text/markdown";
157
- for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
158
- try {
159
- const response = await fetch(url, {
160
- method,
161
- headers: {
162
- "Content-Type": contentType,
163
- Accept: accept,
164
- "X-API-Key": this.apiKey,
165
- },
166
- body: options?.rawBody ?? (body ? JSON.stringify(body) : undefined),
167
- });
168
- if (!response.ok) {
169
- const text = await response.text();
170
- let errorMsg;
171
- try {
172
- errorMsg =
173
- JSON.parse(text).error || `API error: ${response.status}`;
174
- }
175
- catch {
176
- errorMsg = text || `API error: ${response.status}`;
177
- }
178
- if (!isRetryableError(null, response.status)) {
179
- throw new Error(errorMsg);
180
- }
181
- lastError = new Error(errorMsg);
182
- if (attempt < RETRY_CONFIG.maxRetries) {
183
- await sleep(getRetryDelay(attempt));
184
- continue;
185
- }
186
- throw lastError;
187
- }
188
- return await response.text();
189
- }
190
- catch (error) {
191
- lastError = error instanceof Error ? error : new Error(String(error));
192
- if (!isRetryableError(error))
193
- throw lastError;
194
- if (attempt < RETRY_CONFIG.maxRetries) {
195
- await sleep(getRetryDelay(attempt));
196
- }
197
- }
419
+ }
420
+ } catch {}
421
+ }
422
+ if (candidates.length < 20) {
423
+ try {
424
+ const wsResult = await client2.listMemoryEntities({
425
+ workspace_id: workspaceId,
426
+ scope: "workspace",
427
+ limit: 20
428
+ });
429
+ if (wsResult.entities?.length > 0) {
430
+ for (const raw of wsResult.entities) {
431
+ const entity = mapToContextEntity(raw);
432
+ if (!candidateIds.has(entity.id)) {
433
+ candidateIds.add(entity.id);
434
+ candidates.push(entity);
435
+ }
198
436
  }
199
- throw lastError || new Error("Request failed after retries");
200
- }
201
- // ============ WORKSPACE OPERATIONS ============
202
- async listWorkspaces() {
203
- return this.request("GET", "/workspaces");
204
- }
205
- async getWorkspaceMembers(workspaceId) {
206
- return this.request("GET", `/workspaces/${workspaceId}/members`);
207
- }
208
- // ============ PROJECT OPERATIONS ============
209
- async listProjects(workspaceId) {
210
- return this.request("GET", `/workspaces/${workspaceId}/projects`);
211
- }
212
- async getBoard(projectId, options) {
213
- const params = new URLSearchParams();
214
- if (options?.limit !== undefined)
215
- params.set("limit", String(options.limit));
216
- if (options?.offset !== undefined)
217
- params.set("offset", String(options.offset));
218
- if (options?.columnId)
219
- params.set("column_id", options.columnId);
220
- if (options?.summary)
221
- params.set("summary", "true");
222
- if (options?.includeArchived)
223
- params.set("include_archived", "true");
224
- if (options?.labelName)
225
- params.set("label_name", options.labelName);
226
- const query = params.toString() ? `?${params.toString()}` : "";
227
- return this.request("GET", `/board/${projectId}${query}`);
228
- }
229
- // ============ CARD OPERATIONS ============
230
- async createCard(projectId, data) {
231
- return this.request("POST", "/cards", { projectId, ...data });
232
- }
233
- async updateCard(cardId, updates) {
234
- return this.request("PATCH", `/cards/${cardId}`, updates);
235
- }
236
- async moveCard(cardId, columnId, position) {
237
- return this.request("POST", `/cards/${cardId}/move`, {
238
- columnId,
239
- position,
240
- });
241
- }
242
- async archiveCard(cardId) {
243
- return this.updateCard(cardId, { archivedAt: new Date().toISOString() });
244
- }
245
- async unarchiveCard(cardId) {
246
- return this.updateCard(cardId, { archivedAt: null });
247
- }
248
- async deleteCard(cardId) {
249
- return this.request("DELETE", `/cards/${cardId}`);
250
- }
251
- async getCard(cardId) {
252
- return this.request("GET", `/cards/${cardId}`);
253
- }
254
- async getCardByShortId(projectId, shortId) {
255
- return this.request("GET", `/projects/${projectId}/cards/${shortId}`);
256
- }
257
- async searchCards(query, options) {
258
- const params = new URLSearchParams({ q: query });
259
- if (options?.projectId) {
260
- params.set("project_id", options.projectId);
437
+ }
438
+ } catch {}
439
+ }
440
+ let graphRelations = [];
441
+ if (graphWalkEnabled && candidates.length > 0) {
442
+ try {
443
+ const seedCandidates = [...candidates].sort((a, b) => (b.rrf_score ?? 0) - (a.rrf_score ?? 0)).slice(0, GRAPH_WALK_SEED_COUNT);
444
+ const seedIds = seedCandidates.map((c) => c.id);
445
+ const walkResult = await discoverRelatedContext(client2, seedIds, GRAPH_WALK_MAX_DEPTH, GRAPH_WALK_MAX_ENTITIES, GRAPH_WALK_MIN_CONFIDENCE);
446
+ graphRelations = walkResult.relations;
447
+ const newEntityIds = walkResult.entities.filter((e) => !candidateIds.has(e.id)).map((e) => e.id);
448
+ if (newEntityIds.length > 0) {
449
+ const fetchResults = await Promise.allSettled(newEntityIds.map((id) => client2.getMemoryEntity(id)));
450
+ for (const result of fetchResults) {
451
+ if (result.status !== "fulfilled" || !result.value.entity)
452
+ continue;
453
+ const mapped = mapToContextEntity(result.value.entity);
454
+ candidateIds.add(mapped.id);
455
+ candidates.push(mapped);
261
456
  }
262
- return this.request("GET", `/search?${params.toString()}`);
263
- }
264
- async addLabelToCard(cardId, labelId) {
265
- return this.request("POST", `/cards/${cardId}/labels`, { labelId });
266
- }
267
- async removeLabelFromCard(cardId, labelId) {
268
- return this.request("DELETE", `/cards/${cardId}/labels/${labelId}`);
269
- }
270
- // ============ CARD LINK OPERATIONS ============
271
- async addLinkToCard(sourceCardId, targetCardId, linkType) {
272
- return this.request("POST", `/cards/${sourceCardId}/links`, {
273
- targetCardId,
274
- linkType,
275
- });
276
- }
277
- async removeLinkFromCard(linkId) {
278
- return this.request("DELETE", `/card-links/${linkId}`);
279
- }
280
- async getCardLinks(cardId) {
281
- return this.request("GET", `/cards/${cardId}/links`);
282
- }
283
- // ============ COLUMN OPERATIONS ============
284
- async createColumn(projectId, name) {
285
- return this.request("POST", "/columns", { projectId, name });
286
- }
287
- async updateColumn(columnId, name) {
288
- return this.request("PATCH", `/columns/${columnId}`, { name });
289
- }
290
- async deleteColumn(columnId) {
291
- return this.request("DELETE", `/columns/${columnId}`);
292
- }
293
- // ============ LABEL OPERATIONS ============
294
- async createLabel(projectId, data) {
295
- return this.request("POST", "/labels", { projectId, ...data });
296
- }
297
- // ============ SUBTASK OPERATIONS ============
298
- async createSubtask(cardId, title) {
299
- return this.request("POST", "/subtasks", { cardId, title });
300
- }
301
- async toggleSubtask(subtaskId) {
302
- return this.request("POST", `/subtasks/${subtaskId}/toggle`);
303
- }
304
- async deleteSubtask(subtaskId) {
305
- return this.request("DELETE", `/subtasks/${subtaskId}`);
306
- }
307
- // ============ AGENT CONTEXT OPERATIONS ============
308
- async startAgentSession(cardId, data) {
309
- return this.request("POST", `/cards/${cardId}/agent-context`, data);
310
- }
311
- async updateAgentProgress(cardId, data) {
312
- return this.request("POST", `/cards/${cardId}/agent-context`, data);
313
- }
314
- async endAgentSession(cardId, data) {
315
- return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
316
- }
317
- async flushActivityLog(cardId, data) {
318
- return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
319
- }
320
- async getActivityLog(cardId, sessionId) {
321
- return this.request("GET", `/cards/${cardId}/agent-activity-log?sessionId=${sessionId}`);
322
- }
323
- async getAgentSession(cardId, options) {
324
- const params = new URLSearchParams();
325
- if (options?.includeEnded)
326
- params.set("include_ended", "true");
327
- const query = params.toString() ? `?${params.toString()}` : "";
328
- return this.request("GET", `/cards/${cardId}/agent-context${query}`);
329
- }
330
- // ============ AGENT PERFORMANCE PROFILES ============
331
- async getAgentProfile(workspaceId, agentIdentifier) {
332
- const params = new URLSearchParams({
333
- workspace_id: workspaceId,
334
- agent_identifier: agentIdentifier,
457
+ }
458
+ } catch {}
459
+ }
460
+ if (candidates.length === 0) {
461
+ return {
462
+ context: "",
463
+ manifest,
464
+ memories: []
465
+ };
466
+ }
467
+ const qualityCandidates = candidates.filter((entity) => {
468
+ if (passesQualityGate(entity))
469
+ return true;
470
+ manifest.excluded.push({
471
+ entityId: entity.id,
472
+ title: entity.title,
473
+ type: entity.type,
474
+ tier: entity.memory_tier,
475
+ relevanceScore: 0,
476
+ reason: "failed_quality_gate"
477
+ });
478
+ return false;
479
+ });
480
+ if (qualityCandidates.length === 0) {
481
+ return {
482
+ context: "",
483
+ manifest,
484
+ memories: []
485
+ };
486
+ }
487
+ const scored = qualityCandidates.map((entity) => {
488
+ const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels, graphRelations.length > 0 ? graphRelations : undefined);
489
+ return { entity, score, reasons };
490
+ });
491
+ scored.sort((a, b) => b.score - a.score);
492
+ if (enableLlmReranking && rerankFn && scored.length >= RERANK_MIN_CANDIDATES) {
493
+ const topN = scored.slice(0, RERANK_TOP_N);
494
+ const scoreRange = topN[0].score - topN[topN.length - 1].score;
495
+ if (scoreRange <= RERANK_CLUSTER_THRESHOLD) {
496
+ try {
497
+ const rerankCandidates = topN.map((s) => ({
498
+ id: s.entity.id,
499
+ title: s.entity.title,
500
+ snippet: s.entity.content.slice(0, 200)
501
+ }));
502
+ const rerankedIds = await rerankFn(taskContext, rerankCandidates);
503
+ const idOrder = new Map(rerankedIds.map((id, i) => [id, i]));
504
+ topN.sort((a, b) => {
505
+ const aIdx = idOrder.get(a.entity.id) ?? 999;
506
+ const bIdx = idOrder.get(b.entity.id) ?? 999;
507
+ return aIdx - bIdx;
335
508
  });
336
- return this.request("GET", `/agent-profiles?${params.toString()}`);
337
- }
338
- async listAgentProfiles(workspaceId) {
339
- const params = new URLSearchParams({ workspace_id: workspaceId });
340
- return this.request("GET", `/agent-profiles?${params.toString()}`);
341
- }
342
- async refreshAgentProfiles(workspaceId) {
343
- return this.request("POST", "/agent-profiles/refresh", {
344
- workspace_id: workspaceId,
509
+ scored.splice(0, topN.length, ...topN);
510
+ } catch {}
511
+ }
512
+ }
513
+ const procedureBudget = Math.floor(tokenBudget * PROCEDURE_BUDGET_FRACTION);
514
+ const remainingBudget = tokenBudget - procedureBudget;
515
+ const tierBudgets = {
516
+ reference: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.reference),
517
+ episode: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.episode),
518
+ draft: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.draft)
519
+ };
520
+ const tierUsed = {
521
+ reference: 0,
522
+ episode: 0,
523
+ draft: 0
524
+ };
525
+ let procedureUsed = 0;
526
+ const included = [];
527
+ let totalUsed = 0;
528
+ let referenceCount = 0;
529
+ for (const item of scored) {
530
+ if (item.entity.memory_tier === "reference" && item.entity.type !== "procedure" && referenceCount < MIN_REFERENCE_SLOTS) {
531
+ const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
532
+ const tokens = estimateTokens(`### ${item.entity.title}
533
+ ${text}`);
534
+ if (totalUsed + tokens <= tokenBudget) {
535
+ included.push({ ...item, tokens, truncated });
536
+ item.entity.content = text;
537
+ totalUsed += tokens;
538
+ tierUsed.reference += tokens;
539
+ referenceCount++;
540
+ }
541
+ }
542
+ }
543
+ const includedIds = new Set(included.map((i) => i.entity.id));
544
+ const procedureCandidates = scored.filter((item) => item.entity.type === "procedure" && !includedIds.has(item.entity.id));
545
+ for (const item of procedureCandidates) {
546
+ if (item.score < MIN_RELEVANCE_THRESHOLD) {
547
+ manifest.excluded.push({
548
+ entityId: item.entity.id,
549
+ title: item.entity.title,
550
+ type: item.entity.type,
551
+ tier: item.entity.memory_tier,
552
+ relevanceScore: item.score,
553
+ reason: "below_relevance_threshold"
554
+ });
555
+ continue;
556
+ }
557
+ const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
558
+ const tokens = estimateTokens(`### ${item.entity.title}
559
+ ${text}`);
560
+ if (procedureUsed + tokens > procedureBudget) {
561
+ const totalRemaining = tokenBudget - totalUsed;
562
+ if (tokens > totalRemaining) {
563
+ manifest.excluded.push({
564
+ entityId: item.entity.id,
565
+ title: item.entity.title,
566
+ type: item.entity.type,
567
+ tier: item.entity.memory_tier,
568
+ relevanceScore: item.score,
569
+ reason: "procedure_budget_exceeded"
345
570
  });
346
- }
347
- // ============ MEMORY OPERATIONS ============
348
- async createMemoryEntity(data) {
349
- return this.request("POST", "/memory/entities", data);
350
- }
351
- async listMemoryEntities(options) {
352
- const params = new URLSearchParams();
353
- params.set("workspace_id", options.workspace_id);
354
- if (options.project_id)
355
- params.set("project_id", options.project_id);
356
- if (options.type)
357
- params.set("type", options.type);
358
- if (options.scope)
359
- params.set("scope", options.scope);
360
- if (options.tags?.length)
361
- params.set("tags", options.tags.join(","));
362
- if (options.agent_identifier)
363
- params.set("agent_identifier", options.agent_identifier);
364
- if (options.min_confidence !== undefined)
365
- params.set("min_confidence", String(options.min_confidence));
366
- if (options.q)
367
- params.set("q", options.q);
368
- if (options.limit !== undefined)
369
- params.set("limit", String(options.limit));
370
- if (options.offset !== undefined)
371
- params.set("offset", String(options.offset));
372
- return this.request("GET", `/memory/entities?${params.toString()}`);
373
- }
374
- async getMemoryEntity(entityId) {
375
- return this.request("GET", `/memory/entities/${entityId}`);
376
- }
377
- async updateMemoryEntity(entityId, updates) {
378
- return this.request("PUT", `/memory/entities/${entityId}`, updates);
379
- }
380
- async deleteMemoryEntity(entityId) {
381
- return this.request("DELETE", `/memory/entities/${entityId}`);
382
- }
383
- async touchMemoryEntity(entityId) {
384
- return this.request("POST", `/memory/entities/${entityId}/touch`);
385
- }
386
- async batchTouchMemoryEntities(entityIds) {
387
- return this.request("POST", "/memory/entities/batch-touch", {
388
- entity_ids: entityIds,
571
+ continue;
572
+ }
573
+ }
574
+ if (totalUsed + tokens > tokenBudget) {
575
+ manifest.excluded.push({
576
+ entityId: item.entity.id,
577
+ title: item.entity.title,
578
+ type: item.entity.type,
579
+ tier: item.entity.memory_tier,
580
+ relevanceScore: item.score,
581
+ reason: "total_budget_exceeded"
582
+ });
583
+ continue;
584
+ }
585
+ included.push({ ...item, tokens, truncated });
586
+ item.entity.content = text;
587
+ totalUsed += tokens;
588
+ procedureUsed += tokens;
589
+ includedIds.add(item.entity.id);
590
+ }
591
+ for (const item of scored) {
592
+ if (includedIds.has(item.entity.id))
593
+ continue;
594
+ if (item.entity.type === "procedure")
595
+ continue;
596
+ if (item.score < MIN_RELEVANCE_THRESHOLD) {
597
+ manifest.excluded.push({
598
+ entityId: item.entity.id,
599
+ title: item.entity.title,
600
+ type: item.entity.type,
601
+ tier: item.entity.memory_tier,
602
+ relevanceScore: item.score,
603
+ reason: "below_relevance_threshold"
604
+ });
605
+ continue;
606
+ }
607
+ const tier = item.entity.memory_tier;
608
+ const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
609
+ const tokens = estimateTokens(`### ${item.entity.title}
610
+ ${text}`);
611
+ if (tierUsed[tier] + tokens > tierBudgets[tier]) {
612
+ const totalRemaining = tokenBudget - totalUsed;
613
+ if (tokens > totalRemaining) {
614
+ manifest.excluded.push({
615
+ entityId: item.entity.id,
616
+ title: item.entity.title,
617
+ type: item.entity.type,
618
+ tier,
619
+ relevanceScore: item.score,
620
+ reason: "budget_exceeded"
389
621
  });
390
- }
391
- async createMemoryRelation(data) {
392
- return this.request("POST", "/memory/relations", data);
393
- }
394
- async deleteMemoryRelation(relationId) {
395
- return this.request("DELETE", `/memory/relations/${relationId}`);
396
- }
397
- async getRelatedEntities(entityId) {
398
- return this.request("GET", `/memory/entities/${entityId}/related`);
399
- }
400
- async searchMemoryEntities(workspaceId, query, options) {
401
- const params = new URLSearchParams();
402
- params.set("workspace_id", workspaceId);
403
- params.set("q", query);
404
- if (options?.project_id)
405
- params.set("project_id", options.project_id);
406
- if (options?.type)
407
- params.set("type", options.type);
408
- if (options?.limit !== undefined)
409
- params.set("limit", String(options.limit));
410
- return this.request("GET", `/memory/search?${params.toString()}`);
411
- }
412
- // ============ VAULT INDEX ============
413
- async getVaultIndex(options) {
414
- const params = new URLSearchParams();
415
- params.set("workspace_id", options.workspace_id);
416
- if (options.project_id)
417
- params.set("project_id", options.project_id);
418
- if (options.type)
419
- params.set("type", options.type);
420
- if (options.limit !== undefined)
421
- params.set("limit", String(options.limit));
422
- return this.request("GET", `/memory/index?${params.toString()}`);
423
- }
424
- async getVaultIndexMarkdown(options) {
425
- const params = new URLSearchParams();
426
- params.set("workspace_id", options.workspace_id);
427
- if (options.project_id)
428
- params.set("project_id", options.project_id);
429
- if (options.type)
430
- params.set("type", options.type);
431
- if (options.limit !== undefined)
432
- params.set("limit", String(options.limit));
433
- return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
434
- accept: "text/markdown",
622
+ continue;
623
+ }
624
+ }
625
+ if (totalUsed + tokens > tokenBudget) {
626
+ manifest.excluded.push({
627
+ entityId: item.entity.id,
628
+ title: item.entity.title,
629
+ type: item.entity.type,
630
+ tier,
631
+ relevanceScore: item.score,
632
+ reason: "total_budget_exceeded"
633
+ });
634
+ continue;
635
+ }
636
+ included.push({ ...item, tokens, truncated });
637
+ item.entity.content = text;
638
+ totalUsed += tokens;
639
+ tierUsed[tier] += tokens;
640
+ includedIds.add(item.entity.id);
641
+ }
642
+ manifest.budgetUsed = totalUsed;
643
+ const procedureItems = included.filter((i) => i.entity.type === "procedure");
644
+ manifest.tierBreakdown = {
645
+ reference: {
646
+ count: included.filter((i) => i.entity.memory_tier === "reference" && i.entity.type !== "procedure").length,
647
+ tokens: tierUsed.reference
648
+ },
649
+ episode: {
650
+ count: included.filter((i) => i.entity.memory_tier === "episode" && i.entity.type !== "procedure").length,
651
+ tokens: tierUsed.episode
652
+ },
653
+ draft: {
654
+ count: included.filter((i) => i.entity.memory_tier === "draft" && i.entity.type !== "procedure").length,
655
+ tokens: tierUsed.draft
656
+ }
657
+ };
658
+ manifest.procedureBreakdown = {
659
+ count: procedureItems.length,
660
+ tokens: procedureUsed,
661
+ budget: procedureBudget
662
+ };
663
+ for (const item of included) {
664
+ manifest.included.push({
665
+ entityId: item.entity.id,
666
+ title: item.entity.title,
667
+ type: item.entity.type,
668
+ tier: item.entity.memory_tier,
669
+ relevanceScore: item.score,
670
+ reasons: item.reasons,
671
+ tokenCount: item.tokens,
672
+ truncated: item.truncated
673
+ });
674
+ }
675
+ const contextSections = [];
676
+ const nonProcedureItems = included.filter((i) => i.entity.type !== "procedure");
677
+ if (included.length > 0) {
678
+ if (procedureItems.length > 0) {
679
+ contextSections.push(`## Procedures (${procedureItems.length} loaded, ${procedureUsed}/${procedureBudget} tokens)`);
680
+ for (const item of procedureItems) {
681
+ const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
682
+ const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
683
+ contextSections.push(`
684
+ ### ${item.entity.title} (confidence: ${item.entity.confidence})${tierLabel}${tags}`);
685
+ contextSections.push(item.entity.content);
686
+ }
687
+ }
688
+ if (nonProcedureItems.length > 0) {
689
+ contextSections.push(`
690
+ ## Relevant Memories (${nonProcedureItems.length} loaded, ${manifest.excluded.length} excluded)`);
691
+ contextSections.push(`*Assembly: ${assemblyId} | Budget: ${totalUsed}/${tokenBudget} tokens*`);
692
+ for (const item of nonProcedureItems) {
693
+ const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
694
+ const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
695
+ contextSections.push(`
696
+ ### ${item.entity.title} (${item.entity.type}, confidence: ${item.entity.confidence})${tierLabel}${tags}`);
697
+ contextSections.push(item.entity.content);
698
+ }
699
+ }
700
+ }
701
+ incrementAccessCounts(client2, included.map((i) => i.entity.id)).catch(() => {});
702
+ promoteEligibleEntities(client2, included.map((i) => i.entity)).catch(() => {});
703
+ return {
704
+ context: contextSections.join(`
705
+ `),
706
+ manifest,
707
+ memories: included.map((i) => i.entity)
708
+ };
709
+ }
710
+ function mapToContextEntity(raw) {
711
+ const e = raw;
712
+ return {
713
+ id: e.id,
714
+ type: e.type,
715
+ title: e.title,
716
+ content: e.content,
717
+ confidence: e.confidence ?? 1,
718
+ tags: e.tags || [],
719
+ memory_tier: e.memory_tier || "reference",
720
+ access_count: e.access_count || 0,
721
+ last_accessed_at: e.last_accessed_at || null,
722
+ created_at: e.created_at || "",
723
+ updated_at: e.updated_at || "",
724
+ metadata: e.metadata ?? undefined,
725
+ rrf_score: e.rrf_score ?? undefined,
726
+ fts_rank: e.fts_rank ?? undefined,
727
+ semantic_rank: e.semantic_rank ?? undefined
728
+ };
729
+ }
730
+ async function incrementAccessCounts(client2, entityIds) {
731
+ if (entityIds.length === 0)
732
+ return;
733
+ try {
734
+ await client2.batchTouchMemoryEntities(entityIds);
735
+ } catch {
736
+ await Promise.allSettled(entityIds.map((id) => client2.touchMemoryEntity(id)));
737
+ }
738
+ }
739
+ async function promoteEligibleEntities(client2, entities) {
740
+ for (const entity of entities) {
741
+ if (entity.memory_tier === "reference")
742
+ continue;
743
+ if (!entity.created_at)
744
+ continue;
745
+ const promotion = checkPromotion(entity.memory_tier, entity.access_count + 1, entity.confidence, entity.created_at);
746
+ if (promotion.eligible && promotion.targetTier) {
747
+ try {
748
+ await client2.updateMemoryEntity(entity.id, {
749
+ memory_tier: promotion.targetTier,
750
+ metadata: {
751
+ ...entity.metadata || {},
752
+ promoted_at: new Date().toISOString(),
753
+ promotion_reason: promotion.reason,
754
+ promoted_from: entity.memory_tier
755
+ }
435
756
  });
757
+ } catch {}
436
758
  }
437
- // ============ ENTITY STATS (materialized view) ============
438
- async getMemoryStats(options) {
439
- const params = new URLSearchParams();
440
- params.set("workspace_id", options.workspace_id);
441
- if (options.project_id)
442
- params.set("project_id", options.project_id);
443
- return this.request("GET", `/memory/stats?${params.toString()}`);
444
- }
445
- async refreshMemoryStats(workspaceId) {
446
- return this.request("POST", "/memory/stats", { workspace_id: workspaceId });
447
- }
448
- // ============ WIKI-LINK RESOLUTION ============
449
- async resolveLinks(options) {
450
- return this.request("POST", "/memory/resolve-links", options);
451
- }
452
- // ============ MARKDOWN MEMORY OPERATIONS ============
453
- async listMemoryEntitiesMarkdown(options) {
454
- const params = new URLSearchParams();
455
- params.set("workspace_id", options.workspace_id);
456
- if (options.project_id)
457
- params.set("project_id", options.project_id);
458
- if (options.type)
459
- params.set("type", options.type);
460
- if (options.scope)
461
- params.set("scope", options.scope);
462
- if (options.tags?.length)
463
- params.set("tags", options.tags.join(","));
464
- if (options.agent_identifier)
465
- params.set("agent_identifier", options.agent_identifier);
466
- if (options.min_confidence !== undefined)
467
- params.set("min_confidence", String(options.min_confidence));
468
- if (options.q)
469
- params.set("q", options.q);
470
- if (options.limit !== undefined)
471
- params.set("limit", String(options.limit));
472
- if (options.offset !== undefined)
473
- params.set("offset", String(options.offset));
474
- return this.requestRaw("GET", `/memory/entities?${params.toString()}`, undefined, {
475
- accept: "text/markdown",
759
+ }
760
+ }
761
+ function cacheManifest(manifest) {
762
+ if (manifestCache.size >= MAX_CACHE_SIZE) {
763
+ const firstKey = manifestCache.keys().next().value;
764
+ if (firstKey)
765
+ manifestCache.delete(firstKey);
766
+ }
767
+ manifestCache.set(manifest.assemblyId, manifest);
768
+ }
769
+ function getCachedManifest(assemblyId) {
770
+ return manifestCache.get(assemblyId);
771
+ }
772
+ function trackSessionAssembly(cardId, assemblyId) {
773
+ if (sessionAssemblyMap.size >= MAX_SESSION_MAP_SIZE) {
774
+ const firstKey = sessionAssemblyMap.keys().next().value;
775
+ if (firstKey)
776
+ sessionAssemblyMap.delete(firstKey);
777
+ }
778
+ sessionAssemblyMap.set(cardId, assemblyId);
779
+ }
780
+ function getSessionAssemblyId(cardId) {
781
+ return sessionAssemblyMap.get(cardId);
782
+ }
783
+ async function recordContextFeedback(client2, cardId, sessionStatus, progressPercent, hadBlockers) {
784
+ const assemblyId = sessionAssemblyMap.get(cardId);
785
+ if (!assemblyId)
786
+ return { adjusted: 0 };
787
+ const manifest = manifestCache.get(assemblyId);
788
+ if (!manifest || manifest.included.length === 0)
789
+ return { adjusted: 0 };
790
+ let adjusted = 0;
791
+ const isSuccess = sessionStatus === "completed" && (progressPercent ?? 0) >= 100;
792
+ for (const entry of manifest.included) {
793
+ try {
794
+ if (isSuccess) {
795
+ const { entity } = await client2.getMemoryEntity(entry.entityId);
796
+ const e = entity;
797
+ const currentUsefulness = e.metadata?.usefulness_score ?? 0;
798
+ const newConfidence = Math.min((e.confidence ?? 0.5) + 0.05, 1);
799
+ await client2.updateMemoryEntity(entry.entityId, {
800
+ confidence: newConfidence,
801
+ metadata: {
802
+ usefulness_score: currentUsefulness + 1,
803
+ last_feedback_at: new Date().toISOString()
804
+ }
476
805
  });
477
- }
478
- async getMemoryEntityMarkdown(entityId) {
479
- return this.requestRaw("GET", `/memory/entities/${entityId}`, undefined, {
480
- accept: "text/markdown",
806
+ adjusted++;
807
+ } else if (hadBlockers) {
808
+ const { entity } = await client2.getMemoryEntity(entry.entityId);
809
+ const e = entity;
810
+ const newConfidence = Math.max((e.confidence ?? 0.5) - 0.02, 0.1);
811
+ await client2.updateMemoryEntity(entry.entityId, {
812
+ confidence: newConfidence,
813
+ metadata: {
814
+ last_feedback_at: new Date().toISOString()
815
+ }
481
816
  });
482
- }
483
- async searchMemoryEntitiesMarkdown(workspaceId, query, options) {
484
- const params = new URLSearchParams();
485
- params.set("workspace_id", workspaceId);
486
- params.set("q", query);
487
- if (options?.project_id)
488
- params.set("project_id", options.project_id);
489
- if (options?.type)
490
- params.set("type", options.type);
491
- if (options?.limit !== undefined)
492
- params.set("limit", String(options.limit));
493
- return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
494
- accept: "text/markdown",
817
+ adjusted++;
818
+ }
819
+ } catch {}
820
+ }
821
+ sessionAssemblyMap.delete(cardId);
822
+ return { adjusted };
823
+ }
824
+ 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;
825
+ var init_context_assembly = __esm(() => {
826
+ init_dist();
827
+ TIER_WEIGHTS = {
828
+ reference: 1,
829
+ episode: 0.7,
830
+ draft: 0.4
831
+ };
832
+ TIER_BUDGET_ALLOCATION = {
833
+ reference: 0.6,
834
+ episode: 0.3,
835
+ draft: 0.1
836
+ };
837
+ RELATION_BONUSES = {
838
+ depends_on: 0.15,
839
+ resolved_by: 0.2,
840
+ relates_to: 0.1,
841
+ implements: 0.15,
842
+ blocks: 0.15,
843
+ references: 0.1,
844
+ extends: 0.1,
845
+ caused_by: 0.15
846
+ };
847
+ QUERY_SYNONYMS = {
848
+ auth: ["authentication", "authorization", "session"],
849
+ authentication: ["auth", "session", "sign-in"],
850
+ login: ["sign-in", "authentication", "session"],
851
+ bug: ["error", "issue", "defect", "problem"],
852
+ error: ["exception", "failure", "issue"],
853
+ fix: ["resolve", "patch", "repair", "correct"],
854
+ deploy: ["deployment", "release", "ship", "publish"],
855
+ test: ["testing", "spec", "assertion", "verify"],
856
+ config: ["configuration", "settings", "setup"],
857
+ db: ["database", "storage", "persistence"],
858
+ database: ["storage", "persistence", "data store"],
859
+ api: ["endpoint", "route", "service"],
860
+ ui: ["frontend", "component", "view"],
861
+ perf: ["performance", "speed", "latency"],
862
+ performance: ["speed", "latency", "optimization"]
863
+ };
864
+ manifestCache = new Map;
865
+ sessionAssemblyMap = new Map;
866
+ });
867
+
868
+ // src/prompt-builder.ts
869
+ var exports_prompt_builder = {};
870
+ __export(exports_prompt_builder, {
871
+ inferCategoryFromLabels: () => inferCategoryFromLabels,
872
+ getRoleFraming: () => getRoleFraming,
873
+ getAvailableVariants: () => getAvailableVariants,
874
+ getAvailableCategories: () => getAvailableCategories,
875
+ generatePrompt: () => generatePrompt
876
+ });
877
+ function inferCategoryFromLabels(labels) {
878
+ for (const label of labels) {
879
+ const normalizedName = label.name.toLowerCase().trim();
880
+ if (LABEL_CATEGORY_MAP[normalizedName]) {
881
+ return LABEL_CATEGORY_MAP[normalizedName];
882
+ }
883
+ for (const [key, category] of Object.entries(LABEL_CATEGORY_MAP)) {
884
+ if (normalizedName.includes(key) || key.includes(normalizedName)) {
885
+ return category;
886
+ }
887
+ }
888
+ }
889
+ return "custom";
890
+ }
891
+ function getRoleFraming(category) {
892
+ return DEFAULT_ROLE_FRAMINGS[category];
893
+ }
894
+ function estimateTokens2(text) {
895
+ return Math.ceil(text.length / 4);
896
+ }
897
+ function formatSubtasks(subtasks) {
898
+ if (subtasks.length === 0)
899
+ return "";
900
+ const completed = subtasks.filter((s) => s.completed).length;
901
+ const lines = subtasks.map((s) => ` ${s.completed ? "[x]" : "[ ]"} ${s.title}`);
902
+ return `
903
+ ## Subtasks (${completed}/${subtasks.length} completed)
904
+ ${lines.join(`
905
+ `)}`;
906
+ }
907
+ function formatLabels(labels) {
908
+ if (labels.length === 0)
909
+ return "";
910
+ return `
911
+ **Labels:** ${labels.map((l) => l.name).join(", ")}`;
912
+ }
913
+ function formatLinkedCards(links) {
914
+ if (!links || links.length === 0)
915
+ return "";
916
+ const lines = links.map((link) => {
917
+ const prefix = link.direction === "outgoing" ? "->" : "<-";
918
+ return ` ${prefix} #${link.target_card.short_id}: ${link.target_card.title} (${link.display_type})`;
919
+ });
920
+ return `
921
+ ## Related Cards
922
+ ${lines.join(`
923
+ `)}`;
924
+ }
925
+ function generatePrompt(options) {
926
+ const {
927
+ card,
928
+ column,
929
+ variant,
930
+ customConstraints,
931
+ memories,
932
+ assembledContext,
933
+ assemblyId
934
+ } = options;
935
+ const contextOpts = {
936
+ includeTitle: true,
937
+ includeDescription: true,
938
+ includeLabels: true,
939
+ includeSubtasks: true,
940
+ includeActivity: false,
941
+ includeAssignee: true,
942
+ includeDueDate: true,
943
+ includePriority: true,
944
+ includeLinks: true,
945
+ includeColumn: true,
946
+ ...options.contextOptions
947
+ };
948
+ const labels = card.labels || [];
949
+ const subtasks = card.subtasks || [];
950
+ const links = card.links || [];
951
+ const category = inferCategoryFromLabels(labels);
952
+ const roleFraming = getRoleFraming(category);
953
+ const sections = [];
954
+ sections.push(`# Role: ${roleFraming.role}
955
+ `);
956
+ sections.push(roleFraming.perspective);
957
+ sections.push("");
958
+ sections.push(VARIANT_INSTRUCTIONS[variant]);
959
+ sections.push("");
960
+ sections.push(`# Task: ${card.title}`);
961
+ if (contextOpts.includeColumn && column) {
962
+ sections.push(`**Status:** ${column.name}`);
963
+ }
964
+ if (contextOpts.includePriority) {
965
+ sections.push(`**Priority:** ${card.priority}`);
966
+ }
967
+ if (contextOpts.includeDueDate && card.due_date) {
968
+ sections.push(`**Due:** ${card.due_date}`);
969
+ }
970
+ if (contextOpts.includeAssignee && card.assignee) {
971
+ sections.push(`**Assignee:** ${card.assignee.full_name || card.assignee.email}`);
972
+ }
973
+ if (contextOpts.includeLabels && labels.length > 0) {
974
+ sections.push(formatLabels(labels));
975
+ }
976
+ if (contextOpts.includeDescription && card.description) {
977
+ sections.push(`
978
+ ## Description
979
+ ${card.description}`);
980
+ }
981
+ if (contextOpts.includeSubtasks && subtasks.length > 0) {
982
+ sections.push(formatSubtasks(subtasks));
983
+ }
984
+ if (contextOpts.includeLinks && links.length > 0) {
985
+ sections.push(formatLinkedCards(links));
986
+ }
987
+ sections.push(`
988
+ ## Focus Areas`);
989
+ roleFraming.focus.forEach((f) => {
990
+ sections.push(`- ${f}`);
991
+ });
992
+ sections.push(`- **Memory:** Store reusable knowledge via \`harmony_remember\`. Only store what a future agent couldn't easily discover from the code itself, applies beyond this specific card, and includes a "because" (not just what, but why).`);
993
+ sections.push(` - GOOD: "BoardContext card state must use moveCard action, never direct setState — optimistic updates depend on action ordering"`);
994
+ sections.push(` - GOOD: "Mobile bottom bar is 64px, overlaps fixed-position drawers — always add pb-16 to drawer content"`);
995
+ sections.push(` - BAD: "Fixed the login button" (no reusable knowledge — the fix is in the code)`);
996
+ sections.push(` - BAD: "Completed card #42" (ephemeral, auto-tracked by session)`);
997
+ sections.push(`
998
+ ## Suggested Outputs`);
999
+ roleFraming.outputSuggestions.forEach((s) => {
1000
+ sections.push(`- ${s}`);
1001
+ });
1002
+ if (assembledContext) {
1003
+ sections.push(`
1004
+ ${assembledContext}`);
1005
+ } else if (memories && memories.length > 0) {
1006
+ sections.push(`
1007
+ ## Relevant Memories`);
1008
+ sections.push(`*${memories.length} memories recalled from knowledge graph:*`);
1009
+ for (const memory of memories) {
1010
+ const tags = memory.tags.length > 0 ? ` [${memory.tags.join(", ")}]` : "";
1011
+ sections.push(`
1012
+ ### ${memory.title} (${memory.type}, confidence: ${memory.confidence})${tags}`);
1013
+ sections.push(memory.content);
1014
+ }
1015
+ }
1016
+ const oneThingLine = synthesizeOneThing(card, subtasks, links, assembledContext);
1017
+ if (oneThingLine) {
1018
+ sections.push(`
1019
+ ## Recommended Next Step
1020
+ ${oneThingLine}`);
1021
+ }
1022
+ if (variant === "execute") {
1023
+ sections.push(`
1024
+ ## Progress Tracking
1025
+ Update your progress by calling \`harmony_update_agent_progress\` with \`currentTask\` describing what you're doing now:
1026
+ - After exploring the codebase and understanding requirements (~20%)
1027
+ - When you start implementing changes (~50%)
1028
+ - When you move to testing or verification (~80%)
1029
+ - When done, before ending the session (100%)
1030
+
1031
+ Keep \`currentTask\` specific (e.g., "Refactoring auth middleware" not "Working on card").`);
1032
+ }
1033
+ if (customConstraints) {
1034
+ sections.push(`
1035
+ ## Additional Instructions
1036
+ ${customConstraints}`);
1037
+ }
1038
+ sections.push(`
1039
+ ---
1040
+ *Card #${card.short_id} | Generated for ${variant} mode*`);
1041
+ const prompt = sections.join(`
1042
+ `);
1043
+ const memoryCount = assembledContext ? (assembledContext.match(/^### /gm) || []).length : memories?.length || 0;
1044
+ return {
1045
+ prompt,
1046
+ variant,
1047
+ category,
1048
+ role: roleFraming.role,
1049
+ contextSummary: {
1050
+ hasDescription: !!card.description,
1051
+ labelCount: labels.length,
1052
+ subtaskCount: subtasks.length,
1053
+ completedSubtasks: subtasks.filter((s) => s.completed).length,
1054
+ linkedCardCount: links.length,
1055
+ memoryCount
1056
+ },
1057
+ tokenEstimate: estimateTokens2(prompt),
1058
+ ...assemblyId && { assemblyId }
1059
+ };
1060
+ }
1061
+ function extractSessionInsights(assembledContext) {
1062
+ const result = {
1063
+ lastSessionStatus: null,
1064
+ lastSessionTask: null,
1065
+ lastSessionProgress: null,
1066
+ blockers: [],
1067
+ procedureNextStep: null
1068
+ };
1069
+ const sessionMatches = assembledContext.match(/### Session:.*?\n([\s\S]*?)(?=\n###|\n## |\n---|\n\*Assembly|$)/g);
1070
+ if (sessionMatches && sessionMatches.length > 0) {
1071
+ const latest = sessionMatches[0];
1072
+ if (/Completed work on/i.test(latest)) {
1073
+ result.lastSessionStatus = "completed";
1074
+ } else if (/Paused work on|status:\s*paused/i.test(latest)) {
1075
+ result.lastSessionStatus = "paused";
1076
+ }
1077
+ const taskMatch = latest.match(/Final task:\s*(.+)/);
1078
+ if (taskMatch)
1079
+ result.lastSessionTask = taskMatch[1].trim();
1080
+ const progressMatch = latest.match(/Progress:\s*(\d+)%/);
1081
+ if (progressMatch)
1082
+ result.lastSessionProgress = parseInt(progressMatch[1], 10);
1083
+ }
1084
+ const blockerMatches = assembledContext.match(/(?:blocker|blocked by|blocking):\s*(.+)/gi);
1085
+ if (blockerMatches) {
1086
+ result.blockers = blockerMatches.map((m) => m.replace(/(?:blocker|blocked by|blocking):\s*/i, "").trim());
1087
+ }
1088
+ const stepMatches = assembledContext.match(/^\d+\.\s+(?!.*\*\*\[key step\]\*\*.*✓)(.+?)(?:\s*\*\*\[key step\]\*\*)?$/gm);
1089
+ if (stepMatches && stepMatches.length > 0) {
1090
+ result.procedureNextStep = stepMatches[0].replace(/^\d+\.\s+/, "").replace(/\s*\*\*\[key step\]\*\*.*$/, "").trim();
1091
+ }
1092
+ return result;
1093
+ }
1094
+ function synthesizeOneThing(card, subtasks, links, assembledContext) {
1095
+ if (card.done)
1096
+ return null;
1097
+ const blockers = links.filter((l) => l.display_type === "is_blocked_by" && l.direction === "incoming");
1098
+ if (blockers.length > 0) {
1099
+ const blocker = blockers[0];
1100
+ return `Unblock first: resolve #${blocker.target_card.short_id} "${blocker.target_card.title}" which is blocking this card.`;
1101
+ }
1102
+ const session = assembledContext ? extractSessionInsights(assembledContext) : null;
1103
+ if (session?.blockers && session.blockers.length > 0) {
1104
+ return `Resolve blocker: ${session.blockers[0]}`;
1105
+ }
1106
+ if (session?.lastSessionStatus === "paused" && session.lastSessionTask) {
1107
+ const progress = session.lastSessionProgress ? ` (was ${session.lastSessionProgress}% complete)` : "";
1108
+ return `Resume previous session${progress}: "${session.lastSessionTask}".`;
1109
+ }
1110
+ if (subtasks.length > 0) {
1111
+ const completed = subtasks.filter((s) => s.completed).length;
1112
+ if (completed === subtasks.length) {
1113
+ return "All subtasks completed. Review the work and mark the card as done.";
1114
+ }
1115
+ const nextSubtask = subtasks.find((s) => !s.completed);
1116
+ if (nextSubtask) {
1117
+ return `Work on next subtask: "${nextSubtask.title}" (${completed}/${subtasks.length} done).`;
1118
+ }
1119
+ }
1120
+ if (session?.procedureNextStep) {
1121
+ return `Follow procedure: ${session.procedureNextStep}`;
1122
+ }
1123
+ if (session?.lastSessionStatus === "completed" && session.lastSessionTask) {
1124
+ return `Previous session completed ("${session.lastSessionTask}"). Review results and continue with remaining work.`;
1125
+ }
1126
+ if (card.due_date && (card.priority === "urgent" || card.priority === "high")) {
1127
+ return `High-priority task with deadline ${card.due_date}. Start implementation immediately.`;
1128
+ }
1129
+ if (card.description) {
1130
+ return "Analyze the description, identify the approach, and begin implementation.";
1131
+ }
1132
+ return null;
1133
+ }
1134
+ function getAvailableCategories() {
1135
+ return ["bug", "feature", "design", "review", "onboarding", "epic", "custom"];
1136
+ }
1137
+ function getAvailableVariants() {
1138
+ return ["analysis", "draft", "execute"];
1139
+ }
1140
+ var LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS;
1141
+ var init_prompt_builder = __esm(() => {
1142
+ LABEL_CATEGORY_MAP = {
1143
+ bug: "bug",
1144
+ fix: "bug",
1145
+ hotfix: "bug",
1146
+ defect: "bug",
1147
+ issue: "bug",
1148
+ error: "bug",
1149
+ feature: "feature",
1150
+ enhancement: "feature",
1151
+ improvement: "feature",
1152
+ new: "feature",
1153
+ design: "design",
1154
+ ui: "design",
1155
+ ux: "design",
1156
+ frontend: "design",
1157
+ styling: "design",
1158
+ review: "review",
1159
+ "code review": "review",
1160
+ pr: "review",
1161
+ feedback: "review",
1162
+ onboarding: "onboarding",
1163
+ documentation: "onboarding",
1164
+ docs: "onboarding",
1165
+ guide: "onboarding",
1166
+ tutorial: "onboarding",
1167
+ epic: "epic",
1168
+ initiative: "epic",
1169
+ project: "epic",
1170
+ milestone: "epic"
1171
+ };
1172
+ DEFAULT_ROLE_FRAMINGS = {
1173
+ bug: {
1174
+ category: "bug",
1175
+ role: "Senior QA Engineer and Software Developer",
1176
+ perspective: "You are investigating and fixing a bug report.",
1177
+ focus: [
1178
+ "Root cause analysis",
1179
+ "Steps to reproduce",
1180
+ "Impact assessment",
1181
+ "Fix implementation",
1182
+ "Regression prevention",
1183
+ "Test cases to prevent recurrence"
1184
+ ],
1185
+ outputSuggestions: [
1186
+ "Bug triage summary",
1187
+ "Root cause explanation",
1188
+ "Fix implementation plan",
1189
+ "Test cases"
1190
+ ]
1191
+ },
1192
+ feature: {
1193
+ category: "feature",
1194
+ role: "Product Engineer",
1195
+ perspective: "You are implementing a new feature or enhancement.",
1196
+ focus: [
1197
+ "User requirements",
1198
+ "Technical specification",
1199
+ "Implementation approach",
1200
+ "Edge cases",
1201
+ "Acceptance criteria",
1202
+ "Integration points"
1203
+ ],
1204
+ outputSuggestions: [
1205
+ "Technical specification",
1206
+ "Implementation tasks",
1207
+ "Acceptance criteria checklist",
1208
+ "API design"
1209
+ ]
1210
+ },
1211
+ design: {
1212
+ category: "design",
1213
+ role: "UX Designer and Frontend Developer",
1214
+ perspective: "You are designing and implementing a user interface.",
1215
+ focus: [
1216
+ "User experience flow",
1217
+ "Visual design consistency",
1218
+ "Accessibility (WCAG)",
1219
+ "Responsive behavior",
1220
+ "Component architecture",
1221
+ "Interaction patterns"
1222
+ ],
1223
+ outputSuggestions: [
1224
+ "User flow diagram",
1225
+ "Component specifications",
1226
+ "Accessibility checklist",
1227
+ "Responsive breakpoints"
1228
+ ]
1229
+ },
1230
+ review: {
1231
+ category: "review",
1232
+ role: "Code Reviewer and Technical Lead",
1233
+ perspective: "You are reviewing code for quality, correctness, and maintainability.",
1234
+ focus: [
1235
+ "Code correctness",
1236
+ "Performance implications",
1237
+ "Security considerations",
1238
+ "Testing coverage",
1239
+ "Documentation",
1240
+ "Best practices adherence"
1241
+ ],
1242
+ outputSuggestions: [
1243
+ "Review checklist",
1244
+ "Suggested improvements",
1245
+ "Test scenarios",
1246
+ "Security audit"
1247
+ ]
1248
+ },
1249
+ onboarding: {
1250
+ category: "onboarding",
1251
+ role: "Technical Writer and Developer Advocate",
1252
+ perspective: "You are creating documentation or onboarding materials.",
1253
+ focus: [
1254
+ "Clear step-by-step instructions",
1255
+ "Prerequisites and setup",
1256
+ "Common pitfalls",
1257
+ "Examples and use cases",
1258
+ "Troubleshooting guide",
1259
+ "Related resources"
1260
+ ],
1261
+ outputSuggestions: [
1262
+ "Getting started guide",
1263
+ "Step-by-step tutorial",
1264
+ "FAQ section",
1265
+ "Troubleshooting guide"
1266
+ ]
1267
+ },
1268
+ epic: {
1269
+ category: "epic",
1270
+ role: "Technical Project Manager and Architect",
1271
+ perspective: "You are planning and coordinating a large initiative.",
1272
+ focus: [
1273
+ "Scope definition",
1274
+ "Task breakdown",
1275
+ "Dependencies",
1276
+ "Risk assessment",
1277
+ "Timeline considerations",
1278
+ "Success metrics"
1279
+ ],
1280
+ outputSuggestions: [
1281
+ "Epic breakdown into stories",
1282
+ "Dependency graph",
1283
+ "Risk mitigation plan",
1284
+ "Success criteria"
1285
+ ]
1286
+ },
1287
+ custom: {
1288
+ category: "custom",
1289
+ role: "Software Engineer",
1290
+ perspective: "You are working on a software task.",
1291
+ focus: [
1292
+ "Understanding requirements",
1293
+ "Implementation approach",
1294
+ "Quality considerations",
1295
+ "Testing strategy"
1296
+ ],
1297
+ outputSuggestions: [
1298
+ "Implementation plan",
1299
+ "Technical notes",
1300
+ "Task checklist"
1301
+ ]
1302
+ }
1303
+ };
1304
+ VARIANT_INSTRUCTIONS = {
1305
+ analysis: `ANALYSIS MODE: Analyze this task thoroughly. Identify requirements, constraints, edge cases, and potential challenges. Do NOT implement anything yet - focus on understanding and planning.`,
1306
+ draft: `DRAFT MODE: Create a detailed implementation plan with code structure, key decisions, and approach. Include pseudocode or skeleton code where helpful. This is for review before full implementation.`,
1307
+ execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
1308
+ };
1309
+ });
1310
+
1311
+ // src/config.ts
1312
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1313
+ import { homedir } from "node:os";
1314
+ import { join } from "node:path";
1315
+ var DEFAULT_API_URL = "https://app.gethmy.com/api";
1316
+ var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
1317
+ function getConfigDir() {
1318
+ return join(homedir(), ".harmony-mcp");
1319
+ }
1320
+ function getConfigPath() {
1321
+ return join(getConfigDir(), "config.json");
1322
+ }
1323
+ function getLocalConfigPath(cwd) {
1324
+ return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
1325
+ }
1326
+ function loadConfig() {
1327
+ const configPath = getConfigPath();
1328
+ if (!existsSync(configPath)) {
1329
+ return {
1330
+ apiKey: null,
1331
+ apiUrl: DEFAULT_API_URL,
1332
+ activeWorkspaceId: null,
1333
+ activeProjectId: null,
1334
+ userEmail: null,
1335
+ memoryDir: null
1336
+ };
1337
+ }
1338
+ try {
1339
+ const data = readFileSync(configPath, "utf-8");
1340
+ const config = JSON.parse(data);
1341
+ return {
1342
+ apiKey: config.apiKey || null,
1343
+ apiUrl: config.apiUrl || DEFAULT_API_URL,
1344
+ activeWorkspaceId: config.activeWorkspaceId || null,
1345
+ activeProjectId: config.activeProjectId || null,
1346
+ userEmail: config.userEmail || null,
1347
+ memoryDir: config.memoryDir || null
1348
+ };
1349
+ } catch {
1350
+ return {
1351
+ apiKey: null,
1352
+ apiUrl: DEFAULT_API_URL,
1353
+ activeWorkspaceId: null,
1354
+ activeProjectId: null,
1355
+ userEmail: null,
1356
+ memoryDir: null
1357
+ };
1358
+ }
1359
+ }
1360
+ function saveConfig(config) {
1361
+ const configDir = getConfigDir();
1362
+ const configPath = getConfigPath();
1363
+ if (!existsSync(configDir)) {
1364
+ mkdirSync(configDir, { recursive: true, mode: 448 });
1365
+ }
1366
+ const existingConfig = loadConfig();
1367
+ const newConfig = { ...existingConfig, ...config };
1368
+ writeFileSync(configPath, JSON.stringify(newConfig, null, 2), {
1369
+ mode: 384
1370
+ });
1371
+ }
1372
+ function loadLocalConfig(cwd) {
1373
+ const localConfigPath = getLocalConfigPath(cwd);
1374
+ if (!existsSync(localConfigPath)) {
1375
+ return null;
1376
+ }
1377
+ try {
1378
+ const data = readFileSync(localConfigPath, "utf-8");
1379
+ const config = JSON.parse(data);
1380
+ return {
1381
+ workspaceId: config.workspaceId || null,
1382
+ projectId: config.projectId || null
1383
+ };
1384
+ } catch {
1385
+ return null;
1386
+ }
1387
+ }
1388
+ function saveLocalConfig(config, cwd) {
1389
+ const localConfigPath = getLocalConfigPath(cwd);
1390
+ const existingConfig = loadLocalConfig(cwd) || {
1391
+ workspaceId: null,
1392
+ projectId: null
1393
+ };
1394
+ const newConfig = { ...existingConfig, ...config };
1395
+ const cleanConfig = {};
1396
+ if (newConfig.workspaceId)
1397
+ cleanConfig.workspaceId = newConfig.workspaceId;
1398
+ if (newConfig.projectId)
1399
+ cleanConfig.projectId = newConfig.projectId;
1400
+ writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
1401
+ }
1402
+ function hasLocalConfig(cwd) {
1403
+ return existsSync(getLocalConfigPath(cwd));
1404
+ }
1405
+ function getApiKey() {
1406
+ const config = loadConfig();
1407
+ if (!config.apiKey) {
1408
+ throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to set your API key.
1409
+ ` + "You can generate an API key at https://gethmy.com → Settings → API Keys.");
1410
+ }
1411
+ return config.apiKey;
1412
+ }
1413
+ function getApiUrl() {
1414
+ const config = loadConfig();
1415
+ return config.apiUrl;
1416
+ }
1417
+ function getUserEmail() {
1418
+ const config = loadConfig();
1419
+ return config.userEmail;
1420
+ }
1421
+ function setUserEmail(email) {
1422
+ saveConfig({ userEmail: email });
1423
+ }
1424
+ function setActiveWorkspace(workspaceId, options) {
1425
+ if (options?.local) {
1426
+ saveLocalConfig({ workspaceId }, options.cwd);
1427
+ } else {
1428
+ saveConfig({ activeWorkspaceId: workspaceId });
1429
+ }
1430
+ }
1431
+ function setActiveProject(projectId, options) {
1432
+ if (options?.local) {
1433
+ saveLocalConfig({ projectId }, options.cwd);
1434
+ } else {
1435
+ saveConfig({ activeProjectId: projectId });
1436
+ }
1437
+ }
1438
+ function getActiveWorkspaceId(cwd) {
1439
+ const localConfig = loadLocalConfig(cwd);
1440
+ if (localConfig?.workspaceId) {
1441
+ return localConfig.workspaceId;
1442
+ }
1443
+ return loadConfig().activeWorkspaceId;
1444
+ }
1445
+ function getActiveProjectId(cwd) {
1446
+ const localConfig = loadLocalConfig(cwd);
1447
+ if (localConfig?.projectId) {
1448
+ return localConfig.projectId;
1449
+ }
1450
+ return loadConfig().activeProjectId;
1451
+ }
1452
+ function isConfigured() {
1453
+ const config = loadConfig();
1454
+ return !!config.apiKey;
1455
+ }
1456
+ function areSkillsInstalled(cwd) {
1457
+ const home = homedir();
1458
+ const workingDir = cwd || process.cwd();
1459
+ const foundPaths = [];
1460
+ const globalSkillsDir = join(home, ".agents", "skills");
1461
+ const globalSkillPath = join(globalSkillsDir, "hmy", "SKILL.md");
1462
+ if (existsSync(globalSkillPath)) {
1463
+ foundPaths.push(globalSkillPath);
1464
+ return { installed: true, location: "global", paths: foundPaths };
1465
+ }
1466
+ const claudeGlobalSkill = join(home, ".claude", "skills", "hmy.md");
1467
+ if (existsSync(claudeGlobalSkill)) {
1468
+ foundPaths.push(claudeGlobalSkill);
1469
+ return { installed: true, location: "global", paths: foundPaths };
1470
+ }
1471
+ const claudeGlobalSkillAlt = join(home, ".claude", "skills", "hmy", "SKILL.md");
1472
+ if (existsSync(claudeGlobalSkillAlt)) {
1473
+ foundPaths.push(claudeGlobalSkillAlt);
1474
+ return { installed: true, location: "global", paths: foundPaths };
1475
+ }
1476
+ const localSkillPath = join(workingDir, ".claude", "skills", "hmy.md");
1477
+ if (existsSync(localSkillPath)) {
1478
+ foundPaths.push(localSkillPath);
1479
+ return { installed: true, location: "local", paths: foundPaths };
1480
+ }
1481
+ const localSkillPathAlt = join(workingDir, ".claude", "skills", "hmy", "SKILL.md");
1482
+ if (existsSync(localSkillPathAlt)) {
1483
+ foundPaths.push(localSkillPathAlt);
1484
+ return { installed: true, location: "local", paths: foundPaths };
1485
+ }
1486
+ return { installed: false, location: null, paths: [] };
1487
+ }
1488
+ function hasProjectContext(cwd) {
1489
+ const localConfig = loadLocalConfig(cwd);
1490
+ return !!(localConfig?.workspaceId || localConfig?.projectId);
1491
+ }
1492
+ function getMemoryDir() {
1493
+ const config = loadConfig();
1494
+ if (config.memoryDir)
1495
+ return config.memoryDir;
1496
+ return join(homedir(), ".harmony", "memory");
1497
+ }
1498
+
1499
+ // src/api-client.ts
1500
+ var RETRY_CONFIG = {
1501
+ maxRetries: 3,
1502
+ baseDelayMs: 1000,
1503
+ maxDelayMs: 1e4,
1504
+ retryableStatusCodes: [408, 429, 500, 502, 503, 504]
1505
+ };
1506
+ function isRetryableError(error, status) {
1507
+ if (status && RETRY_CONFIG.retryableStatusCodes.includes(status)) {
1508
+ return true;
1509
+ }
1510
+ if (error instanceof TypeError)
1511
+ return true;
1512
+ const msg = error instanceof Error ? error.message : String(error);
1513
+ return msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed");
1514
+ }
1515
+ function getRetryDelay(attempt) {
1516
+ const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
1517
+ return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
1518
+ }
1519
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1520
+
1521
+ class Semaphore {
1522
+ permits;
1523
+ queue = [];
1524
+ constructor(permits) {
1525
+ this.permits = permits;
1526
+ }
1527
+ async acquire() {
1528
+ if (this.permits > 0) {
1529
+ this.permits--;
1530
+ return;
1531
+ }
1532
+ return new Promise((resolve) => this.queue.push(resolve));
1533
+ }
1534
+ release() {
1535
+ const next = this.queue.shift();
1536
+ if (next)
1537
+ next();
1538
+ else
1539
+ this.permits++;
1540
+ }
1541
+ }
1542
+ var requestSemaphore = new Semaphore(3);
1543
+ async function signupUser(apiUrl, data) {
1544
+ const url = `${apiUrl}/v1/auth/signup`;
1545
+ const response = await fetch(url, {
1546
+ method: "POST",
1547
+ headers: { "Content-Type": "application/json" },
1548
+ body: JSON.stringify(data)
1549
+ });
1550
+ const result = await response.json();
1551
+ if (!response.ok) {
1552
+ throw new Error(result.error || `Signup failed: ${response.status}`);
1553
+ }
1554
+ return result;
1555
+ }
1556
+ async function requestWithBearer(apiUrl, bearerToken, method, path, body) {
1557
+ const url = `${apiUrl}/v1${path}`;
1558
+ const response = await fetch(url, {
1559
+ method,
1560
+ headers: {
1561
+ "Content-Type": "application/json",
1562
+ Authorization: `Bearer ${bearerToken}`
1563
+ },
1564
+ body: body ? JSON.stringify(body) : undefined
1565
+ });
1566
+ const result = await response.json();
1567
+ if (!response.ok) {
1568
+ throw new Error(result.error || `API error: ${response.status}`);
1569
+ }
1570
+ return result;
1571
+ }
1572
+
1573
+ class HarmonyApiClient {
1574
+ apiKey;
1575
+ apiUrl;
1576
+ constructor(options) {
1577
+ this.apiKey = options?.apiKey ?? getApiKey();
1578
+ this.apiUrl = options?.apiUrl ?? getApiUrl();
1579
+ }
1580
+ getApiUrl() {
1581
+ return this.apiUrl;
1582
+ }
1583
+ async request(method, path, body, options) {
1584
+ await requestSemaphore.acquire();
1585
+ try {
1586
+ return await this.requestWithRetry(method, path, body, options);
1587
+ } finally {
1588
+ requestSemaphore.release();
1589
+ }
1590
+ }
1591
+ async requestRaw(method, path, body, options) {
1592
+ await requestSemaphore.acquire();
1593
+ try {
1594
+ return await this.requestRawWithRetry(method, path, body, options);
1595
+ } finally {
1596
+ requestSemaphore.release();
1597
+ }
1598
+ }
1599
+ async requestWithRetry(method, path, body, options) {
1600
+ const url = `${this.apiUrl}/v1${path}`;
1601
+ let lastError = null;
1602
+ const contentType = options?.contentType || "application/json";
1603
+ const accept = options?.accept || "application/json";
1604
+ for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
1605
+ try {
1606
+ const response = await fetch(url, {
1607
+ method,
1608
+ headers: {
1609
+ "Content-Type": contentType,
1610
+ Accept: accept,
1611
+ "X-API-Key": this.apiKey
1612
+ },
1613
+ body: options?.rawBody ?? (body ? JSON.stringify(body) : undefined)
495
1614
  });
496
- }
497
- // ============ EMBEDDING OPERATIONS ============
498
- async backfillEmbeddings(workspaceId, batchSize) {
499
- return this.request("POST", "/memory/backfill-embeddings", {
500
- workspace_id: workspaceId,
501
- batch_size: batchSize || 50,
1615
+ const data = await response.json();
1616
+ if (!response.ok) {
1617
+ const errorMsg = data.error || `API error: ${response.status}`;
1618
+ if (!isRetryableError(null, response.status)) {
1619
+ throw new Error(errorMsg);
1620
+ }
1621
+ lastError = new Error(errorMsg);
1622
+ if (attempt < RETRY_CONFIG.maxRetries) {
1623
+ await sleep(getRetryDelay(attempt));
1624
+ continue;
1625
+ }
1626
+ throw lastError;
1627
+ }
1628
+ return data;
1629
+ } catch (error) {
1630
+ lastError = error instanceof Error ? error : new Error(String(error));
1631
+ if (!isRetryableError(error))
1632
+ throw lastError;
1633
+ if (attempt < RETRY_CONFIG.maxRetries) {
1634
+ await sleep(getRetryDelay(attempt));
1635
+ }
1636
+ }
1637
+ }
1638
+ throw lastError || new Error("Request failed after retries");
1639
+ }
1640
+ async requestRawWithRetry(method, path, body, options) {
1641
+ const url = `${this.apiUrl}/v1${path}`;
1642
+ let lastError = null;
1643
+ const contentType = options?.contentType || "application/json";
1644
+ const accept = options?.accept || "text/markdown";
1645
+ for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
1646
+ try {
1647
+ const response = await fetch(url, {
1648
+ method,
1649
+ headers: {
1650
+ "Content-Type": contentType,
1651
+ Accept: accept,
1652
+ "X-API-Key": this.apiKey
1653
+ },
1654
+ body: options?.rawBody ?? (body ? JSON.stringify(body) : undefined)
502
1655
  });
503
- }
504
- // ============ NLU OPERATIONS ============
505
- async processNLU(data) {
506
- return this.request("POST", "/nlu", data);
507
- }
508
- // ============ PLAN OPERATIONS ============
509
- async createPlan(projectId, data) {
510
- return this.request("POST", "/plans", { projectId, ...data });
511
- }
512
- async listPlans(projectId, options) {
513
- const params = new URLSearchParams({ projectId });
514
- if (options?.search)
515
- params.set("search", options.search);
516
- if (options?.status)
517
- params.set("status", options.status);
518
- return this.request("GET", `/plans?${params.toString()}`);
519
- }
520
- async getPlan(planId) {
521
- return this.request("GET", `/plans/${planId}`);
522
- }
523
- async getPlanByCardId(cardId) {
524
- return this.request("GET", `/cards/${cardId}/plan`);
525
- }
526
- async updatePlan(planId, updates) {
527
- return this.request("PATCH", `/plans/${planId}`, updates);
528
- }
529
- async updatePlanTask(planId, taskId, updates) {
530
- return this.request("PATCH", `/plans/${planId}/tasks/${taskId}`, updates);
531
- }
532
- // ============ ONBOARDING OPERATIONS ============
533
- async createWorkspace(data) {
534
- return this.request("POST", "/workspaces", data);
535
- }
536
- async createProject(data) {
537
- return this.request("POST", "/projects", data);
538
- }
539
- async sendInvitations(data) {
540
- return this.request("POST", "/invitations", data);
541
- }
542
- async generateApiKey(name) {
543
- return this.request("POST", "/api-keys", { name });
544
- }
545
- // ============ PROMPT GENERATION ============
546
- /**
547
- * Generate a prompt for a card with full memory context assembly.
548
- *
549
- * This is the shared entry point for prompt generation — used by the MCP
550
- * server tool handler and the agent daemon. It fetches the card, assembles
551
- * relevant memories, and produces a role-framed prompt.
552
- */
553
- async generateCardPrompt(options) {
554
- const { assembleContext, cacheManifest, generatePrompt } = await loadPromptModules();
555
- // Fetch card data
556
- const cardResult = await this.getCard(options.cardId);
557
- const cardData = cardResult.card;
558
- // Try to get column info
559
- let columnData = null;
560
- const projectIdForBoard = options.projectId || cardData.project_id;
561
- if (projectIdForBoard) {
562
- try {
563
- const board = await this.getBoard(projectIdForBoard, { summary: true });
564
- const column = board.columns.find((col) => col.id === cardData.column_id);
565
- if (column) {
566
- columnData = { name: column.name };
567
- }
568
- }
569
- catch {
570
- // Column info not available, continue without it
571
- }
1656
+ if (!response.ok) {
1657
+ const text = await response.text();
1658
+ let errorMsg;
1659
+ try {
1660
+ errorMsg = JSON.parse(text).error || `API error: ${response.status}`;
1661
+ } catch {
1662
+ errorMsg = text || `API error: ${response.status}`;
1663
+ }
1664
+ if (!isRetryableError(null, response.status)) {
1665
+ throw new Error(errorMsg);
1666
+ }
1667
+ lastError = new Error(errorMsg);
1668
+ if (attempt < RETRY_CONFIG.maxRetries) {
1669
+ await sleep(getRetryDelay(attempt));
1670
+ continue;
1671
+ }
1672
+ throw lastError;
572
1673
  }
573
- const variant = options.variant || "execute";
574
- // Assemble memory context
575
- let assembledContextStr;
576
- let assemblyId;
577
- let memories;
578
- try {
579
- if (options.workspaceId && cardData.title) {
580
- const cardLabels = (cardData.labels || []).map((l) => l.name);
581
- const taskContext = [cardData.title, cardData.description || ""]
582
- .filter(Boolean)
583
- .join(" ");
584
- const assembled = await assembleContext({
585
- workspaceId: options.workspaceId,
586
- projectId: options.projectId,
587
- taskContext,
588
- cardLabels,
589
- cardId: cardData.id,
590
- client: this,
591
- });
592
- if (assembled.context) {
593
- assembledContextStr = assembled.context;
594
- assemblyId = assembled.manifest.assemblyId;
595
- cacheManifest(assembled.manifest);
596
- }
597
- }
1674
+ return await response.text();
1675
+ } catch (error) {
1676
+ lastError = error instanceof Error ? error : new Error(String(error));
1677
+ if (!isRetryableError(error))
1678
+ throw lastError;
1679
+ if (attempt < RETRY_CONFIG.maxRetries) {
1680
+ await sleep(getRetryDelay(attempt));
598
1681
  }
599
- catch (err) {
600
- // Context assembly failed, try legacy fallback
601
- const msg = err instanceof Error ? err.message : String(err);
602
- console.debug(`[generateCardPrompt] Context assembly failed: ${msg}`);
603
- try {
604
- if (options.workspaceId && cardData.title) {
605
- const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
606
- project_id: options.projectId,
607
- limit: 5,
608
- });
609
- if (memoryResult.entities?.length > 0) {
610
- memories = memoryResult.entities.map((e) => ({
611
- id: e.id,
612
- type: e.type,
613
- title: e.title,
614
- content: e.content,
615
- confidence: e.confidence,
616
- tags: e.tags || [],
617
- }));
618
- }
619
- }
620
- }
621
- catch (fallbackErr) {
622
- const fallbackMsg = fallbackErr instanceof Error
623
- ? fallbackErr.message
624
- : String(fallbackErr);
625
- console.debug(`[generateCardPrompt] Memory fallback also failed: ${fallbackMsg}`);
626
- }
1682
+ }
1683
+ }
1684
+ throw lastError || new Error("Request failed after retries");
1685
+ }
1686
+ async listWorkspaces() {
1687
+ return this.request("GET", "/workspaces");
1688
+ }
1689
+ async getWorkspaceMembers(workspaceId) {
1690
+ return this.request("GET", `/workspaces/${workspaceId}/members`);
1691
+ }
1692
+ async listProjects(workspaceId) {
1693
+ return this.request("GET", `/workspaces/${workspaceId}/projects`);
1694
+ }
1695
+ async getBoard(projectId, options) {
1696
+ const params = new URLSearchParams;
1697
+ if (options?.limit !== undefined)
1698
+ params.set("limit", String(options.limit));
1699
+ if (options?.offset !== undefined)
1700
+ params.set("offset", String(options.offset));
1701
+ if (options?.columnId)
1702
+ params.set("column_id", options.columnId);
1703
+ if (options?.summary)
1704
+ params.set("summary", "true");
1705
+ if (options?.includeArchived)
1706
+ params.set("include_archived", "true");
1707
+ if (options?.labelName)
1708
+ params.set("label_name", options.labelName);
1709
+ const query = params.toString() ? `?${params.toString()}` : "";
1710
+ return this.request("GET", `/board/${projectId}${query}`);
1711
+ }
1712
+ async createCard(projectId, data) {
1713
+ return this.request("POST", "/cards", { projectId, ...data });
1714
+ }
1715
+ async updateCard(cardId, updates) {
1716
+ return this.request("PATCH", `/cards/${cardId}`, updates);
1717
+ }
1718
+ async moveCard(cardId, columnId, position) {
1719
+ return this.request("POST", `/cards/${cardId}/move`, {
1720
+ columnId,
1721
+ position
1722
+ });
1723
+ }
1724
+ async archiveCard(cardId) {
1725
+ return this.updateCard(cardId, { archivedAt: new Date().toISOString() });
1726
+ }
1727
+ async unarchiveCard(cardId) {
1728
+ return this.updateCard(cardId, { archivedAt: null });
1729
+ }
1730
+ async deleteCard(cardId) {
1731
+ return this.request("DELETE", `/cards/${cardId}`);
1732
+ }
1733
+ async getCard(cardId) {
1734
+ return this.request("GET", `/cards/${cardId}`);
1735
+ }
1736
+ async getCardByShortId(projectId, shortId) {
1737
+ return this.request("GET", `/projects/${projectId}/cards/${shortId}`);
1738
+ }
1739
+ async searchCards(query, options) {
1740
+ const params = new URLSearchParams({ q: query });
1741
+ if (options?.projectId) {
1742
+ params.set("project_id", options.projectId);
1743
+ }
1744
+ return this.request("GET", `/search?${params.toString()}`);
1745
+ }
1746
+ async addLabelToCard(cardId, labelId) {
1747
+ return this.request("POST", `/cards/${cardId}/labels`, { labelId });
1748
+ }
1749
+ async removeLabelFromCard(cardId, labelId) {
1750
+ return this.request("DELETE", `/cards/${cardId}/labels/${labelId}`);
1751
+ }
1752
+ async addLinkToCard(sourceCardId, targetCardId, linkType) {
1753
+ return this.request("POST", `/cards/${sourceCardId}/links`, {
1754
+ targetCardId,
1755
+ linkType
1756
+ });
1757
+ }
1758
+ async removeLinkFromCard(linkId) {
1759
+ return this.request("DELETE", `/card-links/${linkId}`);
1760
+ }
1761
+ async getCardLinks(cardId) {
1762
+ return this.request("GET", `/cards/${cardId}/links`);
1763
+ }
1764
+ async createColumn(projectId, name) {
1765
+ return this.request("POST", "/columns", { projectId, name });
1766
+ }
1767
+ async updateColumn(columnId, name) {
1768
+ return this.request("PATCH", `/columns/${columnId}`, { name });
1769
+ }
1770
+ async deleteColumn(columnId) {
1771
+ return this.request("DELETE", `/columns/${columnId}`);
1772
+ }
1773
+ async createLabel(projectId, data) {
1774
+ return this.request("POST", "/labels", { projectId, ...data });
1775
+ }
1776
+ async createSubtask(cardId, title) {
1777
+ return this.request("POST", "/subtasks", { cardId, title });
1778
+ }
1779
+ async toggleSubtask(subtaskId) {
1780
+ return this.request("POST", `/subtasks/${subtaskId}/toggle`);
1781
+ }
1782
+ async deleteSubtask(subtaskId) {
1783
+ return this.request("DELETE", `/subtasks/${subtaskId}`);
1784
+ }
1785
+ async startAgentSession(cardId, data) {
1786
+ return this.request("POST", `/cards/${cardId}/agent-context`, data);
1787
+ }
1788
+ async updateAgentProgress(cardId, data) {
1789
+ return this.request("POST", `/cards/${cardId}/agent-context`, data);
1790
+ }
1791
+ async endAgentSession(cardId, data) {
1792
+ return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
1793
+ }
1794
+ async flushActivityLog(cardId, data) {
1795
+ return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
1796
+ }
1797
+ async getActivityLog(cardId, sessionId) {
1798
+ return this.request("GET", `/cards/${cardId}/agent-activity-log?sessionId=${sessionId}`);
1799
+ }
1800
+ async getAgentSession(cardId, options) {
1801
+ const params = new URLSearchParams;
1802
+ if (options?.includeEnded)
1803
+ params.set("include_ended", "true");
1804
+ const query = params.toString() ? `?${params.toString()}` : "";
1805
+ return this.request("GET", `/cards/${cardId}/agent-context${query}`);
1806
+ }
1807
+ async getAgentProfile(workspaceId, agentIdentifier) {
1808
+ const params = new URLSearchParams({
1809
+ workspace_id: workspaceId,
1810
+ agent_identifier: agentIdentifier
1811
+ });
1812
+ return this.request("GET", `/agent-profiles?${params.toString()}`);
1813
+ }
1814
+ async listAgentProfiles(workspaceId) {
1815
+ const params = new URLSearchParams({ workspace_id: workspaceId });
1816
+ return this.request("GET", `/agent-profiles?${params.toString()}`);
1817
+ }
1818
+ async refreshAgentProfiles(workspaceId) {
1819
+ return this.request("POST", "/agent-profiles/refresh", {
1820
+ workspace_id: workspaceId
1821
+ });
1822
+ }
1823
+ async createMemoryEntity(data) {
1824
+ return this.request("POST", "/memory/entities", data);
1825
+ }
1826
+ async listMemoryEntities(options) {
1827
+ const params = new URLSearchParams;
1828
+ params.set("workspace_id", options.workspace_id);
1829
+ if (options.project_id)
1830
+ params.set("project_id", options.project_id);
1831
+ if (options.type)
1832
+ params.set("type", options.type);
1833
+ if (options.scope)
1834
+ params.set("scope", options.scope);
1835
+ if (options.tags?.length)
1836
+ params.set("tags", options.tags.join(","));
1837
+ if (options.agent_identifier)
1838
+ params.set("agent_identifier", options.agent_identifier);
1839
+ if (options.min_confidence !== undefined)
1840
+ params.set("min_confidence", String(options.min_confidence));
1841
+ if (options.q)
1842
+ params.set("q", options.q);
1843
+ if (options.limit !== undefined)
1844
+ params.set("limit", String(options.limit));
1845
+ if (options.offset !== undefined)
1846
+ params.set("offset", String(options.offset));
1847
+ return this.request("GET", `/memory/entities?${params.toString()}`);
1848
+ }
1849
+ async getMemoryEntity(entityId) {
1850
+ return this.request("GET", `/memory/entities/${entityId}`);
1851
+ }
1852
+ async updateMemoryEntity(entityId, updates) {
1853
+ return this.request("PUT", `/memory/entities/${entityId}`, updates);
1854
+ }
1855
+ async deleteMemoryEntity(entityId) {
1856
+ return this.request("DELETE", `/memory/entities/${entityId}`);
1857
+ }
1858
+ async touchMemoryEntity(entityId) {
1859
+ return this.request("POST", `/memory/entities/${entityId}/touch`);
1860
+ }
1861
+ async batchTouchMemoryEntities(entityIds) {
1862
+ return this.request("POST", "/memory/entities/batch-touch", {
1863
+ entity_ids: entityIds
1864
+ });
1865
+ }
1866
+ async createMemoryRelation(data) {
1867
+ return this.request("POST", "/memory/relations", data);
1868
+ }
1869
+ async deleteMemoryRelation(relationId) {
1870
+ return this.request("DELETE", `/memory/relations/${relationId}`);
1871
+ }
1872
+ async getRelatedEntities(entityId) {
1873
+ return this.request("GET", `/memory/entities/${entityId}/related`);
1874
+ }
1875
+ async searchMemoryEntities(workspaceId, query, options) {
1876
+ const params = new URLSearchParams;
1877
+ params.set("workspace_id", workspaceId);
1878
+ params.set("q", query);
1879
+ if (options?.project_id)
1880
+ params.set("project_id", options.project_id);
1881
+ if (options?.type)
1882
+ params.set("type", options.type);
1883
+ if (options?.limit !== undefined)
1884
+ params.set("limit", String(options.limit));
1885
+ return this.request("GET", `/memory/search?${params.toString()}`);
1886
+ }
1887
+ async getVaultIndex(options) {
1888
+ const params = new URLSearchParams;
1889
+ params.set("workspace_id", options.workspace_id);
1890
+ if (options.project_id)
1891
+ params.set("project_id", options.project_id);
1892
+ if (options.type)
1893
+ params.set("type", options.type);
1894
+ if (options.limit !== undefined)
1895
+ params.set("limit", String(options.limit));
1896
+ return this.request("GET", `/memory/index?${params.toString()}`);
1897
+ }
1898
+ async getVaultIndexMarkdown(options) {
1899
+ const params = new URLSearchParams;
1900
+ params.set("workspace_id", options.workspace_id);
1901
+ if (options.project_id)
1902
+ params.set("project_id", options.project_id);
1903
+ if (options.type)
1904
+ params.set("type", options.type);
1905
+ if (options.limit !== undefined)
1906
+ params.set("limit", String(options.limit));
1907
+ return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
1908
+ accept: "text/markdown"
1909
+ });
1910
+ }
1911
+ async getMemoryStats(options) {
1912
+ const params = new URLSearchParams;
1913
+ params.set("workspace_id", options.workspace_id);
1914
+ if (options.project_id)
1915
+ params.set("project_id", options.project_id);
1916
+ return this.request("GET", `/memory/stats?${params.toString()}`);
1917
+ }
1918
+ async refreshMemoryStats(workspaceId) {
1919
+ return this.request("POST", "/memory/stats", { workspace_id: workspaceId });
1920
+ }
1921
+ async resolveLinks(options) {
1922
+ return this.request("POST", "/memory/resolve-links", options);
1923
+ }
1924
+ async listMemoryEntitiesMarkdown(options) {
1925
+ const params = new URLSearchParams;
1926
+ params.set("workspace_id", options.workspace_id);
1927
+ if (options.project_id)
1928
+ params.set("project_id", options.project_id);
1929
+ if (options.type)
1930
+ params.set("type", options.type);
1931
+ if (options.scope)
1932
+ params.set("scope", options.scope);
1933
+ if (options.tags?.length)
1934
+ params.set("tags", options.tags.join(","));
1935
+ if (options.agent_identifier)
1936
+ params.set("agent_identifier", options.agent_identifier);
1937
+ if (options.min_confidence !== undefined)
1938
+ params.set("min_confidence", String(options.min_confidence));
1939
+ if (options.q)
1940
+ params.set("q", options.q);
1941
+ if (options.limit !== undefined)
1942
+ params.set("limit", String(options.limit));
1943
+ if (options.offset !== undefined)
1944
+ params.set("offset", String(options.offset));
1945
+ return this.requestRaw("GET", `/memory/entities?${params.toString()}`, undefined, {
1946
+ accept: "text/markdown"
1947
+ });
1948
+ }
1949
+ async getMemoryEntityMarkdown(entityId) {
1950
+ return this.requestRaw("GET", `/memory/entities/${entityId}`, undefined, {
1951
+ accept: "text/markdown"
1952
+ });
1953
+ }
1954
+ async searchMemoryEntitiesMarkdown(workspaceId, query, options) {
1955
+ const params = new URLSearchParams;
1956
+ params.set("workspace_id", workspaceId);
1957
+ params.set("q", query);
1958
+ if (options?.project_id)
1959
+ params.set("project_id", options.project_id);
1960
+ if (options?.type)
1961
+ params.set("type", options.type);
1962
+ if (options?.limit !== undefined)
1963
+ params.set("limit", String(options.limit));
1964
+ return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
1965
+ accept: "text/markdown"
1966
+ });
1967
+ }
1968
+ async backfillEmbeddings(workspaceId, batchSize) {
1969
+ return this.request("POST", "/memory/backfill-embeddings", {
1970
+ workspace_id: workspaceId,
1971
+ batch_size: batchSize || 50
1972
+ });
1973
+ }
1974
+ async processNLU(data) {
1975
+ return this.request("POST", "/nlu", data);
1976
+ }
1977
+ async createPlan(projectId, data) {
1978
+ return this.request("POST", "/plans", { projectId, ...data });
1979
+ }
1980
+ async listPlans(projectId, options) {
1981
+ const params = new URLSearchParams({ projectId });
1982
+ if (options?.search)
1983
+ params.set("search", options.search);
1984
+ if (options?.status)
1985
+ params.set("status", options.status);
1986
+ return this.request("GET", `/plans?${params.toString()}`);
1987
+ }
1988
+ async getPlan(planId) {
1989
+ return this.request("GET", `/plans/${planId}`);
1990
+ }
1991
+ async getPlanByCardId(cardId) {
1992
+ return this.request("GET", `/cards/${cardId}/plan`);
1993
+ }
1994
+ async updatePlan(planId, updates) {
1995
+ return this.request("PATCH", `/plans/${planId}`, updates);
1996
+ }
1997
+ async updatePlanTask(planId, taskId, updates) {
1998
+ return this.request("PATCH", `/plans/${planId}/tasks/${taskId}`, updates);
1999
+ }
2000
+ async createWorkspace(data) {
2001
+ return this.request("POST", "/workspaces", data);
2002
+ }
2003
+ async createProject(data) {
2004
+ return this.request("POST", "/projects", data);
2005
+ }
2006
+ async sendInvitations(data) {
2007
+ return this.request("POST", "/invitations", data);
2008
+ }
2009
+ async generateApiKey(name) {
2010
+ return this.request("POST", "/api-keys", { name });
2011
+ }
2012
+ async generateCardPrompt(options) {
2013
+ const { assembleContext: assembleContext2, cacheManifest: cacheManifest2, generatePrompt: generatePrompt2 } = await loadPromptModules();
2014
+ const cardResult = await this.getCard(options.cardId);
2015
+ const cardData = cardResult.card;
2016
+ let columnData = null;
2017
+ const projectIdForBoard = options.projectId || cardData.project_id;
2018
+ if (projectIdForBoard) {
2019
+ try {
2020
+ const board = await this.getBoard(projectIdForBoard, { summary: true });
2021
+ const column = board.columns.find((col) => col.id === cardData.column_id);
2022
+ if (column) {
2023
+ columnData = { name: column.name };
627
2024
  }
628
- const result = generatePrompt({
629
- card: cardData,
630
- column: columnData,
631
- variant,
632
- contextOptions: options.contextOptions,
633
- customConstraints: options.customConstraints,
634
- memories,
635
- assembledContext: assembledContextStr,
636
- assemblyId,
2025
+ } catch {}
2026
+ }
2027
+ const variant = options.variant || "execute";
2028
+ let assembledContextStr;
2029
+ let assemblyId;
2030
+ let memories;
2031
+ try {
2032
+ if (options.workspaceId && cardData.title) {
2033
+ const cardLabels = (cardData.labels || []).map((l) => l.name);
2034
+ const taskContext = [cardData.title, cardData.description || ""].filter(Boolean).join(" ");
2035
+ const assembled = await assembleContext2({
2036
+ workspaceId: options.workspaceId,
2037
+ projectId: options.projectId,
2038
+ taskContext,
2039
+ cardLabels,
2040
+ cardId: cardData.id,
2041
+ client: this
637
2042
  });
638
- return {
639
- ...result,
640
- cardId: cardData.id,
641
- shortId: cardData.short_id,
642
- title: cardData.title,
643
- };
644
- }
2043
+ if (assembled.context) {
2044
+ assembledContextStr = assembled.context;
2045
+ assemblyId = assembled.manifest.assemblyId;
2046
+ cacheManifest2(assembled.manifest);
2047
+ }
2048
+ }
2049
+ } catch (err) {
2050
+ const msg = err instanceof Error ? err.message : String(err);
2051
+ console.debug(`[generateCardPrompt] Context assembly failed: ${msg}`);
2052
+ try {
2053
+ if (options.workspaceId && cardData.title) {
2054
+ const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
2055
+ project_id: options.projectId,
2056
+ limit: 5
2057
+ });
2058
+ if (memoryResult.entities?.length > 0) {
2059
+ memories = memoryResult.entities.map((e) => ({
2060
+ id: e.id,
2061
+ type: e.type,
2062
+ title: e.title,
2063
+ content: e.content,
2064
+ confidence: e.confidence,
2065
+ tags: e.tags || []
2066
+ }));
2067
+ }
2068
+ }
2069
+ } catch (fallbackErr) {
2070
+ const fallbackMsg = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
2071
+ console.debug(`[generateCardPrompt] Memory fallback also failed: ${fallbackMsg}`);
2072
+ }
2073
+ }
2074
+ const result = generatePrompt2({
2075
+ card: cardData,
2076
+ column: columnData,
2077
+ variant,
2078
+ contextOptions: options.contextOptions,
2079
+ customConstraints: options.customConstraints,
2080
+ memories,
2081
+ assembledContext: assembledContextStr,
2082
+ assemblyId
2083
+ });
2084
+ return {
2085
+ ...result,
2086
+ cardId: cardData.id,
2087
+ shortId: cardData.short_id,
2088
+ title: cardData.title
2089
+ };
2090
+ }
645
2091
  }
646
- // Cached dynamic imports for context-assembly and prompt-builder
647
- let _promptModules = null;
2092
+ var _promptModules = null;
648
2093
  async function loadPromptModules() {
649
- if (!_promptModules) {
650
- const [ca, pb] = await Promise.all([
651
- import("./context-assembly.js"),
652
- import("./prompt-builder.js"),
653
- ]);
654
- _promptModules = {
655
- assembleContext: ca.assembleContext,
656
- cacheManifest: ca.cacheManifest,
657
- generatePrompt: pb.generatePrompt,
658
- };
659
- }
660
- return _promptModules;
661
- }
662
- // Singleton instance
663
- let client = null;
664
- export function getClient() {
665
- if (!client) {
666
- client = new HarmonyApiClient();
667
- }
668
- return client;
669
- }
670
- export function resetClient() {
671
- client = null;
2094
+ if (!_promptModules) {
2095
+ const [ca, pb] = await Promise.all([
2096
+ Promise.resolve().then(() => (init_context_assembly(), exports_context_assembly)),
2097
+ Promise.resolve().then(() => (init_prompt_builder(), exports_prompt_builder))
2098
+ ]);
2099
+ _promptModules = {
2100
+ assembleContext: ca.assembleContext,
2101
+ cacheManifest: ca.cacheManifest,
2102
+ generatePrompt: pb.generatePrompt
2103
+ };
2104
+ }
2105
+ return _promptModules;
672
2106
  }
2107
+ var client2 = null;
2108
+ function getClient() {
2109
+ if (!client2) {
2110
+ client2 = new HarmonyApiClient;
2111
+ }
2112
+ return client2;
2113
+ }
2114
+ function resetClient() {
2115
+ client2 = null;
2116
+ }
2117
+ export {
2118
+ signupUser,
2119
+ resetClient,
2120
+ requestWithBearer,
2121
+ getClient,
2122
+ HarmonyApiClient
2123
+ };