knowns 0.3.0 → 0.4.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.
@@ -10073,10 +10073,10 @@ var require_stringify = __commonJS({
10073
10073
  data = Object.assign({}, file3.data, data);
10074
10074
  const open = opts.delimiters[0];
10075
10075
  const close = opts.delimiters[1];
10076
- const matter3 = engine.stringify(data, options2).trim();
10076
+ const matter5 = engine.stringify(data, options2).trim();
10077
10077
  let buf = "";
10078
- if (matter3 !== "{}") {
10079
- buf = newline(open) + newline(matter3) + newline(close);
10078
+ if (matter5 !== "{}") {
10079
+ buf = newline(open) + newline(matter5) + newline(close);
10080
10080
  }
10081
10081
  if (typeof file3.excerpt === "string" && file3.excerpt !== "") {
10082
10082
  if (str2.indexOf(file3.excerpt.trim()) === -1) {
@@ -10182,19 +10182,19 @@ var require_gray_matter = __commonJS({
10182
10182
  var toFile = require_to_file();
10183
10183
  var parse4 = require_parse();
10184
10184
  var utils = require_utils2();
10185
- function matter3(input, options2) {
10185
+ function matter5(input, options2) {
10186
10186
  if (input === "") {
10187
10187
  return { data: {}, content: input, excerpt: "", orig: input };
10188
10188
  }
10189
10189
  let file3 = toFile(input);
10190
- const cached2 = matter3.cache[file3.content];
10190
+ const cached2 = matter5.cache[file3.content];
10191
10191
  if (!options2) {
10192
10192
  if (cached2) {
10193
10193
  file3 = Object.assign({}, cached2);
10194
10194
  file3.orig = cached2.orig;
10195
10195
  return file3;
10196
10196
  }
10197
- matter3.cache[file3.content] = file3;
10197
+ matter5.cache[file3.content] = file3;
10198
10198
  }
10199
10199
  return parseMatter(file3, options2);
10200
10200
  }
@@ -10216,7 +10216,7 @@ var require_gray_matter = __commonJS({
10216
10216
  }
10217
10217
  str2 = str2.slice(openLen);
10218
10218
  const len = str2.length;
10219
- const language = matter3.language(str2, opts);
10219
+ const language = matter5.language(str2, opts);
10220
10220
  if (language.name) {
10221
10221
  file3.language = language.name;
10222
10222
  str2 = str2.slice(language.raw.length);
@@ -10251,24 +10251,24 @@ var require_gray_matter = __commonJS({
10251
10251
  }
10252
10252
  return file3;
10253
10253
  }
10254
- matter3.engines = engines2;
10255
- matter3.stringify = function(file3, data, options2) {
10256
- if (typeof file3 === "string") file3 = matter3(file3, options2);
10254
+ matter5.engines = engines2;
10255
+ matter5.stringify = function(file3, data, options2) {
10256
+ if (typeof file3 === "string") file3 = matter5(file3, options2);
10257
10257
  return stringify(file3, data, options2);
10258
10258
  };
10259
- matter3.read = function(filepath, options2) {
10259
+ matter5.read = function(filepath, options2) {
10260
10260
  const str2 = fs.readFileSync(filepath, "utf8");
10261
- const file3 = matter3(str2, options2);
10261
+ const file3 = matter5(str2, options2);
10262
10262
  file3.path = filepath;
10263
10263
  return file3;
10264
10264
  };
10265
- matter3.test = function(str2, options2) {
10265
+ matter5.test = function(str2, options2) {
10266
10266
  return utils.startsWith(str2, defaults(options2).delimiters[0]);
10267
10267
  };
10268
- matter3.language = function(str2, options2) {
10268
+ matter5.language = function(str2, options2) {
10269
10269
  const opts = defaults(options2);
10270
10270
  const open = opts.delimiters[0];
10271
- if (matter3.test(str2)) {
10271
+ if (matter5.test(str2)) {
10272
10272
  str2 = str2.slice(open.length);
10273
10273
  }
10274
10274
  const language = str2.slice(0, str2.search(/\r?\n/));
@@ -10277,17 +10277,18 @@ var require_gray_matter = __commonJS({
10277
10277
  name: language ? language.trim() : ""
10278
10278
  };
10279
10279
  };
10280
- matter3.cache = {};
10281
- matter3.clearCache = function() {
10282
- matter3.cache = {};
10280
+ matter5.cache = {};
10281
+ matter5.clearCache = function() {
10282
+ matter5.cache = {};
10283
10283
  };
10284
- module2.exports = matter3;
10284
+ module2.exports = matter5;
10285
10285
  }
10286
10286
  });
10287
10287
 
10288
10288
  // src/mcp/server.ts
10289
- import { readFile as readFile2 } from "node:fs/promises";
10290
- import { join as join4 } from "node:path";
10289
+ import { existsSync as existsSync5 } from "node:fs";
10290
+ import { readFile as readFile4 } from "node:fs/promises";
10291
+ import { join as join8 } from "node:path";
10291
10292
 
10292
10293
  // node_modules/zod/v3/helpers/util.js
10293
10294
  var util;
@@ -29521,7 +29522,7 @@ var Protocol = class {
29521
29522
  const capturedTransport = this._transport;
29522
29523
  const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
29523
29524
  if (handler === void 0) {
29524
- const errorResponse = {
29525
+ const errorResponse2 = {
29525
29526
  jsonrpc: "2.0",
29526
29527
  id: request.id,
29527
29528
  error: {
@@ -29532,11 +29533,11 @@ var Protocol = class {
29532
29533
  if (relatedTaskId && this._taskMessageQueue) {
29533
29534
  this._enqueueTaskMessage(relatedTaskId, {
29534
29535
  type: "error",
29535
- message: errorResponse,
29536
+ message: errorResponse2,
29536
29537
  timestamp: Date.now()
29537
29538
  }, capturedTransport?.sessionId).catch((error46) => this._onerror(new Error(`Failed to enqueue error response: ${error46}`)));
29538
29539
  } else {
29539
- capturedTransport?.send(errorResponse).catch((error46) => this._onerror(new Error(`Failed to send an error response: ${error46}`)));
29540
+ capturedTransport?.send(errorResponse2).catch((error46) => this._onerror(new Error(`Failed to send an error response: ${error46}`)));
29540
29541
  }
29541
29542
  return;
29542
29543
  }
@@ -29601,7 +29602,7 @@ var Protocol = class {
29601
29602
  if (abortController.signal.aborted) {
29602
29603
  return;
29603
29604
  }
29604
- const errorResponse = {
29605
+ const errorResponse2 = {
29605
29606
  jsonrpc: "2.0",
29606
29607
  id: request.id,
29607
29608
  error: {
@@ -29613,11 +29614,11 @@ var Protocol = class {
29613
29614
  if (relatedTaskId && this._taskMessageQueue) {
29614
29615
  await this._enqueueTaskMessage(relatedTaskId, {
29615
29616
  type: "error",
29616
- message: errorResponse,
29617
+ message: errorResponse2,
29617
29618
  timestamp: Date.now()
29618
29619
  }, capturedTransport?.sessionId);
29619
29620
  } else {
29620
- await capturedTransport?.send(errorResponse);
29621
+ await capturedTransport?.send(errorResponse2);
29621
29622
  }
29622
29623
  }).catch((error46) => this._onerror(new Error(`Failed to send response: ${error46}`))).finally(() => {
29623
29624
  this._requestHandlerAbortControllers.delete(request.id);
@@ -31479,6 +31480,22 @@ var FileStore = class {
31479
31480
  return null;
31480
31481
  }
31481
31482
  }
31483
+ /**
31484
+ * Skipped files due to parse errors (for graceful degradation)
31485
+ */
31486
+ skippedFiles = [];
31487
+ /**
31488
+ * Get list of skipped files from last load
31489
+ */
31490
+ getSkippedFiles() {
31491
+ return this.skippedFiles;
31492
+ }
31493
+ /**
31494
+ * Clear skipped files list
31495
+ */
31496
+ clearSkippedFiles() {
31497
+ this.skippedFiles = [];
31498
+ }
31482
31499
  /**
31483
31500
  * Get all tasks
31484
31501
  */
@@ -31487,24 +31504,42 @@ var FileStore = class {
31487
31504
  const files = await readdir(this.tasksPath);
31488
31505
  const taskFiles = files.filter((f) => f.startsWith("task-") && f.endsWith(".md"));
31489
31506
  const allTimeEntries = await this.loadTimeEntries();
31507
+ this.skippedFiles = [];
31490
31508
  const tasksMap = /* @__PURE__ */ new Map();
31491
31509
  for (const taskFile of taskFiles) {
31492
- const filePath = join2(this.tasksPath, taskFile);
31493
- const content = await file2(filePath).text();
31494
- const taskData = parseTaskMarkdown(content);
31495
- if (taskData.id) {
31496
- const timeEntries = (allTimeEntries[taskData.id] || []).map((e) => ({
31497
- ...e,
31498
- startedAt: new Date(e.startedAt),
31499
- endedAt: e.endedAt ? new Date(e.endedAt) : void 0
31500
- }));
31501
- tasksMap.set(taskData.id, {
31502
- ...taskData,
31503
- subtasks: [],
31504
- timeEntries
31510
+ try {
31511
+ const filePath = join2(this.tasksPath, taskFile);
31512
+ const content = await file2(filePath).text();
31513
+ const taskData = parseTaskMarkdown(content);
31514
+ if (taskData.id) {
31515
+ const timeEntries = (allTimeEntries[taskData.id] || []).map((e) => ({
31516
+ ...e,
31517
+ startedAt: new Date(e.startedAt),
31518
+ endedAt: e.endedAt ? new Date(e.endedAt) : void 0
31519
+ }));
31520
+ tasksMap.set(taskData.id, {
31521
+ ...taskData,
31522
+ subtasks: [],
31523
+ timeEntries
31524
+ });
31525
+ } else {
31526
+ this.skippedFiles.push({
31527
+ file: taskFile,
31528
+ error: "Missing task ID in frontmatter"
31529
+ });
31530
+ }
31531
+ } catch (parseError) {
31532
+ this.skippedFiles.push({
31533
+ file: taskFile,
31534
+ error: parseError instanceof Error ? parseError.message : String(parseError)
31505
31535
  });
31506
31536
  }
31507
31537
  }
31538
+ if (this.skippedFiles.length > 0) {
31539
+ console.warn(
31540
+ `\u26A0 Skipped ${this.skippedFiles.length} corrupted task file(s). Run "knowns task validate <id>" or "knowns task repair <id>" to fix.`
31541
+ );
31542
+ }
31508
31543
  for (const task of tasksMap.values()) {
31509
31544
  const subtasks = [];
31510
31545
  for (const otherTask of tasksMap.values()) {
@@ -31750,9 +31785,95 @@ var FileStore = class {
31750
31785
  }
31751
31786
  };
31752
31787
 
31753
- // src/utils/doc-links.ts
31788
+ // src/mcp/server.ts
31789
+ var import_gray_matter4 = __toESM(require_gray_matter(), 1);
31790
+
31791
+ // src/utils/notify-server.ts
31792
+ import { existsSync as existsSync2, readFileSync } from "node:fs";
31793
+ import { join as join4 } from "node:path";
31794
+
31795
+ // src/utils/find-project-root.ts
31754
31796
  import { existsSync } from "node:fs";
31755
- import { join as join3 } from "node:path";
31797
+ import { dirname, join as join3 } from "node:path";
31798
+ function findProjectRoot(startPath = process.cwd()) {
31799
+ let currentPath = startPath;
31800
+ for (let i = 0; i < 20; i++) {
31801
+ const knownsPath = join3(currentPath, ".knowns");
31802
+ if (existsSync(knownsPath)) {
31803
+ return currentPath;
31804
+ }
31805
+ const parentPath = dirname(currentPath);
31806
+ if (parentPath === currentPath) {
31807
+ return null;
31808
+ }
31809
+ currentPath = parentPath;
31810
+ }
31811
+ return null;
31812
+ }
31813
+
31814
+ // src/utils/notify-server.ts
31815
+ var DEFAULT_PORT = 6420;
31816
+ function getServerPort() {
31817
+ try {
31818
+ const projectRoot = findProjectRoot();
31819
+ if (!projectRoot) return DEFAULT_PORT;
31820
+ const configPath = join4(projectRoot, ".knowns/config.json");
31821
+ if (!existsSync2(configPath)) return DEFAULT_PORT;
31822
+ const content = readFileSync(configPath, "utf-8");
31823
+ const config2 = JSON.parse(content);
31824
+ const port = config2.settings?.serverPort;
31825
+ return typeof port === "number" ? port : DEFAULT_PORT;
31826
+ } catch {
31827
+ return DEFAULT_PORT;
31828
+ }
31829
+ }
31830
+ async function notifyTaskUpdate(taskId) {
31831
+ try {
31832
+ const port = getServerPort();
31833
+ const response = await fetch(`http://localhost:${port}/api/notify`, {
31834
+ method: "POST",
31835
+ headers: { "Content-Type": "application/json" },
31836
+ body: JSON.stringify({ taskId }),
31837
+ signal: AbortSignal.timeout(1e3)
31838
+ // 1 second timeout
31839
+ });
31840
+ if (response.ok) {
31841
+ }
31842
+ } catch {
31843
+ }
31844
+ }
31845
+ async function notifyDocUpdate(docPath) {
31846
+ try {
31847
+ const port = getServerPort();
31848
+ await fetch(`http://localhost:${port}/api/notify`, {
31849
+ method: "POST",
31850
+ headers: { "Content-Type": "application/json" },
31851
+ body: JSON.stringify({ type: "docs:updated", docPath }),
31852
+ signal: AbortSignal.timeout(1e3)
31853
+ });
31854
+ } catch {
31855
+ }
31856
+ }
31857
+ async function notifyTimeUpdate(active) {
31858
+ try {
31859
+ const port = getServerPort();
31860
+ await fetch(`http://localhost:${port}/api/notify`, {
31861
+ method: "POST",
31862
+ headers: { "Content-Type": "application/json" },
31863
+ body: JSON.stringify({ type: "time:updated", active }),
31864
+ signal: AbortSignal.timeout(1e3)
31865
+ });
31866
+ } catch {
31867
+ }
31868
+ }
31869
+
31870
+ // src/mcp/utils.ts
31871
+ import { readFile as readFile2 } from "node:fs/promises";
31872
+ import { join as join6 } from "node:path";
31873
+
31874
+ // src/utils/doc-links.ts
31875
+ import { existsSync as existsSync3 } from "node:fs";
31876
+ import { join as join5 } from "node:path";
31756
31877
  function parseMarkdownLinks(text) {
31757
31878
  const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
31758
31879
  const links = [];
@@ -31768,7 +31889,7 @@ function parseMarkdownLinks(text) {
31768
31889
  function resolveDocReferences(content, projectRoot) {
31769
31890
  const links = parseMarkdownLinks(content);
31770
31891
  const docReferences = [];
31771
- const docsDir = join3(projectRoot, ".knowns", "docs");
31892
+ const docsDir = join5(projectRoot, ".knowns", "docs");
31772
31893
  for (const link of links) {
31773
31894
  if (link.target.startsWith("http://") || link.target.startsWith("https://")) {
31774
31895
  continue;
@@ -31784,8 +31905,8 @@ function resolveDocReferences(content, projectRoot) {
31784
31905
  if (!filename.endsWith(".md")) {
31785
31906
  filename = `${filename}.md`;
31786
31907
  }
31787
- const resolvedPath = join3(docsDir, filename);
31788
- const exists = existsSync(resolvedPath);
31908
+ const resolvedPath = join5(docsDir, filename);
31909
+ const exists = existsSync3(resolvedPath);
31789
31910
  docReferences.push({
31790
31911
  text: link.text,
31791
31912
  filename: link.target,
@@ -31796,9 +31917,8 @@ function resolveDocReferences(content, projectRoot) {
31796
31917
  return docReferences;
31797
31918
  }
31798
31919
 
31799
- // src/mcp/server.ts
31920
+ // src/mcp/utils.ts
31800
31921
  var import_gray_matter2 = __toESM(require_gray_matter(), 1);
31801
- var fileStore = new FileStore(process.cwd());
31802
31922
  function parseDuration(durationStr) {
31803
31923
  let totalSeconds = 0;
31804
31924
  const hoursMatch = durationStr.match(/(\d+)h/);
@@ -31830,7 +31950,7 @@ function formatDuration(seconds) {
31830
31950
  }
31831
31951
  async function fetchLinkedDocs(task) {
31832
31952
  const projectRoot = process.cwd();
31833
- const docsDir = join4(projectRoot, ".knowns", "docs");
31953
+ const docsDir = join6(projectRoot, ".knowns", "docs");
31834
31954
  const allContent = [task.description || "", task.implementationPlan || "", task.implementationNotes || ""].join("\n");
31835
31955
  const docRefs = resolveDocReferences(allContent, projectRoot);
31836
31956
  const linkedDocs = [];
@@ -31838,7 +31958,7 @@ async function fetchLinkedDocs(task) {
31838
31958
  if (!ref.exists) continue;
31839
31959
  try {
31840
31960
  const filename = ref.resolvedPath.replace("@.knowns/docs/", "");
31841
- const filepath = join4(docsDir, filename);
31961
+ const filepath = join6(docsDir, filename);
31842
31962
  const fileContent = await readFile2(filepath, "utf-8");
31843
31963
  const { data, content } = (0, import_gray_matter2.default)(fileContent);
31844
31964
  linkedDocs.push({
@@ -31851,6 +31971,28 @@ async function fetchLinkedDocs(task) {
31851
31971
  }
31852
31972
  return linkedDocs;
31853
31973
  }
31974
+ function successResponse(data) {
31975
+ return {
31976
+ content: [
31977
+ {
31978
+ type: "text",
31979
+ text: JSON.stringify({ success: true, ...data }, null, 2)
31980
+ }
31981
+ ]
31982
+ };
31983
+ }
31984
+ function errorResponse(error46) {
31985
+ return {
31986
+ content: [
31987
+ {
31988
+ type: "text",
31989
+ text: JSON.stringify({ success: false, error: error46 }, null, 2)
31990
+ }
31991
+ ]
31992
+ };
31993
+ }
31994
+
31995
+ // src/mcp/handlers/task.ts
31854
31996
  var createTaskSchema = external_exports3.object({
31855
31997
  title: external_exports3.string(),
31856
31998
  description: external_exports3.string().optional(),
@@ -31881,40 +32023,7 @@ var listTasksSchema = external_exports3.object({
31881
32023
  var searchTasksSchema = external_exports3.object({
31882
32024
  query: external_exports3.string()
31883
32025
  });
31884
- var startTimeSchema = external_exports3.object({
31885
- taskId: external_exports3.string()
31886
- });
31887
- var stopTimeSchema = external_exports3.object({
31888
- taskId: external_exports3.string()
31889
- });
31890
- var addTimeSchema = external_exports3.object({
31891
- taskId: external_exports3.string(),
31892
- duration: external_exports3.string(),
31893
- // e.g., "2h", "30m", "1h30m"
31894
- note: external_exports3.string().optional(),
31895
- date: external_exports3.string().optional()
31896
- // ISO date string
31897
- });
31898
- var getTimeReportSchema = external_exports3.object({
31899
- from: external_exports3.string().optional(),
31900
- // YYYY-MM-DD
31901
- to: external_exports3.string().optional(),
31902
- // YYYY-MM-DD
31903
- groupBy: external_exports3.enum(["task", "label", "status"]).optional()
31904
- });
31905
- var server = new Server(
31906
- {
31907
- name: "knowns-mcp-server",
31908
- version: "1.0.0"
31909
- },
31910
- {
31911
- capabilities: {
31912
- tools: {},
31913
- resources: {}
31914
- }
31915
- }
31916
- );
31917
- var tools = [
32026
+ var taskTools = [
31918
32027
  {
31919
32028
  name: "create_task",
31920
32029
  description: "Create a new task with title and optional description, status, priority, labels, and assignee",
@@ -32007,7 +32116,144 @@ var tools = [
32007
32116
  },
32008
32117
  required: ["query"]
32009
32118
  }
32010
- },
32119
+ }
32120
+ ];
32121
+ async function handleCreateTask(args, fileStore2) {
32122
+ const input = createTaskSchema.parse(args);
32123
+ const task = await fileStore2.createTask({
32124
+ title: input.title,
32125
+ description: input.description,
32126
+ status: input.status || "todo",
32127
+ priority: input.priority || "medium",
32128
+ assignee: input.assignee,
32129
+ labels: input.labels || [],
32130
+ parent: input.parent,
32131
+ subtasks: [],
32132
+ acceptanceCriteria: [],
32133
+ timeSpent: 0,
32134
+ timeEntries: []
32135
+ });
32136
+ await notifyTaskUpdate(task.id);
32137
+ return successResponse({
32138
+ task: {
32139
+ id: task.id,
32140
+ title: task.title,
32141
+ status: task.status,
32142
+ priority: task.priority
32143
+ }
32144
+ });
32145
+ }
32146
+ async function handleGetTask(args, fileStore2) {
32147
+ const input = getTaskSchema.parse(args);
32148
+ const task = await fileStore2.getTask(input.taskId);
32149
+ if (!task) {
32150
+ return errorResponse(`Task ${input.taskId} not found`);
32151
+ }
32152
+ const linkedDocs = await fetchLinkedDocs(task);
32153
+ return successResponse({
32154
+ task: {
32155
+ id: task.id,
32156
+ title: task.title,
32157
+ description: task.description,
32158
+ status: task.status,
32159
+ priority: task.priority,
32160
+ assignee: task.assignee,
32161
+ labels: task.labels,
32162
+ acceptanceCriteria: task.acceptanceCriteria,
32163
+ createdAt: task.createdAt,
32164
+ updatedAt: task.updatedAt,
32165
+ linkedDocumentation: linkedDocs
32166
+ }
32167
+ });
32168
+ }
32169
+ async function handleUpdateTask(args, fileStore2) {
32170
+ const input = updateTaskSchema.parse(args);
32171
+ const updates = {};
32172
+ if (input.title) updates.title = input.title;
32173
+ if (input.description) updates.description = input.description;
32174
+ if (input.status) updates.status = input.status;
32175
+ if (input.priority) updates.priority = input.priority;
32176
+ if (input.assignee) updates.assignee = input.assignee;
32177
+ if (input.labels) updates.labels = input.labels;
32178
+ const task = await fileStore2.updateTask(input.taskId, updates);
32179
+ await notifyTaskUpdate(task.id);
32180
+ return successResponse({
32181
+ task: {
32182
+ id: task.id,
32183
+ title: task.title,
32184
+ status: task.status,
32185
+ priority: task.priority
32186
+ }
32187
+ });
32188
+ }
32189
+ async function handleListTasks(args, fileStore2) {
32190
+ const input = listTasksSchema.parse(args);
32191
+ let tasks = await fileStore2.getAllTasks();
32192
+ if (input.status) {
32193
+ tasks = tasks.filter((t) => t.status === input.status);
32194
+ }
32195
+ if (input.priority) {
32196
+ tasks = tasks.filter((t) => t.priority === input.priority);
32197
+ }
32198
+ if (input.assignee) {
32199
+ tasks = tasks.filter((t) => t.assignee === input.assignee);
32200
+ }
32201
+ if (input.label) {
32202
+ tasks = tasks.filter((t) => t.labels.includes(input.label));
32203
+ }
32204
+ return successResponse({
32205
+ count: tasks.length,
32206
+ tasks: tasks.map((t) => ({
32207
+ id: t.id,
32208
+ title: t.title,
32209
+ status: t.status,
32210
+ priority: t.priority,
32211
+ assignee: t.assignee,
32212
+ labels: t.labels
32213
+ }))
32214
+ });
32215
+ }
32216
+ async function handleSearchTasks(args, fileStore2) {
32217
+ const input = searchTasksSchema.parse(args);
32218
+ const tasks = await fileStore2.getAllTasks();
32219
+ const query = input.query.toLowerCase();
32220
+ const results = tasks.filter(
32221
+ (t) => t.title.toLowerCase().includes(query) || t.description?.toLowerCase().includes(query) || t.labels.some((l) => l.toLowerCase().includes(query))
32222
+ );
32223
+ return successResponse({
32224
+ count: results.length,
32225
+ tasks: results.map((t) => ({
32226
+ id: t.id,
32227
+ title: t.title,
32228
+ status: t.status,
32229
+ priority: t.priority
32230
+ }))
32231
+ });
32232
+ }
32233
+
32234
+ // src/mcp/handlers/time.ts
32235
+ var startTimeSchema = external_exports3.object({
32236
+ taskId: external_exports3.string()
32237
+ });
32238
+ var stopTimeSchema = external_exports3.object({
32239
+ taskId: external_exports3.string()
32240
+ });
32241
+ var addTimeSchema = external_exports3.object({
32242
+ taskId: external_exports3.string(),
32243
+ duration: external_exports3.string(),
32244
+ // e.g., "2h", "30m", "1h30m"
32245
+ note: external_exports3.string().optional(),
32246
+ date: external_exports3.string().optional()
32247
+ // ISO date string
32248
+ });
32249
+ var getTimeReportSchema = external_exports3.object({
32250
+ from: external_exports3.string().optional(),
32251
+ // YYYY-MM-DD
32252
+ to: external_exports3.string().optional(),
32253
+ // YYYY-MM-DD
32254
+ groupBy: external_exports3.enum(["task", "label", "status"]).optional()
32255
+ });
32256
+ var timeTools = [
32011
32257
  {
32012
32258
  name: "start_time",
32013
32259
  description: "Start time tracking for a task",
@@ -32065,7 +32311,173 @@ var tools = [
32065
32311
  }
32066
32312
  }
32067
32313
  }
32068
- },
32314
+ }
32315
+ ];
32316
+ async function handleStartTime(args, fileStore2) {
32317
+ const input = startTimeSchema.parse(args);
32318
+ const task = await fileStore2.getTask(input.taskId);
32319
+ if (!task) {
32320
+ return errorResponse(`Task ${input.taskId} not found`);
32321
+ }
32322
+ const activeEntry = task.timeEntries.find((e) => !e.endedAt);
32323
+ if (activeEntry) {
32324
+ return errorResponse("Time tracking already active for this task");
32325
+ }
32326
+ const newEntry = {
32327
+ id: `entry-${Date.now()}`,
32328
+ startedAt: /* @__PURE__ */ new Date(),
32329
+ duration: 0,
32330
+ note: "Started via MCP"
32331
+ };
32332
+ await fileStore2.updateTask(input.taskId, {
32333
+ timeEntries: [...task.timeEntries, newEntry]
32334
+ });
32335
+ await notifyTaskUpdate(input.taskId);
32336
+ await notifyTimeUpdate({
32337
+ taskId: input.taskId,
32338
+ taskTitle: task.title,
32339
+ startedAt: newEntry.startedAt.toISOString(),
32340
+ pausedAt: null,
32341
+ totalPausedMs: 0
32342
+ });
32343
+ return successResponse({
32344
+ message: `Started tracking time for task ${input.taskId}`,
32345
+ startedAt: newEntry.startedAt
32346
+ });
32347
+ }
32348
+ async function handleStopTime(args, fileStore2) {
32349
+ const input = stopTimeSchema.parse(args);
32350
+ const task = await fileStore2.getTask(input.taskId);
32351
+ if (!task) {
32352
+ return errorResponse(`Task ${input.taskId} not found`);
32353
+ }
32354
+ const activeIndex = task.timeEntries.findIndex((e) => !e.endedAt);
32355
+ if (activeIndex === -1) {
32356
+ return errorResponse("No active time tracking for this task");
32357
+ }
32358
+ const endTime = /* @__PURE__ */ new Date();
32359
+ const startTime = task.timeEntries[activeIndex].startedAt;
32360
+ const duration3 = Math.floor((endTime.getTime() - new Date(startTime).getTime()) / 1e3);
32361
+ const updatedEntries = [...task.timeEntries];
32362
+ updatedEntries[activeIndex] = {
32363
+ ...updatedEntries[activeIndex],
32364
+ endedAt: endTime,
32365
+ duration: duration3
32366
+ };
32367
+ const newTimeSpent = task.timeSpent + duration3;
32368
+ await fileStore2.updateTask(input.taskId, {
32369
+ timeEntries: updatedEntries,
32370
+ timeSpent: newTimeSpent
32371
+ });
32372
+ await notifyTaskUpdate(input.taskId);
32373
+ await notifyTimeUpdate(null);
32374
+ return successResponse({
32375
+ message: `Stopped tracking time for task ${input.taskId}`,
32376
+ duration: formatDuration(duration3),
32377
+ totalTime: formatDuration(newTimeSpent)
32378
+ });
32379
+ }
32380
+ async function handleAddTime(args, fileStore2) {
32381
+ const input = addTimeSchema.parse(args);
32382
+ const task = await fileStore2.getTask(input.taskId);
32383
+ if (!task) {
32384
+ return errorResponse(`Task ${input.taskId} not found`);
32385
+ }
32386
+ const duration3 = parseDuration(input.duration);
32387
+ const startDate = input.date ? new Date(input.date) : /* @__PURE__ */ new Date();
32388
+ const newEntry = {
32389
+ id: `entry-${Date.now()}`,
32390
+ startedAt: startDate,
32391
+ endedAt: new Date(startDate.getTime() + duration3 * 1e3),
32392
+ duration: duration3,
32393
+ note: input.note || "Added via MCP"
32394
+ };
32395
+ const newTimeSpent = task.timeSpent + duration3;
32396
+ await fileStore2.updateTask(input.taskId, {
32397
+ timeEntries: [...task.timeEntries, newEntry],
32398
+ timeSpent: newTimeSpent
32399
+ });
32400
+ await notifyTaskUpdate(input.taskId);
32401
+ return successResponse({
32402
+ message: `Added ${formatDuration(duration3)} to task ${input.taskId}`,
32403
+ totalTime: formatDuration(newTimeSpent)
32404
+ });
32405
+ }
32406
+ async function handleGetTimeReport(args, fileStore2) {
32407
+ const input = getTimeReportSchema.parse(args);
32408
+ const tasks = await fileStore2.getAllTasks();
32409
+ let fromDate;
32410
+ let toDate;
32411
+ if (input.from) fromDate = new Date(input.from);
32412
+ if (input.to) toDate = new Date(input.to);
32413
+ const taskTimeData = tasks.map((task) => {
32414
+ let totalSeconds = 0;
32415
+ for (const entry of task.timeEntries) {
32416
+ if (entry.endedAt) {
32417
+ const entryDate = new Date(entry.startedAt);
32418
+ if (fromDate && entryDate < fromDate) continue;
32419
+ if (toDate && entryDate > toDate) continue;
32420
+ totalSeconds += entry.duration;
32421
+ }
32422
+ }
32423
+ return {
32424
+ taskId: task.id,
32425
+ title: task.title,
32426
+ status: task.status,
32427
+ labels: task.labels,
32428
+ totalSeconds
32429
+ };
32430
+ }).filter((data) => data.totalSeconds > 0);
32431
+ if (input.groupBy === "label") {
32432
+ const grouped = {};
32433
+ for (const data of taskTimeData) {
32434
+ for (const label of data.labels) {
32435
+ grouped[label] = (grouped[label] || 0) + data.totalSeconds;
32436
+ }
32437
+ if (data.labels.length === 0) {
32438
+ grouped["(no label)"] = (grouped["(no label)"] || 0) + data.totalSeconds;
32439
+ }
32440
+ }
32441
+ return successResponse({
32442
+ groupBy: "label",
32443
+ data: Object.entries(grouped).map(([label, seconds]) => ({
32444
+ label,
32445
+ time: formatDuration(seconds),
32446
+ seconds
32447
+ })),
32448
+ totalSeconds: Object.values(grouped).reduce((a, b) => a + b, 0)
32449
+ });
32450
+ }
32451
+ if (input.groupBy === "status") {
32452
+ const grouped = {};
32453
+ for (const data of taskTimeData) {
32454
+ grouped[data.status] = (grouped[data.status] || 0) + data.totalSeconds;
32455
+ }
32456
+ return successResponse({
32457
+ groupBy: "status",
32458
+ data: Object.entries(grouped).map(([status, seconds]) => ({
32459
+ status,
32460
+ time: formatDuration(seconds),
32461
+ seconds
32462
+ })),
32463
+ totalSeconds: Object.values(grouped).reduce((a, b) => a + b, 0)
32464
+ });
32465
+ }
32466
+ return successResponse({
32467
+ groupBy: "task",
32468
+ data: taskTimeData.map((data) => ({
32469
+ taskId: data.taskId,
32470
+ title: data.title,
32471
+ status: data.status,
32472
+ time: formatDuration(data.totalSeconds),
32473
+ seconds: data.totalSeconds
32474
+ })),
32475
+ totalSeconds: taskTimeData.reduce((sum, data) => sum + data.totalSeconds, 0)
32476
+ });
32477
+ }
32478
+
32479
+ // src/mcp/handlers/board.ts
32480
+ var boardTools = [
32069
32481
  {
32070
32482
  name: "get_board",
32071
32483
  description: "Get current board state with tasks grouped by status",
@@ -32075,6 +32487,404 @@ var tools = [
32075
32487
  }
32076
32488
  }
32077
32489
  ];
32490
+ async function handleGetBoard(fileStore2) {
32491
+ const tasks = await fileStore2.getAllTasks();
32492
+ const board = {
32493
+ todo: [],
32494
+ "in-progress": [],
32495
+ "in-review": [],
32496
+ done: [],
32497
+ blocked: []
32498
+ };
32499
+ for (const task of tasks) {
32500
+ if (board[task.status]) {
32501
+ board[task.status].push(task);
32502
+ }
32503
+ }
32504
+ const mapTask = (t) => ({
32505
+ id: t.id,
32506
+ title: t.title,
32507
+ priority: t.priority,
32508
+ assignee: t.assignee,
32509
+ labels: t.labels
32510
+ });
32511
+ return successResponse({
32512
+ board: {
32513
+ todo: board.todo.map(mapTask),
32514
+ "in-progress": board["in-progress"].map(mapTask),
32515
+ "in-review": board["in-review"].map(mapTask),
32516
+ done: board.done.map(mapTask),
32517
+ blocked: board.blocked.map(mapTask)
32518
+ },
32519
+ totalTasks: tasks.length
32520
+ });
32521
+ }
32522
+
32523
+ // src/mcp/handlers/doc.ts
32524
+ import { existsSync as existsSync4 } from "node:fs";
32525
+ import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, writeFile as writeFile2 } from "node:fs/promises";
32526
+ import { join as join7 } from "node:path";
32527
+ var import_gray_matter3 = __toESM(require_gray_matter(), 1);
32528
+ var DOCS_DIR = join7(process.cwd(), ".knowns", "docs");
32529
+ var listDocsSchema = external_exports3.object({
32530
+ tag: external_exports3.string().optional()
32531
+ });
32532
+ var getDocSchema = external_exports3.object({
32533
+ path: external_exports3.string()
32534
+ // Document path (filename or folder/filename)
32535
+ });
32536
+ var createDocSchema = external_exports3.object({
32537
+ title: external_exports3.string(),
32538
+ description: external_exports3.string().optional(),
32539
+ content: external_exports3.string().optional(),
32540
+ tags: external_exports3.array(external_exports3.string()).optional(),
32541
+ folder: external_exports3.string().optional()
32542
+ // Optional folder path
32543
+ });
32544
+ var updateDocSchema = external_exports3.object({
32545
+ path: external_exports3.string(),
32546
+ // Document path
32547
+ title: external_exports3.string().optional(),
32548
+ description: external_exports3.string().optional(),
32549
+ content: external_exports3.string().optional(),
32550
+ tags: external_exports3.array(external_exports3.string()).optional(),
32551
+ appendContent: external_exports3.string().optional()
32552
+ // Append to existing content
32553
+ });
32554
+ var searchDocsSchema = external_exports3.object({
32555
+ query: external_exports3.string(),
32556
+ tag: external_exports3.string().optional()
32557
+ });
32558
+ var docTools = [
32559
+ {
32560
+ name: "list_docs",
32561
+ description: "List all documentation files with optional tag filter",
32562
+ inputSchema: {
32563
+ type: "object",
32564
+ properties: {
32565
+ tag: { type: "string", description: "Filter by tag" }
32566
+ }
32567
+ }
32568
+ },
32569
+ {
32570
+ name: "get_doc",
32571
+ description: "Get a documentation file by path (filename or folder/filename)",
32572
+ inputSchema: {
32573
+ type: "object",
32574
+ properties: {
32575
+ path: {
32576
+ type: "string",
32577
+ description: "Document path (e.g., 'readme', 'guides/setup', 'conventions/naming.md')"
32578
+ }
32579
+ },
32580
+ required: ["path"]
32581
+ }
32582
+ },
32583
+ {
32584
+ name: "create_doc",
32585
+ description: "Create a new documentation file",
32586
+ inputSchema: {
32587
+ type: "object",
32588
+ properties: {
32589
+ title: { type: "string", description: "Document title" },
32590
+ description: { type: "string", description: "Document description" },
32591
+ content: { type: "string", description: "Initial content (markdown)" },
32592
+ tags: {
32593
+ type: "array",
32594
+ items: { type: "string" },
32595
+ description: "Document tags"
32596
+ },
32597
+ folder: {
32598
+ type: "string",
32599
+ description: "Folder path (e.g., 'guides', 'patterns/auth')"
32600
+ }
32601
+ },
32602
+ required: ["title"]
32603
+ }
32604
+ },
32605
+ {
32606
+ name: "update_doc",
32607
+ description: "Update an existing documentation file",
32608
+ inputSchema: {
32609
+ type: "object",
32610
+ properties: {
32611
+ path: {
32612
+ type: "string",
32613
+ description: "Document path (e.g., 'readme', 'guides/setup')"
32614
+ },
32615
+ title: { type: "string", description: "New title" },
32616
+ description: { type: "string", description: "New description" },
32617
+ content: { type: "string", description: "Replace content" },
32618
+ tags: {
32619
+ type: "array",
32620
+ items: { type: "string" },
32621
+ description: "New tags"
32622
+ },
32623
+ appendContent: {
32624
+ type: "string",
32625
+ description: "Append to existing content"
32626
+ }
32627
+ },
32628
+ required: ["path"]
32629
+ }
32630
+ },
32631
+ {
32632
+ name: "search_docs",
32633
+ description: "Search documentation by query string",
32634
+ inputSchema: {
32635
+ type: "object",
32636
+ properties: {
32637
+ query: { type: "string", description: "Search query" },
32638
+ tag: { type: "string", description: "Filter by tag" }
32639
+ },
32640
+ required: ["query"]
32641
+ }
32642
+ }
32643
+ ];
32644
+ async function ensureDocsDir() {
32645
+ if (!existsSync4(DOCS_DIR)) {
32646
+ await mkdir3(DOCS_DIR, { recursive: true });
32647
+ }
32648
+ }
32649
+ function titleToFilename(title) {
32650
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
32651
+ }
32652
+ async function getAllMdFiles(dir, basePath = "") {
32653
+ const files = [];
32654
+ if (!existsSync4(dir)) {
32655
+ return files;
32656
+ }
32657
+ const entries = await readdir2(dir, { withFileTypes: true });
32658
+ for (const entry of entries) {
32659
+ const fullPath = join7(dir, entry.name);
32660
+ const relativePath = basePath ? join7(basePath, entry.name) : entry.name;
32661
+ if (entry.isDirectory()) {
32662
+ const subFiles = await getAllMdFiles(fullPath, relativePath);
32663
+ files.push(...subFiles);
32664
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
32665
+ files.push(relativePath);
32666
+ }
32667
+ }
32668
+ return files;
32669
+ }
32670
+ async function resolveDocPath(name) {
32671
+ await ensureDocsDir();
32672
+ let filename = name.endsWith(".md") ? name : `${name}.md`;
32673
+ let filepath = join7(DOCS_DIR, filename);
32674
+ if (existsSync4(filepath)) {
32675
+ return { filepath, filename };
32676
+ }
32677
+ filename = `${titleToFilename(name)}.md`;
32678
+ filepath = join7(DOCS_DIR, filename);
32679
+ if (existsSync4(filepath)) {
32680
+ return { filepath, filename };
32681
+ }
32682
+ const allFiles = await getAllMdFiles(DOCS_DIR);
32683
+ const searchName = name.toLowerCase().replace(/\.md$/, "");
32684
+ const matchingFile = allFiles.find((file3) => {
32685
+ const fileNameOnly = file3.toLowerCase().replace(/\.md$/, "");
32686
+ const fileBaseName = file3.split("/").pop()?.toLowerCase().replace(/\.md$/, "");
32687
+ return fileNameOnly === searchName || fileBaseName === searchName || file3 === name;
32688
+ });
32689
+ if (matchingFile) {
32690
+ return {
32691
+ filepath: join7(DOCS_DIR, matchingFile),
32692
+ filename: matchingFile
32693
+ };
32694
+ }
32695
+ return null;
32696
+ }
32697
+ async function handleListDocs(args) {
32698
+ const input = listDocsSchema.parse(args);
32699
+ await ensureDocsDir();
32700
+ const mdFiles = await getAllMdFiles(DOCS_DIR);
32701
+ if (mdFiles.length === 0) {
32702
+ return successResponse({
32703
+ count: 0,
32704
+ docs: [],
32705
+ message: "No documentation files found"
32706
+ });
32707
+ }
32708
+ const docs = [];
32709
+ for (const file3 of mdFiles) {
32710
+ const content = await readFile3(join7(DOCS_DIR, file3), "utf-8");
32711
+ const { data } = (0, import_gray_matter3.default)(content);
32712
+ const metadata = data;
32713
+ if (input.tag && !metadata.tags?.includes(input.tag)) {
32714
+ continue;
32715
+ }
32716
+ docs.push({
32717
+ path: file3.replace(/\.md$/, ""),
32718
+ title: metadata.title || file3.replace(/\.md$/, ""),
32719
+ description: metadata.description,
32720
+ tags: metadata.tags,
32721
+ updatedAt: metadata.updatedAt
32722
+ });
32723
+ }
32724
+ return successResponse({
32725
+ count: docs.length,
32726
+ docs
32727
+ });
32728
+ }
32729
+ async function handleGetDoc(args) {
32730
+ const input = getDocSchema.parse(args);
32731
+ const resolved = await resolveDocPath(input.path);
32732
+ if (!resolved) {
32733
+ return errorResponse(`Documentation not found: ${input.path}`);
32734
+ }
32735
+ const fileContent = await readFile3(resolved.filepath, "utf-8");
32736
+ const { data, content } = (0, import_gray_matter3.default)(fileContent);
32737
+ const metadata = data;
32738
+ return successResponse({
32739
+ doc: {
32740
+ path: resolved.filename.replace(/\.md$/, ""),
32741
+ title: metadata.title,
32742
+ description: metadata.description,
32743
+ tags: metadata.tags,
32744
+ createdAt: metadata.createdAt,
32745
+ updatedAt: metadata.updatedAt,
32746
+ content: content.trim()
32747
+ }
32748
+ });
32749
+ }
32750
+ async function handleCreateDoc(args) {
32751
+ const input = createDocSchema.parse(args);
32752
+ await ensureDocsDir();
32753
+ const filename = `${titleToFilename(input.title)}.md`;
32754
+ let targetDir = DOCS_DIR;
32755
+ let relativePath = filename;
32756
+ if (input.folder) {
32757
+ const folderPath = input.folder.replace(/^\/|\/$/g, "");
32758
+ targetDir = join7(DOCS_DIR, folderPath);
32759
+ relativePath = join7(folderPath, filename);
32760
+ if (!existsSync4(targetDir)) {
32761
+ await mkdir3(targetDir, { recursive: true });
32762
+ }
32763
+ }
32764
+ const filepath = join7(targetDir, filename);
32765
+ if (existsSync4(filepath)) {
32766
+ return errorResponse(`Document already exists: ${relativePath}`);
32767
+ }
32768
+ const now = (/* @__PURE__ */ new Date()).toISOString();
32769
+ const metadata = {
32770
+ title: input.title,
32771
+ createdAt: now,
32772
+ updatedAt: now
32773
+ };
32774
+ if (input.description) {
32775
+ metadata.description = input.description;
32776
+ }
32777
+ if (input.tags) {
32778
+ metadata.tags = input.tags;
32779
+ }
32780
+ const initialContent = input.content || "# Content\n\nWrite your documentation here.";
32781
+ const fileContent = import_gray_matter3.default.stringify(initialContent, metadata);
32782
+ await writeFile2(filepath, fileContent, "utf-8");
32783
+ await notifyDocUpdate(relativePath);
32784
+ return successResponse({
32785
+ message: `Created documentation: ${relativePath}`,
32786
+ doc: {
32787
+ path: relativePath.replace(/\.md$/, ""),
32788
+ title: metadata.title,
32789
+ description: metadata.description,
32790
+ tags: metadata.tags
32791
+ }
32792
+ });
32793
+ }
32794
+ async function handleUpdateDoc(args) {
32795
+ const input = updateDocSchema.parse(args);
32796
+ const resolved = await resolveDocPath(input.path);
32797
+ if (!resolved) {
32798
+ return errorResponse(`Documentation not found: ${input.path}`);
32799
+ }
32800
+ const fileContent = await readFile3(resolved.filepath, "utf-8");
32801
+ const { data, content } = (0, import_gray_matter3.default)(fileContent);
32802
+ const metadata = data;
32803
+ if (input.title) metadata.title = input.title;
32804
+ if (input.description) metadata.description = input.description;
32805
+ if (input.tags) metadata.tags = input.tags;
32806
+ metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
32807
+ let updatedContent = content;
32808
+ if (input.content) {
32809
+ updatedContent = input.content;
32810
+ }
32811
+ if (input.appendContent) {
32812
+ updatedContent = `${content.trimEnd()}
32813
+
32814
+ ${input.appendContent}`;
32815
+ }
32816
+ const newFileContent = import_gray_matter3.default.stringify(updatedContent, metadata);
32817
+ await writeFile2(resolved.filepath, newFileContent, "utf-8");
32818
+ await notifyDocUpdate(resolved.filename);
32819
+ return successResponse({
32820
+ message: `Updated documentation: ${resolved.filename}`,
32821
+ doc: {
32822
+ path: resolved.filename.replace(/\.md$/, ""),
32823
+ title: metadata.title,
32824
+ description: metadata.description,
32825
+ tags: metadata.tags,
32826
+ updatedAt: metadata.updatedAt
32827
+ }
32828
+ });
32829
+ }
32830
+ async function handleSearchDocs(args) {
32831
+ const input = searchDocsSchema.parse(args);
32832
+ await ensureDocsDir();
32833
+ const mdFiles = await getAllMdFiles(DOCS_DIR);
32834
+ const query = input.query.toLowerCase();
32835
+ const results = [];
32836
+ for (const file3 of mdFiles) {
32837
+ const fileContent = await readFile3(join7(DOCS_DIR, file3), "utf-8");
32838
+ const { data, content } = (0, import_gray_matter3.default)(fileContent);
32839
+ const metadata = data;
32840
+ if (input.tag && !metadata.tags?.includes(input.tag)) {
32841
+ continue;
32842
+ }
32843
+ const titleMatch = metadata.title?.toLowerCase().includes(query);
32844
+ const descMatch = metadata.description?.toLowerCase().includes(query);
32845
+ const tagMatch = metadata.tags?.some((t) => t.toLowerCase().includes(query));
32846
+ const contentMatch = content.toLowerCase().includes(query);
32847
+ if (titleMatch || descMatch || tagMatch || contentMatch) {
32848
+ let matchContext;
32849
+ if (contentMatch) {
32850
+ const contentLower = content.toLowerCase();
32851
+ const matchIndex = contentLower.indexOf(query);
32852
+ if (matchIndex !== -1) {
32853
+ const start = Math.max(0, matchIndex - 50);
32854
+ const end = Math.min(content.length, matchIndex + query.length + 50);
32855
+ matchContext = `...${content.slice(start, end).replace(/\n/g, " ")}...`;
32856
+ }
32857
+ }
32858
+ results.push({
32859
+ path: file3.replace(/\.md$/, ""),
32860
+ title: metadata.title || file3.replace(/\.md$/, ""),
32861
+ description: metadata.description,
32862
+ tags: metadata.tags,
32863
+ matchContext
32864
+ });
32865
+ }
32866
+ }
32867
+ return successResponse({
32868
+ count: results.length,
32869
+ docs: results
32870
+ });
32871
+ }
32872
+
32873
+ // src/mcp/server.ts
32874
+ var fileStore = new FileStore(process.cwd());
32875
+ var server = new Server(
32876
+ {
32877
+ name: "knowns-mcp-server",
32878
+ version: "1.0.0"
32879
+ },
32880
+ {
32881
+ capabilities: {
32882
+ tools: {},
32883
+ resources: {}
32884
+ }
32885
+ }
32886
+ );
32887
+ var tools = [...taskTools, ...timeTools, ...boardTools, ...docTools];
32078
32888
  server.setRequestHandler(ListToolsRequestSchema, async () => {
32079
32889
  return { tools };
32080
32890
  });
@@ -32082,540 +32892,129 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
32082
32892
  const { name, arguments: args } = request.params;
32083
32893
  try {
32084
32894
  switch (name) {
32085
- case "create_task": {
32086
- const input = createTaskSchema.parse(args);
32087
- const task = await fileStore.createTask({
32088
- title: input.title,
32089
- description: input.description,
32090
- status: input.status || "todo",
32091
- priority: input.priority || "medium",
32092
- assignee: input.assignee,
32093
- labels: input.labels || [],
32094
- parent: input.parent,
32095
- subtasks: [],
32096
- acceptanceCriteria: [],
32097
- timeSpent: 0,
32098
- timeEntries: []
32099
- });
32100
- return {
32101
- content: [
32102
- {
32103
- type: "text",
32104
- text: JSON.stringify(
32105
- {
32106
- success: true,
32107
- task: {
32108
- id: task.id,
32109
- title: task.title,
32110
- status: task.status,
32111
- priority: task.priority
32112
- }
32113
- },
32114
- null,
32115
- 2
32116
- )
32117
- }
32118
- ]
32119
- };
32120
- }
32121
- case "get_task": {
32122
- const input = getTaskSchema.parse(args);
32123
- const task = await fileStore.getTask(input.taskId);
32124
- if (!task) {
32125
- return {
32126
- content: [
32127
- {
32128
- type: "text",
32129
- text: JSON.stringify(
32130
- {
32131
- success: false,
32132
- error: `Task ${input.taskId} not found`
32133
- },
32134
- null,
32135
- 2
32136
- )
32137
- }
32138
- ]
32139
- };
32140
- }
32141
- const linkedDocs = await fetchLinkedDocs(task);
32142
- return {
32143
- content: [
32144
- {
32145
- type: "text",
32146
- text: JSON.stringify(
32147
- {
32148
- success: true,
32149
- task: {
32150
- id: task.id,
32151
- title: task.title,
32152
- description: task.description,
32153
- status: task.status,
32154
- priority: task.priority,
32155
- assignee: task.assignee,
32156
- labels: task.labels,
32157
- acceptanceCriteria: task.acceptanceCriteria,
32158
- createdAt: task.createdAt,
32159
- updatedAt: task.updatedAt,
32160
- linkedDocumentation: linkedDocs
32161
- }
32162
- },
32163
- null,
32164
- 2
32165
- )
32166
- }
32167
- ]
32168
- };
32169
- }
32170
- case "update_task": {
32171
- const input = updateTaskSchema.parse(args);
32172
- const updates = {};
32173
- if (input.title) updates.title = input.title;
32174
- if (input.description) updates.description = input.description;
32175
- if (input.status) updates.status = input.status;
32176
- if (input.priority) updates.priority = input.priority;
32177
- if (input.assignee) updates.assignee = input.assignee;
32178
- if (input.labels) updates.labels = input.labels;
32179
- const task = await fileStore.updateTask(input.taskId, updates);
32180
- return {
32181
- content: [
32182
- {
32183
- type: "text",
32184
- text: JSON.stringify(
32185
- {
32186
- success: true,
32187
- task: {
32188
- id: task.id,
32189
- title: task.title,
32190
- status: task.status,
32191
- priority: task.priority
32192
- }
32193
- },
32194
- null,
32195
- 2
32196
- )
32197
- }
32198
- ]
32199
- };
32200
- }
32201
- case "list_tasks": {
32202
- const input = listTasksSchema.parse(args);
32203
- let tasks = await fileStore.getAllTasks();
32204
- if (input.status) {
32205
- tasks = tasks.filter((t) => t.status === input.status);
32206
- }
32207
- if (input.priority) {
32208
- tasks = tasks.filter((t) => t.priority === input.priority);
32209
- }
32210
- if (input.assignee) {
32211
- tasks = tasks.filter((t) => t.assignee === input.assignee);
32212
- }
32213
- if (input.label) {
32214
- tasks = tasks.filter((t) => t.labels.includes(input.label));
32215
- }
32216
- return {
32217
- content: [
32218
- {
32219
- type: "text",
32220
- text: JSON.stringify(
32221
- {
32222
- success: true,
32223
- count: tasks.length,
32224
- tasks: tasks.map((t) => ({
32225
- id: t.id,
32226
- title: t.title,
32227
- status: t.status,
32228
- priority: t.priority,
32229
- assignee: t.assignee,
32230
- labels: t.labels
32231
- }))
32232
- },
32233
- null,
32234
- 2
32235
- )
32236
- }
32237
- ]
32238
- };
32239
- }
32240
- case "search_tasks": {
32241
- const input = searchTasksSchema.parse(args);
32242
- const tasks = await fileStore.getAllTasks();
32243
- const query = input.query.toLowerCase();
32244
- const results = tasks.filter(
32245
- (t) => t.title.toLowerCase().includes(query) || t.description?.toLowerCase().includes(query) || t.labels.some((l) => l.toLowerCase().includes(query))
32246
- );
32247
- return {
32248
- content: [
32249
- {
32250
- type: "text",
32251
- text: JSON.stringify(
32252
- {
32253
- success: true,
32254
- count: results.length,
32255
- tasks: results.map((t) => ({
32256
- id: t.id,
32257
- title: t.title,
32258
- status: t.status,
32259
- priority: t.priority
32260
- }))
32261
- },
32262
- null,
32263
- 2
32264
- )
32265
- }
32266
- ]
32267
- };
32268
- }
32269
- case "start_time": {
32270
- const input = startTimeSchema.parse(args);
32271
- const task = await fileStore.getTask(input.taskId);
32272
- if (!task) {
32273
- return {
32274
- content: [
32275
- {
32276
- type: "text",
32277
- text: JSON.stringify({
32278
- success: false,
32279
- error: `Task ${input.taskId} not found`
32280
- })
32281
- }
32282
- ]
32283
- };
32284
- }
32285
- const activeEntry = task.timeEntries.find((e) => !e.endedAt);
32286
- if (activeEntry) {
32287
- return {
32288
- content: [
32289
- {
32290
- type: "text",
32291
- text: JSON.stringify({
32292
- success: false,
32293
- error: "Time tracking already active for this task"
32294
- })
32295
- }
32296
- ]
32297
- };
32298
- }
32299
- const newEntry = {
32300
- id: `entry-${Date.now()}`,
32301
- startedAt: /* @__PURE__ */ new Date(),
32302
- duration: 0,
32303
- note: "Started via MCP"
32304
- };
32305
- await fileStore.updateTask(input.taskId, {
32306
- timeEntries: [...task.timeEntries, newEntry]
32307
- });
32308
- return {
32309
- content: [
32310
- {
32311
- type: "text",
32312
- text: JSON.stringify({
32313
- success: true,
32314
- message: `Started tracking time for task ${input.taskId}`,
32315
- startedAt: newEntry.startedAt
32316
- })
32317
- }
32318
- ]
32319
- };
32320
- }
32321
- case "stop_time": {
32322
- const input = stopTimeSchema.parse(args);
32323
- const task = await fileStore.getTask(input.taskId);
32324
- if (!task) {
32325
- return {
32326
- content: [
32327
- {
32328
- type: "text",
32329
- text: JSON.stringify({
32330
- success: false,
32331
- error: `Task ${input.taskId} not found`
32332
- })
32333
- }
32334
- ]
32335
- };
32336
- }
32337
- const activeIndex = task.timeEntries.findIndex((e) => !e.endedAt);
32338
- if (activeIndex === -1) {
32339
- return {
32340
- content: [
32341
- {
32342
- type: "text",
32343
- text: JSON.stringify({
32344
- success: false,
32345
- error: "No active time tracking for this task"
32346
- })
32347
- }
32348
- ]
32349
- };
32350
- }
32351
- const endTime = /* @__PURE__ */ new Date();
32352
- const startTime = task.timeEntries[activeIndex].startedAt;
32353
- const duration3 = Math.floor((endTime.getTime() - startTime.getTime()) / 1e3);
32354
- const updatedEntries = [...task.timeEntries];
32355
- updatedEntries[activeIndex] = {
32356
- ...updatedEntries[activeIndex],
32357
- endedAt: endTime,
32358
- duration: duration3
32359
- };
32360
- const newTimeSpent = task.timeSpent + duration3;
32361
- await fileStore.updateTask(input.taskId, {
32362
- timeEntries: updatedEntries,
32363
- timeSpent: newTimeSpent
32364
- });
32365
- return {
32366
- content: [
32367
- {
32368
- type: "text",
32369
- text: JSON.stringify({
32370
- success: true,
32371
- message: `Stopped tracking time for task ${input.taskId}`,
32372
- duration: formatDuration(duration3),
32373
- totalTime: formatDuration(newTimeSpent)
32374
- })
32375
- }
32376
- ]
32377
- };
32378
- }
32379
- case "add_time": {
32380
- const input = addTimeSchema.parse(args);
32381
- const task = await fileStore.getTask(input.taskId);
32382
- if (!task) {
32383
- return {
32384
- content: [
32385
- {
32386
- type: "text",
32387
- text: JSON.stringify({
32388
- success: false,
32389
- error: `Task ${input.taskId} not found`
32390
- })
32391
- }
32392
- ]
32393
- };
32394
- }
32395
- const duration3 = parseDuration(input.duration);
32396
- const startDate = input.date ? new Date(input.date) : /* @__PURE__ */ new Date();
32397
- const newEntry = {
32398
- id: `entry-${Date.now()}`,
32399
- startedAt: startDate,
32400
- endedAt: new Date(startDate.getTime() + duration3 * 1e3),
32401
- duration: duration3,
32402
- note: input.note || "Added via MCP"
32403
- };
32404
- const newTimeSpent = task.timeSpent + duration3;
32405
- await fileStore.updateTask(input.taskId, {
32406
- timeEntries: [...task.timeEntries, newEntry],
32407
- timeSpent: newTimeSpent
32408
- });
32409
- return {
32410
- content: [
32411
- {
32412
- type: "text",
32413
- text: JSON.stringify({
32414
- success: true,
32415
- message: `Added ${formatDuration(duration3)} to task ${input.taskId}`,
32416
- totalTime: formatDuration(newTimeSpent)
32417
- })
32418
- }
32419
- ]
32420
- };
32421
- }
32422
- case "get_time_report": {
32423
- const input = getTimeReportSchema.parse(args);
32424
- const tasks = await fileStore.getAllTasks();
32425
- let fromDate;
32426
- let toDate;
32427
- if (input.from) fromDate = new Date(input.from);
32428
- if (input.to) toDate = new Date(input.to);
32429
- const taskTimeData = tasks.map((task) => {
32430
- let totalSeconds = 0;
32431
- for (const entry of task.timeEntries) {
32432
- if (entry.endedAt) {
32433
- const entryDate = new Date(entry.startedAt);
32434
- if (fromDate && entryDate < fromDate) continue;
32435
- if (toDate && entryDate > toDate) continue;
32436
- totalSeconds += entry.duration;
32437
- }
32438
- }
32439
- return {
32440
- taskId: task.id,
32441
- title: task.title,
32442
- status: task.status,
32443
- labels: task.labels,
32444
- totalSeconds
32445
- };
32446
- }).filter((data) => data.totalSeconds > 0);
32447
- if (input.groupBy === "label") {
32448
- const grouped = {};
32449
- for (const data of taskTimeData) {
32450
- for (const label of data.labels) {
32451
- grouped[label] = (grouped[label] || 0) + data.totalSeconds;
32452
- }
32453
- if (data.labels.length === 0) {
32454
- grouped["(no label)"] = (grouped["(no label)"] || 0) + data.totalSeconds;
32455
- }
32456
- }
32457
- return {
32458
- content: [
32459
- {
32460
- type: "text",
32461
- text: JSON.stringify(
32462
- {
32463
- success: true,
32464
- groupBy: "label",
32465
- data: Object.entries(grouped).map(([label, seconds]) => ({
32466
- label,
32467
- time: formatDuration(seconds),
32468
- seconds
32469
- })),
32470
- totalSeconds: Object.values(grouped).reduce((a, b) => a + b, 0)
32471
- },
32472
- null,
32473
- 2
32474
- )
32475
- }
32476
- ]
32477
- };
32478
- }
32479
- if (input.groupBy === "status") {
32480
- const grouped = {};
32481
- for (const data of taskTimeData) {
32482
- grouped[data.status] = (grouped[data.status] || 0) + data.totalSeconds;
32483
- }
32484
- return {
32485
- content: [
32486
- {
32487
- type: "text",
32488
- text: JSON.stringify(
32489
- {
32490
- success: true,
32491
- groupBy: "status",
32492
- data: Object.entries(grouped).map(([status, seconds]) => ({
32493
- status,
32494
- time: formatDuration(seconds),
32495
- seconds
32496
- })),
32497
- totalSeconds: Object.values(grouped).reduce((a, b) => a + b, 0)
32498
- },
32499
- null,
32500
- 2
32501
- )
32502
- }
32503
- ]
32504
- };
32505
- }
32506
- return {
32507
- content: [
32508
- {
32509
- type: "text",
32510
- text: JSON.stringify(
32511
- {
32512
- success: true,
32513
- groupBy: "task",
32514
- data: taskTimeData.map((data) => ({
32515
- taskId: data.taskId,
32516
- title: data.title,
32517
- status: data.status,
32518
- time: formatDuration(data.totalSeconds),
32519
- seconds: data.totalSeconds
32520
- })),
32521
- totalSeconds: taskTimeData.reduce((sum, data) => sum + data.totalSeconds, 0)
32522
- },
32523
- null,
32524
- 2
32525
- )
32526
- }
32527
- ]
32528
- };
32529
- }
32530
- case "get_board": {
32531
- const tasks = await fileStore.getAllTasks();
32532
- const board = {
32533
- todo: [],
32534
- "in-progress": [],
32535
- "in-review": [],
32536
- done: [],
32537
- blocked: []
32538
- };
32539
- for (const task of tasks) {
32540
- if (board[task.status]) {
32541
- board[task.status].push(task);
32542
- }
32543
- }
32544
- return {
32545
- content: [
32546
- {
32547
- type: "text",
32548
- text: JSON.stringify(
32549
- {
32550
- success: true,
32551
- board: {
32552
- todo: board.todo.map((t) => ({
32553
- id: t.id,
32554
- title: t.title,
32555
- priority: t.priority,
32556
- assignee: t.assignee,
32557
- labels: t.labels
32558
- })),
32559
- "in-progress": board["in-progress"].map((t) => ({
32560
- id: t.id,
32561
- title: t.title,
32562
- priority: t.priority,
32563
- assignee: t.assignee,
32564
- labels: t.labels
32565
- })),
32566
- "in-review": board["in-review"].map((t) => ({
32567
- id: t.id,
32568
- title: t.title,
32569
- priority: t.priority,
32570
- assignee: t.assignee,
32571
- labels: t.labels
32572
- })),
32573
- done: board.done.map((t) => ({
32574
- id: t.id,
32575
- title: t.title,
32576
- priority: t.priority,
32577
- assignee: t.assignee,
32578
- labels: t.labels
32579
- })),
32580
- blocked: board.blocked.map((t) => ({
32581
- id: t.id,
32582
- title: t.title,
32583
- priority: t.priority,
32584
- assignee: t.assignee,
32585
- labels: t.labels
32586
- }))
32587
- },
32588
- totalTasks: tasks.length
32589
- },
32590
- null,
32591
- 2
32592
- )
32593
- }
32594
- ]
32595
- };
32596
- }
32895
+ // Task handlers
32896
+ case "create_task":
32897
+ return await handleCreateTask(args, fileStore);
32898
+ case "get_task":
32899
+ return await handleGetTask(args, fileStore);
32900
+ case "update_task":
32901
+ return await handleUpdateTask(args, fileStore);
32902
+ case "list_tasks":
32903
+ return await handleListTasks(args, fileStore);
32904
+ case "search_tasks":
32905
+ return await handleSearchTasks(args, fileStore);
32906
+ // Time handlers
32907
+ case "start_time":
32908
+ return await handleStartTime(args, fileStore);
32909
+ case "stop_time":
32910
+ return await handleStopTime(args, fileStore);
32911
+ case "add_time":
32912
+ return await handleAddTime(args, fileStore);
32913
+ case "get_time_report":
32914
+ return await handleGetTimeReport(args, fileStore);
32915
+ // Board handlers
32916
+ case "get_board":
32917
+ return await handleGetBoard(fileStore);
32918
+ // Doc handlers
32919
+ case "list_docs":
32920
+ return await handleListDocs(args);
32921
+ case "get_doc":
32922
+ return await handleGetDoc(args);
32923
+ case "create_doc":
32924
+ return await handleCreateDoc(args);
32925
+ case "update_doc":
32926
+ return await handleUpdateDoc(args);
32927
+ case "search_docs":
32928
+ return await handleSearchDocs(args);
32597
32929
  default:
32598
- return {
32599
- content: [
32600
- {
32601
- type: "text",
32602
- text: JSON.stringify({
32603
- success: false,
32604
- error: `Unknown tool: ${name}`
32605
- })
32606
- }
32607
- ]
32608
- };
32930
+ return errorResponse(`Unknown tool: ${name}`);
32609
32931
  }
32610
32932
  } catch (error46) {
32933
+ return errorResponse(error46 instanceof Error ? error46.message : String(error46));
32934
+ }
32935
+ });
32936
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
32937
+ const tasks = await fileStore.getAllTasks();
32938
+ const docsDir = join8(process.cwd(), ".knowns", "docs");
32939
+ const taskResources = tasks.map((task) => ({
32940
+ uri: `knowns://task/${task.id}`,
32941
+ name: task.title,
32942
+ mimeType: "application/json",
32943
+ description: `Task #${task.id}: ${task.title}`
32944
+ }));
32945
+ const docResources = [];
32946
+ if (existsSync5(docsDir)) {
32947
+ const { readdir: readdir3 } = await import("node:fs/promises");
32948
+ async function getAllMdFiles2(dir, basePath = "") {
32949
+ const files = [];
32950
+ const entries = await readdir3(dir, { withFileTypes: true });
32951
+ for (const entry of entries) {
32952
+ const fullPath = join8(dir, entry.name);
32953
+ const relativePath = basePath ? join8(basePath, entry.name) : entry.name;
32954
+ if (entry.isDirectory()) {
32955
+ const subFiles = await getAllMdFiles2(fullPath, relativePath);
32956
+ files.push(...subFiles);
32957
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
32958
+ files.push(relativePath);
32959
+ }
32960
+ }
32961
+ return files;
32962
+ }
32963
+ const mdFiles = await getAllMdFiles2(docsDir);
32964
+ for (const file3 of mdFiles) {
32965
+ const filepath = join8(docsDir, file3);
32966
+ const content = await readFile4(filepath, "utf-8");
32967
+ const { data } = (0, import_gray_matter4.default)(content);
32968
+ docResources.push({
32969
+ uri: `knowns://doc/${file3.replace(/\.md$/, "")}`,
32970
+ name: data.title || file3.replace(/\.md$/, ""),
32971
+ mimeType: "text/markdown",
32972
+ description: data.description || `Documentation: ${file3}`
32973
+ });
32974
+ }
32975
+ }
32976
+ return {
32977
+ resources: [...taskResources, ...docResources]
32978
+ };
32979
+ });
32980
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
32981
+ const uri = request.params.uri;
32982
+ const taskMatch = uri.match(/^knowns:\/\/task\/(.+)$/);
32983
+ if (taskMatch) {
32984
+ const taskId = taskMatch[1];
32985
+ const task = await fileStore.getTask(taskId);
32986
+ if (!task) {
32987
+ throw new Error(`Task ${taskId} not found`);
32988
+ }
32611
32989
  return {
32612
- content: [
32990
+ contents: [
32613
32991
  {
32614
- type: "text",
32992
+ uri,
32993
+ mimeType: "application/json",
32994
+ text: JSON.stringify(task, null, 2)
32995
+ }
32996
+ ]
32997
+ };
32998
+ }
32999
+ const docMatch = uri.match(/^knowns:\/\/doc\/(.+)$/);
33000
+ if (docMatch) {
33001
+ const docPath = docMatch[1];
33002
+ const docsDir = join8(process.cwd(), ".knowns", "docs");
33003
+ const filepath = join8(docsDir, `${docPath}.md`);
33004
+ if (!existsSync5(filepath)) {
33005
+ throw new Error(`Documentation ${docPath} not found`);
33006
+ }
33007
+ const content = await readFile4(filepath, "utf-8");
33008
+ const { data, content: docContent } = (0, import_gray_matter4.default)(content);
33009
+ return {
33010
+ contents: [
33011
+ {
33012
+ uri,
33013
+ mimeType: "text/markdown",
32615
33014
  text: JSON.stringify(
32616
33015
  {
32617
- success: false,
32618
- error: error46 instanceof Error ? error46.message : String(error46)
33016
+ metadata: data,
33017
+ content: docContent.trim()
32619
33018
  },
32620
33019
  null,
32621
33020
  2
@@ -32624,48 +33023,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
32624
33023
  ]
32625
33024
  };
32626
33025
  }
33026
+ throw new Error(`Invalid resource URI: ${uri}`);
32627
33027
  });
32628
- server.setRequestHandler(ListResourcesRequestSchema, async () => {
32629
- const tasks = await fileStore.getAllTasks();
32630
- return {
32631
- resources: tasks.map((task) => ({
32632
- uri: `knowns://task/${task.id}`,
32633
- name: task.title,
32634
- mimeType: "application/json",
32635
- description: `Task #${task.id}: ${task.title}`
32636
- }))
32637
- };
32638
- });
32639
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
32640
- const uri = request.params.uri;
32641
- const match = uri.match(/^knowns:\/\/task\/(.+)$/);
32642
- if (!match) {
32643
- throw new Error(`Invalid resource URI: ${uri}`);
32644
- }
32645
- const taskId = match[1];
32646
- const task = await fileStore.getTask(taskId);
32647
- if (!task) {
32648
- throw new Error(`Task ${taskId} not found`);
33028
+ async function startMcpServer(options2 = {}) {
33029
+ if (options2.verbose) {
33030
+ console.error("Knowns MCP Server starting...");
32649
33031
  }
32650
- return {
32651
- contents: [
32652
- {
32653
- uri,
32654
- mimeType: "application/json",
32655
- text: JSON.stringify(task, null, 2)
32656
- }
32657
- ]
32658
- };
32659
- });
32660
- async function main() {
32661
33032
  const transport = new StdioServerTransport();
32662
33033
  await server.connect(transport);
32663
- console.error("Knowns MCP Server running on stdio");
33034
+ if (options2.verbose) {
33035
+ console.error("Knowns MCP Server running on stdio");
33036
+ }
32664
33037
  }
32665
- main().catch((error46) => {
32666
- console.error("Fatal error:", error46);
32667
- process.exit(1);
32668
- });
33038
+ var isStandaloneServer = process.argv[1]?.includes("mcp/server") || process.argv[1]?.includes("mcp\\server");
33039
+ if (isStandaloneServer) {
33040
+ startMcpServer({ verbose: true }).catch((error46) => {
33041
+ console.error("Fatal error:", error46);
33042
+ process.exit(1);
33043
+ });
33044
+ }
33045
+ export {
33046
+ startMcpServer
33047
+ };
32669
33048
  /*! Bundled license information:
32670
33049
 
32671
33050
  is-extendable/index.js: