harmony-mcp 1.9.1 → 1.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +1864 -178
  2. package/dist/index.js +1816 -130
  3. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -14210,7 +14210,8 @@ function loadConfig() {
14210
14210
  apiUrl: DEFAULT_API_URL,
14211
14211
  activeWorkspaceId: null,
14212
14212
  activeProjectId: null,
14213
- userEmail: null
14213
+ userEmail: null,
14214
+ memoryDir: null
14214
14215
  };
14215
14216
  }
14216
14217
  try {
@@ -14221,7 +14222,8 @@ function loadConfig() {
14221
14222
  apiUrl: config.apiUrl || DEFAULT_API_URL,
14222
14223
  activeWorkspaceId: config.activeWorkspaceId || null,
14223
14224
  activeProjectId: config.activeProjectId || null,
14224
- userEmail: config.userEmail || null
14225
+ userEmail: config.userEmail || null,
14226
+ memoryDir: config.memoryDir || null
14225
14227
  };
14226
14228
  } catch {
14227
14229
  return {
@@ -14229,7 +14231,8 @@ function loadConfig() {
14229
14231
  apiUrl: DEFAULT_API_URL,
14230
14232
  activeWorkspaceId: null,
14231
14233
  activeProjectId: null,
14232
- userEmail: null
14234
+ userEmail: null,
14235
+ memoryDir: null
14233
14236
  };
14234
14237
  }
14235
14238
  }
@@ -14362,6 +14365,12 @@ function hasProjectContext(cwd) {
14362
14365
  const localConfig = loadLocalConfig(cwd);
14363
14366
  return !!(localConfig?.workspaceId || localConfig?.projectId);
14364
14367
  }
14368
+ function getMemoryDir() {
14369
+ const config = loadConfig();
14370
+ if (config.memoryDir)
14371
+ return config.memoryDir;
14372
+ return join(homedir(), ".harmony", "memory");
14373
+ }
14365
14374
 
14366
14375
  // ../../node_modules/zod/v4/core/index.js
14367
14376
  var exports_core2 = {};
@@ -30482,6 +30491,33 @@ class HarmonyApiClient {
30482
30491
  params.set("limit", String(options.limit));
30483
30492
  return this.request("GET", `/memory/search?${params.toString()}`);
30484
30493
  }
30494
+ async getVaultIndex(options) {
30495
+ const params = new URLSearchParams;
30496
+ params.set("workspace_id", options.workspace_id);
30497
+ if (options.project_id)
30498
+ params.set("project_id", options.project_id);
30499
+ if (options.type)
30500
+ params.set("type", options.type);
30501
+ if (options.limit !== undefined)
30502
+ params.set("limit", String(options.limit));
30503
+ return this.request("GET", `/memory/index?${params.toString()}`);
30504
+ }
30505
+ async getVaultIndexMarkdown(options) {
30506
+ const params = new URLSearchParams;
30507
+ params.set("workspace_id", options.workspace_id);
30508
+ if (options.project_id)
30509
+ params.set("project_id", options.project_id);
30510
+ if (options.type)
30511
+ params.set("type", options.type);
30512
+ if (options.limit !== undefined)
30513
+ params.set("limit", String(options.limit));
30514
+ return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
30515
+ accept: "text/markdown"
30516
+ });
30517
+ }
30518
+ async resolveLinks(options) {
30519
+ return this.request("POST", "/memory/resolve-links", options);
30520
+ }
30485
30521
  async listMemoryEntitiesMarkdown(options) {
30486
30522
  const params = new URLSearchParams;
30487
30523
  params.set("workspace_id", options.workspace_id);
@@ -30549,7 +30585,559 @@ function getClient() {
30549
30585
  }
30550
30586
  return client;
30551
30587
  }
30552
-
30588
+ // ../memory/src/sync-storage.ts
30589
+ function parseSyncMarkdown(markdown) {
30590
+ const trimmed = markdown.trim();
30591
+ let frontmatter = {
30592
+ type: "context",
30593
+ scope: "project",
30594
+ tier: "reference",
30595
+ confidence: 1,
30596
+ tags: []
30597
+ };
30598
+ let body = trimmed;
30599
+ const fmMatch = trimmed.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
30600
+ if (fmMatch) {
30601
+ frontmatter = parseSyncYamlFrontmatter(fmMatch[1]);
30602
+ body = fmMatch[2].trim();
30603
+ }
30604
+ let title = "";
30605
+ const titleMatch = body.match(/^#\s+(.+)/m);
30606
+ if (titleMatch) {
30607
+ title = titleMatch[1].trim();
30608
+ body = body.replace(/^#\s+.+\n?/, "").trim();
30609
+ }
30610
+ return { frontmatter, title, content: body };
30611
+ }
30612
+ function serializeSyncMarkdown(entity) {
30613
+ const lines = ["---"];
30614
+ lines.push(`id: ${entity.id}`);
30615
+ lines.push(`workspace_id: ${entity.workspace_id}`);
30616
+ if (entity.project_id) {
30617
+ lines.push(`project_id: ${entity.project_id}`);
30618
+ }
30619
+ lines.push(`type: ${entity.type}`);
30620
+ lines.push(`scope: ${entity.scope}`);
30621
+ lines.push(`tier: ${entity.memory_tier || "reference"}`);
30622
+ lines.push(`confidence: ${entity.confidence}`);
30623
+ if (entity.tags.length > 0) {
30624
+ lines.push(`tags: [${entity.tags.join(", ")}]`);
30625
+ } else {
30626
+ lines.push("tags: []");
30627
+ }
30628
+ if (entity.agent_identifier) {
30629
+ lines.push(`agent: ${entity.agent_identifier}`);
30630
+ }
30631
+ lines.push(`created_at: ${entity.created_at}`);
30632
+ lines.push(`updated_at: ${entity.updated_at}`);
30633
+ lines.push("---");
30634
+ lines.push("");
30635
+ lines.push(`# ${entity.title}`);
30636
+ lines.push("");
30637
+ lines.push(entity.content);
30638
+ return lines.join(`
30639
+ `);
30640
+ }
30641
+ function parseSyncYamlFrontmatter(yaml) {
30642
+ const result = {
30643
+ type: "context",
30644
+ scope: "project",
30645
+ tier: "reference",
30646
+ confidence: 1,
30647
+ tags: []
30648
+ };
30649
+ for (const line of yaml.split(`
30650
+ `)) {
30651
+ const colonIndex = line.indexOf(":");
30652
+ if (colonIndex === -1)
30653
+ continue;
30654
+ const key = line.slice(0, colonIndex).trim();
30655
+ const value = line.slice(colonIndex + 1).trim();
30656
+ switch (key) {
30657
+ case "id":
30658
+ result.id = value;
30659
+ break;
30660
+ case "workspace_id":
30661
+ result.workspace_id = value;
30662
+ break;
30663
+ case "project_id":
30664
+ result.project_id = value;
30665
+ break;
30666
+ case "type":
30667
+ result.type = value;
30668
+ break;
30669
+ case "scope":
30670
+ result.scope = value;
30671
+ break;
30672
+ case "tier":
30673
+ result.tier = value;
30674
+ break;
30675
+ case "confidence":
30676
+ result.confidence = parseFloat(value) || 1;
30677
+ break;
30678
+ case "tags":
30679
+ result.tags = parseYamlArray(value);
30680
+ break;
30681
+ case "related":
30682
+ result.related = parseYamlArray(value);
30683
+ break;
30684
+ case "agent":
30685
+ result.agent = value;
30686
+ break;
30687
+ case "created_at":
30688
+ result.created_at = value;
30689
+ break;
30690
+ case "updated_at":
30691
+ result.updated_at = value;
30692
+ break;
30693
+ }
30694
+ }
30695
+ return result;
30696
+ }
30697
+ function parseYamlArray(value) {
30698
+ const match = value.match(/^\[(.*)]\s*$/);
30699
+ if (!match)
30700
+ return [];
30701
+ return match[1].split(",").map((s) => s.trim()).filter(Boolean);
30702
+ }
30703
+ // ../memory/src/sync.ts
30704
+ import { createHash } from "node:crypto";
30705
+ import {
30706
+ existsSync as existsSync2,
30707
+ mkdirSync as mkdirSync2,
30708
+ readFileSync as readFileSync2,
30709
+ readdirSync,
30710
+ rmSync,
30711
+ writeFileSync as writeFileSync2
30712
+ } from "node:fs";
30713
+ import { join as join2, relative, sep } from "node:path";
30714
+ function computeFileHash(content) {
30715
+ return `sha256:${createHash("sha256").update(content).digest("hex")}`;
30716
+ }
30717
+ function slugifyTitle(title) {
30718
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
30719
+ }
30720
+ function entityToFilename(entity) {
30721
+ const slug = slugifyTitle(entity.title);
30722
+ const shortId = entity.id.slice(0, 8);
30723
+ return `${entity.type}--${slug}--${shortId}.md`;
30724
+ }
30725
+ function entityToDirectoryPath(entity, memoryDir) {
30726
+ const wsDir = join2(memoryDir, entity.workspace_id);
30727
+ if (entity.scope === "private") {
30728
+ return join2(wsDir, "_private");
30729
+ }
30730
+ if (entity.scope === "workspace" || !entity.project_id) {
30731
+ return join2(wsDir, "_workspace");
30732
+ }
30733
+ return join2(wsDir, entity.project_id);
30734
+ }
30735
+ function emptySyncState() {
30736
+ return { version: 1, lastPullAt: null, entities: {} };
30737
+ }
30738
+ function loadSyncState(memoryDir) {
30739
+ const statePath = join2(memoryDir, ".sync-state.json");
30740
+ if (!existsSync2(statePath))
30741
+ return emptySyncState();
30742
+ try {
30743
+ return JSON.parse(readFileSync2(statePath, "utf-8"));
30744
+ } catch {
30745
+ return emptySyncState();
30746
+ }
30747
+ }
30748
+ function saveSyncState(memoryDir, state) {
30749
+ if (!existsSync2(memoryDir)) {
30750
+ mkdirSync2(memoryDir, { recursive: true });
30751
+ }
30752
+ writeFileSync2(join2(memoryDir, ".sync-state.json"), JSON.stringify(state, null, 2));
30753
+ }
30754
+ function writeEntityFile(entity, memoryDir) {
30755
+ const dir = entityToDirectoryPath(entity, memoryDir);
30756
+ if (!existsSync2(dir))
30757
+ mkdirSync2(dir, { recursive: true });
30758
+ const filename = entityToFilename(entity);
30759
+ const filePath = join2(dir, filename);
30760
+ const markdown = serializeSyncMarkdown(entity);
30761
+ writeFileSync2(filePath, markdown);
30762
+ return relative(memoryDir, filePath);
30763
+ }
30764
+ function deleteEntityFile(relPath, memoryDir) {
30765
+ const absPath = join2(memoryDir, relPath);
30766
+ if (existsSync2(absPath)) {
30767
+ rmSync(absPath);
30768
+ }
30769
+ }
30770
+ function findMarkdownFiles(dir) {
30771
+ const results = [];
30772
+ if (!existsSync2(dir))
30773
+ return results;
30774
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
30775
+ const fullPath = join2(dir, entry.name);
30776
+ if (entry.isDirectory()) {
30777
+ results.push(...findMarkdownFiles(fullPath));
30778
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
30779
+ results.push(fullPath);
30780
+ }
30781
+ }
30782
+ return results;
30783
+ }
30784
+ async function syncPull(client2, config2, workspaceId, projectId) {
30785
+ const { memoryDir } = config2;
30786
+ const state = loadSyncState(memoryDir);
30787
+ const result = {
30788
+ pulled: 0,
30789
+ pushed: 0,
30790
+ deleted: 0,
30791
+ conflicts: 0,
30792
+ errors: []
30793
+ };
30794
+ const allEntities = [];
30795
+ let offset = 0;
30796
+ const batchSize = 100;
30797
+ while (true) {
30798
+ try {
30799
+ const resp = await client2.listMemoryEntities({
30800
+ workspace_id: workspaceId,
30801
+ project_id: projectId,
30802
+ limit: batchSize,
30803
+ offset
30804
+ });
30805
+ const batch = resp.entities;
30806
+ allEntities.push(...batch);
30807
+ if (batch.length < batchSize)
30808
+ break;
30809
+ offset += batchSize;
30810
+ } catch (err) {
30811
+ result.errors.push(`Failed to fetch entities: ${err}`);
30812
+ return result;
30813
+ }
30814
+ }
30815
+ const remoteIds = new Set(allEntities.map((e) => e.id));
30816
+ for (const entity of allEntities) {
30817
+ const existing = state.entities[entity.id];
30818
+ const markdown = serializeSyncMarkdown(entity);
30819
+ const hash2 = computeFileHash(markdown);
30820
+ if (!existing) {
30821
+ const relPath = writeEntityFile(entity, memoryDir);
30822
+ state.entities[entity.id] = {
30823
+ filePath: relPath,
30824
+ remoteUpdatedAt: entity.updated_at,
30825
+ lastSyncedHash: hash2
30826
+ };
30827
+ result.pulled++;
30828
+ } else {
30829
+ const remoteChanged = entity.updated_at > existing.remoteUpdatedAt;
30830
+ if (!remoteChanged)
30831
+ continue;
30832
+ const absPath = join2(memoryDir, existing.filePath);
30833
+ let localChanged = false;
30834
+ if (existsSync2(absPath)) {
30835
+ const localContent = readFileSync2(absPath, "utf-8");
30836
+ const localHash = computeFileHash(localContent);
30837
+ localChanged = localHash !== existing.lastSyncedHash;
30838
+ }
30839
+ if (localChanged) {
30840
+ result.conflicts++;
30841
+ }
30842
+ const newRelPath = writeEntityFile(entity, memoryDir);
30843
+ if (existing.filePath !== newRelPath) {
30844
+ deleteEntityFile(existing.filePath, memoryDir);
30845
+ }
30846
+ state.entities[entity.id] = {
30847
+ filePath: newRelPath,
30848
+ remoteUpdatedAt: entity.updated_at,
30849
+ lastSyncedHash: hash2
30850
+ };
30851
+ result.pulled++;
30852
+ }
30853
+ }
30854
+ for (const [entityId, entry] of Object.entries(state.entities)) {
30855
+ if (!remoteIds.has(entityId)) {
30856
+ deleteEntityFile(entry.filePath, memoryDir);
30857
+ delete state.entities[entityId];
30858
+ result.deleted++;
30859
+ }
30860
+ }
30861
+ state.lastPullAt = new Date().toISOString();
30862
+ saveSyncState(memoryDir, state);
30863
+ return result;
30864
+ }
30865
+ async function syncPush(client2, config2, workspaceId) {
30866
+ const { memoryDir } = config2;
30867
+ const state = loadSyncState(memoryDir);
30868
+ const result = {
30869
+ pulled: 0,
30870
+ pushed: 0,
30871
+ deleted: 0,
30872
+ conflicts: 0,
30873
+ errors: []
30874
+ };
30875
+ const mdFiles = findMarkdownFiles(memoryDir);
30876
+ for (const absPath of mdFiles) {
30877
+ const content = readFileSync2(absPath, "utf-8");
30878
+ const hash2 = computeFileHash(content);
30879
+ const parsed = parseSyncMarkdown(content);
30880
+ if (parsed.frontmatter.id) {
30881
+ const entityId = parsed.frontmatter.id;
30882
+ const existing = state.entities[entityId];
30883
+ if (existing && hash2 === existing.lastSyncedHash)
30884
+ continue;
30885
+ try {
30886
+ const resp = await client2.updateMemoryEntity(entityId, {
30887
+ title: parsed.title || "Untitled",
30888
+ content: parsed.content,
30889
+ type: parsed.frontmatter.type,
30890
+ scope: parsed.frontmatter.scope,
30891
+ confidence: parsed.frontmatter.confidence,
30892
+ tags: parsed.frontmatter.tags
30893
+ });
30894
+ const updated = resp.entity;
30895
+ const newMarkdown = serializeSyncMarkdown(updated);
30896
+ writeFileSync2(absPath, newMarkdown);
30897
+ const relPath = relative(memoryDir, absPath);
30898
+ state.entities[entityId] = {
30899
+ filePath: relPath,
30900
+ remoteUpdatedAt: updated.updated_at,
30901
+ lastSyncedHash: computeFileHash(newMarkdown)
30902
+ };
30903
+ result.pushed++;
30904
+ } catch (err) {
30905
+ result.errors.push(`Failed to update ${entityId}: ${err}`);
30906
+ }
30907
+ } else {
30908
+ const relPath = relative(memoryDir, absPath);
30909
+ const parts = relPath.split(sep);
30910
+ const fileWorkspaceId = parts.length >= 2 ? parts[0] : workspaceId;
30911
+ let fileProjectId;
30912
+ if (parts.length >= 3) {
30913
+ const scopeDir = parts[1];
30914
+ if (scopeDir !== "_workspace" && scopeDir !== "_private") {
30915
+ fileProjectId = scopeDir;
30916
+ }
30917
+ }
30918
+ let scope = parsed.frontmatter.scope || "project";
30919
+ if (parts.length >= 3) {
30920
+ const scopeDir = parts[1];
30921
+ if (scopeDir === "_private")
30922
+ scope = "private";
30923
+ else if (scopeDir === "_workspace")
30924
+ scope = "workspace";
30925
+ }
30926
+ try {
30927
+ const resp = await client2.createMemoryEntity({
30928
+ workspace_id: fileWorkspaceId,
30929
+ project_id: fileProjectId,
30930
+ type: parsed.frontmatter.type,
30931
+ scope,
30932
+ title: parsed.title || "Untitled",
30933
+ content: parsed.content,
30934
+ confidence: parsed.frontmatter.confidence,
30935
+ tags: parsed.frontmatter.tags,
30936
+ agent_identifier: parsed.frontmatter.agent
30937
+ });
30938
+ const created = resp.entity;
30939
+ const dir = entityToDirectoryPath(created, memoryDir);
30940
+ if (!existsSync2(dir))
30941
+ mkdirSync2(dir, { recursive: true });
30942
+ const newFilename = entityToFilename(created);
30943
+ const newAbsPath = join2(dir, newFilename);
30944
+ const newMarkdown = serializeSyncMarkdown(created);
30945
+ writeFileSync2(newAbsPath, newMarkdown);
30946
+ if (absPath !== newAbsPath && existsSync2(absPath)) {
30947
+ rmSync(absPath);
30948
+ }
30949
+ const newRelPath = relative(memoryDir, newAbsPath);
30950
+ state.entities[created.id] = {
30951
+ filePath: newRelPath,
30952
+ remoteUpdatedAt: created.updated_at,
30953
+ lastSyncedHash: computeFileHash(newMarkdown)
30954
+ };
30955
+ result.pushed++;
30956
+ } catch (err) {
30957
+ result.errors.push(`Failed to create entity from ${relPath}: ${err}`);
30958
+ }
30959
+ }
30960
+ }
30961
+ saveSyncState(memoryDir, state);
30962
+ return result;
30963
+ }
30964
+ async function syncFull(client2, config2, workspaceId, projectId) {
30965
+ const pullResult = await syncPull(client2, config2, workspaceId, projectId);
30966
+ const pushResult = await syncPush(client2, config2, workspaceId);
30967
+ return {
30968
+ pulled: pullResult.pulled,
30969
+ pushed: pushResult.pushed,
30970
+ deleted: pullResult.deleted,
30971
+ conflicts: pullResult.conflicts,
30972
+ errors: [...pullResult.errors, ...pushResult.errors]
30973
+ };
30974
+ }
30975
+ // ../memory/src/lifecycle.ts
30976
+ var DECAY_HALF_LIVES = {
30977
+ draft: 7,
30978
+ episode: 30,
30979
+ reference: 180
30980
+ };
30981
+ var PROMOTION_RULES = {
30982
+ draftToEpisode: {
30983
+ minAccessCount: 5,
30984
+ minConfidence: 0.8,
30985
+ minAgeDays: 1
30986
+ },
30987
+ episodeToReference: {
30988
+ minAccessCount: 10,
30989
+ minConfidence: 0.9,
30990
+ minAgeDays: 7
30991
+ }
30992
+ };
30993
+ var ARCHIVE_THRESHOLD = 0.3;
30994
+ var STALE_DAYS = 90;
30995
+ var STALE_MIN_ACCESS = 3;
30996
+ function computeDecayScore(tier, lastAccessedAt, accessCount) {
30997
+ const halfLife = DECAY_HALF_LIVES[tier];
30998
+ const now = Date.now();
30999
+ let daysSinceAccess = 0;
31000
+ if (lastAccessedAt) {
31001
+ daysSinceAccess = (now - new Date(lastAccessedAt).getTime()) / (1000 * 60 * 60 * 24);
31002
+ }
31003
+ const timeDecay = Math.pow(0.5, daysSinceAccess / halfLife);
31004
+ const accessBonus = Math.log10(accessCount + 1) * 0.1;
31005
+ const score = Math.min(timeDecay + accessBonus, 1);
31006
+ return { score, daysSinceAccess, halfLife, accessBonus };
31007
+ }
31008
+ function checkPromotion(currentTier, accessCount, confidence, createdAt) {
31009
+ const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
31010
+ const base = {
31011
+ eligible: false,
31012
+ targetTier: null,
31013
+ reason: null,
31014
+ currentTier,
31015
+ accessCount,
31016
+ confidence,
31017
+ ageDays
31018
+ };
31019
+ if (currentTier === "draft") {
31020
+ const rules = PROMOTION_RULES.draftToEpisode;
31021
+ if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
31022
+ return {
31023
+ ...base,
31024
+ eligible: true,
31025
+ targetTier: "episode",
31026
+ reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
31027
+ };
31028
+ }
31029
+ }
31030
+ if (currentTier === "episode") {
31031
+ const rules = PROMOTION_RULES.episodeToReference;
31032
+ if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
31033
+ return {
31034
+ ...base,
31035
+ eligible: true,
31036
+ targetTier: "reference",
31037
+ reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
31038
+ };
31039
+ }
31040
+ }
31041
+ return base;
31042
+ }
31043
+ function evaluateLifecycle(entity) {
31044
+ const decay = computeDecayScore(entity.memory_tier, entity.last_accessed_at, entity.access_count);
31045
+ const promotion = checkPromotion(entity.memory_tier, entity.access_count, entity.confidence, entity.created_at);
31046
+ const shouldArchive = entity.confidence < ARCHIVE_THRESHOLD;
31047
+ const archiveReason = shouldArchive ? `Confidence ${entity.confidence} below threshold ${ARCHIVE_THRESHOLD}` : undefined;
31048
+ const shouldFlagForReview = decay.daysSinceAccess >= STALE_DAYS && entity.access_count < STALE_MIN_ACCESS;
31049
+ const reviewReason = shouldFlagForReview ? `Not accessed in ${Math.round(decay.daysSinceAccess)} days with only ${entity.access_count} accesses` : undefined;
31050
+ return {
31051
+ decay,
31052
+ promotion,
31053
+ shouldArchive,
31054
+ shouldFlagForReview,
31055
+ archiveReason,
31056
+ reviewReason
31057
+ };
31058
+ }
31059
+ // ../memory/src/graph-walk.ts
31060
+ async function discoverRelatedContext(client2, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
31061
+ const visited = new Set;
31062
+ const collectedEntities = [];
31063
+ const collectedRelations = [];
31064
+ let truncated = false;
31065
+ const queue = startIds.map((id) => [id, 0]);
31066
+ for (const id of startIds) {
31067
+ visited.add(id);
31068
+ }
31069
+ while (queue.length > 0) {
31070
+ const [entityId, depth] = queue.shift();
31071
+ if (collectedEntities.length >= maxEntities) {
31072
+ truncated = true;
31073
+ break;
31074
+ }
31075
+ if (depth > maxDepth)
31076
+ continue;
31077
+ try {
31078
+ const entityResult = await client2.getMemoryEntity(entityId);
31079
+ const entity = entityResult.entity;
31080
+ if (entity) {
31081
+ collectedEntities.push({
31082
+ id: entity.id,
31083
+ type: entity.type,
31084
+ title: entity.title,
31085
+ confidence: entity.confidence ?? 1,
31086
+ memory_tier: entity.memory_tier || "reference"
31087
+ });
31088
+ }
31089
+ if (depth >= maxDepth)
31090
+ continue;
31091
+ const related = await client2.getRelatedEntities(entityId);
31092
+ for (const raw of related.outgoing || []) {
31093
+ const rel = raw;
31094
+ const relConfidence = rel.confidence ?? 1;
31095
+ if (relConfidence < minConfidence)
31096
+ continue;
31097
+ const target = rel.target;
31098
+ const targetId = target?.id ?? rel.target_id;
31099
+ if (targetId && !visited.has(targetId)) {
31100
+ visited.add(targetId);
31101
+ queue.push([targetId, depth + 1]);
31102
+ collectedRelations.push({
31103
+ id: rel.id,
31104
+ source_id: entityId,
31105
+ target_id: targetId,
31106
+ relation_type: rel.relation_type,
31107
+ confidence: relConfidence
31108
+ });
31109
+ }
31110
+ }
31111
+ for (const raw of related.incoming || []) {
31112
+ const rel = raw;
31113
+ const relConfidence = rel.confidence ?? 1;
31114
+ if (relConfidence < minConfidence)
31115
+ continue;
31116
+ const source = rel.source;
31117
+ const sourceId = source?.id ?? rel.source_id;
31118
+ if (sourceId && !visited.has(sourceId)) {
31119
+ visited.add(sourceId);
31120
+ queue.push([sourceId, depth + 1]);
31121
+ collectedRelations.push({
31122
+ id: rel.id,
31123
+ source_id: sourceId,
31124
+ target_id: entityId,
31125
+ relation_type: rel.relation_type,
31126
+ confidence: relConfidence
31127
+ });
31128
+ }
31129
+ }
31130
+ } catch {
31131
+ continue;
31132
+ }
31133
+ }
31134
+ return {
31135
+ entities: collectedEntities,
31136
+ relations: collectedRelations,
31137
+ depth: maxDepth,
31138
+ truncated
31139
+ };
31140
+ }
30553
31141
  // src/prompt-builder.ts
30554
31142
  var LABEL_CATEGORY_MAP = {
30555
31143
  bug: "bug",
@@ -30767,7 +31355,7 @@ ${lines.join(`
30767
31355
  `)}`;
30768
31356
  }
30769
31357
  function generatePrompt(options) {
30770
- const { card, column, variant, customConstraints, memories } = options;
31358
+ const { card, column, variant, customConstraints, memories, assembledContext, assemblyId } = options;
30771
31359
  const contextOpts = {
30772
31360
  includeTitle: true,
30773
31361
  includeDescription: true,
@@ -30830,7 +31418,10 @@ ${card.description}`);
30830
31418
  roleFraming.outputSuggestions.forEach((s) => {
30831
31419
  sections.push(`- ${s}`);
30832
31420
  });
30833
- if (memories && memories.length > 0) {
31421
+ if (assembledContext) {
31422
+ sections.push(`
31423
+ ${assembledContext}`);
31424
+ } else if (memories && memories.length > 0) {
30834
31425
  sections.push(`
30835
31426
  ## Relevant Memories`);
30836
31427
  sections.push(`*${memories.length} memories recalled from knowledge graph:*`);
@@ -30851,6 +31442,7 @@ ${customConstraints}`);
30851
31442
  *Card #${card.short_id} | Generated for ${variant} mode*`);
30852
31443
  const prompt = sections.join(`
30853
31444
  `);
31445
+ const memoryCount = assembledContext ? (assembledContext.match(/^### /gm) || []).length : memories?.length || 0;
30854
31446
  return {
30855
31447
  prompt,
30856
31448
  variant,
@@ -30862,11 +31454,453 @@ ${customConstraints}`);
30862
31454
  subtaskCount: subtasks.length,
30863
31455
  completedSubtasks: subtasks.filter((s) => s.completed).length,
30864
31456
  linkedCardCount: links.length,
30865
- memoryCount: memories?.length || 0
31457
+ memoryCount
30866
31458
  },
30867
- tokenEstimate: estimateTokens(prompt)
31459
+ tokenEstimate: estimateTokens(prompt),
31460
+ ...assemblyId && { assemblyId }
31461
+ };
31462
+ }
31463
+
31464
+ // src/context-assembly.ts
31465
+ var DEFAULT_TOKEN_BUDGET = 4000;
31466
+ var MAX_TOKENS_PER_ENTITY = 500;
31467
+ var MIN_RELEVANCE_THRESHOLD = 0.1;
31468
+ var TIER_WEIGHTS = {
31469
+ reference: 1,
31470
+ episode: 0.7,
31471
+ draft: 0.4
31472
+ };
31473
+ var TIER_BUDGET_ALLOCATION = {
31474
+ reference: 0.6,
31475
+ episode: 0.3,
31476
+ draft: 0.1
31477
+ };
31478
+ var MIN_REFERENCE_SLOTS = 3;
31479
+ function estimateTokens2(text) {
31480
+ return Math.ceil(text.length / 4);
31481
+ }
31482
+ function generateAssemblyId() {
31483
+ return `ctx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
31484
+ }
31485
+ function truncateContent(content, maxTokens) {
31486
+ const currentTokens = estimateTokens2(content);
31487
+ if (currentTokens <= maxTokens) {
31488
+ return { text: content, truncated: false };
31489
+ }
31490
+ const paragraphs = content.split(/\n\n+/);
31491
+ let result = paragraphs[0];
31492
+ for (let i = 1;i < paragraphs.length; i++) {
31493
+ const lines = paragraphs[i].split(`
31494
+ `).filter((l) => l.startsWith("- ") || l.startsWith("* "));
31495
+ if (lines.length > 0) {
31496
+ const bulletSection = lines.join(`
31497
+ `);
31498
+ if (estimateTokens2(result + `
31499
+
31500
+ ` + bulletSection) <= maxTokens) {
31501
+ result += `
31502
+
31503
+ ` + bulletSection;
31504
+ }
31505
+ }
31506
+ }
31507
+ if (estimateTokens2(result) > maxTokens) {
31508
+ const maxChars = maxTokens * 4;
31509
+ result = result.slice(0, maxChars - 3) + "...";
31510
+ }
31511
+ return { text: result, truncated: true };
31512
+ }
31513
+ function computeRelevanceScore(entity, taskContext, cardLabels) {
31514
+ const reasons = [];
31515
+ let score = 0;
31516
+ const taskWords = new Set(taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
31517
+ const entityWords = new Set(`${entity.title} ${entity.content}`.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
31518
+ const overlap = [...taskWords].filter((w) => entityWords.has(w));
31519
+ if (overlap.length > 0) {
31520
+ const textScore = Math.min(overlap.length / Math.max(taskWords.size, 1), 1) * 0.4;
31521
+ score += textScore;
31522
+ reasons.push(`text_match(${overlap.length} words)`);
31523
+ }
31524
+ if (cardLabels.length > 0 && entity.tags.length > 0) {
31525
+ const labelSet = new Set(cardLabels.map((l) => l.toLowerCase()));
31526
+ const tagOverlap = entity.tags.filter((t) => labelSet.has(t.toLowerCase()));
31527
+ if (tagOverlap.length > 0) {
31528
+ const tagScore = tagOverlap.length / cardLabels.length * 0.3;
31529
+ score += tagScore;
31530
+ reasons.push(`tag_match(${tagOverlap.join(",")})`);
31531
+ }
31532
+ }
31533
+ score += entity.confidence * 0.15;
31534
+ if (entity.confidence >= 0.9) {
31535
+ reasons.push("high_confidence");
31536
+ }
31537
+ if (entity.last_accessed_at) {
31538
+ const daysSinceAccess = (Date.now() - new Date(entity.last_accessed_at).getTime()) / (1000 * 60 * 60 * 24);
31539
+ const halfLife = { draft: 7, episode: 30, reference: 180 }[entity.memory_tier];
31540
+ const recencyScore = Math.pow(0.5, daysSinceAccess / halfLife) * 0.1;
31541
+ score += recencyScore;
31542
+ if (daysSinceAccess < 7)
31543
+ reasons.push("recently_accessed");
31544
+ }
31545
+ if (entity.access_count > 0) {
31546
+ const freqScore = Math.log10(entity.access_count + 1) * 0.05;
31547
+ score += Math.min(freqScore, 0.1);
31548
+ if (entity.access_count >= 5)
31549
+ reasons.push(`frequently_used(${entity.access_count})`);
31550
+ }
31551
+ const tierWeight = TIER_WEIGHTS[entity.memory_tier];
31552
+ score *= tierWeight;
31553
+ return { score, reasons };
31554
+ }
31555
+ async function assembleContext(options) {
31556
+ const {
31557
+ workspaceId,
31558
+ projectId,
31559
+ taskContext,
31560
+ cardLabels = [],
31561
+ tokenBudget = DEFAULT_TOKEN_BUDGET,
31562
+ client: client3
31563
+ } = options;
31564
+ const assemblyId = generateAssemblyId();
31565
+ const manifest = {
31566
+ assemblyId,
31567
+ timestamp: new Date().toISOString(),
31568
+ included: [],
31569
+ excluded: [],
31570
+ budgetUsed: 0,
31571
+ budgetTotal: tokenBudget,
31572
+ tierBreakdown: {
31573
+ draft: { count: 0, tokens: 0 },
31574
+ episode: { count: 0, tokens: 0 },
31575
+ reference: { count: 0, tokens: 0 }
31576
+ }
31577
+ };
31578
+ let candidates = [];
31579
+ try {
31580
+ const searchResult = await client3.searchMemoryEntities(workspaceId, taskContext, { project_id: projectId, limit: 30 });
31581
+ if (searchResult.entities?.length > 0) {
31582
+ candidates = searchResult.entities.map(mapToContextEntity);
31583
+ }
31584
+ } catch {}
31585
+ if (candidates.length < 10 && projectId) {
31586
+ try {
31587
+ const listResult = await client3.listMemoryEntities({
31588
+ workspace_id: workspaceId,
31589
+ project_id: projectId,
31590
+ limit: 30
31591
+ });
31592
+ if (listResult.entities?.length > 0) {
31593
+ const existingIds = new Set(candidates.map((c) => c.id));
31594
+ const additional = listResult.entities.map(mapToContextEntity).filter((e) => !existingIds.has(e.id));
31595
+ candidates.push(...additional);
31596
+ }
31597
+ } catch {}
31598
+ }
31599
+ if (candidates.length === 0) {
31600
+ return {
31601
+ context: "",
31602
+ manifest,
31603
+ memories: []
31604
+ };
31605
+ }
31606
+ const scored = candidates.map((entity) => {
31607
+ const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels);
31608
+ return { entity, score, reasons };
31609
+ });
31610
+ scored.sort((a, b) => b.score - a.score);
31611
+ const tierBudgets = {
31612
+ reference: Math.floor(tokenBudget * TIER_BUDGET_ALLOCATION.reference),
31613
+ episode: Math.floor(tokenBudget * TIER_BUDGET_ALLOCATION.episode),
31614
+ draft: Math.floor(tokenBudget * TIER_BUDGET_ALLOCATION.draft)
31615
+ };
31616
+ const tierUsed = { reference: 0, episode: 0, draft: 0 };
31617
+ const included = [];
31618
+ let totalUsed = 0;
31619
+ let referenceCount = 0;
31620
+ for (const item of scored) {
31621
+ if (item.entity.memory_tier === "reference" && referenceCount < MIN_REFERENCE_SLOTS) {
31622
+ const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
31623
+ const tokens = estimateTokens2(`### ${item.entity.title}
31624
+ ${text}`);
31625
+ if (totalUsed + tokens <= tokenBudget) {
31626
+ included.push({ ...item, tokens, truncated });
31627
+ item.entity.content = text;
31628
+ totalUsed += tokens;
31629
+ tierUsed.reference += tokens;
31630
+ referenceCount++;
31631
+ }
31632
+ }
31633
+ }
31634
+ const includedIds = new Set(included.map((i) => i.entity.id));
31635
+ for (const item of scored) {
31636
+ if (includedIds.has(item.entity.id))
31637
+ continue;
31638
+ if (item.score < MIN_RELEVANCE_THRESHOLD) {
31639
+ manifest.excluded.push({
31640
+ entityId: item.entity.id,
31641
+ title: item.entity.title,
31642
+ type: item.entity.type,
31643
+ tier: item.entity.memory_tier,
31644
+ relevanceScore: item.score,
31645
+ reason: "below_relevance_threshold"
31646
+ });
31647
+ continue;
31648
+ }
31649
+ const tier = item.entity.memory_tier;
31650
+ const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
31651
+ const tokens = estimateTokens2(`### ${item.entity.title}
31652
+ ${text}`);
31653
+ if (tierUsed[tier] + tokens > tierBudgets[tier]) {
31654
+ const totalRemaining = tokenBudget - totalUsed;
31655
+ if (tokens > totalRemaining) {
31656
+ manifest.excluded.push({
31657
+ entityId: item.entity.id,
31658
+ title: item.entity.title,
31659
+ type: item.entity.type,
31660
+ tier,
31661
+ relevanceScore: item.score,
31662
+ reason: "budget_exceeded"
31663
+ });
31664
+ continue;
31665
+ }
31666
+ }
31667
+ if (totalUsed + tokens > tokenBudget) {
31668
+ manifest.excluded.push({
31669
+ entityId: item.entity.id,
31670
+ title: item.entity.title,
31671
+ type: item.entity.type,
31672
+ tier,
31673
+ relevanceScore: item.score,
31674
+ reason: "total_budget_exceeded"
31675
+ });
31676
+ continue;
31677
+ }
31678
+ included.push({ ...item, tokens, truncated });
31679
+ item.entity.content = text;
31680
+ totalUsed += tokens;
31681
+ tierUsed[tier] += tokens;
31682
+ includedIds.add(item.entity.id);
31683
+ }
31684
+ manifest.budgetUsed = totalUsed;
31685
+ manifest.tierBreakdown = {
31686
+ reference: { count: included.filter((i) => i.entity.memory_tier === "reference").length, tokens: tierUsed.reference },
31687
+ episode: { count: included.filter((i) => i.entity.memory_tier === "episode").length, tokens: tierUsed.episode },
31688
+ draft: { count: included.filter((i) => i.entity.memory_tier === "draft").length, tokens: tierUsed.draft }
31689
+ };
31690
+ for (const item of included) {
31691
+ manifest.included.push({
31692
+ entityId: item.entity.id,
31693
+ title: item.entity.title,
31694
+ type: item.entity.type,
31695
+ tier: item.entity.memory_tier,
31696
+ relevanceScore: item.score,
31697
+ reasons: item.reasons,
31698
+ tokenCount: item.tokens,
31699
+ truncated: item.truncated
31700
+ });
31701
+ }
31702
+ const contextSections = [];
31703
+ if (included.length > 0) {
31704
+ contextSections.push(`## Relevant Memories (${included.length} loaded, ${manifest.excluded.length} excluded)`);
31705
+ contextSections.push(`*Assembly: ${assemblyId} | Budget: ${totalUsed}/${tokenBudget} tokens*`);
31706
+ for (const item of included) {
31707
+ const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
31708
+ const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
31709
+ contextSections.push(`
31710
+ ### ${item.entity.title} (${item.entity.type}, confidence: ${item.entity.confidence})${tierLabel}${tags}`);
31711
+ contextSections.push(item.entity.content);
31712
+ }
31713
+ }
31714
+ incrementAccessCounts(client3, included.map((i) => i.entity.id)).catch(() => {});
31715
+ return {
31716
+ context: contextSections.join(`
31717
+ `),
31718
+ manifest,
31719
+ memories: included.map((i) => i.entity)
30868
31720
  };
30869
31721
  }
31722
+ function mapToContextEntity(raw) {
31723
+ const e = raw;
31724
+ return {
31725
+ id: e.id,
31726
+ type: e.type,
31727
+ title: e.title,
31728
+ content: e.content,
31729
+ confidence: e.confidence ?? 1,
31730
+ tags: e.tags || [],
31731
+ memory_tier: e.memory_tier || "reference",
31732
+ access_count: e.access_count || 0,
31733
+ last_accessed_at: e.last_accessed_at || null,
31734
+ updated_at: e.updated_at || ""
31735
+ };
31736
+ }
31737
+ async function incrementAccessCounts(client3, entityIds) {
31738
+ for (const id of entityIds) {
31739
+ try {
31740
+ await client3.updateMemoryEntity(id, {
31741
+ metadata: {
31742
+ _last_context_load: new Date().toISOString()
31743
+ }
31744
+ });
31745
+ } catch {}
31746
+ }
31747
+ }
31748
+ var manifestCache = new Map;
31749
+ var MAX_CACHE_SIZE = 50;
31750
+ function cacheManifest(manifest) {
31751
+ if (manifestCache.size >= MAX_CACHE_SIZE) {
31752
+ const firstKey = manifestCache.keys().next().value;
31753
+ if (firstKey)
31754
+ manifestCache.delete(firstKey);
31755
+ }
31756
+ manifestCache.set(manifest.assemblyId, manifest);
31757
+ }
31758
+ function getCachedManifest(assemblyId) {
31759
+ return manifestCache.get(assemblyId);
31760
+ }
31761
+
31762
+ // src/active-learning.ts
31763
+ async function extractLearnings(client3, session) {
31764
+ const workspaceId = getActiveWorkspaceId();
31765
+ if (!workspaceId) {
31766
+ return { count: 0, entityIds: [] };
31767
+ }
31768
+ const projectId = getActiveProjectId() || undefined;
31769
+ const learnings = [];
31770
+ if (session.blockers && session.blockers.length > 0) {
31771
+ for (const blocker of session.blockers) {
31772
+ learnings.push({
31773
+ title: `Blocker: ${blocker.slice(0, 100)}`,
31774
+ content: `Encountered while working on "${session.cardTitle}":
31775
+
31776
+ ${blocker}
31777
+
31778
+ Agent: ${session.agentName}
31779
+ Session status: ${session.status}`,
31780
+ type: "error",
31781
+ tier: "reference",
31782
+ confidence: 0.7,
31783
+ tags: ["auto-extracted", "blocker", ...session.cardLabels.slice(0, 3)],
31784
+ metadata: {
31785
+ source: "active_learning",
31786
+ card_id: session.cardId,
31787
+ review_status: "pending"
31788
+ }
31789
+ });
31790
+ }
31791
+ }
31792
+ if (session.status === "completed") {
31793
+ const durationInfo = session.sessionDurationMs ? `
31794
+ Duration: ${Math.round(session.sessionDurationMs / 60000)} minutes` : "";
31795
+ learnings.push({
31796
+ title: `Session: ${session.cardTitle}`,
31797
+ content: [
31798
+ `Completed work on "${session.cardTitle}".`,
31799
+ session.currentTask ? `Final task: ${session.currentTask}` : "",
31800
+ session.progressPercent !== undefined ? `Progress: ${session.progressPercent}%` : "",
31801
+ durationInfo,
31802
+ session.blockers?.length ? `Blockers encountered: ${session.blockers.join("; ")}` : "",
31803
+ `
31804
+ Agent: ${session.agentName}`
31805
+ ].filter(Boolean).join(`
31806
+ `),
31807
+ type: "lesson",
31808
+ tier: "episode",
31809
+ confidence: 0.7,
31810
+ tags: ["auto-extracted", "session-summary", ...session.cardLabels.slice(0, 3)],
31811
+ metadata: {
31812
+ source: "active_learning",
31813
+ card_id: session.cardId,
31814
+ review_status: session.blockers?.length ? "pending" : undefined
31815
+ }
31816
+ });
31817
+ }
31818
+ const hasBugLabel = session.cardLabels.some((l) => ["bug", "fix", "hotfix", "defect", "error"].includes(l.toLowerCase()));
31819
+ if (hasBugLabel && session.status === "completed") {
31820
+ learnings.push({
31821
+ title: `Solution: ${session.cardTitle}`,
31822
+ content: [
31823
+ `Resolved bug: "${session.cardTitle}"`,
31824
+ session.currentTask ? `
31825
+ Approach: ${session.currentTask}` : "",
31826
+ `
31827
+ Agent: ${session.agentName}`
31828
+ ].filter(Boolean).join(`
31829
+ `),
31830
+ type: "solution",
31831
+ tier: "reference",
31832
+ confidence: 0.8,
31833
+ tags: ["auto-extracted", "bug-fix", ...session.cardLabels.slice(0, 3)],
31834
+ metadata: {
31835
+ source: "active_learning",
31836
+ card_id: session.cardId,
31837
+ auto_confidence: true
31838
+ }
31839
+ });
31840
+ }
31841
+ const entityIds = [];
31842
+ for (const learning of learnings) {
31843
+ try {
31844
+ const shouldAutoStore = learning.confidence >= 0.85;
31845
+ const metadata = {
31846
+ ...learning.metadata,
31847
+ ...shouldAutoStore ? {} : { review_status: "pending" }
31848
+ };
31849
+ const result = await client3.createMemoryEntity({
31850
+ workspace_id: workspaceId,
31851
+ project_id: projectId,
31852
+ type: learning.type,
31853
+ scope: "project",
31854
+ memory_tier: learning.tier,
31855
+ title: learning.title,
31856
+ content: learning.content,
31857
+ confidence: learning.confidence,
31858
+ tags: learning.tags,
31859
+ metadata,
31860
+ agent_identifier: session.agentIdentifier
31861
+ });
31862
+ const entity = result.entity;
31863
+ if (entity?.id) {
31864
+ entityIds.push(entity.id);
31865
+ }
31866
+ } catch {}
31867
+ }
31868
+ return { count: entityIds.length, entityIds };
31869
+ }
31870
+ async function listPendingLearnings(client3, workspaceId, projectId) {
31871
+ try {
31872
+ const result = await client3.listMemoryEntities({
31873
+ workspace_id: workspaceId,
31874
+ project_id: projectId,
31875
+ limit: 50
31876
+ });
31877
+ return (result.entities || []).filter((e) => {
31878
+ const entity = e;
31879
+ return entity.metadata?.review_status === "pending";
31880
+ });
31881
+ } catch {
31882
+ return [];
31883
+ }
31884
+ }
31885
+ async function approveLearning(client3, entityId, newConfidence) {
31886
+ await client3.updateMemoryEntity(entityId, {
31887
+ confidence: newConfidence ?? 1,
31888
+ metadata: {
31889
+ review_status: "approved",
31890
+ reviewed_at: new Date().toISOString()
31891
+ }
31892
+ });
31893
+ }
31894
+ async function rejectLearning(client3, entityId) {
31895
+ await client3.updateMemoryEntity(entityId, {
31896
+ confidence: 0,
31897
+ metadata: {
31898
+ review_status: "rejected",
31899
+ reviewed_at: new Date().toISOString(),
31900
+ archived: true
31901
+ }
31902
+ });
31903
+ }
30870
31904
 
30871
31905
  // src/server.ts
30872
31906
  var TOOLS = {
@@ -31413,6 +32447,11 @@ var TOOLS = {
31413
32447
  enum: ["private", "project", "workspace", "global"],
31414
32448
  description: "Visibility scope (default: project). Private = only creator can see."
31415
32449
  },
32450
+ tier: {
32451
+ type: "string",
32452
+ enum: ["draft", "episode", "reference"],
32453
+ description: "Memory tier: draft (working notes), episode (session summaries), reference (durable knowledge). Auto-inferred from type if not set."
32454
+ },
31416
32455
  tags: {
31417
32456
  type: "array",
31418
32457
  items: { type: "string" },
@@ -31558,7 +32597,7 @@ var TOOLS = {
31558
32597
  }
31559
32598
  },
31560
32599
  harmony_relate: {
31561
- description: "Create a typed relationship between two memory entities. Relation types: learned_from, resolved_by, contradicts, supports, depends_on, part_of, caused_by.",
32600
+ description: "Create a typed relationship between two memory entities. Relation types: learned_from, resolved_by, contradicts, supports, depends_on, part_of, caused_by, relates_to.",
31562
32601
  inputSchema: {
31563
32602
  type: "object",
31564
32603
  properties: {
@@ -31579,7 +32618,8 @@ var TOOLS = {
31579
32618
  "supports",
31580
32619
  "depends_on",
31581
32620
  "part_of",
31582
- "caused_by"
32621
+ "caused_by",
32622
+ "relates_to"
31583
32623
  ],
31584
32624
  description: "Type of relationship between entities"
31585
32625
  },
@@ -31632,28 +32672,107 @@ var TOOLS = {
31632
32672
  required: ["query"]
31633
32673
  }
31634
32674
  },
31635
- harmony_create_plan: {
31636
- description: "Create a new project plan. Use this to upload implementation plans created during planning. Returns a URL where the plan can be viewed and edited in Harmony.",
32675
+ harmony_vault_index: {
32676
+ description: "Get a compact index of all memory entities in the vault. Returns a markdown table with title, type, scope, confidence, tags, summary, and last updated date. Use this for quick overview of stored knowledge.",
31637
32677
  inputSchema: {
31638
32678
  type: "object",
31639
32679
  properties: {
31640
- projectId: {
32680
+ workspaceId: {
31641
32681
  type: "string",
31642
- description: "Project ID (optional if context set)"
32682
+ description: "Workspace ID (optional if context set)"
31643
32683
  },
31644
- title: { type: "string", description: "Plan title" },
31645
- content: {
32684
+ projectId: {
31646
32685
  type: "string",
31647
- description: "Plan content in Markdown format"
32686
+ description: "Project ID (optional, filters to project scope)"
31648
32687
  },
31649
- source: {
32688
+ type: {
31650
32689
  type: "string",
31651
- enum: ["user", "agent", "imported"],
31652
- description: "Plan source: agent for AI-generated plans (default: agent)"
32690
+ enum: [
32691
+ "agent",
32692
+ "task",
32693
+ "decision",
32694
+ "context",
32695
+ "pattern",
32696
+ "error",
32697
+ "solution",
32698
+ "preference",
32699
+ "relationship",
32700
+ "commitment",
32701
+ "lesson",
32702
+ "project",
32703
+ "handoff"
32704
+ ],
32705
+ description: "Filter by entity type"
31653
32706
  },
31654
- tasks: {
31655
- type: "array",
31656
- items: {
32707
+ limit: {
32708
+ type: "number",
32709
+ description: "Max entities to return (default: 200)"
32710
+ }
32711
+ },
32712
+ required: []
32713
+ }
32714
+ },
32715
+ harmony_resolve_links: {
32716
+ description: "Batch-scan all memory entities in a workspace/project for [[wiki-links]] and auto-create 'relates_to' relations. Use this to retroactively link existing entities.",
32717
+ inputSchema: {
32718
+ type: "object",
32719
+ properties: {
32720
+ workspaceId: {
32721
+ type: "string",
32722
+ description: "Workspace ID (optional if context set)"
32723
+ },
32724
+ projectId: {
32725
+ type: "string",
32726
+ description: "Project ID (optional, limits scan to project)"
32727
+ }
32728
+ },
32729
+ required: []
32730
+ }
32731
+ },
32732
+ harmony_sync: {
32733
+ description: "Synchronize local markdown memory files with the shared database. Pull downloads remote entities as .md files, push uploads local changes, full does both (pull first, then push). Server wins on conflicts.",
32734
+ inputSchema: {
32735
+ type: "object",
32736
+ properties: {
32737
+ direction: {
32738
+ type: "string",
32739
+ enum: ["pull", "push", "full"],
32740
+ description: "Sync direction: pull (remote->local), push (local->remote), full (pull then push). Default: full"
32741
+ },
32742
+ workspaceId: {
32743
+ type: "string",
32744
+ description: "Workspace ID (optional if context set)"
32745
+ },
32746
+ projectId: {
32747
+ type: "string",
32748
+ description: "Project ID (optional, limits pull scope)"
32749
+ }
32750
+ },
32751
+ required: []
32752
+ }
32753
+ },
32754
+ harmony_create_plan: {
32755
+ description: "Create a new project plan. Use this to upload implementation plans created during planning. Returns a URL where the plan can be viewed and edited in Harmony.",
32756
+ inputSchema: {
32757
+ type: "object",
32758
+ properties: {
32759
+ projectId: {
32760
+ type: "string",
32761
+ description: "Project ID (optional if context set)"
32762
+ },
32763
+ title: { type: "string", description: "Plan title" },
32764
+ content: {
32765
+ type: "string",
32766
+ description: "Plan content in Markdown format"
32767
+ },
32768
+ source: {
32769
+ type: "string",
32770
+ enum: ["user", "agent", "imported"],
32771
+ description: "Plan source: agent for AI-generated plans (default: agent)"
32772
+ },
32773
+ tasks: {
32774
+ type: "array",
32775
+ items: {
31657
32776
  type: "object",
31658
32777
  properties: {
31659
32778
  content: { type: "string", description: "Task description" },
@@ -31709,6 +32828,148 @@ var TOOLS = {
31709
32828
  },
31710
32829
  required: ["planId"]
31711
32830
  }
32831
+ },
32832
+ harmony_review_learnings: {
32833
+ description: "List pending auto-extracted memories awaiting human review. These are memories automatically created from agent work sessions.",
32834
+ inputSchema: {
32835
+ type: "object",
32836
+ properties: {
32837
+ workspaceId: {
32838
+ type: "string",
32839
+ description: "Workspace ID (optional if context set)"
32840
+ },
32841
+ projectId: {
32842
+ type: "string",
32843
+ description: "Project ID (optional)"
32844
+ }
32845
+ },
32846
+ required: []
32847
+ }
32848
+ },
32849
+ harmony_approve_learning: {
32850
+ description: "Approve or reject a pending auto-extracted memory. Approved memories get confidence boosted to 1.0. Rejected memories are archived.",
32851
+ inputSchema: {
32852
+ type: "object",
32853
+ properties: {
32854
+ entityId: {
32855
+ type: "string",
32856
+ description: "Memory entity ID to approve or reject"
32857
+ },
32858
+ action: {
32859
+ type: "string",
32860
+ enum: ["approve", "reject"],
32861
+ description: "Whether to approve or reject the learning"
32862
+ },
32863
+ confidence: {
32864
+ type: "number",
32865
+ description: "Override confidence for approved memories (default: 1.0)"
32866
+ }
32867
+ },
32868
+ required: ["entityId", "action"]
32869
+ }
32870
+ },
32871
+ harmony_prune_draft: {
32872
+ description: "Remove stale draft memories that haven't been accessed recently. Runs in dry-run mode by default to preview what would be pruned.",
32873
+ inputSchema: {
32874
+ type: "object",
32875
+ properties: {
32876
+ workspaceId: {
32877
+ type: "string",
32878
+ description: "Workspace ID (optional if context set)"
32879
+ },
32880
+ projectId: {
32881
+ type: "string",
32882
+ description: "Project ID (optional)"
32883
+ },
32884
+ dryRun: {
32885
+ type: "boolean",
32886
+ description: "Preview what would be pruned without deleting (default: true)"
32887
+ },
32888
+ maxAgeDays: {
32889
+ type: "number",
32890
+ description: "Maximum age in days for draft memories to keep (default: 30)"
32891
+ }
32892
+ },
32893
+ required: []
32894
+ }
32895
+ },
32896
+ harmony_get_context_manifest: {
32897
+ description: "Retrieve the context assembly manifest for a given assembly ID to debug what memories were loaded or excluded and why.",
32898
+ inputSchema: {
32899
+ type: "object",
32900
+ properties: {
32901
+ assemblyId: {
32902
+ type: "string",
32903
+ description: "Assembly ID from a previous harmony_generate_prompt call"
32904
+ }
32905
+ },
32906
+ required: ["assemblyId"]
32907
+ }
32908
+ },
32909
+ harmony_debug_context: {
32910
+ description: "Explain why a specific memory was or wasn't included in context assembly for a given task. Returns relevance score breakdown.",
32911
+ inputSchema: {
32912
+ type: "object",
32913
+ properties: {
32914
+ entityId: {
32915
+ type: "string",
32916
+ description: "Memory entity ID to analyze"
32917
+ },
32918
+ taskContext: {
32919
+ type: "string",
32920
+ description: "Task context (card title + description) to score against"
32921
+ },
32922
+ cardLabels: {
32923
+ type: "array",
32924
+ items: { type: "string" },
32925
+ description: "Card labels for tag matching"
32926
+ }
32927
+ },
32928
+ required: ["entityId", "taskContext"]
32929
+ }
32930
+ },
32931
+ harmony_export_memory_graph: {
32932
+ description: "Export the knowledge graph as DOT format for Graphviz visualization. Nodes are colored by tier (draft=yellow, episode=blue, reference=green).",
32933
+ inputSchema: {
32934
+ type: "object",
32935
+ properties: {
32936
+ workspaceId: {
32937
+ type: "string",
32938
+ description: "Workspace ID (optional if context set)"
32939
+ },
32940
+ projectId: {
32941
+ type: "string",
32942
+ description: "Project ID (optional)"
32943
+ },
32944
+ limit: {
32945
+ type: "number",
32946
+ description: "Max entities to include (default: 50)"
32947
+ }
32948
+ },
32949
+ required: []
32950
+ }
32951
+ },
32952
+ harmony_promote_memory: {
32953
+ description: "Promote a memory entity to a higher tier: draft→episode or episode→reference. Tracks promotion reason and source.",
32954
+ inputSchema: {
32955
+ type: "object",
32956
+ properties: {
32957
+ entityId: {
32958
+ type: "string",
32959
+ description: "Memory entity ID to promote"
32960
+ },
32961
+ targetTier: {
32962
+ type: "string",
32963
+ enum: ["episode", "reference"],
32964
+ description: "Target tier to promote to"
32965
+ },
32966
+ reason: {
32967
+ type: "string",
32968
+ description: "Reason for promotion (e.g., 'proven useful across 5+ sessions')"
32969
+ }
32970
+ },
32971
+ required: ["entityId", "targetTier", "reason"]
32972
+ }
31712
32973
  }
31713
32974
  };
31714
32975
  var RESOURCES = [
@@ -31783,7 +33044,7 @@ class HarmonyMCPServer {
31783
33044
  throw new Error(`Not configured. Run "harmony-mcp configure" to set your API key.
31784
33045
  ` + "You can generate an API key at https://gethmy.com → Settings → API Keys.");
31785
33046
  }
31786
- const client2 = getClient();
33047
+ const client3 = getClient();
31787
33048
  const getProjectId = () => {
31788
33049
  const id = args.projectId || getActiveProjectId();
31789
33050
  if (!id) {
@@ -31802,7 +33063,7 @@ class HarmonyMCPServer {
31802
33063
  case "harmony_create_card": {
31803
33064
  const title = exports_external.string().min(1).parse(args.title);
31804
33065
  const projectId = args.projectId || getProjectId();
31805
- const result = await client2.createCard(projectId, {
33066
+ const result = await client3.createCard(projectId, {
31806
33067
  title,
31807
33068
  columnId: args.columnId,
31808
33069
  description: args.description,
@@ -31813,7 +33074,7 @@ class HarmonyMCPServer {
31813
33074
  }
31814
33075
  case "harmony_update_card": {
31815
33076
  const cardId = exports_external.string().uuid().parse(args.cardId);
31816
- const result = await client2.updateCard(cardId, {
33077
+ const result = await client3.updateCard(cardId, {
31817
33078
  title: args.title,
31818
33079
  description: args.description,
31819
33080
  priority: args.priority,
@@ -31825,114 +33086,139 @@ class HarmonyMCPServer {
31825
33086
  case "harmony_move_card": {
31826
33087
  const cardId = exports_external.string().uuid().parse(args.cardId);
31827
33088
  const columnId = exports_external.string().uuid().parse(args.columnId);
31828
- const result = await client2.moveCard(cardId, columnId, args.position);
31829
- return { success: true, ...result };
33089
+ const result = await client3.moveCard(cardId, columnId, args.position);
33090
+ let sessionEnded = false;
33091
+ try {
33092
+ const { card } = result;
33093
+ if (card?.project_id) {
33094
+ const board = await client3.getBoard(card.project_id, {
33095
+ summary: true
33096
+ });
33097
+ const columns = board.columns;
33098
+ const destCol = columns.find((c) => c.id === columnId);
33099
+ const colName = destCol?.name?.toLowerCase() || "";
33100
+ const isTerminal2 = destCol?.mark_cards_done || colName === "done" || colName === "completed" || colName === "review";
33101
+ if (isTerminal2) {
33102
+ const { session } = await client3.getAgentSession(cardId);
33103
+ if (session) {
33104
+ await client3.endAgentSession(cardId, { status: "completed" });
33105
+ sessionEnded = true;
33106
+ }
33107
+ }
33108
+ }
33109
+ } catch {}
33110
+ return { success: true, sessionEnded, ...result };
31830
33111
  }
31831
33112
  case "harmony_delete_card": {
31832
33113
  const cardId = exports_external.string().uuid().parse(args.cardId);
31833
- await client2.deleteCard(cardId);
33114
+ await client3.deleteCard(cardId);
31834
33115
  return { success: true };
31835
33116
  }
31836
33117
  case "harmony_assign_card": {
31837
33118
  const cardId = exports_external.string().uuid().parse(args.cardId);
31838
33119
  const assigneeId = args.assigneeId ? exports_external.string().uuid().parse(args.assigneeId) : null;
31839
- const result = await client2.updateCard(cardId, { assigneeId });
33120
+ const result = await client3.updateCard(cardId, { assigneeId });
31840
33121
  return { success: true, ...result };
31841
33122
  }
31842
33123
  case "harmony_search_cards": {
31843
33124
  const query = exports_external.string().min(1).parse(args.query);
31844
- const result = await client2.searchCards(query, {
33125
+ const result = await client3.searchCards(query, {
31845
33126
  projectId: args.projectId
31846
33127
  });
31847
33128
  return { success: true, ...result, count: result.cards.length };
31848
33129
  }
31849
33130
  case "harmony_get_card": {
31850
33131
  const cardId = exports_external.string().uuid().parse(args.cardId);
31851
- const result = await client2.getCard(cardId);
33132
+ const result = await client3.getCard(cardId);
31852
33133
  return { success: true, ...result };
31853
33134
  }
31854
33135
  case "harmony_get_card_by_short_id": {
31855
33136
  const shortId = exports_external.number().int().positive().parse(args.shortId);
31856
33137
  const projectId = args.projectId || getProjectId();
31857
- const result = await client2.getCardByShortId(projectId, shortId);
33138
+ const result = await client3.getCardByShortId(projectId, shortId);
31858
33139
  return { success: true, ...result };
31859
33140
  }
31860
33141
  case "harmony_create_column": {
31861
33142
  const name2 = exports_external.string().min(1).parse(args.name);
31862
33143
  const projectId = args.projectId || getProjectId();
31863
- const result = await client2.createColumn(projectId, name2);
33144
+ const result = await client3.createColumn(projectId, name2);
31864
33145
  return { success: true, ...result };
31865
33146
  }
31866
33147
  case "harmony_update_column": {
31867
33148
  const columnId = exports_external.string().uuid().parse(args.columnId);
31868
33149
  const name2 = exports_external.string().min(1).parse(args.name);
31869
- const result = await client2.updateColumn(columnId, name2);
33150
+ const result = await client3.updateColumn(columnId, name2);
31870
33151
  return { success: true, ...result };
31871
33152
  }
31872
33153
  case "harmony_delete_column": {
31873
33154
  const columnId = exports_external.string().uuid().parse(args.columnId);
31874
- await client2.deleteColumn(columnId);
33155
+ await client3.deleteColumn(columnId);
31875
33156
  return { success: true };
31876
33157
  }
31877
33158
  case "harmony_create_label": {
31878
33159
  const name2 = exports_external.string().min(1).parse(args.name);
31879
33160
  const color = exports_external.string().regex(/^#[0-9a-fA-F]{6}$/).parse(args.color);
31880
33161
  const projectId = args.projectId || getProjectId();
31881
- const result = await client2.createLabel(projectId, { name: name2, color });
33162
+ const board = await client3.getBoard(projectId, { summary: true });
33163
+ const existing = board.labels.find((l) => l.name.toLowerCase() === name2.toLowerCase());
33164
+ if (existing) {
33165
+ return { success: true, ...existing, deduplicated: true };
33166
+ }
33167
+ const result = await client3.createLabel(projectId, { name: name2, color });
31882
33168
  return { success: true, ...result };
31883
33169
  }
31884
33170
  case "harmony_add_label_to_card": {
31885
33171
  const cardId = exports_external.string().uuid().parse(args.cardId);
31886
33172
  const labelId = exports_external.string().uuid().parse(args.labelId);
31887
- await client2.addLabelToCard(cardId, labelId);
33173
+ await client3.addLabelToCard(cardId, labelId);
31888
33174
  return { success: true };
31889
33175
  }
31890
33176
  case "harmony_remove_label_from_card": {
31891
33177
  const cardId = exports_external.string().uuid().parse(args.cardId);
31892
33178
  const labelId = exports_external.string().uuid().parse(args.labelId);
31893
- await client2.removeLabelFromCard(cardId, labelId);
33179
+ await client3.removeLabelFromCard(cardId, labelId);
31894
33180
  return { success: true };
31895
33181
  }
31896
33182
  case "harmony_add_link_to_card": {
31897
33183
  const sourceCardId = exports_external.string().uuid().parse(args.sourceCardId);
31898
33184
  const targetCardId = exports_external.string().uuid().parse(args.targetCardId);
31899
33185
  const linkType = exports_external.enum(["relates_to", "blocks", "duplicates", "is_part_of"]).parse(args.linkType);
31900
- const result = await client2.addLinkToCard(sourceCardId, targetCardId, linkType);
33186
+ const result = await client3.addLinkToCard(sourceCardId, targetCardId, linkType);
31901
33187
  return { success: true, ...result };
31902
33188
  }
31903
33189
  case "harmony_remove_link_from_card": {
31904
33190
  const linkId = exports_external.string().uuid().parse(args.linkId);
31905
- await client2.removeLinkFromCard(linkId);
33191
+ await client3.removeLinkFromCard(linkId);
31906
33192
  return { success: true };
31907
33193
  }
31908
33194
  case "harmony_get_card_links": {
31909
33195
  const cardId = exports_external.string().uuid().parse(args.cardId);
31910
- const result = await client2.getCardLinks(cardId);
33196
+ const result = await client3.getCardLinks(cardId);
31911
33197
  return result;
31912
33198
  }
31913
33199
  case "harmony_create_subtask": {
31914
33200
  const cardId = exports_external.string().uuid().parse(args.cardId);
31915
33201
  const title = exports_external.string().min(1).parse(args.title);
31916
- const result = await client2.createSubtask(cardId, title);
33202
+ const result = await client3.createSubtask(cardId, title);
31917
33203
  return { success: true, ...result };
31918
33204
  }
31919
33205
  case "harmony_toggle_subtask": {
31920
33206
  const subtaskId = exports_external.string().uuid().parse(args.subtaskId);
31921
- const result = await client2.toggleSubtask(subtaskId);
33207
+ const result = await client3.toggleSubtask(subtaskId);
31922
33208
  return { success: true, ...result };
31923
33209
  }
31924
33210
  case "harmony_delete_subtask": {
31925
33211
  const subtaskId = exports_external.string().uuid().parse(args.subtaskId);
31926
- await client2.deleteSubtask(subtaskId);
33212
+ await client3.deleteSubtask(subtaskId);
31927
33213
  return { success: true };
31928
33214
  }
31929
33215
  case "harmony_list_workspaces": {
31930
- const result = await client2.listWorkspaces();
33216
+ const result = await client3.listWorkspaces();
31931
33217
  return { success: true, ...result };
31932
33218
  }
31933
33219
  case "harmony_list_projects": {
31934
33220
  const workspaceId = getWorkspaceId();
31935
- const result = await client2.listProjects(workspaceId);
33221
+ const result = await client3.listProjects(workspaceId);
31936
33222
  return { success: true, ...result };
31937
33223
  }
31938
33224
  case "harmony_get_board": {
@@ -31946,12 +33232,12 @@ class HarmonyMCPServer {
31946
33232
  options.columnId = String(args.columnId);
31947
33233
  if (args.summary === true || args.summary === "true")
31948
33234
  options.summary = true;
31949
- const result = await client2.getBoard(projectId, options);
33235
+ const result = await client3.getBoard(projectId, options);
31950
33236
  return { success: true, board: result };
31951
33237
  }
31952
33238
  case "harmony_get_workspace_members": {
31953
33239
  const workspaceId = getWorkspaceId();
31954
- const result = await client2.getWorkspaceMembers(workspaceId);
33240
+ const result = await client3.getWorkspaceMembers(workspaceId);
31955
33241
  return { success: true, ...result };
31956
33242
  }
31957
33243
  case "harmony_set_workspace_context": {
@@ -31975,7 +33261,7 @@ class HarmonyMCPServer {
31975
33261
  }
31976
33262
  case "harmony_process_command": {
31977
33263
  const command = exports_external.string().min(1).parse(args.command);
31978
- const result = await client2.processNLU({
33264
+ const result = await client3.processNLU({
31979
33265
  command,
31980
33266
  projectId: args.projectId || getActiveProjectId() || undefined,
31981
33267
  workspaceId: args.workspaceId || getActiveWorkspaceId() || undefined,
@@ -31994,10 +33280,10 @@ class HarmonyMCPServer {
31994
33280
  const labelsAdded = [];
31995
33281
  if (moveToColumn || addLabels?.length) {
31996
33282
  try {
31997
- const { card } = await client2.getCard(cardId);
33283
+ const { card } = await client3.getCard(cardId);
31998
33284
  const projectId = card.project_id;
31999
33285
  if (projectId) {
32000
- const board = await client2.getBoard(projectId, {
33286
+ const board = await client3.getBoard(projectId, {
32001
33287
  summary: true
32002
33288
  });
32003
33289
  const columns = board.columns;
@@ -32005,7 +33291,7 @@ class HarmonyMCPServer {
32005
33291
  if (moveToColumn) {
32006
33292
  const col = columns.find((c) => c.name.toLowerCase().includes(moveToColumn.toLowerCase()));
32007
33293
  if (col) {
32008
- await client2.moveCard(cardId, col.id);
33294
+ await client3.moveCard(cardId, col.id);
32009
33295
  movedTo = col.name;
32010
33296
  }
32011
33297
  }
@@ -32013,7 +33299,7 @@ class HarmonyMCPServer {
32013
33299
  for (const labelName of addLabels) {
32014
33300
  const label = labels.find((l) => l.name.toLowerCase() === labelName.toLowerCase());
32015
33301
  if (label) {
32016
- await client2.addLabelToCard(cardId, label.id);
33302
+ await client3.addLabelToCard(cardId, label.id);
32017
33303
  labelsAdded.push(label.name);
32018
33304
  }
32019
33305
  }
@@ -32027,27 +33313,56 @@ class HarmonyMCPServer {
32027
33313
  try {
32028
33314
  const workspaceId = getActiveWorkspaceId();
32029
33315
  if (workspaceId) {
32030
- const { members } = await client2.getWorkspaceMembers(workspaceId);
33316
+ const { members } = await client3.getWorkspaceMembers(workspaceId);
32031
33317
  const user = members.find((m) => m.email === userEmail);
32032
33318
  if (user) {
32033
- await client2.updateCard(cardId, { assigneeId: user.id });
33319
+ await client3.updateCard(cardId, { assigneeId: user.id });
32034
33320
  assignedTo = user.email;
32035
33321
  }
32036
33322
  }
32037
33323
  } catch {}
32038
33324
  }
32039
- const result = await client2.startAgentSession(cardId, {
33325
+ const result = await client3.startAgentSession(cardId, {
32040
33326
  agentIdentifier,
32041
33327
  agentName,
32042
33328
  status: "working",
32043
33329
  currentTask: args.currentTask,
32044
33330
  estimatedMinutesRemaining: args.estimatedMinutesRemaining
32045
33331
  });
33332
+ let prefetchedMemoryIds = [];
33333
+ try {
33334
+ const workspaceId = getActiveWorkspaceId();
33335
+ if (workspaceId) {
33336
+ const { card } = await client3.getCard(cardId);
33337
+ const typedCard = card;
33338
+ const cardLabels = (typedCard.labels || []).map((l) => l.name);
33339
+ const taskContext = [
33340
+ typedCard.title || "",
33341
+ typedCard.description || ""
33342
+ ].filter(Boolean).join(" ");
33343
+ const assembled = await assembleContext({
33344
+ workspaceId,
33345
+ projectId: getActiveProjectId() || undefined,
33346
+ taskContext,
33347
+ cardLabels,
33348
+ cardId,
33349
+ tokenBudget: 2000,
33350
+ client: client3
33351
+ });
33352
+ prefetchedMemoryIds = assembled.memories.map((m) => m.id);
33353
+ if (prefetchedMemoryIds.length > 0) {
33354
+ const graphResult = await discoverRelatedContext(client3, prefetchedMemoryIds.slice(0, 5), 2, 10);
33355
+ const additionalIds = graphResult.entities.map((e) => e.id).filter((id) => !prefetchedMemoryIds.includes(id));
33356
+ prefetchedMemoryIds.push(...additionalIds);
33357
+ }
33358
+ }
33359
+ } catch {}
32046
33360
  return {
32047
33361
  success: true,
32048
33362
  assignedTo,
32049
33363
  movedTo,
32050
33364
  labelsAdded,
33365
+ prefetchedMemoryCount: prefetchedMemoryIds.length,
32051
33366
  ...result
32052
33367
  };
32053
33368
  }
@@ -32055,7 +33370,7 @@ class HarmonyMCPServer {
32055
33370
  const cardId = exports_external.string().uuid().parse(args.cardId);
32056
33371
  const agentIdentifier = exports_external.string().min(1).parse(args.agentIdentifier);
32057
33372
  const agentName = exports_external.string().min(1).parse(args.agentName);
32058
- const result = await client2.updateAgentProgress(cardId, {
33373
+ const result = await client3.updateAgentProgress(cardId, {
32059
33374
  agentIdentifier,
32060
33375
  agentName,
32061
33376
  status: args.status,
@@ -32069,33 +33384,54 @@ class HarmonyMCPServer {
32069
33384
  case "harmony_end_agent_session": {
32070
33385
  const cardId = exports_external.string().uuid().parse(args.cardId);
32071
33386
  const moveToColumn = args.moveToColumn;
32072
- const result = await client2.endAgentSession(cardId, {
32073
- status: args.status || "completed",
33387
+ const sessionStatus = args.status || "completed";
33388
+ const result = await client3.endAgentSession(cardId, {
33389
+ status: sessionStatus,
32074
33390
  progressPercent: args.progressPercent
32075
33391
  });
32076
33392
  let movedTo = null;
32077
- if (moveToColumn) {
32078
- try {
32079
- const { card } = await client2.getCard(cardId);
32080
- const projectId = card.project_id;
32081
- if (projectId) {
32082
- const board = await client2.getBoard(projectId, {
32083
- summary: true
32084
- });
32085
- const columns = board.columns;
32086
- const col = columns.find((c) => c.name.toLowerCase().includes(moveToColumn.toLowerCase()));
32087
- if (col) {
32088
- await client2.moveCard(cardId, col.id);
32089
- movedTo = col.name;
32090
- }
33393
+ let learningsExtracted = 0;
33394
+ let cardTitle = "";
33395
+ let cardLabels = [];
33396
+ try {
33397
+ const { card } = await client3.getCard(cardId);
33398
+ const typedCard = card;
33399
+ cardTitle = typedCard.title || "";
33400
+ cardLabels = (typedCard.labels || []).map((l) => l.name);
33401
+ const projectId = typedCard.project_id;
33402
+ if (moveToColumn && projectId) {
33403
+ const board = await client3.getBoard(projectId, {
33404
+ summary: true
33405
+ });
33406
+ const columns = board.columns;
33407
+ const col = columns.find((c) => c.name.toLowerCase().includes(moveToColumn.toLowerCase()));
33408
+ if (col) {
33409
+ await client3.moveCard(cardId, col.id);
33410
+ movedTo = col.name;
32091
33411
  }
32092
- } catch {}
32093
- }
32094
- return { success: true, movedTo, ...result };
33412
+ }
33413
+ } catch {}
33414
+ try {
33415
+ const session = result.session;
33416
+ const sessionContext = {
33417
+ cardId,
33418
+ cardTitle,
33419
+ cardLabels,
33420
+ agentIdentifier: session?.agent_identifier || "unknown",
33421
+ agentName: session?.agent_name || "Unknown Agent",
33422
+ status: sessionStatus,
33423
+ progressPercent: args.progressPercent,
33424
+ blockers: session?.blockers || undefined,
33425
+ currentTask: session?.current_task || undefined
33426
+ };
33427
+ const learningResult = await extractLearnings(client3, sessionContext);
33428
+ learningsExtracted = learningResult.count;
33429
+ } catch {}
33430
+ return { success: true, movedTo, learningsExtracted, ...result };
32095
33431
  }
32096
33432
  case "harmony_get_agent_session": {
32097
33433
  const cardId = exports_external.string().uuid().parse(args.cardId);
32098
- const result = await client2.getAgentSession(cardId, {
33434
+ const result = await client3.getAgentSession(cardId, {
32099
33435
  includeEnded: args.includeEnded === true || args.includeEnded === "true"
32100
33436
  });
32101
33437
  return { success: true, ...result };
@@ -32105,7 +33441,7 @@ class HarmonyMCPServer {
32105
33441
  let columnData = null;
32106
33442
  if (args.cardId) {
32107
33443
  const cardId = exports_external.string().uuid().parse(args.cardId);
32108
- const cardResult = await client2.getCard(cardId);
33444
+ const cardResult = await client3.getCard(cardId);
32109
33445
  cardData = cardResult.card;
32110
33446
  } else if (args.shortId !== undefined) {
32111
33447
  const shortId = exports_external.number().int().positive().parse(args.shortId);
@@ -32113,7 +33449,7 @@ class HarmonyMCPServer {
32113
33449
  if (!projectId) {
32114
33450
  throw new Error("Project ID required when using shortId. Use harmony_set_project_context or provide projectId.");
32115
33451
  }
32116
- const cardResult = await client2.getCardByShortId(projectId, shortId);
33452
+ const cardResult = await client3.getCardByShortId(projectId, shortId);
32117
33453
  cardData = cardResult.card;
32118
33454
  } else {
32119
33455
  throw new Error("Either cardId or shortId must be provided");
@@ -32121,7 +33457,7 @@ class HarmonyMCPServer {
32121
33457
  const projectIdForBoard = args.projectId || getActiveProjectId() || cardData.project_id;
32122
33458
  if (projectIdForBoard) {
32123
33459
  try {
32124
- const board = await client2.getBoard(projectIdForBoard, {
33460
+ const board = await client3.getBoard(projectIdForBoard, {
32125
33461
  summary: true
32126
33462
  });
32127
33463
  const columnId = cardData.column_id;
@@ -32142,36 +33478,64 @@ class HarmonyMCPServer {
32142
33478
  if (args.includeDescription !== undefined) {
32143
33479
  contextOptions.includeDescription = args.includeDescription === true || args.includeDescription === "true";
32144
33480
  }
33481
+ let assembledContextStr;
33482
+ let assemblyId;
32145
33483
  let memories;
32146
33484
  try {
32147
33485
  const workspaceId = getActiveWorkspaceId();
32148
33486
  if (workspaceId && cardData.title) {
32149
- const memoryResult = await client2.searchMemoryEntities(workspaceId, cardData.title, {
32150
- project_id: getActiveProjectId() || undefined,
32151
- limit: 5
33487
+ const cardLabels = (cardData.labels || []).map((l) => l.name);
33488
+ const taskContext = [
33489
+ cardData.title,
33490
+ cardData.description || ""
33491
+ ].filter(Boolean).join(" ");
33492
+ const assembled = await assembleContext({
33493
+ workspaceId,
33494
+ projectId: getActiveProjectId() || undefined,
33495
+ taskContext,
33496
+ cardLabels,
33497
+ cardId: cardData.id,
33498
+ client: client3
32152
33499
  });
32153
- if (memoryResult.entities?.length > 0) {
32154
- memories = memoryResult.entities.map((e) => {
32155
- const entity = e;
32156
- return {
32157
- id: entity.id,
32158
- type: entity.type,
32159
- title: entity.title,
32160
- content: entity.content,
32161
- confidence: entity.confidence,
32162
- tags: entity.tags || []
32163
- };
32164
- });
33500
+ if (assembled.context) {
33501
+ assembledContextStr = assembled.context;
33502
+ assemblyId = assembled.manifest.assemblyId;
33503
+ cacheManifest(assembled.manifest);
32165
33504
  }
32166
33505
  }
32167
- } catch {}
33506
+ } catch {
33507
+ try {
33508
+ const workspaceId = getActiveWorkspaceId();
33509
+ if (workspaceId && cardData.title) {
33510
+ const memoryResult = await client3.searchMemoryEntities(workspaceId, cardData.title, {
33511
+ project_id: getActiveProjectId() || undefined,
33512
+ limit: 5
33513
+ });
33514
+ if (memoryResult.entities?.length > 0) {
33515
+ memories = memoryResult.entities.map((e) => {
33516
+ const entity = e;
33517
+ return {
33518
+ id: entity.id,
33519
+ type: entity.type,
33520
+ title: entity.title,
33521
+ content: entity.content,
33522
+ confidence: entity.confidence,
33523
+ tags: entity.tags || []
33524
+ };
33525
+ });
33526
+ }
33527
+ }
33528
+ } catch {}
33529
+ }
32168
33530
  const result = generatePrompt({
32169
33531
  card: cardData,
32170
33532
  column: columnData,
32171
33533
  variant,
32172
33534
  contextOptions,
32173
33535
  customConstraints: args.customConstraints,
32174
- memories
33536
+ memories,
33537
+ assembledContext: assembledContextStr,
33538
+ assemblyId
32175
33539
  });
32176
33540
  return {
32177
33541
  success: true,
@@ -32188,11 +33552,13 @@ class HarmonyMCPServer {
32188
33552
  if (!workspaceId) {
32189
33553
  throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
32190
33554
  }
32191
- const result = await client2.createMemoryEntity({
33555
+ const entityType = args.type || "context";
33556
+ const result = await client3.createMemoryEntity({
32192
33557
  workspace_id: workspaceId,
32193
33558
  project_id: args.projectId || getActiveProjectId() || undefined,
32194
- type: args.type || "context",
33559
+ type: entityType,
32195
33560
  scope: args.scope || "project",
33561
+ memory_tier: args.tier || undefined,
32196
33562
  title,
32197
33563
  content,
32198
33564
  metadata: args.metadata,
@@ -32207,7 +33573,7 @@ class HarmonyMCPServer {
32207
33573
  if (!workspaceId) {
32208
33574
  throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
32209
33575
  }
32210
- const markdown = await client2.listMemoryEntitiesMarkdown({
33576
+ const markdown = await client3.listMemoryEntitiesMarkdown({
32211
33577
  workspace_id: workspaceId,
32212
33578
  project_id: args.projectId || getActiveProjectId() || undefined,
32213
33579
  type: args.type,
@@ -32236,12 +33602,12 @@ class HarmonyMCPServer {
32236
33602
  updates.confidence = args.confidence;
32237
33603
  if (args.metadata !== undefined)
32238
33604
  updates.metadata = args.metadata;
32239
- const result = await client2.updateMemoryEntity(entityId, updates);
33605
+ const result = await client3.updateMemoryEntity(entityId, updates);
32240
33606
  return { success: true, ...result };
32241
33607
  }
32242
33608
  case "harmony_forget": {
32243
33609
  const entityId = exports_external.string().uuid().parse(args.entityId);
32244
- await client2.deleteMemoryEntity(entityId);
33610
+ await client3.deleteMemoryEntity(entityId);
32245
33611
  return { success: true };
32246
33612
  }
32247
33613
  case "harmony_relate": {
@@ -32254,9 +33620,10 @@ class HarmonyMCPServer {
32254
33620
  "supports",
32255
33621
  "depends_on",
32256
33622
  "part_of",
32257
- "caused_by"
33623
+ "caused_by",
33624
+ "relates_to"
32258
33625
  ]).parse(args.relationType);
32259
- const result = await client2.createMemoryRelation({
33626
+ const result = await client3.createMemoryRelation({
32260
33627
  source_id: sourceId,
32261
33628
  target_id: targetId,
32262
33629
  relation_type: relationType,
@@ -32270,17 +33637,81 @@ class HarmonyMCPServer {
32270
33637
  if (!workspaceId) {
32271
33638
  throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
32272
33639
  }
32273
- const markdown = await client2.searchMemoryEntitiesMarkdown(workspaceId, query, {
33640
+ const markdown = await client3.searchMemoryEntitiesMarkdown(workspaceId, query, {
32274
33641
  project_id: args.projectId || getActiveProjectId() || undefined,
32275
33642
  type: args.type,
32276
33643
  limit: args.limit
32277
33644
  });
32278
33645
  return markdown || "No memories found.";
32279
33646
  }
33647
+ case "harmony_vault_index": {
33648
+ const workspaceId = args.workspaceId || getActiveWorkspaceId();
33649
+ if (!workspaceId) {
33650
+ throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
33651
+ }
33652
+ const markdown = await client3.getVaultIndexMarkdown({
33653
+ workspace_id: workspaceId,
33654
+ project_id: args.projectId || getActiveProjectId() || undefined,
33655
+ type: args.type,
33656
+ limit: args.limit
33657
+ });
33658
+ return markdown || "No entities found.";
33659
+ }
33660
+ case "harmony_resolve_links": {
33661
+ const workspaceId = args.workspaceId || getActiveWorkspaceId();
33662
+ if (!workspaceId) {
33663
+ throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
33664
+ }
33665
+ const result = await client3.resolveLinks({
33666
+ workspace_id: workspaceId,
33667
+ project_id: args.projectId || getActiveProjectId() || undefined
33668
+ });
33669
+ return {
33670
+ success: true,
33671
+ ...result,
33672
+ message: `Resolved ${result.resolved} wiki-links, ${result.unresolved} remain unresolved.`
33673
+ };
33674
+ }
33675
+ case "harmony_sync": {
33676
+ const workspaceId = args.workspaceId || getActiveWorkspaceId();
33677
+ if (!workspaceId) {
33678
+ throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
33679
+ }
33680
+ const direction = args.direction || "full";
33681
+ const syncProjectId = args.projectId || getActiveProjectId() || undefined;
33682
+ const syncConfig = { memoryDir: getMemoryDir() };
33683
+ let syncResult;
33684
+ switch (direction) {
33685
+ case "pull":
33686
+ syncResult = await syncPull(client3, syncConfig, workspaceId, syncProjectId);
33687
+ break;
33688
+ case "push":
33689
+ syncResult = await syncPush(client3, syncConfig, workspaceId);
33690
+ break;
33691
+ default:
33692
+ syncResult = await syncFull(client3, syncConfig, workspaceId, syncProjectId);
33693
+ }
33694
+ const parts = [];
33695
+ if (syncResult.pulled > 0)
33696
+ parts.push(`${syncResult.pulled} pulled`);
33697
+ if (syncResult.pushed > 0)
33698
+ parts.push(`${syncResult.pushed} pushed`);
33699
+ if (syncResult.deleted > 0)
33700
+ parts.push(`${syncResult.deleted} deleted`);
33701
+ if (syncResult.conflicts > 0)
33702
+ parts.push(`${syncResult.conflicts} conflicts (server wins)`);
33703
+ const summary = parts.length > 0 ? parts.join(", ") : "Already in sync";
33704
+ return {
33705
+ success: syncResult.errors.length === 0,
33706
+ ...syncResult,
33707
+ memoryDir: syncConfig.memoryDir,
33708
+ message: summary
33709
+ };
33710
+ }
32280
33711
  case "harmony_create_plan": {
32281
33712
  const title = exports_external.string().min(1).parse(args.title);
32282
33713
  const projectId = args.projectId || getProjectId();
32283
- const result = await client2.createPlan(projectId, {
33714
+ const result = await client3.createPlan(projectId, {
32284
33715
  title,
32285
33716
  content: args.content,
32286
33717
  source: args.source || "agent",
@@ -32299,10 +33730,10 @@ class HarmonyMCPServer {
32299
33730
  let result = null;
32300
33731
  if (args.planId) {
32301
33732
  const planId = exports_external.string().uuid().parse(args.planId);
32302
- result = await client2.getPlan(planId);
33733
+ result = await client3.getPlan(planId);
32303
33734
  } else if (args.cardId) {
32304
33735
  const cardId = exports_external.string().uuid().parse(args.cardId);
32305
- result = await client2.getPlanByCardId(cardId);
33736
+ result = await client3.getPlanByCardId(cardId);
32306
33737
  if (!result) {
32307
33738
  return {
32308
33739
  success: true,
@@ -32330,9 +33761,251 @@ class HarmonyMCPServer {
32330
33761
  if (args.status !== undefined) {
32331
33762
  updates.status = exports_external.enum(["draft", "active", "archived"]).parse(args.status);
32332
33763
  }
32333
- const result = await client2.updatePlan(planId, updates);
33764
+ const result = await client3.updatePlan(planId, updates);
32334
33765
  return { success: true, plan: result.plan };
32335
33766
  }
33767
+ case "harmony_debug_context": {
33768
+ const entityId = exports_external.string().uuid().parse(args.entityId);
33769
+ const taskContext = exports_external.string().min(1).parse(args.taskContext);
33770
+ const cardLabels = args.cardLabels || [];
33771
+ const entityResult = await client3.getMemoryEntity(entityId);
33772
+ const entity = mapToContextEntity(entityResult.entity);
33773
+ const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels);
33774
+ const lifecycle2 = evaluateLifecycle({
33775
+ memory_tier: entity.memory_tier,
33776
+ confidence: entity.confidence,
33777
+ access_count: entity.access_count,
33778
+ last_accessed_at: entity.last_accessed_at,
33779
+ created_at: entity.updated_at
33780
+ });
33781
+ return {
33782
+ success: true,
33783
+ entity: {
33784
+ id: entity.id,
33785
+ title: entity.title,
33786
+ type: entity.type,
33787
+ tier: entity.memory_tier,
33788
+ confidence: entity.confidence,
33789
+ accessCount: entity.access_count
33790
+ },
33791
+ relevance: {
33792
+ score: Math.round(score * 1000) / 1000,
33793
+ reasons,
33794
+ wouldBeIncluded: score >= 0.1,
33795
+ threshold: 0.1
33796
+ },
33797
+ lifecycle: {
33798
+ decayScore: Math.round(lifecycle2.decay.score * 1000) / 1000,
33799
+ daysSinceAccess: Math.round(lifecycle2.decay.daysSinceAccess),
33800
+ promotionEligible: lifecycle2.promotion.eligible,
33801
+ promotionTarget: lifecycle2.promotion.targetTier,
33802
+ shouldArchive: lifecycle2.shouldArchive,
33803
+ shouldFlagForReview: lifecycle2.shouldFlagForReview
33804
+ },
33805
+ suggestions: [
33806
+ ...lifecycle2.promotion.eligible ? [`Eligible for promotion to ${lifecycle2.promotion.targetTier}: ${lifecycle2.promotion.reason}`] : [],
33807
+ ...lifecycle2.shouldArchive ? [`Consider archiving: ${lifecycle2.archiveReason}`] : [],
33808
+ ...lifecycle2.shouldFlagForReview ? [`Flagged for review: ${lifecycle2.reviewReason}`] : [],
33809
+ ...score < 0.1 ? ["Below relevance threshold - consider adding more specific tags or updating content"] : []
33810
+ ]
33811
+ };
33812
+ }
33813
+ case "harmony_export_memory_graph": {
33814
+ const workspaceId = args.workspaceId || getActiveWorkspaceId();
33815
+ if (!workspaceId) {
33816
+ throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
33817
+ }
33818
+ const projectId = args.projectId || getActiveProjectId() || undefined;
33819
+ const limit = args.limit || 50;
33820
+ const result = await client3.listMemoryEntities({
33821
+ workspace_id: workspaceId,
33822
+ project_id: projectId,
33823
+ limit
33824
+ });
33825
+ const entities = result.entities || [];
33826
+ const tierColors = {
33827
+ draft: "#FFEB3B",
33828
+ episode: "#2196F3",
33829
+ reference: "#4CAF50"
33830
+ };
33831
+ const typeShapes = {
33832
+ error: "octagon",
33833
+ solution: "diamond",
33834
+ pattern: "hexagon",
33835
+ decision: "house",
33836
+ lesson: "ellipse",
33837
+ preference: "parallelogram"
33838
+ };
33839
+ const lines = [
33840
+ "digraph KnowledgeGraph {",
33841
+ " rankdir=LR;",
33842
+ " node [style=filled, fontsize=10];",
33843
+ ""
33844
+ ];
33845
+ for (const entity of entities) {
33846
+ const tier = entity.memory_tier || "reference";
33847
+ const color = tierColors[tier] || "#9E9E9E";
33848
+ const shape = typeShapes[entity.type] || "box";
33849
+ const label = entity.title.length > 40 ? entity.title.slice(0, 37) + "..." : entity.title;
33850
+ const safeLabel = label.replace(/"/g, "\\\"");
33851
+ lines.push(` "${entity.id.slice(0, 8)}" [label="${safeLabel}\\n(${entity.type}, ${tier})", fillcolor="${color}", shape=${shape}];`);
33852
+ }
33853
+ const entityIds = new Set(entities.map((e) => e.id));
33854
+ const addedEdges = new Set;
33855
+ for (const entity of entities.slice(0, 20)) {
33856
+ try {
33857
+ const related = await client3.getRelatedEntities(entity.id);
33858
+ for (const raw of [...related.outgoing || [], ...related.incoming || []]) {
33859
+ const rel = raw;
33860
+ if (entityIds.has(rel.source_id) && entityIds.has(rel.target_id)) {
33861
+ const edgeKey = `${rel.source_id}-${rel.target_id}-${rel.relation_type}`;
33862
+ if (!addedEdges.has(edgeKey)) {
33863
+ addedEdges.add(edgeKey);
33864
+ lines.push(` "${rel.source_id.slice(0, 8)}" -> "${rel.target_id.slice(0, 8)}" [label="${rel.relation_type}"];`);
33865
+ }
33866
+ }
33867
+ }
33868
+ } catch {}
33869
+ }
33870
+ lines.push("}");
33871
+ const dotGraph = lines.join(`
33872
+ `);
33873
+ return {
33874
+ success: true,
33875
+ format: "dot",
33876
+ entityCount: entities.length,
33877
+ edgeCount: addedEdges.size,
33878
+ graph: dotGraph,
33879
+ message: `Exported ${entities.length} entities and ${addedEdges.size} relations as DOT graph. Use Graphviz to render: echo '<graph>' | dot -Tpng -o graph.png`
33880
+ };
33881
+ }
33882
+ case "harmony_prune_draft": {
33883
+ const workspaceId = args.workspaceId || getActiveWorkspaceId();
33884
+ if (!workspaceId) {
33885
+ throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
33886
+ }
33887
+ const projectId = args.projectId || getActiveProjectId() || undefined;
33888
+ const dryRun = args.dryRun !== false;
33889
+ const maxAgeDays = args.maxAgeDays || 30;
33890
+ const result = await client3.listMemoryEntities({
33891
+ workspace_id: workspaceId,
33892
+ project_id: projectId,
33893
+ limit: 100
33894
+ });
33895
+ const drafts = (result.entities || []).filter((e) => {
33896
+ const entity = e;
33897
+ return entity.memory_tier === "draft";
33898
+ });
33899
+ const now = Date.now();
33900
+ const stale = [];
33901
+ for (const raw of drafts) {
33902
+ const entity = raw;
33903
+ const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
33904
+ if (ageDays < maxAgeDays)
33905
+ continue;
33906
+ const lifecycle2 = evaluateLifecycle(entity);
33907
+ stale.push({
33908
+ id: entity.id,
33909
+ title: entity.title,
33910
+ ageDays: Math.round(ageDays),
33911
+ decayScore: Math.round(lifecycle2.decay.score * 100) / 100
33912
+ });
33913
+ }
33914
+ if (!dryRun) {
33915
+ for (const item of stale) {
33916
+ try {
33917
+ await client3.deleteMemoryEntity(item.id);
33918
+ } catch {}
33919
+ }
33920
+ }
33921
+ return {
33922
+ success: true,
33923
+ dryRun,
33924
+ totalDrafts: drafts.length,
33925
+ staleDrafts: stale.length,
33926
+ pruned: dryRun ? 0 : stale.length,
33927
+ items: stale,
33928
+ message: dryRun ? `Found ${stale.length} stale drafts (>${maxAgeDays} days old). Run with dryRun=false to delete.` : `Pruned ${stale.length} stale draft memories.`
33929
+ };
33930
+ }
33931
+ case "harmony_review_learnings": {
33932
+ const workspaceId = args.workspaceId || getActiveWorkspaceId();
33933
+ if (!workspaceId) {
33934
+ throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
33935
+ }
33936
+ const projectId = args.projectId || getActiveProjectId() || undefined;
33937
+ const pending = await listPendingLearnings(client3, workspaceId, projectId);
33938
+ return {
33939
+ success: true,
33940
+ count: pending.length,
33941
+ learnings: pending,
33942
+ message: pending.length > 0 ? `${pending.length} pending learnings awaiting review. Use harmony_approve_learning to approve or reject.` : "No pending learnings to review."
33943
+ };
33944
+ }
33945
+ case "harmony_approve_learning": {
33946
+ const entityId = exports_external.string().uuid().parse(args.entityId);
33947
+ const action = exports_external.enum(["approve", "reject"]).parse(args.action);
33948
+ if (action === "approve") {
33949
+ await approveLearning(client3, entityId, args.confidence);
33950
+ return {
33951
+ success: true,
33952
+ action: "approved",
33953
+ message: "Learning approved and confidence set."
33954
+ };
33955
+ }
33956
+ await rejectLearning(client3, entityId);
33957
+ return {
33958
+ success: true,
33959
+ action: "rejected",
33960
+ message: "Learning rejected and archived."
33961
+ };
33962
+ }
33963
+ case "harmony_get_context_manifest": {
33964
+ const assemblyId = exports_external.string().min(1).parse(args.assemblyId);
33965
+ const manifest = getCachedManifest(assemblyId);
33966
+ if (!manifest) {
33967
+ throw new Error(`Manifest not found for assembly '${assemblyId}'. Manifests are cached in-memory and expire after server restart.`);
33968
+ }
33969
+ return {
33970
+ success: true,
33971
+ manifest,
33972
+ summary: {
33973
+ totalIncluded: manifest.included.length,
33974
+ totalExcluded: manifest.excluded.length,
33975
+ budgetUsed: `${manifest.budgetUsed}/${manifest.budgetTotal} tokens`,
33976
+ tierBreakdown: manifest.tierBreakdown
33977
+ }
33978
+ };
33979
+ }
33980
+ case "harmony_promote_memory": {
33981
+ const entityId = exports_external.string().uuid().parse(args.entityId);
33982
+ const targetTier = exports_external.enum(["episode", "reference"]).parse(args.targetTier);
33983
+ const reason = exports_external.string().min(1).parse(args.reason);
33984
+ const entityResult = await client3.getMemoryEntity(entityId);
33985
+ const entity = entityResult.entity;
33986
+ const currentTier = entity.memory_tier || "reference";
33987
+ const tierOrder = { draft: 0, episode: 1, reference: 2 };
33988
+ if ((tierOrder[targetTier] || 0) <= (tierOrder[currentTier] || 0)) {
33989
+ throw new Error(`Cannot promote from '${currentTier}' to '${targetTier}'. Must promote to a higher tier.`);
33990
+ }
33991
+ const result = await client3.updateMemoryEntity(entityId, {
33992
+ memory_tier: targetTier,
33993
+ metadata: {
33994
+ promoted_from_tier: currentTier,
33995
+ promotion_reason: reason,
33996
+ promoted_at: new Date().toISOString()
33997
+ }
33998
+ });
33999
+ return {
34000
+ success: true,
34001
+ promoted: {
34002
+ from: currentTier,
34003
+ to: targetTier,
34004
+ reason
34005
+ },
34006
+ ...result
34007
+ };
34008
+ }
32336
34009
  default:
32337
34010
  throw new Error(`Unknown tool: ${name}`);
32338
34011
  }
@@ -32341,19 +34014,32 @@ class HarmonyMCPServer {
32341
34014
  const transport = new StdioServerTransport;
32342
34015
  await this.server.connect(transport);
32343
34016
  console.error("Harmony MCP server running on stdio");
34017
+ try {
34018
+ if (isConfigured()) {
34019
+ const workspaceId = getActiveWorkspaceId();
34020
+ if (workspaceId) {
34021
+ const client3 = getClient();
34022
+ const syncConfig = { memoryDir: getMemoryDir() };
34023
+ const result = await syncPull(client3, syncConfig, workspaceId);
34024
+ console.error(`Startup sync: ${result.pulled} pulled, ${result.deleted} deleted, ${result.errors.length} errors`);
34025
+ }
34026
+ }
34027
+ } catch (err) {
34028
+ console.error(`Startup sync failed (non-fatal): ${err}`);
34029
+ }
32344
34030
  }
32345
34031
  }
32346
34032
 
32347
34033
  // src/tui/setup.ts
32348
34034
  import {
32349
- existsSync as existsSync4,
34035
+ existsSync as existsSync5,
32350
34036
  lstatSync,
32351
- mkdirSync as mkdirSync3,
34037
+ mkdirSync as mkdirSync4,
32352
34038
  symlinkSync,
32353
34039
  unlinkSync
32354
34040
  } from "node:fs";
32355
34041
  import { homedir as homedir4 } from "node:os";
32356
- import { dirname as dirname2, join as join3 } from "node:path";
34042
+ import { dirname as dirname3, join as join4 } from "node:path";
32357
34043
 
32358
34044
  // ../../node_modules/@clack/core/dist/index.mjs
32359
34045
  var import_sisteransi = __toESM(require_src(), 1);
@@ -33067,16 +34753,16 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
33067
34753
  };
33068
34754
 
33069
34755
  // src/tui/agents.ts
33070
- import { existsSync as existsSync2 } from "node:fs";
34756
+ import { existsSync as existsSync3 } from "node:fs";
33071
34757
  import { homedir as homedir2 } from "node:os";
33072
- import { join as join2 } from "node:path";
34758
+ import { join as join3 } from "node:path";
33073
34759
  var AGENT_DEFINITIONS = [
33074
34760
  {
33075
34761
  id: "claude",
33076
34762
  name: "Claude Code",
33077
34763
  description: "Anthropic CLI agent",
33078
34764
  hint: "/hmy <card>",
33079
- globalPaths: [join2(homedir2(), ".claude")],
34765
+ globalPaths: [join3(homedir2(), ".claude")],
33080
34766
  localPaths: [".claude"]
33081
34767
  },
33082
34768
  {
@@ -33084,7 +34770,7 @@ var AGENT_DEFINITIONS = [
33084
34770
  name: "Codex",
33085
34771
  description: "OpenAI coding agent",
33086
34772
  hint: "/prompts:hmy <card>",
33087
- globalPaths: [join2(homedir2(), ".codex")],
34773
+ globalPaths: [join3(homedir2(), ".codex")],
33088
34774
  localPaths: ["AGENTS.md"]
33089
34775
  },
33090
34776
  {
@@ -33100,20 +34786,20 @@ var AGENT_DEFINITIONS = [
33100
34786
  name: "Windsurf",
33101
34787
  description: "Codeium AI IDE",
33102
34788
  hint: "MCP tools available automatically",
33103
- globalPaths: [join2(homedir2(), ".codeium", "windsurf")],
34789
+ globalPaths: [join3(homedir2(), ".codeium", "windsurf")],
33104
34790
  localPaths: [".windsurf", ".windsurfrules"]
33105
34791
  }
33106
34792
  ];
33107
34793
  function detectAgents(cwd = process.cwd()) {
33108
34794
  return AGENT_DEFINITIONS.map((def) => {
33109
- const globalPath = def.globalPaths.find((p2) => existsSync2(p2)) || null;
33110
- const localPath = def.localPaths.find((p2) => existsSync2(join2(cwd, p2))) || null;
34795
+ const globalPath = def.globalPaths.find((p2) => existsSync3(p2)) || null;
34796
+ const localPath = def.localPaths.find((p2) => existsSync3(join3(cwd, p2))) || null;
33111
34797
  return {
33112
34798
  id: def.id,
33113
34799
  name: def.name,
33114
34800
  detected: !!(globalPath || localPath),
33115
34801
  globalPath,
33116
- localPath: localPath ? join2(cwd, localPath) : null,
34802
+ localPath: localPath ? join3(cwd, localPath) : null,
33117
34803
  description: def.description,
33118
34804
  hint: def.hint
33119
34805
  };
@@ -33185,23 +34871,23 @@ function formatPath(path, homeDir) {
33185
34871
  }
33186
34872
 
33187
34873
  // src/tui/writer.ts
33188
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
34874
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
33189
34875
  import { homedir as homedir3 } from "node:os";
33190
- import { dirname } from "node:path";
34876
+ import { dirname as dirname2 } from "node:path";
33191
34877
  function ensureDir(dirPath) {
33192
- if (!existsSync3(dirPath)) {
33193
- mkdirSync2(dirPath, { recursive: true, mode: 493 });
34878
+ if (!existsSync4(dirPath)) {
34879
+ mkdirSync3(dirPath, { recursive: true, mode: 493 });
33194
34880
  }
33195
34881
  }
33196
34882
  function writeFile(filePath, content, options = {}) {
33197
- const exists = existsSync3(filePath);
34883
+ const exists = existsSync4(filePath);
33198
34884
  if (exists && !options.force) {
33199
34885
  return { path: filePath, action: "skip" };
33200
34886
  }
33201
34887
  try {
33202
- ensureDir(dirname(filePath));
34888
+ ensureDir(dirname2(filePath));
33203
34889
  const mode = filePath.includes(".harmony-mcp") ? 384 : 420;
33204
- writeFileSync2(filePath, content, { mode });
34890
+ writeFileSync3(filePath, content, { mode });
33205
34891
  return { path: filePath, action: exists ? "update" : "create" };
33206
34892
  } catch (error48) {
33207
34893
  return {
@@ -33212,11 +34898,11 @@ function writeFile(filePath, content, options = {}) {
33212
34898
  }
33213
34899
  }
33214
34900
  function mergeJsonFile(filePath, updates, options = {}) {
33215
- const exists = existsSync3(filePath);
34901
+ const exists = existsSync4(filePath);
33216
34902
  if (!exists) {
33217
34903
  try {
33218
- ensureDir(dirname(filePath));
33219
- writeFileSync2(filePath, JSON.stringify(updates, null, 2), {
34904
+ ensureDir(dirname2(filePath));
34905
+ writeFileSync3(filePath, JSON.stringify(updates, null, 2), {
33220
34906
  mode: 420
33221
34907
  });
33222
34908
  return { path: filePath, action: "create" };
@@ -33229,7 +34915,7 @@ function mergeJsonFile(filePath, updates, options = {}) {
33229
34915
  }
33230
34916
  }
33231
34917
  try {
33232
- const existing = JSON.parse(readFileSync2(filePath, "utf-8"));
34918
+ const existing = JSON.parse(readFileSync3(filePath, "utf-8"));
33233
34919
  if (updates.mcpServers && existing.mcpServers) {
33234
34920
  const existingServers = existing.mcpServers;
33235
34921
  const updateServers = updates.mcpServers;
@@ -33237,12 +34923,12 @@ function mergeJsonFile(filePath, updates, options = {}) {
33237
34923
  } else {
33238
34924
  Object.assign(existing, updates);
33239
34925
  }
33240
- writeFileSync2(filePath, JSON.stringify(existing, null, 2), { mode: 420 });
34926
+ writeFileSync3(filePath, JSON.stringify(existing, null, 2), { mode: 420 });
33241
34927
  return { path: filePath, action: "merge" };
33242
34928
  } catch {
33243
34929
  if (options.force) {
33244
34930
  try {
33245
- writeFileSync2(filePath, JSON.stringify(updates, null, 2), {
34931
+ writeFileSync3(filePath, JSON.stringify(updates, null, 2), {
33246
34932
  mode: 420
33247
34933
  });
33248
34934
  return { path: filePath, action: "update" };
@@ -33262,11 +34948,11 @@ function mergeJsonFile(filePath, updates, options = {}) {
33262
34948
  }
33263
34949
  }
33264
34950
  function appendToToml(filePath, section, content, options = {}) {
33265
- const exists = existsSync3(filePath);
34951
+ const exists = existsSync4(filePath);
33266
34952
  if (!exists) {
33267
34953
  try {
33268
- ensureDir(dirname(filePath));
33269
- writeFileSync2(filePath, content, { mode: 420 });
34954
+ ensureDir(dirname2(filePath));
34955
+ writeFileSync3(filePath, content, { mode: 420 });
33270
34956
  return { path: filePath, action: "create" };
33271
34957
  } catch (error48) {
33272
34958
  return {
@@ -33277,18 +34963,18 @@ function appendToToml(filePath, section, content, options = {}) {
33277
34963
  }
33278
34964
  }
33279
34965
  try {
33280
- const existing = readFileSync2(filePath, "utf-8");
34966
+ const existing = readFileSync3(filePath, "utf-8");
33281
34967
  if (existing.includes(section)) {
33282
34968
  if (options.force) {
33283
34969
  const updated = existing.replace(new RegExp(`\\[${section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\[|$)`), content.trim() + `
33284
34970
 
33285
34971
  `);
33286
- writeFileSync2(filePath, updated, { mode: 420 });
34972
+ writeFileSync3(filePath, updated, { mode: 420 });
33287
34973
  return { path: filePath, action: "update" };
33288
34974
  }
33289
34975
  return { path: filePath, action: "skip" };
33290
34976
  }
33291
- writeFileSync2(filePath, existing + `
34977
+ writeFileSync3(filePath, existing + `
33292
34978
  ` + content, { mode: 420 });
33293
34979
  return { path: filePath, action: "merge" };
33294
34980
  } catch (error48) {
@@ -33338,7 +35024,7 @@ function getWriteSummary(files, options = {}) {
33338
35024
  const home = homedir3();
33339
35025
  for (const file2 of files) {
33340
35026
  const displayPath = formatPath(file2.path, home);
33341
- const exists = existsSync3(file2.path);
35027
+ const exists = existsSync4(file2.path);
33342
35028
  if (exists && !options.force) {
33343
35029
  toSkip.push(displayPath);
33344
35030
  } else if (exists) {
@@ -33351,7 +35037,7 @@ function getWriteSummary(files, options = {}) {
33351
35037
  }
33352
35038
 
33353
35039
  // src/tui/setup.ts
33354
- var GLOBAL_SKILLS_DIR = join3(homedir4(), ".agents", "skills");
35040
+ var GLOBAL_SKILLS_DIR = join4(homedir4(), ".agents", "skills");
33355
35041
  var API_URL = "https://gethmy.com/api";
33356
35042
  var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
33357
35043
 
@@ -33578,16 +35264,16 @@ async function registerMcpServer() {
33578
35264
  }
33579
35265
  }
33580
35266
  async function writeMcpConfigFallback(home) {
33581
- const { readFileSync: readFileSync3, writeFileSync: writeFileSync3, mkdirSync: mkdirSync4, existsSync: existsSync5 } = await import("node:fs");
33582
- const settingsPath = join3(home, ".claude", "settings.json");
33583
- const settingsDir = dirname2(settingsPath);
33584
- if (!existsSync5(settingsDir)) {
33585
- mkdirSync4(settingsDir, { recursive: true });
35267
+ const { readFileSync: readFileSync4, writeFileSync: writeFileSync4, mkdirSync: mkdirSync5, existsSync: existsSync6 } = await import("node:fs");
35268
+ const settingsPath = join4(home, ".claude", "settings.json");
35269
+ const settingsDir = dirname3(settingsPath);
35270
+ if (!existsSync6(settingsDir)) {
35271
+ mkdirSync5(settingsDir, { recursive: true });
33586
35272
  }
33587
35273
  let settings = {};
33588
- if (existsSync5(settingsPath)) {
35274
+ if (existsSync6(settingsPath)) {
33589
35275
  try {
33590
- settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
35276
+ settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
33591
35277
  } catch {}
33592
35278
  }
33593
35279
  const mcpServers = settings.mcpServers || {};
@@ -33596,7 +35282,7 @@ async function writeMcpConfigFallback(home) {
33596
35282
  args: ["-y", "harmony-mcp@latest", "serve"]
33597
35283
  };
33598
35284
  settings.mcpServers = mcpServers;
33599
- writeFileSync3(settingsPath, JSON.stringify(settings, null, 2));
35285
+ writeFileSync4(settingsPath, JSON.stringify(settings, null, 2));
33600
35286
  }
33601
35287
  async function validateApiKey(apiKey, apiUrl = API_URL) {
33602
35288
  try {
@@ -33685,31 +35371,31 @@ ${HARMONY_PLAN_PROMPT.replace("$ARGUMENTS", "$ARGUMENTS")}
33685
35371
  `;
33686
35372
  if (installMode === "global") {
33687
35373
  files.push({
33688
- path: join3(GLOBAL_SKILLS_DIR, "hmy", "SKILL.md"),
35374
+ path: join4(GLOBAL_SKILLS_DIR, "hmy", "SKILL.md"),
33689
35375
  content: skillContent,
33690
35376
  type: "text"
33691
35377
  });
33692
35378
  symlinks.push({
33693
- target: join3(GLOBAL_SKILLS_DIR, "hmy"),
33694
- link: join3(home, ".claude", "skills", "hmy")
35379
+ target: join4(GLOBAL_SKILLS_DIR, "hmy"),
35380
+ link: join4(home, ".claude", "skills", "hmy")
33695
35381
  });
33696
35382
  files.push({
33697
- path: join3(GLOBAL_SKILLS_DIR, "hmy-plan", "SKILL.md"),
35383
+ path: join4(GLOBAL_SKILLS_DIR, "hmy-plan", "SKILL.md"),
33698
35384
  content: planSkillContent,
33699
35385
  type: "text"
33700
35386
  });
33701
35387
  symlinks.push({
33702
- target: join3(GLOBAL_SKILLS_DIR, "hmy-plan"),
33703
- link: join3(home, ".claude", "skills", "hmy-plan")
35388
+ target: join4(GLOBAL_SKILLS_DIR, "hmy-plan"),
35389
+ link: join4(home, ".claude", "skills", "hmy-plan")
33704
35390
  });
33705
35391
  } else {
33706
35392
  files.push({
33707
- path: join3(cwd, ".claude", "skills", "hmy", "SKILL.md"),
35393
+ path: join4(cwd, ".claude", "skills", "hmy", "SKILL.md"),
33708
35394
  content: skillContent,
33709
35395
  type: "text"
33710
35396
  });
33711
35397
  files.push({
33712
- path: join3(cwd, ".claude", "skills", "hmy-plan", "SKILL.md"),
35398
+ path: join4(cwd, ".claude", "skills", "hmy-plan", "SKILL.md"),
33713
35399
  content: planSkillContent,
33714
35400
  type: "text"
33715
35401
  });
@@ -33744,7 +35430,7 @@ When given a card reference (e.g., #42 or a card name), follow this workflow:
33744
35430
  - \`harmony_generate_prompt\` - Get role-based guidance and focus areas for the card
33745
35431
  `;
33746
35432
  files.push({
33747
- path: join3(cwd, "AGENTS.md"),
35433
+ path: join4(cwd, "AGENTS.md"),
33748
35434
  content: agentsContent,
33749
35435
  type: "text"
33750
35436
  });
@@ -33761,17 +35447,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "{{card}}").replace("Your agent
33761
35447
  `;
33762
35448
  if (installMode === "global") {
33763
35449
  files.push({
33764
- path: join3(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
35450
+ path: join4(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
33765
35451
  content: promptContent,
33766
35452
  type: "text"
33767
35453
  });
33768
35454
  symlinks.push({
33769
- target: join3(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
33770
- link: join3(home, ".codex", "prompts", "hmy.md")
35455
+ target: join4(GLOBAL_SKILLS_DIR, "codex", "hmy.md"),
35456
+ link: join4(home, ".codex", "prompts", "hmy.md")
33771
35457
  });
33772
35458
  } else {
33773
35459
  files.push({
33774
- path: join3(home, ".codex", "prompts", "hmy.md"),
35460
+ path: join4(home, ".codex", "prompts", "hmy.md"),
33775
35461
  content: promptContent,
33776
35462
  type: "text"
33777
35463
  });
@@ -33783,7 +35469,7 @@ command = "npx"
33783
35469
  args = ["-y", "harmony-mcp@latest", "serve"]
33784
35470
  `;
33785
35471
  files.push({
33786
- path: join3(home, ".codex", "config.toml"),
35472
+ path: join4(home, ".codex", "config.toml"),
33787
35473
  content: tomlContent,
33788
35474
  type: "toml",
33789
35475
  tomlSection: "mcp_servers.harmony"
@@ -33792,7 +35478,7 @@ args = ["-y", "harmony-mcp@latest", "serve"]
33792
35478
  }
33793
35479
  case "cursor": {
33794
35480
  files.push({
33795
- path: join3(cwd, ".cursor", "mcp.json"),
35481
+ path: join4(cwd, ".cursor", "mcp.json"),
33796
35482
  content: JSON.stringify({
33797
35483
  mcpServers: {
33798
35484
  harmony: {
@@ -33818,17 +35504,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
33818
35504
  `;
33819
35505
  if (installMode === "global") {
33820
35506
  files.push({
33821
- path: join3(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
35507
+ path: join4(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
33822
35508
  content: ruleContent,
33823
35509
  type: "text"
33824
35510
  });
33825
35511
  symlinks.push({
33826
- target: join3(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
33827
- link: join3(home, ".cursor", "rules", "harmony.mdc")
35512
+ target: join4(GLOBAL_SKILLS_DIR, "cursor", "harmony.mdc"),
35513
+ link: join4(home, ".cursor", "rules", "harmony.mdc")
33828
35514
  });
33829
35515
  } else {
33830
35516
  files.push({
33831
- path: join3(cwd, ".cursor", "rules", "harmony.mdc"),
35517
+ path: join4(cwd, ".cursor", "rules", "harmony.mdc"),
33832
35518
  content: ruleContent,
33833
35519
  type: "text"
33834
35520
  });
@@ -33837,7 +35523,7 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
33837
35523
  }
33838
35524
  case "windsurf": {
33839
35525
  files.push({
33840
- path: join3(home, ".codeium", "windsurf", "mcp_config.json"),
35526
+ path: join4(home, ".codeium", "windsurf", "mcp_config.json"),
33841
35527
  content: JSON.stringify({
33842
35528
  mcpServers: {
33843
35529
  harmony: {
@@ -33863,17 +35549,17 @@ ${HARMONY_WORKFLOW_PROMPT.replace("$ARGUMENTS", "the card reference").replace("Y
33863
35549
  `;
33864
35550
  if (installMode === "global") {
33865
35551
  files.push({
33866
- path: join3(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
35552
+ path: join4(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
33867
35553
  content: ruleContent,
33868
35554
  type: "text"
33869
35555
  });
33870
35556
  symlinks.push({
33871
- target: join3(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
33872
- link: join3(home, ".codeium", "windsurf", "rules", "harmony.md")
35557
+ target: join4(GLOBAL_SKILLS_DIR, "windsurf", "harmony.md"),
35558
+ link: join4(home, ".codeium", "windsurf", "rules", "harmony.md")
33873
35559
  });
33874
35560
  } else {
33875
35561
  files.push({
33876
- path: join3(cwd, ".windsurf", "rules", "harmony.md"),
35562
+ path: join4(cwd, ".windsurf", "rules", "harmony.md"),
33877
35563
  content: ruleContent,
33878
35564
  type: "text"
33879
35565
  });
@@ -34148,9 +35834,9 @@ async function runSetup(options = {}) {
34148
35834
  if (allSymlinks.length > 0) {
34149
35835
  for (const symlink of allSymlinks) {
34150
35836
  try {
34151
- const linkDir = dirname2(symlink.link);
34152
- if (!existsSync4(linkDir)) {
34153
- mkdirSync3(linkDir, { recursive: true });
35837
+ const linkDir = dirname3(symlink.link);
35838
+ if (!existsSync5(linkDir)) {
35839
+ mkdirSync4(linkDir, { recursive: true });
34154
35840
  }
34155
35841
  let linkExists = false;
34156
35842
  try {
@@ -34178,7 +35864,7 @@ async function runSetup(options = {}) {
34178
35864
  } else {
34179
35865
  try {
34180
35866
  await writeMcpConfigFallback(home);
34181
- console.log(` ${colors.success("✓")} ${colors.dim(formatPath(join3(home, ".claude", "settings.json"), home))} ${colors.dim("(updated)")}`);
35867
+ console.log(` ${colors.success("✓")} ${colors.dim(formatPath(join4(home, ".claude", "settings.json"), home))} ${colors.dim("(updated)")}`);
34182
35868
  } catch {
34183
35869
  M2.warning("Could not register MCP server. Run manually: claude mcp add --transport stdio harmony -- npx -y harmony-mcp@latest serve");
34184
35870
  }