memory-journal-mcp 7.0.1 → 7.2.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 +75 -66
  2. package/dist/{chunk-6J4RPJ4I.js → chunk-GR4T3SRW.js} +146 -105
  3. package/dist/{chunk-ARLH46WS.js → chunk-IWKLHSPU.js} +89 -3
  4. package/dist/{chunk-2BJHLTYP.js → chunk-ORV7ZZOE.js} +1086 -86
  5. package/dist/cli.js +30 -4
  6. package/dist/github-integration-2TFMXHIJ.js +1 -0
  7. package/dist/index.d.ts +6 -2
  8. package/dist/index.js +3 -3
  9. package/dist/{tools-FFFGXIKN.js → tools-CXR2FEB2.js} +2 -2
  10. package/package.json +2 -2
  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-PDRLXKGM.js +0 -1
  124. package/skills/github-commander/workflows/full-audit.md +0 -134
@@ -1,14 +1,15 @@
1
1
  import { transformAutoReturn } from './chunk-OKOVZ5QE.js';
2
- import { GitHubIntegration, resolveAuthor, logger, MemoryJournalMcpError, matchSuggestion, ConfigurationError } from './chunk-ARLH46WS.js';
2
+ import { GitHubIntegration, resolveAuthor, logger, ValidationError, ResourceNotFoundError, assertSafeDirectoryPath, MemoryJournalMcpError, matchSuggestion, ConfigurationError } from './chunk-IWKLHSPU.js';
3
3
  import { z, ZodError } from 'zod';
4
+ import { open, stat, mkdir, rename, appendFile, readdir, readFile } from 'fs/promises';
5
+ import { tmpdir } from 'os';
6
+ import * as path from 'path';
7
+ import { dirname, resolve, sep, join } from 'path';
4
8
  import * as vm from 'vm';
5
9
  import { MessageChannel, Worker } from 'worker_threads';
6
10
  import * as crypto2 from 'crypto';
7
11
  import { fileURLToPath } from 'url';
8
- import * as path from 'path';
9
- import { dirname } from 'path';
10
12
  import { performance as performance$1 } from 'perf_hooks';
11
- import { open, stat, mkdir, rename, appendFile } from 'fs/promises';
12
13
 
13
14
  function formatZodError(error) {
14
15
  return error.issues.map((issue) => {
@@ -58,7 +59,7 @@ async function resolveIssueUrl(context, projectNumber, issueNumber, existingUrl)
58
59
  ([_, v]) => v.project_number === projectNumber
59
60
  );
60
61
  if (entry) {
61
- const { GitHubIntegration: GitHubIntegration2 } = await import('./github-integration-PDRLXKGM.js');
62
+ const { GitHubIntegration: GitHubIntegration2 } = await import('./github-integration-2TFMXHIJ.js');
62
63
  const targetGithub = new GitHubIntegration2(entry[1].path);
63
64
  const repoInfo = await targetGithub.getRepoInfo();
64
65
  if (repoInfo.owner && repoInfo.repo) {
@@ -113,6 +114,7 @@ var SIGNIFICANCE_TYPES = [
113
114
  "release"
114
115
  ];
115
116
  var MAX_CONTENT_LENGTH = 5e4;
117
+ var MAX_QUERY_LIMIT = 500;
116
118
  var DATE_MIN_SENTINEL = "1970-01-01";
117
119
  var DATE_MAX_SENTINEL = "2999-12-31";
118
120
  var DATE_FORMAT_REGEX = /^\d{4}-\d{2}-\d{2}$/;
@@ -214,7 +216,7 @@ var GetEntryByIdSchemaMcp = z.object({
214
216
  include_relationships: z.boolean().optional().default(true)
215
217
  });
216
218
  var GetRecentEntriesSchema = z.object({
217
- limit: z.number().max(500).optional().default(5),
219
+ limit: z.number().min(1).max(MAX_QUERY_LIMIT).optional().default(5),
218
220
  is_personal: z.boolean().optional()
219
221
  });
220
222
  var GetRecentEntriesSchemaMcp = z.object({
@@ -451,7 +453,6 @@ function getCoreTools(context) {
451
453
  }
452
454
 
453
455
  // src/handlers/tools/search/helpers.ts
454
- var MAX_QUERY_LIMIT = 500;
455
456
  var DEDUP_KEY_LENGTH = 200;
456
457
  function calcPerDbLimit(limit, hasTeamDb) {
457
458
  return hasTeamDb ? Math.min(limit * 2, MAX_QUERY_LIMIT) : limit;
@@ -729,11 +730,23 @@ function getSearchTools(context) {
729
730
  handler: async (params) => {
730
731
  try {
731
732
  const input = SearchEntriesSchema.parse(params);
732
- const query = input.query || "";
733
+ const query = input.query?.trim() || "";
733
734
  const mode = input.mode;
734
- const { resolvedMode, isAuto } = resolveSearchMode(mode, query);
735
735
  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;
736
- const effectiveMode = !query && !hasFilters ? "fts" : resolvedMode;
736
+ if (!query && !hasFilters) {
737
+ return {
738
+ ...formatHandlerError(
739
+ new ValidationError(
740
+ "Search requires either a query string or at least one filter",
741
+ { suggestion: "Provide a search query or use get_recent_entries instead" }
742
+ )
743
+ ),
744
+ entries: [],
745
+ count: 0
746
+ };
747
+ }
748
+ const { resolvedMode, isAuto } = resolveSearchMode(mode, query);
749
+ const effectiveMode = resolvedMode;
737
750
  const searchOptions = {
738
751
  limit: input.limit,
739
752
  isPersonal: input.is_personal,
@@ -1261,12 +1274,23 @@ var LinkEntriesSchemaMcp = z.object({
1261
1274
  var VisualizeInputSchema = z.object({
1262
1275
  entry_id: z.number().optional().describe("Specific entry ID to visualize (shows connected entries)"),
1263
1276
  tags: z.array(z.string()).optional().describe("Filter entries by tags"),
1277
+ relationship_type: z.enum([
1278
+ "evolves_from",
1279
+ "references",
1280
+ "implements",
1281
+ "clarifies",
1282
+ "response_to",
1283
+ "blocked_by",
1284
+ "resolved",
1285
+ "caused"
1286
+ ]).optional().describe("Filter to show only this relationship type"),
1264
1287
  depth: z.number().min(1).max(3).optional().default(2).describe("Relationship traversal depth"),
1265
1288
  limit: z.number().max(500).optional().default(20).describe("Maximum entries to include")
1266
1289
  });
1267
1290
  var VisualizeInputSchemaMcp = z.object({
1268
1291
  entry_id: relaxedNumber().optional().describe("Specific entry ID to visualize (shows connected entries)"),
1269
1292
  tags: z.array(z.string()).optional().describe("Filter entries by tags"),
1293
+ relationship_type: z.string().optional().describe("Filter to show only this relationship type (e.g., blocked_by, implements)"),
1270
1294
  depth: relaxedNumber().optional().default(2).describe("Relationship traversal depth"),
1271
1295
  limit: relaxedNumber().optional().default(20).describe("Maximum entries to include")
1272
1296
  });
@@ -1308,10 +1332,10 @@ function getRelationshipTools(context) {
1308
1332
  const input = LinkEntriesSchema.parse(params);
1309
1333
  if (input.from_entry_id === input.to_entry_id) {
1310
1334
  return {
1311
- success: false,
1312
- error: "Cannot link an entry to itself",
1313
- code: "VALIDATION_ERROR",
1314
- category: "validation"
1335
+ ...formatHandlerError(
1336
+ new ValidationError("Cannot link an entry to itself")
1337
+ ),
1338
+ success: false
1315
1339
  };
1316
1340
  }
1317
1341
  const existingRelationships = db.getRelationships(input.from_entry_id);
@@ -1346,12 +1370,13 @@ function getRelationshipTools(context) {
1346
1370
  const isFkError = errMsg.includes("FOREIGN KEY constraint failed");
1347
1371
  if (isFkError) {
1348
1372
  return {
1349
- success: false,
1350
- error: `One or both entries not found (from: ${String(input.from_entry_id)}, to: ${String(input.to_entry_id)})`,
1351
- code: "RESOURCE_NOT_FOUND",
1352
- category: "resource",
1353
- suggestion: "Verify both entry IDs exist before linking",
1354
- recoverable: true
1373
+ ...formatHandlerError(
1374
+ new ResourceNotFoundError(
1375
+ "Entry",
1376
+ `from: ${String(input.from_entry_id)}, to: ${String(input.to_entry_id)}`
1377
+ )
1378
+ ),
1379
+ success: false
1355
1380
  };
1356
1381
  }
1357
1382
  }
@@ -1463,15 +1488,18 @@ function getRelationshipTools(context) {
1463
1488
  }
1464
1489
  const entryIds = Object.keys(entries).map(Number);
1465
1490
  const placeholders = entryIds.map(() => "?").join(",");
1466
- const relsResult = db.executeRawQuery(
1467
- `
1491
+ let relsQuery = `
1468
1492
  SELECT from_entry_id, to_entry_id, relationship_type
1469
1493
  FROM relationships
1470
1494
  WHERE from_entry_id IN (${placeholders})
1471
1495
  AND to_entry_id IN (${placeholders})
1472
- `,
1473
- [...entryIds, ...entryIds]
1474
- );
1496
+ `;
1497
+ const relsParams = [...entryIds, ...entryIds];
1498
+ if (input.relationship_type) {
1499
+ relsQuery += " AND relationship_type = ?";
1500
+ relsParams.push(input.relationship_type);
1501
+ }
1502
+ const relsResult = db.executeRawQuery(relsQuery, relsParams);
1475
1503
  const relationships = relsResult[0]?.values ?? [];
1476
1504
  const MERMAID_CONTENT_PREVIEW_LENGTH = 40;
1477
1505
  let mermaid = "```mermaid\\ngraph TD\\n";
@@ -1554,8 +1582,345 @@ async function sendProgress(ctx, progress, total, message) {
1554
1582
  } catch {
1555
1583
  }
1556
1584
  }
1585
+ var FrontmatterSchema = z.object({
1586
+ mj_id: z.number().int().positive().optional(),
1587
+ entry_type: z.string().optional(),
1588
+ author: z.string().optional(),
1589
+ tags: z.array(z.string()).optional(),
1590
+ timestamp: z.string().optional(),
1591
+ significance: z.string().optional(),
1592
+ relationships: z.array(
1593
+ z.object({
1594
+ type: z.string(),
1595
+ target_id: z.number().int().positive()
1596
+ })
1597
+ ).optional(),
1598
+ source: z.string().optional()
1599
+ });
1600
+ function serializeFrontmatter(data) {
1601
+ if (data === null || data === void 0) return "";
1602
+ const lines = ["---"];
1603
+ if (data.mj_id !== void 0) lines.push(`mj_id: ${String(data.mj_id)}`);
1604
+ if (data.entry_type !== void 0) lines.push(`entry_type: ${data.entry_type}`);
1605
+ if (data.author !== void 0) lines.push(`author: ${data.author}`);
1606
+ if (data.timestamp !== void 0) lines.push(`timestamp: ${data.timestamp}`);
1607
+ if (data.significance !== void 0) lines.push(`significance: ${data.significance}`);
1608
+ if (data.source !== void 0) lines.push(`source: ${data.source}`);
1609
+ if (data.tags && data.tags.length > 0) {
1610
+ lines.push("tags:");
1611
+ for (const tag of data.tags) {
1612
+ lines.push(` - ${tag}`);
1613
+ }
1614
+ }
1615
+ if (data.relationships !== void 0 && data.relationships.length > 0) {
1616
+ lines.push("relationships:");
1617
+ for (const rel of data.relationships) {
1618
+ lines.push(` - type: ${rel.type}`);
1619
+ lines.push(` target_id: ${String(rel.target_id)}`);
1620
+ }
1621
+ }
1622
+ if (lines.length === 1) {
1623
+ return "";
1624
+ }
1625
+ lines.push("---");
1626
+ return lines.join("\n") + "\n";
1627
+ }
1628
+ function parseFrontmatter(content) {
1629
+ const lines = content.split("\n");
1630
+ if (lines[0]?.trim() !== "---") {
1631
+ return { metadata: {}, body: content };
1632
+ }
1633
+ let closingIndex = -1;
1634
+ for (let i = 1; i < lines.length; i++) {
1635
+ if (lines[i]?.trim() === "---") {
1636
+ closingIndex = i;
1637
+ break;
1638
+ }
1639
+ }
1640
+ if (closingIndex === -1) {
1641
+ return { metadata: {}, body: content };
1642
+ }
1643
+ const fmLines = lines.slice(1, closingIndex);
1644
+ const raw = {};
1645
+ let currentKey = null;
1646
+ let currentArray = null;
1647
+ let isRelationshipArray = false;
1648
+ let currentRelObj = null;
1649
+ for (const line of fmLines) {
1650
+ if (line.trim() === "") continue;
1651
+ if (/^\s{2,}- /.test(line)) {
1652
+ const itemContent = line.replace(/^\s{2,}- /, "");
1653
+ if (isRelationshipArray && currentKey === "relationships") {
1654
+ const colonIdx2 = itemContent.indexOf(":");
1655
+ if (colonIdx2 !== -1) {
1656
+ if (currentRelObj !== null) {
1657
+ currentArray?.push(currentRelObj);
1658
+ }
1659
+ currentRelObj = {};
1660
+ const key = itemContent.slice(0, colonIdx2).trim();
1661
+ const value = itemContent.slice(colonIdx2 + 1).trim();
1662
+ currentRelObj[key] = parseScalar(value);
1663
+ }
1664
+ } else if (currentArray !== null) {
1665
+ currentArray.push(itemContent.trim());
1666
+ }
1667
+ continue;
1668
+ }
1669
+ if (/^\s{4,}\w/.test(line) && currentRelObj !== null) {
1670
+ const colonIdx2 = line.indexOf(":");
1671
+ if (colonIdx2 !== -1) {
1672
+ const key = line.slice(0, colonIdx2).trim();
1673
+ const value = line.slice(colonIdx2 + 1).trim();
1674
+ currentRelObj[key] = parseScalar(value);
1675
+ }
1676
+ continue;
1677
+ }
1678
+ if (currentKey !== null && currentArray !== null) {
1679
+ if (isRelationshipArray && currentRelObj !== null) {
1680
+ currentArray.push(currentRelObj);
1681
+ currentRelObj = null;
1682
+ }
1683
+ raw[currentKey] = currentArray;
1684
+ currentArray = null;
1685
+ currentKey = null;
1686
+ isRelationshipArray = false;
1687
+ }
1688
+ const colonIdx = line.indexOf(":");
1689
+ if (colonIdx !== -1) {
1690
+ const key = line.slice(0, colonIdx).trim();
1691
+ const value = line.slice(colonIdx + 1).trim();
1692
+ if (value === "") {
1693
+ currentKey = key;
1694
+ currentArray = [];
1695
+ isRelationshipArray = key === "relationships";
1696
+ } else {
1697
+ raw[key] = parseScalar(value);
1698
+ }
1699
+ }
1700
+ }
1701
+ if (currentKey !== null && currentArray !== null) {
1702
+ if (isRelationshipArray && currentRelObj !== null) {
1703
+ currentArray.push(currentRelObj);
1704
+ }
1705
+ raw[currentKey] = currentArray;
1706
+ }
1707
+ const parseResult = FrontmatterSchema.safeParse(raw);
1708
+ if (!parseResult.success) {
1709
+ const issues = parseResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
1710
+ throw new Error(`Invalid frontmatter: ${issues.join("; ")}`);
1711
+ }
1712
+ const body = lines.slice(closingIndex + 1).join("\n").replace(/^\n/, "");
1713
+ return { metadata: parseResult.data, body };
1714
+ }
1715
+ function parseScalar(value) {
1716
+ if (/^-?\d+$/.test(value)) return parseInt(value, 10);
1717
+ if (/^-?\d+\.\d+$/.test(value)) return parseFloat(value);
1718
+ if (value === "true") return true;
1719
+ if (value === "false") return false;
1720
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1721
+ return value.slice(1, -1);
1722
+ }
1723
+ return value;
1724
+ }
1725
+ function generateSlug(content) {
1726
+ const slug = content.slice(0, 50).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
1727
+ return slug || "untitled";
1728
+ }
1729
+ function generateFilename(id, content) {
1730
+ return `${String(id)}-${generateSlug(content)}.md`;
1731
+ }
1732
+ async function exportEntriesToMarkdown(entries, outputDir, db) {
1733
+ const resolvedOutputDir = resolve(outputDir);
1734
+ const resolvedTmpDir = resolve(tmpdir());
1735
+ const tmpPrefix = resolvedTmpDir.endsWith(sep) ? resolvedTmpDir : resolvedTmpDir + sep;
1736
+ if (resolvedOutputDir === resolvedTmpDir || resolvedOutputDir.startsWith(tmpPrefix)) {
1737
+ throw new Error("Refusing to export markdown files into the OS temporary directory");
1738
+ }
1739
+ await mkdir(resolvedOutputDir, { recursive: true });
1740
+ const files = [];
1741
+ let skipped = 0;
1742
+ for (const entry of entries) {
1743
+ if (!entry.content.trim()) {
1744
+ skipped++;
1745
+ continue;
1746
+ }
1747
+ const fmData = {
1748
+ mj_id: entry.id,
1749
+ entry_type: entry.entryType,
1750
+ timestamp: entry.timestamp,
1751
+ source: "memory-journal-mcp"
1752
+ };
1753
+ if (entry.tags !== void 0 && entry.tags.length > 0) {
1754
+ fmData.tags = entry.tags;
1755
+ }
1756
+ if (entry.significance !== void 0) {
1757
+ fmData.significance = entry.significance;
1758
+ }
1759
+ if (entry.author !== void 0) {
1760
+ fmData.author = entry.author;
1761
+ }
1762
+ try {
1763
+ const relationships = db.getRelationships(entry.id);
1764
+ if (relationships.length > 0) {
1765
+ fmData.relationships = relationships.map((r) => ({
1766
+ type: r.relationshipType,
1767
+ target_id: r.fromEntryId === entry.id ? r.toEntryId : r.fromEntryId
1768
+ }));
1769
+ }
1770
+ } catch {
1771
+ }
1772
+ const frontmatter = serializeFrontmatter(fmData);
1773
+ const fileContent = frontmatter + "\n" + entry.content + "\n";
1774
+ const filename = generateFilename(entry.id, entry.content);
1775
+ const filepath = join(resolvedOutputDir, filename);
1776
+ const handle = await open(filepath, "w", 384);
1777
+ try {
1778
+ await handle.writeFile(fileContent, "utf-8");
1779
+ } finally {
1780
+ await handle.close();
1781
+ }
1782
+ files.push(filename);
1783
+ }
1784
+ return {
1785
+ success: true,
1786
+ exported_count: files.length,
1787
+ output_dir: resolvedOutputDir,
1788
+ files,
1789
+ skipped
1790
+ };
1791
+ }
1792
+ var VALID_RELATIONSHIP_TYPES = /* @__PURE__ */ new Set([
1793
+ "evolves_from",
1794
+ "references",
1795
+ "implements",
1796
+ "clarifies",
1797
+ "response_to",
1798
+ "blocked_by",
1799
+ "resolved",
1800
+ "caused"
1801
+ ]);
1802
+ var VALID_ENTRY_TYPES = /* @__PURE__ */ new Set([
1803
+ "personal_reflection",
1804
+ "project_decision",
1805
+ "technical_achievement",
1806
+ "bug_fix",
1807
+ "feature_implementation",
1808
+ "code_review",
1809
+ "meeting_notes",
1810
+ "learning",
1811
+ "research",
1812
+ "planning",
1813
+ "retrospective",
1814
+ "standup",
1815
+ "technical_note",
1816
+ "development_note",
1817
+ "enhancement",
1818
+ "milestone",
1819
+ "system_integration_test",
1820
+ "test_entry",
1821
+ "other"
1822
+ ]);
1823
+ function toEntryType(value) {
1824
+ if (value === void 0) return void 0;
1825
+ return VALID_ENTRY_TYPES.has(value) ? value : void 0;
1826
+ }
1827
+ async function importMarkdownEntries(sourceDir, db, options = {}, vectorManager) {
1828
+ const { dry_run = false, limit = 100 } = options;
1829
+ const allFiles = await readdir(sourceDir);
1830
+ const mdFiles = allFiles.filter((f) => f.endsWith(".md")).slice(0, limit);
1831
+ const result = {
1832
+ success: true,
1833
+ created: 0,
1834
+ updated: 0,
1835
+ skipped: 0,
1836
+ errors: [],
1837
+ dry_run
1838
+ };
1839
+ for (const filename of mdFiles) {
1840
+ try {
1841
+ const filepath = join(sourceDir, filename);
1842
+ const content = await readFile(filepath, "utf-8");
1843
+ const { metadata, body } = parseFrontmatter(content);
1844
+ if (!body.trim()) {
1845
+ result.skipped++;
1846
+ continue;
1847
+ }
1848
+ if (dry_run) {
1849
+ if (metadata.mj_id !== void 0) {
1850
+ const existing = db.getEntryById(metadata.mj_id);
1851
+ if (existing) {
1852
+ result.updated++;
1853
+ } else {
1854
+ result.created++;
1855
+ }
1856
+ } else {
1857
+ result.created++;
1858
+ }
1859
+ continue;
1860
+ }
1861
+ let entryId;
1862
+ const entryType = toEntryType(metadata.entry_type) ?? "personal_reflection";
1863
+ if (metadata.mj_id !== void 0) {
1864
+ const existing = db.getEntryById(metadata.mj_id);
1865
+ if (existing) {
1866
+ db.updateEntry(metadata.mj_id, {
1867
+ content: body,
1868
+ entryType: toEntryType(metadata.entry_type),
1869
+ tags: metadata.tags
1870
+ });
1871
+ entryId = metadata.mj_id;
1872
+ result.updated++;
1873
+ } else {
1874
+ const newEntry = db.createEntry({
1875
+ content: body,
1876
+ entryType,
1877
+ tags: metadata.tags,
1878
+ ...metadata.significance !== void 0 && {
1879
+ significanceType: metadata.significance
1880
+ }
1881
+ });
1882
+ entryId = newEntry.id;
1883
+ result.created++;
1884
+ }
1885
+ } else {
1886
+ const newEntry = db.createEntry({
1887
+ content: body,
1888
+ entryType,
1889
+ tags: metadata.tags,
1890
+ ...metadata.significance !== void 0 && {
1891
+ significanceType: metadata.significance
1892
+ }
1893
+ });
1894
+ entryId = newEntry.id;
1895
+ result.created++;
1896
+ }
1897
+ if (metadata.relationships !== void 0) {
1898
+ for (const rel of metadata.relationships) {
1899
+ if (!VALID_RELATIONSHIP_TYPES.has(rel.type)) continue;
1900
+ try {
1901
+ const targetExists = db.getEntryById(rel.target_id);
1902
+ if (targetExists) {
1903
+ db.linkEntries(entryId, rel.target_id, rel.type);
1904
+ }
1905
+ } catch {
1906
+ }
1907
+ }
1908
+ }
1909
+ if (vectorManager) {
1910
+ void vectorManager.addEntry(entryId, body).catch(() => {
1911
+ });
1912
+ }
1913
+ } catch (err) {
1914
+ result.errors.push({
1915
+ file: filename,
1916
+ error: err instanceof Error ? err.message : String(err)
1917
+ });
1918
+ }
1919
+ }
1920
+ return result;
1921
+ }
1557
1922
 
1558
- // src/handlers/tools/export.ts
1923
+ // src/handlers/tools/io.ts
1559
1924
  var ExportEntriesSchema = z.object({
1560
1925
  format: z.enum(["json", "markdown"]).optional().default("json"),
1561
1926
  start_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional(),
@@ -1580,14 +1945,60 @@ var ExportEntriesOutputSchema = z.object({
1580
1945
  success: z.boolean().optional(),
1581
1946
  error: z.string().optional()
1582
1947
  }).extend(ErrorFieldsMixin.shape);
1583
- function getExportTools(context) {
1948
+ var ExportMarkdownSchema = z.object({
1949
+ output_dir: z.string().min(1),
1950
+ start_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional(),
1951
+ end_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional(),
1952
+ entry_types: z.array(z.enum(ENTRY_TYPES)).optional(),
1953
+ tags: z.array(z.string()).optional(),
1954
+ limit: z.number().max(500).optional().default(100)
1955
+ });
1956
+ var ExportMarkdownSchemaMcp = z.object({
1957
+ output_dir: z.string().describe("Target directory for .md files"),
1958
+ start_date: z.string().optional().describe("Start date filter (YYYY-MM-DD)"),
1959
+ end_date: z.string().optional().describe("End date filter (YYYY-MM-DD)"),
1960
+ entry_types: z.array(z.string()).optional().describe("Filter by entry types"),
1961
+ tags: z.array(z.string()).optional().describe("Filter by tags"),
1962
+ limit: relaxedNumber().optional().default(100).describe("Max entries to export (default: 100, max: 500)")
1963
+ });
1964
+ var ExportMarkdownOutputSchema = z.object({
1965
+ success: z.boolean().optional(),
1966
+ exported_count: z.number().optional(),
1967
+ output_dir: z.string().optional(),
1968
+ files: z.array(z.string()).optional(),
1969
+ skipped: z.number().optional()
1970
+ }).extend(ErrorFieldsMixin.shape);
1971
+ var ImportMarkdownSchema = z.object({
1972
+ source_dir: z.string().min(1),
1973
+ dry_run: z.boolean().optional().default(false),
1974
+ limit: z.number().max(500).optional().default(100)
1975
+ });
1976
+ var ImportMarkdownSchemaMcp = z.object({
1977
+ source_dir: z.string().describe("Directory containing .md files to import"),
1978
+ dry_run: z.boolean().optional().default(false).describe("Parse and validate without writing to database"),
1979
+ limit: relaxedNumber().optional().default(100).describe("Max files to process (default: 100, max: 500)")
1980
+ });
1981
+ var ImportMarkdownOutputSchema = z.object({
1982
+ success: z.boolean().optional(),
1983
+ created: z.number().optional(),
1984
+ updated: z.number().optional(),
1985
+ skipped: z.number().optional(),
1986
+ errors: z.array(
1987
+ z.object({
1988
+ file: z.string(),
1989
+ error: z.string()
1990
+ })
1991
+ ).optional(),
1992
+ dry_run: z.boolean().optional()
1993
+ }).extend(ErrorFieldsMixin.shape);
1994
+ function getIoTools(context) {
1584
1995
  const { db, progress } = context;
1585
1996
  return [
1586
1997
  {
1587
1998
  name: "export_entries",
1588
1999
  title: "Export Entries",
1589
2000
  description: "Export journal entries to JSON or Markdown format",
1590
- group: "export",
2001
+ group: "io",
1591
2002
  inputSchema: ExportEntriesSchemaMcp,
1592
2003
  outputSchema: ExportEntriesOutputSchema,
1593
2004
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
@@ -1643,6 +2054,107 @@ ${e.content}
1643
2054
  return formatHandlerError(err);
1644
2055
  }
1645
2056
  }
2057
+ },
2058
+ // ==================================================================
2059
+ // export_markdown
2060
+ // ==================================================================
2061
+ {
2062
+ name: "export_markdown",
2063
+ title: "Export to Markdown Files",
2064
+ description: "Export journal entries as individual frontmattered Markdown files (.md). Each file contains YAML frontmatter (mj_id, entry_type, tags, relationships) and the entry content. Files can be edited externally and re-imported via import_markdown.",
2065
+ group: "io",
2066
+ inputSchema: ExportMarkdownSchemaMcp,
2067
+ outputSchema: ExportMarkdownOutputSchema,
2068
+ annotations: {
2069
+ readOnlyHint: false,
2070
+ destructiveHint: false,
2071
+ idempotentHint: true,
2072
+ openWorldHint: true
2073
+ },
2074
+ handler: async (params) => {
2075
+ try {
2076
+ const input = ExportMarkdownSchema.parse(params);
2077
+ assertSafeDirectoryPath(input.output_dir);
2078
+ await sendProgress(progress, 0, 3, "Fetching entries...");
2079
+ const limit = input.limit ?? 100;
2080
+ let entries;
2081
+ if (input.start_date || input.end_date) {
2082
+ const startDate = input.start_date ?? DATE_MIN_SENTINEL;
2083
+ const endDate = input.end_date ?? DATE_MAX_SENTINEL;
2084
+ entries = db.searchByDateRange(startDate, endDate, {
2085
+ tags: input.tags,
2086
+ limit
2087
+ });
2088
+ } else if (input.tags && input.tags.length > 0) {
2089
+ entries = db.searchByDateRange(DATE_MIN_SENTINEL, DATE_MAX_SENTINEL, {
2090
+ tags: input.tags,
2091
+ limit
2092
+ });
2093
+ } else {
2094
+ entries = db.getRecentEntries(limit);
2095
+ }
2096
+ if (input.entry_types && input.entry_types.length > 0) {
2097
+ const allowedTypes = new Set(input.entry_types);
2098
+ entries = entries.filter((e) => allowedTypes.has(e.entryType)).slice(0, limit);
2099
+ }
2100
+ await sendProgress(
2101
+ progress,
2102
+ 1,
2103
+ 3,
2104
+ `Exporting ${String(entries.length)} entries...`
2105
+ );
2106
+ const exportable = entries.map((e) => ({
2107
+ id: e.id,
2108
+ content: e.content,
2109
+ entryType: e.entryType,
2110
+ timestamp: e.timestamp,
2111
+ tags: e.tags,
2112
+ significance: e.significanceType ?? void 0
2113
+ }));
2114
+ const result = await exportEntriesToMarkdown(exportable, input.output_dir, db);
2115
+ await sendProgress(progress, 3, 3, "Export complete");
2116
+ return result;
2117
+ } catch (err) {
2118
+ return formatHandlerError(err);
2119
+ }
2120
+ }
2121
+ },
2122
+ // ==================================================================
2123
+ // import_markdown
2124
+ // ==================================================================
2125
+ {
2126
+ name: "import_markdown",
2127
+ title: "Import from Markdown Files",
2128
+ description: "Import frontmattered Markdown files (.md) into the journal. If a file has an mj_id in its frontmatter and the entry exists, it is updated. Otherwise, a new entry is created. Tags and relationships are reconciled automatically. Use dry_run: true to preview what would happen without writing.",
2129
+ group: "io",
2130
+ inputSchema: ImportMarkdownSchemaMcp,
2131
+ outputSchema: ImportMarkdownOutputSchema,
2132
+ annotations: {
2133
+ readOnlyHint: false,
2134
+ destructiveHint: false,
2135
+ idempotentHint: false,
2136
+ openWorldHint: true
2137
+ },
2138
+ handler: async (params) => {
2139
+ try {
2140
+ const input = ImportMarkdownSchema.parse(params);
2141
+ assertSafeDirectoryPath(input.source_dir);
2142
+ await sendProgress(progress, 0, 2, "Reading markdown files...");
2143
+ const result = await importMarkdownEntries(
2144
+ input.source_dir,
2145
+ db,
2146
+ {
2147
+ dry_run: input.dry_run,
2148
+ limit: input.limit
2149
+ },
2150
+ context.vectorManager
2151
+ );
2152
+ await sendProgress(progress, 2, 2, "Import complete");
2153
+ return result;
2154
+ } catch (err) {
2155
+ return formatHandlerError(err);
2156
+ }
2157
+ }
1646
2158
  }
1647
2159
  ];
1648
2160
  }
@@ -1936,6 +2448,8 @@ var GitHubIssueOutputSchema = z.object({
1936
2448
  }).extend(ErrorFieldsMixin.shape);
1937
2449
  var GitHubIssueDetailsOutputSchema = GitHubIssueOutputSchema.extend({
1938
2450
  body: z.string().nullable(),
2451
+ bodyTruncated: z.boolean().optional(),
2452
+ bodyFullLength: z.number().optional(),
1939
2453
  labels: z.array(z.string()),
1940
2454
  assignees: z.array(z.string()),
1941
2455
  createdAt: z.string(),
@@ -1956,6 +2470,14 @@ var GitHubIssuesListOutputSchema = z.object({
1956
2470
  }).extend(ErrorFieldsMixin.shape);
1957
2471
  var GitHubIssueResultOutputSchema = z.object({
1958
2472
  issue: GitHubIssueDetailsOutputSchema.optional(),
2473
+ comments: z.array(
2474
+ z.object({
2475
+ author: z.string(),
2476
+ body: z.string(),
2477
+ createdAt: z.string()
2478
+ })
2479
+ ).optional(),
2480
+ commentCount: z.number().optional(),
1959
2481
  owner: z.string().optional(),
1960
2482
  repo: z.string().optional(),
1961
2483
  detectedOwner: z.string().nullable().optional(),
@@ -1972,6 +2494,8 @@ var GitHubPullRequestOutputSchema = z.object({
1972
2494
  }).extend(ErrorFieldsMixin.shape);
1973
2495
  var GitHubPRDetailsOutputSchema = GitHubPullRequestOutputSchema.extend({
1974
2496
  body: z.string().nullable(),
2497
+ bodyTruncated: z.boolean().optional(),
2498
+ bodyFullLength: z.number().optional(),
1975
2499
  draft: z.boolean(),
1976
2500
  headBranch: z.string(),
1977
2501
  baseBranch: z.string(),
@@ -2036,7 +2560,9 @@ var StatusOptionOutputSchema = z.object({
2036
2560
  var KanbanColumnOutputSchema = z.object({
2037
2561
  status: z.string(),
2038
2562
  statusOptionId: z.string(),
2039
- items: z.array(KanbanItemOutputSchema)
2563
+ items: z.array(KanbanItemOutputSchema).optional(),
2564
+ itemCount: z.number().optional(),
2565
+ truncated: z.boolean().optional()
2040
2566
  });
2041
2567
  var KanbanBoardOutputSchema = z.object({
2042
2568
  projectId: z.string().optional(),
@@ -2046,6 +2572,14 @@ var KanbanBoardOutputSchema = z.object({
2046
2572
  statusOptions: z.array(StatusOptionOutputSchema).optional(),
2047
2573
  columns: z.array(KanbanColumnOutputSchema).optional(),
2048
2574
  totalItems: z.number().optional(),
2575
+ itemDirectory: z.array(
2576
+ z.object({
2577
+ id: z.string(),
2578
+ title: z.string(),
2579
+ status: z.string().nullable()
2580
+ })
2581
+ ).optional(),
2582
+ summaryOnly: z.boolean().optional(),
2049
2583
  owner: z.string().optional(),
2050
2584
  detectedOwner: z.string().nullable().optional(),
2051
2585
  detectedRepo: z.string().nullable().optional(),
@@ -2055,16 +2589,19 @@ var KanbanBoardOutputSchema = z.object({
2055
2589
  instruction: z.string().optional()
2056
2590
  }).extend(ErrorFieldsMixin.shape);
2057
2591
  var MoveKanbanItemOutputSchema = z.object({
2058
- success: z.boolean().optional(),
2059
2592
  itemId: z.string().optional(),
2060
2593
  newStatus: z.string().optional(),
2061
2594
  projectNumber: z.number().optional(),
2062
- message: z.string().optional(),
2063
- error: z.string().optional(),
2064
- requiresUserInput: z.boolean().optional(),
2065
- hint: z.string().optional(),
2066
2595
  availableStatuses: z.array(z.string()).optional()
2067
2596
  }).extend(ErrorFieldsMixin.shape);
2597
+ var AddKanbanItemOutputSchema = z.object({
2598
+ itemId: z.string().optional(),
2599
+ projectNumber: z.number().optional()
2600
+ }).extend(ErrorFieldsMixin.shape);
2601
+ var DeleteKanbanItemOutputSchema = z.object({
2602
+ itemId: z.string().optional(),
2603
+ projectNumber: z.number().optional()
2604
+ }).extend(ErrorFieldsMixin.shape);
2068
2605
  var CreateGitHubIssueWithEntryOutputSchema = z.object({
2069
2606
  success: z.boolean().optional(),
2070
2607
  issue: z.object({
@@ -2355,7 +2892,7 @@ function getGitHubReadTools(context) {
2355
2892
  owner: z.string().optional(),
2356
2893
  repo: z.string().optional(),
2357
2894
  state: z.enum(["open", "closed", "all"]).optional().default("open"),
2358
- limit: z.number().optional().default(20)
2895
+ limit: z.number().max(MAX_QUERY_LIMIT).optional().default(20)
2359
2896
  }).parse(params);
2360
2897
  const resolved = await resolveOwnerRepo(
2361
2898
  context,
@@ -2405,7 +2942,7 @@ function getGitHubReadTools(context) {
2405
2942
  owner: z.string().optional(),
2406
2943
  repo: z.string().optional(),
2407
2944
  state: z.enum(["open", "closed", "all"]).optional().default("open"),
2408
- limit: z.number().optional().default(20)
2945
+ limit: z.number().max(MAX_QUERY_LIMIT).optional().default(20)
2409
2946
  }).parse(params);
2410
2947
  const resolved = await resolveOwnerRepo(
2411
2948
  context,
@@ -2440,7 +2977,11 @@ function getGitHubReadTools(context) {
2440
2977
  inputSchema: z.object({
2441
2978
  issue_number: z.number(),
2442
2979
  owner: z.string().optional().describe("LEAVE EMPTY to auto-detect from git"),
2443
- repo: z.string().optional().describe("LEAVE EMPTY to auto-detect from git")
2980
+ repo: z.string().optional().describe("LEAVE EMPTY to auto-detect from git"),
2981
+ truncate_body: relaxedNumber().optional().default(800).describe(
2982
+ "Max characters for body text (0 = full body, default 800). Reduces token usage."
2983
+ ),
2984
+ include_comments: z.boolean().optional().default(false).describe("Include issue comments (default false). Each comment adds tokens.")
2444
2985
  }),
2445
2986
  outputSchema: GitHubIssueResultOutputSchema,
2446
2987
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
@@ -2449,7 +2990,9 @@ function getGitHubReadTools(context) {
2449
2990
  const input = z.object({
2450
2991
  issue_number: z.number(),
2451
2992
  owner: z.string().optional(),
2452
- repo: z.string().optional()
2993
+ repo: z.string().optional(),
2994
+ truncate_body: z.number().optional().default(800),
2995
+ include_comments: z.boolean().optional().default(false)
2453
2996
  }).parse(params);
2454
2997
  const resolved = await resolveOwnerRepo(context, input, "is this issue from");
2455
2998
  if ("error" in resolved) return resolved.response;
@@ -2472,8 +3015,30 @@ function getGitHubReadTools(context) {
2472
3015
  detectedRepo: resolved.detectedRepo
2473
3016
  };
2474
3017
  }
3018
+ const truncateBody = input.truncate_body;
3019
+ let bodyTruncated = false;
3020
+ let bodyFullLength;
3021
+ if (truncateBody > 0 && issue.body && issue.body.length > truncateBody) {
3022
+ bodyFullLength = issue.body.length;
3023
+ const remaining = issue.body.length - truncateBody;
3024
+ issue.body = issue.body.slice(0, truncateBody) + `
3025
+ [Truncated. Re-run with truncate_body: 0 to view remaining ${String(remaining)} chars]`;
3026
+ bodyTruncated = true;
3027
+ }
3028
+ let comments;
3029
+ if (input.include_comments) {
3030
+ comments = await resolved.github.getIssueComments(
3031
+ resolved.owner,
3032
+ resolved.repo,
3033
+ input.issue_number
3034
+ );
3035
+ }
2475
3036
  return {
2476
- issue,
3037
+ issue: {
3038
+ ...issue,
3039
+ ...bodyTruncated ? { bodyTruncated: true, bodyFullLength } : {}
3040
+ },
3041
+ ...comments ? { comments, commentCount: comments.length } : {},
2477
3042
  owner: resolved.owner,
2478
3043
  repo: resolved.repo,
2479
3044
  detectedOwner: resolved.detectedOwner,
@@ -2492,7 +3057,10 @@ function getGitHubReadTools(context) {
2492
3057
  inputSchema: z.object({
2493
3058
  pr_number: z.number(),
2494
3059
  owner: z.string().optional().describe("LEAVE EMPTY to auto-detect from git"),
2495
- repo: z.string().optional().describe("LEAVE EMPTY to auto-detect from git")
3060
+ repo: z.string().optional().describe("LEAVE EMPTY to auto-detect from git"),
3061
+ truncate_body: relaxedNumber().optional().default(800).describe(
3062
+ "Max characters for body text (0 = full body, default 800). Reduces token usage."
3063
+ )
2496
3064
  }),
2497
3065
  outputSchema: GitHubPRResultOutputSchema,
2498
3066
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
@@ -2501,7 +3069,8 @@ function getGitHubReadTools(context) {
2501
3069
  const input = z.object({
2502
3070
  pr_number: z.number(),
2503
3071
  owner: z.string().optional(),
2504
- repo: z.string().optional()
3072
+ repo: z.string().optional(),
3073
+ truncate_body: z.number().optional().default(800)
2505
3074
  }).parse(params);
2506
3075
  const resolved = await resolveOwnerRepo(context, input, "is this PR from");
2507
3076
  if ("error" in resolved) return resolved.response;
@@ -2524,8 +3093,21 @@ function getGitHubReadTools(context) {
2524
3093
  detectedRepo: resolved.detectedRepo
2525
3094
  };
2526
3095
  }
3096
+ const truncateBody = input.truncate_body;
3097
+ let bodyTruncated = false;
3098
+ let bodyFullLength;
3099
+ if (truncateBody > 0 && pullRequest.body && pullRequest.body.length > truncateBody) {
3100
+ bodyFullLength = pullRequest.body.length;
3101
+ const remaining = pullRequest.body.length - truncateBody;
3102
+ pullRequest.body = pullRequest.body.slice(0, truncateBody) + `
3103
+ [Truncated. Re-run with truncate_body: 0 to view remaining ${String(remaining)} chars]`;
3104
+ bodyTruncated = true;
3105
+ }
2527
3106
  return {
2528
- pullRequest,
3107
+ pullRequest: {
3108
+ ...pullRequest,
3109
+ ...bodyTruncated ? { bodyTruncated: true, bodyFullLength } : {}
3110
+ },
2529
3111
  owner: resolved.owner,
2530
3112
  repo: resolved.repo,
2531
3113
  detectedOwner: resolved.detectedOwner,
@@ -2593,7 +3175,13 @@ function getKanbanTools(context) {
2593
3175
  inputSchema: z.object({
2594
3176
  project_number: z.number().optional().describe("GitHub Project number (optional if repo is registered)"),
2595
3177
  owner: z.string().optional().describe("Project owner - LEAVE EMPTY to auto-detect"),
2596
- repo: z.string().optional().describe("Repository name - LEAVE EMPTY to auto-detect")
3178
+ repo: z.string().optional().describe("Repository name - LEAVE EMPTY to auto-detect"),
3179
+ summary_only: z.boolean().optional().default(false).describe(
3180
+ "Return column summaries only (name + itemCount), omitting individual items. Saves ~80% tokens."
3181
+ ),
3182
+ item_limit: relaxedNumber().optional().default(25).describe(
3183
+ "Maximum items per column (default 25, max 100). Set to 0 for summary_only behavior."
3184
+ )
2597
3185
  }),
2598
3186
  outputSchema: KanbanBoardOutputSchema,
2599
3187
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
@@ -2602,7 +3190,9 @@ function getKanbanTools(context) {
2602
3190
  const input = z.object({
2603
3191
  project_number: z.number().optional(),
2604
3192
  owner: z.string().optional(),
2605
- repo: z.string().optional()
3193
+ repo: z.string().optional(),
3194
+ summary_only: z.boolean().optional().default(false),
3195
+ item_limit: z.number().max(100).optional().default(25)
2606
3196
  }).parse(params);
2607
3197
  const resolved = await resolveOwner(context, input.owner);
2608
3198
  if ("error" in resolved) return resolved.response;
@@ -2641,7 +3231,44 @@ function getKanbanTools(context) {
2641
3231
  recoverable: true
2642
3232
  };
2643
3233
  }
2644
- return board;
3234
+ const summaryOnly = input.summary_only || input.item_limit === 0;
3235
+ if (summaryOnly) {
3236
+ const summaryColumns = board.columns.map((col) => ({
3237
+ status: col.status,
3238
+ statusOptionId: col.statusOptionId,
3239
+ items: [],
3240
+ itemCount: col.items.length
3241
+ }));
3242
+ const itemDirectory = board.columns.flatMap(
3243
+ (col) => col.items.map((item) => ({
3244
+ id: item.id,
3245
+ title: item.title,
3246
+ status: item.status
3247
+ }))
3248
+ );
3249
+ return {
3250
+ ...board,
3251
+ columns: summaryColumns,
3252
+ summaryOnly: true,
3253
+ itemDirectory
3254
+ };
3255
+ }
3256
+ const itemLimit = input.item_limit;
3257
+ const truncatedColumns = board.columns.map((col) => {
3258
+ if (col.items.length <= itemLimit) {
3259
+ return { ...col, itemCount: col.items.length };
3260
+ }
3261
+ return {
3262
+ ...col,
3263
+ items: col.items.slice(0, itemLimit),
3264
+ itemCount: col.items.length,
3265
+ truncated: true
3266
+ };
3267
+ });
3268
+ return {
3269
+ ...board,
3270
+ columns: truncatedColumns
3271
+ };
2645
3272
  } catch (err) {
2646
3273
  return formatHandlerError(err);
2647
3274
  }
@@ -2734,6 +3361,155 @@ function getKanbanTools(context) {
2734
3361
  return formatHandlerError(err);
2735
3362
  }
2736
3363
  }
3364
+ },
3365
+ {
3366
+ name: "add_kanban_item",
3367
+ title: "Add Kanban Item",
3368
+ description: "Add an existing GitHub Issue or Pull Request to a Kanban board (GitHub Project v2). Returns the project item ID on success.",
3369
+ group: "github",
3370
+ inputSchema: z.object({
3371
+ project_number: z.number().optional().describe("GitHub Project number (optional if repo is registered)"),
3372
+ issue_number: z.number().describe("The number of the issue or PR to add"),
3373
+ owner: z.string().optional().describe("Repository owner - LEAVE EMPTY to auto-detect"),
3374
+ repo: z.string().optional().describe("Repository name - LEAVE EMPTY to auto-detect")
3375
+ }),
3376
+ outputSchema: AddKanbanItemOutputSchema,
3377
+ annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: true },
3378
+ handler: async (params) => {
3379
+ try {
3380
+ const input = z.object({
3381
+ project_number: z.number().optional(),
3382
+ issue_number: z.number(),
3383
+ owner: z.string().optional(),
3384
+ repo: z.string().optional()
3385
+ }).parse(params);
3386
+ const resolved = await resolveOwnerRepo(context, input);
3387
+ if ("error" in resolved) return resolved.response;
3388
+ const projectNum = resolveProjectNumber(
3389
+ context,
3390
+ resolved.repo,
3391
+ input.project_number
3392
+ );
3393
+ if (projectNum === void 0) {
3394
+ return {
3395
+ success: false,
3396
+ error: "project_number is required and could not be resolved from registry. Please supply it explicitly.",
3397
+ code: "VALIDATION_ERROR",
3398
+ category: "validation",
3399
+ recoverable: true
3400
+ };
3401
+ }
3402
+ const issueDetails = await resolved.github.getIssue(
3403
+ resolved.owner,
3404
+ resolved.repo,
3405
+ input.issue_number
3406
+ );
3407
+ if (!issueDetails?.nodeId) {
3408
+ return {
3409
+ success: false,
3410
+ error: `Issue or PR #${String(input.issue_number)} not found or lacks a nodeId`,
3411
+ code: "RESOURCE_NOT_FOUND",
3412
+ category: "resource",
3413
+ recoverable: true
3414
+ };
3415
+ }
3416
+ const board = await resolved.github.getProjectKanban(
3417
+ resolved.owner,
3418
+ projectNum,
3419
+ resolved.repo
3420
+ );
3421
+ if (!board) {
3422
+ return {
3423
+ success: false,
3424
+ error: `Project #${String(projectNum)} not found`,
3425
+ code: "RESOURCE_NOT_FOUND",
3426
+ category: "resource",
3427
+ recoverable: true
3428
+ };
3429
+ }
3430
+ const result = await resolved.github.addProjectItem(
3431
+ board.projectId,
3432
+ issueDetails.nodeId
3433
+ );
3434
+ return {
3435
+ success: result.success,
3436
+ itemId: result.itemId,
3437
+ projectNumber: projectNum,
3438
+ message: result.success ? `Added Issue/PR #${String(input.issue_number)} to Project #${projectNum}` : void 0,
3439
+ error: result.error
3440
+ };
3441
+ } catch (err) {
3442
+ return formatHandlerError(err);
3443
+ }
3444
+ }
3445
+ },
3446
+ {
3447
+ name: "delete_kanban_item",
3448
+ title: "Delete Kanban Item",
3449
+ 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.",
3450
+ group: "github",
3451
+ inputSchema: z.object({
3452
+ project_number: z.number().optional().describe("GitHub Project number (optional if repo is registered)"),
3453
+ item_id: z.string().describe("The project item ID to remove (from get_kanban_board)"),
3454
+ owner: z.string().optional().describe("Project owner - LEAVE EMPTY to auto-detect"),
3455
+ repo: z.string().optional().describe("Repository name - LEAVE EMPTY to auto-detect")
3456
+ }),
3457
+ outputSchema: DeleteKanbanItemOutputSchema,
3458
+ annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
3459
+ handler: async (params) => {
3460
+ try {
3461
+ const input = z.object({
3462
+ project_number: z.number().optional(),
3463
+ item_id: z.string(),
3464
+ owner: z.string().optional(),
3465
+ repo: z.string().optional()
3466
+ }).parse(params);
3467
+ const resolved = await resolveOwner(context, input.owner);
3468
+ if ("error" in resolved) return resolved.response;
3469
+ const effectiveRepo = input.repo ?? resolved.repo;
3470
+ const projectNum = resolveProjectNumber(
3471
+ context,
3472
+ effectiveRepo,
3473
+ input.project_number
3474
+ );
3475
+ if (projectNum === void 0) {
3476
+ return {
3477
+ success: false,
3478
+ error: "project_number is required and could not be resolved from registry. Please supply it explicitly.",
3479
+ code: "VALIDATION_ERROR",
3480
+ category: "validation",
3481
+ recoverable: true
3482
+ };
3483
+ }
3484
+ const board = await resolved.github.getProjectKanban(
3485
+ resolved.owner,
3486
+ projectNum,
3487
+ effectiveRepo
3488
+ );
3489
+ if (!board) {
3490
+ return {
3491
+ success: false,
3492
+ error: `Project #${String(projectNum)} not found`,
3493
+ code: "RESOURCE_NOT_FOUND",
3494
+ category: "resource",
3495
+ recoverable: true
3496
+ };
3497
+ }
3498
+ const result = await resolved.github.deleteProjectItem(
3499
+ board.projectId,
3500
+ input.item_id
3501
+ );
3502
+ return {
3503
+ success: result.success,
3504
+ itemId: input.item_id,
3505
+ projectNumber: projectNum,
3506
+ message: result.success ? `Deleted item ${input.item_id} from Project #${projectNum}` : void 0,
3507
+ error: result.error
3508
+ };
3509
+ } catch (err) {
3510
+ return formatHandlerError(err);
3511
+ }
3512
+ }
2737
3513
  }
2738
3514
  ];
2739
3515
  }
@@ -2791,7 +3567,11 @@ function getGitHubIssueTools(context) {
2791
3567
  if (!issue) {
2792
3568
  return {
2793
3569
  success: false,
2794
- error: "Failed to create GitHub issue. Check GITHUB_TOKEN permissions."
3570
+ error: "Failed to create GitHub issue. Check GITHUB_TOKEN permissions.",
3571
+ code: "API_ERROR",
3572
+ category: "api",
3573
+ suggestion: "Verify GitHub token has write access to issues.",
3574
+ recoverable: true
2795
3575
  };
2796
3576
  }
2797
3577
  const projectNumber = resolveProjectNumber(
@@ -2950,13 +3730,21 @@ Description: ${input.body.slice(0, 200)}${input.body.length > 200 ? "..." : ""}`
2950
3730
  if (!issueDetails) {
2951
3731
  return {
2952
3732
  success: false,
2953
- error: `Issue #${String(input.issue_number)} not found`
3733
+ error: `Issue #${String(input.issue_number)} not found`,
3734
+ code: "RESOURCE_NOT_FOUND",
3735
+ category: "resource",
3736
+ suggestion: "Verify the issue number exists in this repository.",
3737
+ recoverable: true
2954
3738
  };
2955
3739
  }
2956
3740
  if (issueDetails.state === "CLOSED") {
2957
3741
  return {
2958
- success: false,
2959
- error: `Issue #${String(input.issue_number)} is already closed`
3742
+ ...formatHandlerError(
3743
+ new ValidationError(
3744
+ `Issue #${String(input.issue_number)} is already closed`
3745
+ )
3746
+ ),
3747
+ success: false
2960
3748
  };
2961
3749
  }
2962
3750
  const result = await resolved.github.closeIssue(
@@ -2968,7 +3756,11 @@ Description: ${input.body.slice(0, 200)}${input.body.length > 200 ? "..." : ""}`
2968
3756
  if (!result) {
2969
3757
  return {
2970
3758
  success: false,
2971
- error: "Failed to close GitHub issue. Check GITHUB_TOKEN permissions."
3759
+ error: "Failed to close GitHub issue. Check GITHUB_TOKEN permissions.",
3760
+ code: "API_ERROR",
3761
+ category: "api",
3762
+ suggestion: "Verify GitHub token has write access to issues.",
3763
+ recoverable: true
2972
3764
  };
2973
3765
  }
2974
3766
  let kanbanResult;
@@ -3127,10 +3919,12 @@ function isResourceError(result) {
3127
3919
  }
3128
3920
  var DEFAULT_BRIEFING_CONFIG = {
3129
3921
  entryCount: 3,
3922
+ summaryCount: 1,
3130
3923
  includeTeam: false,
3131
3924
  issueCount: 0,
3132
3925
  prCount: 0,
3133
3926
  prStatusBreakdown: false,
3927
+ milestoneCount: 3,
3134
3928
  workflowCount: 0,
3135
3929
  workflowStatusBreakdown: false,
3136
3930
  copilotReviews: false
@@ -3870,7 +4664,7 @@ var TeamCreateEntrySchemaMcp = z.object({
3870
4664
  author: z.string().optional()
3871
4665
  });
3872
4666
  var TeamGetRecentSchema = z.object({
3873
- limit: z.number().max(500).optional().default(10)
4667
+ limit: z.number().min(1).max(500).optional().default(10)
3874
4668
  });
3875
4669
  var TeamGetRecentSchemaMcp = z.object({
3876
4670
  limit: relaxedNumber().optional().default(10)
@@ -5015,6 +5809,179 @@ function getTeamExportTools(context) {
5015
5809
  }
5016
5810
  ];
5017
5811
  }
5812
+ var TeamExportMarkdownSchema = z.object({
5813
+ output_dir: z.string().min(1),
5814
+ start_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional(),
5815
+ end_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional(),
5816
+ tags: z.array(z.string()).optional(),
5817
+ limit: z.number().max(500).optional().default(100)
5818
+ });
5819
+ var TeamExportMarkdownSchemaMcp = z.object({
5820
+ output_dir: z.string().describe("Target directory for .md files"),
5821
+ start_date: z.string().optional().describe("Start date filter (YYYY-MM-DD)"),
5822
+ end_date: z.string().optional().describe("End date filter (YYYY-MM-DD)"),
5823
+ tags: z.array(z.string()).optional().describe("Filter by tags"),
5824
+ limit: relaxedNumber().optional().default(100).describe("Max entries to export (default: 100, max: 500)")
5825
+ });
5826
+ var TeamExportMarkdownOutputSchema = z.object({
5827
+ success: z.boolean().optional(),
5828
+ exported_count: z.number().optional(),
5829
+ output_dir: z.string().optional(),
5830
+ files: z.array(z.string()).optional(),
5831
+ skipped: z.number().optional()
5832
+ }).extend(ErrorFieldsMixin.shape);
5833
+ var TeamImportMarkdownSchema = z.object({
5834
+ source_dir: z.string().min(1),
5835
+ dry_run: z.boolean().optional().default(false),
5836
+ limit: z.number().max(500).optional().default(100)
5837
+ });
5838
+ var TeamImportMarkdownSchemaMcp = z.object({
5839
+ source_dir: z.string().describe("Directory containing .md files to import"),
5840
+ dry_run: z.boolean().optional().default(false).describe("Parse and validate without writing to database"),
5841
+ limit: relaxedNumber().optional().default(100).describe("Max files to process (default: 100, max: 500)")
5842
+ });
5843
+ var TeamImportMarkdownOutputSchema = z.object({
5844
+ success: z.boolean().optional(),
5845
+ created: z.number().optional(),
5846
+ updated: z.number().optional(),
5847
+ skipped: z.number().optional(),
5848
+ errors: z.array(z.object({ file: z.string(), error: z.string() })).optional(),
5849
+ dry_run: z.boolean().optional()
5850
+ }).extend(ErrorFieldsMixin.shape);
5851
+ function getTeamIoTools(context) {
5852
+ const { teamDb, progress } = context;
5853
+ if (!teamDb) {
5854
+ return [
5855
+ {
5856
+ name: "team_export_markdown",
5857
+ title: "Team Export to Markdown",
5858
+ description: "Export team entries as frontmattered Markdown files",
5859
+ group: "team",
5860
+ inputSchema: TeamExportMarkdownSchemaMcp,
5861
+ outputSchema: TeamExportMarkdownOutputSchema,
5862
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
5863
+ handler: () => ({
5864
+ success: false,
5865
+ error: "Team collaboration is not configured. Set TEAM_DB_PATH to enable."
5866
+ })
5867
+ },
5868
+ {
5869
+ name: "team_import_markdown",
5870
+ title: "Team Import from Markdown",
5871
+ description: "Import frontmattered Markdown files into the team journal",
5872
+ group: "team",
5873
+ inputSchema: TeamImportMarkdownSchemaMcp,
5874
+ outputSchema: TeamImportMarkdownOutputSchema,
5875
+ annotations: {
5876
+ readOnlyHint: false,
5877
+ idempotentHint: false,
5878
+ openWorldHint: true
5879
+ },
5880
+ handler: () => ({
5881
+ success: false,
5882
+ error: "Team collaboration is not configured. Set TEAM_DB_PATH to enable."
5883
+ })
5884
+ }
5885
+ ];
5886
+ }
5887
+ return [
5888
+ {
5889
+ name: "team_export_markdown",
5890
+ title: "Team Export to Markdown",
5891
+ description: "Export team journal entries as individual frontmattered Markdown files (.md). Includes author field in frontmatter. Files can be re-imported via team_import_markdown.",
5892
+ group: "team",
5893
+ inputSchema: TeamExportMarkdownSchemaMcp,
5894
+ outputSchema: TeamExportMarkdownOutputSchema,
5895
+ annotations: {
5896
+ readOnlyHint: false,
5897
+ destructiveHint: false,
5898
+ idempotentHint: true,
5899
+ openWorldHint: true
5900
+ },
5901
+ handler: async (params) => {
5902
+ try {
5903
+ const input = TeamExportMarkdownSchema.parse(params);
5904
+ assertSafeDirectoryPath(input.output_dir);
5905
+ await sendProgress(progress, 0, 3, "Fetching team entries...");
5906
+ const limit = input.limit ?? 100;
5907
+ let entries;
5908
+ if (input.start_date || input.end_date) {
5909
+ const startDate = input.start_date ?? DATE_MIN_SENTINEL;
5910
+ const endDate = input.end_date ?? DATE_MAX_SENTINEL;
5911
+ entries = teamDb.searchByDateRange(startDate, endDate, {
5912
+ tags: input.tags,
5913
+ limit
5914
+ });
5915
+ } else {
5916
+ entries = teamDb.getRecentEntries(limit);
5917
+ }
5918
+ await sendProgress(
5919
+ progress,
5920
+ 1,
5921
+ 3,
5922
+ `Exporting ${String(entries.length)} team entries...`
5923
+ );
5924
+ const entryIds = entries.map((e) => e.id);
5925
+ const authorMap = batchFetchAuthors(teamDb, entryIds);
5926
+ const exportable = entries.map((e) => ({
5927
+ id: e.id,
5928
+ content: e.content,
5929
+ entryType: e.entryType,
5930
+ timestamp: e.timestamp,
5931
+ tags: e.tags,
5932
+ significance: e.significanceType ?? void 0,
5933
+ author: authorMap.get(e.id) ?? void 0
5934
+ }));
5935
+ const result = await exportEntriesToMarkdown(
5936
+ exportable,
5937
+ input.output_dir,
5938
+ teamDb
5939
+ );
5940
+ await sendProgress(progress, 3, 3, "Team export complete");
5941
+ return result;
5942
+ } catch (err) {
5943
+ return formatHandlerError(err);
5944
+ }
5945
+ }
5946
+ },
5947
+ {
5948
+ name: "team_import_markdown",
5949
+ title: "Team Import from Markdown",
5950
+ description: "Import frontmattered Markdown files (.md) into the team journal. Author is set from TEAM_AUTHOR env or git config. Use dry_run: true to preview without writing.",
5951
+ group: "team",
5952
+ inputSchema: TeamImportMarkdownSchemaMcp,
5953
+ outputSchema: TeamImportMarkdownOutputSchema,
5954
+ annotations: {
5955
+ readOnlyHint: false,
5956
+ destructiveHint: false,
5957
+ idempotentHint: false,
5958
+ openWorldHint: true
5959
+ },
5960
+ handler: async (params) => {
5961
+ try {
5962
+ const input = TeamImportMarkdownSchema.parse(params);
5963
+ assertSafeDirectoryPath(input.source_dir);
5964
+ await sendProgress(progress, 0, 2, "Reading markdown files...");
5965
+ const author = resolveAuthor();
5966
+ const result = await importMarkdownEntries(
5967
+ input.source_dir,
5968
+ teamDb,
5969
+ {
5970
+ dry_run: input.dry_run,
5971
+ limit: input.limit,
5972
+ author
5973
+ },
5974
+ context.vectorManager
5975
+ );
5976
+ await sendProgress(progress, 2, 2, "Team import complete");
5977
+ return result;
5978
+ } catch (err) {
5979
+ return formatHandlerError(err);
5980
+ }
5981
+ }
5982
+ }
5983
+ ];
5984
+ }
5018
5985
  function getTeamBackupTools(context) {
5019
5986
  const { teamDb } = context;
5020
5987
  return [
@@ -5295,6 +6262,7 @@ function getTeamTools(context) {
5295
6262
  ...getTeamAnalyticsTools(context),
5296
6263
  ...getTeamRelationshipTools(context),
5297
6264
  ...getTeamExportTools(context),
6265
+ ...getTeamIoTools(context),
5298
6266
  ...getTeamBackupTools(context),
5299
6267
  ...getTeamVectorTools(context)
5300
6268
  ];
@@ -5324,8 +6292,10 @@ var METHOD_ALIASES = {
5324
6292
  link: "linkEntries",
5325
6293
  graph: "visualizeRelationships"
5326
6294
  },
5327
- export: {
5328
- dump: "exportEntries"
6295
+ io: {
6296
+ dump: "exportEntries",
6297
+ md: "exportMarkdown",
6298
+ importMd: "importMarkdown"
5329
6299
  },
5330
6300
  admin: {
5331
6301
  edit: "updateEntry",
@@ -5342,6 +6312,8 @@ var METHOD_ALIASES = {
5342
6312
  context: "getGithubContext",
5343
6313
  kanban: "getKanbanBoard",
5344
6314
  moveKanban: "moveKanbanItem",
6315
+ addKanban: "addKanbanItem",
6316
+ deleteKanban: "deleteKanbanItem",
5345
6317
  milestones: "getGithubMilestones",
5346
6318
  milestone: "getGithubMilestone",
5347
6319
  createMilestone: "createGithubMilestone",
@@ -5400,9 +6372,10 @@ var GROUP_EXAMPLES = {
5400
6372
  'mj.relationships.linkEntries({ from_entry_id: 1, to_entry_id: 2, relationship_type: "implements" })',
5401
6373
  "mj.relationships.visualizeRelationships({ entry_id: 1 })"
5402
6374
  ],
5403
- export: [
5404
- 'mj.export.exportEntries({ format: "json" })',
5405
- 'mj.export.exportEntries({ format: "markdown", limit: 50 })'
6375
+ io: [
6376
+ 'mj.io.exportEntries({ format: "json" })',
6377
+ 'mj.io.exportMarkdown({ output_dir: "./export" })',
6378
+ 'mj.io.importMarkdown({ source_dir: "./import" })'
5406
6379
  ],
5407
6380
  admin: [
5408
6381
  'mj.admin.updateEntry({ entry_id: 1, content: "Updated content" })',
@@ -5415,6 +6388,7 @@ var GROUP_EXAMPLES = {
5415
6388
  'mj.github.getGithubIssues({ state: "open" })',
5416
6389
  'mj.github.getGithubPrs({ state: "open" })',
5417
6390
  "mj.github.getKanbanBoard({ project_number: 1 })",
6391
+ "mj.github.addKanbanItem({ project_number: 1, issue_number: 123 })",
5418
6392
  "mj.github.getGithubMilestones()",
5419
6393
  "mj.github.getRepoInsights()"
5420
6394
  ],
@@ -5463,7 +6437,9 @@ var POSITIONAL_PARAM_MAP = {
5463
6437
  getGithubIssue: "issue_number",
5464
6438
  getGithubPr: "pr_number",
5465
6439
  getKanbanBoard: "project_number",
5466
- moveKanbanItem: ["item_id", "status"],
6440
+ moveKanbanItem: ["item_id", "target_status"],
6441
+ addKanbanItem: "issue_number",
6442
+ deleteKanbanItem: "item_id",
5467
6443
  getGithubMilestone: "milestone_number",
5468
6444
  createGithubMilestone: "title",
5469
6445
  updateGithubMilestone: "milestone_number",
@@ -5493,7 +6469,7 @@ var GROUP_PREFIX_MAP = {
5493
6469
  search: "",
5494
6470
  analytics: "",
5495
6471
  relationships: "",
5496
- export: "",
6472
+ io: "",
5497
6473
  admin: "",
5498
6474
  github: "",
5499
6475
  backup: "",
@@ -5596,7 +6572,7 @@ var JournalApi = class {
5596
6572
  search;
5597
6573
  analytics;
5598
6574
  relationships;
5599
- export;
6575
+ io;
5600
6576
  admin;
5601
6577
  github;
5602
6578
  backup;
@@ -5617,7 +6593,7 @@ var JournalApi = class {
5617
6593
  "relationships",
5618
6594
  this.toolsByGroup.get("relationships") ?? []
5619
6595
  );
5620
- this.export = createGroupApi("export", this.toolsByGroup.get("export") ?? []);
6596
+ this.io = createGroupApi("io", this.toolsByGroup.get("io") ?? []);
5621
6597
  this.admin = createGroupApi("admin", this.toolsByGroup.get("admin") ?? []);
5622
6598
  this.github = createGroupApi("github", this.toolsByGroup.get("github") ?? []);
5623
6599
  this.backup = createGroupApi("backup", this.toolsByGroup.get("backup") ?? []);
@@ -5649,7 +6625,9 @@ var JournalApi = class {
5649
6625
  search: this.search,
5650
6626
  analytics: this.analytics,
5651
6627
  relationships: this.relationships,
5652
- export: this.export,
6628
+ io: this.io,
6629
+ // Backward-compat alias — agents using mj.export.* still work
6630
+ export: this.io,
5653
6631
  admin: this.admin,
5654
6632
  github: this.github,
5655
6633
  backup: this.backup,
@@ -5695,8 +6673,8 @@ var DEFAULT_SECURITY_CONFIG = {
5695
6673
  maxCodeLength: 50 * 1024,
5696
6674
  // 50KB
5697
6675
  maxExecutionsPerMinute: 60,
5698
- maxResultSize: 10 * 1024 * 1024,
5699
- // 10MB
6676
+ maxResultSize: 100 * 1024,
6677
+ // 100KB — context window protection (configurable via CODE_MODE_MAX_RESULT_SIZE)
5700
6678
  blockedPatterns: [
5701
6679
  /\brequire\s*\(/,
5702
6680
  // No require()
@@ -5811,9 +6789,12 @@ var CodeModeSecurityManager = class {
5811
6789
  const errors = [];
5812
6790
  try {
5813
6791
  const serialized = JSON.stringify(result);
5814
- if (Buffer.byteLength(serialized, "utf-8") > this.config.maxResultSize) {
6792
+ const actualBytes = Buffer.byteLength(serialized, "utf-8");
6793
+ if (actualBytes > this.config.maxResultSize) {
6794
+ const actualKb = Math.ceil(actualBytes / 1024);
6795
+ const limitKb = Math.ceil(this.config.maxResultSize / 1024);
5815
6796
  errors.push(
5816
- `Result exceeds maximum size of ${String(this.config.maxResultSize)} bytes`
6797
+ `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 }\``
5817
6798
  );
5818
6799
  }
5819
6800
  } catch (error) {
@@ -5999,7 +6980,7 @@ var WorkerSandbox = class {
5999
6980
  const effectiveTimeout = timeoutMs ?? this.options.timeoutMs;
6000
6981
  const startTime = performance.now();
6001
6982
  const startRss = process.memoryUsage.rss();
6002
- return new Promise((resolve) => {
6983
+ return new Promise((resolve2) => {
6003
6984
  const methodList = {};
6004
6985
  const topLevel = [];
6005
6986
  for (const [key, value] of Object.entries(apiBindings)) {
@@ -6055,7 +7036,7 @@ var WorkerSandbox = class {
6055
7036
  cpuTimeMs: result.metrics.cpuTimeMs,
6056
7037
  memoryUsedMb: Math.round((endRss - startRss) / 1024 / 1024)
6057
7038
  };
6058
- resolve(result);
7039
+ resolve2(result);
6059
7040
  });
6060
7041
  worker.on("error", (err) => {
6061
7042
  clearTimeout(timeoutHandle);
@@ -6064,7 +7045,7 @@ var WorkerSandbox = class {
6064
7045
  const endRss = process.memoryUsage.rss();
6065
7046
  const errorMessage = err.message;
6066
7047
  const errorStack = err.stack;
6067
- resolve({
7048
+ resolve2({
6068
7049
  success: false,
6069
7050
  error: errorMessage,
6070
7051
  stack: errorStack,
@@ -6081,7 +7062,7 @@ var WorkerSandbox = class {
6081
7062
  if (exitCode !== 0) {
6082
7063
  const endTime = performance.now();
6083
7064
  const endRss = process.memoryUsage.rss();
6084
- resolve({
7065
+ resolve2({
6085
7066
  success: false,
6086
7067
  error: `Worker exited with code ${String(exitCode)} (likely timeout or OOM)`,
6087
7068
  metrics: {
@@ -6189,7 +7170,12 @@ var ExecuteCodeSchemaMcp = z.object({
6189
7170
  var securityManager = null;
6190
7171
  var sandboxPool = null;
6191
7172
  function getSecurityManager() {
6192
- securityManager ??= new CodeModeSecurityManager();
7173
+ if (!securityManager) {
7174
+ const envMaxSize = process.env["CODE_MODE_MAX_RESULT_SIZE"];
7175
+ const parsedMaxSize = envMaxSize && /^\d+$/.test(envMaxSize) ? parseInt(envMaxSize, 10) : void 0;
7176
+ const overrides = parsedMaxSize !== void 0 && Number.isFinite(parsedMaxSize) && Number.isInteger(parsedMaxSize) && parsedMaxSize > 0 ? { maxResultSize: parsedMaxSize } : void 0;
7177
+ securityManager = new CodeModeSecurityManager(overrides);
7178
+ }
6193
7179
  return securityManager;
6194
7180
  }
6195
7181
  function getSandboxPool() {
@@ -6207,7 +7193,7 @@ function collectNonCodeModeTools(context) {
6207
7193
  ...getSearchTools(context),
6208
7194
  ...getAnalyticsTools(context),
6209
7195
  ...getRelationshipTools(context),
6210
- ...getExportTools(context),
7196
+ ...getIoTools(context),
6211
7197
  ...getAdminTools(context),
6212
7198
  ...getGitHubTools(context),
6213
7199
  ...getBackupTools(context),
@@ -6221,7 +7207,7 @@ function getCodeModeTools(context) {
6221
7207
  {
6222
7208
  name: "mj_execute_code",
6223
7209
  title: "Execute Code (Code Mode)",
6224
- description: "Execute JavaScript in a sandboxed environment with access to all journal tools via the `mj.*` API. Enables multi-step workflows in a single call, reducing token usage by 70-90%. API groups: mj.core.*, mj.search.*, mj.analytics.*, mj.relationships.*, mj.export.*, mj.admin.*, mj.github.*, mj.backup.*, mj.team.*. Use mj.help() for method listing. Returns the last expression value.",
7210
+ description: "Execute JavaScript in a sandboxed environment with access to all journal tools via the `mj.*` API. Enables multi-step workflows in a single call, reducing token usage by 70-90%. API groups: mj.core.*, mj.search.*, mj.analytics.*, mj.relationships.*, mj.io.*, mj.admin.*, mj.github.*, mj.backup.*, mj.team.*. Use mj.help() for method listing. Returns the last expression value.",
6225
7211
  group: "codemode",
6226
7212
  inputSchema: ExecuteCodeSchemaMcp,
6227
7213
  annotations: {
@@ -6645,7 +7631,7 @@ var TOOL_GROUPS = {
6645
7631
  search: ["search_entries", "search_by_date_range", "semantic_search", "get_vector_index_stats"],
6646
7632
  analytics: ["get_statistics", "get_cross_project_insights"],
6647
7633
  relationships: ["link_entries", "visualize_relationships"],
6648
- export: ["export_entries"],
7634
+ io: ["export_entries", "export_markdown", "import_markdown"],
6649
7635
  admin: [
6650
7636
  "update_entry",
6651
7637
  "delete_entry",
@@ -6661,6 +7647,8 @@ var TOOL_GROUPS = {
6661
7647
  "get_github_context",
6662
7648
  "get_kanban_board",
6663
7649
  "move_kanban_item",
7650
+ "add_kanban_item",
7651
+ "delete_kanban_item",
6664
7652
  "create_github_issue_with_entry",
6665
7653
  "close_github_issue_with_entry",
6666
7654
  "get_github_milestones",
@@ -6686,6 +7674,8 @@ var TOOL_GROUPS = {
6686
7674
  "team_link_entries",
6687
7675
  "team_visualize_relationships",
6688
7676
  "team_export_entries",
7677
+ "team_export_markdown",
7678
+ "team_import_markdown",
6689
7679
  "team_backup",
6690
7680
  "team_list_backups",
6691
7681
  "team_semantic_search",
@@ -6704,14 +7694,14 @@ var META_GROUPS = {
6704
7694
  "search",
6705
7695
  "analytics",
6706
7696
  "relationships",
6707
- "export",
7697
+ "io",
6708
7698
  "admin",
6709
7699
  "github",
6710
7700
  "backup",
6711
7701
  "team",
6712
7702
  "codemode"
6713
7703
  ],
6714
- readonly: ["core", "search", "analytics", "relationships", "export"]
7704
+ readonly: ["core", "search", "analytics", "relationships", "io"]
6715
7705
  };
6716
7706
  function getAllToolNames() {
6717
7707
  const allTools = [];
@@ -6737,8 +7727,12 @@ function getEnabledGroups(enabledTools) {
6737
7727
  }
6738
7728
  return groups;
6739
7729
  }
7730
+ var GROUP_ALIASES = { export: "io" };
7731
+ function resolveGroupAlias(name) {
7732
+ return GROUP_ALIASES[name] ?? name;
7733
+ }
6740
7734
  function isGroup(name) {
6741
- return name in TOOL_GROUPS;
7735
+ return resolveGroupAlias(name) in TOOL_GROUPS;
6742
7736
  }
6743
7737
  function isMetaGroup(name) {
6744
7738
  return name in META_GROUPS;
@@ -6761,7 +7755,8 @@ function parseToolFilter(filterString) {
6761
7755
  enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[group]]);
6762
7756
  }
6763
7757
  } else if (isGroup(name)) {
6764
- enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[name]]);
7758
+ const resolved = resolveGroupAlias(name);
7759
+ enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[resolved]]);
6765
7760
  } else {
6766
7761
  enabledTools.add(name);
6767
7762
  }
@@ -6772,7 +7767,8 @@ function parseToolFilter(filterString) {
6772
7767
  });
6773
7768
  } else if (isRemove) {
6774
7769
  if (isGroup(name)) {
6775
- for (const tool of TOOL_GROUPS[name]) {
7770
+ const resolved = resolveGroupAlias(name);
7771
+ for (const tool of TOOL_GROUPS[resolved]) {
6776
7772
  enabledTools.delete(tool);
6777
7773
  }
6778
7774
  } else {
@@ -6789,7 +7785,8 @@ function parseToolFilter(filterString) {
6789
7785
  enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[group]]);
6790
7786
  }
6791
7787
  } else if (isGroup(name)) {
6792
- enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[name]]);
7788
+ const resolved = resolveGroupAlias(name);
7789
+ enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[resolved]]);
6793
7790
  } else {
6794
7791
  enabledTools.add(name);
6795
7792
  }
@@ -6805,7 +7802,8 @@ function parseToolFilter(filterString) {
6805
7802
  for (const rule of rules) {
6806
7803
  if (rule.type === "exclude") {
6807
7804
  if (isGroup(rule.target)) {
6808
- for (const tool of TOOL_GROUPS[rule.target]) {
7805
+ const resolved = resolveGroupAlias(rule.target);
7806
+ for (const tool of TOOL_GROUPS[resolved]) {
6809
7807
  enabledTools.delete(tool);
6810
7808
  }
6811
7809
  } else {
@@ -6861,7 +7859,7 @@ var TOOL_GROUP_SCOPES = {
6861
7859
  search: SCOPES.READ,
6862
7860
  analytics: SCOPES.READ,
6863
7861
  relationships: SCOPES.READ,
6864
- export: SCOPES.READ,
7862
+ io: SCOPES.READ,
6865
7863
  admin: SCOPES.ADMIN,
6866
7864
  github: SCOPES.WRITE,
6867
7865
  backup: SCOPES.ADMIN,
@@ -6914,6 +7912,8 @@ for (const [group, tools] of Object.entries(TOOL_GROUPS)) {
6914
7912
  }
6915
7913
  }
6916
7914
  }
7915
+ toolScopeMap.set("import_markdown", SCOPES.WRITE);
7916
+ toolScopeMap.set("team_import_markdown", SCOPES.WRITE);
6917
7917
  function getRequiredScope(toolName) {
6918
7918
  return toolScopeMap.get(toolName) ?? SCOPES.READ;
6919
7919
  }
@@ -7147,10 +8147,10 @@ function getToolIcon(group) {
7147
8147
  title: "Relationships",
7148
8148
  description: "Entry relationship management"
7149
8149
  },
7150
- export: {
7151
- iconUrl: "https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/export.svg",
7152
- title: "Export",
7153
- description: "Data export operations"
8150
+ io: {
8151
+ iconUrl: "https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/swap-horizontal.svg",
8152
+ title: "IO",
8153
+ description: "Import/export operations"
7154
8154
  },
7155
8155
  admin: {
7156
8156
  iconUrl: "https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/cog.svg",
@@ -7264,7 +8264,7 @@ function getAllToolDefinitions(context) {
7264
8264
  ...getSearchTools(context),
7265
8265
  ...getAnalyticsTools(context),
7266
8266
  ...getRelationshipTools(context),
7267
- ...getExportTools(context),
8267
+ ...getIoTools(context),
7268
8268
  ...getAdminTools(context),
7269
8269
  ...getGitHubTools(context),
7270
8270
  ...getBackupTools(context),