@grec0/memory-bank-mcp 0.0.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/.memoryignore.example +76 -0
- package/README.md +425 -0
- package/dist/common/chunker.js +407 -0
- package/dist/common/embeddingService.js +302 -0
- package/dist/common/errors.js +71 -0
- package/dist/common/fileScanner.js +261 -0
- package/dist/common/indexManager.js +332 -0
- package/dist/common/setup.js +49 -0
- package/dist/common/types.js +115 -0
- package/dist/common/utils.js +215 -0
- package/dist/common/vectorStore.js +332 -0
- package/dist/common/version.js +2 -0
- package/dist/index.js +274 -0
- package/dist/operations/boardMemberships.js +186 -0
- package/dist/operations/boards.js +268 -0
- package/dist/operations/cards.js +426 -0
- package/dist/operations/comments.js +249 -0
- package/dist/operations/labels.js +258 -0
- package/dist/operations/lists.js +157 -0
- package/dist/operations/projects.js +102 -0
- package/dist/operations/tasks.js +238 -0
- package/dist/tools/analyzeCoverage.js +316 -0
- package/dist/tools/board-summary.js +151 -0
- package/dist/tools/card-details.js +106 -0
- package/dist/tools/create-card-with-tasks.js +81 -0
- package/dist/tools/getStats.js +59 -0
- package/dist/tools/index.js +12 -0
- package/dist/tools/indexCode.js +53 -0
- package/dist/tools/readFile.js +69 -0
- package/dist/tools/searchMemory.js +60 -0
- package/dist/tools/workflow-actions.js +145 -0
- package/dist/tools/writeFile.js +66 -0
- package/package.json +58 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Index manager for Memory Bank
|
|
3
|
+
* Coordinates scanning, chunking, embedding, and storage
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import { scanFiles, scanSingleFile } from "./fileScanner.js";
|
|
8
|
+
import { chunkCode } from "./chunker.js";
|
|
9
|
+
/**
|
|
10
|
+
* Index manager coordinating the entire indexing pipeline
|
|
11
|
+
*/
|
|
12
|
+
export class IndexManager {
|
|
13
|
+
embeddingService;
|
|
14
|
+
vectorStore;
|
|
15
|
+
metadataPath;
|
|
16
|
+
metadata;
|
|
17
|
+
constructor(embeddingService, vectorStore, storagePath = ".memorybank") {
|
|
18
|
+
this.embeddingService = embeddingService;
|
|
19
|
+
this.vectorStore = vectorStore;
|
|
20
|
+
this.metadataPath = path.join(storagePath, "index-metadata.json");
|
|
21
|
+
this.metadata = this.loadMetadata();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Loads index metadata from disk
|
|
25
|
+
*/
|
|
26
|
+
loadMetadata() {
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(this.metadataPath)) {
|
|
29
|
+
const data = fs.readFileSync(this.metadataPath, "utf-8");
|
|
30
|
+
return JSON.parse(data);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error(`Warning: Could not load index metadata: ${error}`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
version: "1.0",
|
|
38
|
+
lastIndexed: 0,
|
|
39
|
+
files: {},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Saves index metadata to disk
|
|
44
|
+
*/
|
|
45
|
+
saveMetadata() {
|
|
46
|
+
try {
|
|
47
|
+
const dir = path.dirname(this.metadataPath);
|
|
48
|
+
if (!fs.existsSync(dir)) {
|
|
49
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
fs.writeFileSync(this.metadataPath, JSON.stringify(this.metadata, null, 2));
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error(`Warning: Could not save index metadata: ${error}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Checks if a file needs reindexing
|
|
59
|
+
*/
|
|
60
|
+
needsReindexing(file, forceReindex) {
|
|
61
|
+
if (forceReindex) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
const fileInfo = this.metadata.files[file.path];
|
|
65
|
+
if (!fileInfo) {
|
|
66
|
+
return true; // New file
|
|
67
|
+
}
|
|
68
|
+
if (fileInfo.hash !== file.hash) {
|
|
69
|
+
return true; // File changed
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Indexes a single file
|
|
75
|
+
*/
|
|
76
|
+
async indexFile(file, forceReindex = false) {
|
|
77
|
+
try {
|
|
78
|
+
// Check if file needs reindexing
|
|
79
|
+
if (!this.needsReindexing(file, forceReindex)) {
|
|
80
|
+
console.error(`Skipping ${file.path} (no changes)`);
|
|
81
|
+
return { chunksCreated: 0 };
|
|
82
|
+
}
|
|
83
|
+
console.error(`Indexing: ${file.path}`);
|
|
84
|
+
// Read file content
|
|
85
|
+
const content = fs.readFileSync(file.absolutePath, "utf-8");
|
|
86
|
+
// Get chunk size from environment or use defaults
|
|
87
|
+
const maxChunkSize = parseInt(process.env.MEMORYBANK_CHUNK_SIZE || "1000");
|
|
88
|
+
const chunkOverlap = parseInt(process.env.MEMORYBANK_CHUNK_OVERLAP || "200");
|
|
89
|
+
// Chunk the code
|
|
90
|
+
const chunks = chunkCode({
|
|
91
|
+
filePath: file.path,
|
|
92
|
+
content,
|
|
93
|
+
language: file.language,
|
|
94
|
+
maxChunkSize,
|
|
95
|
+
chunkOverlap,
|
|
96
|
+
});
|
|
97
|
+
if (chunks.length === 0) {
|
|
98
|
+
console.error(`Warning: No chunks created for ${file.path}`);
|
|
99
|
+
return { chunksCreated: 0 };
|
|
100
|
+
}
|
|
101
|
+
console.error(` Created ${chunks.length} chunks`);
|
|
102
|
+
// Generate embeddings
|
|
103
|
+
const embeddingInputs = chunks.map((chunk) => ({
|
|
104
|
+
id: chunk.id,
|
|
105
|
+
content: chunk.content,
|
|
106
|
+
}));
|
|
107
|
+
const embeddings = await this.embeddingService.generateBatchEmbeddings(embeddingInputs);
|
|
108
|
+
console.error(` Generated ${embeddings.length} embeddings`);
|
|
109
|
+
// Prepare chunk records for storage
|
|
110
|
+
const timestamp = Date.now();
|
|
111
|
+
const chunkRecords = chunks.map((chunk, i) => ({
|
|
112
|
+
id: chunk.id,
|
|
113
|
+
vector: embeddings[i].vector,
|
|
114
|
+
filePath: chunk.filePath,
|
|
115
|
+
content: chunk.content,
|
|
116
|
+
startLine: chunk.startLine,
|
|
117
|
+
endLine: chunk.endLine,
|
|
118
|
+
chunkType: chunk.chunkType,
|
|
119
|
+
name: chunk.name,
|
|
120
|
+
language: chunk.language,
|
|
121
|
+
fileHash: file.hash,
|
|
122
|
+
timestamp,
|
|
123
|
+
context: chunk.context,
|
|
124
|
+
}));
|
|
125
|
+
// Delete old chunks for this file
|
|
126
|
+
await this.vectorStore.deleteChunksByFile(file.path);
|
|
127
|
+
// Insert new chunks
|
|
128
|
+
await this.vectorStore.insertChunks(chunkRecords);
|
|
129
|
+
console.error(` Stored ${chunkRecords.length} chunks in vector store`);
|
|
130
|
+
// Update metadata
|
|
131
|
+
this.metadata.files[file.path] = {
|
|
132
|
+
hash: file.hash,
|
|
133
|
+
lastIndexed: timestamp,
|
|
134
|
+
chunkCount: chunks.length,
|
|
135
|
+
};
|
|
136
|
+
this.metadata.lastIndexed = timestamp;
|
|
137
|
+
this.saveMetadata();
|
|
138
|
+
return { chunksCreated: chunks.length };
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const errorMsg = `Error indexing ${file.path}: ${error}`;
|
|
142
|
+
console.error(errorMsg);
|
|
143
|
+
return { chunksCreated: 0, error: errorMsg };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Indexes multiple files or a directory
|
|
148
|
+
*/
|
|
149
|
+
async indexFiles(options) {
|
|
150
|
+
const startTime = Date.now();
|
|
151
|
+
console.error(`\n=== Starting indexing process ===`);
|
|
152
|
+
console.error(`Root path: ${options.rootPath}`);
|
|
153
|
+
console.error(`Force reindex: ${options.forceReindex || false}`);
|
|
154
|
+
// Initialize vector store
|
|
155
|
+
await this.vectorStore.initialize();
|
|
156
|
+
// Scan files
|
|
157
|
+
console.error(`\nScanning files...`);
|
|
158
|
+
const files = scanFiles({
|
|
159
|
+
rootPath: options.rootPath,
|
|
160
|
+
recursive: options.recursive !== undefined ? options.recursive : true,
|
|
161
|
+
});
|
|
162
|
+
if (files.length === 0) {
|
|
163
|
+
console.error("No files found to index");
|
|
164
|
+
return {
|
|
165
|
+
filesProcessed: 0,
|
|
166
|
+
chunksCreated: 0,
|
|
167
|
+
errors: [],
|
|
168
|
+
duration: Date.now() - startTime,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// Filter files that need reindexing
|
|
172
|
+
const filesToIndex = files.filter((file) => this.needsReindexing(file, options.forceReindex || false));
|
|
173
|
+
console.error(`\nFound ${files.length} files, ${filesToIndex.length} need indexing`);
|
|
174
|
+
if (filesToIndex.length === 0) {
|
|
175
|
+
console.error("All files are up to date");
|
|
176
|
+
return {
|
|
177
|
+
filesProcessed: 0,
|
|
178
|
+
chunksCreated: 0,
|
|
179
|
+
errors: [],
|
|
180
|
+
duration: Date.now() - startTime,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// Index files
|
|
184
|
+
const errors = [];
|
|
185
|
+
let totalChunks = 0;
|
|
186
|
+
let processedFiles = 0;
|
|
187
|
+
for (let i = 0; i < filesToIndex.length; i++) {
|
|
188
|
+
const file = filesToIndex[i];
|
|
189
|
+
console.error(`\n[${i + 1}/${filesToIndex.length}] Processing ${file.path}`);
|
|
190
|
+
const result = await this.indexFile(file, options.forceReindex || false);
|
|
191
|
+
if (result.error) {
|
|
192
|
+
errors.push(result.error);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
processedFiles++;
|
|
196
|
+
totalChunks += result.chunksCreated;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const duration = Date.now() - startTime;
|
|
200
|
+
console.error(`\n=== Indexing complete ===`);
|
|
201
|
+
console.error(`Files processed: ${processedFiles}`);
|
|
202
|
+
console.error(`Chunks created: ${totalChunks}`);
|
|
203
|
+
console.error(`Errors: ${errors.length}`);
|
|
204
|
+
console.error(`Duration: ${(duration / 1000).toFixed(2)}s`);
|
|
205
|
+
return {
|
|
206
|
+
filesProcessed: processedFiles,
|
|
207
|
+
chunksCreated: totalChunks,
|
|
208
|
+
errors,
|
|
209
|
+
duration,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Re-indexes a specific file by path
|
|
214
|
+
*/
|
|
215
|
+
async reindexFile(filePath, rootPath) {
|
|
216
|
+
try {
|
|
217
|
+
// Scan the specific file
|
|
218
|
+
const file = scanSingleFile(filePath, rootPath);
|
|
219
|
+
if (!file) {
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
chunksCreated: 0,
|
|
223
|
+
error: "File not found or not a code file",
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// Initialize vector store
|
|
227
|
+
await this.vectorStore.initialize();
|
|
228
|
+
// Index the file
|
|
229
|
+
const result = await this.indexFile(file, true);
|
|
230
|
+
if (result.error) {
|
|
231
|
+
return {
|
|
232
|
+
success: false,
|
|
233
|
+
chunksCreated: 0,
|
|
234
|
+
error: result.error,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
success: true,
|
|
239
|
+
chunksCreated: result.chunksCreated,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
success: false,
|
|
245
|
+
chunksCreated: 0,
|
|
246
|
+
error: `Error reindexing file: ${error}`,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Gets statistics about the index
|
|
252
|
+
*/
|
|
253
|
+
async getStats() {
|
|
254
|
+
await this.vectorStore.initialize();
|
|
255
|
+
const vectorStats = await this.vectorStore.getStats();
|
|
256
|
+
const fileHashes = await this.vectorStore.getFileHashes();
|
|
257
|
+
// Check for files that need reindexing
|
|
258
|
+
const pendingFiles = [];
|
|
259
|
+
for (const [filePath, storedHash] of fileHashes) {
|
|
260
|
+
const metadataHash = this.metadata.files[filePath]?.hash;
|
|
261
|
+
if (metadataHash && metadataHash !== storedHash) {
|
|
262
|
+
pendingFiles.push(filePath);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
totalFiles: vectorStats.fileCount,
|
|
267
|
+
totalChunks: vectorStats.totalChunks,
|
|
268
|
+
lastIndexed: vectorStats.lastUpdated,
|
|
269
|
+
languages: vectorStats.languageCounts,
|
|
270
|
+
pendingFiles: pendingFiles.length > 0 ? pendingFiles : undefined,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Searches the index
|
|
275
|
+
*/
|
|
276
|
+
async search(query, options = {}) {
|
|
277
|
+
await this.vectorStore.initialize();
|
|
278
|
+
// Generate query embedding
|
|
279
|
+
const queryVector = await this.embeddingService.generateQueryEmbedding(query);
|
|
280
|
+
// Search vector store
|
|
281
|
+
const results = await this.vectorStore.search(queryVector, {
|
|
282
|
+
topK: options.topK || 10,
|
|
283
|
+
minScore: options.minScore || 0.0,
|
|
284
|
+
filterByFile: options.filterByFile,
|
|
285
|
+
filterByLanguage: options.filterByLanguage,
|
|
286
|
+
});
|
|
287
|
+
// Format results
|
|
288
|
+
return results.map((result) => ({
|
|
289
|
+
filePath: result.chunk.filePath,
|
|
290
|
+
content: result.chunk.content,
|
|
291
|
+
startLine: result.chunk.startLine,
|
|
292
|
+
endLine: result.chunk.endLine,
|
|
293
|
+
chunkType: result.chunk.chunkType,
|
|
294
|
+
name: result.chunk.name,
|
|
295
|
+
language: result.chunk.language,
|
|
296
|
+
score: result.score,
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Clears the entire index
|
|
301
|
+
*/
|
|
302
|
+
async clearIndex() {
|
|
303
|
+
await this.vectorStore.initialize();
|
|
304
|
+
await this.vectorStore.clear();
|
|
305
|
+
this.metadata = {
|
|
306
|
+
version: "1.0",
|
|
307
|
+
lastIndexed: 0,
|
|
308
|
+
files: {},
|
|
309
|
+
};
|
|
310
|
+
this.saveMetadata();
|
|
311
|
+
// Clear embedding cache
|
|
312
|
+
this.embeddingService.clearCache();
|
|
313
|
+
console.error("Index cleared");
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Removes a file from the index
|
|
317
|
+
*/
|
|
318
|
+
async removeFile(filePath) {
|
|
319
|
+
await this.vectorStore.initialize();
|
|
320
|
+
await this.vectorStore.deleteChunksByFile(filePath);
|
|
321
|
+
delete this.metadata.files[filePath];
|
|
322
|
+
this.saveMetadata();
|
|
323
|
+
console.error(`Removed ${filePath} from index`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Creates an index manager from environment variables
|
|
328
|
+
*/
|
|
329
|
+
export function createIndexManager(embeddingService, vectorStore) {
|
|
330
|
+
const storagePath = process.env.MEMORYBANK_STORAGE_PATH || ".memorybank";
|
|
331
|
+
return new IndexManager(embeddingService, vectorStore, storagePath);
|
|
332
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Global variables to store user IDs
|
|
2
|
+
let adminUserId = null;
|
|
3
|
+
import { getUserIdByEmail, getUserIdByUsername } from "./utils.js";
|
|
4
|
+
/**
|
|
5
|
+
* Gets the admin user ID by looking up the user by email or username
|
|
6
|
+
*
|
|
7
|
+
* This function will try the following methods in order:
|
|
8
|
+
* 1. Use the cached admin user ID if available
|
|
9
|
+
* 2. Use the PLANKA_ADMIN_ID environment variable if set (for backwards compatibility)
|
|
10
|
+
* 3. Look up the admin user ID by email using PLANKA_ADMIN_EMAIL
|
|
11
|
+
* 4. Look up the admin user ID by username using PLANKA_ADMIN_USERNAME
|
|
12
|
+
*/
|
|
13
|
+
export async function getAdminUserId() {
|
|
14
|
+
if (adminUserId) {
|
|
15
|
+
return adminUserId;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
// Check for direct admin ID (for backwards compatibility)
|
|
19
|
+
const directAdminId = process.env.PLANKA_ADMIN_ID;
|
|
20
|
+
if (directAdminId) {
|
|
21
|
+
adminUserId = directAdminId;
|
|
22
|
+
return adminUserId;
|
|
23
|
+
}
|
|
24
|
+
// Try to get the admin ID by email
|
|
25
|
+
const adminEmail = process.env.PLANKA_ADMIN_EMAIL;
|
|
26
|
+
if (adminEmail) {
|
|
27
|
+
const id = await getUserIdByEmail(adminEmail);
|
|
28
|
+
if (id) {
|
|
29
|
+
adminUserId = id;
|
|
30
|
+
return adminUserId;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// If that fails, try to get the admin ID by username
|
|
34
|
+
const adminUsername = process.env.PLANKA_ADMIN_USERNAME;
|
|
35
|
+
if (adminUsername) {
|
|
36
|
+
const id = await getUserIdByUsername(adminUsername);
|
|
37
|
+
if (id) {
|
|
38
|
+
adminUserId = id;
|
|
39
|
+
return adminUserId;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
console.error("Could not determine admin user ID. Please set PLANKA_ADMIN_ID, PLANKA_ADMIN_EMAIL, or PLANKA_ADMIN_USERNAME.");
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error("Failed to get admin user ID:", error);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Planka schemas
|
|
3
|
+
export const PlankaUserSchema = z.object({
|
|
4
|
+
id: z.string(),
|
|
5
|
+
email: z.string(),
|
|
6
|
+
name: z.string().nullable(),
|
|
7
|
+
username: z.string(),
|
|
8
|
+
avatarUrl: z.string().nullable(),
|
|
9
|
+
createdAt: z.string(),
|
|
10
|
+
updatedAt: z.string().nullable(),
|
|
11
|
+
});
|
|
12
|
+
export const PlankaProjectSchema = z.object({
|
|
13
|
+
id: z.string(),
|
|
14
|
+
name: z.string(),
|
|
15
|
+
background: z.string().nullable(),
|
|
16
|
+
createdAt: z.string(),
|
|
17
|
+
updatedAt: z.string().nullable(),
|
|
18
|
+
});
|
|
19
|
+
export const PlankaBoardSchema = z.object({
|
|
20
|
+
id: z.string(),
|
|
21
|
+
projectId: z.string(),
|
|
22
|
+
name: z.string(),
|
|
23
|
+
position: z.number(),
|
|
24
|
+
createdAt: z.string(),
|
|
25
|
+
updatedAt: z.string().nullable(),
|
|
26
|
+
});
|
|
27
|
+
export const PlankaListSchema = z.object({
|
|
28
|
+
id: z.string(),
|
|
29
|
+
boardId: z.string(),
|
|
30
|
+
name: z.string(),
|
|
31
|
+
position: z.number(),
|
|
32
|
+
createdAt: z.string(),
|
|
33
|
+
updatedAt: z.string().nullable(),
|
|
34
|
+
});
|
|
35
|
+
export const PlankaLabelSchema = z.object({
|
|
36
|
+
id: z.string(),
|
|
37
|
+
boardId: z.string(),
|
|
38
|
+
name: z.string(),
|
|
39
|
+
color: z.string(),
|
|
40
|
+
createdAt: z.string(),
|
|
41
|
+
updatedAt: z.string().nullable(),
|
|
42
|
+
});
|
|
43
|
+
// Define the stopwatch schema
|
|
44
|
+
export const PlankaStopwatchSchema = z.object({
|
|
45
|
+
startedAt: z.string().nullable(),
|
|
46
|
+
total: z.number(),
|
|
47
|
+
});
|
|
48
|
+
export const PlankaCardSchema = z.object({
|
|
49
|
+
id: z.string(),
|
|
50
|
+
listId: z.string(),
|
|
51
|
+
name: z.string(),
|
|
52
|
+
description: z.string().nullable(),
|
|
53
|
+
position: z.number(),
|
|
54
|
+
dueDate: z.string().nullable(),
|
|
55
|
+
isCompleted: z.boolean().optional(),
|
|
56
|
+
stopwatch: PlankaStopwatchSchema.nullable().optional(),
|
|
57
|
+
createdAt: z.string(),
|
|
58
|
+
updatedAt: z.string().nullable(),
|
|
59
|
+
});
|
|
60
|
+
export const PlankaTaskSchema = z.object({
|
|
61
|
+
id: z.string(),
|
|
62
|
+
cardId: z.string(),
|
|
63
|
+
name: z.string(),
|
|
64
|
+
isCompleted: z.boolean(),
|
|
65
|
+
position: z.number(),
|
|
66
|
+
createdAt: z.string(),
|
|
67
|
+
updatedAt: z.string().nullable(),
|
|
68
|
+
});
|
|
69
|
+
export const PlankaCommentSchema = z.object({
|
|
70
|
+
id: z.string(),
|
|
71
|
+
cardId: z.string(),
|
|
72
|
+
userId: z.string(),
|
|
73
|
+
text: z.string(),
|
|
74
|
+
createdAt: z.string(),
|
|
75
|
+
updatedAt: z.string().nullable(),
|
|
76
|
+
});
|
|
77
|
+
export const PlankaAttachmentSchema = z.object({
|
|
78
|
+
id: z.string(),
|
|
79
|
+
cardId: z.string(),
|
|
80
|
+
userId: z.string(),
|
|
81
|
+
name: z.string(),
|
|
82
|
+
url: z.string(),
|
|
83
|
+
createdAt: z.string(),
|
|
84
|
+
updatedAt: z.string().nullable(),
|
|
85
|
+
});
|
|
86
|
+
export const PlankaCardMembershipSchema = z.object({
|
|
87
|
+
id: z.string(),
|
|
88
|
+
cardId: z.string(),
|
|
89
|
+
userId: z.string(),
|
|
90
|
+
createdAt: z.string(),
|
|
91
|
+
updatedAt: z.string().nullable(),
|
|
92
|
+
});
|
|
93
|
+
export const PlankaBoardMembershipSchema = z.object({
|
|
94
|
+
id: z.string(),
|
|
95
|
+
boardId: z.string(),
|
|
96
|
+
userId: z.string(),
|
|
97
|
+
role: z.enum(["editor", "admin"]),
|
|
98
|
+
createdAt: z.string(),
|
|
99
|
+
updatedAt: z.string(),
|
|
100
|
+
});
|
|
101
|
+
export const PlankaProjectMembershipSchema = z.object({
|
|
102
|
+
id: z.string(),
|
|
103
|
+
projectId: z.string(),
|
|
104
|
+
userId: z.string(),
|
|
105
|
+
role: z.enum(["editor", "admin"]),
|
|
106
|
+
createdAt: z.string(),
|
|
107
|
+
updatedAt: z.string(),
|
|
108
|
+
});
|
|
109
|
+
export const PlankaCardLabelSchema = z.object({
|
|
110
|
+
id: z.string(),
|
|
111
|
+
cardId: z.string(),
|
|
112
|
+
labelId: z.string(),
|
|
113
|
+
createdAt: z.string(),
|
|
114
|
+
updatedAt: z.string(),
|
|
115
|
+
});
|