memory-journal-mcp 7.1.0 → 7.3.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 (124) hide show
  1. package/README.md +59 -51
  2. package/dist/{chunk-GW5DYUQJ.js → chunk-CHWIPVQN.js} +174 -74
  3. package/dist/{chunk-37BQOJDZ.js → chunk-WXDEVIFL.js} +87 -8
  4. package/dist/{chunk-JEGRDY6W.js → chunk-ZJJD2F5T.js} +487 -89
  5. package/dist/cli.js +30 -4
  6. package/dist/github-integration-YODGZH3K.js +1 -0
  7. package/dist/index.d.ts +5 -1
  8. package/dist/index.js +3 -3
  9. package/dist/{tools-O44Q52RD.js → tools-MNMGDTQI.js} +2 -2
  10. package/package.json +4 -4
  11. package/skills/README.md +77 -0
  12. package/skills/autonomous-dev/SKILL.md +56 -0
  13. package/skills/bin/sync.js +50 -0
  14. package/skills/bun/SKILL.md +156 -0
  15. package/skills/github-commander/SKILL.md +1 -1
  16. package/skills/github-commander/workflows/code-quality-audit.md +7 -5
  17. package/skills/github-commander/workflows/issue-triage.md +13 -4
  18. package/skills/github-commander/workflows/milestone-sprint.md +9 -1
  19. package/skills/github-commander/workflows/perf-audit.md +2 -0
  20. package/skills/github-commander/workflows/pr-review.md +9 -3
  21. package/skills/github-commander/workflows/roadmap-kickoff.md +79 -0
  22. package/skills/github-commander/workflows/security-audit.md +3 -3
  23. package/skills/github-commander/workflows/update-deps.md +2 -2
  24. package/skills/gitlab/SKILL.md +115 -0
  25. package/skills/gitlab/package-lock.json +392 -0
  26. package/skills/gitlab/package.json +14 -0
  27. package/skills/gitlab/scripts/gitlab-client.ts +125 -0
  28. package/skills/gitlab/scripts/gitlab-helper.ts +80 -0
  29. package/skills/golang/SKILL.md +54 -0
  30. package/skills/mysql/SKILL.md +30 -0
  31. package/skills/package.json +48 -0
  32. package/skills/playwright-standard/SKILL.md +58 -0
  33. package/skills/playwright-standard/examples/fixtures.ts +66 -0
  34. package/skills/playwright-standard/examples/type-stubs.d.ts +10 -0
  35. package/skills/playwright-standard/references/advanced-scenarios.md +59 -0
  36. package/skills/playwright-standard/references/infrastructure.md +43 -0
  37. package/skills/postgres/SKILL.md +33 -0
  38. package/skills/react-best-practices/AGENTS.md +2883 -0
  39. package/skills/react-best-practices/README.md +127 -0
  40. package/skills/react-best-practices/SKILL.md +138 -0
  41. package/skills/react-best-practices/metadata.json +17 -0
  42. package/skills/react-best-practices/rules/_sections.md +46 -0
  43. package/skills/react-best-practices/rules/_template.md +28 -0
  44. package/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  45. package/skills/react-best-practices/rules/advanced-init-once.md +42 -0
  46. package/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
  47. package/skills/react-best-practices/rules/async-api-routes.md +35 -0
  48. package/skills/react-best-practices/rules/async-defer-await.md +80 -0
  49. package/skills/react-best-practices/rules/async-dependencies.md +48 -0
  50. package/skills/react-best-practices/rules/async-parallel.md +24 -0
  51. package/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  52. package/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  53. package/skills/react-best-practices/rules/bundle-conditional.md +37 -0
  54. package/skills/react-best-practices/rules/bundle-defer-third-party.md +48 -0
  55. package/skills/react-best-practices/rules/bundle-dynamic-imports.md +34 -0
  56. package/skills/react-best-practices/rules/bundle-preload.md +44 -0
  57. package/skills/react-best-practices/rules/client-event-listeners.md +78 -0
  58. package/skills/react-best-practices/rules/client-localstorage-schema.md +74 -0
  59. package/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
  60. package/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  61. package/skills/react-best-practices/rules/js-batch-dom-css.md +110 -0
  62. package/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  63. package/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  64. package/skills/react-best-practices/rules/js-cache-storage.md +68 -0
  65. package/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  66. package/skills/react-best-practices/rules/js-early-exit.md +50 -0
  67. package/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  68. package/skills/react-best-practices/rules/js-index-maps.md +37 -0
  69. package/skills/react-best-practices/rules/js-length-check-first.md +50 -0
  70. package/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  71. package/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  72. package/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  73. package/skills/react-best-practices/rules/rendering-activity.md +24 -0
  74. package/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +38 -0
  75. package/skills/react-best-practices/rules/rendering-conditional-render.md +32 -0
  76. package/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  77. package/skills/react-best-practices/rules/rendering-hoist-jsx.md +36 -0
  78. package/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +72 -0
  79. package/skills/react-best-practices/rules/rendering-hydration-suppress-warning.md +26 -0
  80. package/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  81. package/skills/react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  82. package/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  83. package/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  84. package/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  85. package/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  86. package/skills/react-best-practices/rules/rerender-functional-setstate.md +77 -0
  87. package/skills/react-best-practices/rules/rerender-lazy-state-init.md +56 -0
  88. package/skills/react-best-practices/rules/rerender-memo-with-default-value.md +36 -0
  89. package/skills/react-best-practices/rules/rerender-memo.md +44 -0
  90. package/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  91. package/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  92. package/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  93. package/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  94. package/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  95. package/skills/react-best-practices/rules/server-auth-actions.md +96 -0
  96. package/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  97. package/skills/react-best-practices/rules/server-cache-react.md +76 -0
  98. package/skills/react-best-practices/rules/server-dedup-props.md +65 -0
  99. package/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
  100. package/skills/react-best-practices/rules/server-serialization.md +38 -0
  101. package/skills/rust/SKILL.md +86 -0
  102. package/skills/shadcn-ui/SKILL.md +72 -0
  103. package/skills/skill-builder/SKILL.md +457 -0
  104. package/skills/skill-builder/checklist.md +65 -0
  105. package/skills/sqlite/SKILL.md +38 -0
  106. package/skills/typescript/SKILL.md +453 -0
  107. package/skills/typescript/assets/eslint-template.js +102 -0
  108. package/skills/typescript/assets/tsconfig-template.json +45 -0
  109. package/skills/typescript/references/enterprise-patterns.md +531 -0
  110. package/skills/typescript/references/generics.md +493 -0
  111. package/skills/typescript/references/nestjs-integration.md +579 -0
  112. package/skills/typescript/references/react-integration.md +616 -0
  113. package/skills/typescript/references/toolchain.md +547 -0
  114. package/skills/typescript/references/type-system.md +481 -0
  115. package/skills/vitest-standard/SKILL.md +82 -0
  116. package/skills/vitest-standard/examples/service-mock.ts +60 -0
  117. package/skills/vitest-standard/examples/tdd-calculator.ts +41 -0
  118. package/skills/vitest-standard/examples/type-stubs.d.ts +18 -0
  119. package/skills/vitest-standard/references/async-and-errors.md +58 -0
  120. package/skills/vitest-standard/references/coverage-and-config.md +53 -0
  121. package/skills/vitest-standard/references/mocking.md +61 -0
  122. package/skills/vitest-standard/references/tdd-patterns.md +60 -0
  123. package/dist/github-integration-FOJ4U6I5.js +0 -1
  124. package/skills/github-commander/workflows/full-audit.md +0 -134
@@ -1,5 +1,5 @@
1
1
  import { transformAutoReturn } from './chunk-OKOVZ5QE.js';
2
- import { GitHubIntegration, resolveAuthor, logger, assertSafeDirectoryPath, MemoryJournalMcpError, matchSuggestion, ConfigurationError } from './chunk-37BQOJDZ.js';
2
+ import { GitHubIntegration, resolveAuthor, logger, ValidationError, ResourceNotFoundError, assertSafeDirectoryPath, MemoryJournalMcpError, matchSuggestion, ConfigurationError } from './chunk-WXDEVIFL.js';
3
3
  import { z, ZodError } from 'zod';
4
4
  import { open, stat, mkdir, rename, appendFile, readdir, readFile } from 'fs/promises';
5
5
  import { tmpdir } from 'os';
@@ -59,7 +59,7 @@ async function resolveIssueUrl(context, projectNumber, issueNumber, existingUrl)
59
59
  ([_, v]) => v.project_number === projectNumber
60
60
  );
61
61
  if (entry) {
62
- const { GitHubIntegration: GitHubIntegration2 } = await import('./github-integration-FOJ4U6I5.js');
62
+ const { GitHubIntegration: GitHubIntegration2 } = await import('./github-integration-YODGZH3K.js');
63
63
  const targetGithub = new GitHubIntegration2(entry[1].path);
64
64
  const repoInfo = await targetGithub.getRepoInfo();
65
65
  if (repoInfo.owner && repoInfo.repo) {
@@ -114,6 +114,7 @@ var SIGNIFICANCE_TYPES = [
114
114
  "release"
115
115
  ];
116
116
  var MAX_CONTENT_LENGTH = 5e4;
117
+ var MAX_QUERY_LIMIT = 500;
117
118
  var DATE_MIN_SENTINEL = "1970-01-01";
118
119
  var DATE_MAX_SENTINEL = "2999-12-31";
119
120
  var DATE_FORMAT_REGEX = /^\d{4}-\d{2}-\d{2}$/;
@@ -207,7 +208,7 @@ var CreateEntrySchemaMcp = z.object({
207
208
  share_with_team: z.boolean().optional().default(false)
208
209
  });
209
210
  var GetEntryByIdSchema = z.object({
210
- entry_id: z.number(),
211
+ entry_id: z.number().int(),
211
212
  include_relationships: z.boolean().optional().default(true)
212
213
  });
213
214
  var GetEntryByIdSchemaMcp = z.object({
@@ -215,7 +216,7 @@ var GetEntryByIdSchemaMcp = z.object({
215
216
  include_relationships: z.boolean().optional().default(true)
216
217
  });
217
218
  var GetRecentEntriesSchema = z.object({
218
- limit: z.number().max(500).optional().default(5),
219
+ limit: z.number().min(1).max(MAX_QUERY_LIMIT).optional().default(5),
219
220
  is_personal: z.boolean().optional()
220
221
  });
221
222
  var GetRecentEntriesSchemaMcp = z.object({
@@ -246,6 +247,7 @@ var EntryByIdOutputSchema = z.object({
246
247
  error: z.string().optional()
247
248
  }).extend(ErrorFieldsMixin.shape);
248
249
  var TestSimpleOutputSchema = z.object({
250
+ success: z.boolean().optional(),
249
251
  message: z.string()
250
252
  }).extend(ErrorFieldsMixin.shape);
251
253
  var TagsListOutputSchema = z.object({
@@ -363,6 +365,7 @@ function getCoreTools(context) {
363
365
  }
364
366
  const { score: importance, breakdown: importanceBreakdown } = db.calculateImportance(entry_id);
365
367
  const result = {
368
+ success: true,
366
369
  entry,
367
370
  importance,
368
371
  importanceBreakdown
@@ -388,7 +391,7 @@ function getCoreTools(context) {
388
391
  try {
389
392
  const { limit, is_personal } = GetRecentEntriesSchema.parse(params);
390
393
  const entries = db.getRecentEntries(limit, is_personal);
391
- return { entries, count: entries.length };
394
+ return { success: true, entries, count: entries.length };
392
395
  } catch (err) {
393
396
  return formatHandlerError(err);
394
397
  }
@@ -424,7 +427,7 @@ function getCoreTools(context) {
424
427
  handler: (params) => {
425
428
  try {
426
429
  const { message } = TestSimpleSchema.parse(params);
427
- return { message: `Test response: ${message}` };
430
+ return { success: true, message: `Test response: ${message}` };
428
431
  } catch (err) {
429
432
  return formatHandlerError(err);
430
433
  }
@@ -442,7 +445,7 @@ function getCoreTools(context) {
442
445
  try {
443
446
  const rawTags = db.listTags();
444
447
  const tags = rawTags.map((t) => ({ name: t.name, count: t.usageCount }));
445
- return { tags, count: tags.length };
448
+ return { success: true, tags, count: tags.length };
446
449
  } catch (err) {
447
450
  return formatHandlerError(err);
448
451
  }
@@ -452,7 +455,6 @@ function getCoreTools(context) {
452
455
  }
453
456
 
454
457
  // src/handlers/tools/search/helpers.ts
455
- var MAX_QUERY_LIMIT = 500;
456
458
  var DEDUP_KEY_LENGTH = 200;
457
459
  function calcPerDbLimit(limit, hasTeamDb) {
458
460
  return hasTeamDb ? Math.min(limit * 2, MAX_QUERY_LIMIT) : limit;
@@ -470,6 +472,30 @@ function mergeAndDedup(personal, team, limit) {
470
472
  }
471
473
  return limit !== void 0 ? merged.slice(0, limit) : merged;
472
474
  }
475
+ function passMetadataFilters(entry, options, db) {
476
+ if (options.isPersonal !== void 0 && entry.isPersonal !== options.isPersonal) return false;
477
+ if (options.projectNumber !== void 0 && entry.projectNumber !== options.projectNumber)
478
+ return false;
479
+ if (options.issueNumber !== void 0 && entry.issueNumber !== options.issueNumber) return false;
480
+ if (options.prNumber !== void 0 && entry.prNumber !== options.prNumber) return false;
481
+ if (options.prStatus !== void 0 && entry.prStatus !== options.prStatus) return false;
482
+ if (options.workflowRunId !== void 0 && entry.workflowRunId !== options.workflowRunId)
483
+ return false;
484
+ if (options.entryType && entry.entryType !== options.entryType) return false;
485
+ if (options.startDate) {
486
+ const entryDate = entry.timestamp.split("T")[0] ?? "";
487
+ if (entryDate < options.startDate) return false;
488
+ }
489
+ if (options.endDate) {
490
+ const entryDate = entry.timestamp.split("T")[0] ?? "";
491
+ if (entryDate > options.endDate) return false;
492
+ }
493
+ if (options.tags && options.tags.length > 0) {
494
+ const entryTags = Array.isArray(entry.tags) ? entry.tags : db.getTagsForEntry(entry.id);
495
+ if (!options.tags.some((t) => entryTags.includes(t))) return false;
496
+ }
497
+ return true;
498
+ }
473
499
 
474
500
  // src/handlers/tools/search/auto.ts
475
501
  var QUESTION_PATTERNS = [
@@ -603,7 +629,7 @@ async function hybridSearch(query, db, vectorManager, options) {
603
629
  for (const id of sortedIds) {
604
630
  const entry = entriesMap.get(id);
605
631
  if (!entry) continue;
606
- if (options.isPersonal !== void 0 && entry.isPersonal !== options.isPersonal) continue;
632
+ if (!passMetadataFilters(entry, options, db)) continue;
607
633
  entries.push({ ...entry, source: "personal" });
608
634
  }
609
635
  return { entries, fusionScores };
@@ -730,11 +756,25 @@ function getSearchTools(context) {
730
756
  handler: async (params) => {
731
757
  try {
732
758
  const input = SearchEntriesSchema.parse(params);
733
- const query = input.query || "";
759
+ const query = input.query?.trim() || "";
734
760
  const mode = input.mode;
735
- const { resolvedMode, isAuto } = resolveSearchMode(mode, query);
736
761
  const hasFilters = input.project_number !== void 0 || input.issue_number !== void 0 || input.pr_number !== void 0 || input.pr_status !== void 0 || input.workflow_run_id !== void 0 || input.is_personal !== void 0 || input.tags !== void 0 || input.entry_type !== void 0 || input.start_date !== void 0 || input.end_date !== void 0;
737
- const effectiveMode = !query && !hasFilters ? "fts" : resolvedMode;
762
+ if (!query && !hasFilters) {
763
+ return {
764
+ ...formatHandlerError(
765
+ new ValidationError(
766
+ "Search requires either a query string or at least one filter",
767
+ {
768
+ suggestion: "Provide a search query or use get_recent_entries instead"
769
+ }
770
+ )
771
+ ),
772
+ entries: [],
773
+ count: 0
774
+ };
775
+ }
776
+ const { resolvedMode, isAuto } = resolveSearchMode(mode, query);
777
+ const effectiveMode = resolvedMode;
738
778
  const searchOptions = {
739
779
  limit: input.limit,
740
780
  isPersonal: input.is_personal,
@@ -754,9 +794,10 @@ function getSearchTools(context) {
754
794
  const result = ftsSearch(input.query, db, teamDb, searchOptions);
755
795
  return { ...result, searchMode: "fts (fallback)" };
756
796
  }
797
+ const internalLimit = hasFilters ? Math.min(Math.max(input.limit * 10, 100), 1e3) : input.limit * 2;
757
798
  const semanticResults = await vectorManager.search(
758
799
  query,
759
- input.limit,
800
+ internalLimit,
760
801
  0.25
761
802
  );
762
803
  const entryIds = semanticResults.map((r) => r.entryId);
@@ -764,11 +805,11 @@ function getSearchTools(context) {
764
805
  const entries = semanticResults.map((r) => {
765
806
  const entry = entriesMap.get(r.entryId);
766
807
  if (!entry) return null;
767
- if (input.is_personal !== void 0 && entry.isPersonal !== input.is_personal)
768
- return null;
808
+ if (!passMetadataFilters(entry, searchOptions, db)) return null;
769
809
  return { ...entry, source: "personal" };
770
810
  }).filter((e) => e !== null);
771
811
  return {
812
+ success: true,
772
813
  entries,
773
814
  count: entries.length,
774
815
  searchMode: isAuto ? "semantic (auto)" : "semantic"
@@ -786,6 +827,7 @@ function getSearchTools(context) {
786
827
  searchOptions
787
828
  );
788
829
  return {
830
+ success: true,
789
831
  entries,
790
832
  count: entries.length,
791
833
  searchMode: isAuto ? "hybrid (auto)" : "hybrid"
@@ -796,6 +838,7 @@ function getSearchTools(context) {
796
838
  const result = ftsSearch(input.query, db, teamDb, searchOptions);
797
839
  return {
798
840
  ...result,
841
+ success: true,
799
842
  searchMode: isAuto ? "fts (auto)" : "fts"
800
843
  };
801
844
  }
@@ -856,9 +899,13 @@ function getSearchTools(context) {
856
899
  teamEntries.map((e) => ({ ...e, source: "team" })),
857
900
  input.limit
858
901
  );
859
- return { entries: merged, count: merged.length };
902
+ return { success: true, entries: merged, count: merged.length };
860
903
  }
861
- return { entries: personalEntries, count: personalEntries.length };
904
+ return {
905
+ success: true,
906
+ entries: personalEntries,
907
+ count: personalEntries.length
908
+ };
862
909
  } catch (err) {
863
910
  return formatHandlerError(err);
864
911
  }
@@ -900,17 +947,19 @@ function getSearchTools(context) {
900
947
  count: 0
901
948
  };
902
949
  }
950
+ const hasFilters = input.is_personal !== void 0 || input.tags !== void 0 || input.entry_type !== void 0 || input.start_date !== void 0 || input.end_date !== void 0;
951
+ const internalLimit = hasFilters ? Math.min(Math.max(input.limit * 10, 100), 1e3) : input.limit * 2;
903
952
  let results;
904
953
  if (input.entry_id !== void 0) {
905
954
  results = await vectorManager.searchByEntryId(
906
955
  input.entry_id,
907
- input.limit ?? 10,
956
+ internalLimit,
908
957
  input.similarity_threshold ?? 0.25
909
958
  );
910
959
  } else {
911
960
  results = await vectorManager.search(
912
961
  input.query ?? "",
913
- input.limit ?? 10,
962
+ internalLimit,
914
963
  input.similarity_threshold ?? 0.25
915
964
  );
916
965
  }
@@ -919,29 +968,21 @@ function getSearchTools(context) {
919
968
  const entries = results.map((r) => {
920
969
  const entry = entriesMap.get(r.entryId);
921
970
  if (!entry) return null;
922
- if (input.is_personal !== void 0 && entry.isPersonal !== input.is_personal)
923
- return null;
924
- if (input.tags && input.tags.length > 0) {
925
- const entryTags = db.getTagsForEntry(entry.id);
926
- if (!input.tags.some((t) => entryTags.includes(t))) return null;
927
- }
928
- if (input.entry_type && entry.entryType !== input.entry_type)
929
- return null;
930
- if (input.start_date) {
931
- const entryDate = entry.timestamp.split("T")[0] ?? "";
932
- if (entryDate < input.start_date) return null;
933
- }
934
- if (input.end_date) {
935
- const entryDate = entry.timestamp.split("T")[0] ?? "";
936
- if (entryDate > input.end_date) return null;
937
- }
971
+ const filterOptions = {
972
+ isPersonal: input.is_personal,
973
+ tags: input.tags,
974
+ entryType: input.entry_type,
975
+ startDate: input.start_date,
976
+ endDate: input.end_date
977
+ };
978
+ if (!passMetadataFilters(entry, filterOptions, db)) return null;
938
979
  if (input.entry_id !== void 0 && entry.id === input.entry_id)
939
980
  return null;
940
981
  return {
941
982
  ...entry,
942
983
  similarity: Math.round(r.score * 100) / 100
943
984
  };
944
- }).filter((e) => e !== null);
985
+ }).filter((e) => e !== null).slice(0, input.limit);
945
986
  const stats = vectorManager.getStats();
946
987
  const isIndexEmpty = stats.itemCount === 0;
947
988
  const includeHint = input.hint_on_empty ?? true;
@@ -950,6 +991,7 @@ function getSearchTools(context) {
950
991
  const allNoise = entries.length > 0 && bestSimilarity < QUALITY_FLOOR2;
951
992
  const hint = isIndexEmpty && includeHint ? "No entries in vector index. Use rebuild_vector_index to index existing entries." : entries.length === 0 && includeHint ? `No entries matched your query above the similarity threshold (${String(input.similarity_threshold ?? 0.25)}). Try lowering similarity_threshold (e.g., 0.15) for broader matches.` : allNoise ? `Results may be noise \u2014 best similarity (${String(bestSimilarity)}) is below quality floor (${String(QUALITY_FLOOR2)}). Try a more specific query or raise similarity_threshold to filter weak matches.` : void 0;
952
993
  return {
994
+ success: true,
953
995
  query: input.query,
954
996
  ...input.entry_id !== void 0 ? { entryId: input.entry_id } : {},
955
997
  entries,
@@ -1108,7 +1150,7 @@ function getAnalyticsTools(context) {
1108
1150
  end_date,
1109
1151
  project_breakdown
1110
1152
  );
1111
- return { ...stats, groupBy: group_by };
1153
+ return { success: true, ...stats, groupBy: group_by };
1112
1154
  } catch (err) {
1113
1155
  return formatHandlerError(err);
1114
1156
  }
@@ -1150,6 +1192,7 @@ function getAnalyticsTools(context) {
1150
1192
  );
1151
1193
  if (!projectsResult[0] || projectsResult[0].values.length === 0) {
1152
1194
  return {
1195
+ success: true,
1153
1196
  project_count: 0,
1154
1197
  total_entries: 0,
1155
1198
  projects: [],
@@ -1221,6 +1264,7 @@ function getAnalyticsTools(context) {
1221
1264
  )
1222
1265
  }));
1223
1266
  return {
1267
+ success: true,
1224
1268
  project_count: projects.length,
1225
1269
  total_entries: totalEntries,
1226
1270
  projects: projects.map((p) => ({
@@ -1320,10 +1364,28 @@ function getRelationshipTools(context) {
1320
1364
  const input = LinkEntriesSchema.parse(params);
1321
1365
  if (input.from_entry_id === input.to_entry_id) {
1322
1366
  return {
1323
- success: false,
1324
- error: "Cannot link an entry to itself",
1325
- code: "VALIDATION_ERROR",
1326
- category: "validation"
1367
+ ...formatHandlerError(
1368
+ new ValidationError("Cannot link an entry to itself")
1369
+ ),
1370
+ success: false
1371
+ };
1372
+ }
1373
+ const fromEntry = db.getEntryById(input.from_entry_id);
1374
+ if (!fromEntry || fromEntry.deletedAt) {
1375
+ return {
1376
+ ...formatHandlerError(
1377
+ new ResourceNotFoundError("Entry", String(input.from_entry_id))
1378
+ ),
1379
+ success: false
1380
+ };
1381
+ }
1382
+ const toEntry = db.getEntryById(input.to_entry_id);
1383
+ if (!toEntry || toEntry.deletedAt) {
1384
+ return {
1385
+ ...formatHandlerError(
1386
+ new ResourceNotFoundError("Entry", String(input.to_entry_id))
1387
+ ),
1388
+ success: false
1327
1389
  };
1328
1390
  }
1329
1391
  const existingRelationships = db.getRelationships(input.from_entry_id);
@@ -1358,12 +1420,13 @@ function getRelationshipTools(context) {
1358
1420
  const isFkError = errMsg.includes("FOREIGN KEY constraint failed");
1359
1421
  if (isFkError) {
1360
1422
  return {
1361
- success: false,
1362
- error: `One or both entries not found (from: ${String(input.from_entry_id)}, to: ${String(input.to_entry_id)})`,
1363
- code: "RESOURCE_NOT_FOUND",
1364
- category: "resource",
1365
- suggestion: "Verify both entry IDs exist before linking",
1366
- recoverable: true
1423
+ ...formatHandlerError(
1424
+ new ResourceNotFoundError(
1425
+ "Entry",
1426
+ `from: ${String(input.from_entry_id)}, to: ${String(input.to_entry_id)}`
1427
+ )
1428
+ ),
1429
+ success: false
1367
1430
  };
1368
1431
  }
1369
1432
  }
@@ -1387,6 +1450,9 @@ function getRelationshipTools(context) {
1387
1450
  const entry = db.getEntryById(input.entry_id);
1388
1451
  if (!entry) {
1389
1452
  return {
1453
+ ...formatHandlerError(
1454
+ new ResourceNotFoundError("Entry", String(input.entry_id))
1455
+ ),
1390
1456
  success: false,
1391
1457
  entry_count: 0,
1392
1458
  relationship_count: 0,
@@ -2098,11 +2164,7 @@ ${e.content}
2098
2164
  tags: e.tags,
2099
2165
  significance: e.significanceType ?? void 0
2100
2166
  }));
2101
- const result = await exportEntriesToMarkdown(
2102
- exportable,
2103
- input.output_dir,
2104
- db
2105
- );
2167
+ const result = await exportEntriesToMarkdown(exportable, input.output_dir, db);
2106
2168
  await sendProgress(progress, 3, 3, "Export complete");
2107
2169
  return result;
2108
2170
  } catch (err) {
@@ -2439,6 +2501,8 @@ var GitHubIssueOutputSchema = z.object({
2439
2501
  }).extend(ErrorFieldsMixin.shape);
2440
2502
  var GitHubIssueDetailsOutputSchema = GitHubIssueOutputSchema.extend({
2441
2503
  body: z.string().nullable(),
2504
+ bodyTruncated: z.boolean().optional(),
2505
+ bodyFullLength: z.number().optional(),
2442
2506
  labels: z.array(z.string()),
2443
2507
  assignees: z.array(z.string()),
2444
2508
  createdAt: z.string(),
@@ -2459,6 +2523,14 @@ var GitHubIssuesListOutputSchema = z.object({
2459
2523
  }).extend(ErrorFieldsMixin.shape);
2460
2524
  var GitHubIssueResultOutputSchema = z.object({
2461
2525
  issue: GitHubIssueDetailsOutputSchema.optional(),
2526
+ comments: z.array(
2527
+ z.object({
2528
+ author: z.string(),
2529
+ body: z.string(),
2530
+ createdAt: z.string()
2531
+ })
2532
+ ).optional(),
2533
+ commentCount: z.number().optional(),
2462
2534
  owner: z.string().optional(),
2463
2535
  repo: z.string().optional(),
2464
2536
  detectedOwner: z.string().nullable().optional(),
@@ -2475,6 +2547,8 @@ var GitHubPullRequestOutputSchema = z.object({
2475
2547
  }).extend(ErrorFieldsMixin.shape);
2476
2548
  var GitHubPRDetailsOutputSchema = GitHubPullRequestOutputSchema.extend({
2477
2549
  body: z.string().nullable(),
2550
+ bodyTruncated: z.boolean().optional(),
2551
+ bodyFullLength: z.number().optional(),
2478
2552
  draft: z.boolean(),
2479
2553
  headBranch: z.string(),
2480
2554
  baseBranch: z.string(),
@@ -2539,7 +2613,9 @@ var StatusOptionOutputSchema = z.object({
2539
2613
  var KanbanColumnOutputSchema = z.object({
2540
2614
  status: z.string(),
2541
2615
  statusOptionId: z.string(),
2542
- items: z.array(KanbanItemOutputSchema)
2616
+ items: z.array(KanbanItemOutputSchema).optional(),
2617
+ itemCount: z.number().optional(),
2618
+ truncated: z.boolean().optional()
2543
2619
  });
2544
2620
  var KanbanBoardOutputSchema = z.object({
2545
2621
  projectId: z.string().optional(),
@@ -2549,6 +2625,14 @@ var KanbanBoardOutputSchema = z.object({
2549
2625
  statusOptions: z.array(StatusOptionOutputSchema).optional(),
2550
2626
  columns: z.array(KanbanColumnOutputSchema).optional(),
2551
2627
  totalItems: z.number().optional(),
2628
+ itemDirectory: z.array(
2629
+ z.object({
2630
+ id: z.string(),
2631
+ title: z.string(),
2632
+ status: z.string().nullable()
2633
+ })
2634
+ ).optional(),
2635
+ summaryOnly: z.boolean().optional(),
2552
2636
  owner: z.string().optional(),
2553
2637
  detectedOwner: z.string().nullable().optional(),
2554
2638
  detectedRepo: z.string().nullable().optional(),
@@ -2558,16 +2642,19 @@ var KanbanBoardOutputSchema = z.object({
2558
2642
  instruction: z.string().optional()
2559
2643
  }).extend(ErrorFieldsMixin.shape);
2560
2644
  var MoveKanbanItemOutputSchema = z.object({
2561
- success: z.boolean().optional(),
2562
2645
  itemId: z.string().optional(),
2563
2646
  newStatus: z.string().optional(),
2564
2647
  projectNumber: z.number().optional(),
2565
- message: z.string().optional(),
2566
- error: z.string().optional(),
2567
- requiresUserInput: z.boolean().optional(),
2568
- hint: z.string().optional(),
2569
2648
  availableStatuses: z.array(z.string()).optional()
2570
2649
  }).extend(ErrorFieldsMixin.shape);
2650
+ var AddKanbanItemOutputSchema = z.object({
2651
+ itemId: z.string().optional(),
2652
+ projectNumber: z.number().optional()
2653
+ }).extend(ErrorFieldsMixin.shape);
2654
+ var DeleteKanbanItemOutputSchema = z.object({
2655
+ itemId: z.string().optional(),
2656
+ projectNumber: z.number().optional()
2657
+ }).extend(ErrorFieldsMixin.shape);
2571
2658
  var CreateGitHubIssueWithEntryOutputSchema = z.object({
2572
2659
  success: z.boolean().optional(),
2573
2660
  issue: z.object({
@@ -2578,7 +2665,8 @@ var CreateGitHubIssueWithEntryOutputSchema = z.object({
2578
2665
  project: z.object({
2579
2666
  projectNumber: z.number(),
2580
2667
  added: z.boolean(),
2581
- message: z.string(),
2668
+ message: z.string().optional(),
2669
+ error: z.string().optional(),
2582
2670
  initialStatus: z.object({
2583
2671
  status: z.string(),
2584
2672
  set: z.boolean()
@@ -2677,6 +2765,7 @@ var DeleteMilestoneOutputSchema = z.object({
2677
2765
  instruction: z.string().optional()
2678
2766
  }).extend(ErrorFieldsMixin.shape);
2679
2767
  var RepoInsightsOutputSchema = z.object({
2768
+ success: z.boolean().optional(),
2680
2769
  owner: z.string().optional(),
2681
2770
  repo: z.string().optional(),
2682
2771
  section: z.string().optional(),
@@ -2858,7 +2947,7 @@ function getGitHubReadTools(context) {
2858
2947
  owner: z.string().optional(),
2859
2948
  repo: z.string().optional(),
2860
2949
  state: z.enum(["open", "closed", "all"]).optional().default("open"),
2861
- limit: z.number().optional().default(20)
2950
+ limit: z.number().max(MAX_QUERY_LIMIT).optional().default(20)
2862
2951
  }).parse(params);
2863
2952
  const resolved = await resolveOwnerRepo(
2864
2953
  context,
@@ -2908,7 +2997,7 @@ function getGitHubReadTools(context) {
2908
2997
  owner: z.string().optional(),
2909
2998
  repo: z.string().optional(),
2910
2999
  state: z.enum(["open", "closed", "all"]).optional().default("open"),
2911
- limit: z.number().optional().default(20)
3000
+ limit: z.number().max(MAX_QUERY_LIMIT).optional().default(20)
2912
3001
  }).parse(params);
2913
3002
  const resolved = await resolveOwnerRepo(
2914
3003
  context,
@@ -2943,7 +3032,11 @@ function getGitHubReadTools(context) {
2943
3032
  inputSchema: z.object({
2944
3033
  issue_number: z.number(),
2945
3034
  owner: z.string().optional().describe("LEAVE EMPTY to auto-detect from git"),
2946
- repo: z.string().optional().describe("LEAVE EMPTY to auto-detect from git")
3035
+ repo: z.string().optional().describe("LEAVE EMPTY to auto-detect from git"),
3036
+ truncate_body: relaxedNumber().optional().default(800).describe(
3037
+ "Max characters for body text (0 = full body, default 800). Reduces token usage."
3038
+ ),
3039
+ include_comments: z.boolean().optional().default(false).describe("Include issue comments (default false). Each comment adds tokens.")
2947
3040
  }),
2948
3041
  outputSchema: GitHubIssueResultOutputSchema,
2949
3042
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
@@ -2952,7 +3045,9 @@ function getGitHubReadTools(context) {
2952
3045
  const input = z.object({
2953
3046
  issue_number: z.number(),
2954
3047
  owner: z.string().optional(),
2955
- repo: z.string().optional()
3048
+ repo: z.string().optional(),
3049
+ truncate_body: z.number().optional().default(800),
3050
+ include_comments: z.boolean().optional().default(false)
2956
3051
  }).parse(params);
2957
3052
  const resolved = await resolveOwnerRepo(context, input, "is this issue from");
2958
3053
  if ("error" in resolved) return resolved.response;
@@ -2975,8 +3070,30 @@ function getGitHubReadTools(context) {
2975
3070
  detectedRepo: resolved.detectedRepo
2976
3071
  };
2977
3072
  }
3073
+ const truncateBody = input.truncate_body;
3074
+ let bodyTruncated = false;
3075
+ let bodyFullLength;
3076
+ if (truncateBody > 0 && issue.body && issue.body.length > truncateBody) {
3077
+ bodyFullLength = issue.body.length;
3078
+ const remaining = issue.body.length - truncateBody;
3079
+ issue.body = issue.body.slice(0, truncateBody) + `
3080
+ [Truncated. Re-run with truncate_body: 0 to view remaining ${String(remaining)} chars]`;
3081
+ bodyTruncated = true;
3082
+ }
3083
+ let comments;
3084
+ if (input.include_comments) {
3085
+ comments = await resolved.github.getIssueComments(
3086
+ resolved.owner,
3087
+ resolved.repo,
3088
+ input.issue_number
3089
+ );
3090
+ }
2978
3091
  return {
2979
- issue,
3092
+ issue: {
3093
+ ...issue,
3094
+ ...bodyTruncated ? { bodyTruncated: true, bodyFullLength } : {}
3095
+ },
3096
+ ...comments ? { comments, commentCount: comments.length } : {},
2980
3097
  owner: resolved.owner,
2981
3098
  repo: resolved.repo,
2982
3099
  detectedOwner: resolved.detectedOwner,
@@ -2995,7 +3112,10 @@ function getGitHubReadTools(context) {
2995
3112
  inputSchema: z.object({
2996
3113
  pr_number: z.number(),
2997
3114
  owner: z.string().optional().describe("LEAVE EMPTY to auto-detect from git"),
2998
- repo: z.string().optional().describe("LEAVE EMPTY to auto-detect from git")
3115
+ repo: z.string().optional().describe("LEAVE EMPTY to auto-detect from git"),
3116
+ truncate_body: relaxedNumber().optional().default(800).describe(
3117
+ "Max characters for body text (0 = full body, default 800). Reduces token usage."
3118
+ )
2999
3119
  }),
3000
3120
  outputSchema: GitHubPRResultOutputSchema,
3001
3121
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
@@ -3004,7 +3124,8 @@ function getGitHubReadTools(context) {
3004
3124
  const input = z.object({
3005
3125
  pr_number: z.number(),
3006
3126
  owner: z.string().optional(),
3007
- repo: z.string().optional()
3127
+ repo: z.string().optional(),
3128
+ truncate_body: z.number().optional().default(800)
3008
3129
  }).parse(params);
3009
3130
  const resolved = await resolveOwnerRepo(context, input, "is this PR from");
3010
3131
  if ("error" in resolved) return resolved.response;
@@ -3027,8 +3148,21 @@ function getGitHubReadTools(context) {
3027
3148
  detectedRepo: resolved.detectedRepo
3028
3149
  };
3029
3150
  }
3151
+ const truncateBody = input.truncate_body;
3152
+ let bodyTruncated = false;
3153
+ let bodyFullLength;
3154
+ if (truncateBody > 0 && pullRequest.body && pullRequest.body.length > truncateBody) {
3155
+ bodyFullLength = pullRequest.body.length;
3156
+ const remaining = pullRequest.body.length - truncateBody;
3157
+ pullRequest.body = pullRequest.body.slice(0, truncateBody) + `
3158
+ [Truncated. Re-run with truncate_body: 0 to view remaining ${String(remaining)} chars]`;
3159
+ bodyTruncated = true;
3160
+ }
3030
3161
  return {
3031
- pullRequest,
3162
+ pullRequest: {
3163
+ ...pullRequest,
3164
+ ...bodyTruncated ? { bodyTruncated: true, bodyFullLength } : {}
3165
+ },
3032
3166
  owner: resolved.owner,
3033
3167
  repo: resolved.repo,
3034
3168
  detectedOwner: resolved.detectedOwner,
@@ -3096,7 +3230,13 @@ function getKanbanTools(context) {
3096
3230
  inputSchema: z.object({
3097
3231
  project_number: z.number().optional().describe("GitHub Project number (optional if repo is registered)"),
3098
3232
  owner: z.string().optional().describe("Project owner - LEAVE EMPTY to auto-detect"),
3099
- repo: z.string().optional().describe("Repository name - LEAVE EMPTY to auto-detect")
3233
+ repo: z.string().optional().describe("Repository name - LEAVE EMPTY to auto-detect"),
3234
+ summary_only: z.boolean().optional().default(false).describe(
3235
+ "Return column summaries only (name + itemCount), omitting individual items. Saves ~80% tokens."
3236
+ ),
3237
+ item_limit: relaxedNumber().optional().default(25).describe(
3238
+ "Maximum items per column (default 25, max 100). Set to 0 for summary_only behavior."
3239
+ )
3100
3240
  }),
3101
3241
  outputSchema: KanbanBoardOutputSchema,
3102
3242
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
@@ -3105,7 +3245,9 @@ function getKanbanTools(context) {
3105
3245
  const input = z.object({
3106
3246
  project_number: z.number().optional(),
3107
3247
  owner: z.string().optional(),
3108
- repo: z.string().optional()
3248
+ repo: z.string().optional(),
3249
+ summary_only: z.boolean().optional().default(false),
3250
+ item_limit: z.number().max(100).optional().default(25)
3109
3251
  }).parse(params);
3110
3252
  const resolved = await resolveOwner(context, input.owner);
3111
3253
  if ("error" in resolved) return resolved.response;
@@ -3144,7 +3286,44 @@ function getKanbanTools(context) {
3144
3286
  recoverable: true
3145
3287
  };
3146
3288
  }
3147
- return board;
3289
+ const summaryOnly = input.summary_only || input.item_limit === 0;
3290
+ if (summaryOnly) {
3291
+ const summaryColumns = board.columns.map((col) => ({
3292
+ status: col.status,
3293
+ statusOptionId: col.statusOptionId,
3294
+ items: [],
3295
+ itemCount: col.items.length
3296
+ }));
3297
+ const itemDirectory = board.columns.flatMap(
3298
+ (col) => col.items.map((item) => ({
3299
+ id: item.id,
3300
+ title: item.title,
3301
+ status: item.status
3302
+ }))
3303
+ );
3304
+ return {
3305
+ ...board,
3306
+ columns: summaryColumns,
3307
+ summaryOnly: true,
3308
+ itemDirectory
3309
+ };
3310
+ }
3311
+ const itemLimit = input.item_limit;
3312
+ const truncatedColumns = board.columns.map((col) => {
3313
+ if (col.items.length <= itemLimit) {
3314
+ return { ...col, itemCount: col.items.length };
3315
+ }
3316
+ return {
3317
+ ...col,
3318
+ items: col.items.slice(0, itemLimit),
3319
+ itemCount: col.items.length,
3320
+ truncated: true
3321
+ };
3322
+ });
3323
+ return {
3324
+ ...board,
3325
+ columns: truncatedColumns
3326
+ };
3148
3327
  } catch (err) {
3149
3328
  return formatHandlerError(err);
3150
3329
  }
@@ -3237,6 +3416,155 @@ function getKanbanTools(context) {
3237
3416
  return formatHandlerError(err);
3238
3417
  }
3239
3418
  }
3419
+ },
3420
+ {
3421
+ name: "add_kanban_item",
3422
+ title: "Add Kanban Item",
3423
+ description: "Add an existing GitHub Issue or Pull Request to a Kanban board (GitHub Project v2). Returns the project item ID on success.",
3424
+ group: "github",
3425
+ inputSchema: z.object({
3426
+ project_number: z.number().optional().describe("GitHub Project number (optional if repo is registered)"),
3427
+ issue_number: z.number().describe("The number of the issue or PR to add"),
3428
+ owner: z.string().optional().describe("Repository owner - LEAVE EMPTY to auto-detect"),
3429
+ repo: z.string().optional().describe("Repository name - LEAVE EMPTY to auto-detect")
3430
+ }),
3431
+ outputSchema: AddKanbanItemOutputSchema,
3432
+ annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: true },
3433
+ handler: async (params) => {
3434
+ try {
3435
+ const input = z.object({
3436
+ project_number: z.number().optional(),
3437
+ issue_number: z.number(),
3438
+ owner: z.string().optional(),
3439
+ repo: z.string().optional()
3440
+ }).parse(params);
3441
+ const resolved = await resolveOwnerRepo(context, input);
3442
+ if ("error" in resolved) return resolved.response;
3443
+ const projectNum = resolveProjectNumber(
3444
+ context,
3445
+ resolved.repo,
3446
+ input.project_number
3447
+ );
3448
+ if (projectNum === void 0) {
3449
+ return {
3450
+ success: false,
3451
+ error: "project_number is required and could not be resolved from registry. Please supply it explicitly.",
3452
+ code: "VALIDATION_ERROR",
3453
+ category: "validation",
3454
+ recoverable: true
3455
+ };
3456
+ }
3457
+ const issueDetails = await resolved.github.getIssue(
3458
+ resolved.owner,
3459
+ resolved.repo,
3460
+ input.issue_number
3461
+ );
3462
+ if (!issueDetails?.nodeId) {
3463
+ return {
3464
+ success: false,
3465
+ error: `Issue or PR #${String(input.issue_number)} not found or lacks a nodeId`,
3466
+ code: "RESOURCE_NOT_FOUND",
3467
+ category: "resource",
3468
+ recoverable: true
3469
+ };
3470
+ }
3471
+ const board = await resolved.github.getProjectKanban(
3472
+ resolved.owner,
3473
+ projectNum,
3474
+ resolved.repo
3475
+ );
3476
+ if (!board) {
3477
+ return {
3478
+ success: false,
3479
+ error: `Project #${String(projectNum)} not found`,
3480
+ code: "RESOURCE_NOT_FOUND",
3481
+ category: "resource",
3482
+ recoverable: true
3483
+ };
3484
+ }
3485
+ const result = await resolved.github.addProjectItem(
3486
+ board.projectId,
3487
+ issueDetails.nodeId
3488
+ );
3489
+ return {
3490
+ success: result.success,
3491
+ itemId: result.itemId,
3492
+ projectNumber: projectNum,
3493
+ message: result.success ? `Added Issue/PR #${String(input.issue_number)} to Project #${projectNum}` : void 0,
3494
+ error: result.error
3495
+ };
3496
+ } catch (err) {
3497
+ return formatHandlerError(err);
3498
+ }
3499
+ }
3500
+ },
3501
+ {
3502
+ name: "delete_kanban_item",
3503
+ title: "Delete Kanban Item",
3504
+ description: "Remove an item from a GitHub Kanban board cleanly. This untethers it from the project but does NOT close or delete the underlying Issue/PR.",
3505
+ group: "github",
3506
+ inputSchema: z.object({
3507
+ project_number: z.number().optional().describe("GitHub Project number (optional if repo is registered)"),
3508
+ item_id: z.string().describe("The project item ID to remove (from get_kanban_board)"),
3509
+ owner: z.string().optional().describe("Project owner - LEAVE EMPTY to auto-detect"),
3510
+ repo: z.string().optional().describe("Repository name - LEAVE EMPTY to auto-detect")
3511
+ }),
3512
+ outputSchema: DeleteKanbanItemOutputSchema,
3513
+ annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
3514
+ handler: async (params) => {
3515
+ try {
3516
+ const input = z.object({
3517
+ project_number: z.number().optional(),
3518
+ item_id: z.string(),
3519
+ owner: z.string().optional(),
3520
+ repo: z.string().optional()
3521
+ }).parse(params);
3522
+ const resolved = await resolveOwner(context, input.owner);
3523
+ if ("error" in resolved) return resolved.response;
3524
+ const effectiveRepo = input.repo ?? resolved.repo;
3525
+ const projectNum = resolveProjectNumber(
3526
+ context,
3527
+ effectiveRepo,
3528
+ input.project_number
3529
+ );
3530
+ if (projectNum === void 0) {
3531
+ return {
3532
+ success: false,
3533
+ error: "project_number is required and could not be resolved from registry. Please supply it explicitly.",
3534
+ code: "VALIDATION_ERROR",
3535
+ category: "validation",
3536
+ recoverable: true
3537
+ };
3538
+ }
3539
+ const board = await resolved.github.getProjectKanban(
3540
+ resolved.owner,
3541
+ projectNum,
3542
+ effectiveRepo
3543
+ );
3544
+ if (!board) {
3545
+ return {
3546
+ success: false,
3547
+ error: `Project #${String(projectNum)} not found`,
3548
+ code: "RESOURCE_NOT_FOUND",
3549
+ category: "resource",
3550
+ recoverable: true
3551
+ };
3552
+ }
3553
+ const result = await resolved.github.deleteProjectItem(
3554
+ board.projectId,
3555
+ input.item_id
3556
+ );
3557
+ return {
3558
+ success: result.success,
3559
+ itemId: input.item_id,
3560
+ projectNumber: projectNum,
3561
+ message: result.success ? `Deleted item ${input.item_id} from Project #${projectNum}` : void 0,
3562
+ error: result.error
3563
+ };
3564
+ } catch (err) {
3565
+ return formatHandlerError(err);
3566
+ }
3567
+ }
3240
3568
  }
3241
3569
  ];
3242
3570
  }
@@ -3294,7 +3622,11 @@ function getGitHubIssueTools(context) {
3294
3622
  if (!issue) {
3295
3623
  return {
3296
3624
  success: false,
3297
- error: "Failed to create GitHub issue. Check GITHUB_TOKEN permissions."
3625
+ error: "Failed to create GitHub issue. Check GITHUB_TOKEN permissions.",
3626
+ code: "API_ERROR",
3627
+ category: "api",
3628
+ suggestion: "Verify GitHub token has write access to issues.",
3629
+ recoverable: true
3298
3630
  };
3299
3631
  }
3300
3632
  const projectNumber = resolveProjectNumber(
@@ -3453,13 +3785,21 @@ Description: ${input.body.slice(0, 200)}${input.body.length > 200 ? "..." : ""}`
3453
3785
  if (!issueDetails) {
3454
3786
  return {
3455
3787
  success: false,
3456
- error: `Issue #${String(input.issue_number)} not found`
3788
+ error: `Issue #${String(input.issue_number)} not found`,
3789
+ code: "RESOURCE_NOT_FOUND",
3790
+ category: "resource",
3791
+ suggestion: "Verify the issue number exists in this repository.",
3792
+ recoverable: true
3457
3793
  };
3458
3794
  }
3459
3795
  if (issueDetails.state === "CLOSED") {
3460
3796
  return {
3461
- success: false,
3462
- error: `Issue #${String(input.issue_number)} is already closed`
3797
+ ...formatHandlerError(
3798
+ new ValidationError(
3799
+ `Issue #${String(input.issue_number)} is already closed`
3800
+ )
3801
+ ),
3802
+ success: false
3463
3803
  };
3464
3804
  }
3465
3805
  const result = await resolved.github.closeIssue(
@@ -3471,7 +3811,11 @@ Description: ${input.body.slice(0, 200)}${input.body.length > 200 ? "..." : ""}`
3471
3811
  if (!result) {
3472
3812
  return {
3473
3813
  success: false,
3474
- error: "Failed to close GitHub issue. Check GITHUB_TOKEN permissions."
3814
+ error: "Failed to close GitHub issue. Check GITHUB_TOKEN permissions.",
3815
+ code: "API_ERROR",
3816
+ category: "api",
3817
+ suggestion: "Verify GitHub token has write access to issues.",
3818
+ recoverable: true
3475
3819
  };
3476
3820
  }
3477
3821
  let kanbanResult;
@@ -3630,10 +3974,12 @@ function isResourceError(result) {
3630
3974
  }
3631
3975
  var DEFAULT_BRIEFING_CONFIG = {
3632
3976
  entryCount: 3,
3977
+ summaryCount: 1,
3633
3978
  includeTeam: false,
3634
3979
  issueCount: 0,
3635
3980
  prCount: 0,
3636
3981
  prStatusBreakdown: false,
3982
+ milestoneCount: 3,
3637
3983
  workflowCount: 0,
3638
3984
  workflowStatusBreakdown: false,
3639
3985
  copilotReviews: false
@@ -4013,6 +4359,7 @@ function getGitHubInsightsTools(context) {
4013
4359
  const repo = resolved.repo;
4014
4360
  const section = input.sections;
4015
4361
  const result = {
4362
+ success: true,
4016
4363
  owner,
4017
4364
  repo,
4018
4365
  section
@@ -4373,7 +4720,7 @@ var TeamCreateEntrySchemaMcp = z.object({
4373
4720
  author: z.string().optional()
4374
4721
  });
4375
4722
  var TeamGetRecentSchema = z.object({
4376
- limit: z.number().max(500).optional().default(10)
4723
+ limit: z.number().min(1).max(500).optional().default(10)
4377
4724
  });
4378
4725
  var TeamGetRecentSchemaMcp = z.object({
4379
4726
  limit: relaxedNumber().optional().default(10)
@@ -4600,6 +4947,10 @@ var TeamSemanticSearchSchema = z.object({
4600
4947
  entry_id: z.number().optional().describe("Find entries related to this entry ID"),
4601
4948
  limit: z.number().max(500).optional().default(10),
4602
4949
  similarity_threshold: z.number().optional().default(0.25),
4950
+ tags: z.array(z.string()).optional(),
4951
+ entry_type: z.enum(ENTRY_TYPES).optional(),
4952
+ start_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional(),
4953
+ end_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional(),
4603
4954
  hint_on_empty: z.boolean().optional().default(true).describe("Include hint when no results found (default: true)")
4604
4955
  });
4605
4956
  var TeamSemanticSearchSchemaMcp = z.object({
@@ -4607,6 +4958,10 @@ var TeamSemanticSearchSchemaMcp = z.object({
4607
4958
  entry_id: relaxedNumber().optional().describe("Find entries related to this entry ID"),
4608
4959
  limit: relaxedNumber().optional().default(10),
4609
4960
  similarity_threshold: relaxedNumber().optional().default(0.25),
4961
+ tags: z.array(z.string()).optional(),
4962
+ entry_type: z.string().optional(),
4963
+ start_date: z.string().optional(),
4964
+ end_date: z.string().optional(),
4610
4965
  hint_on_empty: z.boolean().optional().default(true).describe("Include hint when no results found (default: true)")
4611
4966
  });
4612
4967
  var TeamAddToVectorIndexSchema = z.object({
@@ -4857,11 +5212,12 @@ function getTeamSearchTools(context) {
4857
5212
  return { ...TEAM_DB_ERROR_RESPONSE };
4858
5213
  }
4859
5214
  const { query, tags, limit } = TeamSearchSchema.parse(params);
5215
+ const searchLimit = tags && tags.length > 0 ? Math.max(limit * 5, 50) : limit;
4860
5216
  let entries;
4861
5217
  if (query) {
4862
- entries = teamDb.searchEntries(query, { limit });
5218
+ entries = teamDb.searchEntries(query, { limit: searchLimit });
4863
5219
  } else {
4864
- entries = teamDb.getRecentEntries(limit);
5220
+ entries = teamDb.getRecentEntries(searchLimit);
4865
5221
  }
4866
5222
  if (tags && tags.length > 0) {
4867
5223
  const entryIds = entries.map((e) => e.id);
@@ -4887,6 +5243,7 @@ function getTeamSearchTools(context) {
4887
5243
  });
4888
5244
  }
4889
5245
  }
5246
+ entries = entries.slice(0, limit);
4890
5247
  const authorMap = batchFetchAuthors(
4891
5248
  teamDb,
4892
5249
  entries.map((e) => e.id)
@@ -4895,7 +5252,7 @@ function getTeamSearchTools(context) {
4895
5252
  ...e,
4896
5253
  author: authorMap.get(e.id) ?? null
4897
5254
  }));
4898
- return { entries: enriched, count: enriched.length };
5255
+ return { success: true, entries: enriched, count: enriched.length };
4899
5256
  } catch (err) {
4900
5257
  return formatHandlerError(err);
4901
5258
  }
@@ -4938,7 +5295,7 @@ function getTeamSearchTools(context) {
4938
5295
  ...e,
4939
5296
  author: authorMap.get(e.id) ?? null
4940
5297
  }));
4941
- return { entries: enriched, count: enriched.length };
5298
+ return { success: true, entries: enriched, count: enriched.length };
4942
5299
  } catch (err) {
4943
5300
  return formatHandlerError(err);
4944
5301
  }
@@ -5347,6 +5704,17 @@ function getTeamRelationshipTools(context) {
5347
5704
  const { entry_id, tag, depth } = TeamVisualizeRelationshipsSchema.parse(params);
5348
5705
  let entryIds = [];
5349
5706
  if (entry_id !== void 0) {
5707
+ const fromEntry = teamDb.getEntryById(entry_id);
5708
+ if (!fromEntry) {
5709
+ return {
5710
+ success: false,
5711
+ error: `Team entry ${String(entry_id)} not found`,
5712
+ code: "RESOURCE_NOT_FOUND",
5713
+ category: "resource",
5714
+ suggestion: "Verify the team entry ID and try again",
5715
+ recoverable: true
5716
+ };
5717
+ }
5350
5718
  entryIds = [entry_id];
5351
5719
  const visited = new Set(entryIds);
5352
5720
  let frontier = [...entryIds];
@@ -5792,17 +6160,19 @@ function getTeamVectorTools(context) {
5792
6160
  count: 0
5793
6161
  };
5794
6162
  }
6163
+ const hasFilters = input.tags !== void 0 || input.entry_type !== void 0 || input.start_date !== void 0 || input.end_date !== void 0;
6164
+ const internalLimit = hasFilters ? Math.min(Math.max(input.limit * 10, 100), 1e3) : input.limit * 2;
5795
6165
  let results;
5796
6166
  if (input.entry_id !== void 0) {
5797
6167
  results = await teamVectorManager.searchByEntryId(
5798
6168
  input.entry_id,
5799
- input.limit ?? 10,
6169
+ internalLimit,
5800
6170
  input.similarity_threshold ?? 0.25
5801
6171
  );
5802
6172
  } else {
5803
6173
  results = await teamVectorManager.search(
5804
6174
  input.query ?? "",
5805
- input.limit ?? 10,
6175
+ internalLimit,
5806
6176
  input.similarity_threshold ?? 0.25
5807
6177
  );
5808
6178
  }
@@ -5814,12 +6184,24 @@ function getTeamVectorTools(context) {
5814
6184
  if (!entry) return null;
5815
6185
  if (input.entry_id !== void 0 && entry.id === input.entry_id)
5816
6186
  return null;
6187
+ if (!passMetadataFilters(
6188
+ entry,
6189
+ {
6190
+ tags: input.tags,
6191
+ entryType: input.entry_type,
6192
+ startDate: input.start_date,
6193
+ endDate: input.end_date
6194
+ },
6195
+ teamDb
6196
+ )) {
6197
+ return null;
6198
+ }
5817
6199
  return {
5818
6200
  ...entry,
5819
6201
  author: authorMap.get(r.entryId) ?? null,
5820
6202
  similarity: Math.round(r.score * 100) / 100
5821
6203
  };
5822
- }).filter((e) => e !== null);
6204
+ }).filter((e) => e !== null).slice(0, input.limit);
5823
6205
  const stats = teamVectorManager.getStats();
5824
6206
  const isIndexEmpty = stats.itemCount === 0;
5825
6207
  const includeHint = input.hint_on_empty ?? true;
@@ -5827,6 +6209,7 @@ function getTeamVectorTools(context) {
5827
6209
  const allNoise = entries.length > 0 && bestSimilarity < QUALITY_FLOOR;
5828
6210
  const hint = isIndexEmpty && includeHint ? "No entries in team vector index. Use team_rebuild_vector_index to index existing entries." : entries.length === 0 && includeHint ? `No entries matched your query above the similarity threshold (${String(input.similarity_threshold ?? 0.25)}). Try lowering similarity_threshold (e.g., 0.15) for broader matches.` : allNoise ? `Results may be noise \u2014 best similarity (${String(bestSimilarity)}) is below quality floor (${String(QUALITY_FLOOR)}). Try a more specific query or raise similarity_threshold to filter weak matches.` : void 0;
5829
6211
  return {
6212
+ success: true,
5830
6213
  query: input.query,
5831
6214
  ...input.entry_id !== void 0 ? { entryId: input.entry_id } : {},
5832
6215
  entries,
@@ -6021,6 +6404,8 @@ var METHOD_ALIASES = {
6021
6404
  context: "getGithubContext",
6022
6405
  kanban: "getKanbanBoard",
6023
6406
  moveKanban: "moveKanbanItem",
6407
+ addKanban: "addKanbanItem",
6408
+ deleteKanban: "deleteKanbanItem",
6024
6409
  milestones: "getGithubMilestones",
6025
6410
  milestone: "getGithubMilestone",
6026
6411
  createMilestone: "createGithubMilestone",
@@ -6095,6 +6480,7 @@ var GROUP_EXAMPLES = {
6095
6480
  'mj.github.getGithubIssues({ state: "open" })',
6096
6481
  'mj.github.getGithubPrs({ state: "open" })',
6097
6482
  "mj.github.getKanbanBoard({ project_number: 1 })",
6483
+ "mj.github.addKanbanItem({ project_number: 1, issue_number: 123 })",
6098
6484
  "mj.github.getGithubMilestones()",
6099
6485
  "mj.github.getRepoInsights()"
6100
6486
  ],
@@ -6143,7 +6529,9 @@ var POSITIONAL_PARAM_MAP = {
6143
6529
  getGithubIssue: "issue_number",
6144
6530
  getGithubPr: "pr_number",
6145
6531
  getKanbanBoard: "project_number",
6146
- moveKanbanItem: ["item_id", "status"],
6532
+ moveKanbanItem: ["item_id", "target_status"],
6533
+ addKanbanItem: "issue_number",
6534
+ deleteKanbanItem: "item_id",
6147
6535
  getGithubMilestone: "milestone_number",
6148
6536
  createGithubMilestone: "title",
6149
6537
  updateGithubMilestone: "milestone_number",
@@ -6377,8 +6765,8 @@ var DEFAULT_SECURITY_CONFIG = {
6377
6765
  maxCodeLength: 50 * 1024,
6378
6766
  // 50KB
6379
6767
  maxExecutionsPerMinute: 60,
6380
- maxResultSize: 10 * 1024 * 1024,
6381
- // 10MB
6768
+ maxResultSize: 100 * 1024,
6769
+ // 100KB — context window protection (configurable via CODE_MODE_MAX_RESULT_SIZE)
6382
6770
  blockedPatterns: [
6383
6771
  /\brequire\s*\(/,
6384
6772
  // No require()
@@ -6493,9 +6881,12 @@ var CodeModeSecurityManager = class {
6493
6881
  const errors = [];
6494
6882
  try {
6495
6883
  const serialized = JSON.stringify(result);
6496
- if (Buffer.byteLength(serialized, "utf-8") > this.config.maxResultSize) {
6884
+ const actualBytes = Buffer.byteLength(serialized, "utf-8");
6885
+ if (actualBytes > this.config.maxResultSize) {
6886
+ const actualKb = Math.ceil(actualBytes / 1024);
6887
+ const limitKb = Math.ceil(this.config.maxResultSize / 1024);
6497
6888
  errors.push(
6498
- `Result exceeds maximum size of ${String(this.config.maxResultSize)} bytes`
6889
+ `Result exceeds maximum size of ${String(limitKb)} KB (${String(actualKb)} KB returned). Extract specific fields or aggregate data before returning. Example: instead of \`return await mj.github.getKanbanBoard(5)\`, use \`const b = await mj.github.getKanbanBoard(5); return { columns: b.columns?.length ?? 0, totalItems: b.totalItems }\``
6499
6890
  );
6500
6891
  }
6501
6892
  } catch (error) {
@@ -6871,7 +7262,12 @@ var ExecuteCodeSchemaMcp = z.object({
6871
7262
  var securityManager = null;
6872
7263
  var sandboxPool = null;
6873
7264
  function getSecurityManager() {
6874
- securityManager ??= new CodeModeSecurityManager();
7265
+ if (!securityManager) {
7266
+ const envMaxSize = process.env["CODE_MODE_MAX_RESULT_SIZE"];
7267
+ const parsedMaxSize = envMaxSize && /^\d+$/.test(envMaxSize) ? parseInt(envMaxSize, 10) : void 0;
7268
+ const overrides = parsedMaxSize !== void 0 && Number.isFinite(parsedMaxSize) && Number.isInteger(parsedMaxSize) && parsedMaxSize > 0 ? { maxResultSize: parsedMaxSize } : void 0;
7269
+ securityManager = new CodeModeSecurityManager(overrides);
7270
+ }
6875
7271
  return securityManager;
6876
7272
  }
6877
7273
  function getSandboxPool() {
@@ -7343,6 +7739,8 @@ var TOOL_GROUPS = {
7343
7739
  "get_github_context",
7344
7740
  "get_kanban_board",
7345
7741
  "move_kanban_item",
7742
+ "add_kanban_item",
7743
+ "delete_kanban_item",
7346
7744
  "create_github_issue_with_entry",
7347
7745
  "close_github_issue_with_entry",
7348
7746
  "get_github_milestones",