@opsee/mcp-server 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gen/api/v1/board_pb.d.ts +225 -1
- package/gen/api/v1/board_pb.js +54 -5
- package/gen/api/v1/comment_pb.d.ts +15 -0
- package/gen/api/v1/comment_pb.js +1 -1
- package/gen/api/v1/initiative_pb.d.ts +785 -0
- package/gen/api/v1/initiative_pb.js +199 -0
- package/gen/api/v1/integration_pb.d.ts +750 -0
- package/gen/api/v1/integration_pb.js +193 -0
- package/gen/api/v1/models_pb.d.ts +234 -0
- package/gen/api/v1/models_pb.js +17 -3
- package/gen/api/v1/readiness_pb.d.ts +834 -0
- package/gen/api/v1/readiness_pb.js +197 -0
- package/gen/api/v1/task_pb.d.ts +8 -0
- package/gen/api/v1/task_pb.js +1 -1
- package/package.json +1 -1
- package/src/client/api.ts +3 -0
- package/src/server.ts +2 -0
- package/src/tools/comments.ts +10 -6
- package/src/tools/initiatives.ts +321 -0
- package/src/tools/tasks.ts +100 -1
- package/src/utils/format.ts +244 -1
package/src/utils/format.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { Task, Project, Cycle, DocPage, DocSpace, Milestone, MilestoneTask, User, Label, TaskLabel, Comment, TaskDependency } from "../../gen/api/v1/models_pb.js";
|
|
1
|
+
import type { Task, Project, Cycle, DocPage, DocSpace, Milestone, MilestoneTask, User, Label, TaskLabel, Comment, TaskDependency, Initiative, InitiativeMemoryEntry, TaskPullRequest } from "../../gen/api/v1/models_pb.js";
|
|
2
2
|
import { TaskDependencyType } from "../../gen/api/v1/models_pb.js";
|
|
3
|
+
import type { InitiativeGraph, InitiativeSlice } from "../../gen/api/v1/initiative_pb.js";
|
|
3
4
|
|
|
4
5
|
export function formatProject(p: Project): string {
|
|
5
6
|
const lines = [
|
|
@@ -284,6 +285,248 @@ export function formatTaskDependencyList(deps: TaskDependency[]): string {
|
|
|
284
285
|
deps.map((d, i) => `${i + 1}. ${formatTaskDependency(d)}`).join("\n\n");
|
|
285
286
|
}
|
|
286
287
|
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// BlockNote JSON → Markdown
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
type BNInlineStyle = { bold?: boolean; italic?: boolean; underline?: boolean; strikethrough?: boolean; code?: boolean };
|
|
293
|
+
type BNTextContent = { type: "text"; text: string; styles?: BNInlineStyle };
|
|
294
|
+
type BNLinkContent = { type: "link"; href: string; content: BNTextContent[] };
|
|
295
|
+
type BNInlineContent = BNTextContent | BNLinkContent;
|
|
296
|
+
|
|
297
|
+
interface BNBlock {
|
|
298
|
+
type: string;
|
|
299
|
+
props?: Record<string, unknown>;
|
|
300
|
+
content?: BNInlineContent[];
|
|
301
|
+
children?: BNBlock[];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function renderInline(content: BNInlineContent[]): string {
|
|
305
|
+
return content.map((node) => {
|
|
306
|
+
if (node.type === "link") {
|
|
307
|
+
const inner = renderInline(node.content ?? []);
|
|
308
|
+
return `[${inner}](${node.href})`;
|
|
309
|
+
}
|
|
310
|
+
// text node
|
|
311
|
+
const t = node as BNTextContent;
|
|
312
|
+
let s = t.text ?? "";
|
|
313
|
+
if (t.styles?.code) s = `\`${s}\``;
|
|
314
|
+
if (t.styles?.bold) s = `**${s}**`;
|
|
315
|
+
if (t.styles?.italic) s = `*${s}*`;
|
|
316
|
+
return s;
|
|
317
|
+
}).join("");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function renderBlock(block: BNBlock, indent: string): string[] {
|
|
321
|
+
const lines: string[] = [];
|
|
322
|
+
const inline = renderInline(block.content ?? []);
|
|
323
|
+
|
|
324
|
+
switch (block.type) {
|
|
325
|
+
case "heading": {
|
|
326
|
+
const level = (block.props?.level as number) || 1;
|
|
327
|
+
const prefix = "#".repeat(Math.min(Math.max(level, 1), 3));
|
|
328
|
+
lines.push(`${indent}${prefix} ${inline}`);
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
case "bulletListItem":
|
|
332
|
+
lines.push(`${indent}- ${inline}`);
|
|
333
|
+
break;
|
|
334
|
+
case "numberedListItem":
|
|
335
|
+
lines.push(`${indent}1. ${inline}`);
|
|
336
|
+
break;
|
|
337
|
+
case "checkListItem": {
|
|
338
|
+
const checked = block.props?.checked ? "x" : " ";
|
|
339
|
+
lines.push(`${indent}- [${checked}] ${inline}`);
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
case "codeBlock": {
|
|
343
|
+
const lang = (block.props?.language as string) || "";
|
|
344
|
+
lines.push(`${indent}\`\`\`${lang}`);
|
|
345
|
+
lines.push(`${indent}${inline}`);
|
|
346
|
+
lines.push(`${indent}\`\`\``);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
case "quote":
|
|
350
|
+
lines.push(`${indent}> ${inline}`);
|
|
351
|
+
break;
|
|
352
|
+
case "paragraph":
|
|
353
|
+
default:
|
|
354
|
+
lines.push(`${indent}${inline}`);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (block.children?.length) {
|
|
359
|
+
for (const child of block.children) {
|
|
360
|
+
lines.push(...renderBlock(child, indent + " "));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return lines;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function blockNoteToMarkdown(content: string): string {
|
|
368
|
+
try {
|
|
369
|
+
const parsed = JSON.parse(content);
|
|
370
|
+
if (!Array.isArray(parsed)) return content;
|
|
371
|
+
|
|
372
|
+
const mdLines: string[] = [];
|
|
373
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
374
|
+
const block = parsed[i] as BNBlock;
|
|
375
|
+
if (typeof block !== "object" || block === null || !block.type) return content;
|
|
376
|
+
const blockLines = renderBlock(block, "");
|
|
377
|
+
mdLines.push(...blockLines);
|
|
378
|
+
// Separate top-level blocks with a blank line (except last)
|
|
379
|
+
if (i < parsed.length - 1) mdLines.push("");
|
|
380
|
+
}
|
|
381
|
+
return mdLines.join("\n");
|
|
382
|
+
} catch {
|
|
383
|
+
return content;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function formatInitiative(ini: Initiative): string {
|
|
388
|
+
const lines = [`${ini.title}`];
|
|
389
|
+
if (ini.summary) lines.push(` Summary: ${ini.summary}`);
|
|
390
|
+
lines.push(` Status: ${ini.status}`);
|
|
391
|
+
lines.push(` Anchor: ${ini.anchorType}${ini.anchorId ? ` #${ini.anchorId}` : ""}`);
|
|
392
|
+
if (ini.coreIdea) {
|
|
393
|
+
const md = blockNoteToMarkdown(ini.coreIdea);
|
|
394
|
+
const preview = md.length > 120 ? md.substring(0, 120) + "..." : md;
|
|
395
|
+
lines.push(` Core Idea: ${preview}`);
|
|
396
|
+
}
|
|
397
|
+
const creator = ini.createdByUser?.fullName || `User #${ini.createdByUserId}`;
|
|
398
|
+
lines.push(` Created by: ${creator}`);
|
|
399
|
+
lines.push(` Project ID: ${ini.projectId}`);
|
|
400
|
+
lines.push(` ID: ${ini.id} | UUID: ${ini.uuid}`);
|
|
401
|
+
return lines.join("\n");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function formatInitiativeList(initiatives: Initiative[]): string {
|
|
405
|
+
if (initiatives.length === 0) return "No initiatives found.";
|
|
406
|
+
const header = `Found ${initiatives.length} initiative(s):\n`;
|
|
407
|
+
return header + initiatives.map((ini, i) => `${i + 1}. ${formatInitiative(ini)}`).join("\n\n");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function formatInitiativeMemoryEntry(e: InitiativeMemoryEntry): string {
|
|
411
|
+
const lines = [];
|
|
412
|
+
const who = e.isSystem ? "system" : e.isAgent ? "agent" : (e.authorUser?.fullName || `User #${e.authorUserId}`);
|
|
413
|
+
const created = e.createdAt
|
|
414
|
+
? new Date((typeof e.createdAt.seconds === "bigint" ? Number(e.createdAt.seconds) : e.createdAt.seconds) * 1000).toISOString()
|
|
415
|
+
: "—";
|
|
416
|
+
lines.push(`[${e.kind.toUpperCase()}] ${who} @ ${created}`);
|
|
417
|
+
lines.push(` ${e.body}`);
|
|
418
|
+
if (e.sourceType && e.sourceType !== "none") {
|
|
419
|
+
const src = e.sourceId ? `${e.sourceType} #${e.sourceId}` : e.sourceType;
|
|
420
|
+
lines.push(` Source: ${src}${e.sourceUrl ? ` (${e.sourceUrl})` : ""}`);
|
|
421
|
+
}
|
|
422
|
+
lines.push(` ID: ${e.id}`);
|
|
423
|
+
return lines.join("\n");
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function formatInitiativeMemoryList(entries: InitiativeMemoryEntry[]): string {
|
|
427
|
+
if (entries.length === 0) return "No memory entries found.";
|
|
428
|
+
const header = `Found ${entries.length} memory entry(ies) (newest first):\n`;
|
|
429
|
+
return header + entries.map((e, i) => `${i + 1}. ${formatInitiativeMemoryEntry(e)}`).join("\n\n");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function formatInitiativeGraph(graph: InitiativeGraph, taskMap?: Map<number, Task>): string {
|
|
433
|
+
const lines: string[] = [];
|
|
434
|
+
|
|
435
|
+
if (graph.tasks.length === 0) {
|
|
436
|
+
lines.push("No tasks in this initiative yet.");
|
|
437
|
+
} else {
|
|
438
|
+
lines.push(`Tasks (${graph.tasks.length}):`);
|
|
439
|
+
graph.tasks.forEach((t, i) => {
|
|
440
|
+
const status = t.boardColumn?.name || "Unknown";
|
|
441
|
+
const priority = t.taskPriority?.name || "None";
|
|
442
|
+
lines.push(` ${i + 1}. [${t.identifier || `#${t.id}`}] ${t.title}`);
|
|
443
|
+
lines.push(` Status: ${status} | Priority: ${priority} | ID: ${t.id}`);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (graph.edges.length > 0) {
|
|
448
|
+
lines.push(`\nDependency Edges (${graph.edges.length}):`);
|
|
449
|
+
graph.edges.forEach((e) => {
|
|
450
|
+
const localMap = taskMap || new Map(graph.tasks.map(t => [t.id, t]));
|
|
451
|
+
const fromTask = localMap.get(e.fromTaskId);
|
|
452
|
+
const toTask = localMap.get(e.toTaskId);
|
|
453
|
+
const from = fromTask ? (fromTask.identifier || `#${e.fromTaskId}`) : `#${e.fromTaskId}`;
|
|
454
|
+
const to = toTask ? (toTask.identifier || `#${e.toTaskId}`) : `#${e.toTaskId}`;
|
|
455
|
+
lines.push(` ${from} ${e.type.toUpperCase()} ${to}`);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (graph.slices.length > 0) {
|
|
460
|
+
lines.push(`\nParallel Execution Batches:`);
|
|
461
|
+
const localMap = taskMap || new Map(graph.tasks.map(t => [t.id, t]));
|
|
462
|
+
const sorted = [...graph.slices].sort((a, b) => a.layer - b.layer);
|
|
463
|
+
sorted.forEach((slice: InitiativeSlice) => {
|
|
464
|
+
const taskLabels = slice.taskIds.map((id) => {
|
|
465
|
+
const t = localMap.get(id);
|
|
466
|
+
return t ? `[${t.identifier || `#${id}`}] ${t.title}` : `#${id}`;
|
|
467
|
+
});
|
|
468
|
+
lines.push(` Parallel batch ${slice.layer}: ${taskLabels.join(", ")}`);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return lines.join("\n");
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function formatInitiativeContext(
|
|
476
|
+
initiative: Initiative,
|
|
477
|
+
graph: InitiativeGraph | undefined,
|
|
478
|
+
memoryEntries: InitiativeMemoryEntry[],
|
|
479
|
+
pullRequests: TaskPullRequest[],
|
|
480
|
+
comments: Comment[],
|
|
481
|
+
): string {
|
|
482
|
+
const sections: string[] = [];
|
|
483
|
+
|
|
484
|
+
sections.push("=== INITIATIVE ===");
|
|
485
|
+
sections.push(formatInitiative(initiative));
|
|
486
|
+
|
|
487
|
+
if (initiative.coreIdea) {
|
|
488
|
+
sections.push("\n=== CORE IDEA ===");
|
|
489
|
+
const md = blockNoteToMarkdown(initiative.coreIdea);
|
|
490
|
+
const body = md.length > 2000
|
|
491
|
+
? md.substring(0, 2000) + "\n... [truncated]"
|
|
492
|
+
: md;
|
|
493
|
+
sections.push(body);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (graph) {
|
|
497
|
+
sections.push("\n=== EXECUTION GRAPH ===");
|
|
498
|
+
sections.push(formatInitiativeGraph(graph));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (memoryEntries.length > 0) {
|
|
502
|
+
sections.push("\n=== MEMORY LOG (newest first) ===");
|
|
503
|
+
memoryEntries.forEach((e, i) => {
|
|
504
|
+
sections.push(`${i + 1}. ${formatInitiativeMemoryEntry(e)}`);
|
|
505
|
+
});
|
|
506
|
+
} else {
|
|
507
|
+
sections.push("\n=== MEMORY LOG ===");
|
|
508
|
+
sections.push("No memory entries yet.");
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (pullRequests.length > 0) {
|
|
512
|
+
sections.push("\n=== PULL REQUESTS ===");
|
|
513
|
+
pullRequests.forEach((pr, i) => {
|
|
514
|
+
const repo = pr.projectRepository?.repositoryFullName || `repo #${pr.projectRepositoryId}`;
|
|
515
|
+
sections.push(` ${i + 1}. [${pr.state.toUpperCase()}] #${pr.prNumber} ${pr.title} (${repo})`);
|
|
516
|
+
sections.push(` ${pr.url}`);
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (comments.length > 0) {
|
|
521
|
+
sections.push("\n=== DISCUSSION ===");
|
|
522
|
+
comments.forEach((c, i) => {
|
|
523
|
+
sections.push(`${i + 1}. ${formatComment(c)}`);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return sections.join("\n");
|
|
528
|
+
}
|
|
529
|
+
|
|
287
530
|
export function formatError(error: unknown): string {
|
|
288
531
|
if (error instanceof Error) {
|
|
289
532
|
const msg = error.message;
|