devglide 0.1.1
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/LICENSE +21 -0
- package/README.md +338 -0
- package/bin/claude-md-template.js +94 -0
- package/bin/devglide.js +387 -0
- package/package.json +85 -0
- package/pnpm-workspace.yaml +3 -0
- package/src/apps/coder/.turbo/turbo-lint.log +5 -0
- package/src/apps/coder/package.json +16 -0
- package/src/apps/coder/public/favicon.svg +7 -0
- package/src/apps/coder/public/page.css +275 -0
- package/src/apps/coder/public/page.js +528 -0
- package/src/apps/coder/server.js +3 -0
- package/src/apps/documentation/public/page.css +597 -0
- package/src/apps/documentation/public/page.js +609 -0
- package/src/apps/kanban/.turbo/turbo-lint.log +97 -0
- package/src/apps/kanban/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/kanban/package.json +32 -0
- package/src/apps/kanban/public/favicon.svg +7 -0
- package/src/apps/kanban/public/page.css +1010 -0
- package/src/apps/kanban/public/page.js +1730 -0
- package/src/apps/kanban/public/vendor/marked.min.js +6 -0
- package/src/apps/kanban/public/vendor/sortable.min.js +2 -0
- package/src/apps/kanban/src/db.ts +319 -0
- package/src/apps/kanban/src/index.ts +14 -0
- package/src/apps/kanban/src/mcp-helpers.test.ts +88 -0
- package/src/apps/kanban/src/mcp-helpers.ts +60 -0
- package/src/apps/kanban/src/mcp.ts +59 -0
- package/src/apps/kanban/src/routes/attachments.ts +161 -0
- package/src/apps/kanban/src/routes/features.ts +233 -0
- package/src/apps/kanban/src/routes/issues.ts +373 -0
- package/src/apps/kanban/src/tools/feature-tools.ts +164 -0
- package/src/apps/kanban/src/tools/item-tools.ts +307 -0
- package/src/apps/kanban/src/tools/versioned-entry-tools.ts +72 -0
- package/src/apps/kanban/tsconfig.check.json +9 -0
- package/src/apps/kanban/tsconfig.json +9 -0
- package/src/apps/keymap/.turbo/turbo-lint.log +5 -0
- package/src/apps/keymap/package.json +16 -0
- package/src/apps/keymap/public/page.css +275 -0
- package/src/apps/keymap/public/page.js +294 -0
- package/src/apps/keymap/server.js +25 -0
- package/src/apps/log/.turbo/turbo-build.log +5 -0
- package/src/apps/log/.turbo/turbo-lint.log +45 -0
- package/src/apps/log/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/log/node_modules/.bin/tsc +21 -0
- package/src/apps/log/node_modules/.bin/tsserver +21 -0
- package/src/apps/log/node_modules/.bin/tsx +21 -0
- package/src/apps/log/package.json +36 -0
- package/src/apps/log/public/console-sniffer.js +221 -0
- package/src/apps/log/public/favicon.svg +7 -0
- package/src/apps/log/public/page.css +322 -0
- package/src/apps/log/public/page.js +463 -0
- package/src/apps/log/src/index.ts +9 -0
- package/src/apps/log/src/mcp.ts +122 -0
- package/src/apps/log/src/routes/log.ts +333 -0
- package/src/apps/log/src/routes/status.ts +25 -0
- package/src/apps/log/src/server-sniffer.ts +118 -0
- package/src/apps/log/src/services/file-patterns.ts +39 -0
- package/src/apps/log/src/services/file-tailer.ts +228 -0
- package/src/apps/log/src/services/line-parser.ts +94 -0
- package/src/apps/log/src/services/log-writer.ts +39 -0
- package/src/apps/log/tsconfig.json +8 -0
- package/src/apps/prompts/.turbo/turbo-build.log +5 -0
- package/src/apps/prompts/.turbo/turbo-lint.log +24 -0
- package/src/apps/prompts/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/prompts/mcp.ts +175 -0
- package/src/apps/prompts/node_modules/.bin/tsc +21 -0
- package/src/apps/prompts/node_modules/.bin/tsserver +21 -0
- package/src/apps/prompts/node_modules/.bin/tsx +21 -0
- package/src/apps/prompts/package.json +25 -0
- package/src/apps/prompts/public/page.css +315 -0
- package/src/apps/prompts/public/page.js +541 -0
- package/src/apps/prompts/services/prompt-store.ts +212 -0
- package/src/apps/prompts/src/index.ts +9 -0
- package/src/apps/prompts/tsconfig.json +8 -0
- package/src/apps/prompts/types.ts +27 -0
- package/src/apps/shell/.turbo/turbo-build.log +5 -0
- package/src/apps/shell/.turbo/turbo-lint.log +34 -0
- package/src/apps/shell/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/shell/package.json +35 -0
- package/src/apps/shell/public/favicon.svg +7 -0
- package/src/apps/shell/public/page.css +407 -0
- package/src/apps/shell/public/page.js +1577 -0
- package/src/apps/shell/src/index.ts +150 -0
- package/src/apps/shell/src/mcp.ts +398 -0
- package/src/apps/shell/src/shell-types.ts +41 -0
- package/src/apps/shell/tsconfig.json +8 -0
- package/src/apps/test/.turbo/turbo-build.log +5 -0
- package/src/apps/test/.turbo/turbo-lint.log +27 -0
- package/src/apps/test/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/test/node_modules/.bin/tsc +21 -0
- package/src/apps/test/node_modules/.bin/tsserver +21 -0
- package/src/apps/test/node_modules/.bin/tsx +21 -0
- package/src/apps/test/node_modules/.bin/uuid +21 -0
- package/src/apps/test/package.json +35 -0
- package/src/apps/test/public/favicon.svg +7 -0
- package/src/apps/test/public/page.css +499 -0
- package/src/apps/test/public/page.js +417 -0
- package/src/apps/test/public/scenario-runner.js +450 -0
- package/src/apps/test/src/index.ts +9 -0
- package/src/apps/test/src/mcp.ts +192 -0
- package/src/apps/test/src/routes/trigger.ts +285 -0
- package/src/apps/test/src/services/scenario-broadcaster.ts +60 -0
- package/src/apps/test/src/services/scenario-manager.ts +361 -0
- package/src/apps/test/src/services/scenario-store.ts +145 -0
- package/src/apps/test/tsconfig.json +8 -0
- package/src/apps/vocabulary/.turbo/turbo-build.log +5 -0
- package/src/apps/vocabulary/.turbo/turbo-lint.log +25 -0
- package/src/apps/vocabulary/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/vocabulary/mcp.ts +173 -0
- package/src/apps/vocabulary/node_modules/.bin/tsc +21 -0
- package/src/apps/vocabulary/node_modules/.bin/tsserver +21 -0
- package/src/apps/vocabulary/node_modules/.bin/tsx +21 -0
- package/src/apps/vocabulary/package.json +25 -0
- package/src/apps/vocabulary/public/page.css +247 -0
- package/src/apps/vocabulary/public/page.js +444 -0
- package/src/apps/vocabulary/services/vocabulary-store.ts +179 -0
- package/src/apps/vocabulary/src/index.ts +10 -0
- package/src/apps/vocabulary/tsconfig.json +8 -0
- package/src/apps/vocabulary/types.ts +22 -0
- package/src/apps/voice/.turbo/turbo-build.log +5 -0
- package/src/apps/voice/.turbo/turbo-lint.log +43 -0
- package/src/apps/voice/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/voice/node_modules/.bin/openai +21 -0
- package/src/apps/voice/node_modules/.bin/tsc +21 -0
- package/src/apps/voice/node_modules/.bin/tsserver +21 -0
- package/src/apps/voice/node_modules/.bin/tsx +21 -0
- package/src/apps/voice/package.json +35 -0
- package/src/apps/voice/public/favicon.svg +7 -0
- package/src/apps/voice/public/page.css +388 -0
- package/src/apps/voice/public/page.js +718 -0
- package/src/apps/voice/src/index.ts +10 -0
- package/src/apps/voice/src/mcp.ts +70 -0
- package/src/apps/voice/src/providers/index.ts +85 -0
- package/src/apps/voice/src/providers/openai-compatible.ts +94 -0
- package/src/apps/voice/src/providers/types.ts +27 -0
- package/src/apps/voice/src/routes/config.ts +118 -0
- package/src/apps/voice/src/routes/transcribe.ts +90 -0
- package/src/apps/voice/src/services/config-store.ts +129 -0
- package/src/apps/voice/src/services/stats.ts +108 -0
- package/src/apps/voice/src/transcribe.ts +11 -0
- package/src/apps/voice/src/utils/mime.ts +16 -0
- package/src/apps/voice/tsconfig.json +8 -0
- package/src/apps/workflow/.turbo/turbo-build.log +5 -0
- package/src/apps/workflow/.turbo/turbo-lint.log +96 -0
- package/src/apps/workflow/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/workflow/engine/executors/decision-executor.ts +87 -0
- package/src/apps/workflow/engine/executors/file-executor.ts +90 -0
- package/src/apps/workflow/engine/executors/git-executor.ts +137 -0
- package/src/apps/workflow/engine/executors/http-executor.ts +65 -0
- package/src/apps/workflow/engine/executors/index.ts +28 -0
- package/src/apps/workflow/engine/executors/kanban-executor.ts +154 -0
- package/src/apps/workflow/engine/executors/llm-executor.ts +46 -0
- package/src/apps/workflow/engine/executors/log-executor.ts +62 -0
- package/src/apps/workflow/engine/executors/loop-executor.ts +14 -0
- package/src/apps/workflow/engine/executors/shell-executor.ts +107 -0
- package/src/apps/workflow/engine/executors/sub-workflow-executor.ts +61 -0
- package/src/apps/workflow/engine/executors/test-executor.ts +73 -0
- package/src/apps/workflow/engine/executors/trigger-executor.ts +39 -0
- package/src/apps/workflow/engine/expression-evaluator.ts +117 -0
- package/src/apps/workflow/engine/graph-runner.ts +438 -0
- package/src/apps/workflow/engine/node-executor.ts +104 -0
- package/src/apps/workflow/engine/node-registry.ts +15 -0
- package/src/apps/workflow/engine/variable-resolver.ts +109 -0
- package/src/apps/workflow/mcp.ts +223 -0
- package/src/apps/workflow/node_modules/.bin/tsc +21 -0
- package/src/apps/workflow/node_modules/.bin/tsserver +21 -0
- package/src/apps/workflow/node_modules/.bin/tsx +21 -0
- package/src/apps/workflow/package.json +25 -0
- package/src/apps/workflow/public/editor/canvas.js +366 -0
- package/src/apps/workflow/public/editor/drag-manager.js +326 -0
- package/src/apps/workflow/public/editor/edge-renderer.js +235 -0
- package/src/apps/workflow/public/editor/history-manager.js +147 -0
- package/src/apps/workflow/public/editor/layout-engine.js +159 -0
- package/src/apps/workflow/public/editor/node-renderer.js +199 -0
- package/src/apps/workflow/public/editor/selection-manager.js +193 -0
- package/src/apps/workflow/public/favicon.svg +7 -0
- package/src/apps/workflow/public/models/node-types.js +300 -0
- package/src/apps/workflow/public/models/workflow-model.js +257 -0
- package/src/apps/workflow/public/page.css +406 -0
- package/src/apps/workflow/public/page.js +658 -0
- package/src/apps/workflow/public/panels/inspector.js +360 -0
- package/src/apps/workflow/public/panels/palette.js +106 -0
- package/src/apps/workflow/public/panels/run-view.js +275 -0
- package/src/apps/workflow/public/panels/toolbar.js +232 -0
- package/src/apps/workflow/public/panels/workflow-list.js +237 -0
- package/src/apps/workflow/public/state/store.js +47 -0
- package/src/apps/workflow/services/custom-node-loader.ts +48 -0
- package/src/apps/workflow/services/legacy-converter.ts +72 -0
- package/src/apps/workflow/services/run-manager.ts +190 -0
- package/src/apps/workflow/services/workflow-store.ts +424 -0
- package/src/apps/workflow/services/workflow-validator.test.ts +103 -0
- package/src/apps/workflow/services/workflow-validator.ts +98 -0
- package/src/apps/workflow/src/index.ts +10 -0
- package/src/apps/workflow/templates/ci-pipeline.json +18 -0
- package/src/apps/workflow/templates/code-review.json +22 -0
- package/src/apps/workflow/templates/kanban-testing.json +24 -0
- package/src/apps/workflow/tsconfig.json +8 -0
- package/src/apps/workflow/types.ts +268 -0
- package/src/packages/auth-middleware.ts +14 -0
- package/src/packages/design-tokens/.turbo/turbo-build.log +10 -0
- package/src/packages/design-tokens/STYLEGUIDE.md +414 -0
- package/src/packages/design-tokens/build.js +413 -0
- package/src/packages/design-tokens/demo/index.html +1367 -0
- package/src/packages/design-tokens/demo/proposition-a.html +717 -0
- package/src/packages/design-tokens/demo/proposition-b.html +1239 -0
- package/src/packages/design-tokens/demo/proposition-c.html +1049 -0
- package/src/packages/design-tokens/dist/tailwind-preset.js +115 -0
- package/src/packages/design-tokens/dist/tokens.css +345 -0
- package/src/packages/design-tokens/dist/tokens.d.ts +229 -0
- package/src/packages/design-tokens/dist/tokens.js +386 -0
- package/src/packages/design-tokens/package.json +25 -0
- package/src/packages/design-tokens/tokens.json +228 -0
- package/src/packages/devtools-middleware.ts +22 -0
- package/src/packages/eslint-config/index.js +63 -0
- package/src/packages/eslint-config/node_modules/.bin/eslint +21 -0
- package/src/packages/eslint-config/package.json +18 -0
- package/src/packages/json-file-store.ts +232 -0
- package/src/packages/mcp-utils/.turbo/turbo-build.log +5 -0
- package/src/packages/mcp-utils/dist/index.d.ts +33 -0
- package/src/packages/mcp-utils/dist/index.d.ts.map +1 -0
- package/src/packages/mcp-utils/dist/index.js +126 -0
- package/src/packages/mcp-utils/dist/index.js.map +1 -0
- package/src/packages/mcp-utils/node_modules/.bin/tsc +21 -0
- package/src/packages/mcp-utils/node_modules/.bin/tsserver +21 -0
- package/src/packages/mcp-utils/package.json +32 -0
- package/src/packages/mcp-utils/src/index.ts +171 -0
- package/src/packages/mcp-utils/tsconfig.json +9 -0
- package/src/packages/paths.ts +18 -0
- package/src/packages/project-context/index.js +55 -0
- package/src/packages/project-context/package.json +13 -0
- package/src/packages/project-store.ts +127 -0
- package/src/packages/server-sniffer.ts +132 -0
- package/src/packages/shared-assets/favicon.svg +7 -0
- package/src/packages/shared-assets/keymap-registry.js +512 -0
- package/src/packages/shared-assets/logo.svg +6 -0
- package/src/packages/shared-assets/package.json +11 -0
- package/src/packages/shared-assets/ui-utils.js +48 -0
- package/src/packages/shared-assets/voice-widget.d.ts +37 -0
- package/src/packages/shared-assets/voice-widget.js +695 -0
- package/src/packages/shared-types/.turbo/turbo-build.log +5 -0
- package/src/packages/shared-types/dist/index.d.ts +39 -0
- package/src/packages/shared-types/dist/index.d.ts.map +1 -0
- package/src/packages/shared-types/node_modules/.bin/tsc +21 -0
- package/src/packages/shared-types/node_modules/.bin/tsserver +21 -0
- package/src/packages/shared-types/package.json +25 -0
- package/src/packages/shared-types/src/index.ts +41 -0
- package/src/packages/shared-types/tsconfig.json +11 -0
- package/src/packages/tsconfig/base.json +15 -0
- package/src/packages/tsconfig/next.json +14 -0
- package/src/packages/tsconfig/node.json +11 -0
- package/src/packages/tsconfig/package.json +10 -0
- package/turbo.json +25 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getDb, generateId, nowIso, appendVersionedEntry, getVersionedEntries, type IssueRow } from "../db.js";
|
|
4
|
+
import { jsonResult, errorResult } from "../../../../packages/mcp-utils/src/index.js";
|
|
5
|
+
import { normalizeEscapes, mapIssueRow, resolveColumnId, truncateDescription } from "../mcp-helpers.js";
|
|
6
|
+
|
|
7
|
+
export function registerItemTools(server: McpServer, projectId?: string | null): void {
|
|
8
|
+
|
|
9
|
+
// ── kanban_list_items ─────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
server.tool(
|
|
12
|
+
"kanban_list_items",
|
|
13
|
+
"List tasks and bugs with pagination. Descriptions are truncated to 200 chars — use kanban_get_item for full details. Supports filtering by feature, column, priority, type (TASK/BUG), column name (status), or review feedback. When looking for work to pick up, default to columnName: 'Todo' unless the user specifies otherwise.",
|
|
14
|
+
{
|
|
15
|
+
featureId: z.string().optional().describe("Filter by feature ID"),
|
|
16
|
+
columnId: z.string().optional().describe("Filter by column ID"),
|
|
17
|
+
columnName: z.string().optional().describe("Filter by column name (status) — case-sensitive: 'Backlog', 'Todo', 'In Progress', 'In Review', 'Testing', 'Done'. Ignored if columnId is provided."),
|
|
18
|
+
priority: z.enum(["LOW", "MEDIUM", "HIGH", "URGENT"]).optional().describe("Filter by priority"),
|
|
19
|
+
type: z.enum(["TASK", "BUG"]).optional().describe("Filter by item type — TASK or BUG"),
|
|
20
|
+
hasReviewFeedback: z.preprocess(
|
|
21
|
+
(val) => (typeof val === "string" ? val === "true" : val),
|
|
22
|
+
z.boolean().optional()
|
|
23
|
+
).describe("If true, return only issues that have review feedback populated"),
|
|
24
|
+
limit: z.coerce.number().int().min(1).max(50).optional().describe("Max items to return (default 25, max 50)"),
|
|
25
|
+
offset: z.coerce.number().int().min(0).optional().describe("Number of items to skip (default 0)"),
|
|
26
|
+
fields: z.preprocess(
|
|
27
|
+
(val) => {
|
|
28
|
+
if (typeof val === "string") {
|
|
29
|
+
try { return JSON.parse(val); } catch { return [val]; }
|
|
30
|
+
}
|
|
31
|
+
return val;
|
|
32
|
+
},
|
|
33
|
+
z.array(z.string()).optional()
|
|
34
|
+
).describe("Return only these fields per issue (e.g. ['id', 'title', 'priority', 'columnName']). 'columnName' is a virtual field resolved from the column relation. If omitted, all fields are returned."),
|
|
35
|
+
},
|
|
36
|
+
async ({ featureId, columnId, columnName, priority, type, hasReviewFeedback, limit, offset, fields }) => {
|
|
37
|
+
const db = getDb(projectId);
|
|
38
|
+
const take = limit ?? 25;
|
|
39
|
+
const skip = offset ?? 0;
|
|
40
|
+
|
|
41
|
+
const conditions: string[] = [];
|
|
42
|
+
const params: any[] = [];
|
|
43
|
+
|
|
44
|
+
if (featureId) { conditions.push(`i."projectId" = ?`); params.push(featureId); }
|
|
45
|
+
if (columnId) { conditions.push(`i."columnId" = ?`); params.push(columnId); }
|
|
46
|
+
else if (columnName) { conditions.push(`c."name" = ?`); params.push(columnName); }
|
|
47
|
+
if (priority) { conditions.push(`i."priority" = ?`); params.push(priority); }
|
|
48
|
+
if (type) { conditions.push(`i."type" = ?`); params.push(type); }
|
|
49
|
+
if (hasReviewFeedback) {
|
|
50
|
+
conditions.push(`EXISTS (SELECT 1 FROM "VersionedEntry" ve WHERE ve."issueId" = i."id" AND ve."type" = 'review')`);
|
|
51
|
+
conditions.push(`c."name" != 'Done'`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
55
|
+
|
|
56
|
+
const countRow = db
|
|
57
|
+
.prepare(`SELECT COUNT(*) AS cnt FROM "Issue" i LEFT JOIN "Column" c ON i."columnId" = c."id" ${where}`)
|
|
58
|
+
.get(...params) as any;
|
|
59
|
+
const total = countRow.cnt;
|
|
60
|
+
|
|
61
|
+
const rows = db
|
|
62
|
+
.prepare(
|
|
63
|
+
`SELECT i.*,
|
|
64
|
+
c."name" AS columnName, c."order" AS columnOrder, c."color" AS columnColor,
|
|
65
|
+
p."name" AS featureName, p."description" AS featureDescription, p."color" AS featureColor
|
|
66
|
+
FROM "Issue" i
|
|
67
|
+
LEFT JOIN "Column" c ON i."columnId" = c."id"
|
|
68
|
+
LEFT JOIN "Project" p ON i."projectId" = p."id"
|
|
69
|
+
${where}
|
|
70
|
+
ORDER BY i."order" ASC
|
|
71
|
+
LIMIT ? OFFSET ?`
|
|
72
|
+
)
|
|
73
|
+
.all(...params, take, skip) as any[];
|
|
74
|
+
|
|
75
|
+
let data: unknown[];
|
|
76
|
+
|
|
77
|
+
if (fields && fields.length > 0) {
|
|
78
|
+
data = rows.map((row) => {
|
|
79
|
+
const mapped = mapIssueRow(row) as Record<string, unknown>;
|
|
80
|
+
const picked: Record<string, unknown> = {};
|
|
81
|
+
for (const f of fields) {
|
|
82
|
+
if (f in mapped) {
|
|
83
|
+
picked[f] = f === "description" ? truncateDescription(mapped[f] as string) : mapped[f];
|
|
84
|
+
} else if (f === "columnName") {
|
|
85
|
+
picked.columnName = row.columnName ?? null;
|
|
86
|
+
} else if (f === "featureName") {
|
|
87
|
+
picked.featureName = row.featureName ?? null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return picked;
|
|
91
|
+
});
|
|
92
|
+
} else {
|
|
93
|
+
data = rows.map((row) => {
|
|
94
|
+
const mapped = mapIssueRow(row);
|
|
95
|
+
mapped.description = truncateDescription(mapped.description);
|
|
96
|
+
return mapped;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return jsonResult({
|
|
101
|
+
data,
|
|
102
|
+
pagination: { total, limit: take, offset: skip, hasMore: skip + take < total },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// ── kanban_create_item ────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
server.tool(
|
|
110
|
+
"kanban_create_item",
|
|
111
|
+
"Create a new task or bug on a feature's kanban board. Defaults to the Backlog column if no column is specified.",
|
|
112
|
+
{
|
|
113
|
+
title: z.string().describe("Issue title"),
|
|
114
|
+
description: z.string().optional().describe("Issue description"),
|
|
115
|
+
featureId: z.string().describe("Feature ID"),
|
|
116
|
+
columnId: z.string().optional().describe("Column ID to place the issue in. Optional — defaults to Backlog column if neither columnId nor columnName is provided."),
|
|
117
|
+
columnName: z.string().optional().describe("Column name to place the issue in — e.g. 'Backlog', 'Todo'. Defaults to 'Backlog' if omitted. Ignored if columnId is provided."),
|
|
118
|
+
priority: z.enum(["LOW", "MEDIUM", "HIGH", "URGENT"]).optional().describe("Priority level"),
|
|
119
|
+
type: z.enum(["TASK", "BUG"]).optional().describe("Item type — defaults to TASK"),
|
|
120
|
+
labels: z.preprocess(
|
|
121
|
+
(val) => {
|
|
122
|
+
if (typeof val === "string") { try { return JSON.parse(val); } catch { return [val]; } }
|
|
123
|
+
return val;
|
|
124
|
+
},
|
|
125
|
+
z.array(z.string()).optional()
|
|
126
|
+
).describe("List of label strings"),
|
|
127
|
+
dueDate: z.string().optional().describe("Due date ISO string e.g. 2025-12-31"),
|
|
128
|
+
},
|
|
129
|
+
async ({ title, description, featureId, columnId, columnName, priority, type, labels, dueDate }) => {
|
|
130
|
+
const db = getDb(projectId);
|
|
131
|
+
|
|
132
|
+
const effectiveColumnName = columnName ?? "Backlog";
|
|
133
|
+
let resolvedColumnId = columnId;
|
|
134
|
+
if (!resolvedColumnId) {
|
|
135
|
+
const resolved = resolveColumnId(db, featureId, effectiveColumnName);
|
|
136
|
+
if (!resolved) return errorResult(`Column "${effectiveColumnName}" not found in feature.`);
|
|
137
|
+
resolvedColumnId = resolved;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Validate target column is Backlog or Todo — auto-correct to Todo if invalid
|
|
141
|
+
const targetCol = db.prepare(`SELECT "name" FROM "Column" WHERE "id" = ?`).get(resolvedColumnId) as any;
|
|
142
|
+
if (!targetCol || !["Backlog", "Todo"].includes(targetCol.name)) {
|
|
143
|
+
const fallback = resolveColumnId(db, featureId, "Todo");
|
|
144
|
+
if (!fallback) return errorResult("Could not resolve default Todo column.");
|
|
145
|
+
resolvedColumnId = fallback;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const maxOrder = db.prepare(`SELECT MAX("order") AS maxOrd FROM "Issue" WHERE "columnId" = ?`).get(resolvedColumnId) as any;
|
|
149
|
+
const order = (maxOrder?.maxOrd ?? -1) + 1;
|
|
150
|
+
|
|
151
|
+
const now = nowIso();
|
|
152
|
+
const id = generateId();
|
|
153
|
+
|
|
154
|
+
db.prepare(
|
|
155
|
+
`INSERT INTO "Issue" ("id", "title", "description", "type", "priority", "order", "labels", "dueDate", "projectId", "columnId", "updatedAt")
|
|
156
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
157
|
+
).run(id, title, description ? normalizeEscapes(description) : null, type ?? "TASK", priority ?? "MEDIUM", order, JSON.stringify(labels ?? []), dueDate ?? null, featureId, resolvedColumnId, now);
|
|
158
|
+
|
|
159
|
+
const row = db.prepare(`SELECT * FROM "Issue" WHERE "id" = ?`).get(id) as IssueRow | undefined;
|
|
160
|
+
return jsonResult(mapIssueRow(row));
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// ── kanban_update_item ────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
server.tool(
|
|
167
|
+
"kanban_update_item",
|
|
168
|
+
"Update an existing task or bug",
|
|
169
|
+
{
|
|
170
|
+
id: z.string().describe("Issue ID"),
|
|
171
|
+
title: z.string().optional().describe("New title"),
|
|
172
|
+
description: z.string().optional().describe("New description"),
|
|
173
|
+
priority: z.enum(["LOW", "MEDIUM", "HIGH", "URGENT"]).optional().describe("New priority"),
|
|
174
|
+
type: z.enum(["TASK", "BUG"]).optional().describe("Change item type"),
|
|
175
|
+
labels: z.preprocess(
|
|
176
|
+
(val) => {
|
|
177
|
+
if (typeof val === "string") { try { return JSON.parse(val); } catch { return [val]; } }
|
|
178
|
+
return val;
|
|
179
|
+
},
|
|
180
|
+
z.array(z.string()).optional()
|
|
181
|
+
).describe("New labels"),
|
|
182
|
+
dueDate: z.string().nullable().optional().describe("Due date or null to clear"),
|
|
183
|
+
reviewFeedback: z.string().nullable().optional().describe("Review feedback (deprecated — use kanban_append_review instead). If provided, appends as a versioned review entry."),
|
|
184
|
+
},
|
|
185
|
+
async ({ id, title, description, priority, type, labels, dueDate, reviewFeedback }) => {
|
|
186
|
+
const db = getDb(projectId);
|
|
187
|
+
|
|
188
|
+
const existing = db.prepare(`SELECT "id" FROM "Issue" WHERE "id" = ?`).get(id);
|
|
189
|
+
if (!existing) return errorResult("Item not found");
|
|
190
|
+
|
|
191
|
+
if (reviewFeedback && reviewFeedback.trim()) {
|
|
192
|
+
appendVersionedEntry(db, id, "review", reviewFeedback.trim());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const setClauses: string[] = [];
|
|
196
|
+
const params: any[] = [];
|
|
197
|
+
|
|
198
|
+
if (title !== undefined) { setClauses.push(`"title" = ?`); params.push(title); }
|
|
199
|
+
if (description !== undefined) { setClauses.push(`"description" = ?`); params.push(description ? normalizeEscapes(description) : description); }
|
|
200
|
+
if (priority !== undefined) { setClauses.push(`"priority" = ?`); params.push(priority); }
|
|
201
|
+
if (type !== undefined) { setClauses.push(`"type" = ?`); params.push(type); }
|
|
202
|
+
if (labels !== undefined) { setClauses.push(`"labels" = ?`); params.push(JSON.stringify(labels)); }
|
|
203
|
+
if (dueDate !== undefined) { setClauses.push(`"dueDate" = ?`); params.push(dueDate ?? null); }
|
|
204
|
+
|
|
205
|
+
const now = nowIso();
|
|
206
|
+
setClauses.push(`"updatedAt" = ?`);
|
|
207
|
+
params.push(now);
|
|
208
|
+
params.push(id);
|
|
209
|
+
|
|
210
|
+
db.prepare(`UPDATE "Issue" SET ${setClauses.join(", ")} WHERE "id" = ?`).run(...params);
|
|
211
|
+
|
|
212
|
+
const row = db.prepare(`SELECT * FROM "Issue" WHERE "id" = ?`).get(id) as IssueRow | undefined;
|
|
213
|
+
return jsonResult(mapIssueRow(row));
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// ── kanban_move_item ──────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
server.tool(
|
|
220
|
+
"kanban_move_item",
|
|
221
|
+
"Move a task or bug to a different column (status). Cannot move items to the Done column — only the user can mark items as done.",
|
|
222
|
+
{
|
|
223
|
+
id: z.string().describe("Issue ID"),
|
|
224
|
+
columnId: z.string().optional().describe("Target column ID. Required if columnName is not provided."),
|
|
225
|
+
columnName: z.string().optional().describe("Target column name — e.g. 'In Progress', 'In Review', 'Testing'. Ignored if columnId is provided."),
|
|
226
|
+
},
|
|
227
|
+
async ({ id, columnId, columnName }) => {
|
|
228
|
+
const db = getDb(projectId);
|
|
229
|
+
|
|
230
|
+
const issue = db.prepare(`SELECT * FROM "Issue" WHERE "id" = ?`).get(id) as any;
|
|
231
|
+
if (!issue) return errorResult("Issue not found.");
|
|
232
|
+
|
|
233
|
+
let resolvedColumnId = columnId;
|
|
234
|
+
if (!resolvedColumnId && columnName) {
|
|
235
|
+
const resolved = resolveColumnId(db, issue.projectId, columnName);
|
|
236
|
+
if (!resolved) return errorResult(`Column "${columnName}" not found in feature.`);
|
|
237
|
+
resolvedColumnId = resolved;
|
|
238
|
+
}
|
|
239
|
+
if (!resolvedColumnId) return errorResult("Either columnId or columnName is required.");
|
|
240
|
+
|
|
241
|
+
const targetCol = db.prepare(`SELECT "name" FROM "Column" WHERE "id" = ?`).get(resolvedColumnId) as any;
|
|
242
|
+
if (targetCol?.name === "Done") return errorResult("Not allowed: only the user can move issues to the Done column.");
|
|
243
|
+
|
|
244
|
+
const maxOrder = db.prepare(`SELECT MAX("order") AS maxOrd FROM "Issue" WHERE "columnId" = ?`).get(resolvedColumnId) as any;
|
|
245
|
+
const order = (maxOrder?.maxOrd ?? -1) + 1;
|
|
246
|
+
|
|
247
|
+
const now = nowIso();
|
|
248
|
+
db.prepare(`UPDATE "Issue" SET "columnId" = ?, "order" = ?, "updatedAt" = ? WHERE "id" = ?`).run(resolvedColumnId, order, now, id);
|
|
249
|
+
|
|
250
|
+
const row = db
|
|
251
|
+
.prepare(
|
|
252
|
+
`SELECT i.*, c."name" AS columnName, c."order" AS columnOrder, c."color" AS columnColor
|
|
253
|
+
FROM "Issue" i LEFT JOIN "Column" c ON i."columnId" = c."id"
|
|
254
|
+
WHERE i."id" = ?`
|
|
255
|
+
)
|
|
256
|
+
.get(id) as IssueRow | undefined;
|
|
257
|
+
|
|
258
|
+
return jsonResult(mapIssueRow(row));
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// ── kanban_get_item ───────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
server.tool(
|
|
265
|
+
"kanban_get_item",
|
|
266
|
+
"Get full details of a single task or bug",
|
|
267
|
+
{ id: z.string().describe("Issue ID") },
|
|
268
|
+
async ({ id }) => {
|
|
269
|
+
const db = getDb(projectId);
|
|
270
|
+
|
|
271
|
+
const row = db
|
|
272
|
+
.prepare(
|
|
273
|
+
`SELECT i.*,
|
|
274
|
+
c."name" AS columnName, c."order" AS columnOrder, c."color" AS columnColor,
|
|
275
|
+
p."name" AS featureName, p."description" AS featureDescription, p."color" AS featureColor
|
|
276
|
+
FROM "Issue" i
|
|
277
|
+
LEFT JOIN "Column" c ON i."columnId" = c."id"
|
|
278
|
+
LEFT JOIN "Project" p ON i."projectId" = p."id"
|
|
279
|
+
WHERE i."id" = ?`
|
|
280
|
+
)
|
|
281
|
+
.get(id) as IssueRow | undefined;
|
|
282
|
+
|
|
283
|
+
if (!row) return errorResult("Item not found");
|
|
284
|
+
|
|
285
|
+
return jsonResult({
|
|
286
|
+
...mapIssueRow(row),
|
|
287
|
+
workLog: getVersionedEntries(db, id, "work_log"),
|
|
288
|
+
reviewHistory: getVersionedEntries(db, id, "review"),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// ── kanban_delete_item ────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
server.tool(
|
|
296
|
+
"kanban_delete_item",
|
|
297
|
+
"Delete a task or bug",
|
|
298
|
+
{ id: z.string().describe("Issue ID") },
|
|
299
|
+
async ({ id }) => {
|
|
300
|
+
const db = getDb(projectId);
|
|
301
|
+
const existing = db.prepare(`SELECT "id" FROM "Issue" WHERE "id" = ?`).get(id);
|
|
302
|
+
if (!existing) return errorResult("Item not found");
|
|
303
|
+
db.prepare(`DELETE FROM "Issue" WHERE "id" = ?`).run(id);
|
|
304
|
+
return jsonResult({ message: `Item ${id} deleted.` });
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getDb, nowIso, appendVersionedEntry, getVersionedEntries } from "../db.js";
|
|
4
|
+
import { jsonResult, errorResult } from "../../../../packages/mcp-utils/src/index.js";
|
|
5
|
+
import { normalizeEscapes } from "../mcp-helpers.js";
|
|
6
|
+
|
|
7
|
+
export function registerVersionedEntryTools(server: McpServer, projectId?: string | null): void {
|
|
8
|
+
|
|
9
|
+
// ── kanban_append_work_log ────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
server.tool(
|
|
12
|
+
"kanban_append_work_log",
|
|
13
|
+
"Append a work log entry to a task (params: id, content). Records what was done while working on the task. Entries are append-only with automatic versioning.",
|
|
14
|
+
{
|
|
15
|
+
id: z.string().describe("Issue ID"),
|
|
16
|
+
content: z.string().describe("Work log content (markdown supported)"),
|
|
17
|
+
},
|
|
18
|
+
async ({ id, content }) => {
|
|
19
|
+
const db = getDb(projectId);
|
|
20
|
+
const issue = db.prepare('SELECT "id" FROM "Issue" WHERE "id" = ?').get(id);
|
|
21
|
+
if (!issue) return errorResult("Item not found");
|
|
22
|
+
const entry = appendVersionedEntry(db, id, "work_log", normalizeEscapes(content));
|
|
23
|
+
db.prepare('UPDATE "Issue" SET "updatedAt" = ? WHERE "id" = ?').run(nowIso(), id);
|
|
24
|
+
return jsonResult(entry);
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// ── kanban_get_work_log ───────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
server.tool(
|
|
31
|
+
"kanban_get_work_log",
|
|
32
|
+
"Get the full work log history for a task, ordered by version",
|
|
33
|
+
{ id: z.string().describe("Issue ID") },
|
|
34
|
+
async ({ id }) => {
|
|
35
|
+
const db = getDb(projectId);
|
|
36
|
+
const entries = getVersionedEntries(db, id, "work_log");
|
|
37
|
+
return jsonResult({ issueId: id, entries });
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// ── kanban_append_review ──────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
server.tool(
|
|
44
|
+
"kanban_append_review",
|
|
45
|
+
"Append review feedback to a task (params: id, content). Used when reviewing work and providing notes for the next iteration. Entries are append-only with automatic versioning.",
|
|
46
|
+
{
|
|
47
|
+
id: z.string().describe("Issue ID"),
|
|
48
|
+
content: z.string().describe("Review feedback content (markdown supported)"),
|
|
49
|
+
},
|
|
50
|
+
async ({ id, content }) => {
|
|
51
|
+
const db = getDb(projectId);
|
|
52
|
+
const issue = db.prepare('SELECT "id" FROM "Issue" WHERE "id" = ?').get(id);
|
|
53
|
+
if (!issue) return errorResult("Item not found");
|
|
54
|
+
const entry = appendVersionedEntry(db, id, "review", normalizeEscapes(content));
|
|
55
|
+
db.prepare('UPDATE "Issue" SET "updatedAt" = ? WHERE "id" = ?').run(nowIso(), id);
|
|
56
|
+
return jsonResult(entry);
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// ── kanban_get_review_history ─────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
server.tool(
|
|
63
|
+
"kanban_get_review_history",
|
|
64
|
+
"Get the full review feedback history for a task, ordered by version",
|
|
65
|
+
{ id: z.string().describe("Issue ID") },
|
|
66
|
+
async ({ id }) => {
|
|
67
|
+
const db = getDb(projectId);
|
|
68
|
+
const entries = getVersionedEntries(db, id, "review");
|
|
69
|
+
return jsonResult({ issueId: id, entries });
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../packages/tsconfig/node.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"noEmit": true,
|
|
5
|
+
"rootDir": "../.."
|
|
6
|
+
},
|
|
7
|
+
"include": ["src", "../../packages/paths.ts", "../../packages/project-store.ts", "../../packages/mcp-utils/src"],
|
|
8
|
+
"exclude": ["node_modules", "dist"]
|
|
9
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devglide/keymap",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Keyboard shortcut configuration for Devglide",
|
|
6
|
+
"main": "server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"devglide-keymap": "./server.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"lint": "eslint ."
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"express": "^5.2.1"
|
|
15
|
+
}
|
|
16
|
+
}
|