@gethmy/mcp 2.4.4 → 2.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5,25 +5,43 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+ var __returnValue = (v) => v;
35
+ function __exportSetter(name, newValue) {
36
+ this[name] = __returnValue.bind(null, newValue);
37
+ }
20
38
  var __export = (target, all) => {
21
39
  for (var name in all)
22
40
  __defProp(target, name, {
23
41
  get: all[name],
24
42
  enumerable: true,
25
43
  configurable: true,
26
- set: (newValue) => all[name] = () => newValue
44
+ set: __exportSetter.bind(all, name)
27
45
  });
28
46
  };
29
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -8942,7 +8960,7 @@ var require_formats = __commonJS((exports) => {
8942
8960
  }
8943
8961
  var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
8944
8962
  function getTime(strictTimeZone) {
8945
- return function time(str) {
8963
+ return function time3(str) {
8946
8964
  const matches = TIME.exec(str);
8947
8965
  if (!matches)
8948
8966
  return false;
@@ -27052,16 +27070,31 @@ async function requestWithBearer(apiUrl, bearerToken, method, path, body) {
27052
27070
  return result;
27053
27071
  }
27054
27072
 
27073
+ class HarmonyUnauthorizedError extends Error {
27074
+ constructor(message = "Invalid or expired credentials") {
27075
+ super(message);
27076
+ this.name = "HarmonyUnauthorizedError";
27077
+ }
27078
+ }
27079
+
27055
27080
  class HarmonyApiClient {
27056
27081
  apiKey;
27057
27082
  apiUrl;
27083
+ onUnauthorized;
27058
27084
  constructor(options) {
27059
27085
  this.apiKey = options?.apiKey ?? getApiKey();
27060
27086
  this.apiUrl = options?.apiUrl ?? getApiUrl();
27087
+ this.onUnauthorized = options?.onUnauthorized;
27061
27088
  }
27062
27089
  getApiUrl() {
27063
27090
  return this.apiUrl;
27064
27091
  }
27092
+ setApiKey(apiKey) {
27093
+ this.apiKey = apiKey;
27094
+ }
27095
+ getApiKey() {
27096
+ return this.apiKey;
27097
+ }
27065
27098
  async request(method, path, body, options) {
27066
27099
  await requestSemaphore.acquire();
27067
27100
  try {
@@ -27108,6 +27141,10 @@ class HarmonyApiClient {
27108
27141
  }
27109
27142
  if (!response.ok) {
27110
27143
  const errorMsg = data?.error || (looksLikeJson ? null : `API error: ${response.status} (non-JSON response)`) || `API error: ${response.status}`;
27144
+ if (response.status === 401) {
27145
+ this.onUnauthorized?.();
27146
+ throw new HarmonyUnauthorizedError(errorMsg);
27147
+ }
27111
27148
  if (!isRetryableError(null, response.status)) {
27112
27149
  throw new Error(errorMsg);
27113
27150
  }
@@ -27157,6 +27194,10 @@ class HarmonyApiClient {
27157
27194
  } catch {
27158
27195
  errorMsg = text || `API error: ${response.status}`;
27159
27196
  }
27197
+ if (response.status === 401) {
27198
+ this.onUnauthorized?.();
27199
+ throw new HarmonyUnauthorizedError(errorMsg);
27200
+ }
27160
27201
  if (!isRetryableError(null, response.status)) {
27161
27202
  throw new Error(errorMsg);
27162
27203
  }
@@ -28088,23 +28129,25 @@ var BATCH_SIZE = 100;
28088
28129
  var CONCURRENCY_LIMIT = 5;
28089
28130
  var BOILERPLATE_PATTERNS = [
28090
28131
  /^todo:?$/i,
28091
- /^placeholder/i,
28132
+ /^placeholder(\s+\d+|:)?$/i,
28092
28133
  /^\.\.\.$/,
28093
- /^untitled/i,
28094
- /^(note|memo|draft)\s*\d*$/i,
28134
+ /^untitled(\s+\d+|:)?$/i,
28135
+ /^(note|memo|draft)\s+\d+$/i,
28095
28136
  /^task transition:/i
28096
28137
  ];
28097
- function isBoilerplate(title, content) {
28138
+ function isBoilerplateTitle(title) {
28098
28139
  const t = title.trim();
28099
- const c = content.trim();
28100
- if (c.length === 0)
28101
- return true;
28102
28140
  for (const pat of BOILERPLATE_PATTERNS) {
28103
28141
  if (pat.test(t))
28104
28142
  return true;
28105
28143
  }
28106
28144
  return false;
28107
28145
  }
28146
+ function isBoilerplate(title, content) {
28147
+ if (content.trim().length === 0)
28148
+ return true;
28149
+ return isBoilerplateTitle(title);
28150
+ }
28108
28151
  function scoreEntity(entity, relationCount, archiveBelow, deleteBelow, staleDraftAgeDays) {
28109
28152
  const now = Date.now();
28110
28153
  const ageDays = (now - Date.parse(entity.created_at)) / MS_PER_DAY;
@@ -28186,10 +28229,11 @@ function scoreEntity(entity, relationCount, archiveBelow, deleteBelow, staleDraf
28186
28229
  legacy = true;
28187
28230
  legacyReasons.push("no graph presence");
28188
28231
  }
28232
+ const boilerplateTitle = isBoilerplateTitle(entity.title);
28189
28233
  let bucket;
28190
- if (boilerplate && deleteBelow > 0) {
28234
+ if (boilerplateTitle && deleteBelow > 0) {
28191
28235
  bucket = "delete";
28192
- reasons.push("boilerplate override");
28236
+ reasons.push("boilerplate title override");
28193
28237
  } else if (score < deleteBelow)
28194
28238
  bucket = "delete";
28195
28239
  else if (score < archiveBelow)
package/dist/index.js CHANGED
@@ -5,25 +5,43 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+ var __returnValue = (v) => v;
35
+ function __exportSetter(name, newValue) {
36
+ this[name] = __returnValue.bind(null, newValue);
37
+ }
20
38
  var __export = (target, all) => {
21
39
  for (var name in all)
22
40
  __defProp(target, name, {
23
41
  get: all[name],
24
42
  enumerable: true,
25
43
  configurable: true,
26
- set: (newValue) => all[name] = () => newValue
44
+ set: __exportSetter.bind(all, name)
27
45
  });
28
46
  };
29
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -6849,7 +6867,7 @@ var require_formats = __commonJS((exports) => {
6849
6867
  }
6850
6868
  var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
6851
6869
  function getTime(strictTimeZone) {
6852
- return function time(str) {
6870
+ return function time3(str) {
6853
6871
  const matches = TIME.exec(str);
6854
6872
  if (!matches)
6855
6873
  return false;
@@ -24812,16 +24830,31 @@ async function requestWithBearer(apiUrl, bearerToken, method, path, body) {
24812
24830
  return result;
24813
24831
  }
24814
24832
 
24833
+ class HarmonyUnauthorizedError extends Error {
24834
+ constructor(message = "Invalid or expired credentials") {
24835
+ super(message);
24836
+ this.name = "HarmonyUnauthorizedError";
24837
+ }
24838
+ }
24839
+
24815
24840
  class HarmonyApiClient {
24816
24841
  apiKey;
24817
24842
  apiUrl;
24843
+ onUnauthorized;
24818
24844
  constructor(options) {
24819
24845
  this.apiKey = options?.apiKey ?? getApiKey();
24820
24846
  this.apiUrl = options?.apiUrl ?? getApiUrl();
24847
+ this.onUnauthorized = options?.onUnauthorized;
24821
24848
  }
24822
24849
  getApiUrl() {
24823
24850
  return this.apiUrl;
24824
24851
  }
24852
+ setApiKey(apiKey) {
24853
+ this.apiKey = apiKey;
24854
+ }
24855
+ getApiKey() {
24856
+ return this.apiKey;
24857
+ }
24825
24858
  async request(method, path, body, options) {
24826
24859
  await requestSemaphore.acquire();
24827
24860
  try {
@@ -24868,6 +24901,10 @@ class HarmonyApiClient {
24868
24901
  }
24869
24902
  if (!response.ok) {
24870
24903
  const errorMsg = data?.error || (looksLikeJson ? null : `API error: ${response.status} (non-JSON response)`) || `API error: ${response.status}`;
24904
+ if (response.status === 401) {
24905
+ this.onUnauthorized?.();
24906
+ throw new HarmonyUnauthorizedError(errorMsg);
24907
+ }
24871
24908
  if (!isRetryableError(null, response.status)) {
24872
24909
  throw new Error(errorMsg);
24873
24910
  }
@@ -24917,6 +24954,10 @@ class HarmonyApiClient {
24917
24954
  } catch {
24918
24955
  errorMsg = text || `API error: ${response.status}`;
24919
24956
  }
24957
+ if (response.status === 401) {
24958
+ this.onUnauthorized?.();
24959
+ throw new HarmonyUnauthorizedError(errorMsg);
24960
+ }
24920
24961
  if (!isRetryableError(null, response.status)) {
24921
24962
  throw new Error(errorMsg);
24922
24963
  }
@@ -25848,23 +25889,25 @@ var BATCH_SIZE = 100;
25848
25889
  var CONCURRENCY_LIMIT = 5;
25849
25890
  var BOILERPLATE_PATTERNS = [
25850
25891
  /^todo:?$/i,
25851
- /^placeholder/i,
25892
+ /^placeholder(\s+\d+|:)?$/i,
25852
25893
  /^\.\.\.$/,
25853
- /^untitled/i,
25854
- /^(note|memo|draft)\s*\d*$/i,
25894
+ /^untitled(\s+\d+|:)?$/i,
25895
+ /^(note|memo|draft)\s+\d+$/i,
25855
25896
  /^task transition:/i
25856
25897
  ];
25857
- function isBoilerplate(title, content) {
25898
+ function isBoilerplateTitle(title) {
25858
25899
  const t = title.trim();
25859
- const c = content.trim();
25860
- if (c.length === 0)
25861
- return true;
25862
25900
  for (const pat of BOILERPLATE_PATTERNS) {
25863
25901
  if (pat.test(t))
25864
25902
  return true;
25865
25903
  }
25866
25904
  return false;
25867
25905
  }
25906
+ function isBoilerplate(title, content) {
25907
+ if (content.trim().length === 0)
25908
+ return true;
25909
+ return isBoilerplateTitle(title);
25910
+ }
25868
25911
  function scoreEntity(entity, relationCount, archiveBelow, deleteBelow, staleDraftAgeDays) {
25869
25912
  const now = Date.now();
25870
25913
  const ageDays = (now - Date.parse(entity.created_at)) / MS_PER_DAY;
@@ -25946,10 +25989,11 @@ function scoreEntity(entity, relationCount, archiveBelow, deleteBelow, staleDraf
25946
25989
  legacy = true;
25947
25990
  legacyReasons.push("no graph presence");
25948
25991
  }
25992
+ const boilerplateTitle = isBoilerplateTitle(entity.title);
25949
25993
  let bucket;
25950
- if (boilerplate && deleteBelow > 0) {
25994
+ if (boilerplateTitle && deleteBelow > 0) {
25951
25995
  bucket = "delete";
25952
- reasons.push("boilerplate override");
25996
+ reasons.push("boilerplate title override");
25953
25997
  } else if (score < deleteBelow)
25954
25998
  bucket = "delete";
25955
25999
  else if (score < archiveBelow)
@@ -1,11 +1,15 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __returnValue = (v) => v;
3
+ function __exportSetter(name, newValue) {
4
+ this[name] = __returnValue.bind(null, newValue);
5
+ }
2
6
  var __export = (target, all) => {
3
7
  for (var name in all)
4
8
  __defProp(target, name, {
5
9
  get: all[name],
6
10
  enumerable: true,
7
11
  configurable: true,
8
- set: (newValue) => all[name] = () => newValue
12
+ set: __exportSetter.bind(all, name)
9
13
  });
10
14
  };
11
15
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -1570,16 +1574,31 @@ async function requestWithBearer(apiUrl, bearerToken, method, path, body) {
1570
1574
  return result;
1571
1575
  }
1572
1576
 
1577
+ class HarmonyUnauthorizedError extends Error {
1578
+ constructor(message = "Invalid or expired credentials") {
1579
+ super(message);
1580
+ this.name = "HarmonyUnauthorizedError";
1581
+ }
1582
+ }
1583
+
1573
1584
  class HarmonyApiClient {
1574
1585
  apiKey;
1575
1586
  apiUrl;
1587
+ onUnauthorized;
1576
1588
  constructor(options) {
1577
1589
  this.apiKey = options?.apiKey ?? getApiKey();
1578
1590
  this.apiUrl = options?.apiUrl ?? getApiUrl();
1591
+ this.onUnauthorized = options?.onUnauthorized;
1579
1592
  }
1580
1593
  getApiUrl() {
1581
1594
  return this.apiUrl;
1582
1595
  }
1596
+ setApiKey(apiKey) {
1597
+ this.apiKey = apiKey;
1598
+ }
1599
+ getApiKey() {
1600
+ return this.apiKey;
1601
+ }
1583
1602
  async request(method, path, body, options) {
1584
1603
  await requestSemaphore.acquire();
1585
1604
  try {
@@ -1626,6 +1645,10 @@ class HarmonyApiClient {
1626
1645
  }
1627
1646
  if (!response.ok) {
1628
1647
  const errorMsg = data?.error || (looksLikeJson ? null : `API error: ${response.status} (non-JSON response)`) || `API error: ${response.status}`;
1648
+ if (response.status === 401) {
1649
+ this.onUnauthorized?.();
1650
+ throw new HarmonyUnauthorizedError(errorMsg);
1651
+ }
1629
1652
  if (!isRetryableError(null, response.status)) {
1630
1653
  throw new Error(errorMsg);
1631
1654
  }
@@ -1675,6 +1698,10 @@ class HarmonyApiClient {
1675
1698
  } catch {
1676
1699
  errorMsg = text || `API error: ${response.status}`;
1677
1700
  }
1701
+ if (response.status === 401) {
1702
+ this.onUnauthorized?.();
1703
+ throw new HarmonyUnauthorizedError(errorMsg);
1704
+ }
1678
1705
  if (!isRetryableError(null, response.status)) {
1679
1706
  throw new Error(errorMsg);
1680
1707
  }
@@ -2136,5 +2163,6 @@ export {
2136
2163
  resetClient,
2137
2164
  requestWithBearer,
2138
2165
  getClient,
2166
+ HarmonyUnauthorizedError,
2139
2167
  HarmonyApiClient
2140
2168
  };
@@ -1,11 +1,15 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __returnValue = (v) => v;
3
+ function __exportSetter(name, newValue) {
4
+ this[name] = __returnValue.bind(null, newValue);
5
+ }
2
6
  var __export = (target, all) => {
3
7
  for (var name in all)
4
8
  __defProp(target, name, {
5
9
  get: all[name],
6
10
  enumerable: true,
7
11
  configurable: true,
8
- set: (newValue) => all[name] = () => newValue
12
+ set: __exportSetter.bind(all, name)
9
13
  });
10
14
  };
11
15
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/mcp",
3
- "version": "2.4.4",
3
+ "version": "2.4.7",
4
4
  "description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -312,7 +312,89 @@ describe("runMemoryAudit", () => {
312
312
  });
313
313
  expect(report.summary.delete).toBe(1);
314
314
  expect(deletedIds).toContain("promoted-junk");
315
- expect(report.lowest[0].reasons).toContain("boilerplate override");
315
+ expect(report.lowest[0].reasons).toContain("boilerplate title override");
316
+ });
317
+
318
+ test("legitimate titles starting with boilerplate-prefix words are NOT deleted", async () => {
319
+ // Regression test for the over-broad regex bug. Pre-fix patterns matched
320
+ // any title starting with "Placeholder", "Untitled", "Note", etc. After
321
+ // tightening, only exact boilerplate forms (with optional digit suffix
322
+ // or colon) match — real titles survive.
323
+ const { client, deletedIds } = makeMockClient(
324
+ [
325
+ {
326
+ id: "legit-placeholder",
327
+ type: "pattern",
328
+ title: "Placeholder pattern in React Suspense",
329
+ content:
330
+ "Use React.Suspense with a fallback component as the placeholder pattern for streaming SSR.",
331
+ confidence: 0.9,
332
+ memory_tier: "reference",
333
+ access_count: 12,
334
+ last_accessed_at: daysAgo(1),
335
+ created_at: daysAgo(60),
336
+ tags: ["react", "ssr"],
337
+ embedding: [0.1],
338
+ },
339
+ {
340
+ id: "legit-untitled",
341
+ type: "context",
342
+ title: "UntitledMaster.fig — design source for the homepage",
343
+ content:
344
+ "Reference Figma file containing master components for landing page assets.",
345
+ confidence: 0.85,
346
+ memory_tier: "reference",
347
+ access_count: 8,
348
+ last_accessed_at: daysAgo(2),
349
+ created_at: daysAgo(45),
350
+ tags: ["design"],
351
+ embedding: [0.1],
352
+ },
353
+ {
354
+ id: "legit-note",
355
+ type: "context",
356
+ title: "Note: schema migration order matters",
357
+ content: "Always run 0042 before 0043 because of FK dependency.",
358
+ confidence: 0.8,
359
+ memory_tier: "reference",
360
+ access_count: 5,
361
+ last_accessed_at: daysAgo(3),
362
+ created_at: daysAgo(30),
363
+ tags: ["db"],
364
+ embedding: [0.1],
365
+ },
366
+ ],
367
+ { "legit-placeholder": 3, "legit-untitled": 2, "legit-note": 1 },
368
+ );
369
+
370
+ const report = await runMemoryAudit(client, "ws-1", undefined, {
371
+ dryRun: false,
372
+ });
373
+ expect(deletedIds).toHaveLength(0);
374
+ expect(report.summary.delete).toBe(0);
375
+ });
376
+
377
+ test("empty-content draft with real title is NOT delete-bucketed", async () => {
378
+ // Users sometimes save a draft with title only and fill content later.
379
+ // The override is title-only, so empty content alone must not delete.
380
+ const { client, deletedIds } = makeMockClient([
381
+ {
382
+ id: "draft-empty-body",
383
+ type: "decision",
384
+ title: "Decision: skip Q3 launch",
385
+ content: "",
386
+ confidence: 0.7,
387
+ memory_tier: "draft",
388
+ access_count: 1,
389
+ last_accessed_at: daysAgo(1),
390
+ created_at: daysAgo(2),
391
+ tags: ["q3"],
392
+ embedding: null,
393
+ },
394
+ ]);
395
+
396
+ await runMemoryAudit(client, "ws-1", undefined, { dryRun: false });
397
+ expect(deletedIds).not.toContain("draft-empty-body");
316
398
  });
317
399
 
318
400
  test("boilerplate override respects deleteBelow=0 escape hatch", async () => {