@gethmy/mcp 2.1.3 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -26,6 +26,7 @@ var __export = (target, all) => {
26
26
  set: (newValue) => all[name] = () => newValue
27
27
  });
28
28
  };
29
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
30
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
31
 
31
32
  // ../../node_modules/commander/lib/error.js
@@ -2121,204 +2122,781 @@ var require_commander = __commonJS((exports) => {
2121
2122
  exports.InvalidOptionArgumentError = InvalidArgumentError;
2122
2123
  });
2123
2124
 
2124
- // ../../node_modules/ajv/dist/compile/codegen/code.js
2125
- var require_code = __commonJS((exports) => {
2126
- Object.defineProperty(exports, "__esModule", { value: true });
2127
- exports.regexpCode = exports.getEsmExportName = exports.getProperty = exports.safeStringify = exports.stringify = exports.strConcat = exports.addCodeArg = exports.str = exports._ = exports.nil = exports._Code = exports.Name = exports.IDENTIFIER = exports._CodeOrName = undefined;
2125
+ // ../memory/src/schema.ts
2126
+ var init_schema = () => {};
2128
2127
 
2129
- class _CodeOrName {
2130
- }
2131
- exports._CodeOrName = _CodeOrName;
2132
- exports.IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i;
2128
+ // ../memory/src/constraints.ts
2129
+ var init_constraints = __esm(() => {
2130
+ init_schema();
2131
+ });
2132
+ // ../memory/src/client.ts
2133
+ var init_client = __esm(() => {
2134
+ init_constraints();
2135
+ });
2133
2136
 
2134
- class Name extends _CodeOrName {
2135
- constructor(s) {
2136
- super();
2137
- if (!exports.IDENTIFIER.test(s))
2138
- throw new Error("CodeGen: name must be a valid identifier");
2139
- this.str = s;
2140
- }
2141
- toString() {
2142
- return this.str;
2143
- }
2144
- emptyStr() {
2145
- return false;
2146
- }
2147
- get names() {
2148
- return { [this.str]: 1 };
2137
+ // ../memory/src/graph-walk.ts
2138
+ async function discoverRelatedContext(client, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
2139
+ const visited = new Set;
2140
+ const collectedEntities = [];
2141
+ const collectedRelations = [];
2142
+ let truncated = false;
2143
+ const queue = startIds.map((id) => [id, 0]);
2144
+ for (const id of startIds) {
2145
+ visited.add(id);
2146
+ }
2147
+ while (queue.length > 0) {
2148
+ const [entityId, depth] = queue.shift();
2149
+ if (collectedEntities.length >= maxEntities) {
2150
+ truncated = true;
2151
+ break;
2149
2152
  }
2153
+ if (depth > maxDepth)
2154
+ continue;
2155
+ try {
2156
+ const entityResult = await client.getMemoryEntity(entityId);
2157
+ const entity = entityResult.entity;
2158
+ if (entity) {
2159
+ collectedEntities.push({
2160
+ id: entity.id,
2161
+ type: entity.type,
2162
+ title: entity.title,
2163
+ confidence: entity.confidence ?? 1,
2164
+ memory_tier: entity.memory_tier || "reference"
2165
+ });
2166
+ }
2167
+ if (depth >= maxDepth)
2168
+ continue;
2169
+ const related = await client.getRelatedEntities(entityId);
2170
+ for (const raw of related.outgoing || []) {
2171
+ const rel = raw;
2172
+ const relConfidence = rel.confidence ?? 1;
2173
+ if (relConfidence < minConfidence)
2174
+ continue;
2175
+ const target = rel.target;
2176
+ const targetId = target?.id ?? rel.target_id;
2177
+ if (targetId && !visited.has(targetId)) {
2178
+ visited.add(targetId);
2179
+ queue.push([targetId, depth + 1]);
2180
+ collectedRelations.push({
2181
+ id: rel.id,
2182
+ source_id: entityId,
2183
+ target_id: targetId,
2184
+ relation_type: rel.relation_type,
2185
+ confidence: relConfidence
2186
+ });
2187
+ }
2188
+ }
2189
+ for (const raw of related.incoming || []) {
2190
+ const rel = raw;
2191
+ const relConfidence = rel.confidence ?? 1;
2192
+ if (relConfidence < minConfidence)
2193
+ continue;
2194
+ const source = rel.source;
2195
+ const sourceId = source?.id ?? rel.source_id;
2196
+ if (sourceId && !visited.has(sourceId)) {
2197
+ visited.add(sourceId);
2198
+ queue.push([sourceId, depth + 1]);
2199
+ collectedRelations.push({
2200
+ id: rel.id,
2201
+ source_id: sourceId,
2202
+ target_id: entityId,
2203
+ relation_type: rel.relation_type,
2204
+ confidence: relConfidence
2205
+ });
2206
+ }
2207
+ }
2208
+ } catch {}
2150
2209
  }
2151
- exports.Name = Name;
2210
+ return {
2211
+ entities: collectedEntities,
2212
+ relations: collectedRelations,
2213
+ depth: maxDepth,
2214
+ truncated
2215
+ };
2216
+ }
2152
2217
 
2153
- class _Code extends _CodeOrName {
2154
- constructor(code) {
2155
- super();
2156
- this._items = typeof code === "string" ? [code] : code;
2157
- }
2158
- toString() {
2159
- return this.str;
2160
- }
2161
- emptyStr() {
2162
- if (this._items.length > 1)
2163
- return false;
2164
- const item = this._items[0];
2165
- return item === "" || item === '""';
2166
- }
2167
- get str() {
2168
- var _a2;
2169
- return (_a2 = this._str) !== null && _a2 !== undefined ? _a2 : this._str = this._items.reduce((s, c) => `${s}${c}`, "");
2170
- }
2171
- get names() {
2172
- var _a2;
2173
- return (_a2 = this._names) !== null && _a2 !== undefined ? _a2 : this._names = this._items.reduce((names, c) => {
2174
- if (c instanceof Name)
2175
- names[c.str] = (names[c.str] || 0) + 1;
2176
- return names;
2177
- }, {});
2218
+ // ../memory/src/lifecycle.ts
2219
+ function computeDecayScore(tier, lastAccessedAt, accessCount) {
2220
+ const halfLife = DECAY_HALF_LIVES[tier];
2221
+ const now = Date.now();
2222
+ let daysSinceAccess = 0;
2223
+ if (lastAccessedAt) {
2224
+ daysSinceAccess = (now - new Date(lastAccessedAt).getTime()) / (1000 * 60 * 60 * 24);
2225
+ }
2226
+ const timeDecay = 0.5 ** (daysSinceAccess / halfLife);
2227
+ const accessBonus = Math.log10(accessCount + 1) * 0.1;
2228
+ const score = Math.min(timeDecay + accessBonus, 1);
2229
+ return { score, daysSinceAccess, halfLife, accessBonus };
2230
+ }
2231
+ function checkPromotion(currentTier, accessCount, confidence, createdAt) {
2232
+ const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
2233
+ const base = {
2234
+ eligible: false,
2235
+ targetTier: null,
2236
+ reason: null,
2237
+ currentTier,
2238
+ accessCount,
2239
+ confidence,
2240
+ ageDays
2241
+ };
2242
+ if (currentTier === "draft") {
2243
+ const rules = PROMOTION_RULES.draftToEpisode;
2244
+ if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
2245
+ return {
2246
+ ...base,
2247
+ eligible: true,
2248
+ targetTier: "episode",
2249
+ reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
2250
+ };
2178
2251
  }
2179
2252
  }
2180
- exports._Code = _Code;
2181
- exports.nil = new _Code("");
2182
- function _(strs, ...args) {
2183
- const code = [strs[0]];
2184
- let i = 0;
2185
- while (i < args.length) {
2186
- addCodeArg(code, args[i]);
2187
- code.push(strs[++i]);
2253
+ if (currentTier === "episode") {
2254
+ const rules = PROMOTION_RULES.episodeToReference;
2255
+ if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
2256
+ return {
2257
+ ...base,
2258
+ eligible: true,
2259
+ targetTier: "reference",
2260
+ reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
2261
+ };
2188
2262
  }
2189
- return new _Code(code);
2190
2263
  }
2191
- exports._ = _;
2192
- var plus = new _Code("+");
2193
- function str(strs, ...args) {
2194
- const expr = [safeStringify(strs[0])];
2195
- let i = 0;
2196
- while (i < args.length) {
2197
- expr.push(plus);
2198
- addCodeArg(expr, args[i]);
2199
- expr.push(plus, safeStringify(strs[++i]));
2264
+ return base;
2265
+ }
2266
+ function evaluateLifecycle(entity) {
2267
+ const decay = computeDecayScore(entity.memory_tier, entity.last_accessed_at, entity.access_count);
2268
+ const promotion = checkPromotion(entity.memory_tier, entity.access_count, entity.confidence, entity.created_at);
2269
+ const shouldArchive = entity.confidence < ARCHIVE_THRESHOLD;
2270
+ const archiveReason = shouldArchive ? `Confidence ${entity.confidence} below threshold ${ARCHIVE_THRESHOLD}` : undefined;
2271
+ const shouldFlagForReview = decay.daysSinceAccess >= STALE_DAYS && entity.access_count < STALE_MIN_ACCESS;
2272
+ const reviewReason = shouldFlagForReview ? `Not accessed in ${Math.round(decay.daysSinceAccess)} days with only ${entity.access_count} accesses` : undefined;
2273
+ return {
2274
+ decay,
2275
+ promotion,
2276
+ shouldArchive,
2277
+ shouldFlagForReview,
2278
+ archiveReason,
2279
+ reviewReason
2280
+ };
2281
+ }
2282
+ var DECAY_HALF_LIVES, PROMOTION_RULES, ARCHIVE_THRESHOLD = 0.3, STALE_DAYS = 90, STALE_MIN_ACCESS = 3;
2283
+ var init_lifecycle = __esm(() => {
2284
+ DECAY_HALF_LIVES = {
2285
+ draft: 7,
2286
+ episode: 30,
2287
+ reference: 180
2288
+ };
2289
+ PROMOTION_RULES = {
2290
+ draftToEpisode: {
2291
+ minAccessCount: 5,
2292
+ minConfidence: 0.8,
2293
+ minAgeDays: 1
2294
+ },
2295
+ episodeToReference: {
2296
+ minAccessCount: 10,
2297
+ minConfidence: 0.9,
2298
+ minAgeDays: 7
2200
2299
  }
2201
- optimize(expr);
2202
- return new _Code(expr);
2300
+ };
2301
+ });
2302
+
2303
+ // ../memory/src/sync-storage.ts
2304
+ function parseSyncMarkdown(markdown) {
2305
+ const trimmed = markdown.trim();
2306
+ let frontmatter = {
2307
+ type: "context",
2308
+ scope: "project",
2309
+ tier: "reference",
2310
+ confidence: 1,
2311
+ tags: []
2312
+ };
2313
+ let body = trimmed;
2314
+ const fmMatch = trimmed.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
2315
+ if (fmMatch) {
2316
+ frontmatter = parseSyncYamlFrontmatter(fmMatch[1]);
2317
+ body = fmMatch[2].trim();
2203
2318
  }
2204
- exports.str = str;
2205
- function addCodeArg(code, arg) {
2206
- if (arg instanceof _Code)
2207
- code.push(...arg._items);
2208
- else if (arg instanceof Name)
2209
- code.push(arg);
2210
- else
2211
- code.push(interpolate(arg));
2319
+ let title = "";
2320
+ const titleMatch = body.match(/^#\s+(.+)/m);
2321
+ if (titleMatch) {
2322
+ title = titleMatch[1].trim();
2323
+ body = body.replace(/^#\s+.+\n?/, "").trim();
2212
2324
  }
2213
- exports.addCodeArg = addCodeArg;
2214
- function optimize(expr) {
2215
- let i = 1;
2216
- while (i < expr.length - 1) {
2217
- if (expr[i] === plus) {
2218
- const res = mergeExprItems(expr[i - 1], expr[i + 1]);
2219
- if (res !== undefined) {
2220
- expr.splice(i - 1, 3, res);
2221
- continue;
2222
- }
2223
- expr[i++] = "+";
2224
- }
2225
- i++;
2226
- }
2325
+ return { frontmatter, title, content: body };
2326
+ }
2327
+ function serializeSyncMarkdown(entity) {
2328
+ const lines = ["---"];
2329
+ lines.push(`id: ${entity.id}`);
2330
+ lines.push(`workspace_id: ${entity.workspace_id}`);
2331
+ if (entity.project_id) {
2332
+ lines.push(`project_id: ${entity.project_id}`);
2227
2333
  }
2228
- function mergeExprItems(a, b) {
2229
- if (b === '""')
2230
- return a;
2231
- if (a === '""')
2232
- return b;
2233
- if (typeof a == "string") {
2234
- if (b instanceof Name || a[a.length - 1] !== '"')
2235
- return;
2236
- if (typeof b != "string")
2237
- return `${a.slice(0, -1)}${b}"`;
2238
- if (b[0] === '"')
2239
- return a.slice(0, -1) + b.slice(1);
2240
- return;
2241
- }
2242
- if (typeof b == "string" && b[0] === '"' && !(a instanceof Name))
2243
- return `"${a}${b.slice(1)}`;
2244
- return;
2334
+ lines.push(`type: ${entity.type}`);
2335
+ lines.push(`scope: ${entity.scope}`);
2336
+ lines.push(`tier: ${entity.memory_tier || "reference"}`);
2337
+ lines.push(`confidence: ${entity.confidence}`);
2338
+ if (entity.tags.length > 0) {
2339
+ lines.push(`tags: [${entity.tags.join(", ")}]`);
2340
+ } else {
2341
+ lines.push("tags: []");
2245
2342
  }
2246
- function strConcat(c1, c2) {
2247
- return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str`${c1}${c2}`;
2343
+ if (entity.agent_identifier) {
2344
+ lines.push(`agent: ${entity.agent_identifier}`);
2248
2345
  }
2249
- exports.strConcat = strConcat;
2250
- function interpolate(x) {
2251
- return typeof x == "number" || typeof x == "boolean" || x === null ? x : safeStringify(Array.isArray(x) ? x.join(",") : x);
2346
+ lines.push(`created_at: ${entity.created_at}`);
2347
+ lines.push(`updated_at: ${entity.updated_at}`);
2348
+ lines.push("---");
2349
+ lines.push("");
2350
+ lines.push(`# ${entity.title}`);
2351
+ lines.push("");
2352
+ lines.push(entity.content);
2353
+ return lines.join(`
2354
+ `);
2355
+ }
2356
+ function parseSyncYamlFrontmatter(yaml) {
2357
+ const result = {
2358
+ type: "context",
2359
+ scope: "project",
2360
+ tier: "reference",
2361
+ confidence: 1,
2362
+ tags: []
2363
+ };
2364
+ for (const line of yaml.split(`
2365
+ `)) {
2366
+ const colonIndex = line.indexOf(":");
2367
+ if (colonIndex === -1)
2368
+ continue;
2369
+ const key = line.slice(0, colonIndex).trim();
2370
+ const value = line.slice(colonIndex + 1).trim();
2371
+ switch (key) {
2372
+ case "id":
2373
+ result.id = value;
2374
+ break;
2375
+ case "workspace_id":
2376
+ result.workspace_id = value;
2377
+ break;
2378
+ case "project_id":
2379
+ result.project_id = value;
2380
+ break;
2381
+ case "type":
2382
+ result.type = value;
2383
+ break;
2384
+ case "scope":
2385
+ result.scope = value;
2386
+ break;
2387
+ case "tier":
2388
+ result.tier = value;
2389
+ break;
2390
+ case "confidence":
2391
+ result.confidence = parseFloat(value) || 1;
2392
+ break;
2393
+ case "tags":
2394
+ result.tags = parseYamlArray(value);
2395
+ break;
2396
+ case "related":
2397
+ result.related = parseYamlArray(value);
2398
+ break;
2399
+ case "agent":
2400
+ result.agent = value;
2401
+ break;
2402
+ case "created_at":
2403
+ result.created_at = value;
2404
+ break;
2405
+ case "updated_at":
2406
+ result.updated_at = value;
2407
+ break;
2408
+ }
2252
2409
  }
2253
- function stringify(x) {
2254
- return new _Code(safeStringify(x));
2410
+ return result;
2411
+ }
2412
+ function parseYamlArray(value) {
2413
+ const match = value.match(/^\[(.*)]\s*$/);
2414
+ if (!match)
2415
+ return [];
2416
+ return match[1].split(",").map((s) => s.trim()).filter(Boolean);
2417
+ }
2418
+
2419
+ // ../memory/src/sync.ts
2420
+ import { createHash } from "node:crypto";
2421
+ import {
2422
+ existsSync as existsSync2,
2423
+ mkdirSync as mkdirSync2,
2424
+ readdirSync,
2425
+ readFileSync as readFileSync2,
2426
+ rmSync,
2427
+ writeFileSync as writeFileSync2
2428
+ } from "node:fs";
2429
+ import { join as join2, relative, sep } from "node:path";
2430
+ function computeFileHash(content) {
2431
+ return `sha256:${createHash("sha256").update(content).digest("hex")}`;
2432
+ }
2433
+ function slugifyTitle(title) {
2434
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
2435
+ }
2436
+ function entityToFilename(entity) {
2437
+ const slug = slugifyTitle(entity.title);
2438
+ const shortId = entity.id.slice(0, 8);
2439
+ return `${entity.type}--${slug}--${shortId}.md`;
2440
+ }
2441
+ function entityToDirectoryPath(entity, memoryDir) {
2442
+ const wsDir = join2(memoryDir, entity.workspace_id);
2443
+ if (entity.scope === "private") {
2444
+ return join2(wsDir, "_private");
2255
2445
  }
2256
- exports.stringify = stringify;
2257
- function safeStringify(x) {
2258
- return JSON.stringify(x).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
2446
+ if (entity.scope === "workspace" || !entity.project_id) {
2447
+ return join2(wsDir, "_workspace");
2259
2448
  }
2260
- exports.safeStringify = safeStringify;
2261
- function getProperty(key) {
2262
- return typeof key == "string" && exports.IDENTIFIER.test(key) ? new _Code(`.${key}`) : _`[${key}]`;
2449
+ return join2(wsDir, entity.project_id);
2450
+ }
2451
+ function emptySyncState() {
2452
+ return { version: 1, lastPullAt: null, entities: {} };
2453
+ }
2454
+ function loadSyncState(memoryDir) {
2455
+ const statePath = join2(memoryDir, ".sync-state.json");
2456
+ if (!existsSync2(statePath))
2457
+ return emptySyncState();
2458
+ try {
2459
+ return JSON.parse(readFileSync2(statePath, "utf-8"));
2460
+ } catch {
2461
+ return emptySyncState();
2263
2462
  }
2264
- exports.getProperty = getProperty;
2265
- function getEsmExportName(key) {
2266
- if (typeof key == "string" && exports.IDENTIFIER.test(key)) {
2267
- return new _Code(`${key}`);
2268
- }
2269
- throw new Error(`CodeGen: invalid export name: ${key}, use explicit $id name mapping`);
2463
+ }
2464
+ function saveSyncState(memoryDir, state) {
2465
+ if (!existsSync2(memoryDir)) {
2466
+ mkdirSync2(memoryDir, { recursive: true });
2270
2467
  }
2271
- exports.getEsmExportName = getEsmExportName;
2272
- function regexpCode(rx) {
2273
- return new _Code(rx.toString());
2468
+ writeFileSync2(join2(memoryDir, ".sync-state.json"), JSON.stringify(state, null, 2));
2469
+ }
2470
+ function writeEntityFile(entity, memoryDir) {
2471
+ const dir = entityToDirectoryPath(entity, memoryDir);
2472
+ if (!existsSync2(dir))
2473
+ mkdirSync2(dir, { recursive: true });
2474
+ const filename = entityToFilename(entity);
2475
+ const filePath = join2(dir, filename);
2476
+ const markdown = serializeSyncMarkdown(entity);
2477
+ writeFileSync2(filePath, markdown);
2478
+ return relative(memoryDir, filePath);
2479
+ }
2480
+ function deleteEntityFile(relPath, memoryDir) {
2481
+ const absPath = join2(memoryDir, relPath);
2482
+ if (existsSync2(absPath)) {
2483
+ rmSync(absPath);
2274
2484
  }
2275
- exports.regexpCode = regexpCode;
2276
- });
2277
-
2278
- // ../../node_modules/ajv/dist/compile/codegen/scope.js
2279
- var require_scope = __commonJS((exports) => {
2280
- Object.defineProperty(exports, "__esModule", { value: true });
2281
- exports.ValueScope = exports.ValueScopeName = exports.Scope = exports.varKinds = exports.UsedValueState = undefined;
2282
- var code_1 = require_code();
2283
-
2284
- class ValueError extends Error {
2285
- constructor(name) {
2286
- super(`CodeGen: "code" for ${name} not defined`);
2287
- this.value = name.value;
2485
+ }
2486
+ function findMarkdownFiles(dir) {
2487
+ const results = [];
2488
+ if (!existsSync2(dir))
2489
+ return results;
2490
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
2491
+ const fullPath = join2(dir, entry.name);
2492
+ if (entry.isDirectory()) {
2493
+ results.push(...findMarkdownFiles(fullPath));
2494
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
2495
+ results.push(fullPath);
2288
2496
  }
2289
2497
  }
2290
- var UsedValueState;
2291
- (function(UsedValueState2) {
2292
- UsedValueState2[UsedValueState2["Started"] = 0] = "Started";
2293
- UsedValueState2[UsedValueState2["Completed"] = 1] = "Completed";
2294
- })(UsedValueState || (exports.UsedValueState = UsedValueState = {}));
2295
- exports.varKinds = {
2296
- const: new code_1.Name("const"),
2297
- let: new code_1.Name("let"),
2298
- var: new code_1.Name("var")
2498
+ return results;
2499
+ }
2500
+ async function syncPull(client, config, workspaceId, projectId) {
2501
+ const { memoryDir } = config;
2502
+ const state = loadSyncState(memoryDir);
2503
+ const result = {
2504
+ pulled: 0,
2505
+ pushed: 0,
2506
+ deleted: 0,
2507
+ conflicts: 0,
2508
+ errors: []
2299
2509
  };
2300
-
2301
- class Scope {
2302
- constructor({ prefixes, parent } = {}) {
2303
- this._names = {};
2304
- this._prefixes = prefixes;
2305
- this._parent = parent;
2306
- }
2307
- toName(nameOrPrefix) {
2308
- return nameOrPrefix instanceof code_1.Name ? nameOrPrefix : this.name(nameOrPrefix);
2309
- }
2310
- name(prefix) {
2311
- return new code_1.Name(this._newName(prefix));
2312
- }
2313
- _newName(prefix) {
2314
- const ng = this._names[prefix] || this._nameGroup(prefix);
2315
- return `${prefix}${ng.index++}`;
2316
- }
2317
- _nameGroup(prefix) {
2318
- var _a2, _b;
2319
- if (((_b = (_a2 = this._parent) === null || _a2 === undefined ? undefined : _a2._prefixes) === null || _b === undefined ? undefined : _b.has(prefix)) || this._prefixes && !this._prefixes.has(prefix)) {
2320
- throw new Error(`CodeGen: prefix "${prefix}" is not allowed in this scope`);
2321
- }
2510
+ const allEntities = [];
2511
+ let offset = 0;
2512
+ const batchSize = 100;
2513
+ while (true) {
2514
+ try {
2515
+ const resp = await client.listMemoryEntities({
2516
+ workspace_id: workspaceId,
2517
+ project_id: projectId,
2518
+ limit: batchSize,
2519
+ offset
2520
+ });
2521
+ const batch = resp.entities;
2522
+ allEntities.push(...batch);
2523
+ if (batch.length < batchSize)
2524
+ break;
2525
+ offset += batchSize;
2526
+ } catch (err) {
2527
+ result.errors.push(`Failed to fetch entities: ${err}`);
2528
+ return result;
2529
+ }
2530
+ }
2531
+ const remoteIds = new Set(allEntities.map((e) => e.id));
2532
+ for (const entity of allEntities) {
2533
+ const existing = state.entities[entity.id];
2534
+ const markdown = serializeSyncMarkdown(entity);
2535
+ const hash = computeFileHash(markdown);
2536
+ if (!existing) {
2537
+ const relPath = writeEntityFile(entity, memoryDir);
2538
+ state.entities[entity.id] = {
2539
+ filePath: relPath,
2540
+ remoteUpdatedAt: entity.updated_at,
2541
+ lastSyncedHash: hash
2542
+ };
2543
+ result.pulled++;
2544
+ } else {
2545
+ const remoteChanged = entity.updated_at > existing.remoteUpdatedAt;
2546
+ if (!remoteChanged)
2547
+ continue;
2548
+ const absPath = join2(memoryDir, existing.filePath);
2549
+ let localChanged = false;
2550
+ if (existsSync2(absPath)) {
2551
+ const localContent = readFileSync2(absPath, "utf-8");
2552
+ const localHash = computeFileHash(localContent);
2553
+ localChanged = localHash !== existing.lastSyncedHash;
2554
+ }
2555
+ if (localChanged) {
2556
+ result.conflicts++;
2557
+ }
2558
+ const newRelPath = writeEntityFile(entity, memoryDir);
2559
+ if (existing.filePath !== newRelPath) {
2560
+ deleteEntityFile(existing.filePath, memoryDir);
2561
+ }
2562
+ state.entities[entity.id] = {
2563
+ filePath: newRelPath,
2564
+ remoteUpdatedAt: entity.updated_at,
2565
+ lastSyncedHash: hash
2566
+ };
2567
+ result.pulled++;
2568
+ }
2569
+ }
2570
+ for (const [entityId, entry] of Object.entries(state.entities)) {
2571
+ if (!remoteIds.has(entityId)) {
2572
+ deleteEntityFile(entry.filePath, memoryDir);
2573
+ delete state.entities[entityId];
2574
+ result.deleted++;
2575
+ }
2576
+ }
2577
+ state.lastPullAt = new Date().toISOString();
2578
+ saveSyncState(memoryDir, state);
2579
+ return result;
2580
+ }
2581
+ async function syncPush(client, config, workspaceId) {
2582
+ const { memoryDir } = config;
2583
+ const state = loadSyncState(memoryDir);
2584
+ const result = {
2585
+ pulled: 0,
2586
+ pushed: 0,
2587
+ deleted: 0,
2588
+ conflicts: 0,
2589
+ errors: []
2590
+ };
2591
+ const mdFiles = findMarkdownFiles(memoryDir);
2592
+ for (const absPath of mdFiles) {
2593
+ const content = readFileSync2(absPath, "utf-8");
2594
+ const hash = computeFileHash(content);
2595
+ const parsed = parseSyncMarkdown(content);
2596
+ if (parsed.frontmatter.id) {
2597
+ const entityId = parsed.frontmatter.id;
2598
+ const existing = state.entities[entityId];
2599
+ if (existing && hash === existing.lastSyncedHash)
2600
+ continue;
2601
+ try {
2602
+ const resp = await client.updateMemoryEntity(entityId, {
2603
+ title: parsed.title || "Untitled",
2604
+ content: parsed.content,
2605
+ type: parsed.frontmatter.type,
2606
+ scope: parsed.frontmatter.scope,
2607
+ confidence: parsed.frontmatter.confidence,
2608
+ tags: parsed.frontmatter.tags
2609
+ });
2610
+ const updated = resp.entity;
2611
+ const newMarkdown = serializeSyncMarkdown(updated);
2612
+ writeFileSync2(absPath, newMarkdown);
2613
+ const relPath = relative(memoryDir, absPath);
2614
+ state.entities[entityId] = {
2615
+ filePath: relPath,
2616
+ remoteUpdatedAt: updated.updated_at,
2617
+ lastSyncedHash: computeFileHash(newMarkdown)
2618
+ };
2619
+ result.pushed++;
2620
+ } catch (err) {
2621
+ result.errors.push(`Failed to update ${entityId}: ${err}`);
2622
+ }
2623
+ } else {
2624
+ const relPath = relative(memoryDir, absPath);
2625
+ const parts = relPath.split(sep);
2626
+ const fileWorkspaceId = parts.length >= 2 ? parts[0] : workspaceId;
2627
+ let fileProjectId;
2628
+ if (parts.length >= 3) {
2629
+ const scopeDir = parts[1];
2630
+ if (scopeDir !== "_workspace" && scopeDir !== "_private") {
2631
+ fileProjectId = scopeDir;
2632
+ }
2633
+ }
2634
+ let scope = parsed.frontmatter.scope || "project";
2635
+ if (parts.length >= 3) {
2636
+ const scopeDir = parts[1];
2637
+ if (scopeDir === "_private")
2638
+ scope = "private";
2639
+ else if (scopeDir === "_workspace")
2640
+ scope = "workspace";
2641
+ }
2642
+ try {
2643
+ const resp = await client.createMemoryEntity({
2644
+ workspace_id: fileWorkspaceId,
2645
+ project_id: fileProjectId,
2646
+ type: parsed.frontmatter.type,
2647
+ scope,
2648
+ title: parsed.title || "Untitled",
2649
+ content: parsed.content,
2650
+ confidence: parsed.frontmatter.confidence,
2651
+ tags: parsed.frontmatter.tags,
2652
+ agent_identifier: parsed.frontmatter.agent
2653
+ });
2654
+ const created = resp.entity;
2655
+ const dir = entityToDirectoryPath(created, memoryDir);
2656
+ if (!existsSync2(dir))
2657
+ mkdirSync2(dir, { recursive: true });
2658
+ const newFilename = entityToFilename(created);
2659
+ const newAbsPath = join2(dir, newFilename);
2660
+ const newMarkdown = serializeSyncMarkdown(created);
2661
+ writeFileSync2(newAbsPath, newMarkdown);
2662
+ if (absPath !== newAbsPath && existsSync2(absPath)) {
2663
+ rmSync(absPath);
2664
+ }
2665
+ const newRelPath = relative(memoryDir, newAbsPath);
2666
+ state.entities[created.id] = {
2667
+ filePath: newRelPath,
2668
+ remoteUpdatedAt: created.updated_at,
2669
+ lastSyncedHash: computeFileHash(newMarkdown)
2670
+ };
2671
+ result.pushed++;
2672
+ } catch (err) {
2673
+ result.errors.push(`Failed to create entity from ${relPath}: ${err}`);
2674
+ }
2675
+ }
2676
+ }
2677
+ saveSyncState(memoryDir, state);
2678
+ return result;
2679
+ }
2680
+ async function syncFull(client, config, workspaceId, projectId) {
2681
+ const pullResult = await syncPull(client, config, workspaceId, projectId);
2682
+ const pushResult = await syncPush(client, config, workspaceId);
2683
+ return {
2684
+ pulled: pullResult.pulled,
2685
+ pushed: pushResult.pushed,
2686
+ deleted: pullResult.deleted,
2687
+ conflicts: pullResult.conflicts,
2688
+ errors: [...pullResult.errors, ...pushResult.errors]
2689
+ };
2690
+ }
2691
+ var init_sync = () => {};
2692
+
2693
+ // ../memory/src/index.ts
2694
+ var init_src = __esm(() => {
2695
+ init_client();
2696
+ init_constraints();
2697
+ init_lifecycle();
2698
+ init_schema();
2699
+ init_sync();
2700
+ });
2701
+
2702
+ // ../../node_modules/ajv/dist/compile/codegen/code.js
2703
+ var require_code = __commonJS((exports) => {
2704
+ Object.defineProperty(exports, "__esModule", { value: true });
2705
+ exports.regexpCode = exports.getEsmExportName = exports.getProperty = exports.safeStringify = exports.stringify = exports.strConcat = exports.addCodeArg = exports.str = exports._ = exports.nil = exports._Code = exports.Name = exports.IDENTIFIER = exports._CodeOrName = undefined;
2706
+
2707
+ class _CodeOrName {
2708
+ }
2709
+ exports._CodeOrName = _CodeOrName;
2710
+ exports.IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i;
2711
+
2712
+ class Name extends _CodeOrName {
2713
+ constructor(s) {
2714
+ super();
2715
+ if (!exports.IDENTIFIER.test(s))
2716
+ throw new Error("CodeGen: name must be a valid identifier");
2717
+ this.str = s;
2718
+ }
2719
+ toString() {
2720
+ return this.str;
2721
+ }
2722
+ emptyStr() {
2723
+ return false;
2724
+ }
2725
+ get names() {
2726
+ return { [this.str]: 1 };
2727
+ }
2728
+ }
2729
+ exports.Name = Name;
2730
+
2731
+ class _Code extends _CodeOrName {
2732
+ constructor(code) {
2733
+ super();
2734
+ this._items = typeof code === "string" ? [code] : code;
2735
+ }
2736
+ toString() {
2737
+ return this.str;
2738
+ }
2739
+ emptyStr() {
2740
+ if (this._items.length > 1)
2741
+ return false;
2742
+ const item = this._items[0];
2743
+ return item === "" || item === '""';
2744
+ }
2745
+ get str() {
2746
+ var _a2;
2747
+ return (_a2 = this._str) !== null && _a2 !== undefined ? _a2 : this._str = this._items.reduce((s, c) => `${s}${c}`, "");
2748
+ }
2749
+ get names() {
2750
+ var _a2;
2751
+ return (_a2 = this._names) !== null && _a2 !== undefined ? _a2 : this._names = this._items.reduce((names, c) => {
2752
+ if (c instanceof Name)
2753
+ names[c.str] = (names[c.str] || 0) + 1;
2754
+ return names;
2755
+ }, {});
2756
+ }
2757
+ }
2758
+ exports._Code = _Code;
2759
+ exports.nil = new _Code("");
2760
+ function _(strs, ...args) {
2761
+ const code = [strs[0]];
2762
+ let i = 0;
2763
+ while (i < args.length) {
2764
+ addCodeArg(code, args[i]);
2765
+ code.push(strs[++i]);
2766
+ }
2767
+ return new _Code(code);
2768
+ }
2769
+ exports._ = _;
2770
+ var plus = new _Code("+");
2771
+ function str(strs, ...args) {
2772
+ const expr = [safeStringify(strs[0])];
2773
+ let i = 0;
2774
+ while (i < args.length) {
2775
+ expr.push(plus);
2776
+ addCodeArg(expr, args[i]);
2777
+ expr.push(plus, safeStringify(strs[++i]));
2778
+ }
2779
+ optimize(expr);
2780
+ return new _Code(expr);
2781
+ }
2782
+ exports.str = str;
2783
+ function addCodeArg(code, arg) {
2784
+ if (arg instanceof _Code)
2785
+ code.push(...arg._items);
2786
+ else if (arg instanceof Name)
2787
+ code.push(arg);
2788
+ else
2789
+ code.push(interpolate(arg));
2790
+ }
2791
+ exports.addCodeArg = addCodeArg;
2792
+ function optimize(expr) {
2793
+ let i = 1;
2794
+ while (i < expr.length - 1) {
2795
+ if (expr[i] === plus) {
2796
+ const res = mergeExprItems(expr[i - 1], expr[i + 1]);
2797
+ if (res !== undefined) {
2798
+ expr.splice(i - 1, 3, res);
2799
+ continue;
2800
+ }
2801
+ expr[i++] = "+";
2802
+ }
2803
+ i++;
2804
+ }
2805
+ }
2806
+ function mergeExprItems(a, b) {
2807
+ if (b === '""')
2808
+ return a;
2809
+ if (a === '""')
2810
+ return b;
2811
+ if (typeof a == "string") {
2812
+ if (b instanceof Name || a[a.length - 1] !== '"')
2813
+ return;
2814
+ if (typeof b != "string")
2815
+ return `${a.slice(0, -1)}${b}"`;
2816
+ if (b[0] === '"')
2817
+ return a.slice(0, -1) + b.slice(1);
2818
+ return;
2819
+ }
2820
+ if (typeof b == "string" && b[0] === '"' && !(a instanceof Name))
2821
+ return `"${a}${b.slice(1)}`;
2822
+ return;
2823
+ }
2824
+ function strConcat(c1, c2) {
2825
+ return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str`${c1}${c2}`;
2826
+ }
2827
+ exports.strConcat = strConcat;
2828
+ function interpolate(x) {
2829
+ return typeof x == "number" || typeof x == "boolean" || x === null ? x : safeStringify(Array.isArray(x) ? x.join(",") : x);
2830
+ }
2831
+ function stringify(x) {
2832
+ return new _Code(safeStringify(x));
2833
+ }
2834
+ exports.stringify = stringify;
2835
+ function safeStringify(x) {
2836
+ return JSON.stringify(x).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
2837
+ }
2838
+ exports.safeStringify = safeStringify;
2839
+ function getProperty(key) {
2840
+ return typeof key == "string" && exports.IDENTIFIER.test(key) ? new _Code(`.${key}`) : _`[${key}]`;
2841
+ }
2842
+ exports.getProperty = getProperty;
2843
+ function getEsmExportName(key) {
2844
+ if (typeof key == "string" && exports.IDENTIFIER.test(key)) {
2845
+ return new _Code(`${key}`);
2846
+ }
2847
+ throw new Error(`CodeGen: invalid export name: ${key}, use explicit $id name mapping`);
2848
+ }
2849
+ exports.getEsmExportName = getEsmExportName;
2850
+ function regexpCode(rx) {
2851
+ return new _Code(rx.toString());
2852
+ }
2853
+ exports.regexpCode = regexpCode;
2854
+ });
2855
+
2856
+ // ../../node_modules/ajv/dist/compile/codegen/scope.js
2857
+ var require_scope = __commonJS((exports) => {
2858
+ Object.defineProperty(exports, "__esModule", { value: true });
2859
+ exports.ValueScope = exports.ValueScopeName = exports.Scope = exports.varKinds = exports.UsedValueState = undefined;
2860
+ var code_1 = require_code();
2861
+
2862
+ class ValueError extends Error {
2863
+ constructor(name) {
2864
+ super(`CodeGen: "code" for ${name} not defined`);
2865
+ this.value = name.value;
2866
+ }
2867
+ }
2868
+ var UsedValueState;
2869
+ (function(UsedValueState2) {
2870
+ UsedValueState2[UsedValueState2["Started"] = 0] = "Started";
2871
+ UsedValueState2[UsedValueState2["Completed"] = 1] = "Completed";
2872
+ })(UsedValueState || (exports.UsedValueState = UsedValueState = {}));
2873
+ exports.varKinds = {
2874
+ const: new code_1.Name("const"),
2875
+ let: new code_1.Name("let"),
2876
+ var: new code_1.Name("var")
2877
+ };
2878
+
2879
+ class Scope {
2880
+ constructor({ prefixes, parent } = {}) {
2881
+ this._names = {};
2882
+ this._prefixes = prefixes;
2883
+ this._parent = parent;
2884
+ }
2885
+ toName(nameOrPrefix) {
2886
+ return nameOrPrefix instanceof code_1.Name ? nameOrPrefix : this.name(nameOrPrefix);
2887
+ }
2888
+ name(prefix) {
2889
+ return new code_1.Name(this._newName(prefix));
2890
+ }
2891
+ _newName(prefix) {
2892
+ const ng = this._names[prefix] || this._nameGroup(prefix);
2893
+ return `${prefix}${ng.index++}`;
2894
+ }
2895
+ _nameGroup(prefix) {
2896
+ var _a2, _b;
2897
+ if (((_b = (_a2 = this._parent) === null || _a2 === undefined ? undefined : _a2._prefixes) === null || _b === undefined ? undefined : _b.has(prefix)) || this._prefixes && !this._prefixes.has(prefix)) {
2898
+ throw new Error(`CodeGen: prefix "${prefix}" is not allowed in this scope`);
2899
+ }
2322
2900
  return this._names[prefix] = { prefix, index: 0 };
2323
2901
  }
2324
2902
  }
@@ -8452,136 +9030,1083 @@ var require_formats = __commonJS((exports) => {
8452
9030
  BYTE.lastIndex = 0;
8453
9031
  return BYTE.test(str);
8454
9032
  }
8455
- var MIN_INT32 = -(2 ** 31);
8456
- var MAX_INT32 = 2 ** 31 - 1;
8457
- function validateInt32(value) {
8458
- return Number.isInteger(value) && value <= MAX_INT32 && value >= MIN_INT32;
9033
+ var MIN_INT32 = -(2 ** 31);
9034
+ var MAX_INT32 = 2 ** 31 - 1;
9035
+ function validateInt32(value) {
9036
+ return Number.isInteger(value) && value <= MAX_INT32 && value >= MIN_INT32;
9037
+ }
9038
+ function validateInt64(value) {
9039
+ return Number.isInteger(value);
9040
+ }
9041
+ function validateNumber() {
9042
+ return true;
9043
+ }
9044
+ var Z_ANCHOR = /[^\\]\\Z/;
9045
+ function regex(str) {
9046
+ if (Z_ANCHOR.test(str))
9047
+ return false;
9048
+ try {
9049
+ new RegExp(str);
9050
+ return true;
9051
+ } catch (e) {
9052
+ return false;
9053
+ }
9054
+ }
9055
+ });
9056
+
9057
+ // ../../node_modules/ajv-formats/dist/limit.js
9058
+ var require_limit = __commonJS((exports) => {
9059
+ Object.defineProperty(exports, "__esModule", { value: true });
9060
+ exports.formatLimitDefinition = undefined;
9061
+ var ajv_1 = require_ajv();
9062
+ var codegen_1 = require_codegen();
9063
+ var ops = codegen_1.operators;
9064
+ var KWDs = {
9065
+ formatMaximum: { okStr: "<=", ok: ops.LTE, fail: ops.GT },
9066
+ formatMinimum: { okStr: ">=", ok: ops.GTE, fail: ops.LT },
9067
+ formatExclusiveMaximum: { okStr: "<", ok: ops.LT, fail: ops.GTE },
9068
+ formatExclusiveMinimum: { okStr: ">", ok: ops.GT, fail: ops.LTE }
9069
+ };
9070
+ var error48 = {
9071
+ message: ({ keyword, schemaCode }) => (0, codegen_1.str)`should be ${KWDs[keyword].okStr} ${schemaCode}`,
9072
+ params: ({ keyword, schemaCode }) => (0, codegen_1._)`{comparison: ${KWDs[keyword].okStr}, limit: ${schemaCode}}`
9073
+ };
9074
+ exports.formatLimitDefinition = {
9075
+ keyword: Object.keys(KWDs),
9076
+ type: "string",
9077
+ schemaType: "string",
9078
+ $data: true,
9079
+ error: error48,
9080
+ code(cxt) {
9081
+ const { gen, data, schemaCode, keyword, it } = cxt;
9082
+ const { opts, self } = it;
9083
+ if (!opts.validateFormats)
9084
+ return;
9085
+ const fCxt = new ajv_1.KeywordCxt(it, self.RULES.all.format.definition, "format");
9086
+ if (fCxt.$data)
9087
+ validate$DataFormat();
9088
+ else
9089
+ validateFormat();
9090
+ function validate$DataFormat() {
9091
+ const fmts = gen.scopeValue("formats", {
9092
+ ref: self.formats,
9093
+ code: opts.code.formats
9094
+ });
9095
+ const fmt = gen.const("fmt", (0, codegen_1._)`${fmts}[${fCxt.schemaCode}]`);
9096
+ cxt.fail$data((0, codegen_1.or)((0, codegen_1._)`typeof ${fmt} != "object"`, (0, codegen_1._)`${fmt} instanceof RegExp`, (0, codegen_1._)`typeof ${fmt}.compare != "function"`, compareCode(fmt)));
9097
+ }
9098
+ function validateFormat() {
9099
+ const format = fCxt.schema;
9100
+ const fmtDef = self.formats[format];
9101
+ if (!fmtDef || fmtDef === true)
9102
+ return;
9103
+ if (typeof fmtDef != "object" || fmtDef instanceof RegExp || typeof fmtDef.compare != "function") {
9104
+ throw new Error(`"${keyword}": format "${format}" does not define "compare" function`);
9105
+ }
9106
+ const fmt = gen.scopeValue("formats", {
9107
+ key: format,
9108
+ ref: fmtDef,
9109
+ code: opts.code.formats ? (0, codegen_1._)`${opts.code.formats}${(0, codegen_1.getProperty)(format)}` : undefined
9110
+ });
9111
+ cxt.fail$data(compareCode(fmt));
9112
+ }
9113
+ function compareCode(fmt) {
9114
+ return (0, codegen_1._)`${fmt}.compare(${data}, ${schemaCode}) ${KWDs[keyword].fail} 0`;
9115
+ }
9116
+ },
9117
+ dependencies: ["format"]
9118
+ };
9119
+ var formatLimitPlugin = (ajv) => {
9120
+ ajv.addKeyword(exports.formatLimitDefinition);
9121
+ return ajv;
9122
+ };
9123
+ exports.default = formatLimitPlugin;
9124
+ });
9125
+
9126
+ // ../../node_modules/ajv-formats/dist/index.js
9127
+ var require_dist = __commonJS((exports, module) => {
9128
+ Object.defineProperty(exports, "__esModule", { value: true });
9129
+ var formats_1 = require_formats();
9130
+ var limit_1 = require_limit();
9131
+ var codegen_1 = require_codegen();
9132
+ var fullName = new codegen_1.Name("fullFormats");
9133
+ var fastName = new codegen_1.Name("fastFormats");
9134
+ var formatsPlugin = (ajv, opts = { keywords: true }) => {
9135
+ if (Array.isArray(opts)) {
9136
+ addFormats(ajv, opts, formats_1.fullFormats, fullName);
9137
+ return ajv;
9138
+ }
9139
+ const [formats, exportName] = opts.mode === "fast" ? [formats_1.fastFormats, fastName] : [formats_1.fullFormats, fullName];
9140
+ const list = opts.formats || formats_1.formatNames;
9141
+ addFormats(ajv, list, formats, exportName);
9142
+ if (opts.keywords)
9143
+ (0, limit_1.default)(ajv);
9144
+ return ajv;
9145
+ };
9146
+ formatsPlugin.get = (name, mode = "full") => {
9147
+ const formats = mode === "fast" ? formats_1.fastFormats : formats_1.fullFormats;
9148
+ const f = formats[name];
9149
+ if (!f)
9150
+ throw new Error(`Unknown format "${name}"`);
9151
+ return f;
9152
+ };
9153
+ function addFormats(ajv, list, fs, exportName) {
9154
+ var _a2;
9155
+ var _b;
9156
+ (_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== undefined || (_b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`);
9157
+ for (const f of list)
9158
+ ajv.addFormat(f, fs[f]);
9159
+ }
9160
+ module.exports = exports = formatsPlugin;
9161
+ Object.defineProperty(exports, "__esModule", { value: true });
9162
+ exports.default = formatsPlugin;
9163
+ });
9164
+
9165
+ // src/context-assembly.ts
9166
+ var exports_context_assembly = {};
9167
+ __export(exports_context_assembly, {
9168
+ trackSessionAssembly: () => trackSessionAssembly,
9169
+ recordContextFeedback: () => recordContextFeedback,
9170
+ mapToContextEntity: () => mapToContextEntity,
9171
+ getSessionAssemblyId: () => getSessionAssemblyId,
9172
+ getCachedManifest: () => getCachedManifest,
9173
+ computeRelevanceScore: () => computeRelevanceScore,
9174
+ cacheManifest: () => cacheManifest,
9175
+ assembleContext: () => assembleContext
9176
+ });
9177
+ function estimateTokens(text) {
9178
+ return Math.ceil(text.length / 4);
9179
+ }
9180
+ function generateAssemblyId() {
9181
+ return `ctx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
9182
+ }
9183
+ function truncateContent(content, maxTokens) {
9184
+ const currentTokens = estimateTokens(content);
9185
+ if (currentTokens <= maxTokens) {
9186
+ return { text: content, truncated: false };
9187
+ }
9188
+ const paragraphs = content.split(/\n\n+/);
9189
+ let result = paragraphs[0];
9190
+ for (let i = 1;i < paragraphs.length; i++) {
9191
+ const lines = paragraphs[i].split(`
9192
+ `).filter((l) => l.startsWith("- ") || l.startsWith("* "));
9193
+ if (lines.length > 0) {
9194
+ const bulletSection = lines.join(`
9195
+ `);
9196
+ if (estimateTokens(result + `
9197
+
9198
+ ` + bulletSection) <= maxTokens) {
9199
+ result += `
9200
+
9201
+ ` + bulletSection;
9202
+ }
9203
+ }
9204
+ }
9205
+ if (estimateTokens(result) > maxTokens) {
9206
+ const maxChars = maxTokens * 4;
9207
+ result = result.slice(0, maxChars - 3) + "...";
9208
+ }
9209
+ return { text: result, truncated: true };
9210
+ }
9211
+ function computeRelevanceScore(entity, taskContext, cardLabels) {
9212
+ const reasons = [];
9213
+ let score = 0;
9214
+ const hasRrfScore = entity.rrf_score !== undefined && entity.rrf_score > 0;
9215
+ if (hasRrfScore) {
9216
+ const normalizedRrf = Math.min(entity.rrf_score / 0.04, 1);
9217
+ const rrfContribution = normalizedRrf * 0.3;
9218
+ score += rrfContribution;
9219
+ reasons.push(`hybrid_search(rrf=${entity.rrf_score.toFixed(4)})`);
9220
+ }
9221
+ const textMatchWeight = hasRrfScore ? 0.15 : 0.4;
9222
+ const taskWords = new Set(taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
9223
+ const entityWords = new Set(`${entity.title} ${entity.content}`.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
9224
+ const overlap = [...taskWords].filter((w) => entityWords.has(w));
9225
+ if (overlap.length > 0) {
9226
+ const textScore = Math.min(overlap.length / Math.max(taskWords.size, 1), 1) * textMatchWeight;
9227
+ score += textScore;
9228
+ reasons.push(`text_match(${overlap.length} words)`);
9229
+ }
9230
+ if (cardLabels.length > 0 && entity.tags.length > 0) {
9231
+ const labelSet = new Set(cardLabels.map((l) => l.toLowerCase()));
9232
+ const tagOverlap = entity.tags.filter((t) => labelSet.has(t.toLowerCase()));
9233
+ if (tagOverlap.length > 0) {
9234
+ const tagScore = tagOverlap.length / cardLabels.length * 0.3;
9235
+ score += tagScore;
9236
+ reasons.push(`tag_match(${tagOverlap.join(",")})`);
9237
+ }
9238
+ }
9239
+ score += entity.confidence * 0.15;
9240
+ if (entity.confidence >= 0.9) {
9241
+ reasons.push("high_confidence");
9242
+ }
9243
+ if (entity.last_accessed_at) {
9244
+ const daysSinceAccess = (Date.now() - new Date(entity.last_accessed_at).getTime()) / (1000 * 60 * 60 * 24);
9245
+ const halfLife = { draft: 7, episode: 30, reference: 180 }[entity.memory_tier];
9246
+ const recencyScore = 0.5 ** (daysSinceAccess / halfLife) * 0.1;
9247
+ score += recencyScore;
9248
+ if (daysSinceAccess < 7)
9249
+ reasons.push("recently_accessed");
9250
+ }
9251
+ if (entity.access_count > 0) {
9252
+ const freqScore = Math.log10(entity.access_count + 1) * 0.05;
9253
+ score += Math.min(freqScore, 0.1);
9254
+ if (entity.access_count >= 5)
9255
+ reasons.push(`frequently_used(${entity.access_count})`);
9256
+ }
9257
+ const usefulnessScore = entity.metadata?.usefulness_score ?? 0;
9258
+ if (usefulnessScore >= 3) {
9259
+ const usefulnessBoost = Math.min(usefulnessScore / 20, 0.15);
9260
+ score += usefulnessBoost;
9261
+ reasons.push(`useful(${usefulnessScore})`);
9262
+ } else if (usefulnessScore === 0 && entity.access_count >= 5) {
9263
+ score -= 0.02;
9264
+ reasons.push("low_usefulness");
9265
+ }
9266
+ if (entity.type === "procedure") {
9267
+ score += 0.1;
9268
+ reasons.push("procedure_boost");
9269
+ }
9270
+ score = Math.min(score, 1);
9271
+ const tierWeight = TIER_WEIGHTS[entity.memory_tier];
9272
+ score *= tierWeight;
9273
+ return { score, reasons };
9274
+ }
9275
+ async function assembleContext(options) {
9276
+ const {
9277
+ workspaceId,
9278
+ projectId,
9279
+ taskContext,
9280
+ cardLabels = [],
9281
+ tokenBudget = DEFAULT_TOKEN_BUDGET,
9282
+ client: client2
9283
+ } = options;
9284
+ const assemblyId = generateAssemblyId();
9285
+ const manifest = {
9286
+ assemblyId,
9287
+ timestamp: new Date().toISOString(),
9288
+ included: [],
9289
+ excluded: [],
9290
+ budgetUsed: 0,
9291
+ budgetTotal: tokenBudget,
9292
+ tierBreakdown: {
9293
+ draft: { count: 0, tokens: 0 },
9294
+ episode: { count: 0, tokens: 0 },
9295
+ reference: { count: 0, tokens: 0 }
9296
+ }
9297
+ };
9298
+ let candidates = [];
9299
+ try {
9300
+ const searchResult = await client2.searchMemoryEntities(workspaceId, taskContext, { project_id: projectId, limit: 30 });
9301
+ if (searchResult.entities?.length > 0) {
9302
+ candidates = searchResult.entities.map(mapToContextEntity);
9303
+ }
9304
+ } catch {}
9305
+ if (candidates.length < 10 && projectId) {
9306
+ try {
9307
+ const listResult = await client2.listMemoryEntities({
9308
+ workspace_id: workspaceId,
9309
+ project_id: projectId,
9310
+ limit: 30
9311
+ });
9312
+ if (listResult.entities?.length > 0) {
9313
+ const existingIds = new Set(candidates.map((c) => c.id));
9314
+ const additional = listResult.entities.map(mapToContextEntity).filter((e) => !existingIds.has(e.id));
9315
+ candidates.push(...additional);
9316
+ }
9317
+ } catch {}
9318
+ }
9319
+ if (candidates.length < 20) {
9320
+ try {
9321
+ const wsResult = await client2.listMemoryEntities({
9322
+ workspace_id: workspaceId,
9323
+ scope: "workspace",
9324
+ limit: 20
9325
+ });
9326
+ if (wsResult.entities?.length > 0) {
9327
+ const existingIds = new Set(candidates.map((c) => c.id));
9328
+ const additional = wsResult.entities.map(mapToContextEntity).filter((e) => !existingIds.has(e.id));
9329
+ candidates.push(...additional);
9330
+ }
9331
+ } catch {}
9332
+ }
9333
+ if (candidates.length === 0) {
9334
+ return {
9335
+ context: "",
9336
+ manifest,
9337
+ memories: []
9338
+ };
9339
+ }
9340
+ const scored = candidates.map((entity) => {
9341
+ const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels);
9342
+ return { entity, score, reasons };
9343
+ });
9344
+ scored.sort((a, b) => b.score - a.score);
9345
+ const procedureBudget = Math.floor(tokenBudget * PROCEDURE_BUDGET_FRACTION);
9346
+ const remainingBudget = tokenBudget - procedureBudget;
9347
+ const tierBudgets = {
9348
+ reference: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.reference),
9349
+ episode: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.episode),
9350
+ draft: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.draft)
9351
+ };
9352
+ const tierUsed = {
9353
+ reference: 0,
9354
+ episode: 0,
9355
+ draft: 0
9356
+ };
9357
+ let procedureUsed = 0;
9358
+ const included = [];
9359
+ let totalUsed = 0;
9360
+ let referenceCount = 0;
9361
+ for (const item of scored) {
9362
+ if (item.entity.memory_tier === "reference" && item.entity.type !== "procedure" && referenceCount < MIN_REFERENCE_SLOTS) {
9363
+ const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
9364
+ const tokens = estimateTokens(`### ${item.entity.title}
9365
+ ${text}`);
9366
+ if (totalUsed + tokens <= tokenBudget) {
9367
+ included.push({ ...item, tokens, truncated });
9368
+ item.entity.content = text;
9369
+ totalUsed += tokens;
9370
+ tierUsed.reference += tokens;
9371
+ referenceCount++;
9372
+ }
9373
+ }
9374
+ }
9375
+ const includedIds = new Set(included.map((i) => i.entity.id));
9376
+ const procedureCandidates = scored.filter((item) => item.entity.type === "procedure" && !includedIds.has(item.entity.id));
9377
+ for (const item of procedureCandidates) {
9378
+ if (item.score < MIN_RELEVANCE_THRESHOLD) {
9379
+ manifest.excluded.push({
9380
+ entityId: item.entity.id,
9381
+ title: item.entity.title,
9382
+ type: item.entity.type,
9383
+ tier: item.entity.memory_tier,
9384
+ relevanceScore: item.score,
9385
+ reason: "below_relevance_threshold"
9386
+ });
9387
+ continue;
9388
+ }
9389
+ const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
9390
+ const tokens = estimateTokens(`### ${item.entity.title}
9391
+ ${text}`);
9392
+ if (procedureUsed + tokens > procedureBudget) {
9393
+ const totalRemaining = tokenBudget - totalUsed;
9394
+ if (tokens > totalRemaining) {
9395
+ manifest.excluded.push({
9396
+ entityId: item.entity.id,
9397
+ title: item.entity.title,
9398
+ type: item.entity.type,
9399
+ tier: item.entity.memory_tier,
9400
+ relevanceScore: item.score,
9401
+ reason: "procedure_budget_exceeded"
9402
+ });
9403
+ continue;
9404
+ }
9405
+ }
9406
+ if (totalUsed + tokens > tokenBudget) {
9407
+ manifest.excluded.push({
9408
+ entityId: item.entity.id,
9409
+ title: item.entity.title,
9410
+ type: item.entity.type,
9411
+ tier: item.entity.memory_tier,
9412
+ relevanceScore: item.score,
9413
+ reason: "total_budget_exceeded"
9414
+ });
9415
+ continue;
9416
+ }
9417
+ included.push({ ...item, tokens, truncated });
9418
+ item.entity.content = text;
9419
+ totalUsed += tokens;
9420
+ procedureUsed += tokens;
9421
+ includedIds.add(item.entity.id);
9422
+ }
9423
+ for (const item of scored) {
9424
+ if (includedIds.has(item.entity.id))
9425
+ continue;
9426
+ if (item.entity.type === "procedure")
9427
+ continue;
9428
+ if (item.score < MIN_RELEVANCE_THRESHOLD) {
9429
+ manifest.excluded.push({
9430
+ entityId: item.entity.id,
9431
+ title: item.entity.title,
9432
+ type: item.entity.type,
9433
+ tier: item.entity.memory_tier,
9434
+ relevanceScore: item.score,
9435
+ reason: "below_relevance_threshold"
9436
+ });
9437
+ continue;
9438
+ }
9439
+ const tier = item.entity.memory_tier;
9440
+ const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
9441
+ const tokens = estimateTokens(`### ${item.entity.title}
9442
+ ${text}`);
9443
+ if (tierUsed[tier] + tokens > tierBudgets[tier]) {
9444
+ const totalRemaining = tokenBudget - totalUsed;
9445
+ if (tokens > totalRemaining) {
9446
+ manifest.excluded.push({
9447
+ entityId: item.entity.id,
9448
+ title: item.entity.title,
9449
+ type: item.entity.type,
9450
+ tier,
9451
+ relevanceScore: item.score,
9452
+ reason: "budget_exceeded"
9453
+ });
9454
+ continue;
9455
+ }
9456
+ }
9457
+ if (totalUsed + tokens > tokenBudget) {
9458
+ manifest.excluded.push({
9459
+ entityId: item.entity.id,
9460
+ title: item.entity.title,
9461
+ type: item.entity.type,
9462
+ tier,
9463
+ relevanceScore: item.score,
9464
+ reason: "total_budget_exceeded"
9465
+ });
9466
+ continue;
9467
+ }
9468
+ included.push({ ...item, tokens, truncated });
9469
+ item.entity.content = text;
9470
+ totalUsed += tokens;
9471
+ tierUsed[tier] += tokens;
9472
+ includedIds.add(item.entity.id);
9473
+ }
9474
+ manifest.budgetUsed = totalUsed;
9475
+ const procedureItems = included.filter((i) => i.entity.type === "procedure");
9476
+ manifest.tierBreakdown = {
9477
+ reference: {
9478
+ count: included.filter((i) => i.entity.memory_tier === "reference" && i.entity.type !== "procedure").length,
9479
+ tokens: tierUsed.reference
9480
+ },
9481
+ episode: {
9482
+ count: included.filter((i) => i.entity.memory_tier === "episode" && i.entity.type !== "procedure").length,
9483
+ tokens: tierUsed.episode
9484
+ },
9485
+ draft: {
9486
+ count: included.filter((i) => i.entity.memory_tier === "draft" && i.entity.type !== "procedure").length,
9487
+ tokens: tierUsed.draft
9488
+ }
9489
+ };
9490
+ manifest.procedureBreakdown = {
9491
+ count: procedureItems.length,
9492
+ tokens: procedureUsed,
9493
+ budget: procedureBudget
9494
+ };
9495
+ for (const item of included) {
9496
+ manifest.included.push({
9497
+ entityId: item.entity.id,
9498
+ title: item.entity.title,
9499
+ type: item.entity.type,
9500
+ tier: item.entity.memory_tier,
9501
+ relevanceScore: item.score,
9502
+ reasons: item.reasons,
9503
+ tokenCount: item.tokens,
9504
+ truncated: item.truncated
9505
+ });
9506
+ }
9507
+ const contextSections = [];
9508
+ const nonProcedureItems = included.filter((i) => i.entity.type !== "procedure");
9509
+ if (included.length > 0) {
9510
+ if (procedureItems.length > 0) {
9511
+ contextSections.push(`## Procedures (${procedureItems.length} loaded, ${procedureUsed}/${procedureBudget} tokens)`);
9512
+ for (const item of procedureItems) {
9513
+ const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
9514
+ const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
9515
+ contextSections.push(`
9516
+ ### ${item.entity.title} (confidence: ${item.entity.confidence})${tierLabel}${tags}`);
9517
+ contextSections.push(item.entity.content);
9518
+ }
9519
+ }
9520
+ if (nonProcedureItems.length > 0) {
9521
+ contextSections.push(`
9522
+ ## Relevant Memories (${nonProcedureItems.length} loaded, ${manifest.excluded.length} excluded)`);
9523
+ contextSections.push(`*Assembly: ${assemblyId} | Budget: ${totalUsed}/${tokenBudget} tokens*`);
9524
+ for (const item of nonProcedureItems) {
9525
+ const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
9526
+ const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
9527
+ contextSections.push(`
9528
+ ### ${item.entity.title} (${item.entity.type}, confidence: ${item.entity.confidence})${tierLabel}${tags}`);
9529
+ contextSections.push(item.entity.content);
9530
+ }
9531
+ }
9532
+ }
9533
+ incrementAccessCounts(client2, included.map((i) => i.entity.id)).catch(() => {});
9534
+ promoteEligibleEntities(client2, included.map((i) => i.entity)).catch(() => {});
9535
+ return {
9536
+ context: contextSections.join(`
9537
+ `),
9538
+ manifest,
9539
+ memories: included.map((i) => i.entity)
9540
+ };
9541
+ }
9542
+ function mapToContextEntity(raw) {
9543
+ const e = raw;
9544
+ return {
9545
+ id: e.id,
9546
+ type: e.type,
9547
+ title: e.title,
9548
+ content: e.content,
9549
+ confidence: e.confidence ?? 1,
9550
+ tags: e.tags || [],
9551
+ memory_tier: e.memory_tier || "reference",
9552
+ access_count: e.access_count || 0,
9553
+ last_accessed_at: e.last_accessed_at || null,
9554
+ created_at: e.created_at || "",
9555
+ updated_at: e.updated_at || "",
9556
+ metadata: e.metadata ?? undefined,
9557
+ rrf_score: e.rrf_score ?? undefined,
9558
+ fts_rank: e.fts_rank ?? undefined,
9559
+ semantic_rank: e.semantic_rank ?? undefined
9560
+ };
9561
+ }
9562
+ async function incrementAccessCounts(client2, entityIds) {
9563
+ if (entityIds.length === 0)
9564
+ return;
9565
+ try {
9566
+ await client2.batchTouchMemoryEntities(entityIds);
9567
+ } catch {
9568
+ await Promise.allSettled(entityIds.map((id) => client2.touchMemoryEntity(id)));
9569
+ }
9570
+ }
9571
+ async function promoteEligibleEntities(client2, entities) {
9572
+ for (const entity of entities) {
9573
+ if (entity.memory_tier === "reference")
9574
+ continue;
9575
+ if (!entity.created_at)
9576
+ continue;
9577
+ const promotion = checkPromotion(entity.memory_tier, entity.access_count + 1, entity.confidence, entity.created_at);
9578
+ if (promotion.eligible && promotion.targetTier) {
9579
+ try {
9580
+ await client2.updateMemoryEntity(entity.id, {
9581
+ memory_tier: promotion.targetTier,
9582
+ metadata: {
9583
+ ...entity.metadata || {},
9584
+ promoted_at: new Date().toISOString(),
9585
+ promotion_reason: promotion.reason,
9586
+ promoted_from: entity.memory_tier
9587
+ }
9588
+ });
9589
+ } catch {}
9590
+ }
9591
+ }
9592
+ }
9593
+ function cacheManifest(manifest) {
9594
+ if (manifestCache.size >= MAX_CACHE_SIZE) {
9595
+ const firstKey = manifestCache.keys().next().value;
9596
+ if (firstKey)
9597
+ manifestCache.delete(firstKey);
9598
+ }
9599
+ manifestCache.set(manifest.assemblyId, manifest);
9600
+ }
9601
+ function getCachedManifest(assemblyId) {
9602
+ return manifestCache.get(assemblyId);
9603
+ }
9604
+ function trackSessionAssembly(cardId, assemblyId) {
9605
+ if (sessionAssemblyMap.size >= MAX_SESSION_MAP_SIZE) {
9606
+ const firstKey = sessionAssemblyMap.keys().next().value;
9607
+ if (firstKey)
9608
+ sessionAssemblyMap.delete(firstKey);
9609
+ }
9610
+ sessionAssemblyMap.set(cardId, assemblyId);
9611
+ }
9612
+ function getSessionAssemblyId(cardId) {
9613
+ return sessionAssemblyMap.get(cardId);
9614
+ }
9615
+ async function recordContextFeedback(client2, cardId, sessionStatus, progressPercent, hadBlockers) {
9616
+ const assemblyId = sessionAssemblyMap.get(cardId);
9617
+ if (!assemblyId)
9618
+ return { adjusted: 0 };
9619
+ const manifest = manifestCache.get(assemblyId);
9620
+ if (!manifest || manifest.included.length === 0)
9621
+ return { adjusted: 0 };
9622
+ let adjusted = 0;
9623
+ const isSuccess = sessionStatus === "completed" && (progressPercent ?? 0) >= 100;
9624
+ for (const entry of manifest.included) {
9625
+ try {
9626
+ if (isSuccess) {
9627
+ const { entity } = await client2.getMemoryEntity(entry.entityId);
9628
+ const e = entity;
9629
+ const currentUsefulness = e.metadata?.usefulness_score ?? 0;
9630
+ const newConfidence = Math.min((e.confidence ?? 0.5) + 0.05, 1);
9631
+ await client2.updateMemoryEntity(entry.entityId, {
9632
+ confidence: newConfidence,
9633
+ metadata: {
9634
+ usefulness_score: currentUsefulness + 1,
9635
+ last_feedback_at: new Date().toISOString()
9636
+ }
9637
+ });
9638
+ adjusted++;
9639
+ } else if (hadBlockers) {
9640
+ const { entity } = await client2.getMemoryEntity(entry.entityId);
9641
+ const e = entity;
9642
+ const newConfidence = Math.max((e.confidence ?? 0.5) - 0.02, 0.1);
9643
+ await client2.updateMemoryEntity(entry.entityId, {
9644
+ confidence: newConfidence,
9645
+ metadata: {
9646
+ last_feedback_at: new Date().toISOString()
9647
+ }
9648
+ });
9649
+ adjusted++;
9650
+ }
9651
+ } catch {}
9652
+ }
9653
+ sessionAssemblyMap.delete(cardId);
9654
+ return { adjusted };
9655
+ }
9656
+ var DEFAULT_TOKEN_BUDGET = 4000, MAX_TOKENS_PER_ENTITY = 500, MIN_RELEVANCE_THRESHOLD = 0.1, TIER_WEIGHTS, PROCEDURE_BUDGET_FRACTION = 0.15, TIER_BUDGET_ALLOCATION, MIN_REFERENCE_SLOTS = 3, manifestCache, MAX_CACHE_SIZE = 50, sessionAssemblyMap, MAX_SESSION_MAP_SIZE = 100;
9657
+ var init_context_assembly = __esm(() => {
9658
+ init_src();
9659
+ TIER_WEIGHTS = {
9660
+ reference: 1,
9661
+ episode: 0.7,
9662
+ draft: 0.4
9663
+ };
9664
+ TIER_BUDGET_ALLOCATION = {
9665
+ reference: 0.6,
9666
+ episode: 0.3,
9667
+ draft: 0.1
9668
+ };
9669
+ manifestCache = new Map;
9670
+ sessionAssemblyMap = new Map;
9671
+ });
9672
+
9673
+ // src/prompt-builder.ts
9674
+ var exports_prompt_builder = {};
9675
+ __export(exports_prompt_builder, {
9676
+ inferCategoryFromLabels: () => inferCategoryFromLabels,
9677
+ getRoleFraming: () => getRoleFraming,
9678
+ getAvailableVariants: () => getAvailableVariants,
9679
+ getAvailableCategories: () => getAvailableCategories,
9680
+ generatePrompt: () => generatePrompt
9681
+ });
9682
+ function inferCategoryFromLabels(labels) {
9683
+ for (const label of labels) {
9684
+ const normalizedName = label.name.toLowerCase().trim();
9685
+ if (LABEL_CATEGORY_MAP[normalizedName]) {
9686
+ return LABEL_CATEGORY_MAP[normalizedName];
9687
+ }
9688
+ for (const [key, category] of Object.entries(LABEL_CATEGORY_MAP)) {
9689
+ if (normalizedName.includes(key) || key.includes(normalizedName)) {
9690
+ return category;
9691
+ }
9692
+ }
9693
+ }
9694
+ return "custom";
9695
+ }
9696
+ function getRoleFraming(category) {
9697
+ return DEFAULT_ROLE_FRAMINGS[category];
9698
+ }
9699
+ function estimateTokens2(text) {
9700
+ return Math.ceil(text.length / 4);
9701
+ }
9702
+ function formatSubtasks(subtasks) {
9703
+ if (subtasks.length === 0)
9704
+ return "";
9705
+ const completed = subtasks.filter((s) => s.completed).length;
9706
+ const lines = subtasks.map((s) => ` ${s.completed ? "[x]" : "[ ]"} ${s.title}`);
9707
+ return `
9708
+ ## Subtasks (${completed}/${subtasks.length} completed)
9709
+ ${lines.join(`
9710
+ `)}`;
9711
+ }
9712
+ function formatLabels(labels) {
9713
+ if (labels.length === 0)
9714
+ return "";
9715
+ return `
9716
+ **Labels:** ${labels.map((l) => l.name).join(", ")}`;
9717
+ }
9718
+ function formatLinkedCards(links) {
9719
+ if (!links || links.length === 0)
9720
+ return "";
9721
+ const lines = links.map((link) => {
9722
+ const prefix = link.direction === "outgoing" ? "->" : "<-";
9723
+ return ` ${prefix} #${link.target_card.short_id}: ${link.target_card.title} (${link.display_type})`;
9724
+ });
9725
+ return `
9726
+ ## Related Cards
9727
+ ${lines.join(`
9728
+ `)}`;
9729
+ }
9730
+ function generatePrompt(options) {
9731
+ const {
9732
+ card,
9733
+ column,
9734
+ variant,
9735
+ customConstraints,
9736
+ memories,
9737
+ assembledContext,
9738
+ assemblyId
9739
+ } = options;
9740
+ const contextOpts = {
9741
+ includeTitle: true,
9742
+ includeDescription: true,
9743
+ includeLabels: true,
9744
+ includeSubtasks: true,
9745
+ includeActivity: false,
9746
+ includeAssignee: true,
9747
+ includeDueDate: true,
9748
+ includePriority: true,
9749
+ includeLinks: true,
9750
+ includeColumn: true,
9751
+ ...options.contextOptions
9752
+ };
9753
+ const labels = card.labels || [];
9754
+ const subtasks = card.subtasks || [];
9755
+ const links = card.links || [];
9756
+ const category = inferCategoryFromLabels(labels);
9757
+ const roleFraming = getRoleFraming(category);
9758
+ const sections = [];
9759
+ sections.push(`# Role: ${roleFraming.role}
9760
+ `);
9761
+ sections.push(roleFraming.perspective);
9762
+ sections.push("");
9763
+ sections.push(VARIANT_INSTRUCTIONS[variant]);
9764
+ sections.push("");
9765
+ sections.push(`# Task: ${card.title}`);
9766
+ if (contextOpts.includeColumn && column) {
9767
+ sections.push(`**Status:** ${column.name}`);
9768
+ }
9769
+ if (contextOpts.includePriority) {
9770
+ sections.push(`**Priority:** ${card.priority}`);
9771
+ }
9772
+ if (contextOpts.includeDueDate && card.due_date) {
9773
+ sections.push(`**Due:** ${card.due_date}`);
9774
+ }
9775
+ if (contextOpts.includeAssignee && card.assignee) {
9776
+ sections.push(`**Assignee:** ${card.assignee.full_name || card.assignee.email}`);
9777
+ }
9778
+ if (contextOpts.includeLabels && labels.length > 0) {
9779
+ sections.push(formatLabels(labels));
9780
+ }
9781
+ if (contextOpts.includeDescription && card.description) {
9782
+ sections.push(`
9783
+ ## Description
9784
+ ${card.description}`);
9785
+ }
9786
+ if (contextOpts.includeSubtasks && subtasks.length > 0) {
9787
+ sections.push(formatSubtasks(subtasks));
9788
+ }
9789
+ if (contextOpts.includeLinks && links.length > 0) {
9790
+ sections.push(formatLinkedCards(links));
9791
+ }
9792
+ sections.push(`
9793
+ ## Focus Areas`);
9794
+ roleFraming.focus.forEach((f) => {
9795
+ sections.push(`- ${f}`);
9796
+ });
9797
+ sections.push(`- **Memory:** When you discover important domain knowledge, architectural decisions, or infrastructure details, store them via \`harmony_remember\`. Focus on durable knowledge that future agents would benefit from — not ephemeral task details (those are auto-extracted from your session).`);
9798
+ sections.push(`
9799
+ ## Suggested Outputs`);
9800
+ roleFraming.outputSuggestions.forEach((s) => {
9801
+ sections.push(`- ${s}`);
9802
+ });
9803
+ if (assembledContext) {
9804
+ sections.push(`
9805
+ ${assembledContext}`);
9806
+ } else if (memories && memories.length > 0) {
9807
+ sections.push(`
9808
+ ## Relevant Memories`);
9809
+ sections.push(`*${memories.length} memories recalled from knowledge graph:*`);
9810
+ for (const memory of memories) {
9811
+ const tags = memory.tags.length > 0 ? ` [${memory.tags.join(", ")}]` : "";
9812
+ sections.push(`
9813
+ ### ${memory.title} (${memory.type}, confidence: ${memory.confidence})${tags}`);
9814
+ sections.push(memory.content);
9815
+ }
9816
+ }
9817
+ const oneThingLine = synthesizeOneThing(card, subtasks, links, assembledContext);
9818
+ if (oneThingLine) {
9819
+ sections.push(`
9820
+ ## Recommended Next Step
9821
+ ${oneThingLine}`);
9822
+ }
9823
+ if (variant === "execute") {
9824
+ sections.push(`
9825
+ ## Progress Tracking
9826
+ Update your progress by calling \`harmony_update_agent_progress\` with \`currentTask\` describing what you're doing now:
9827
+ - After exploring the codebase and understanding requirements (~20%)
9828
+ - When you start implementing changes (~50%)
9829
+ - When you move to testing or verification (~80%)
9830
+ - When done, before ending the session (100%)
9831
+
9832
+ Keep \`currentTask\` specific (e.g., "Refactoring auth middleware" not "Working on card").`);
9833
+ }
9834
+ if (customConstraints) {
9835
+ sections.push(`
9836
+ ## Additional Instructions
9837
+ ${customConstraints}`);
9838
+ }
9839
+ sections.push(`
9840
+ ---
9841
+ *Card #${card.short_id} | Generated for ${variant} mode*`);
9842
+ const prompt = sections.join(`
9843
+ `);
9844
+ const memoryCount = assembledContext ? (assembledContext.match(/^### /gm) || []).length : memories?.length || 0;
9845
+ return {
9846
+ prompt,
9847
+ variant,
9848
+ category,
9849
+ role: roleFraming.role,
9850
+ contextSummary: {
9851
+ hasDescription: !!card.description,
9852
+ labelCount: labels.length,
9853
+ subtaskCount: subtasks.length,
9854
+ completedSubtasks: subtasks.filter((s) => s.completed).length,
9855
+ linkedCardCount: links.length,
9856
+ memoryCount
9857
+ },
9858
+ tokenEstimate: estimateTokens2(prompt),
9859
+ ...assemblyId && { assemblyId }
9860
+ };
9861
+ }
9862
+ function extractSessionInsights(assembledContext) {
9863
+ const result = {
9864
+ lastSessionStatus: null,
9865
+ lastSessionTask: null,
9866
+ lastSessionProgress: null,
9867
+ blockers: [],
9868
+ procedureNextStep: null
9869
+ };
9870
+ const sessionMatches = assembledContext.match(/### Session:.*?\n([\s\S]*?)(?=\n###|\n## |\n---|\n\*Assembly|$)/g);
9871
+ if (sessionMatches && sessionMatches.length > 0) {
9872
+ const latest = sessionMatches[0];
9873
+ if (/Completed work on/i.test(latest)) {
9874
+ result.lastSessionStatus = "completed";
9875
+ } else if (/Paused work on|status:\s*paused/i.test(latest)) {
9876
+ result.lastSessionStatus = "paused";
9877
+ }
9878
+ const taskMatch = latest.match(/Final task:\s*(.+)/);
9879
+ if (taskMatch)
9880
+ result.lastSessionTask = taskMatch[1].trim();
9881
+ const progressMatch = latest.match(/Progress:\s*(\d+)%/);
9882
+ if (progressMatch)
9883
+ result.lastSessionProgress = parseInt(progressMatch[1], 10);
9884
+ }
9885
+ const blockerMatches = assembledContext.match(/(?:blocker|blocked by|blocking):\s*(.+)/gi);
9886
+ if (blockerMatches) {
9887
+ result.blockers = blockerMatches.map((m) => m.replace(/(?:blocker|blocked by|blocking):\s*/i, "").trim());
9888
+ }
9889
+ const stepMatches = assembledContext.match(/^\d+\.\s+(?!.*\*\*\[key step\]\*\*.*✓)(.+?)(?:\s*\*\*\[key step\]\*\*)?$/gm);
9890
+ if (stepMatches && stepMatches.length > 0) {
9891
+ result.procedureNextStep = stepMatches[0].replace(/^\d+\.\s+/, "").replace(/\s*\*\*\[key step\]\*\*.*$/, "").trim();
9892
+ }
9893
+ return result;
9894
+ }
9895
+ function synthesizeOneThing(card, subtasks, links, assembledContext) {
9896
+ if (card.done)
9897
+ return null;
9898
+ const blockers = links.filter((l) => l.display_type === "is_blocked_by" && l.direction === "incoming");
9899
+ if (blockers.length > 0) {
9900
+ const blocker = blockers[0];
9901
+ return `Unblock first: resolve #${blocker.target_card.short_id} "${blocker.target_card.title}" which is blocking this card.`;
9902
+ }
9903
+ const session = assembledContext ? extractSessionInsights(assembledContext) : null;
9904
+ if (session?.blockers && session.blockers.length > 0) {
9905
+ return `Resolve blocker: ${session.blockers[0]}`;
9906
+ }
9907
+ if (session?.lastSessionStatus === "paused" && session.lastSessionTask) {
9908
+ const progress = session.lastSessionProgress ? ` (was ${session.lastSessionProgress}% complete)` : "";
9909
+ return `Resume previous session${progress}: "${session.lastSessionTask}".`;
9910
+ }
9911
+ if (subtasks.length > 0) {
9912
+ const completed = subtasks.filter((s) => s.completed).length;
9913
+ if (completed === subtasks.length) {
9914
+ return "All subtasks completed. Review the work and mark the card as done.";
9915
+ }
9916
+ const nextSubtask = subtasks.find((s) => !s.completed);
9917
+ if (nextSubtask) {
9918
+ return `Work on next subtask: "${nextSubtask.title}" (${completed}/${subtasks.length} done).`;
9919
+ }
8459
9920
  }
8460
- function validateInt64(value) {
8461
- return Number.isInteger(value);
9921
+ if (session?.procedureNextStep) {
9922
+ return `Follow procedure: ${session.procedureNextStep}`;
8462
9923
  }
8463
- function validateNumber() {
8464
- return true;
9924
+ if (session?.lastSessionStatus === "completed" && session.lastSessionTask) {
9925
+ return `Previous session completed ("${session.lastSessionTask}"). Review results and continue with remaining work.`;
8465
9926
  }
8466
- var Z_ANCHOR = /[^\\]\\Z/;
8467
- function regex(str) {
8468
- if (Z_ANCHOR.test(str))
8469
- return false;
8470
- try {
8471
- new RegExp(str);
8472
- return true;
8473
- } catch (e) {
8474
- return false;
8475
- }
9927
+ if (card.due_date && (card.priority === "urgent" || card.priority === "high")) {
9928
+ return `High-priority task with deadline ${card.due_date}. Start implementation immediately.`;
8476
9929
  }
8477
- });
8478
-
8479
- // ../../node_modules/ajv-formats/dist/limit.js
8480
- var require_limit = __commonJS((exports) => {
8481
- Object.defineProperty(exports, "__esModule", { value: true });
8482
- exports.formatLimitDefinition = undefined;
8483
- var ajv_1 = require_ajv();
8484
- var codegen_1 = require_codegen();
8485
- var ops = codegen_1.operators;
8486
- var KWDs = {
8487
- formatMaximum: { okStr: "<=", ok: ops.LTE, fail: ops.GT },
8488
- formatMinimum: { okStr: ">=", ok: ops.GTE, fail: ops.LT },
8489
- formatExclusiveMaximum: { okStr: "<", ok: ops.LT, fail: ops.GTE },
8490
- formatExclusiveMinimum: { okStr: ">", ok: ops.GT, fail: ops.LTE }
8491
- };
8492
- var error48 = {
8493
- message: ({ keyword, schemaCode }) => (0, codegen_1.str)`should be ${KWDs[keyword].okStr} ${schemaCode}`,
8494
- params: ({ keyword, schemaCode }) => (0, codegen_1._)`{comparison: ${KWDs[keyword].okStr}, limit: ${schemaCode}}`
8495
- };
8496
- exports.formatLimitDefinition = {
8497
- keyword: Object.keys(KWDs),
8498
- type: "string",
8499
- schemaType: "string",
8500
- $data: true,
8501
- error: error48,
8502
- code(cxt) {
8503
- const { gen, data, schemaCode, keyword, it } = cxt;
8504
- const { opts, self } = it;
8505
- if (!opts.validateFormats)
8506
- return;
8507
- const fCxt = new ajv_1.KeywordCxt(it, self.RULES.all.format.definition, "format");
8508
- if (fCxt.$data)
8509
- validate$DataFormat();
8510
- else
8511
- validateFormat();
8512
- function validate$DataFormat() {
8513
- const fmts = gen.scopeValue("formats", {
8514
- ref: self.formats,
8515
- code: opts.code.formats
8516
- });
8517
- const fmt = gen.const("fmt", (0, codegen_1._)`${fmts}[${fCxt.schemaCode}]`);
8518
- cxt.fail$data((0, codegen_1.or)((0, codegen_1._)`typeof ${fmt} != "object"`, (0, codegen_1._)`${fmt} instanceof RegExp`, (0, codegen_1._)`typeof ${fmt}.compare != "function"`, compareCode(fmt)));
8519
- }
8520
- function validateFormat() {
8521
- const format = fCxt.schema;
8522
- const fmtDef = self.formats[format];
8523
- if (!fmtDef || fmtDef === true)
8524
- return;
8525
- if (typeof fmtDef != "object" || fmtDef instanceof RegExp || typeof fmtDef.compare != "function") {
8526
- throw new Error(`"${keyword}": format "${format}" does not define "compare" function`);
8527
- }
8528
- const fmt = gen.scopeValue("formats", {
8529
- key: format,
8530
- ref: fmtDef,
8531
- code: opts.code.formats ? (0, codegen_1._)`${opts.code.formats}${(0, codegen_1.getProperty)(format)}` : undefined
8532
- });
8533
- cxt.fail$data(compareCode(fmt));
8534
- }
8535
- function compareCode(fmt) {
8536
- return (0, codegen_1._)`${fmt}.compare(${data}, ${schemaCode}) ${KWDs[keyword].fail} 0`;
8537
- }
9930
+ if (card.description) {
9931
+ return "Analyze the description, identify the approach, and begin implementation.";
9932
+ }
9933
+ return null;
9934
+ }
9935
+ function getAvailableCategories() {
9936
+ return ["bug", "feature", "design", "review", "onboarding", "epic", "custom"];
9937
+ }
9938
+ function getAvailableVariants() {
9939
+ return ["analysis", "draft", "execute"];
9940
+ }
9941
+ var LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS;
9942
+ var init_prompt_builder = __esm(() => {
9943
+ LABEL_CATEGORY_MAP = {
9944
+ bug: "bug",
9945
+ fix: "bug",
9946
+ hotfix: "bug",
9947
+ defect: "bug",
9948
+ issue: "bug",
9949
+ error: "bug",
9950
+ feature: "feature",
9951
+ enhancement: "feature",
9952
+ improvement: "feature",
9953
+ new: "feature",
9954
+ design: "design",
9955
+ ui: "design",
9956
+ ux: "design",
9957
+ frontend: "design",
9958
+ styling: "design",
9959
+ review: "review",
9960
+ "code review": "review",
9961
+ pr: "review",
9962
+ feedback: "review",
9963
+ onboarding: "onboarding",
9964
+ documentation: "onboarding",
9965
+ docs: "onboarding",
9966
+ guide: "onboarding",
9967
+ tutorial: "onboarding",
9968
+ epic: "epic",
9969
+ initiative: "epic",
9970
+ project: "epic",
9971
+ milestone: "epic"
9972
+ };
9973
+ DEFAULT_ROLE_FRAMINGS = {
9974
+ bug: {
9975
+ category: "bug",
9976
+ role: "Senior QA Engineer and Software Developer",
9977
+ perspective: "You are investigating and fixing a bug report.",
9978
+ focus: [
9979
+ "Root cause analysis",
9980
+ "Steps to reproduce",
9981
+ "Impact assessment",
9982
+ "Fix implementation",
9983
+ "Regression prevention",
9984
+ "Test cases to prevent recurrence"
9985
+ ],
9986
+ outputSuggestions: [
9987
+ "Bug triage summary",
9988
+ "Root cause explanation",
9989
+ "Fix implementation plan",
9990
+ "Test cases"
9991
+ ]
8538
9992
  },
8539
- dependencies: ["format"]
8540
- };
8541
- var formatLimitPlugin = (ajv) => {
8542
- ajv.addKeyword(exports.formatLimitDefinition);
8543
- return ajv;
8544
- };
8545
- exports.default = formatLimitPlugin;
8546
- });
8547
-
8548
- // ../../node_modules/ajv-formats/dist/index.js
8549
- var require_dist = __commonJS((exports, module) => {
8550
- Object.defineProperty(exports, "__esModule", { value: true });
8551
- var formats_1 = require_formats();
8552
- var limit_1 = require_limit();
8553
- var codegen_1 = require_codegen();
8554
- var fullName = new codegen_1.Name("fullFormats");
8555
- var fastName = new codegen_1.Name("fastFormats");
8556
- var formatsPlugin = (ajv, opts = { keywords: true }) => {
8557
- if (Array.isArray(opts)) {
8558
- addFormats(ajv, opts, formats_1.fullFormats, fullName);
8559
- return ajv;
9993
+ feature: {
9994
+ category: "feature",
9995
+ role: "Product Engineer",
9996
+ perspective: "You are implementing a new feature or enhancement.",
9997
+ focus: [
9998
+ "User requirements",
9999
+ "Technical specification",
10000
+ "Implementation approach",
10001
+ "Edge cases",
10002
+ "Acceptance criteria",
10003
+ "Integration points"
10004
+ ],
10005
+ outputSuggestions: [
10006
+ "Technical specification",
10007
+ "Implementation tasks",
10008
+ "Acceptance criteria checklist",
10009
+ "API design"
10010
+ ]
10011
+ },
10012
+ design: {
10013
+ category: "design",
10014
+ role: "UX Designer and Frontend Developer",
10015
+ perspective: "You are designing and implementing a user interface.",
10016
+ focus: [
10017
+ "User experience flow",
10018
+ "Visual design consistency",
10019
+ "Accessibility (WCAG)",
10020
+ "Responsive behavior",
10021
+ "Component architecture",
10022
+ "Interaction patterns"
10023
+ ],
10024
+ outputSuggestions: [
10025
+ "User flow diagram",
10026
+ "Component specifications",
10027
+ "Accessibility checklist",
10028
+ "Responsive breakpoints"
10029
+ ]
10030
+ },
10031
+ review: {
10032
+ category: "review",
10033
+ role: "Code Reviewer and Technical Lead",
10034
+ perspective: "You are reviewing code for quality, correctness, and maintainability.",
10035
+ focus: [
10036
+ "Code correctness",
10037
+ "Performance implications",
10038
+ "Security considerations",
10039
+ "Testing coverage",
10040
+ "Documentation",
10041
+ "Best practices adherence"
10042
+ ],
10043
+ outputSuggestions: [
10044
+ "Review checklist",
10045
+ "Suggested improvements",
10046
+ "Test scenarios",
10047
+ "Security audit"
10048
+ ]
10049
+ },
10050
+ onboarding: {
10051
+ category: "onboarding",
10052
+ role: "Technical Writer and Developer Advocate",
10053
+ perspective: "You are creating documentation or onboarding materials.",
10054
+ focus: [
10055
+ "Clear step-by-step instructions",
10056
+ "Prerequisites and setup",
10057
+ "Common pitfalls",
10058
+ "Examples and use cases",
10059
+ "Troubleshooting guide",
10060
+ "Related resources"
10061
+ ],
10062
+ outputSuggestions: [
10063
+ "Getting started guide",
10064
+ "Step-by-step tutorial",
10065
+ "FAQ section",
10066
+ "Troubleshooting guide"
10067
+ ]
10068
+ },
10069
+ epic: {
10070
+ category: "epic",
10071
+ role: "Technical Project Manager and Architect",
10072
+ perspective: "You are planning and coordinating a large initiative.",
10073
+ focus: [
10074
+ "Scope definition",
10075
+ "Task breakdown",
10076
+ "Dependencies",
10077
+ "Risk assessment",
10078
+ "Timeline considerations",
10079
+ "Success metrics"
10080
+ ],
10081
+ outputSuggestions: [
10082
+ "Epic breakdown into stories",
10083
+ "Dependency graph",
10084
+ "Risk mitigation plan",
10085
+ "Success criteria"
10086
+ ]
10087
+ },
10088
+ custom: {
10089
+ category: "custom",
10090
+ role: "Software Engineer",
10091
+ perspective: "You are working on a software task.",
10092
+ focus: [
10093
+ "Understanding requirements",
10094
+ "Implementation approach",
10095
+ "Quality considerations",
10096
+ "Testing strategy"
10097
+ ],
10098
+ outputSuggestions: [
10099
+ "Implementation plan",
10100
+ "Technical notes",
10101
+ "Task checklist"
10102
+ ]
8560
10103
  }
8561
- const [formats, exportName] = opts.mode === "fast" ? [formats_1.fastFormats, fastName] : [formats_1.fullFormats, fullName];
8562
- const list = opts.formats || formats_1.formatNames;
8563
- addFormats(ajv, list, formats, exportName);
8564
- if (opts.keywords)
8565
- (0, limit_1.default)(ajv);
8566
- return ajv;
8567
10104
  };
8568
- formatsPlugin.get = (name, mode = "full") => {
8569
- const formats = mode === "fast" ? formats_1.fastFormats : formats_1.fullFormats;
8570
- const f = formats[name];
8571
- if (!f)
8572
- throw new Error(`Unknown format "${name}"`);
8573
- return f;
10105
+ VARIANT_INSTRUCTIONS = {
10106
+ 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.`,
10107
+ 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.`,
10108
+ execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
8574
10109
  };
8575
- function addFormats(ajv, list, fs, exportName) {
8576
- var _a2;
8577
- var _b;
8578
- (_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== undefined || (_b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`);
8579
- for (const f of list)
8580
- ajv.addFormat(f, fs[f]);
8581
- }
8582
- module.exports = exports = formatsPlugin;
8583
- Object.defineProperty(exports, "__esModule", { value: true });
8584
- exports.default = formatsPlugin;
8585
10110
  });
8586
10111
 
8587
10112
  // ../../node_modules/sisteransi/src/index.js
@@ -8724,751 +10249,201 @@ var {
8724
10249
  createOption,
8725
10250
  CommanderError,
8726
10251
  InvalidArgumentError,
8727
- InvalidOptionArgumentError,
8728
- Command,
8729
- Argument,
8730
- Option,
8731
- Help
8732
- } = import__.default;
8733
-
8734
- // src/config.ts
8735
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8736
- import { homedir } from "node:os";
8737
- import { join } from "node:path";
8738
- var DEFAULT_API_URL = "https://gethmy.com/api";
8739
- var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
8740
- function getConfigDir() {
8741
- return join(homedir(), ".harmony-mcp");
8742
- }
8743
- function getConfigPath() {
8744
- return join(getConfigDir(), "config.json");
8745
- }
8746
- function getLocalConfigPath(cwd) {
8747
- return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
8748
- }
8749
- function loadConfig() {
8750
- const configPath = getConfigPath();
8751
- if (!existsSync(configPath)) {
8752
- return {
8753
- apiKey: null,
8754
- apiUrl: DEFAULT_API_URL,
8755
- activeWorkspaceId: null,
8756
- activeProjectId: null,
8757
- userEmail: null,
8758
- memoryDir: null
8759
- };
8760
- }
8761
- try {
8762
- const data = readFileSync(configPath, "utf-8");
8763
- const config = JSON.parse(data);
8764
- return {
8765
- apiKey: config.apiKey || null,
8766
- apiUrl: config.apiUrl || DEFAULT_API_URL,
8767
- activeWorkspaceId: config.activeWorkspaceId || null,
8768
- activeProjectId: config.activeProjectId || null,
8769
- userEmail: config.userEmail || null,
8770
- memoryDir: config.memoryDir || null
8771
- };
8772
- } catch {
8773
- return {
8774
- apiKey: null,
8775
- apiUrl: DEFAULT_API_URL,
8776
- activeWorkspaceId: null,
8777
- activeProjectId: null,
8778
- userEmail: null,
8779
- memoryDir: null
8780
- };
8781
- }
8782
- }
8783
- function saveConfig(config) {
8784
- const configDir = getConfigDir();
8785
- const configPath = getConfigPath();
8786
- if (!existsSync(configDir)) {
8787
- mkdirSync(configDir, { recursive: true, mode: 448 });
8788
- }
8789
- const existingConfig = loadConfig();
8790
- const newConfig = { ...existingConfig, ...config };
8791
- writeFileSync(configPath, JSON.stringify(newConfig, null, 2), {
8792
- mode: 384
8793
- });
8794
- }
8795
- function loadLocalConfig(cwd) {
8796
- const localConfigPath = getLocalConfigPath(cwd);
8797
- if (!existsSync(localConfigPath)) {
8798
- return null;
8799
- }
8800
- try {
8801
- const data = readFileSync(localConfigPath, "utf-8");
8802
- const config = JSON.parse(data);
8803
- return {
8804
- workspaceId: config.workspaceId || null,
8805
- projectId: config.projectId || null
8806
- };
8807
- } catch {
8808
- return null;
8809
- }
8810
- }
8811
- function saveLocalConfig(config, cwd) {
8812
- const localConfigPath = getLocalConfigPath(cwd);
8813
- const existingConfig = loadLocalConfig(cwd) || {
8814
- workspaceId: null,
8815
- projectId: null
8816
- };
8817
- const newConfig = { ...existingConfig, ...config };
8818
- const cleanConfig = {};
8819
- if (newConfig.workspaceId)
8820
- cleanConfig.workspaceId = newConfig.workspaceId;
8821
- if (newConfig.projectId)
8822
- cleanConfig.projectId = newConfig.projectId;
8823
- writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
8824
- }
8825
- function hasLocalConfig(cwd) {
8826
- return existsSync(getLocalConfigPath(cwd));
8827
- }
8828
- function getApiKey() {
8829
- const config = loadConfig();
8830
- if (!config.apiKey) {
8831
- throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to set your API key.
8832
- ` + "You can generate an API key at https://gethmy.com → Settings → API Keys.");
8833
- }
8834
- return config.apiKey;
8835
- }
8836
- function getApiUrl() {
8837
- const config = loadConfig();
8838
- return config.apiUrl;
8839
- }
8840
- function getUserEmail() {
8841
- const config = loadConfig();
8842
- return config.userEmail;
8843
- }
8844
- function setActiveWorkspace(workspaceId, options) {
8845
- if (options?.local) {
8846
- saveLocalConfig({ workspaceId }, options.cwd);
8847
- } else {
8848
- saveConfig({ activeWorkspaceId: workspaceId });
8849
- }
8850
- }
8851
- function setActiveProject(projectId, options) {
8852
- if (options?.local) {
8853
- saveLocalConfig({ projectId }, options.cwd);
8854
- } else {
8855
- saveConfig({ activeProjectId: projectId });
8856
- }
8857
- }
8858
- function getActiveWorkspaceId(cwd) {
8859
- const localConfig = loadLocalConfig(cwd);
8860
- if (localConfig?.workspaceId) {
8861
- return localConfig.workspaceId;
8862
- }
8863
- return loadConfig().activeWorkspaceId;
8864
- }
8865
- function getActiveProjectId(cwd) {
8866
- const localConfig = loadLocalConfig(cwd);
8867
- if (localConfig?.projectId) {
8868
- return localConfig.projectId;
8869
- }
8870
- return loadConfig().activeProjectId;
8871
- }
8872
- function isConfigured() {
8873
- const config = loadConfig();
8874
- return !!config.apiKey;
8875
- }
8876
- function areSkillsInstalled(cwd) {
8877
- const home = homedir();
8878
- const workingDir = cwd || process.cwd();
8879
- const foundPaths = [];
8880
- const globalSkillsDir = join(home, ".agents", "skills");
8881
- const globalSkillPath = join(globalSkillsDir, "hmy", "SKILL.md");
8882
- if (existsSync(globalSkillPath)) {
8883
- foundPaths.push(globalSkillPath);
8884
- return { installed: true, location: "global", paths: foundPaths };
8885
- }
8886
- const claudeGlobalSkill = join(home, ".claude", "skills", "hmy.md");
8887
- if (existsSync(claudeGlobalSkill)) {
8888
- foundPaths.push(claudeGlobalSkill);
8889
- return { installed: true, location: "global", paths: foundPaths };
8890
- }
8891
- const claudeGlobalSkillAlt = join(home, ".claude", "skills", "hmy", "SKILL.md");
8892
- if (existsSync(claudeGlobalSkillAlt)) {
8893
- foundPaths.push(claudeGlobalSkillAlt);
8894
- return { installed: true, location: "global", paths: foundPaths };
8895
- }
8896
- const localSkillPath = join(workingDir, ".claude", "skills", "hmy.md");
8897
- if (existsSync(localSkillPath)) {
8898
- foundPaths.push(localSkillPath);
8899
- return { installed: true, location: "local", paths: foundPaths };
8900
- }
8901
- const localSkillPathAlt = join(workingDir, ".claude", "skills", "hmy", "SKILL.md");
8902
- if (existsSync(localSkillPathAlt)) {
8903
- foundPaths.push(localSkillPathAlt);
8904
- return { installed: true, location: "local", paths: foundPaths };
8905
- }
8906
- return { installed: false, location: null, paths: [] };
8907
- }
8908
- function hasProjectContext(cwd) {
8909
- const localConfig = loadLocalConfig(cwd);
8910
- return !!(localConfig?.workspaceId || localConfig?.projectId);
8911
- }
8912
- function getMemoryDir() {
8913
- const config = loadConfig();
8914
- if (config.memoryDir)
8915
- return config.memoryDir;
8916
- return join(homedir(), ".harmony", "memory");
8917
- }
8918
- // ../memory/src/graph-walk.ts
8919
- async function discoverRelatedContext(client, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
8920
- const visited = new Set;
8921
- const collectedEntities = [];
8922
- const collectedRelations = [];
8923
- let truncated = false;
8924
- const queue = startIds.map((id) => [id, 0]);
8925
- for (const id of startIds) {
8926
- visited.add(id);
8927
- }
8928
- while (queue.length > 0) {
8929
- const [entityId, depth] = queue.shift();
8930
- if (collectedEntities.length >= maxEntities) {
8931
- truncated = true;
8932
- break;
8933
- }
8934
- if (depth > maxDepth)
8935
- continue;
8936
- try {
8937
- const entityResult = await client.getMemoryEntity(entityId);
8938
- const entity = entityResult.entity;
8939
- if (entity) {
8940
- collectedEntities.push({
8941
- id: entity.id,
8942
- type: entity.type,
8943
- title: entity.title,
8944
- confidence: entity.confidence ?? 1,
8945
- memory_tier: entity.memory_tier || "reference"
8946
- });
8947
- }
8948
- if (depth >= maxDepth)
8949
- continue;
8950
- const related = await client.getRelatedEntities(entityId);
8951
- for (const raw of related.outgoing || []) {
8952
- const rel = raw;
8953
- const relConfidence = rel.confidence ?? 1;
8954
- if (relConfidence < minConfidence)
8955
- continue;
8956
- const target = rel.target;
8957
- const targetId = target?.id ?? rel.target_id;
8958
- if (targetId && !visited.has(targetId)) {
8959
- visited.add(targetId);
8960
- queue.push([targetId, depth + 1]);
8961
- collectedRelations.push({
8962
- id: rel.id,
8963
- source_id: entityId,
8964
- target_id: targetId,
8965
- relation_type: rel.relation_type,
8966
- confidence: relConfidence
8967
- });
8968
- }
8969
- }
8970
- for (const raw of related.incoming || []) {
8971
- const rel = raw;
8972
- const relConfidence = rel.confidence ?? 1;
8973
- if (relConfidence < minConfidence)
8974
- continue;
8975
- const source = rel.source;
8976
- const sourceId = source?.id ?? rel.source_id;
8977
- if (sourceId && !visited.has(sourceId)) {
8978
- visited.add(sourceId);
8979
- queue.push([sourceId, depth + 1]);
8980
- collectedRelations.push({
8981
- id: rel.id,
8982
- source_id: sourceId,
8983
- target_id: entityId,
8984
- relation_type: rel.relation_type,
8985
- confidence: relConfidence
8986
- });
8987
- }
8988
- }
8989
- } catch {}
8990
- }
8991
- return {
8992
- entities: collectedEntities,
8993
- relations: collectedRelations,
8994
- depth: maxDepth,
8995
- truncated
8996
- };
8997
- }
8998
- // ../memory/src/lifecycle.ts
8999
- var DECAY_HALF_LIVES = {
9000
- draft: 7,
9001
- episode: 30,
9002
- reference: 180
9003
- };
9004
- var PROMOTION_RULES = {
9005
- draftToEpisode: {
9006
- minAccessCount: 5,
9007
- minConfidence: 0.8,
9008
- minAgeDays: 1
9009
- },
9010
- episodeToReference: {
9011
- minAccessCount: 10,
9012
- minConfidence: 0.9,
9013
- minAgeDays: 7
9014
- }
9015
- };
9016
- var ARCHIVE_THRESHOLD = 0.3;
9017
- var STALE_DAYS = 90;
9018
- var STALE_MIN_ACCESS = 3;
9019
- function computeDecayScore(tier, lastAccessedAt, accessCount) {
9020
- const halfLife = DECAY_HALF_LIVES[tier];
9021
- const now = Date.now();
9022
- let daysSinceAccess = 0;
9023
- if (lastAccessedAt) {
9024
- daysSinceAccess = (now - new Date(lastAccessedAt).getTime()) / (1000 * 60 * 60 * 24);
9025
- }
9026
- const timeDecay = 0.5 ** (daysSinceAccess / halfLife);
9027
- const accessBonus = Math.log10(accessCount + 1) * 0.1;
9028
- const score = Math.min(timeDecay + accessBonus, 1);
9029
- return { score, daysSinceAccess, halfLife, accessBonus };
9030
- }
9031
- function checkPromotion(currentTier, accessCount, confidence, createdAt) {
9032
- const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
9033
- const base = {
9034
- eligible: false,
9035
- targetTier: null,
9036
- reason: null,
9037
- currentTier,
9038
- accessCount,
9039
- confidence,
9040
- ageDays
9041
- };
9042
- if (currentTier === "draft") {
9043
- const rules = PROMOTION_RULES.draftToEpisode;
9044
- if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
9045
- return {
9046
- ...base,
9047
- eligible: true,
9048
- targetTier: "episode",
9049
- reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
9050
- };
9051
- }
9052
- }
9053
- if (currentTier === "episode") {
9054
- const rules = PROMOTION_RULES.episodeToReference;
9055
- if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
9056
- return {
9057
- ...base,
9058
- eligible: true,
9059
- targetTier: "reference",
9060
- reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
9061
- };
9062
- }
9063
- }
9064
- return base;
9065
- }
9066
- function evaluateLifecycle(entity) {
9067
- const decay = computeDecayScore(entity.memory_tier, entity.last_accessed_at, entity.access_count);
9068
- const promotion = checkPromotion(entity.memory_tier, entity.access_count, entity.confidence, entity.created_at);
9069
- const shouldArchive = entity.confidence < ARCHIVE_THRESHOLD;
9070
- const archiveReason = shouldArchive ? `Confidence ${entity.confidence} below threshold ${ARCHIVE_THRESHOLD}` : undefined;
9071
- const shouldFlagForReview = decay.daysSinceAccess >= STALE_DAYS && entity.access_count < STALE_MIN_ACCESS;
9072
- const reviewReason = shouldFlagForReview ? `Not accessed in ${Math.round(decay.daysSinceAccess)} days with only ${entity.access_count} accesses` : undefined;
9073
- return {
9074
- decay,
9075
- promotion,
9076
- shouldArchive,
9077
- shouldFlagForReview,
9078
- archiveReason,
9079
- reviewReason
9080
- };
9081
- }
9082
- // ../memory/src/sync.ts
9083
- import { createHash } from "node:crypto";
9084
- import {
9085
- existsSync as existsSync2,
9086
- mkdirSync as mkdirSync2,
9087
- readdirSync,
9088
- readFileSync as readFileSync2,
9089
- rmSync,
9090
- writeFileSync as writeFileSync2
9091
- } from "node:fs";
9092
- import { join as join2, relative, sep } from "node:path";
10252
+ InvalidOptionArgumentError,
10253
+ Command,
10254
+ Argument,
10255
+ Option,
10256
+ Help
10257
+ } = import__.default;
9093
10258
 
9094
- // ../memory/src/sync-storage.ts
9095
- function parseSyncMarkdown(markdown) {
9096
- const trimmed = markdown.trim();
9097
- let frontmatter = {
9098
- type: "context",
9099
- scope: "project",
9100
- tier: "reference",
9101
- confidence: 1,
9102
- tags: []
9103
- };
9104
- let body = trimmed;
9105
- const fmMatch = trimmed.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
9106
- if (fmMatch) {
9107
- frontmatter = parseSyncYamlFrontmatter(fmMatch[1]);
9108
- body = fmMatch[2].trim();
10259
+ // src/config.ts
10260
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10261
+ import { homedir } from "node:os";
10262
+ import { join } from "node:path";
10263
+ var DEFAULT_API_URL = "https://app.gethmy.com/api";
10264
+ var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
10265
+ function getConfigDir() {
10266
+ return join(homedir(), ".harmony-mcp");
10267
+ }
10268
+ function getConfigPath() {
10269
+ return join(getConfigDir(), "config.json");
10270
+ }
10271
+ function getLocalConfigPath(cwd) {
10272
+ return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
10273
+ }
10274
+ function loadConfig() {
10275
+ const configPath = getConfigPath();
10276
+ if (!existsSync(configPath)) {
10277
+ return {
10278
+ apiKey: null,
10279
+ apiUrl: DEFAULT_API_URL,
10280
+ activeWorkspaceId: null,
10281
+ activeProjectId: null,
10282
+ userEmail: null,
10283
+ memoryDir: null
10284
+ };
9109
10285
  }
9110
- let title = "";
9111
- const titleMatch = body.match(/^#\s+(.+)/m);
9112
- if (titleMatch) {
9113
- title = titleMatch[1].trim();
9114
- body = body.replace(/^#\s+.+\n?/, "").trim();
10286
+ try {
10287
+ const data = readFileSync(configPath, "utf-8");
10288
+ const config = JSON.parse(data);
10289
+ return {
10290
+ apiKey: config.apiKey || null,
10291
+ apiUrl: config.apiUrl || DEFAULT_API_URL,
10292
+ activeWorkspaceId: config.activeWorkspaceId || null,
10293
+ activeProjectId: config.activeProjectId || null,
10294
+ userEmail: config.userEmail || null,
10295
+ memoryDir: config.memoryDir || null
10296
+ };
10297
+ } catch {
10298
+ return {
10299
+ apiKey: null,
10300
+ apiUrl: DEFAULT_API_URL,
10301
+ activeWorkspaceId: null,
10302
+ activeProjectId: null,
10303
+ userEmail: null,
10304
+ memoryDir: null
10305
+ };
9115
10306
  }
9116
- return { frontmatter, title, content: body };
9117
10307
  }
9118
- function serializeSyncMarkdown(entity) {
9119
- const lines = ["---"];
9120
- lines.push(`id: ${entity.id}`);
9121
- lines.push(`workspace_id: ${entity.workspace_id}`);
9122
- if (entity.project_id) {
9123
- lines.push(`project_id: ${entity.project_id}`);
10308
+ function saveConfig(config) {
10309
+ const configDir = getConfigDir();
10310
+ const configPath = getConfigPath();
10311
+ if (!existsSync(configDir)) {
10312
+ mkdirSync(configDir, { recursive: true, mode: 448 });
9124
10313
  }
9125
- lines.push(`type: ${entity.type}`);
9126
- lines.push(`scope: ${entity.scope}`);
9127
- lines.push(`tier: ${entity.memory_tier || "reference"}`);
9128
- lines.push(`confidence: ${entity.confidence}`);
9129
- if (entity.tags.length > 0) {
9130
- lines.push(`tags: [${entity.tags.join(", ")}]`);
9131
- } else {
9132
- lines.push("tags: []");
10314
+ const existingConfig = loadConfig();
10315
+ const newConfig = { ...existingConfig, ...config };
10316
+ writeFileSync(configPath, JSON.stringify(newConfig, null, 2), {
10317
+ mode: 384
10318
+ });
10319
+ }
10320
+ function loadLocalConfig(cwd) {
10321
+ const localConfigPath = getLocalConfigPath(cwd);
10322
+ if (!existsSync(localConfigPath)) {
10323
+ return null;
9133
10324
  }
9134
- if (entity.agent_identifier) {
9135
- lines.push(`agent: ${entity.agent_identifier}`);
10325
+ try {
10326
+ const data = readFileSync(localConfigPath, "utf-8");
10327
+ const config = JSON.parse(data);
10328
+ return {
10329
+ workspaceId: config.workspaceId || null,
10330
+ projectId: config.projectId || null
10331
+ };
10332
+ } catch {
10333
+ return null;
9136
10334
  }
9137
- lines.push(`created_at: ${entity.created_at}`);
9138
- lines.push(`updated_at: ${entity.updated_at}`);
9139
- lines.push("---");
9140
- lines.push("");
9141
- lines.push(`# ${entity.title}`);
9142
- lines.push("");
9143
- lines.push(entity.content);
9144
- return lines.join(`
9145
- `);
9146
10335
  }
9147
- function parseSyncYamlFrontmatter(yaml) {
9148
- const result = {
9149
- type: "context",
9150
- scope: "project",
9151
- tier: "reference",
9152
- confidence: 1,
9153
- tags: []
10336
+ function saveLocalConfig(config, cwd) {
10337
+ const localConfigPath = getLocalConfigPath(cwd);
10338
+ const existingConfig = loadLocalConfig(cwd) || {
10339
+ workspaceId: null,
10340
+ projectId: null
9154
10341
  };
9155
- for (const line of yaml.split(`
9156
- `)) {
9157
- const colonIndex = line.indexOf(":");
9158
- if (colonIndex === -1)
9159
- continue;
9160
- const key = line.slice(0, colonIndex).trim();
9161
- const value = line.slice(colonIndex + 1).trim();
9162
- switch (key) {
9163
- case "id":
9164
- result.id = value;
9165
- break;
9166
- case "workspace_id":
9167
- result.workspace_id = value;
9168
- break;
9169
- case "project_id":
9170
- result.project_id = value;
9171
- break;
9172
- case "type":
9173
- result.type = value;
9174
- break;
9175
- case "scope":
9176
- result.scope = value;
9177
- break;
9178
- case "tier":
9179
- result.tier = value;
9180
- break;
9181
- case "confidence":
9182
- result.confidence = parseFloat(value) || 1;
9183
- break;
9184
- case "tags":
9185
- result.tags = parseYamlArray(value);
9186
- break;
9187
- case "related":
9188
- result.related = parseYamlArray(value);
9189
- break;
9190
- case "agent":
9191
- result.agent = value;
9192
- break;
9193
- case "created_at":
9194
- result.created_at = value;
9195
- break;
9196
- case "updated_at":
9197
- result.updated_at = value;
9198
- break;
9199
- }
9200
- }
9201
- return result;
10342
+ const newConfig = { ...existingConfig, ...config };
10343
+ const cleanConfig = {};
10344
+ if (newConfig.workspaceId)
10345
+ cleanConfig.workspaceId = newConfig.workspaceId;
10346
+ if (newConfig.projectId)
10347
+ cleanConfig.projectId = newConfig.projectId;
10348
+ writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
9202
10349
  }
9203
- function parseYamlArray(value) {
9204
- const match = value.match(/^\[(.*)]\s*$/);
9205
- if (!match)
9206
- return [];
9207
- return match[1].split(",").map((s) => s.trim()).filter(Boolean);
10350
+ function hasLocalConfig(cwd) {
10351
+ return existsSync(getLocalConfigPath(cwd));
9208
10352
  }
9209
-
9210
- // ../memory/src/sync.ts
9211
- function computeFileHash(content) {
9212
- return `sha256:${createHash("sha256").update(content).digest("hex")}`;
10353
+ function getApiKey() {
10354
+ const config = loadConfig();
10355
+ if (!config.apiKey) {
10356
+ throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to set your API key.
10357
+ ` + "You can generate an API key at https://gethmy.com → Settings → API Keys.");
10358
+ }
10359
+ return config.apiKey;
9213
10360
  }
9214
- function slugifyTitle(title) {
9215
- return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
10361
+ function getApiUrl() {
10362
+ const config = loadConfig();
10363
+ return config.apiUrl;
9216
10364
  }
9217
- function entityToFilename(entity) {
9218
- const slug = slugifyTitle(entity.title);
9219
- const shortId = entity.id.slice(0, 8);
9220
- return `${entity.type}--${slug}--${shortId}.md`;
10365
+ function getUserEmail() {
10366
+ const config = loadConfig();
10367
+ return config.userEmail;
9221
10368
  }
9222
- function entityToDirectoryPath(entity, memoryDir) {
9223
- const wsDir = join2(memoryDir, entity.workspace_id);
9224
- if (entity.scope === "private") {
9225
- return join2(wsDir, "_private");
9226
- }
9227
- if (entity.scope === "workspace" || !entity.project_id) {
9228
- return join2(wsDir, "_workspace");
10369
+ function setActiveWorkspace(workspaceId, options) {
10370
+ if (options?.local) {
10371
+ saveLocalConfig({ workspaceId }, options.cwd);
10372
+ } else {
10373
+ saveConfig({ activeWorkspaceId: workspaceId });
9229
10374
  }
9230
- return join2(wsDir, entity.project_id);
9231
10375
  }
9232
- function emptySyncState() {
9233
- return { version: 1, lastPullAt: null, entities: {} };
10376
+ function setActiveProject(projectId, options) {
10377
+ if (options?.local) {
10378
+ saveLocalConfig({ projectId }, options.cwd);
10379
+ } else {
10380
+ saveConfig({ activeProjectId: projectId });
10381
+ }
9234
10382
  }
9235
- function loadSyncState(memoryDir) {
9236
- const statePath = join2(memoryDir, ".sync-state.json");
9237
- if (!existsSync2(statePath))
9238
- return emptySyncState();
9239
- try {
9240
- return JSON.parse(readFileSync2(statePath, "utf-8"));
9241
- } catch {
9242
- return emptySyncState();
10383
+ function getActiveWorkspaceId(cwd) {
10384
+ const localConfig = loadLocalConfig(cwd);
10385
+ if (localConfig?.workspaceId) {
10386
+ return localConfig.workspaceId;
9243
10387
  }
10388
+ return loadConfig().activeWorkspaceId;
9244
10389
  }
9245
- function saveSyncState(memoryDir, state) {
9246
- if (!existsSync2(memoryDir)) {
9247
- mkdirSync2(memoryDir, { recursive: true });
10390
+ function getActiveProjectId(cwd) {
10391
+ const localConfig = loadLocalConfig(cwd);
10392
+ if (localConfig?.projectId) {
10393
+ return localConfig.projectId;
9248
10394
  }
9249
- writeFileSync2(join2(memoryDir, ".sync-state.json"), JSON.stringify(state, null, 2));
10395
+ return loadConfig().activeProjectId;
9250
10396
  }
9251
- function writeEntityFile(entity, memoryDir) {
9252
- const dir = entityToDirectoryPath(entity, memoryDir);
9253
- if (!existsSync2(dir))
9254
- mkdirSync2(dir, { recursive: true });
9255
- const filename = entityToFilename(entity);
9256
- const filePath = join2(dir, filename);
9257
- const markdown = serializeSyncMarkdown(entity);
9258
- writeFileSync2(filePath, markdown);
9259
- return relative(memoryDir, filePath);
10397
+ function isConfigured() {
10398
+ const config = loadConfig();
10399
+ return !!config.apiKey;
9260
10400
  }
9261
- function deleteEntityFile(relPath, memoryDir) {
9262
- const absPath = join2(memoryDir, relPath);
9263
- if (existsSync2(absPath)) {
9264
- rmSync(absPath);
10401
+ function areSkillsInstalled(cwd) {
10402
+ const home = homedir();
10403
+ const workingDir = cwd || process.cwd();
10404
+ const foundPaths = [];
10405
+ const globalSkillsDir = join(home, ".agents", "skills");
10406
+ const globalSkillPath = join(globalSkillsDir, "hmy", "SKILL.md");
10407
+ if (existsSync(globalSkillPath)) {
10408
+ foundPaths.push(globalSkillPath);
10409
+ return { installed: true, location: "global", paths: foundPaths };
9265
10410
  }
9266
- }
9267
- function findMarkdownFiles(dir) {
9268
- const results = [];
9269
- if (!existsSync2(dir))
9270
- return results;
9271
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
9272
- const fullPath = join2(dir, entry.name);
9273
- if (entry.isDirectory()) {
9274
- results.push(...findMarkdownFiles(fullPath));
9275
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
9276
- results.push(fullPath);
9277
- }
10411
+ const claudeGlobalSkill = join(home, ".claude", "skills", "hmy.md");
10412
+ if (existsSync(claudeGlobalSkill)) {
10413
+ foundPaths.push(claudeGlobalSkill);
10414
+ return { installed: true, location: "global", paths: foundPaths };
9278
10415
  }
9279
- return results;
9280
- }
9281
- async function syncPull(client, config, workspaceId, projectId) {
9282
- const { memoryDir } = config;
9283
- const state = loadSyncState(memoryDir);
9284
- const result = {
9285
- pulled: 0,
9286
- pushed: 0,
9287
- deleted: 0,
9288
- conflicts: 0,
9289
- errors: []
9290
- };
9291
- const allEntities = [];
9292
- let offset = 0;
9293
- const batchSize = 100;
9294
- while (true) {
9295
- try {
9296
- const resp = await client.listMemoryEntities({
9297
- workspace_id: workspaceId,
9298
- project_id: projectId,
9299
- limit: batchSize,
9300
- offset
9301
- });
9302
- const batch = resp.entities;
9303
- allEntities.push(...batch);
9304
- if (batch.length < batchSize)
9305
- break;
9306
- offset += batchSize;
9307
- } catch (err) {
9308
- result.errors.push(`Failed to fetch entities: ${err}`);
9309
- return result;
9310
- }
10416
+ const claudeGlobalSkillAlt = join(home, ".claude", "skills", "hmy", "SKILL.md");
10417
+ if (existsSync(claudeGlobalSkillAlt)) {
10418
+ foundPaths.push(claudeGlobalSkillAlt);
10419
+ return { installed: true, location: "global", paths: foundPaths };
9311
10420
  }
9312
- const remoteIds = new Set(allEntities.map((e) => e.id));
9313
- for (const entity of allEntities) {
9314
- const existing = state.entities[entity.id];
9315
- const markdown = serializeSyncMarkdown(entity);
9316
- const hash = computeFileHash(markdown);
9317
- if (!existing) {
9318
- const relPath = writeEntityFile(entity, memoryDir);
9319
- state.entities[entity.id] = {
9320
- filePath: relPath,
9321
- remoteUpdatedAt: entity.updated_at,
9322
- lastSyncedHash: hash
9323
- };
9324
- result.pulled++;
9325
- } else {
9326
- const remoteChanged = entity.updated_at > existing.remoteUpdatedAt;
9327
- if (!remoteChanged)
9328
- continue;
9329
- const absPath = join2(memoryDir, existing.filePath);
9330
- let localChanged = false;
9331
- if (existsSync2(absPath)) {
9332
- const localContent = readFileSync2(absPath, "utf-8");
9333
- const localHash = computeFileHash(localContent);
9334
- localChanged = localHash !== existing.lastSyncedHash;
9335
- }
9336
- if (localChanged) {
9337
- result.conflicts++;
9338
- }
9339
- const newRelPath = writeEntityFile(entity, memoryDir);
9340
- if (existing.filePath !== newRelPath) {
9341
- deleteEntityFile(existing.filePath, memoryDir);
9342
- }
9343
- state.entities[entity.id] = {
9344
- filePath: newRelPath,
9345
- remoteUpdatedAt: entity.updated_at,
9346
- lastSyncedHash: hash
9347
- };
9348
- result.pulled++;
9349
- }
10421
+ const localSkillPath = join(workingDir, ".claude", "skills", "hmy.md");
10422
+ if (existsSync(localSkillPath)) {
10423
+ foundPaths.push(localSkillPath);
10424
+ return { installed: true, location: "local", paths: foundPaths };
9350
10425
  }
9351
- for (const [entityId, entry] of Object.entries(state.entities)) {
9352
- if (!remoteIds.has(entityId)) {
9353
- deleteEntityFile(entry.filePath, memoryDir);
9354
- delete state.entities[entityId];
9355
- result.deleted++;
9356
- }
10426
+ const localSkillPathAlt = join(workingDir, ".claude", "skills", "hmy", "SKILL.md");
10427
+ if (existsSync(localSkillPathAlt)) {
10428
+ foundPaths.push(localSkillPathAlt);
10429
+ return { installed: true, location: "local", paths: foundPaths };
9357
10430
  }
9358
- state.lastPullAt = new Date().toISOString();
9359
- saveSyncState(memoryDir, state);
9360
- return result;
10431
+ return { installed: false, location: null, paths: [] };
9361
10432
  }
9362
- async function syncPush(client, config, workspaceId) {
9363
- const { memoryDir } = config;
9364
- const state = loadSyncState(memoryDir);
9365
- const result = {
9366
- pulled: 0,
9367
- pushed: 0,
9368
- deleted: 0,
9369
- conflicts: 0,
9370
- errors: []
9371
- };
9372
- const mdFiles = findMarkdownFiles(memoryDir);
9373
- for (const absPath of mdFiles) {
9374
- const content = readFileSync2(absPath, "utf-8");
9375
- const hash = computeFileHash(content);
9376
- const parsed = parseSyncMarkdown(content);
9377
- if (parsed.frontmatter.id) {
9378
- const entityId = parsed.frontmatter.id;
9379
- const existing = state.entities[entityId];
9380
- if (existing && hash === existing.lastSyncedHash)
9381
- continue;
9382
- try {
9383
- const resp = await client.updateMemoryEntity(entityId, {
9384
- title: parsed.title || "Untitled",
9385
- content: parsed.content,
9386
- type: parsed.frontmatter.type,
9387
- scope: parsed.frontmatter.scope,
9388
- confidence: parsed.frontmatter.confidence,
9389
- tags: parsed.frontmatter.tags
9390
- });
9391
- const updated = resp.entity;
9392
- const newMarkdown = serializeSyncMarkdown(updated);
9393
- writeFileSync2(absPath, newMarkdown);
9394
- const relPath = relative(memoryDir, absPath);
9395
- state.entities[entityId] = {
9396
- filePath: relPath,
9397
- remoteUpdatedAt: updated.updated_at,
9398
- lastSyncedHash: computeFileHash(newMarkdown)
9399
- };
9400
- result.pushed++;
9401
- } catch (err) {
9402
- result.errors.push(`Failed to update ${entityId}: ${err}`);
9403
- }
9404
- } else {
9405
- const relPath = relative(memoryDir, absPath);
9406
- const parts = relPath.split(sep);
9407
- const fileWorkspaceId = parts.length >= 2 ? parts[0] : workspaceId;
9408
- let fileProjectId;
9409
- if (parts.length >= 3) {
9410
- const scopeDir = parts[1];
9411
- if (scopeDir !== "_workspace" && scopeDir !== "_private") {
9412
- fileProjectId = scopeDir;
9413
- }
9414
- }
9415
- let scope = parsed.frontmatter.scope || "project";
9416
- if (parts.length >= 3) {
9417
- const scopeDir = parts[1];
9418
- if (scopeDir === "_private")
9419
- scope = "private";
9420
- else if (scopeDir === "_workspace")
9421
- scope = "workspace";
9422
- }
9423
- try {
9424
- const resp = await client.createMemoryEntity({
9425
- workspace_id: fileWorkspaceId,
9426
- project_id: fileProjectId,
9427
- type: parsed.frontmatter.type,
9428
- scope,
9429
- title: parsed.title || "Untitled",
9430
- content: parsed.content,
9431
- confidence: parsed.frontmatter.confidence,
9432
- tags: parsed.frontmatter.tags,
9433
- agent_identifier: parsed.frontmatter.agent
9434
- });
9435
- const created = resp.entity;
9436
- const dir = entityToDirectoryPath(created, memoryDir);
9437
- if (!existsSync2(dir))
9438
- mkdirSync2(dir, { recursive: true });
9439
- const newFilename = entityToFilename(created);
9440
- const newAbsPath = join2(dir, newFilename);
9441
- const newMarkdown = serializeSyncMarkdown(created);
9442
- writeFileSync2(newAbsPath, newMarkdown);
9443
- if (absPath !== newAbsPath && existsSync2(absPath)) {
9444
- rmSync(absPath);
9445
- }
9446
- const newRelPath = relative(memoryDir, newAbsPath);
9447
- state.entities[created.id] = {
9448
- filePath: newRelPath,
9449
- remoteUpdatedAt: created.updated_at,
9450
- lastSyncedHash: computeFileHash(newMarkdown)
9451
- };
9452
- result.pushed++;
9453
- } catch (err) {
9454
- result.errors.push(`Failed to create entity from ${relPath}: ${err}`);
9455
- }
9456
- }
9457
- }
9458
- saveSyncState(memoryDir, state);
9459
- return result;
10433
+ function hasProjectContext(cwd) {
10434
+ const localConfig = loadLocalConfig(cwd);
10435
+ return !!(localConfig?.workspaceId || localConfig?.projectId);
9460
10436
  }
9461
- async function syncFull(client, config, workspaceId, projectId) {
9462
- const pullResult = await syncPull(client, config, workspaceId, projectId);
9463
- const pushResult = await syncPush(client, config, workspaceId);
9464
- return {
9465
- pulled: pullResult.pulled,
9466
- pushed: pushResult.pushed,
9467
- deleted: pullResult.deleted,
9468
- conflicts: pullResult.conflicts,
9469
- errors: [...pullResult.errors, ...pushResult.errors]
9470
- };
10437
+ function getMemoryDir() {
10438
+ const config = loadConfig();
10439
+ if (config.memoryDir)
10440
+ return config.memoryDir;
10441
+ return join(homedir(), ".harmony", "memory");
9471
10442
  }
10443
+
10444
+ // src/server.ts
10445
+ init_src();
10446
+
9472
10447
  // ../../node_modules/zod/v4/core/index.js
9473
10448
  var exports_core2 = {};
9474
10449
  __export(exports_core2, {
@@ -26600,6 +27575,100 @@ class HarmonyApiClient {
26600
27575
  async generateApiKey(name) {
26601
27576
  return this.request("POST", "/api-keys", { name });
26602
27577
  }
27578
+ async generateCardPrompt(options) {
27579
+ const { assembleContext: assembleContext2, cacheManifest: cacheManifest2, generatePrompt: generatePrompt2 } = await loadPromptModules();
27580
+ const cardResult = await this.getCard(options.cardId);
27581
+ const cardData = cardResult.card;
27582
+ let columnData = null;
27583
+ const projectIdForBoard = options.projectId || cardData.project_id;
27584
+ if (projectIdForBoard) {
27585
+ try {
27586
+ const board = await this.getBoard(projectIdForBoard, { summary: true });
27587
+ const column = board.columns.find((col) => col.id === cardData.column_id);
27588
+ if (column) {
27589
+ columnData = { name: column.name };
27590
+ }
27591
+ } catch {}
27592
+ }
27593
+ const variant = options.variant || "execute";
27594
+ let assembledContextStr;
27595
+ let assemblyId;
27596
+ let memories;
27597
+ try {
27598
+ if (options.workspaceId && cardData.title) {
27599
+ const cardLabels = (cardData.labels || []).map((l) => l.name);
27600
+ const taskContext = [cardData.title, cardData.description || ""].filter(Boolean).join(" ");
27601
+ const assembled = await assembleContext2({
27602
+ workspaceId: options.workspaceId,
27603
+ projectId: options.projectId,
27604
+ taskContext,
27605
+ cardLabels,
27606
+ cardId: cardData.id,
27607
+ client: this
27608
+ });
27609
+ if (assembled.context) {
27610
+ assembledContextStr = assembled.context;
27611
+ assemblyId = assembled.manifest.assemblyId;
27612
+ cacheManifest2(assembled.manifest);
27613
+ }
27614
+ }
27615
+ } catch (err) {
27616
+ const msg = err instanceof Error ? err.message : String(err);
27617
+ console.debug(`[generateCardPrompt] Context assembly failed: ${msg}`);
27618
+ try {
27619
+ if (options.workspaceId && cardData.title) {
27620
+ const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
27621
+ project_id: options.projectId,
27622
+ limit: 5
27623
+ });
27624
+ if (memoryResult.entities?.length > 0) {
27625
+ memories = memoryResult.entities.map((e) => ({
27626
+ id: e.id,
27627
+ type: e.type,
27628
+ title: e.title,
27629
+ content: e.content,
27630
+ confidence: e.confidence,
27631
+ tags: e.tags || []
27632
+ }));
27633
+ }
27634
+ }
27635
+ } catch (fallbackErr) {
27636
+ const fallbackMsg = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
27637
+ console.debug(`[generateCardPrompt] Memory fallback also failed: ${fallbackMsg}`);
27638
+ }
27639
+ }
27640
+ const result = generatePrompt2({
27641
+ card: cardData,
27642
+ column: columnData,
27643
+ variant,
27644
+ contextOptions: options.contextOptions,
27645
+ customConstraints: options.customConstraints,
27646
+ memories,
27647
+ assembledContext: assembledContextStr,
27648
+ assemblyId
27649
+ });
27650
+ return {
27651
+ ...result,
27652
+ cardId: cardData.id,
27653
+ shortId: cardData.short_id,
27654
+ title: cardData.title
27655
+ };
27656
+ }
27657
+ }
27658
+ var _promptModules = null;
27659
+ async function loadPromptModules() {
27660
+ if (!_promptModules) {
27661
+ const [ca, pb] = await Promise.all([
27662
+ Promise.resolve().then(() => (init_context_assembly(), exports_context_assembly)),
27663
+ Promise.resolve().then(() => (init_prompt_builder(), exports_prompt_builder))
27664
+ ]);
27665
+ _promptModules = {
27666
+ assembleContext: ca.assembleContext,
27667
+ cacheManifest: ca.cacheManifest,
27668
+ generatePrompt: pb.generatePrompt
27669
+ };
27670
+ }
27671
+ return _promptModules;
26603
27672
  }
26604
27673
  var client2 = null;
26605
27674
  function getClient() {
@@ -26613,6 +27682,28 @@ function resetClient() {
26613
27682
  }
26614
27683
 
26615
27684
  // src/auto-session.ts
27685
+ var CLIENT_DISPLAY_NAMES = {
27686
+ "claude-code": "Claude Code",
27687
+ "claude-desktop": "Claude Desktop",
27688
+ cursor: "Cursor",
27689
+ windsurf: "Windsurf",
27690
+ cline: "Cline",
27691
+ continue: "Continue",
27692
+ "codex-cli": "OpenAI Codex",
27693
+ zed: "Zed",
27694
+ "gemini-cli": "Gemini CLI"
27695
+ };
27696
+ function toIdentifier(name) {
27697
+ return name.toLowerCase().replace(/\s+/g, "-");
27698
+ }
27699
+ function resolveAgentIdentity(info) {
27700
+ if (!info?.name) {
27701
+ return { agentIdentifier: "unknown", agentName: "Unknown Agent" };
27702
+ }
27703
+ const key = toIdentifier(info.name);
27704
+ const displayName = CLIENT_DISPLAY_NAMES[key] ?? info.name;
27705
+ return { agentIdentifier: key, agentName: displayName };
27706
+ }
26616
27707
  var AUTO_START_TRIGGERS = new Set([
26617
27708
  "harmony_generate_prompt",
26618
27709
  "harmony_update_card",
@@ -26628,9 +27719,11 @@ var activeSessions = new Map;
26628
27719
  var inactivityTimer = null;
26629
27720
  var endCallback = null;
26630
27721
  var clientGetter = null;
26631
- function initAutoSession(callback, getClient2) {
27722
+ var clientInfoGetter = null;
27723
+ function initAutoSession(callback, getClient2, getClientInfo) {
26632
27724
  endCallback = callback;
26633
27725
  clientGetter = getClient2;
27726
+ clientInfoGetter = getClientInfo ?? null;
26634
27727
  if (inactivityTimer)
26635
27728
  clearInterval(inactivityTimer);
26636
27729
  inactivityTimer = setInterval(checkInactivity, CHECK_INTERVAL_MS);
@@ -26656,10 +27749,12 @@ async function trackActivity(cardId, options) {
26656
27749
  for (const otherCardId of toEnd) {
26657
27750
  await autoEndSession(client3, otherCardId, "completed");
26658
27751
  }
27752
+ const info = clientInfoGetter?.() ?? null;
27753
+ const { agentIdentifier, agentName } = resolveAgentIdentity(info);
26659
27754
  try {
26660
27755
  await client3.startAgentSession(cardId, {
26661
- agentIdentifier: "auto",
26662
- agentName: "Auto-detected Agent",
27756
+ agentIdentifier,
27757
+ agentName,
26663
27758
  status: "working"
26664
27759
  });
26665
27760
  } catch {}
@@ -26668,22 +27763,26 @@ async function trackActivity(cardId, options) {
26668
27763
  startedAt: now,
26669
27764
  lastActivityAt: now,
26670
27765
  isExplicit: false,
26671
- agentIdentifier: "auto",
26672
- agentName: "Auto-detected Agent"
27766
+ agentIdentifier,
27767
+ agentName
26673
27768
  });
26674
27769
  }
26675
- function markExplicit(cardId) {
27770
+ function markExplicit(cardId, options) {
26676
27771
  const existing = activeSessions.get(cardId);
26677
27772
  if (existing) {
26678
27773
  existing.isExplicit = true;
27774
+ if (options?.agentIdentifier)
27775
+ existing.agentIdentifier = options.agentIdentifier;
27776
+ if (options?.agentName)
27777
+ existing.agentName = options.agentName;
26679
27778
  } else {
26680
27779
  activeSessions.set(cardId, {
26681
27780
  cardId,
26682
27781
  startedAt: Date.now(),
26683
27782
  lastActivityAt: Date.now(),
26684
27783
  isExplicit: true,
26685
- agentIdentifier: "explicit",
26686
- agentName: "Explicit Agent"
27784
+ agentIdentifier: options?.agentIdentifier ?? "explicit",
27785
+ agentName: options?.agentName ?? "Explicit Agent"
26687
27786
  });
26688
27787
  }
26689
27788
  }
@@ -26706,6 +27805,7 @@ function destroyAutoSession() {
26706
27805
  activeSessions.clear();
26707
27806
  endCallback = null;
26708
27807
  clientGetter = null;
27808
+ clientInfoGetter = null;
26709
27809
  }
26710
27810
  function checkInactivity() {
26711
27811
  const now = Date.now();
@@ -26925,504 +28025,11 @@ function deriveClusterTitle(cluster, type) {
26925
28025
  return `Consolidated ${type}: ${suffix}`;
26926
28026
  }
26927
28027
 
26928
- // src/context-assembly.ts
26929
- var DEFAULT_TOKEN_BUDGET = 4000;
26930
- var MAX_TOKENS_PER_ENTITY = 500;
26931
- var MIN_RELEVANCE_THRESHOLD = 0.1;
26932
- var TIER_WEIGHTS = {
26933
- reference: 1,
26934
- episode: 0.7,
26935
- draft: 0.4
26936
- };
26937
- var PROCEDURE_BUDGET_FRACTION = 0.15;
26938
- var TIER_BUDGET_ALLOCATION = {
26939
- reference: 0.6,
26940
- episode: 0.3,
26941
- draft: 0.1
26942
- };
26943
- var MIN_REFERENCE_SLOTS = 3;
26944
- function estimateTokens(text) {
26945
- return Math.ceil(text.length / 4);
26946
- }
26947
- function generateAssemblyId() {
26948
- return `ctx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
26949
- }
26950
- function truncateContent(content, maxTokens) {
26951
- const currentTokens = estimateTokens(content);
26952
- if (currentTokens <= maxTokens) {
26953
- return { text: content, truncated: false };
26954
- }
26955
- const paragraphs = content.split(/\n\n+/);
26956
- let result = paragraphs[0];
26957
- for (let i = 1;i < paragraphs.length; i++) {
26958
- const lines = paragraphs[i].split(`
26959
- `).filter((l) => l.startsWith("- ") || l.startsWith("* "));
26960
- if (lines.length > 0) {
26961
- const bulletSection = lines.join(`
26962
- `);
26963
- if (estimateTokens(result + `
26964
-
26965
- ` + bulletSection) <= maxTokens) {
26966
- result += `
26967
-
26968
- ` + bulletSection;
26969
- }
26970
- }
26971
- }
26972
- if (estimateTokens(result) > maxTokens) {
26973
- const maxChars = maxTokens * 4;
26974
- result = result.slice(0, maxChars - 3) + "...";
26975
- }
26976
- return { text: result, truncated: true };
26977
- }
26978
- function computeRelevanceScore(entity, taskContext, cardLabels) {
26979
- const reasons = [];
26980
- let score = 0;
26981
- const hasRrfScore = entity.rrf_score !== undefined && entity.rrf_score > 0;
26982
- if (hasRrfScore) {
26983
- const normalizedRrf = Math.min(entity.rrf_score / 0.04, 1);
26984
- const rrfContribution = normalizedRrf * 0.3;
26985
- score += rrfContribution;
26986
- reasons.push(`hybrid_search(rrf=${entity.rrf_score.toFixed(4)})`);
26987
- }
26988
- const textMatchWeight = hasRrfScore ? 0.15 : 0.4;
26989
- const taskWords = new Set(taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
26990
- const entityWords = new Set(`${entity.title} ${entity.content}`.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
26991
- const overlap = [...taskWords].filter((w) => entityWords.has(w));
26992
- if (overlap.length > 0) {
26993
- const textScore = Math.min(overlap.length / Math.max(taskWords.size, 1), 1) * textMatchWeight;
26994
- score += textScore;
26995
- reasons.push(`text_match(${overlap.length} words)`);
26996
- }
26997
- if (cardLabels.length > 0 && entity.tags.length > 0) {
26998
- const labelSet = new Set(cardLabels.map((l) => l.toLowerCase()));
26999
- const tagOverlap = entity.tags.filter((t) => labelSet.has(t.toLowerCase()));
27000
- if (tagOverlap.length > 0) {
27001
- const tagScore = tagOverlap.length / cardLabels.length * 0.3;
27002
- score += tagScore;
27003
- reasons.push(`tag_match(${tagOverlap.join(",")})`);
27004
- }
27005
- }
27006
- score += entity.confidence * 0.15;
27007
- if (entity.confidence >= 0.9) {
27008
- reasons.push("high_confidence");
27009
- }
27010
- if (entity.last_accessed_at) {
27011
- const daysSinceAccess = (Date.now() - new Date(entity.last_accessed_at).getTime()) / (1000 * 60 * 60 * 24);
27012
- const halfLife = { draft: 7, episode: 30, reference: 180 }[entity.memory_tier];
27013
- const recencyScore = 0.5 ** (daysSinceAccess / halfLife) * 0.1;
27014
- score += recencyScore;
27015
- if (daysSinceAccess < 7)
27016
- reasons.push("recently_accessed");
27017
- }
27018
- if (entity.access_count > 0) {
27019
- const freqScore = Math.log10(entity.access_count + 1) * 0.05;
27020
- score += Math.min(freqScore, 0.1);
27021
- if (entity.access_count >= 5)
27022
- reasons.push(`frequently_used(${entity.access_count})`);
27023
- }
27024
- const usefulnessScore = entity.metadata?.usefulness_score ?? 0;
27025
- if (usefulnessScore >= 3) {
27026
- const usefulnessBoost = Math.min(usefulnessScore / 20, 0.15);
27027
- score += usefulnessBoost;
27028
- reasons.push(`useful(${usefulnessScore})`);
27029
- } else if (usefulnessScore === 0 && entity.access_count >= 5) {
27030
- score -= 0.02;
27031
- reasons.push("low_usefulness");
27032
- }
27033
- if (entity.type === "procedure") {
27034
- score += 0.1;
27035
- reasons.push("procedure_boost");
27036
- }
27037
- score = Math.min(score, 1);
27038
- const tierWeight = TIER_WEIGHTS[entity.memory_tier];
27039
- score *= tierWeight;
27040
- return { score, reasons };
27041
- }
27042
- async function assembleContext(options) {
27043
- const {
27044
- workspaceId,
27045
- projectId,
27046
- taskContext,
27047
- cardLabels = [],
27048
- tokenBudget = DEFAULT_TOKEN_BUDGET,
27049
- client: client3
27050
- } = options;
27051
- const assemblyId = generateAssemblyId();
27052
- const manifest = {
27053
- assemblyId,
27054
- timestamp: new Date().toISOString(),
27055
- included: [],
27056
- excluded: [],
27057
- budgetUsed: 0,
27058
- budgetTotal: tokenBudget,
27059
- tierBreakdown: {
27060
- draft: { count: 0, tokens: 0 },
27061
- episode: { count: 0, tokens: 0 },
27062
- reference: { count: 0, tokens: 0 }
27063
- }
27064
- };
27065
- let candidates = [];
27066
- try {
27067
- const searchResult = await client3.searchMemoryEntities(workspaceId, taskContext, { project_id: projectId, limit: 30 });
27068
- if (searchResult.entities?.length > 0) {
27069
- candidates = searchResult.entities.map(mapToContextEntity);
27070
- }
27071
- } catch {}
27072
- if (candidates.length < 10 && projectId) {
27073
- try {
27074
- const listResult = await client3.listMemoryEntities({
27075
- workspace_id: workspaceId,
27076
- project_id: projectId,
27077
- limit: 30
27078
- });
27079
- if (listResult.entities?.length > 0) {
27080
- const existingIds = new Set(candidates.map((c) => c.id));
27081
- const additional = listResult.entities.map(mapToContextEntity).filter((e) => !existingIds.has(e.id));
27082
- candidates.push(...additional);
27083
- }
27084
- } catch {}
27085
- }
27086
- if (candidates.length < 20) {
27087
- try {
27088
- const wsResult = await client3.listMemoryEntities({
27089
- workspace_id: workspaceId,
27090
- scope: "workspace",
27091
- limit: 20
27092
- });
27093
- if (wsResult.entities?.length > 0) {
27094
- const existingIds = new Set(candidates.map((c) => c.id));
27095
- const additional = wsResult.entities.map(mapToContextEntity).filter((e) => !existingIds.has(e.id));
27096
- candidates.push(...additional);
27097
- }
27098
- } catch {}
27099
- }
27100
- if (candidates.length === 0) {
27101
- return {
27102
- context: "",
27103
- manifest,
27104
- memories: []
27105
- };
27106
- }
27107
- const scored = candidates.map((entity) => {
27108
- const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels);
27109
- return { entity, score, reasons };
27110
- });
27111
- scored.sort((a, b) => b.score - a.score);
27112
- const procedureBudget = Math.floor(tokenBudget * PROCEDURE_BUDGET_FRACTION);
27113
- const remainingBudget = tokenBudget - procedureBudget;
27114
- const tierBudgets = {
27115
- reference: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.reference),
27116
- episode: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.episode),
27117
- draft: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.draft)
27118
- };
27119
- const tierUsed = {
27120
- reference: 0,
27121
- episode: 0,
27122
- draft: 0
27123
- };
27124
- let procedureUsed = 0;
27125
- const included = [];
27126
- let totalUsed = 0;
27127
- let referenceCount = 0;
27128
- for (const item of scored) {
27129
- if (item.entity.memory_tier === "reference" && item.entity.type !== "procedure" && referenceCount < MIN_REFERENCE_SLOTS) {
27130
- const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
27131
- const tokens = estimateTokens(`### ${item.entity.title}
27132
- ${text}`);
27133
- if (totalUsed + tokens <= tokenBudget) {
27134
- included.push({ ...item, tokens, truncated });
27135
- item.entity.content = text;
27136
- totalUsed += tokens;
27137
- tierUsed.reference += tokens;
27138
- referenceCount++;
27139
- }
27140
- }
27141
- }
27142
- const includedIds = new Set(included.map((i) => i.entity.id));
27143
- const procedureCandidates = scored.filter((item) => item.entity.type === "procedure" && !includedIds.has(item.entity.id));
27144
- for (const item of procedureCandidates) {
27145
- if (item.score < MIN_RELEVANCE_THRESHOLD) {
27146
- manifest.excluded.push({
27147
- entityId: item.entity.id,
27148
- title: item.entity.title,
27149
- type: item.entity.type,
27150
- tier: item.entity.memory_tier,
27151
- relevanceScore: item.score,
27152
- reason: "below_relevance_threshold"
27153
- });
27154
- continue;
27155
- }
27156
- const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
27157
- const tokens = estimateTokens(`### ${item.entity.title}
27158
- ${text}`);
27159
- if (procedureUsed + tokens > procedureBudget) {
27160
- const totalRemaining = tokenBudget - totalUsed;
27161
- if (tokens > totalRemaining) {
27162
- manifest.excluded.push({
27163
- entityId: item.entity.id,
27164
- title: item.entity.title,
27165
- type: item.entity.type,
27166
- tier: item.entity.memory_tier,
27167
- relevanceScore: item.score,
27168
- reason: "procedure_budget_exceeded"
27169
- });
27170
- continue;
27171
- }
27172
- }
27173
- if (totalUsed + tokens > tokenBudget) {
27174
- manifest.excluded.push({
27175
- entityId: item.entity.id,
27176
- title: item.entity.title,
27177
- type: item.entity.type,
27178
- tier: item.entity.memory_tier,
27179
- relevanceScore: item.score,
27180
- reason: "total_budget_exceeded"
27181
- });
27182
- continue;
27183
- }
27184
- included.push({ ...item, tokens, truncated });
27185
- item.entity.content = text;
27186
- totalUsed += tokens;
27187
- procedureUsed += tokens;
27188
- includedIds.add(item.entity.id);
27189
- }
27190
- for (const item of scored) {
27191
- if (includedIds.has(item.entity.id))
27192
- continue;
27193
- if (item.entity.type === "procedure")
27194
- continue;
27195
- if (item.score < MIN_RELEVANCE_THRESHOLD) {
27196
- manifest.excluded.push({
27197
- entityId: item.entity.id,
27198
- title: item.entity.title,
27199
- type: item.entity.type,
27200
- tier: item.entity.memory_tier,
27201
- relevanceScore: item.score,
27202
- reason: "below_relevance_threshold"
27203
- });
27204
- continue;
27205
- }
27206
- const tier = item.entity.memory_tier;
27207
- const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
27208
- const tokens = estimateTokens(`### ${item.entity.title}
27209
- ${text}`);
27210
- if (tierUsed[tier] + tokens > tierBudgets[tier]) {
27211
- const totalRemaining = tokenBudget - totalUsed;
27212
- if (tokens > totalRemaining) {
27213
- manifest.excluded.push({
27214
- entityId: item.entity.id,
27215
- title: item.entity.title,
27216
- type: item.entity.type,
27217
- tier,
27218
- relevanceScore: item.score,
27219
- reason: "budget_exceeded"
27220
- });
27221
- continue;
27222
- }
27223
- }
27224
- if (totalUsed + tokens > tokenBudget) {
27225
- manifest.excluded.push({
27226
- entityId: item.entity.id,
27227
- title: item.entity.title,
27228
- type: item.entity.type,
27229
- tier,
27230
- relevanceScore: item.score,
27231
- reason: "total_budget_exceeded"
27232
- });
27233
- continue;
27234
- }
27235
- included.push({ ...item, tokens, truncated });
27236
- item.entity.content = text;
27237
- totalUsed += tokens;
27238
- tierUsed[tier] += tokens;
27239
- includedIds.add(item.entity.id);
27240
- }
27241
- manifest.budgetUsed = totalUsed;
27242
- const procedureItems = included.filter((i) => i.entity.type === "procedure");
27243
- manifest.tierBreakdown = {
27244
- reference: {
27245
- count: included.filter((i) => i.entity.memory_tier === "reference" && i.entity.type !== "procedure").length,
27246
- tokens: tierUsed.reference
27247
- },
27248
- episode: {
27249
- count: included.filter((i) => i.entity.memory_tier === "episode" && i.entity.type !== "procedure").length,
27250
- tokens: tierUsed.episode
27251
- },
27252
- draft: {
27253
- count: included.filter((i) => i.entity.memory_tier === "draft" && i.entity.type !== "procedure").length,
27254
- tokens: tierUsed.draft
27255
- }
27256
- };
27257
- manifest.procedureBreakdown = {
27258
- count: procedureItems.length,
27259
- tokens: procedureUsed,
27260
- budget: procedureBudget
27261
- };
27262
- for (const item of included) {
27263
- manifest.included.push({
27264
- entityId: item.entity.id,
27265
- title: item.entity.title,
27266
- type: item.entity.type,
27267
- tier: item.entity.memory_tier,
27268
- relevanceScore: item.score,
27269
- reasons: item.reasons,
27270
- tokenCount: item.tokens,
27271
- truncated: item.truncated
27272
- });
27273
- }
27274
- const contextSections = [];
27275
- const nonProcedureItems = included.filter((i) => i.entity.type !== "procedure");
27276
- if (included.length > 0) {
27277
- if (procedureItems.length > 0) {
27278
- contextSections.push(`## Procedures (${procedureItems.length} loaded, ${procedureUsed}/${procedureBudget} tokens)`);
27279
- for (const item of procedureItems) {
27280
- const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
27281
- const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
27282
- contextSections.push(`
27283
- ### ${item.entity.title} (confidence: ${item.entity.confidence})${tierLabel}${tags}`);
27284
- contextSections.push(item.entity.content);
27285
- }
27286
- }
27287
- if (nonProcedureItems.length > 0) {
27288
- contextSections.push(`
27289
- ## Relevant Memories (${nonProcedureItems.length} loaded, ${manifest.excluded.length} excluded)`);
27290
- contextSections.push(`*Assembly: ${assemblyId} | Budget: ${totalUsed}/${tokenBudget} tokens*`);
27291
- for (const item of nonProcedureItems) {
27292
- const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
27293
- const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
27294
- contextSections.push(`
27295
- ### ${item.entity.title} (${item.entity.type}, confidence: ${item.entity.confidence})${tierLabel}${tags}`);
27296
- contextSections.push(item.entity.content);
27297
- }
27298
- }
27299
- }
27300
- incrementAccessCounts(client3, included.map((i) => i.entity.id)).catch(() => {});
27301
- promoteEligibleEntities(client3, included.map((i) => i.entity)).catch(() => {});
27302
- return {
27303
- context: contextSections.join(`
27304
- `),
27305
- manifest,
27306
- memories: included.map((i) => i.entity)
27307
- };
27308
- }
27309
- function mapToContextEntity(raw) {
27310
- const e = raw;
27311
- return {
27312
- id: e.id,
27313
- type: e.type,
27314
- title: e.title,
27315
- content: e.content,
27316
- confidence: e.confidence ?? 1,
27317
- tags: e.tags || [],
27318
- memory_tier: e.memory_tier || "reference",
27319
- access_count: e.access_count || 0,
27320
- last_accessed_at: e.last_accessed_at || null,
27321
- created_at: e.created_at || "",
27322
- updated_at: e.updated_at || "",
27323
- metadata: e.metadata ?? undefined,
27324
- rrf_score: e.rrf_score ?? undefined,
27325
- fts_rank: e.fts_rank ?? undefined,
27326
- semantic_rank: e.semantic_rank ?? undefined
27327
- };
27328
- }
27329
- async function incrementAccessCounts(client3, entityIds) {
27330
- if (entityIds.length === 0)
27331
- return;
27332
- try {
27333
- await client3.batchTouchMemoryEntities(entityIds);
27334
- } catch {
27335
- await Promise.allSettled(entityIds.map((id) => client3.touchMemoryEntity(id)));
27336
- }
27337
- }
27338
- async function promoteEligibleEntities(client3, entities) {
27339
- for (const entity of entities) {
27340
- if (entity.memory_tier === "reference")
27341
- continue;
27342
- if (!entity.created_at)
27343
- continue;
27344
- const promotion = checkPromotion(entity.memory_tier, entity.access_count + 1, entity.confidence, entity.created_at);
27345
- if (promotion.eligible && promotion.targetTier) {
27346
- try {
27347
- await client3.updateMemoryEntity(entity.id, {
27348
- memory_tier: promotion.targetTier,
27349
- metadata: {
27350
- ...entity.metadata || {},
27351
- promoted_at: new Date().toISOString(),
27352
- promotion_reason: promotion.reason,
27353
- promoted_from: entity.memory_tier
27354
- }
27355
- });
27356
- } catch {}
27357
- }
27358
- }
27359
- }
27360
- var manifestCache = new Map;
27361
- var MAX_CACHE_SIZE = 50;
27362
- function cacheManifest(manifest) {
27363
- if (manifestCache.size >= MAX_CACHE_SIZE) {
27364
- const firstKey = manifestCache.keys().next().value;
27365
- if (firstKey)
27366
- manifestCache.delete(firstKey);
27367
- }
27368
- manifestCache.set(manifest.assemblyId, manifest);
27369
- }
27370
- function getCachedManifest(assemblyId) {
27371
- return manifestCache.get(assemblyId);
27372
- }
27373
- var sessionAssemblyMap = new Map;
27374
- var MAX_SESSION_MAP_SIZE = 100;
27375
- function trackSessionAssembly(cardId, assemblyId) {
27376
- if (sessionAssemblyMap.size >= MAX_SESSION_MAP_SIZE) {
27377
- const firstKey = sessionAssemblyMap.keys().next().value;
27378
- if (firstKey)
27379
- sessionAssemblyMap.delete(firstKey);
27380
- }
27381
- sessionAssemblyMap.set(cardId, assemblyId);
27382
- }
27383
- async function recordContextFeedback(client3, cardId, sessionStatus, progressPercent, hadBlockers) {
27384
- const assemblyId = sessionAssemblyMap.get(cardId);
27385
- if (!assemblyId)
27386
- return { adjusted: 0 };
27387
- const manifest = manifestCache.get(assemblyId);
27388
- if (!manifest || manifest.included.length === 0)
27389
- return { adjusted: 0 };
27390
- let adjusted = 0;
27391
- const isSuccess = sessionStatus === "completed" && (progressPercent ?? 0) >= 100;
27392
- for (const entry of manifest.included) {
27393
- try {
27394
- if (isSuccess) {
27395
- const { entity } = await client3.getMemoryEntity(entry.entityId);
27396
- const e = entity;
27397
- const currentUsefulness = e.metadata?.usefulness_score ?? 0;
27398
- const newConfidence = Math.min((e.confidence ?? 0.5) + 0.05, 1);
27399
- await client3.updateMemoryEntity(entry.entityId, {
27400
- confidence: newConfidence,
27401
- metadata: {
27402
- usefulness_score: currentUsefulness + 1,
27403
- last_feedback_at: new Date().toISOString()
27404
- }
27405
- });
27406
- adjusted++;
27407
- } else if (hadBlockers) {
27408
- const { entity } = await client3.getMemoryEntity(entry.entityId);
27409
- const e = entity;
27410
- const newConfidence = Math.max((e.confidence ?? 0.5) - 0.02, 0.1);
27411
- await client3.updateMemoryEntity(entry.entityId, {
27412
- confidence: newConfidence,
27413
- metadata: {
27414
- last_feedback_at: new Date().toISOString()
27415
- }
27416
- });
27417
- adjusted++;
27418
- }
27419
- } catch {}
27420
- }
27421
- sessionAssemblyMap.delete(cardId);
27422
- return { adjusted };
27423
- }
28028
+ // src/server.ts
28029
+ init_context_assembly();
27424
28030
 
27425
28031
  // src/lifecycle-maintenance.ts
28032
+ init_src();
27426
28033
  async function runLifecycleMaintenance(client3, workspaceId, projectId) {
27427
28034
  const result = {
27428
28035
  archived: 0,
@@ -27491,428 +28098,97 @@ async function runLifecycleMaintenance(client3, workspaceId, projectId) {
27491
28098
  return result;
27492
28099
  }
27493
28100
 
27494
- // src/prompt-builder.ts
27495
- var LABEL_CATEGORY_MAP = {
27496
- bug: "bug",
27497
- fix: "bug",
27498
- hotfix: "bug",
27499
- defect: "bug",
27500
- issue: "bug",
27501
- error: "bug",
27502
- feature: "feature",
27503
- enhancement: "feature",
27504
- improvement: "feature",
27505
- new: "feature",
27506
- design: "design",
27507
- ui: "design",
27508
- ux: "design",
27509
- frontend: "design",
27510
- styling: "design",
27511
- review: "review",
27512
- "code review": "review",
27513
- pr: "review",
27514
- feedback: "review",
27515
- onboarding: "onboarding",
27516
- documentation: "onboarding",
27517
- docs: "onboarding",
27518
- guide: "onboarding",
27519
- tutorial: "onboarding",
27520
- epic: "epic",
27521
- initiative: "epic",
27522
- project: "epic",
27523
- milestone: "epic"
27524
- };
27525
- var DEFAULT_ROLE_FRAMINGS = {
27526
- bug: {
27527
- category: "bug",
27528
- role: "Senior QA Engineer and Software Developer",
27529
- perspective: "You are investigating and fixing a bug report.",
27530
- focus: [
27531
- "Root cause analysis",
27532
- "Steps to reproduce",
27533
- "Impact assessment",
27534
- "Fix implementation",
27535
- "Regression prevention",
27536
- "Test cases to prevent recurrence"
27537
- ],
27538
- outputSuggestions: [
27539
- "Bug triage summary",
27540
- "Root cause explanation",
27541
- "Fix implementation plan",
27542
- "Test cases"
27543
- ]
27544
- },
27545
- feature: {
27546
- category: "feature",
27547
- role: "Product Engineer",
27548
- perspective: "You are implementing a new feature or enhancement.",
27549
- focus: [
27550
- "User requirements",
27551
- "Technical specification",
27552
- "Implementation approach",
27553
- "Edge cases",
27554
- "Acceptance criteria",
27555
- "Integration points"
27556
- ],
27557
- outputSuggestions: [
27558
- "Technical specification",
27559
- "Implementation tasks",
27560
- "Acceptance criteria checklist",
27561
- "API design"
27562
- ]
27563
- },
27564
- design: {
27565
- category: "design",
27566
- role: "UX Designer and Frontend Developer",
27567
- perspective: "You are designing and implementing a user interface.",
27568
- focus: [
27569
- "User experience flow",
27570
- "Visual design consistency",
27571
- "Accessibility (WCAG)",
27572
- "Responsive behavior",
27573
- "Component architecture",
27574
- "Interaction patterns"
27575
- ],
27576
- outputSuggestions: [
27577
- "User flow diagram",
27578
- "Component specifications",
27579
- "Accessibility checklist",
27580
- "Responsive breakpoints"
27581
- ]
27582
- },
27583
- review: {
27584
- category: "review",
27585
- role: "Code Reviewer and Technical Lead",
27586
- perspective: "You are reviewing code for quality, correctness, and maintainability.",
27587
- focus: [
27588
- "Code correctness",
27589
- "Performance implications",
27590
- "Security considerations",
27591
- "Testing coverage",
27592
- "Documentation",
27593
- "Best practices adherence"
27594
- ],
27595
- outputSuggestions: [
27596
- "Review checklist",
27597
- "Suggested improvements",
27598
- "Test scenarios",
27599
- "Security audit"
27600
- ]
27601
- },
27602
- onboarding: {
27603
- category: "onboarding",
27604
- role: "Technical Writer and Developer Advocate",
27605
- perspective: "You are creating documentation or onboarding materials.",
27606
- focus: [
27607
- "Clear step-by-step instructions",
27608
- "Prerequisites and setup",
27609
- "Common pitfalls",
27610
- "Examples and use cases",
27611
- "Troubleshooting guide",
27612
- "Related resources"
27613
- ],
27614
- outputSuggestions: [
27615
- "Getting started guide",
27616
- "Step-by-step tutorial",
27617
- "FAQ section",
27618
- "Troubleshooting guide"
27619
- ]
27620
- },
27621
- epic: {
27622
- category: "epic",
27623
- role: "Technical Project Manager and Architect",
27624
- perspective: "You are planning and coordinating a large initiative.",
27625
- focus: [
27626
- "Scope definition",
27627
- "Task breakdown",
27628
- "Dependencies",
27629
- "Risk assessment",
27630
- "Timeline considerations",
27631
- "Success metrics"
27632
- ],
27633
- outputSuggestions: [
27634
- "Epic breakdown into stories",
27635
- "Dependency graph",
27636
- "Risk mitigation plan",
27637
- "Success criteria"
27638
- ]
27639
- },
27640
- custom: {
27641
- category: "custom",
27642
- role: "Software Engineer",
27643
- perspective: "You are working on a software task.",
27644
- focus: [
27645
- "Understanding requirements",
27646
- "Implementation approach",
27647
- "Quality considerations",
27648
- "Testing strategy"
27649
- ],
27650
- outputSuggestions: [
27651
- "Implementation plan",
27652
- "Technical notes",
27653
- "Task checklist"
27654
- ]
27655
- }
27656
- };
27657
- var VARIANT_INSTRUCTIONS = {
27658
- 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.`,
27659
- 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.`,
27660
- execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
27661
- };
27662
- function inferCategoryFromLabels(labels) {
27663
- for (const label of labels) {
27664
- const normalizedName = label.name.toLowerCase().trim();
27665
- if (LABEL_CATEGORY_MAP[normalizedName]) {
27666
- return LABEL_CATEGORY_MAP[normalizedName];
27667
- }
27668
- for (const [key, category] of Object.entries(LABEL_CATEGORY_MAP)) {
27669
- if (normalizedName.includes(key) || key.includes(normalizedName)) {
27670
- return category;
27671
- }
27672
- }
27673
- }
27674
- return "custom";
27675
- }
27676
- function getRoleFraming(category) {
27677
- return DEFAULT_ROLE_FRAMINGS[category];
27678
- }
27679
- function estimateTokens2(text) {
27680
- return Math.ceil(text.length / 4);
27681
- }
27682
- function formatSubtasks(subtasks) {
27683
- if (subtasks.length === 0)
27684
- return "";
27685
- const completed = subtasks.filter((s) => s.completed).length;
27686
- const lines = subtasks.map((s) => ` ${s.completed ? "[x]" : "[ ]"} ${s.title}`);
27687
- return `
27688
- ## Subtasks (${completed}/${subtasks.length} completed)
27689
- ${lines.join(`
27690
- `)}`;
27691
- }
27692
- function formatLabels(labels) {
27693
- if (labels.length === 0)
27694
- return "";
27695
- return `
27696
- **Labels:** ${labels.map((l) => l.name).join(", ")}`;
27697
- }
27698
- function formatLinkedCards(links) {
27699
- if (!links || links.length === 0)
27700
- return "";
27701
- const lines = links.map((link) => {
27702
- const prefix = link.direction === "outgoing" ? "->" : "<-";
27703
- return ` ${prefix} #${link.target_card.short_id}: ${link.target_card.title} (${link.display_type})`;
28101
+ // src/server.ts
28102
+ var memorySessions = new Map;
28103
+ function initMemorySession(cardId, agentIdentifier, agentName) {
28104
+ memorySessions.set(cardId, {
28105
+ cardId,
28106
+ agentIdentifier,
28107
+ agentName,
28108
+ memoryReadCount: 0,
28109
+ pendingActions: [],
28110
+ allActions: [],
28111
+ dirty: false
27704
28112
  });
27705
- return `
27706
- ## Related Cards
27707
- ${lines.join(`
27708
- `)}`;
27709
28113
  }
27710
- function generatePrompt(options) {
27711
- const {
27712
- card,
27713
- column,
27714
- variant,
27715
- customConstraints,
27716
- memories,
27717
- assembledContext,
27718
- assemblyId
27719
- } = options;
27720
- const contextOpts = {
27721
- includeTitle: true,
27722
- includeDescription: true,
27723
- includeLabels: true,
27724
- includeSubtasks: true,
27725
- includeActivity: false,
27726
- includeAssignee: true,
27727
- includeDueDate: true,
27728
- includePriority: true,
27729
- includeLinks: true,
27730
- includeColumn: true,
27731
- ...options.contextOptions
27732
- };
27733
- const labels = card.labels || [];
27734
- const subtasks = card.subtasks || [];
27735
- const links = card.links || [];
27736
- const category = inferCategoryFromLabels(labels);
27737
- const roleFraming = getRoleFraming(category);
27738
- const sections = [];
27739
- sections.push(`# Role: ${roleFraming.role}
27740
- `);
27741
- sections.push(roleFraming.perspective);
27742
- sections.push("");
27743
- sections.push(VARIANT_INSTRUCTIONS[variant]);
27744
- sections.push("");
27745
- sections.push(`# Task: ${card.title}`);
27746
- if (contextOpts.includeColumn && column) {
27747
- sections.push(`**Status:** ${column.name}`);
27748
- }
27749
- if (contextOpts.includePriority) {
27750
- sections.push(`**Priority:** ${card.priority}`);
27751
- }
27752
- if (contextOpts.includeDueDate && card.due_date) {
27753
- sections.push(`**Due:** ${card.due_date}`);
27754
- }
27755
- if (contextOpts.includeAssignee && card.assignee) {
27756
- sections.push(`**Assignee:** ${card.assignee.full_name || card.assignee.email}`);
27757
- }
27758
- if (contextOpts.includeLabels && labels.length > 0) {
27759
- sections.push(formatLabels(labels));
27760
- }
27761
- if (contextOpts.includeDescription && card.description) {
27762
- sections.push(`
27763
- ## Description
27764
- ${card.description}`);
27765
- }
27766
- if (contextOpts.includeSubtasks && subtasks.length > 0) {
27767
- sections.push(formatSubtasks(subtasks));
27768
- }
27769
- if (contextOpts.includeLinks && links.length > 0) {
27770
- sections.push(formatLinkedCards(links));
27771
- }
27772
- sections.push(`
27773
- ## Focus Areas`);
27774
- roleFraming.focus.forEach((f) => {
27775
- sections.push(`- ${f}`);
27776
- });
27777
- sections.push(`
27778
- ## Suggested Outputs`);
27779
- roleFraming.outputSuggestions.forEach((s) => {
27780
- sections.push(`- ${s}`);
27781
- });
27782
- if (assembledContext) {
27783
- sections.push(`
27784
- ${assembledContext}`);
27785
- } else if (memories && memories.length > 0) {
27786
- sections.push(`
27787
- ## Relevant Memories`);
27788
- sections.push(`*${memories.length} memories recalled from knowledge graph:*`);
27789
- for (const memory of memories) {
27790
- const tags = memory.tags.length > 0 ? ` [${memory.tags.join(", ")}]` : "";
27791
- sections.push(`
27792
- ### ${memory.title} (${memory.type}, confidence: ${memory.confidence})${tags}`);
27793
- sections.push(memory.content);
27794
- }
27795
- }
27796
- const oneThingLine = synthesizeOneThing(card, subtasks, links, assembledContext);
27797
- if (oneThingLine) {
27798
- sections.push(`
27799
- ## Recommended Next Step
27800
- ${oneThingLine}`);
27801
- }
27802
- if (variant === "execute") {
27803
- sections.push(`
27804
- ## Progress Tracking
27805
- Update your progress by calling \`harmony_update_agent_progress\` with \`currentTask\` describing what you're doing now:
27806
- - After exploring the codebase and understanding requirements (~20%)
27807
- - When you start implementing changes (~50%)
27808
- - When you move to testing or verification (~80%)
27809
- - When done, before ending the session (100%)
27810
-
27811
- Keep \`currentTask\` specific (e.g., "Refactoring auth middleware" not "Working on card").`);
27812
- }
27813
- if (customConstraints) {
27814
- sections.push(`
27815
- ## Additional Instructions
27816
- ${customConstraints}`);
27817
- }
27818
- sections.push(`
27819
- ---
27820
- *Card #${card.short_id} | Generated for ${variant} mode*`);
27821
- const prompt = sections.join(`
27822
- `);
27823
- const memoryCount = assembledContext ? (assembledContext.match(/^### /gm) || []).length : memories?.length || 0;
27824
- return {
27825
- prompt,
27826
- variant,
27827
- category,
27828
- role: roleFraming.role,
27829
- contextSummary: {
27830
- hasDescription: !!card.description,
27831
- labelCount: labels.length,
27832
- subtaskCount: subtasks.length,
27833
- completedSubtasks: subtasks.filter((s) => s.completed).length,
27834
- linkedCardCount: links.length,
27835
- memoryCount
27836
- },
27837
- tokenEstimate: estimateTokens2(prompt),
27838
- ...assemblyId && { assemblyId }
27839
- };
28114
+ function getMemorySession(cardId) {
28115
+ return memorySessions.get(cardId);
27840
28116
  }
27841
- function extractSessionInsights(assembledContext) {
27842
- const result = {
27843
- lastSessionStatus: null,
27844
- lastSessionTask: null,
27845
- lastSessionProgress: null,
27846
- blockers: [],
27847
- procedureNextStep: null
27848
- };
27849
- const sessionMatches = assembledContext.match(/### Session:.*?\n([\s\S]*?)(?=\n###|\n## |\n---|\n\*Assembly|$)/g);
27850
- if (sessionMatches && sessionMatches.length > 0) {
27851
- const latest = sessionMatches[0];
27852
- if (/Completed work on/i.test(latest)) {
27853
- result.lastSessionStatus = "completed";
27854
- } else if (/Paused work on|status:\s*paused/i.test(latest)) {
27855
- result.lastSessionStatus = "paused";
27856
- }
27857
- const taskMatch = latest.match(/Final task:\s*(.+)/);
27858
- if (taskMatch)
27859
- result.lastSessionTask = taskMatch[1].trim();
27860
- const progressMatch = latest.match(/Progress:\s*(\d+)%/);
27861
- if (progressMatch)
27862
- result.lastSessionProgress = parseInt(progressMatch[1], 10);
27863
- }
27864
- const blockerMatches = assembledContext.match(/(?:blocker|blocked by|blocking):\s*(.+)/gi);
27865
- if (blockerMatches) {
27866
- result.blockers = blockerMatches.map((m) => m.replace(/(?:blocker|blocked by|blocking):\s*/i, "").trim());
27867
- }
27868
- const stepMatches = assembledContext.match(/^\d+\.\s+(?!.*\*\*\[key step\]\*\*.*✓)(.+?)(?:\s*\*\*\[key step\]\*\*)?$/gm);
27869
- if (stepMatches && stepMatches.length > 0) {
27870
- result.procedureNextStep = stepMatches[0].replace(/^\d+\.\s+/, "").replace(/\s*\*\*\[key step\]\*\*.*$/, "").trim();
27871
- }
27872
- return result;
28117
+ function appendMemoryAction(cardId, action) {
28118
+ const session = memorySessions.get(cardId);
28119
+ if (!session)
28120
+ return;
28121
+ const truncated = action.length > 512 ? action.slice(0, 509) + "..." : action;
28122
+ const entry = { action: truncated, ts: new Date().toISOString() };
28123
+ session.pendingActions.push(entry);
28124
+ session.dirty = true;
28125
+ }
28126
+ function incrementMemoryReads(cardId) {
28127
+ const session = memorySessions.get(cardId);
28128
+ if (!session)
28129
+ return;
28130
+ session.memoryReadCount++;
28131
+ session.dirty = true;
27873
28132
  }
27874
- function synthesizeOneThing(card, subtasks, links, assembledContext) {
27875
- if (card.done)
27876
- return null;
27877
- const blockers = links.filter((l) => l.display_type === "is_blocked_by" && l.direction === "incoming");
27878
- if (blockers.length > 0) {
27879
- const blocker = blockers[0];
27880
- return `Unblock first: resolve #${blocker.target_card.short_id} "${blocker.target_card.title}" which is blocking this card.`;
27881
- }
27882
- const session = assembledContext ? extractSessionInsights(assembledContext) : null;
27883
- if (session?.blockers && session.blockers.length > 0) {
27884
- return `Resolve blocker: ${session.blockers[0]}`;
27885
- }
27886
- if (session?.lastSessionStatus === "paused" && session.lastSessionTask) {
27887
- const progress = session.lastSessionProgress ? ` (was ${session.lastSessionProgress}% complete)` : "";
27888
- return `Resume previous session${progress}: "${session.lastSessionTask}".`;
27889
- }
27890
- if (subtasks.length > 0) {
27891
- const completed = subtasks.filter((s) => s.completed).length;
27892
- if (completed === subtasks.length) {
27893
- return "All subtasks completed. Review the work and mark the card as done.";
28133
+ async function flushMemoryActions(client3, cardId) {
28134
+ const session = memorySessions.get(cardId);
28135
+ if (!session || !session.dirty)
28136
+ return;
28137
+ try {
28138
+ if (session.memoryReadCount > 0) {
28139
+ session.allActions.push({
28140
+ action: `Recalled ${session.memoryReadCount} memor${session.memoryReadCount === 1 ? "y" : "ies"}`,
28141
+ ts: new Date().toISOString()
28142
+ });
28143
+ session.memoryReadCount = 0;
27894
28144
  }
27895
- const nextSubtask = subtasks.find((s) => !s.completed);
27896
- if (nextSubtask) {
27897
- return `Work on next subtask: "${nextSubtask.title}" (${completed}/${subtasks.length} done).`;
28145
+ if (session.pendingActions.length > 0) {
28146
+ session.allActions.push(...session.pendingActions);
28147
+ session.pendingActions = [];
27898
28148
  }
28149
+ if (session.allActions.length > 10) {
28150
+ session.allActions = session.allActions.slice(-10);
28151
+ }
28152
+ await client3.updateAgentProgress(cardId, {
28153
+ agentIdentifier: session.agentIdentifier,
28154
+ agentName: session.agentName,
28155
+ recentActions: session.allActions
28156
+ });
28157
+ session.dirty = false;
28158
+ } catch (err) {
28159
+ console.error("[memory-session] flush failed:", err);
28160
+ }
28161
+ }
28162
+ function mergeMemoryActionsInto(cardId, callerActions) {
28163
+ const session = memorySessions.get(cardId);
28164
+ if (!session)
28165
+ return callerActions;
28166
+ if (session.memoryReadCount > 0) {
28167
+ session.allActions.push({
28168
+ action: `Recalled ${session.memoryReadCount} memor${session.memoryReadCount === 1 ? "y" : "ies"}`,
28169
+ ts: new Date().toISOString()
28170
+ });
28171
+ session.memoryReadCount = 0;
27899
28172
  }
27900
- if (session?.procedureNextStep) {
27901
- return `Follow procedure: ${session.procedureNextStep}`;
27902
- }
27903
- if (session?.lastSessionStatus === "completed" && session.lastSessionTask) {
27904
- return `Previous session completed ("${session.lastSessionTask}"). Review results and continue with remaining work.`;
27905
- }
27906
- if (card.due_date && (card.priority === "urgent" || card.priority === "high")) {
27907
- return `High-priority task with deadline ${card.due_date}. Start implementation immediately.`;
28173
+ if (session.pendingActions.length > 0) {
28174
+ session.allActions.push(...session.pendingActions);
28175
+ session.pendingActions = [];
27908
28176
  }
27909
- if (card.description) {
27910
- return "Analyze the description, identify the approach, and begin implementation.";
28177
+ const merged = [...callerActions, ...session.allActions];
28178
+ const trimmed = merged.length > 10 ? merged.slice(-10) : merged;
28179
+ session.allActions = trimmed;
28180
+ session.dirty = false;
28181
+ return trimmed;
28182
+ }
28183
+ function getActiveMemorySession() {
28184
+ for (const session of memorySessions.values()) {
28185
+ return session;
27911
28186
  }
27912
- return null;
28187
+ return;
28188
+ }
28189
+ function cleanupMemorySession(cardId) {
28190
+ memorySessions.delete(cardId);
27913
28191
  }
27914
-
27915
- // src/server.ts
27916
28192
  var TOOLS = {
27917
28193
  harmony_create_card: {
27918
28194
  description: "Create a new card in a Kanban column",
@@ -29426,7 +29702,7 @@ async function handleToolCall(name, args, deps) {
29426
29702
  const unauthenticatedTools = ["harmony_signup", "harmony_onboard"];
29427
29703
  if (!unauthenticatedTools.includes(name) && !deps.isConfigured()) {
29428
29704
  throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to set your API key.
29429
- ` + `You can generate an API key at https://gethmy.com → Settings → API Keys.
29705
+ ` + `You can generate an API key at https://app.gethmy.com → Settings → API Keys.
29430
29706
  ` + 'Or use "harmony_onboard" to create an account and configure automatically.');
29431
29707
  }
29432
29708
  const client3 = deps.isConfigured() ? deps.getClient() : null;
@@ -29729,7 +30005,8 @@ async function handleToolCall(name, args, deps) {
29729
30005
  currentTask: args.currentTask,
29730
30006
  estimatedMinutesRemaining: args.estimatedMinutesRemaining
29731
30007
  });
29732
- markExplicit(cardId);
30008
+ markExplicit(cardId, { agentIdentifier, agentName });
30009
+ initMemorySession(cardId, agentIdentifier, agentName);
29733
30010
  let prefetchedMemoryIds = [];
29734
30011
  try {
29735
30012
  const workspaceId = deps.getActiveWorkspaceId();
@@ -29776,6 +30053,14 @@ async function handleToolCall(name, args, deps) {
29776
30053
  const agentIdentifier = exports_external.string().min(1).max(100).parse(args.agentIdentifier);
29777
30054
  const agentName = exports_external.string().min(1).max(100).parse(args.agentName);
29778
30055
  const progressPercent = args.progressPercent !== undefined ? exports_external.number().min(0).max(100).parse(args.progressPercent) : undefined;
30056
+ const callerRecentActions = args.recentActions;
30057
+ const memSession = getMemorySession(cardId);
30058
+ let mergedRecentActions;
30059
+ if (memSession?.dirty) {
30060
+ mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions || []);
30061
+ } else if (callerRecentActions) {
30062
+ mergedRecentActions = callerRecentActions;
30063
+ }
29779
30064
  const result = await client3.updateAgentProgress(cardId, {
29780
30065
  agentIdentifier,
29781
30066
  agentName,
@@ -29783,7 +30068,8 @@ async function handleToolCall(name, args, deps) {
29783
30068
  progressPercent,
29784
30069
  currentTask: args.currentTask,
29785
30070
  blockers: args.blockers,
29786
- estimatedMinutesRemaining: args.estimatedMinutesRemaining
30071
+ estimatedMinutesRemaining: args.estimatedMinutesRemaining,
30072
+ ...mergedRecentActions && { recentActions: mergedRecentActions }
29787
30073
  });
29788
30074
  let midSessionLearnings = 0;
29789
30075
  try {
@@ -29808,6 +30094,8 @@ async function handleToolCall(name, args, deps) {
29808
30094
  const moveToColumn = args.moveToColumn;
29809
30095
  const sessionStatus = args.status || "completed";
29810
30096
  const endProgressPercent = args.progressPercent !== undefined ? exports_external.number().min(0).max(100).parse(args.progressPercent) : undefined;
30097
+ await flushMemoryActions(client3, cardId);
30098
+ cleanupMemorySession(cardId);
29811
30099
  const result = await client3.endAgentSession(cardId, {
29812
30100
  status: sessionStatus,
29813
30101
  progressPercent: endProgressPercent
@@ -29881,12 +30169,9 @@ async function handleToolCall(name, args, deps) {
29881
30169
  return { success: true, ...result };
29882
30170
  }
29883
30171
  case "harmony_generate_prompt": {
29884
- let cardData;
29885
- let columnData = null;
30172
+ let cardId;
29886
30173
  if (args.cardId) {
29887
- const cardId = exports_external.string().uuid().parse(args.cardId);
29888
- const cardResult = await client3.getCard(cardId);
29889
- cardData = cardResult.card;
30174
+ cardId = exports_external.string().uuid().parse(args.cardId);
29890
30175
  } else if (args.shortId !== undefined) {
29891
30176
  const shortId = exports_external.number().int().positive().parse(args.shortId);
29892
30177
  const projectId = args.projectId || deps.getActiveProjectId();
@@ -29894,24 +30179,10 @@ async function handleToolCall(name, args, deps) {
29894
30179
  throw new Error("Project ID required when using shortId. Use harmony_set_project_context or provide projectId.");
29895
30180
  }
29896
30181
  const cardResult = await client3.getCardByShortId(projectId, shortId);
29897
- cardData = cardResult.card;
30182
+ cardId = cardResult.card.id;
29898
30183
  } else {
29899
30184
  throw new Error("Either cardId or shortId must be provided");
29900
30185
  }
29901
- const projectIdForBoard = args.projectId || getActiveProjectId() || cardData.project_id;
29902
- if (projectIdForBoard) {
29903
- try {
29904
- const board = await client3.getBoard(projectIdForBoard, {
29905
- summary: true
29906
- });
29907
- const columnId = cardData.column_id;
29908
- const column = board.columns.find((col) => col.id === columnId);
29909
- if (column) {
29910
- columnData = { name: column.name };
29911
- }
29912
- } catch {}
29913
- }
29914
- const variant = args.variant || "execute";
29915
30186
  const contextOptions = {};
29916
30187
  if (args.includeSubtasks !== undefined) {
29917
30188
  contextOptions.includeSubtasks = args.includeSubtasks === true || args.includeSubtasks === "true";
@@ -29922,67 +30193,19 @@ async function handleToolCall(name, args, deps) {
29922
30193
  if (args.includeDescription !== undefined) {
29923
30194
  contextOptions.includeDescription = args.includeDescription === true || args.includeDescription === "true";
29924
30195
  }
29925
- let assembledContextStr;
29926
- let assemblyId;
29927
- let memories;
29928
- try {
29929
- const workspaceId = deps.getActiveWorkspaceId();
29930
- if (workspaceId && cardData.title) {
29931
- const cardLabels = (cardData.labels || []).map((l) => l.name);
29932
- const taskContext = [cardData.title, cardData.description || ""].filter(Boolean).join(" ");
29933
- const assembled = await assembleContext({
29934
- workspaceId,
29935
- projectId: getActiveProjectId() || undefined,
29936
- taskContext,
29937
- cardLabels,
29938
- cardId: cardData.id,
29939
- client: client3
29940
- });
29941
- if (assembled.context) {
29942
- assembledContextStr = assembled.context;
29943
- assemblyId = assembled.manifest.assemblyId;
29944
- cacheManifest(assembled.manifest);
29945
- }
29946
- }
29947
- } catch {
29948
- try {
29949
- const workspaceId = deps.getActiveWorkspaceId();
29950
- if (workspaceId && cardData.title) {
29951
- const memoryResult = await client3.searchMemoryEntities(workspaceId, cardData.title, {
29952
- project_id: getActiveProjectId() || undefined,
29953
- limit: 5
29954
- });
29955
- if (memoryResult.entities?.length > 0) {
29956
- memories = memoryResult.entities.map((e) => {
29957
- const entity = e;
29958
- return {
29959
- id: entity.id,
29960
- type: entity.type,
29961
- title: entity.title,
29962
- content: entity.content,
29963
- confidence: entity.confidence,
29964
- tags: entity.tags || []
29965
- };
29966
- });
29967
- }
29968
- }
29969
- } catch {}
29970
- }
29971
- const result = generatePrompt({
29972
- card: cardData,
29973
- column: columnData,
29974
- variant,
29975
- contextOptions,
30196
+ const result = await client3.generateCardPrompt({
30197
+ cardId,
30198
+ workspaceId: deps.getActiveWorkspaceId() || "",
30199
+ projectId: args.projectId || getActiveProjectId() || undefined,
30200
+ variant: args.variant || "execute",
29976
30201
  customConstraints: args.customConstraints,
29977
- memories,
29978
- assembledContext: assembledContextStr,
29979
- assemblyId
30202
+ contextOptions
29980
30203
  });
30204
+ if (result.assemblyId) {
30205
+ trackSessionAssembly(cardId, result.assemblyId);
30206
+ }
29981
30207
  return {
29982
30208
  success: true,
29983
- cardId: cardData.id,
29984
- shortId: cardData.short_id,
29985
- title: cardData.title,
29986
30209
  ...result
29987
30210
  };
29988
30211
  }
@@ -29995,6 +30218,7 @@ async function handleToolCall(name, args, deps) {
29995
30218
  }
29996
30219
  const entityType = args.type || "context";
29997
30220
  const entityTags = args.tags || [];
30221
+ const activeMemSession = getActiveMemorySession();
29998
30222
  const result = await client3.createMemoryEntity({
29999
30223
  workspace_id: workspaceId,
30000
30224
  project_id: args.projectId || deps.getActiveProjectId() || undefined,
@@ -30006,7 +30230,7 @@ async function handleToolCall(name, args, deps) {
30006
30230
  metadata: args.metadata,
30007
30231
  confidence: args.confidence !== undefined ? exports_external.number().min(0).max(1).parse(args.confidence) : undefined,
30008
30232
  tags: entityTags.length > 0 ? entityTags : undefined,
30009
- agent_identifier: "claude-code"
30233
+ agent_identifier: activeMemSession?.agentIdentifier || undefined
30010
30234
  });
30011
30235
  const newEntityIdForGraph = result.entity?.id;
30012
30236
  if (newEntityIdForGraph) {
@@ -30019,6 +30243,10 @@ async function handleToolCall(name, args, deps) {
30019
30243
  potentialContradictions = await detectContradictions(client3, newEntityId, entityType, title, content, entityTags, workspaceId, args.projectId || deps.getActiveProjectId() || undefined);
30020
30244
  } catch {}
30021
30245
  }
30246
+ if (activeMemSession) {
30247
+ appendMemoryAction(activeMemSession.cardId, `Stored memory: ${title}`);
30248
+ flushMemoryActions(client3, activeMemSession.cardId).catch(() => {});
30249
+ }
30022
30250
  return {
30023
30251
  success: true,
30024
30252
  ...result,
@@ -30082,6 +30310,10 @@ async function handleToolCall(name, args, deps) {
30082
30310
  } catch (_) {}
30083
30311
  })).catch(() => {});
30084
30312
  }
30313
+ const recallMemSession = getActiveMemorySession();
30314
+ if (recallMemSession) {
30315
+ incrementMemoryReads(recallMemSession.cardId);
30316
+ }
30085
30317
  return markdown || "No memories found.";
30086
30318
  }
30087
30319
  case "harmony_update_memory": {
@@ -30102,11 +30334,29 @@ async function handleToolCall(name, args, deps) {
30102
30334
  if (args.metadata !== undefined)
30103
30335
  updates.metadata = args.metadata;
30104
30336
  const result = await client3.updateMemoryEntity(entityId, updates);
30337
+ const updateMemSession = getActiveMemorySession();
30338
+ if (updateMemSession) {
30339
+ const updateTitle = updates.title || entityId.slice(0, 8);
30340
+ appendMemoryAction(updateMemSession.cardId, `Updated memory: ${updateTitle}`);
30341
+ flushMemoryActions(client3, updateMemSession.cardId).catch(() => {});
30342
+ }
30105
30343
  return { success: true, ...result };
30106
30344
  }
30107
30345
  case "harmony_forget": {
30108
30346
  const entityId = exports_external.string().uuid().parse(args.entityId);
30347
+ let forgetTitle = null;
30348
+ const forgetMemSession = getActiveMemorySession();
30349
+ if (forgetMemSession) {
30350
+ try {
30351
+ const { entity } = await client3.getMemoryEntity(entityId);
30352
+ forgetTitle = entity?.title || null;
30353
+ } catch {}
30354
+ }
30109
30355
  await client3.deleteMemoryEntity(entityId);
30356
+ if (forgetMemSession) {
30357
+ appendMemoryAction(forgetMemSession.cardId, `Removed memory: ${forgetTitle || entityId.slice(0, 8)}`);
30358
+ flushMemoryActions(client3, forgetMemSession.cardId).catch(() => {});
30359
+ }
30110
30360
  return { success: true };
30111
30361
  }
30112
30362
  case "harmony_relate": {
@@ -30128,6 +30378,11 @@ async function handleToolCall(name, args, deps) {
30128
30378
  relation_type: relationType,
30129
30379
  confidence: args.confidence !== undefined ? exports_external.number().min(0).max(1).parse(args.confidence) : undefined
30130
30380
  });
30381
+ const relateMemSession = getActiveMemorySession();
30382
+ if (relateMemSession) {
30383
+ appendMemoryAction(relateMemSession.cardId, `Linked memories: ${relationType}`);
30384
+ flushMemoryActions(client3, relateMemSession.cardId).catch(() => {});
30385
+ }
30131
30386
  return { success: true, ...result };
30132
30387
  }
30133
30388
  case "harmony_memory_search": {
@@ -30141,6 +30396,10 @@ async function handleToolCall(name, args, deps) {
30141
30396
  type: args.type,
30142
30397
  limit: args.limit
30143
30398
  });
30399
+ const searchMemSession = getActiveMemorySession();
30400
+ if (searchMemSession) {
30401
+ incrementMemoryReads(searchMemSession.cardId);
30402
+ }
30144
30403
  return markdown || "No memories found.";
30145
30404
  }
30146
30405
  case "harmony_vault_index": {
@@ -30235,7 +30494,7 @@ async function handleToolCall(name, args, deps) {
30235
30494
  source: args.source || "agent",
30236
30495
  tasks: args.tasks
30237
30496
  });
30238
- const planUrl = `https://gethmy.com/plans/${result.plan.id}`;
30497
+ const planUrl = `https://app.gethmy.com/plans/${result.plan.id}`;
30239
30498
  return {
30240
30499
  success: true,
30241
30500
  planId: result.plan.id,
@@ -30730,7 +30989,10 @@ class HarmonyMCPServer {
30730
30989
  const configDeps = createConfigDeps();
30731
30990
  initAutoSession(async (client3, cardId, status) => {
30732
30991
  await runEndSessionPipeline(client3, configDeps, cardId, status);
30733
- }, () => getClient());
30992
+ }, () => getClient(), () => {
30993
+ const cv = this.server.getClientVersion();
30994
+ return cv ? { name: cv.name, version: cv.version } : null;
30995
+ });
30734
30996
  const handleShutdown = async () => {
30735
30997
  try {
30736
30998
  await shutdownAllSessions();
@@ -31000,7 +31262,7 @@ Once you have the plan ID, call \`harmony_get_plan\` to fetch the full plan with
31000
31262
  ## [Plan Title]
31001
31263
  **Status:** draft/active/archived | **Phase:** plan/execute/verify/done
31002
31264
  **Tasks:** N total (X pending, Y in_progress, Z completed)
31003
- **URL:** https://gethmy.com/plans/{id}
31265
+ **URL:** https://app.gethmy.com/plans/{id}
31004
31266
  \\\`\\\`\\\`
31005
31267
 
31006
31268
  If plan is in execute phase and tasks already have linked cards, note which tasks have cards and which don't.
@@ -31034,7 +31296,7 @@ Only offer **(A) Single card**, **(C) Analyze only**, or **(D) Skip**.
31034
31296
  #### Option A — Single card
31035
31297
  1. Call \`harmony_create_card\` with:
31036
31298
  - \`title\`: Plan title
31037
- - \`description\`: Brief 2-3 sentence summary of the plan + \`\\n\\n[View plan](https://gethmy.com/plans/{planId})\`
31299
+ - \`description\`: Brief 2-3 sentence summary of the plan + \`\\n\\n[View plan](https://app.gethmy.com/plans/{planId})\`
31038
31300
  - \`priority\`: based on plan task priorities (use highest)
31039
31301
  2. Call \`harmony_update_plan\` to set \`status: "active"\`, \`workflowPhase: "execute"\`
31040
31302
 
@@ -31156,7 +31418,7 @@ Call \`harmony_create_plan\` with:
31156
31418
  ### 2B.5 — User Approval
31157
31419
 
31158
31420
  Show the user:
31159
- - Plan URL: \`https://gethmy.com/plans/{id}\`
31421
+ - Plan URL: \`https://app.gethmy.com/plans/{id}\`
31160
31422
  - Number of tasks created
31161
31423
  - Brief summary
31162
31424
 
@@ -32936,7 +33198,7 @@ function getWriteSummary(files, options = {}) {
32936
33198
 
32937
33199
  // src/tui/setup.ts
32938
33200
  var GLOBAL_SKILLS_DIR = join5(homedir4(), ".agents", "skills");
32939
- var API_URL = "https://gethmy.com/api";
33201
+ var API_URL = "https://app.gethmy.com/api";
32940
33202
  async function registerMcpServer() {
32941
33203
  try {
32942
33204
  const { execSync } = await import("node:child_process");
@@ -33286,7 +33548,7 @@ async function runSetup(options = {}) {
33286
33548
  if (!validation.valid) {
33287
33549
  spinner.stop(colors.error("API key validation failed"));
33288
33550
  M2.error(validation.error || "Could not connect to Harmony API");
33289
- M2.info("Get an API key at: https://gethmy.com/user/keys");
33551
+ M2.info("Get an API key at: https://app.gethmy.com/user/keys");
33290
33552
  process.exit(1);
33291
33553
  }
33292
33554
  if (!userEmail) {
@@ -33586,7 +33848,7 @@ async function runSetup(options = {}) {
33586
33848
  }
33587
33849
  console.log("");
33588
33850
  console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
33589
- console.log(` ${colors.dim("Need help? Visit https://gethmy.com/docs/mcp")}`);
33851
+ console.log(` ${colors.dim("Need help? Visit https://app.gethmy.com/docs/mcp")}`);
33590
33852
  console.log("");
33591
33853
  }
33592
33854
 
@@ -33650,7 +33912,7 @@ Context:`);
33650
33912
  console.log(`Status: Not configured
33651
33913
  `);
33652
33914
  console.log("Run: npx @gethmy/mcp setup");
33653
- console.log("Get an API key at: https://gethmy.com/user/keys");
33915
+ console.log("Get an API key at: https://app.gethmy.com/user/keys");
33654
33916
  }
33655
33917
  });
33656
33918
  program.command("reset").description("Remove stored configuration").action(() => {