harmony-mcp 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +272 -202
  2. package/dist/index.js +265 -195
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -14371,6 +14371,187 @@ function getMemoryDir() {
14371
14371
  return config.memoryDir;
14372
14372
  return join(homedir(), ".harmony", "memory");
14373
14373
  }
14374
+ // ../memory/src/graph-walk.ts
14375
+ async function discoverRelatedContext(client, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
14376
+ const visited = new Set;
14377
+ const collectedEntities = [];
14378
+ const collectedRelations = [];
14379
+ let truncated = false;
14380
+ const queue = startIds.map((id) => [id, 0]);
14381
+ for (const id of startIds) {
14382
+ visited.add(id);
14383
+ }
14384
+ while (queue.length > 0) {
14385
+ const [entityId, depth] = queue.shift();
14386
+ if (collectedEntities.length >= maxEntities) {
14387
+ truncated = true;
14388
+ break;
14389
+ }
14390
+ if (depth > maxDepth)
14391
+ continue;
14392
+ try {
14393
+ const entityResult = await client.getMemoryEntity(entityId);
14394
+ const entity = entityResult.entity;
14395
+ if (entity) {
14396
+ collectedEntities.push({
14397
+ id: entity.id,
14398
+ type: entity.type,
14399
+ title: entity.title,
14400
+ confidence: entity.confidence ?? 1,
14401
+ memory_tier: entity.memory_tier || "reference"
14402
+ });
14403
+ }
14404
+ if (depth >= maxDepth)
14405
+ continue;
14406
+ const related = await client.getRelatedEntities(entityId);
14407
+ for (const raw of related.outgoing || []) {
14408
+ const rel = raw;
14409
+ const relConfidence = rel.confidence ?? 1;
14410
+ if (relConfidence < minConfidence)
14411
+ continue;
14412
+ const target = rel.target;
14413
+ const targetId = target?.id ?? rel.target_id;
14414
+ if (targetId && !visited.has(targetId)) {
14415
+ visited.add(targetId);
14416
+ queue.push([targetId, depth + 1]);
14417
+ collectedRelations.push({
14418
+ id: rel.id,
14419
+ source_id: entityId,
14420
+ target_id: targetId,
14421
+ relation_type: rel.relation_type,
14422
+ confidence: relConfidence
14423
+ });
14424
+ }
14425
+ }
14426
+ for (const raw of related.incoming || []) {
14427
+ const rel = raw;
14428
+ const relConfidence = rel.confidence ?? 1;
14429
+ if (relConfidence < minConfidence)
14430
+ continue;
14431
+ const source = rel.source;
14432
+ const sourceId = source?.id ?? rel.source_id;
14433
+ if (sourceId && !visited.has(sourceId)) {
14434
+ visited.add(sourceId);
14435
+ queue.push([sourceId, depth + 1]);
14436
+ collectedRelations.push({
14437
+ id: rel.id,
14438
+ source_id: sourceId,
14439
+ target_id: entityId,
14440
+ relation_type: rel.relation_type,
14441
+ confidence: relConfidence
14442
+ });
14443
+ }
14444
+ }
14445
+ } catch {}
14446
+ }
14447
+ return {
14448
+ entities: collectedEntities,
14449
+ relations: collectedRelations,
14450
+ depth: maxDepth,
14451
+ truncated
14452
+ };
14453
+ }
14454
+ // ../memory/src/lifecycle.ts
14455
+ var DECAY_HALF_LIVES = {
14456
+ draft: 7,
14457
+ episode: 30,
14458
+ reference: 180
14459
+ };
14460
+ var PROMOTION_RULES = {
14461
+ draftToEpisode: {
14462
+ minAccessCount: 5,
14463
+ minConfidence: 0.8,
14464
+ minAgeDays: 1
14465
+ },
14466
+ episodeToReference: {
14467
+ minAccessCount: 10,
14468
+ minConfidence: 0.9,
14469
+ minAgeDays: 7
14470
+ }
14471
+ };
14472
+ var ARCHIVE_THRESHOLD = 0.3;
14473
+ var STALE_DAYS = 90;
14474
+ var STALE_MIN_ACCESS = 3;
14475
+ function computeDecayScore(tier, lastAccessedAt, accessCount) {
14476
+ const halfLife = DECAY_HALF_LIVES[tier];
14477
+ const now = Date.now();
14478
+ let daysSinceAccess = 0;
14479
+ if (lastAccessedAt) {
14480
+ daysSinceAccess = (now - new Date(lastAccessedAt).getTime()) / (1000 * 60 * 60 * 24);
14481
+ }
14482
+ const timeDecay = 0.5 ** (daysSinceAccess / halfLife);
14483
+ const accessBonus = Math.log10(accessCount + 1) * 0.1;
14484
+ const score = Math.min(timeDecay + accessBonus, 1);
14485
+ return { score, daysSinceAccess, halfLife, accessBonus };
14486
+ }
14487
+ function checkPromotion(currentTier, accessCount, confidence, createdAt) {
14488
+ const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
14489
+ const base = {
14490
+ eligible: false,
14491
+ targetTier: null,
14492
+ reason: null,
14493
+ currentTier,
14494
+ accessCount,
14495
+ confidence,
14496
+ ageDays
14497
+ };
14498
+ if (currentTier === "draft") {
14499
+ const rules = PROMOTION_RULES.draftToEpisode;
14500
+ if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
14501
+ return {
14502
+ ...base,
14503
+ eligible: true,
14504
+ targetTier: "episode",
14505
+ reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
14506
+ };
14507
+ }
14508
+ }
14509
+ if (currentTier === "episode") {
14510
+ const rules = PROMOTION_RULES.episodeToReference;
14511
+ if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
14512
+ return {
14513
+ ...base,
14514
+ eligible: true,
14515
+ targetTier: "reference",
14516
+ reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
14517
+ };
14518
+ }
14519
+ }
14520
+ return base;
14521
+ }
14522
+ function evaluateLifecycle(entity) {
14523
+ const decay = computeDecayScore(entity.memory_tier, entity.last_accessed_at, entity.access_count);
14524
+ const promotion = checkPromotion(entity.memory_tier, entity.access_count, entity.confidence, entity.created_at);
14525
+ const shouldArchive = entity.confidence < ARCHIVE_THRESHOLD;
14526
+ const archiveReason = shouldArchive ? `Confidence ${entity.confidence} below threshold ${ARCHIVE_THRESHOLD}` : undefined;
14527
+ const shouldFlagForReview = decay.daysSinceAccess >= STALE_DAYS && entity.access_count < STALE_MIN_ACCESS;
14528
+ const reviewReason = shouldFlagForReview ? `Not accessed in ${Math.round(decay.daysSinceAccess)} days with only ${entity.access_count} accesses` : undefined;
14529
+ return {
14530
+ decay,
14531
+ promotion,
14532
+ shouldArchive,
14533
+ shouldFlagForReview,
14534
+ archiveReason,
14535
+ reviewReason
14536
+ };
14537
+ }
14538
+ function buildContradictionQuery(type, tags) {
14539
+ if (tags.length === 0)
14540
+ return null;
14541
+ return { type, tags };
14542
+ }
14543
+ // ../memory/src/sync.ts
14544
+ import { createHash } from "node:crypto";
14545
+ import {
14546
+ existsSync as existsSync2,
14547
+ mkdirSync as mkdirSync2,
14548
+ readdirSync,
14549
+ readFileSync as readFileSync2,
14550
+ rmSync,
14551
+ writeFileSync as writeFileSync2
14552
+ } from "node:fs";
14553
+ import { join as join2, relative, sep } from "node:path";
14554
+
14374
14555
  // ../memory/src/sync-storage.ts
14375
14556
  function parseSyncMarkdown(markdown) {
14376
14557
  const trimmed = markdown.trim();
@@ -14486,17 +14667,8 @@ function parseYamlArray(value) {
14486
14667
  return [];
14487
14668
  return match[1].split(",").map((s) => s.trim()).filter(Boolean);
14488
14669
  }
14670
+
14489
14671
  // ../memory/src/sync.ts
14490
- import { createHash } from "node:crypto";
14491
- import {
14492
- existsSync as existsSync2,
14493
- mkdirSync as mkdirSync2,
14494
- readdirSync,
14495
- readFileSync as readFileSync2,
14496
- rmSync,
14497
- writeFileSync as writeFileSync2
14498
- } from "node:fs";
14499
- import { join as join2, relative, sep } from "node:path";
14500
14672
  function computeFileHash(content) {
14501
14673
  return `sha256:${createHash("sha256").update(content).digest("hex")}`;
14502
14674
  }
@@ -14758,177 +14930,6 @@ async function syncFull(client, config, workspaceId, projectId) {
14758
14930
  errors: [...pullResult.errors, ...pushResult.errors]
14759
14931
  };
14760
14932
  }
14761
- // ../memory/src/lifecycle.ts
14762
- var DECAY_HALF_LIVES = {
14763
- draft: 7,
14764
- episode: 30,
14765
- reference: 180
14766
- };
14767
- var PROMOTION_RULES = {
14768
- draftToEpisode: {
14769
- minAccessCount: 5,
14770
- minConfidence: 0.8,
14771
- minAgeDays: 1
14772
- },
14773
- episodeToReference: {
14774
- minAccessCount: 10,
14775
- minConfidence: 0.9,
14776
- minAgeDays: 7
14777
- }
14778
- };
14779
- var ARCHIVE_THRESHOLD = 0.3;
14780
- var STALE_DAYS = 90;
14781
- var STALE_MIN_ACCESS = 3;
14782
- function computeDecayScore(tier, lastAccessedAt, accessCount) {
14783
- const halfLife = DECAY_HALF_LIVES[tier];
14784
- const now = Date.now();
14785
- let daysSinceAccess = 0;
14786
- if (lastAccessedAt) {
14787
- daysSinceAccess = (now - new Date(lastAccessedAt).getTime()) / (1000 * 60 * 60 * 24);
14788
- }
14789
- const timeDecay = Math.pow(0.5, daysSinceAccess / halfLife);
14790
- const accessBonus = Math.log10(accessCount + 1) * 0.1;
14791
- const score = Math.min(timeDecay + accessBonus, 1);
14792
- return { score, daysSinceAccess, halfLife, accessBonus };
14793
- }
14794
- function checkPromotion(currentTier, accessCount, confidence, createdAt) {
14795
- const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
14796
- const base = {
14797
- eligible: false,
14798
- targetTier: null,
14799
- reason: null,
14800
- currentTier,
14801
- accessCount,
14802
- confidence,
14803
- ageDays
14804
- };
14805
- if (currentTier === "draft") {
14806
- const rules = PROMOTION_RULES.draftToEpisode;
14807
- if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
14808
- return {
14809
- ...base,
14810
- eligible: true,
14811
- targetTier: "episode",
14812
- reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
14813
- };
14814
- }
14815
- }
14816
- if (currentTier === "episode") {
14817
- const rules = PROMOTION_RULES.episodeToReference;
14818
- if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
14819
- return {
14820
- ...base,
14821
- eligible: true,
14822
- targetTier: "reference",
14823
- reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
14824
- };
14825
- }
14826
- }
14827
- return base;
14828
- }
14829
- function evaluateLifecycle(entity) {
14830
- const decay = computeDecayScore(entity.memory_tier, entity.last_accessed_at, entity.access_count);
14831
- const promotion = checkPromotion(entity.memory_tier, entity.access_count, entity.confidence, entity.created_at);
14832
- const shouldArchive = entity.confidence < ARCHIVE_THRESHOLD;
14833
- const archiveReason = shouldArchive ? `Confidence ${entity.confidence} below threshold ${ARCHIVE_THRESHOLD}` : undefined;
14834
- const shouldFlagForReview = decay.daysSinceAccess >= STALE_DAYS && entity.access_count < STALE_MIN_ACCESS;
14835
- const reviewReason = shouldFlagForReview ? `Not accessed in ${Math.round(decay.daysSinceAccess)} days with only ${entity.access_count} accesses` : undefined;
14836
- return {
14837
- decay,
14838
- promotion,
14839
- shouldArchive,
14840
- shouldFlagForReview,
14841
- archiveReason,
14842
- reviewReason
14843
- };
14844
- }
14845
- function buildContradictionQuery(type, tags) {
14846
- if (tags.length === 0)
14847
- return null;
14848
- return { type, tags };
14849
- }
14850
- // ../memory/src/graph-walk.ts
14851
- async function discoverRelatedContext(client, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
14852
- const visited = new Set;
14853
- const collectedEntities = [];
14854
- const collectedRelations = [];
14855
- let truncated = false;
14856
- const queue = startIds.map((id) => [id, 0]);
14857
- for (const id of startIds) {
14858
- visited.add(id);
14859
- }
14860
- while (queue.length > 0) {
14861
- const [entityId, depth] = queue.shift();
14862
- if (collectedEntities.length >= maxEntities) {
14863
- truncated = true;
14864
- break;
14865
- }
14866
- if (depth > maxDepth)
14867
- continue;
14868
- try {
14869
- const entityResult = await client.getMemoryEntity(entityId);
14870
- const entity = entityResult.entity;
14871
- if (entity) {
14872
- collectedEntities.push({
14873
- id: entity.id,
14874
- type: entity.type,
14875
- title: entity.title,
14876
- confidence: entity.confidence ?? 1,
14877
- memory_tier: entity.memory_tier || "reference"
14878
- });
14879
- }
14880
- if (depth >= maxDepth)
14881
- continue;
14882
- const related = await client.getRelatedEntities(entityId);
14883
- for (const raw of related.outgoing || []) {
14884
- const rel = raw;
14885
- const relConfidence = rel.confidence ?? 1;
14886
- if (relConfidence < minConfidence)
14887
- continue;
14888
- const target = rel.target;
14889
- const targetId = target?.id ?? rel.target_id;
14890
- if (targetId && !visited.has(targetId)) {
14891
- visited.add(targetId);
14892
- queue.push([targetId, depth + 1]);
14893
- collectedRelations.push({
14894
- id: rel.id,
14895
- source_id: entityId,
14896
- target_id: targetId,
14897
- relation_type: rel.relation_type,
14898
- confidence: relConfidence
14899
- });
14900
- }
14901
- }
14902
- for (const raw of related.incoming || []) {
14903
- const rel = raw;
14904
- const relConfidence = rel.confidence ?? 1;
14905
- if (relConfidence < minConfidence)
14906
- continue;
14907
- const source = rel.source;
14908
- const sourceId = source?.id ?? rel.source_id;
14909
- if (sourceId && !visited.has(sourceId)) {
14910
- visited.add(sourceId);
14911
- queue.push([sourceId, depth + 1]);
14912
- collectedRelations.push({
14913
- id: rel.id,
14914
- source_id: sourceId,
14915
- target_id: entityId,
14916
- relation_type: rel.relation_type,
14917
- confidence: relConfidence
14918
- });
14919
- }
14920
- }
14921
- } catch {
14922
- continue;
14923
- }
14924
- }
14925
- return {
14926
- entities: collectedEntities,
14927
- relations: collectedRelations,
14928
- depth: maxDepth,
14929
- truncated
14930
- };
14931
- }
14932
14933
  // ../../node_modules/zod/v4/core/index.js
14933
14934
  var exports_core2 = {};
14934
14935
  __export(exports_core2, {
@@ -30731,7 +30732,7 @@ class StdioServerTransport {
30731
30732
  }
30732
30733
  }
30733
30734
  // src/graph-expansion.ts
30734
- async function autoExpandGraph(client2, entityId, title, content, tags, workspaceId, projectId, maxRelations = 5) {
30735
+ async function autoExpandGraph(client2, entityId, title, content, _tags, workspaceId, projectId, maxRelations = 5) {
30735
30736
  try {
30736
30737
  const contentSnippet = content.slice(0, 200).trim();
30737
30738
  const query = [title, contentSnippet].filter(Boolean).join(" ");
@@ -30809,7 +30810,11 @@ Agent: ${session.agentName}`
30809
30810
  type: "lesson",
30810
30811
  tier: "episode",
30811
30812
  confidence: 0.7,
30812
- tags: ["auto-extracted", "session-summary", ...session.cardLabels.slice(0, 3)],
30813
+ tags: [
30814
+ "auto-extracted",
30815
+ "session-summary",
30816
+ ...session.cardLabels.slice(0, 3)
30817
+ ],
30813
30818
  metadata: {
30814
30819
  source: "active_learning",
30815
30820
  card_id: session.cardId,
@@ -31003,7 +31008,7 @@ function isRetryableError(error48, status) {
31003
31008
  return msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed");
31004
31009
  }
31005
31010
  function getRetryDelay(attempt) {
31006
- const delay = Math.min(RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt), RETRY_CONFIG.maxDelayMs);
31011
+ const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
31007
31012
  return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
31008
31013
  }
31009
31014
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -31122,7 +31127,6 @@ class HarmonyApiClient {
31122
31127
  throw lastError;
31123
31128
  if (attempt < RETRY_CONFIG.maxRetries) {
31124
31129
  await sleep(getRetryDelay(attempt));
31125
- continue;
31126
31130
  }
31127
31131
  }
31128
31132
  }
@@ -31169,7 +31173,6 @@ class HarmonyApiClient {
31169
31173
  throw lastError;
31170
31174
  if (attempt < RETRY_CONFIG.maxRetries) {
31171
31175
  await sleep(getRetryDelay(attempt));
31172
- continue;
31173
31176
  }
31174
31177
  }
31175
31178
  }
@@ -31194,6 +31197,8 @@ class HarmonyApiClient {
31194
31197
  params.set("column_id", options.columnId);
31195
31198
  if (options?.summary)
31196
31199
  params.set("summary", "true");
31200
+ if (options?.includeArchived)
31201
+ params.set("include_archived", "true");
31197
31202
  const query = params.toString() ? `?${params.toString()}` : "";
31198
31203
  return this.request("GET", `/board/${projectId}${query}`);
31199
31204
  }
@@ -31209,6 +31214,12 @@ class HarmonyApiClient {
31209
31214
  position
31210
31215
  });
31211
31216
  }
31217
+ async archiveCard(cardId) {
31218
+ return this.updateCard(cardId, { archivedAt: new Date().toISOString() });
31219
+ }
31220
+ async unarchiveCard(cardId) {
31221
+ return this.updateCard(cardId, { archivedAt: null });
31222
+ }
31212
31223
  async deleteCard(cardId) {
31213
31224
  return this.request("DELETE", `/cards/${cardId}`);
31214
31225
  }
@@ -31567,7 +31578,7 @@ function computeRelevanceScore(entity, taskContext, cardLabels) {
31567
31578
  if (entity.last_accessed_at) {
31568
31579
  const daysSinceAccess = (Date.now() - new Date(entity.last_accessed_at).getTime()) / (1000 * 60 * 60 * 24);
31569
31580
  const halfLife = { draft: 7, episode: 30, reference: 180 }[entity.memory_tier];
31570
- const recencyScore = Math.pow(0.5, daysSinceAccess / halfLife) * 0.1;
31581
+ const recencyScore = 0.5 ** (daysSinceAccess / halfLife) * 0.1;
31571
31582
  score += recencyScore;
31572
31583
  if (daysSinceAccess < 7)
31573
31584
  reasons.push("recently_accessed");
@@ -31644,7 +31655,11 @@ async function assembleContext(options) {
31644
31655
  episode: Math.floor(tokenBudget * TIER_BUDGET_ALLOCATION.episode),
31645
31656
  draft: Math.floor(tokenBudget * TIER_BUDGET_ALLOCATION.draft)
31646
31657
  };
31647
- const tierUsed = { reference: 0, episode: 0, draft: 0 };
31658
+ const tierUsed = {
31659
+ reference: 0,
31660
+ episode: 0,
31661
+ draft: 0
31662
+ };
31648
31663
  const included = [];
31649
31664
  let totalUsed = 0;
31650
31665
  let referenceCount = 0;
@@ -31714,9 +31729,18 @@ ${text}`);
31714
31729
  }
31715
31730
  manifest.budgetUsed = totalUsed;
31716
31731
  manifest.tierBreakdown = {
31717
- reference: { count: included.filter((i) => i.entity.memory_tier === "reference").length, tokens: tierUsed.reference },
31718
- episode: { count: included.filter((i) => i.entity.memory_tier === "episode").length, tokens: tierUsed.episode },
31719
- draft: { count: included.filter((i) => i.entity.memory_tier === "draft").length, tokens: tierUsed.draft }
31732
+ reference: {
31733
+ count: included.filter((i) => i.entity.memory_tier === "reference").length,
31734
+ tokens: tierUsed.reference
31735
+ },
31736
+ episode: {
31737
+ count: included.filter((i) => i.entity.memory_tier === "episode").length,
31738
+ tokens: tierUsed.episode
31739
+ },
31740
+ draft: {
31741
+ count: included.filter((i) => i.entity.memory_tier === "draft").length,
31742
+ tokens: tierUsed.draft
31743
+ }
31720
31744
  };
31721
31745
  for (const item of included) {
31722
31746
  manifest.included.push({
@@ -32008,7 +32032,15 @@ ${lines.join(`
32008
32032
  `)}`;
32009
32033
  }
32010
32034
  function generatePrompt(options) {
32011
- const { card, column, variant, customConstraints, memories, assembledContext, assemblyId } = options;
32035
+ const {
32036
+ card,
32037
+ column,
32038
+ variant,
32039
+ customConstraints,
32040
+ memories,
32041
+ assembledContext,
32042
+ assemblyId
32043
+ } = options;
32012
32044
  const contextOpts = {
32013
32045
  includeTitle: true,
32014
32046
  includeDescription: true,
@@ -32162,7 +32194,8 @@ var TOOLS = {
32162
32194
  description: { type: "string" },
32163
32195
  priority: { type: "string", enum: ["low", "medium", "high", "urgent"] },
32164
32196
  assigneeId: { type: "string", nullable: true },
32165
- dueDate: { type: "string", nullable: true }
32197
+ dueDate: { type: "string", nullable: true },
32198
+ done: { type: "boolean", description: "Mark card as done or not done" }
32166
32199
  },
32167
32200
  required: ["cardId"]
32168
32201
  }
@@ -32182,6 +32215,26 @@ var TOOLS = {
32182
32215
  required: ["cardId", "columnId"]
32183
32216
  }
32184
32217
  },
32218
+ harmony_archive_card: {
32219
+ description: "Archive a card (soft-delete). Card can be restored later with unarchive.",
32220
+ inputSchema: {
32221
+ type: "object",
32222
+ properties: {
32223
+ cardId: { type: "string", description: "Card ID to archive" }
32224
+ },
32225
+ required: ["cardId"]
32226
+ }
32227
+ },
32228
+ harmony_unarchive_card: {
32229
+ description: "Restore an archived card back to the board.",
32230
+ inputSchema: {
32231
+ type: "object",
32232
+ properties: {
32233
+ cardId: { type: "string", description: "Card ID to unarchive" }
32234
+ },
32235
+ required: ["cardId"]
32236
+ }
32237
+ },
32185
32238
  harmony_delete_card: {
32186
32239
  description: "Delete a card",
32187
32240
  inputSchema: {
@@ -32424,6 +32477,10 @@ var TOOLS = {
32424
32477
  summary: {
32425
32478
  type: "boolean",
32426
32479
  description: "Return only columns with card counts, no card details"
32480
+ },
32481
+ includeArchived: {
32482
+ type: "boolean",
32483
+ description: "Include archived cards in results (default: false). Archived cards are excluded by default."
32427
32484
  }
32428
32485
  }
32429
32486
  }
@@ -33482,7 +33539,8 @@ async function handleToolCall(name, args, deps) {
33482
33539
  description: args.description,
33483
33540
  priority: args.priority,
33484
33541
  assigneeId: args.assigneeId,
33485
- dueDate: args.dueDate
33542
+ dueDate: args.dueDate,
33543
+ done: args.done
33486
33544
  });
33487
33545
  return { success: true, ...result };
33488
33546
  }
@@ -33512,6 +33570,16 @@ async function handleToolCall(name, args, deps) {
33512
33570
  } catch {}
33513
33571
  return { success: true, sessionEnded, ...result };
33514
33572
  }
33573
+ case "harmony_archive_card": {
33574
+ const cardId = exports_external.string().uuid().parse(args.cardId);
33575
+ const result = await client3.archiveCard(cardId);
33576
+ return { success: true, ...result };
33577
+ }
33578
+ case "harmony_unarchive_card": {
33579
+ const cardId = exports_external.string().uuid().parse(args.cardId);
33580
+ const result = await client3.unarchiveCard(cardId);
33581
+ return { success: true, ...result };
33582
+ }
33515
33583
  case "harmony_delete_card": {
33516
33584
  const cardId = exports_external.string().uuid().parse(args.cardId);
33517
33585
  await client3.deleteCard(cardId);
@@ -33635,6 +33703,8 @@ async function handleToolCall(name, args, deps) {
33635
33703
  options.columnId = String(args.columnId);
33636
33704
  if (args.summary === true || args.summary === "true")
33637
33705
  options.summary = true;
33706
+ if (args.includeArchived === true || args.includeArchived === "true")
33707
+ options.includeArchived = true;
33638
33708
  const result = await client3.getBoard(projectId, options);
33639
33709
  return { success: true, board: result };
33640
33710
  }
@@ -34299,7 +34369,7 @@ async function handleToolCall(name, args, deps) {
34299
34369
  const card = cardResult.card;
34300
34370
  await client3.updatePlanTask(planId, task.id, { cardId: card.id });
34301
34371
  cardsCreated++;
34302
- } catch (e) {}
34372
+ } catch (_e) {}
34303
34373
  }
34304
34374
  results.cardsCreated = cardsCreated;
34305
34375
  }
@@ -34783,7 +34853,7 @@ import {
34783
34853
  unlinkSync
34784
34854
  } from "node:fs";
34785
34855
  import { homedir as homedir4 } from "node:os";
34786
- import { dirname as dirname3, join as join4 } from "node:path";
34856
+ import { dirname as dirname2, join as join4 } from "node:path";
34787
34857
 
34788
34858
  // ../../node_modules/@clack/core/dist/index.mjs
34789
34859
  var import_sisteransi = __toESM(require_src(), 1);
@@ -35617,7 +35687,7 @@ function formatPath(path, homeDir) {
35617
35687
  // src/tui/writer.ts
35618
35688
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
35619
35689
  import { homedir as homedir3 } from "node:os";
35620
- import { dirname as dirname2 } from "node:path";
35690
+ import { dirname } from "node:path";
35621
35691
  function ensureDir(dirPath) {
35622
35692
  if (!existsSync4(dirPath)) {
35623
35693
  mkdirSync3(dirPath, { recursive: true, mode: 493 });
@@ -35629,7 +35699,7 @@ function writeFile(filePath, content, options = {}) {
35629
35699
  return { path: filePath, action: "skip" };
35630
35700
  }
35631
35701
  try {
35632
- ensureDir(dirname2(filePath));
35702
+ ensureDir(dirname(filePath));
35633
35703
  const mode = filePath.includes(".harmony-mcp") ? 384 : 420;
35634
35704
  writeFileSync3(filePath, content, { mode });
35635
35705
  return { path: filePath, action: exists ? "update" : "create" };
@@ -35645,7 +35715,7 @@ function mergeJsonFile(filePath, updates, options = {}) {
35645
35715
  const exists = existsSync4(filePath);
35646
35716
  if (!exists) {
35647
35717
  try {
35648
- ensureDir(dirname2(filePath));
35718
+ ensureDir(dirname(filePath));
35649
35719
  writeFileSync3(filePath, JSON.stringify(updates, null, 2), {
35650
35720
  mode: 420
35651
35721
  });
@@ -35695,7 +35765,7 @@ function appendToToml(filePath, section, content, options = {}) {
35695
35765
  const exists = existsSync4(filePath);
35696
35766
  if (!exists) {
35697
35767
  try {
35698
- ensureDir(dirname2(filePath));
35768
+ ensureDir(dirname(filePath));
35699
35769
  writeFileSync3(filePath, content, { mode: 420 });
35700
35770
  return { path: filePath, action: "create" };
35701
35771
  } catch (error48) {
@@ -36010,7 +36080,7 @@ async function registerMcpServer() {
36010
36080
  async function writeMcpConfigFallback(home) {
36011
36081
  const { readFileSync: readFileSync4, writeFileSync: writeFileSync4, mkdirSync: mkdirSync5, existsSync: existsSync6 } = await import("node:fs");
36012
36082
  const settingsPath = join4(home, ".claude", "settings.json");
36013
- const settingsDir = dirname3(settingsPath);
36083
+ const settingsDir = dirname2(settingsPath);
36014
36084
  if (!existsSync6(settingsDir)) {
36015
36085
  mkdirSync5(settingsDir, { recursive: true });
36016
36086
  }
@@ -36578,7 +36648,7 @@ async function runSetup(options = {}) {
36578
36648
  if (allSymlinks.length > 0) {
36579
36649
  for (const symlink of allSymlinks) {
36580
36650
  try {
36581
- const linkDir = dirname3(symlink.link);
36651
+ const linkDir = dirname2(symlink.link);
36582
36652
  if (!existsSync5(linkDir)) {
36583
36653
  mkdirSync4(linkDir, { recursive: true });
36584
36654
  }
package/dist/index.js CHANGED
@@ -11946,6 +11946,187 @@ var require_dist = __commonJS((exports, module) => {
11946
11946
  Object.defineProperty(exports, "__esModule", { value: true });
11947
11947
  exports.default = formatsPlugin;
11948
11948
  });
11949
+ // ../memory/src/graph-walk.ts
11950
+ async function discoverRelatedContext(client, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
11951
+ const visited = new Set;
11952
+ const collectedEntities = [];
11953
+ const collectedRelations = [];
11954
+ let truncated = false;
11955
+ const queue = startIds.map((id) => [id, 0]);
11956
+ for (const id of startIds) {
11957
+ visited.add(id);
11958
+ }
11959
+ while (queue.length > 0) {
11960
+ const [entityId, depth] = queue.shift();
11961
+ if (collectedEntities.length >= maxEntities) {
11962
+ truncated = true;
11963
+ break;
11964
+ }
11965
+ if (depth > maxDepth)
11966
+ continue;
11967
+ try {
11968
+ const entityResult = await client.getMemoryEntity(entityId);
11969
+ const entity = entityResult.entity;
11970
+ if (entity) {
11971
+ collectedEntities.push({
11972
+ id: entity.id,
11973
+ type: entity.type,
11974
+ title: entity.title,
11975
+ confidence: entity.confidence ?? 1,
11976
+ memory_tier: entity.memory_tier || "reference"
11977
+ });
11978
+ }
11979
+ if (depth >= maxDepth)
11980
+ continue;
11981
+ const related = await client.getRelatedEntities(entityId);
11982
+ for (const raw of related.outgoing || []) {
11983
+ const rel = raw;
11984
+ const relConfidence = rel.confidence ?? 1;
11985
+ if (relConfidence < minConfidence)
11986
+ continue;
11987
+ const target = rel.target;
11988
+ const targetId = target?.id ?? rel.target_id;
11989
+ if (targetId && !visited.has(targetId)) {
11990
+ visited.add(targetId);
11991
+ queue.push([targetId, depth + 1]);
11992
+ collectedRelations.push({
11993
+ id: rel.id,
11994
+ source_id: entityId,
11995
+ target_id: targetId,
11996
+ relation_type: rel.relation_type,
11997
+ confidence: relConfidence
11998
+ });
11999
+ }
12000
+ }
12001
+ for (const raw of related.incoming || []) {
12002
+ const rel = raw;
12003
+ const relConfidence = rel.confidence ?? 1;
12004
+ if (relConfidence < minConfidence)
12005
+ continue;
12006
+ const source = rel.source;
12007
+ const sourceId = source?.id ?? rel.source_id;
12008
+ if (sourceId && !visited.has(sourceId)) {
12009
+ visited.add(sourceId);
12010
+ queue.push([sourceId, depth + 1]);
12011
+ collectedRelations.push({
12012
+ id: rel.id,
12013
+ source_id: sourceId,
12014
+ target_id: entityId,
12015
+ relation_type: rel.relation_type,
12016
+ confidence: relConfidence
12017
+ });
12018
+ }
12019
+ }
12020
+ } catch {}
12021
+ }
12022
+ return {
12023
+ entities: collectedEntities,
12024
+ relations: collectedRelations,
12025
+ depth: maxDepth,
12026
+ truncated
12027
+ };
12028
+ }
12029
+ // ../memory/src/lifecycle.ts
12030
+ var DECAY_HALF_LIVES = {
12031
+ draft: 7,
12032
+ episode: 30,
12033
+ reference: 180
12034
+ };
12035
+ var PROMOTION_RULES = {
12036
+ draftToEpisode: {
12037
+ minAccessCount: 5,
12038
+ minConfidence: 0.8,
12039
+ minAgeDays: 1
12040
+ },
12041
+ episodeToReference: {
12042
+ minAccessCount: 10,
12043
+ minConfidence: 0.9,
12044
+ minAgeDays: 7
12045
+ }
12046
+ };
12047
+ var ARCHIVE_THRESHOLD = 0.3;
12048
+ var STALE_DAYS = 90;
12049
+ var STALE_MIN_ACCESS = 3;
12050
+ function computeDecayScore(tier, lastAccessedAt, accessCount) {
12051
+ const halfLife = DECAY_HALF_LIVES[tier];
12052
+ const now = Date.now();
12053
+ let daysSinceAccess = 0;
12054
+ if (lastAccessedAt) {
12055
+ daysSinceAccess = (now - new Date(lastAccessedAt).getTime()) / (1000 * 60 * 60 * 24);
12056
+ }
12057
+ const timeDecay = 0.5 ** (daysSinceAccess / halfLife);
12058
+ const accessBonus = Math.log10(accessCount + 1) * 0.1;
12059
+ const score = Math.min(timeDecay + accessBonus, 1);
12060
+ return { score, daysSinceAccess, halfLife, accessBonus };
12061
+ }
12062
+ function checkPromotion(currentTier, accessCount, confidence, createdAt) {
12063
+ const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
12064
+ const base = {
12065
+ eligible: false,
12066
+ targetTier: null,
12067
+ reason: null,
12068
+ currentTier,
12069
+ accessCount,
12070
+ confidence,
12071
+ ageDays
12072
+ };
12073
+ if (currentTier === "draft") {
12074
+ const rules = PROMOTION_RULES.draftToEpisode;
12075
+ if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
12076
+ return {
12077
+ ...base,
12078
+ eligible: true,
12079
+ targetTier: "episode",
12080
+ reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
12081
+ };
12082
+ }
12083
+ }
12084
+ if (currentTier === "episode") {
12085
+ const rules = PROMOTION_RULES.episodeToReference;
12086
+ if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
12087
+ return {
12088
+ ...base,
12089
+ eligible: true,
12090
+ targetTier: "reference",
12091
+ reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
12092
+ };
12093
+ }
12094
+ }
12095
+ return base;
12096
+ }
12097
+ function evaluateLifecycle(entity) {
12098
+ const decay = computeDecayScore(entity.memory_tier, entity.last_accessed_at, entity.access_count);
12099
+ const promotion = checkPromotion(entity.memory_tier, entity.access_count, entity.confidence, entity.created_at);
12100
+ const shouldArchive = entity.confidence < ARCHIVE_THRESHOLD;
12101
+ const archiveReason = shouldArchive ? `Confidence ${entity.confidence} below threshold ${ARCHIVE_THRESHOLD}` : undefined;
12102
+ const shouldFlagForReview = decay.daysSinceAccess >= STALE_DAYS && entity.access_count < STALE_MIN_ACCESS;
12103
+ const reviewReason = shouldFlagForReview ? `Not accessed in ${Math.round(decay.daysSinceAccess)} days with only ${entity.access_count} accesses` : undefined;
12104
+ return {
12105
+ decay,
12106
+ promotion,
12107
+ shouldArchive,
12108
+ shouldFlagForReview,
12109
+ archiveReason,
12110
+ reviewReason
12111
+ };
12112
+ }
12113
+ function buildContradictionQuery(type, tags) {
12114
+ if (tags.length === 0)
12115
+ return null;
12116
+ return { type, tags };
12117
+ }
12118
+ // ../memory/src/sync.ts
12119
+ import { createHash } from "node:crypto";
12120
+ import {
12121
+ existsSync,
12122
+ mkdirSync,
12123
+ readdirSync,
12124
+ readFileSync,
12125
+ rmSync,
12126
+ writeFileSync
12127
+ } from "node:fs";
12128
+ import { join, relative, sep } from "node:path";
12129
+
11949
12130
  // ../memory/src/sync-storage.ts
11950
12131
  function parseSyncMarkdown(markdown) {
11951
12132
  const trimmed = markdown.trim();
@@ -12061,17 +12242,8 @@ function parseYamlArray(value) {
12061
12242
  return [];
12062
12243
  return match[1].split(",").map((s) => s.trim()).filter(Boolean);
12063
12244
  }
12245
+
12064
12246
  // ../memory/src/sync.ts
12065
- import { createHash } from "node:crypto";
12066
- import {
12067
- existsSync,
12068
- mkdirSync,
12069
- readdirSync,
12070
- readFileSync,
12071
- rmSync,
12072
- writeFileSync
12073
- } from "node:fs";
12074
- import { join, relative, sep } from "node:path";
12075
12247
  function computeFileHash(content) {
12076
12248
  return `sha256:${createHash("sha256").update(content).digest("hex")}`;
12077
12249
  }
@@ -12333,177 +12505,6 @@ async function syncFull(client, config, workspaceId, projectId) {
12333
12505
  errors: [...pullResult.errors, ...pushResult.errors]
12334
12506
  };
12335
12507
  }
12336
- // ../memory/src/lifecycle.ts
12337
- var DECAY_HALF_LIVES = {
12338
- draft: 7,
12339
- episode: 30,
12340
- reference: 180
12341
- };
12342
- var PROMOTION_RULES = {
12343
- draftToEpisode: {
12344
- minAccessCount: 5,
12345
- minConfidence: 0.8,
12346
- minAgeDays: 1
12347
- },
12348
- episodeToReference: {
12349
- minAccessCount: 10,
12350
- minConfidence: 0.9,
12351
- minAgeDays: 7
12352
- }
12353
- };
12354
- var ARCHIVE_THRESHOLD = 0.3;
12355
- var STALE_DAYS = 90;
12356
- var STALE_MIN_ACCESS = 3;
12357
- function computeDecayScore(tier, lastAccessedAt, accessCount) {
12358
- const halfLife = DECAY_HALF_LIVES[tier];
12359
- const now = Date.now();
12360
- let daysSinceAccess = 0;
12361
- if (lastAccessedAt) {
12362
- daysSinceAccess = (now - new Date(lastAccessedAt).getTime()) / (1000 * 60 * 60 * 24);
12363
- }
12364
- const timeDecay = Math.pow(0.5, daysSinceAccess / halfLife);
12365
- const accessBonus = Math.log10(accessCount + 1) * 0.1;
12366
- const score = Math.min(timeDecay + accessBonus, 1);
12367
- return { score, daysSinceAccess, halfLife, accessBonus };
12368
- }
12369
- function checkPromotion(currentTier, accessCount, confidence, createdAt) {
12370
- const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
12371
- const base = {
12372
- eligible: false,
12373
- targetTier: null,
12374
- reason: null,
12375
- currentTier,
12376
- accessCount,
12377
- confidence,
12378
- ageDays
12379
- };
12380
- if (currentTier === "draft") {
12381
- const rules = PROMOTION_RULES.draftToEpisode;
12382
- if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
12383
- return {
12384
- ...base,
12385
- eligible: true,
12386
- targetTier: "episode",
12387
- reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
12388
- };
12389
- }
12390
- }
12391
- if (currentTier === "episode") {
12392
- const rules = PROMOTION_RULES.episodeToReference;
12393
- if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
12394
- return {
12395
- ...base,
12396
- eligible: true,
12397
- targetTier: "reference",
12398
- reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
12399
- };
12400
- }
12401
- }
12402
- return base;
12403
- }
12404
- function evaluateLifecycle(entity) {
12405
- const decay = computeDecayScore(entity.memory_tier, entity.last_accessed_at, entity.access_count);
12406
- const promotion = checkPromotion(entity.memory_tier, entity.access_count, entity.confidence, entity.created_at);
12407
- const shouldArchive = entity.confidence < ARCHIVE_THRESHOLD;
12408
- const archiveReason = shouldArchive ? `Confidence ${entity.confidence} below threshold ${ARCHIVE_THRESHOLD}` : undefined;
12409
- const shouldFlagForReview = decay.daysSinceAccess >= STALE_DAYS && entity.access_count < STALE_MIN_ACCESS;
12410
- const reviewReason = shouldFlagForReview ? `Not accessed in ${Math.round(decay.daysSinceAccess)} days with only ${entity.access_count} accesses` : undefined;
12411
- return {
12412
- decay,
12413
- promotion,
12414
- shouldArchive,
12415
- shouldFlagForReview,
12416
- archiveReason,
12417
- reviewReason
12418
- };
12419
- }
12420
- function buildContradictionQuery(type, tags) {
12421
- if (tags.length === 0)
12422
- return null;
12423
- return { type, tags };
12424
- }
12425
- // ../memory/src/graph-walk.ts
12426
- async function discoverRelatedContext(client, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
12427
- const visited = new Set;
12428
- const collectedEntities = [];
12429
- const collectedRelations = [];
12430
- let truncated = false;
12431
- const queue = startIds.map((id) => [id, 0]);
12432
- for (const id of startIds) {
12433
- visited.add(id);
12434
- }
12435
- while (queue.length > 0) {
12436
- const [entityId, depth] = queue.shift();
12437
- if (collectedEntities.length >= maxEntities) {
12438
- truncated = true;
12439
- break;
12440
- }
12441
- if (depth > maxDepth)
12442
- continue;
12443
- try {
12444
- const entityResult = await client.getMemoryEntity(entityId);
12445
- const entity = entityResult.entity;
12446
- if (entity) {
12447
- collectedEntities.push({
12448
- id: entity.id,
12449
- type: entity.type,
12450
- title: entity.title,
12451
- confidence: entity.confidence ?? 1,
12452
- memory_tier: entity.memory_tier || "reference"
12453
- });
12454
- }
12455
- if (depth >= maxDepth)
12456
- continue;
12457
- const related = await client.getRelatedEntities(entityId);
12458
- for (const raw of related.outgoing || []) {
12459
- const rel = raw;
12460
- const relConfidence = rel.confidence ?? 1;
12461
- if (relConfidence < minConfidence)
12462
- continue;
12463
- const target = rel.target;
12464
- const targetId = target?.id ?? rel.target_id;
12465
- if (targetId && !visited.has(targetId)) {
12466
- visited.add(targetId);
12467
- queue.push([targetId, depth + 1]);
12468
- collectedRelations.push({
12469
- id: rel.id,
12470
- source_id: entityId,
12471
- target_id: targetId,
12472
- relation_type: rel.relation_type,
12473
- confidence: relConfidence
12474
- });
12475
- }
12476
- }
12477
- for (const raw of related.incoming || []) {
12478
- const rel = raw;
12479
- const relConfidence = rel.confidence ?? 1;
12480
- if (relConfidence < minConfidence)
12481
- continue;
12482
- const source = rel.source;
12483
- const sourceId = source?.id ?? rel.source_id;
12484
- if (sourceId && !visited.has(sourceId)) {
12485
- visited.add(sourceId);
12486
- queue.push([sourceId, depth + 1]);
12487
- collectedRelations.push({
12488
- id: rel.id,
12489
- source_id: sourceId,
12490
- target_id: entityId,
12491
- relation_type: rel.relation_type,
12492
- confidence: relConfidence
12493
- });
12494
- }
12495
- }
12496
- } catch {
12497
- continue;
12498
- }
12499
- }
12500
- return {
12501
- entities: collectedEntities,
12502
- relations: collectedRelations,
12503
- depth: maxDepth,
12504
- truncated
12505
- };
12506
- }
12507
12508
  // ../../node_modules/zod/v4/core/index.js
12508
12509
  var exports_core2 = {};
12509
12510
  __export(exports_core2, {
@@ -28491,7 +28492,7 @@ function getMemoryDir() {
28491
28492
  }
28492
28493
 
28493
28494
  // src/graph-expansion.ts
28494
- async function autoExpandGraph(client2, entityId, title, content, tags, workspaceId, projectId, maxRelations = 5) {
28495
+ async function autoExpandGraph(client2, entityId, title, content, _tags, workspaceId, projectId, maxRelations = 5) {
28495
28496
  try {
28496
28497
  const contentSnippet = content.slice(0, 200).trim();
28497
28498
  const query = [title, contentSnippet].filter(Boolean).join(" ");
@@ -28569,7 +28570,11 @@ Agent: ${session.agentName}`
28569
28570
  type: "lesson",
28570
28571
  tier: "episode",
28571
28572
  confidence: 0.7,
28572
- tags: ["auto-extracted", "session-summary", ...session.cardLabels.slice(0, 3)],
28573
+ tags: [
28574
+ "auto-extracted",
28575
+ "session-summary",
28576
+ ...session.cardLabels.slice(0, 3)
28577
+ ],
28573
28578
  metadata: {
28574
28579
  source: "active_learning",
28575
28580
  card_id: session.cardId,
@@ -28763,7 +28768,7 @@ function isRetryableError(error48, status) {
28763
28768
  return msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed");
28764
28769
  }
28765
28770
  function getRetryDelay(attempt) {
28766
- const delay = Math.min(RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt), RETRY_CONFIG.maxDelayMs);
28771
+ const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
28767
28772
  return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
28768
28773
  }
28769
28774
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -28882,7 +28887,6 @@ class HarmonyApiClient {
28882
28887
  throw lastError;
28883
28888
  if (attempt < RETRY_CONFIG.maxRetries) {
28884
28889
  await sleep(getRetryDelay(attempt));
28885
- continue;
28886
28890
  }
28887
28891
  }
28888
28892
  }
@@ -28929,7 +28933,6 @@ class HarmonyApiClient {
28929
28933
  throw lastError;
28930
28934
  if (attempt < RETRY_CONFIG.maxRetries) {
28931
28935
  await sleep(getRetryDelay(attempt));
28932
- continue;
28933
28936
  }
28934
28937
  }
28935
28938
  }
@@ -28954,6 +28957,8 @@ class HarmonyApiClient {
28954
28957
  params.set("column_id", options.columnId);
28955
28958
  if (options?.summary)
28956
28959
  params.set("summary", "true");
28960
+ if (options?.includeArchived)
28961
+ params.set("include_archived", "true");
28957
28962
  const query = params.toString() ? `?${params.toString()}` : "";
28958
28963
  return this.request("GET", `/board/${projectId}${query}`);
28959
28964
  }
@@ -28969,6 +28974,12 @@ class HarmonyApiClient {
28969
28974
  position
28970
28975
  });
28971
28976
  }
28977
+ async archiveCard(cardId) {
28978
+ return this.updateCard(cardId, { archivedAt: new Date().toISOString() });
28979
+ }
28980
+ async unarchiveCard(cardId) {
28981
+ return this.updateCard(cardId, { archivedAt: null });
28982
+ }
28972
28983
  async deleteCard(cardId) {
28973
28984
  return this.request("DELETE", `/cards/${cardId}`);
28974
28985
  }
@@ -29327,7 +29338,7 @@ function computeRelevanceScore(entity, taskContext, cardLabels) {
29327
29338
  if (entity.last_accessed_at) {
29328
29339
  const daysSinceAccess = (Date.now() - new Date(entity.last_accessed_at).getTime()) / (1000 * 60 * 60 * 24);
29329
29340
  const halfLife = { draft: 7, episode: 30, reference: 180 }[entity.memory_tier];
29330
- const recencyScore = Math.pow(0.5, daysSinceAccess / halfLife) * 0.1;
29341
+ const recencyScore = 0.5 ** (daysSinceAccess / halfLife) * 0.1;
29331
29342
  score += recencyScore;
29332
29343
  if (daysSinceAccess < 7)
29333
29344
  reasons.push("recently_accessed");
@@ -29404,7 +29415,11 @@ async function assembleContext(options) {
29404
29415
  episode: Math.floor(tokenBudget * TIER_BUDGET_ALLOCATION.episode),
29405
29416
  draft: Math.floor(tokenBudget * TIER_BUDGET_ALLOCATION.draft)
29406
29417
  };
29407
- const tierUsed = { reference: 0, episode: 0, draft: 0 };
29418
+ const tierUsed = {
29419
+ reference: 0,
29420
+ episode: 0,
29421
+ draft: 0
29422
+ };
29408
29423
  const included = [];
29409
29424
  let totalUsed = 0;
29410
29425
  let referenceCount = 0;
@@ -29474,9 +29489,18 @@ ${text}`);
29474
29489
  }
29475
29490
  manifest.budgetUsed = totalUsed;
29476
29491
  manifest.tierBreakdown = {
29477
- reference: { count: included.filter((i) => i.entity.memory_tier === "reference").length, tokens: tierUsed.reference },
29478
- episode: { count: included.filter((i) => i.entity.memory_tier === "episode").length, tokens: tierUsed.episode },
29479
- draft: { count: included.filter((i) => i.entity.memory_tier === "draft").length, tokens: tierUsed.draft }
29492
+ reference: {
29493
+ count: included.filter((i) => i.entity.memory_tier === "reference").length,
29494
+ tokens: tierUsed.reference
29495
+ },
29496
+ episode: {
29497
+ count: included.filter((i) => i.entity.memory_tier === "episode").length,
29498
+ tokens: tierUsed.episode
29499
+ },
29500
+ draft: {
29501
+ count: included.filter((i) => i.entity.memory_tier === "draft").length,
29502
+ tokens: tierUsed.draft
29503
+ }
29480
29504
  };
29481
29505
  for (const item of included) {
29482
29506
  manifest.included.push({
@@ -29768,7 +29792,15 @@ ${lines.join(`
29768
29792
  `)}`;
29769
29793
  }
29770
29794
  function generatePrompt(options) {
29771
- const { card, column, variant, customConstraints, memories, assembledContext, assemblyId } = options;
29795
+ const {
29796
+ card,
29797
+ column,
29798
+ variant,
29799
+ customConstraints,
29800
+ memories,
29801
+ assembledContext,
29802
+ assemblyId
29803
+ } = options;
29772
29804
  const contextOpts = {
29773
29805
  includeTitle: true,
29774
29806
  includeDescription: true,
@@ -29922,7 +29954,8 @@ var TOOLS = {
29922
29954
  description: { type: "string" },
29923
29955
  priority: { type: "string", enum: ["low", "medium", "high", "urgent"] },
29924
29956
  assigneeId: { type: "string", nullable: true },
29925
- dueDate: { type: "string", nullable: true }
29957
+ dueDate: { type: "string", nullable: true },
29958
+ done: { type: "boolean", description: "Mark card as done or not done" }
29926
29959
  },
29927
29960
  required: ["cardId"]
29928
29961
  }
@@ -29942,6 +29975,26 @@ var TOOLS = {
29942
29975
  required: ["cardId", "columnId"]
29943
29976
  }
29944
29977
  },
29978
+ harmony_archive_card: {
29979
+ description: "Archive a card (soft-delete). Card can be restored later with unarchive.",
29980
+ inputSchema: {
29981
+ type: "object",
29982
+ properties: {
29983
+ cardId: { type: "string", description: "Card ID to archive" }
29984
+ },
29985
+ required: ["cardId"]
29986
+ }
29987
+ },
29988
+ harmony_unarchive_card: {
29989
+ description: "Restore an archived card back to the board.",
29990
+ inputSchema: {
29991
+ type: "object",
29992
+ properties: {
29993
+ cardId: { type: "string", description: "Card ID to unarchive" }
29994
+ },
29995
+ required: ["cardId"]
29996
+ }
29997
+ },
29945
29998
  harmony_delete_card: {
29946
29999
  description: "Delete a card",
29947
30000
  inputSchema: {
@@ -30184,6 +30237,10 @@ var TOOLS = {
30184
30237
  summary: {
30185
30238
  type: "boolean",
30186
30239
  description: "Return only columns with card counts, no card details"
30240
+ },
30241
+ includeArchived: {
30242
+ type: "boolean",
30243
+ description: "Include archived cards in results (default: false). Archived cards are excluded by default."
30187
30244
  }
30188
30245
  }
30189
30246
  }
@@ -31242,7 +31299,8 @@ async function handleToolCall(name, args, deps) {
31242
31299
  description: args.description,
31243
31300
  priority: args.priority,
31244
31301
  assigneeId: args.assigneeId,
31245
- dueDate: args.dueDate
31302
+ dueDate: args.dueDate,
31303
+ done: args.done
31246
31304
  });
31247
31305
  return { success: true, ...result };
31248
31306
  }
@@ -31272,6 +31330,16 @@ async function handleToolCall(name, args, deps) {
31272
31330
  } catch {}
31273
31331
  return { success: true, sessionEnded, ...result };
31274
31332
  }
31333
+ case "harmony_archive_card": {
31334
+ const cardId = exports_external.string().uuid().parse(args.cardId);
31335
+ const result = await client3.archiveCard(cardId);
31336
+ return { success: true, ...result };
31337
+ }
31338
+ case "harmony_unarchive_card": {
31339
+ const cardId = exports_external.string().uuid().parse(args.cardId);
31340
+ const result = await client3.unarchiveCard(cardId);
31341
+ return { success: true, ...result };
31342
+ }
31275
31343
  case "harmony_delete_card": {
31276
31344
  const cardId = exports_external.string().uuid().parse(args.cardId);
31277
31345
  await client3.deleteCard(cardId);
@@ -31395,6 +31463,8 @@ async function handleToolCall(name, args, deps) {
31395
31463
  options.columnId = String(args.columnId);
31396
31464
  if (args.summary === true || args.summary === "true")
31397
31465
  options.summary = true;
31466
+ if (args.includeArchived === true || args.includeArchived === "true")
31467
+ options.includeArchived = true;
31398
31468
  const result = await client3.getBoard(projectId, options);
31399
31469
  return { success: true, board: result };
31400
31470
  }
@@ -32059,7 +32129,7 @@ async function handleToolCall(name, args, deps) {
32059
32129
  const card = cardResult.card;
32060
32130
  await client3.updatePlanTask(planId, task.id, { cardId: card.id });
32061
32131
  cardsCreated++;
32062
- } catch (e) {}
32132
+ } catch (_e) {}
32063
32133
  }
32064
32134
  results.cardsCreated = cardsCreated;
32065
32135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harmony-mcp",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
5
5
  "publishConfig": {
6
6
  "access": "public"