c0de-agent 1.6.0 → 1.7.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.
- package/dist/core/loop.js +6 -0
- package/dist/db/schema.d.ts +299 -0
- package/dist/db/schema.js +36 -0
- package/dist/kanban/index.d.ts +1 -0
- package/dist/kanban/index.js +1 -0
- package/dist/kanban/store.d.ts +8 -0
- package/dist/kanban/store.js +158 -0
- package/dist/server/app.js +3 -0
- package/dist/server/routes/kanban.d.ts +4 -0
- package/dist/server/routes/kanban.js +74 -0
- package/dist/shared/types/index.d.ts +1 -0
- package/dist/shared/types/kanban.d.ts +75 -0
- package/dist/shared/types/kanban.js +24 -0
- package/dist/shared/types/tool.d.ts +4 -0
- package/dist/tools/builtin/kanban.d.ts +32 -0
- package/dist/tools/builtin/kanban.js +203 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +3 -1
- package/drizzle/0004_smooth_red_skull.sql +26 -0
- package/drizzle/meta/0004_snapshot.json +849 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +3 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kanban shared types — used by backend store, kanban tool, REST routes,
|
|
3
|
+
* and frontend components.
|
|
4
|
+
*/
|
|
5
|
+
/** A kanban column definition (stored as JSON in kanban_boards.columns). */
|
|
6
|
+
type KanbanColumnDef = {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
/** A kanban label definition (stored as JSON in kanban_boards.labels). */
|
|
11
|
+
type KanbanLabelDef = {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
/** Hex color, e.g. "#ef4444" */
|
|
15
|
+
color: string;
|
|
16
|
+
};
|
|
17
|
+
/** Card priority level. */
|
|
18
|
+
type KanbanPriority = 'high' | 'medium' | 'low';
|
|
19
|
+
/** A kanban card (task). */
|
|
20
|
+
type KanbanCard = {
|
|
21
|
+
id: string;
|
|
22
|
+
boardId: string;
|
|
23
|
+
title: string;
|
|
24
|
+
description: string | null;
|
|
25
|
+
columnId: string;
|
|
26
|
+
priority: KanbanPriority;
|
|
27
|
+
position: number;
|
|
28
|
+
/** Label ids referencing board.labels[].id */
|
|
29
|
+
labels: string[];
|
|
30
|
+
createdAt: string;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
};
|
|
33
|
+
/** A kanban board (one per project). */
|
|
34
|
+
type KanbanBoard = {
|
|
35
|
+
id: string;
|
|
36
|
+
projectId: string;
|
|
37
|
+
columns: KanbanColumnDef[];
|
|
38
|
+
labels: KanbanLabelDef[];
|
|
39
|
+
createdAt: string;
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
};
|
|
42
|
+
/** Board with all its cards (the full payload for GET /api/kanban/:projectId). */
|
|
43
|
+
type KanbanBoardWithCards = KanbanBoard & {
|
|
44
|
+
cards: KanbanCard[];
|
|
45
|
+
};
|
|
46
|
+
/** Default 5 columns for a new board. */
|
|
47
|
+
declare const DEFAULT_KANBAN_COLUMNS: readonly KanbanColumnDef[];
|
|
48
|
+
/** A curated palette for new labels (user can pick or override). */
|
|
49
|
+
declare const KANBAN_LABEL_COLORS: readonly string[];
|
|
50
|
+
/** Dependency-reversal interface for the `kanban` tool (host injects a db-backed
|
|
51
|
+
* implementation, mirroring the todoState pattern). */
|
|
52
|
+
interface KanbanStore {
|
|
53
|
+
getBoard(): Promise<KanbanBoardWithCards>;
|
|
54
|
+
addCard(input: {
|
|
55
|
+
title: string;
|
|
56
|
+
description?: string | null;
|
|
57
|
+
columnId?: string;
|
|
58
|
+
priority?: KanbanPriority;
|
|
59
|
+
labels?: string[];
|
|
60
|
+
}): Promise<KanbanCard>;
|
|
61
|
+
updateCard(id: string, patch: {
|
|
62
|
+
title?: string;
|
|
63
|
+
description?: string | null;
|
|
64
|
+
priority?: KanbanPriority;
|
|
65
|
+
labels?: string[];
|
|
66
|
+
}): Promise<KanbanCard>;
|
|
67
|
+
moveCard(id: string, columnId: string, position?: number): Promise<KanbanCard>;
|
|
68
|
+
deleteCard(id: string): Promise<void>;
|
|
69
|
+
updateBoard(patch: {
|
|
70
|
+
columns?: KanbanColumnDef[];
|
|
71
|
+
labels?: KanbanLabelDef[];
|
|
72
|
+
}): Promise<KanbanBoard>;
|
|
73
|
+
}
|
|
74
|
+
export type { KanbanBoard, KanbanBoardWithCards, KanbanCard, KanbanColumnDef, KanbanLabelDef, KanbanPriority, KanbanStore, };
|
|
75
|
+
export { DEFAULT_KANBAN_COLUMNS, KANBAN_LABEL_COLORS };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kanban shared types — used by backend store, kanban tool, REST routes,
|
|
3
|
+
* and frontend components.
|
|
4
|
+
*/
|
|
5
|
+
/** Default 5 columns for a new board. */
|
|
6
|
+
const DEFAULT_KANBAN_COLUMNS = [
|
|
7
|
+
{ id: 'todo', name: '待办' },
|
|
8
|
+
{ id: 'in_progress', name: '进行中' },
|
|
9
|
+
{ id: 'in_review', name: '审核中' },
|
|
10
|
+
{ id: 'done', name: '已完成' },
|
|
11
|
+
{ id: 'cancelled', name: '已取消' },
|
|
12
|
+
];
|
|
13
|
+
/** A curated palette for new labels (user can pick or override). */
|
|
14
|
+
const KANBAN_LABEL_COLORS = [
|
|
15
|
+
'#ef4444', // red
|
|
16
|
+
'#f97316', // orange
|
|
17
|
+
'#eab308', // yellow
|
|
18
|
+
'#22c55e', // green
|
|
19
|
+
'#06b6d4', // cyan
|
|
20
|
+
'#3b82f6', // blue
|
|
21
|
+
'#8b5cf6', // violet
|
|
22
|
+
'#ec4899', // pink
|
|
23
|
+
];
|
|
24
|
+
export { DEFAULT_KANBAN_COLUMNS, KANBAN_LABEL_COLORS };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JSONSchema, SessionRef } from './base.js';
|
|
2
|
+
import type { KanbanStore } from './kanban.js';
|
|
2
3
|
export type TodoPhaseLike = {
|
|
3
4
|
name: string;
|
|
4
5
|
tasks: {
|
|
@@ -79,6 +80,9 @@ type ToolContext = {
|
|
|
79
80
|
get: () => TodoPhaseLike[];
|
|
80
81
|
set: (phases: TodoPhaseLike[]) => void;
|
|
81
82
|
};
|
|
83
|
+
/** Kanban store (dependency-reversal for the `kanban` tool).
|
|
84
|
+
* Host injects a db-backed implementation scoped to the session's project. */
|
|
85
|
+
kanbanStore?: KanbanStore;
|
|
82
86
|
};
|
|
83
87
|
/** 单个并行子任务项(批量模式)。 */
|
|
84
88
|
type TaskItem = {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { KanbanPriority } from '../../shared/types/kanban.js';
|
|
2
|
+
import type { ToolDef } from '../../shared/types/tool.js';
|
|
3
|
+
type KanbanInput = {
|
|
4
|
+
op: 'view';
|
|
5
|
+
} | {
|
|
6
|
+
op: 'add';
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
columnId?: string;
|
|
10
|
+
priority?: KanbanPriority;
|
|
11
|
+
labels?: string[];
|
|
12
|
+
} | {
|
|
13
|
+
op: 'update';
|
|
14
|
+
id: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
description?: string | null;
|
|
17
|
+
priority?: KanbanPriority;
|
|
18
|
+
labels?: string[];
|
|
19
|
+
} | {
|
|
20
|
+
op: 'move';
|
|
21
|
+
id: string;
|
|
22
|
+
columnId: string;
|
|
23
|
+
position?: number;
|
|
24
|
+
} | {
|
|
25
|
+
op: 'delete';
|
|
26
|
+
id: string;
|
|
27
|
+
};
|
|
28
|
+
/** kanban tool: project-scoped task board for human↔agent collaboration.
|
|
29
|
+
* Permission: auto (operates on the project's kanban board, no system side effects).
|
|
30
|
+
* State persists in DB via ctx.kanbanStore (dependency-reversal, like todoState). */
|
|
31
|
+
export declare const kanbanTool: ToolDef;
|
|
32
|
+
export type { KanbanInput };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// kanban tool: project-scoped task board for agent collaboration.
|
|
2
|
+
// Mirrors the todo tool's ToolDef pattern but persists to DB (per-project)
|
|
3
|
+
// instead of in-memory session state. Agents can add, move, update, delete
|
|
4
|
+
// tasks and view the full board — enabling human↔agent and agent↔agent
|
|
5
|
+
// coordination via a shared kanban board.
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// Summary formatter
|
|
8
|
+
// =============================================================================
|
|
9
|
+
/** Priority display symbol for compact board summary. */
|
|
10
|
+
const PRIORITY_SYMBOL = {
|
|
11
|
+
high: '🔴',
|
|
12
|
+
medium: '🟡',
|
|
13
|
+
low: '⚪',
|
|
14
|
+
};
|
|
15
|
+
function formatBoardSummary(board, errors = []) {
|
|
16
|
+
const lines = [];
|
|
17
|
+
if (errors.length > 0)
|
|
18
|
+
lines.push(`Errors: ${errors.join('; ')}`);
|
|
19
|
+
for (const col of board.columns) {
|
|
20
|
+
const cards = board.cards.filter((c) => c.columnId === col.id);
|
|
21
|
+
lines.push(`## ${col.name} (${cards.length})`);
|
|
22
|
+
for (const card of cards) {
|
|
23
|
+
const shortId = card.id.slice(0, 8);
|
|
24
|
+
const sym = PRIORITY_SYMBOL[card.priority] ?? '';
|
|
25
|
+
lines.push(` ${sym} [${shortId}] ${card.title}`);
|
|
26
|
+
if (card.description) {
|
|
27
|
+
const preview = card.description.length > 80 ? `${card.description.slice(0, 77)}…` : card.description;
|
|
28
|
+
lines.push(` ${preview}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (cards.length === 0)
|
|
32
|
+
lines.push(' (empty)');
|
|
33
|
+
}
|
|
34
|
+
const total = board.cards.length;
|
|
35
|
+
lines.push(`\nTotal: ${total} card(s).`);
|
|
36
|
+
if (total === 0)
|
|
37
|
+
lines.push('Board is empty — use kanban add to create tasks.');
|
|
38
|
+
return lines.join('\n');
|
|
39
|
+
}
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// Schema
|
|
42
|
+
// =============================================================================
|
|
43
|
+
const kanbanParameters = {
|
|
44
|
+
type: 'object',
|
|
45
|
+
description: 'Apply a single kanban board operation',
|
|
46
|
+
properties: {
|
|
47
|
+
op: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
enum: ['view', 'add', 'update', 'move', 'delete'],
|
|
50
|
+
description: 'Operation to apply',
|
|
51
|
+
},
|
|
52
|
+
title: { type: 'string', description: 'Task title (for add, update)' },
|
|
53
|
+
description: { type: 'string', description: 'Task description (for add, update)' },
|
|
54
|
+
id: { type: 'string', description: 'Card id (for update, move, delete)' },
|
|
55
|
+
columnId: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'Column id to place/move the card (for add, move)',
|
|
58
|
+
},
|
|
59
|
+
priority: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
enum: ['high', 'medium', 'low'],
|
|
62
|
+
description: 'Priority level (for add, update)',
|
|
63
|
+
},
|
|
64
|
+
labels: {
|
|
65
|
+
type: 'array',
|
|
66
|
+
items: { type: 'string' },
|
|
67
|
+
description: 'Label ids (for add, update)',
|
|
68
|
+
},
|
|
69
|
+
position: {
|
|
70
|
+
type: 'number',
|
|
71
|
+
description: 'Position within column (for move). Omit to append.',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
required: ['op'],
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
};
|
|
77
|
+
// =============================================================================
|
|
78
|
+
// Tool definition
|
|
79
|
+
// =============================================================================
|
|
80
|
+
/** kanban tool: project-scoped task board for human↔agent collaboration.
|
|
81
|
+
* Permission: auto (operates on the project's kanban board, no system side effects).
|
|
82
|
+
* State persists in DB via ctx.kanbanStore (dependency-reversal, like todoState). */
|
|
83
|
+
export const kanbanTool = {
|
|
84
|
+
name: 'kanban',
|
|
85
|
+
description: `Manage the project's shared kanban task board. 5 operations:
|
|
86
|
+
- view: list all columns and cards (read-only)
|
|
87
|
+
- add: create a new task card (title required; optional description, columnId defaults to todo, priority defaults to medium, labels)
|
|
88
|
+
- update: edit a card's title/description/priority/labels (id required)
|
|
89
|
+
- move: move a card to a different column or reorder (id + columnId required; optional position)
|
|
90
|
+
- delete: remove a card (id required)
|
|
91
|
+
|
|
92
|
+
The board is shared across all sessions and agents in the same project — use it to coordinate work, track tasks visible to the user, and break down complex projects. Column ids are: todo, in_progress, in_review, done, cancelled (customizable by the user via the board config UI).`,
|
|
93
|
+
parameters: kanbanParameters,
|
|
94
|
+
permission: 'auto',
|
|
95
|
+
execute: async (input, ctx) => {
|
|
96
|
+
const params = input;
|
|
97
|
+
const store = ctx.kanbanStore;
|
|
98
|
+
if (!store) {
|
|
99
|
+
return { _tag: 'error', error: 'Kanban store not available in this context' };
|
|
100
|
+
}
|
|
101
|
+
const errors = [];
|
|
102
|
+
try {
|
|
103
|
+
switch (params.op) {
|
|
104
|
+
case 'view': {
|
|
105
|
+
const board = await store.getBoard();
|
|
106
|
+
return {
|
|
107
|
+
_tag: 'success',
|
|
108
|
+
output: formatBoardSummary(board),
|
|
109
|
+
metadata: { board },
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
case 'add': {
|
|
113
|
+
const addTitle = params.title?.trim();
|
|
114
|
+
if (!addTitle) {
|
|
115
|
+
errors.push('title is required for add');
|
|
116
|
+
}
|
|
117
|
+
if (errors.length > 0)
|
|
118
|
+
break;
|
|
119
|
+
const card = await store.addCard({
|
|
120
|
+
title: addTitle,
|
|
121
|
+
description: params.description ?? null,
|
|
122
|
+
columnId: params.columnId,
|
|
123
|
+
priority: params.priority,
|
|
124
|
+
labels: params.labels,
|
|
125
|
+
});
|
|
126
|
+
const board = await store.getBoard();
|
|
127
|
+
return {
|
|
128
|
+
_tag: 'success',
|
|
129
|
+
output: `Card added: [${card.id.slice(0, 8)}] "${card.title}"\n\n${formatBoardSummary(board)}`,
|
|
130
|
+
metadata: { card, board },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
case 'update': {
|
|
134
|
+
const updateId = params.id?.trim();
|
|
135
|
+
if (!updateId) {
|
|
136
|
+
errors.push('id is required for update');
|
|
137
|
+
}
|
|
138
|
+
if (errors.length > 0)
|
|
139
|
+
break;
|
|
140
|
+
const card = await store.updateCard(updateId, {
|
|
141
|
+
...(params.title !== undefined && { title: params.title }),
|
|
142
|
+
...(params.description !== undefined && { description: params.description }),
|
|
143
|
+
...(params.priority !== undefined && { priority: params.priority }),
|
|
144
|
+
...(params.labels !== undefined && { labels: params.labels }),
|
|
145
|
+
});
|
|
146
|
+
const board = await store.getBoard();
|
|
147
|
+
return {
|
|
148
|
+
_tag: 'success',
|
|
149
|
+
output: `Card updated: [${card.id.slice(0, 8)}] "${card.title}"\n\n${formatBoardSummary(board)}`,
|
|
150
|
+
metadata: { card, board },
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
case 'move': {
|
|
154
|
+
const moveId = params.id?.trim();
|
|
155
|
+
const targetCol = params.columnId?.trim();
|
|
156
|
+
if (!moveId) {
|
|
157
|
+
errors.push('id is required for move');
|
|
158
|
+
}
|
|
159
|
+
if (!targetCol) {
|
|
160
|
+
errors.push('columnId is required for move');
|
|
161
|
+
}
|
|
162
|
+
if (errors.length > 0)
|
|
163
|
+
break;
|
|
164
|
+
const card = await store.moveCard(moveId, targetCol, params.position);
|
|
165
|
+
const board = await store.getBoard();
|
|
166
|
+
return {
|
|
167
|
+
_tag: 'success',
|
|
168
|
+
output: `Card moved: [${card.id.slice(0, 8)}] "${card.title}" → ${targetCol}\n\n${formatBoardSummary(board)}`,
|
|
169
|
+
metadata: { card, board },
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
case 'delete': {
|
|
173
|
+
const deleteId = params.id?.trim();
|
|
174
|
+
if (!deleteId) {
|
|
175
|
+
errors.push('id is required for delete');
|
|
176
|
+
}
|
|
177
|
+
if (errors.length > 0)
|
|
178
|
+
break;
|
|
179
|
+
await store.deleteCard(deleteId);
|
|
180
|
+
const board = await store.getBoard();
|
|
181
|
+
return {
|
|
182
|
+
_tag: 'success',
|
|
183
|
+
output: `Card deleted: ${deleteId.slice(0, 8)}\n\n${formatBoardSummary(board)}`,
|
|
184
|
+
metadata: { board },
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
default:
|
|
188
|
+
errors.push(`Unknown op: ${params.op}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
errors.push(e instanceof Error ? e.message : String(e));
|
|
193
|
+
}
|
|
194
|
+
// Errors are surfaced in output text (like todo tool) so the LLM can read
|
|
195
|
+
// and retry. Return success to avoid skewing tool metrics.
|
|
196
|
+
const board = await store.getBoard();
|
|
197
|
+
return {
|
|
198
|
+
_tag: 'success',
|
|
199
|
+
output: formatBoardSummary(board, errors),
|
|
200
|
+
metadata: { board },
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
};
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { globTool, globToRegex } from './builtin/glob.js';
|
|
|
8
8
|
export { grepTool } from './builtin/grep.js';
|
|
9
9
|
export { readTool } from './builtin/read.js';
|
|
10
10
|
export { createDefaultURLRegistry, createFileResolver, createSkillResolver, } from './builtin/resolvers.js';
|
|
11
|
-
export {
|
|
11
|
+
export { kanbanTool } from './builtin/kanban.js';
|
|
12
12
|
export { todoTool } from './builtin/todo.js';
|
|
13
13
|
export type { TodoInput, TodoItem, TodoPhase, TodoStatus } from './builtin/todo.js';
|
|
14
14
|
export { writeTool } from './builtin/write.js';
|
package/dist/tools/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export { grepTool } from './builtin/grep.js';
|
|
|
7
7
|
export { readTool } from './builtin/read.js';
|
|
8
8
|
// ── Builtin tools ───────────────────────────────────────────
|
|
9
9
|
export { createDefaultURLRegistry, createFileResolver, createSkillResolver, } from './builtin/resolvers.js';
|
|
10
|
-
export {
|
|
10
|
+
export { kanbanTool } from './builtin/kanban.js';
|
|
11
11
|
export { todoTool } from './builtin/todo.js';
|
|
12
12
|
export { writeTool } from './builtin/write.js';
|
|
13
13
|
export { yieldTool } from './builtin/yield.js';
|
|
@@ -27,6 +27,7 @@ import { dapTools } from './builtin/dap.js';
|
|
|
27
27
|
import { editTool } from './builtin/edit.js';
|
|
28
28
|
import { globTool } from './builtin/glob.js';
|
|
29
29
|
import { grepTool } from './builtin/grep.js';
|
|
30
|
+
import { kanbanTool } from './builtin/kanban.js';
|
|
30
31
|
import { readTool } from './builtin/read.js';
|
|
31
32
|
import { taskTool } from './builtin/task.js';
|
|
32
33
|
import { todoTool } from './builtin/todo.js';
|
|
@@ -48,6 +49,7 @@ export function createDefaultRegistry(config = DEFAULT_CONFIG) {
|
|
|
48
49
|
registerTool(reg, globTool);
|
|
49
50
|
registerTool(reg, grepTool);
|
|
50
51
|
registerTool(reg, bashTool);
|
|
52
|
+
registerTool(reg, kanbanTool);
|
|
51
53
|
registerTool(reg, taskTool);
|
|
52
54
|
registerTool(reg, todoTool);
|
|
53
55
|
registerTool(reg, yieldTool);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
CREATE TABLE "kanban_boards" (
|
|
2
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
3
|
+
"project_id" text NOT NULL,
|
|
4
|
+
"columns" jsonb NOT NULL,
|
|
5
|
+
"labels" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
6
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
7
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
8
|
+
);
|
|
9
|
+
--> statement-breakpoint
|
|
10
|
+
CREATE TABLE "kanban_cards" (
|
|
11
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
12
|
+
"board_id" uuid NOT NULL,
|
|
13
|
+
"title" text NOT NULL,
|
|
14
|
+
"description" text,
|
|
15
|
+
"column_id" text NOT NULL,
|
|
16
|
+
"priority" text DEFAULT 'medium' NOT NULL,
|
|
17
|
+
"position" real DEFAULT 0 NOT NULL,
|
|
18
|
+
"labels" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
19
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
20
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
21
|
+
);
|
|
22
|
+
--> statement-breakpoint
|
|
23
|
+
ALTER TABLE "kanban_boards" ADD CONSTRAINT "kanban_boards_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
24
|
+
ALTER TABLE "kanban_cards" ADD CONSTRAINT "kanban_cards_board_id_kanban_boards_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."kanban_boards"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
25
|
+
CREATE UNIQUE INDEX "uq_kanban_boards_project" ON "kanban_boards" USING btree ("project_id");--> statement-breakpoint
|
|
26
|
+
CREATE INDEX "idx_kanban_cards_board" ON "kanban_cards" USING btree ("board_id","column_id","position");
|