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.
- package/dist/cli.js +1864 -178
- package/dist/index.js +1816 -130
- 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 (
|
|
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
|
|
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
|
-
|
|
31636
|
-
description: "
|
|
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
|
-
|
|
32680
|
+
workspaceId: {
|
|
31641
32681
|
type: "string",
|
|
31642
|
-
description: "
|
|
32682
|
+
description: "Workspace ID (optional if context set)"
|
|
31643
32683
|
},
|
|
31644
|
-
|
|
31645
|
-
content: {
|
|
32684
|
+
projectId: {
|
|
31646
32685
|
type: "string",
|
|
31647
|
-
description: "
|
|
32686
|
+
description: "Project ID (optional, filters to project scope)"
|
|
31648
32687
|
},
|
|
31649
|
-
|
|
32688
|
+
type: {
|
|
31650
32689
|
type: "string",
|
|
31651
|
-
enum: [
|
|
31652
|
-
|
|
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
|
-
|
|
31655
|
-
type: "
|
|
31656
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
31829
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
33212
|
+
await client3.deleteSubtask(subtaskId);
|
|
31927
33213
|
return { success: true };
|
|
31928
33214
|
}
|
|
31929
33215
|
case "harmony_list_workspaces": {
|
|
31930
|
-
const result = await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
33283
|
+
const { card } = await client3.getCard(cardId);
|
|
31998
33284
|
const projectId = card.project_id;
|
|
31999
33285
|
if (projectId) {
|
|
32000
|
-
const board = await
|
|
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
|
|
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
|
|
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
|
|
33316
|
+
const { members } = await client3.getWorkspaceMembers(workspaceId);
|
|
32031
33317
|
const user = members.find((m) => m.email === userEmail);
|
|
32032
33318
|
if (user) {
|
|
32033
|
-
await
|
|
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
|
|
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
|
|
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
|
|
32073
|
-
|
|
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
|
-
|
|
32078
|
-
|
|
32079
|
-
|
|
32080
|
-
|
|
32081
|
-
|
|
32082
|
-
|
|
32083
|
-
|
|
32084
|
-
|
|
32085
|
-
|
|
32086
|
-
|
|
32087
|
-
|
|
32088
|
-
|
|
32089
|
-
|
|
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
|
-
}
|
|
32093
|
-
}
|
|
32094
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
32150
|
-
|
|
32151
|
-
|
|
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 (
|
|
32154
|
-
|
|
32155
|
-
|
|
32156
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
34035
|
+
existsSync as existsSync5,
|
|
32350
34036
|
lstatSync,
|
|
32351
|
-
mkdirSync as
|
|
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
|
|
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
|
|
34756
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
33071
34757
|
import { homedir as homedir2 } from "node:os";
|
|
33072
|
-
import { join as
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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) =>
|
|
33110
|
-
const localPath = def.localPaths.find((p2) =>
|
|
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 ?
|
|
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
|
|
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 (!
|
|
33193
|
-
|
|
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 =
|
|
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(
|
|
34888
|
+
ensureDir(dirname2(filePath));
|
|
33203
34889
|
const mode = filePath.includes(".harmony-mcp") ? 384 : 420;
|
|
33204
|
-
|
|
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 =
|
|
34901
|
+
const exists = existsSync4(filePath);
|
|
33216
34902
|
if (!exists) {
|
|
33217
34903
|
try {
|
|
33218
|
-
ensureDir(
|
|
33219
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
34951
|
+
const exists = existsSync4(filePath);
|
|
33266
34952
|
if (!exists) {
|
|
33267
34953
|
try {
|
|
33268
|
-
ensureDir(
|
|
33269
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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:
|
|
33582
|
-
const settingsPath =
|
|
33583
|
-
const settingsDir =
|
|
33584
|
-
if (!
|
|
33585
|
-
|
|
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 (
|
|
35274
|
+
if (existsSync6(settingsPath)) {
|
|
33589
35275
|
try {
|
|
33590
|
-
settings = JSON.parse(
|
|
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
|
-
|
|
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:
|
|
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:
|
|
33694
|
-
link:
|
|
35379
|
+
target: join4(GLOBAL_SKILLS_DIR, "hmy"),
|
|
35380
|
+
link: join4(home, ".claude", "skills", "hmy")
|
|
33695
35381
|
});
|
|
33696
35382
|
files.push({
|
|
33697
|
-
path:
|
|
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:
|
|
33703
|
-
link:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
33770
|
-
link:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
33827
|
-
link:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
33872
|
-
link:
|
|
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:
|
|
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 =
|
|
34152
|
-
if (!
|
|
34153
|
-
|
|
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(
|
|
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
|
}
|