c0de-agent 1.5.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/agent.js +4 -0
- package/dist/core/config.js +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/loop.js +15 -0
- package/dist/core/prompt-registry.d.ts +2 -2
- package/dist/core/prompt-registry.js +42 -3
- package/dist/core/slash.js +40 -6
- package/dist/core/types.d.ts +10 -1
- package/dist/core/workflow.d.ts +1 -1
- package/dist/core/workflow.js +54 -6
- package/dist/core/workflows/runtime.d.ts +3 -0
- package/dist/core/workflows/runtime.js +2 -2
- 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/project/resolve.d.ts +75 -0
- package/dist/project/resolve.js +253 -1
- package/dist/server/app.js +6 -0
- package/dist/server/context.js +2 -1
- package/dist/server/dev.js +3 -1
- package/dist/server/routes/chat.js +12 -0
- package/dist/server/routes/commands.js +1 -0
- package/dist/server/routes/files.js +252 -4
- package/dist/server/routes/kanban.d.ts +4 -0
- package/dist/server/routes/kanban.js +74 -0
- package/dist/server/routes/terminal.js +2 -1
- package/dist/server/routes/todo.d.ts +4 -0
- package/dist/server/routes/todo.js +107 -0
- package/dist/server/routes/workflows.js +51 -11
- package/dist/server/server.d.ts +8 -1
- package/dist/server/server.js +113 -23
- package/dist/server/terminal/pty-manager.d.ts +14 -0
- package/dist/server/terminal/pty-manager.js +105 -7
- package/dist/shared/types/agent.d.ts +9 -0
- package/dist/shared/types/config.d.ts +6 -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 +17 -0
- package/dist/tools/builtin/kanban.d.ts +32 -0
- package/dist/tools/builtin/kanban.js +203 -0
- package/dist/tools/builtin/todo.d.ts +67 -0
- package/dist/tools/builtin/todo.js +517 -0
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.js +6 -1
- package/dist/tools/types.d.ts +32 -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 +4 -1
package/dist/db/schema.d.ts
CHANGED
|
@@ -950,6 +950,301 @@ export declare const toolMetrics: import("drizzle-orm/pg-core").PgTableWithColum
|
|
|
950
950
|
};
|
|
951
951
|
dialect: "pg";
|
|
952
952
|
}>;
|
|
953
|
+
/**
|
|
954
|
+
* Kanban boards — one per project (unique projectId). Stores column/label
|
|
955
|
+
* configuration as JSON; cards live in kanban_cards.
|
|
956
|
+
*/
|
|
957
|
+
export declare const kanbanBoards: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
958
|
+
name: "kanban_boards";
|
|
959
|
+
schema: undefined;
|
|
960
|
+
columns: {
|
|
961
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
962
|
+
name: "id";
|
|
963
|
+
tableName: "kanban_boards";
|
|
964
|
+
dataType: "string";
|
|
965
|
+
columnType: "PgUUID";
|
|
966
|
+
data: string;
|
|
967
|
+
driverParam: string;
|
|
968
|
+
notNull: true;
|
|
969
|
+
hasDefault: true;
|
|
970
|
+
isPrimaryKey: true;
|
|
971
|
+
isAutoincrement: false;
|
|
972
|
+
hasRuntimeDefault: false;
|
|
973
|
+
enumValues: undefined;
|
|
974
|
+
baseColumn: never;
|
|
975
|
+
identity: undefined;
|
|
976
|
+
generated: undefined;
|
|
977
|
+
}, {}, {}>;
|
|
978
|
+
projectId: import("drizzle-orm/pg-core").PgColumn<{
|
|
979
|
+
name: "project_id";
|
|
980
|
+
tableName: "kanban_boards";
|
|
981
|
+
dataType: "string";
|
|
982
|
+
columnType: "PgText";
|
|
983
|
+
data: string;
|
|
984
|
+
driverParam: string;
|
|
985
|
+
notNull: true;
|
|
986
|
+
hasDefault: false;
|
|
987
|
+
isPrimaryKey: false;
|
|
988
|
+
isAutoincrement: false;
|
|
989
|
+
hasRuntimeDefault: false;
|
|
990
|
+
enumValues: [string, ...string[]];
|
|
991
|
+
baseColumn: never;
|
|
992
|
+
identity: undefined;
|
|
993
|
+
generated: undefined;
|
|
994
|
+
}, {}, {}>;
|
|
995
|
+
columns: import("drizzle-orm/pg-core").PgColumn<{
|
|
996
|
+
name: "columns";
|
|
997
|
+
tableName: "kanban_boards";
|
|
998
|
+
dataType: "json";
|
|
999
|
+
columnType: "PgJsonb";
|
|
1000
|
+
data: unknown;
|
|
1001
|
+
driverParam: unknown;
|
|
1002
|
+
notNull: true;
|
|
1003
|
+
hasDefault: false;
|
|
1004
|
+
isPrimaryKey: false;
|
|
1005
|
+
isAutoincrement: false;
|
|
1006
|
+
hasRuntimeDefault: false;
|
|
1007
|
+
enumValues: undefined;
|
|
1008
|
+
baseColumn: never;
|
|
1009
|
+
identity: undefined;
|
|
1010
|
+
generated: undefined;
|
|
1011
|
+
}, {}, {}>;
|
|
1012
|
+
labels: import("drizzle-orm/pg-core").PgColumn<{
|
|
1013
|
+
name: "labels";
|
|
1014
|
+
tableName: "kanban_boards";
|
|
1015
|
+
dataType: "json";
|
|
1016
|
+
columnType: "PgJsonb";
|
|
1017
|
+
data: unknown;
|
|
1018
|
+
driverParam: unknown;
|
|
1019
|
+
notNull: true;
|
|
1020
|
+
hasDefault: true;
|
|
1021
|
+
isPrimaryKey: false;
|
|
1022
|
+
isAutoincrement: false;
|
|
1023
|
+
hasRuntimeDefault: false;
|
|
1024
|
+
enumValues: undefined;
|
|
1025
|
+
baseColumn: never;
|
|
1026
|
+
identity: undefined;
|
|
1027
|
+
generated: undefined;
|
|
1028
|
+
}, {}, {}>;
|
|
1029
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
1030
|
+
name: "created_at";
|
|
1031
|
+
tableName: "kanban_boards";
|
|
1032
|
+
dataType: "date";
|
|
1033
|
+
columnType: "PgTimestamp";
|
|
1034
|
+
data: Date;
|
|
1035
|
+
driverParam: string;
|
|
1036
|
+
notNull: true;
|
|
1037
|
+
hasDefault: true;
|
|
1038
|
+
isPrimaryKey: false;
|
|
1039
|
+
isAutoincrement: false;
|
|
1040
|
+
hasRuntimeDefault: false;
|
|
1041
|
+
enumValues: undefined;
|
|
1042
|
+
baseColumn: never;
|
|
1043
|
+
identity: undefined;
|
|
1044
|
+
generated: undefined;
|
|
1045
|
+
}, {}, {}>;
|
|
1046
|
+
updatedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
1047
|
+
name: "updated_at";
|
|
1048
|
+
tableName: "kanban_boards";
|
|
1049
|
+
dataType: "date";
|
|
1050
|
+
columnType: "PgTimestamp";
|
|
1051
|
+
data: Date;
|
|
1052
|
+
driverParam: string;
|
|
1053
|
+
notNull: true;
|
|
1054
|
+
hasDefault: true;
|
|
1055
|
+
isPrimaryKey: false;
|
|
1056
|
+
isAutoincrement: false;
|
|
1057
|
+
hasRuntimeDefault: false;
|
|
1058
|
+
enumValues: undefined;
|
|
1059
|
+
baseColumn: never;
|
|
1060
|
+
identity: undefined;
|
|
1061
|
+
generated: undefined;
|
|
1062
|
+
}, {}, {}>;
|
|
1063
|
+
};
|
|
1064
|
+
dialect: "pg";
|
|
1065
|
+
}>;
|
|
1066
|
+
/**
|
|
1067
|
+
* Kanban cards — tasks on a board. position is a real for fractional indexing
|
|
1068
|
+
* (insert between two cards without rewriting all positions). columnId
|
|
1069
|
+
* references a column id in the parent board's columns JSON.
|
|
1070
|
+
*/
|
|
1071
|
+
export declare const kanbanCards: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
1072
|
+
name: "kanban_cards";
|
|
1073
|
+
schema: undefined;
|
|
1074
|
+
columns: {
|
|
1075
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
1076
|
+
name: "id";
|
|
1077
|
+
tableName: "kanban_cards";
|
|
1078
|
+
dataType: "string";
|
|
1079
|
+
columnType: "PgUUID";
|
|
1080
|
+
data: string;
|
|
1081
|
+
driverParam: string;
|
|
1082
|
+
notNull: true;
|
|
1083
|
+
hasDefault: true;
|
|
1084
|
+
isPrimaryKey: true;
|
|
1085
|
+
isAutoincrement: false;
|
|
1086
|
+
hasRuntimeDefault: false;
|
|
1087
|
+
enumValues: undefined;
|
|
1088
|
+
baseColumn: never;
|
|
1089
|
+
identity: undefined;
|
|
1090
|
+
generated: undefined;
|
|
1091
|
+
}, {}, {}>;
|
|
1092
|
+
boardId: import("drizzle-orm/pg-core").PgColumn<{
|
|
1093
|
+
name: "board_id";
|
|
1094
|
+
tableName: "kanban_cards";
|
|
1095
|
+
dataType: "string";
|
|
1096
|
+
columnType: "PgUUID";
|
|
1097
|
+
data: string;
|
|
1098
|
+
driverParam: string;
|
|
1099
|
+
notNull: true;
|
|
1100
|
+
hasDefault: false;
|
|
1101
|
+
isPrimaryKey: false;
|
|
1102
|
+
isAutoincrement: false;
|
|
1103
|
+
hasRuntimeDefault: false;
|
|
1104
|
+
enumValues: undefined;
|
|
1105
|
+
baseColumn: never;
|
|
1106
|
+
identity: undefined;
|
|
1107
|
+
generated: undefined;
|
|
1108
|
+
}, {}, {}>;
|
|
1109
|
+
title: import("drizzle-orm/pg-core").PgColumn<{
|
|
1110
|
+
name: "title";
|
|
1111
|
+
tableName: "kanban_cards";
|
|
1112
|
+
dataType: "string";
|
|
1113
|
+
columnType: "PgText";
|
|
1114
|
+
data: string;
|
|
1115
|
+
driverParam: string;
|
|
1116
|
+
notNull: true;
|
|
1117
|
+
hasDefault: false;
|
|
1118
|
+
isPrimaryKey: false;
|
|
1119
|
+
isAutoincrement: false;
|
|
1120
|
+
hasRuntimeDefault: false;
|
|
1121
|
+
enumValues: [string, ...string[]];
|
|
1122
|
+
baseColumn: never;
|
|
1123
|
+
identity: undefined;
|
|
1124
|
+
generated: undefined;
|
|
1125
|
+
}, {}, {}>;
|
|
1126
|
+
description: import("drizzle-orm/pg-core").PgColumn<{
|
|
1127
|
+
name: "description";
|
|
1128
|
+
tableName: "kanban_cards";
|
|
1129
|
+
dataType: "string";
|
|
1130
|
+
columnType: "PgText";
|
|
1131
|
+
data: string;
|
|
1132
|
+
driverParam: string;
|
|
1133
|
+
notNull: false;
|
|
1134
|
+
hasDefault: false;
|
|
1135
|
+
isPrimaryKey: false;
|
|
1136
|
+
isAutoincrement: false;
|
|
1137
|
+
hasRuntimeDefault: false;
|
|
1138
|
+
enumValues: [string, ...string[]];
|
|
1139
|
+
baseColumn: never;
|
|
1140
|
+
identity: undefined;
|
|
1141
|
+
generated: undefined;
|
|
1142
|
+
}, {}, {}>;
|
|
1143
|
+
columnId: import("drizzle-orm/pg-core").PgColumn<{
|
|
1144
|
+
name: "column_id";
|
|
1145
|
+
tableName: "kanban_cards";
|
|
1146
|
+
dataType: "string";
|
|
1147
|
+
columnType: "PgText";
|
|
1148
|
+
data: string;
|
|
1149
|
+
driverParam: string;
|
|
1150
|
+
notNull: true;
|
|
1151
|
+
hasDefault: false;
|
|
1152
|
+
isPrimaryKey: false;
|
|
1153
|
+
isAutoincrement: false;
|
|
1154
|
+
hasRuntimeDefault: false;
|
|
1155
|
+
enumValues: [string, ...string[]];
|
|
1156
|
+
baseColumn: never;
|
|
1157
|
+
identity: undefined;
|
|
1158
|
+
generated: undefined;
|
|
1159
|
+
}, {}, {}>;
|
|
1160
|
+
priority: import("drizzle-orm/pg-core").PgColumn<{
|
|
1161
|
+
name: "priority";
|
|
1162
|
+
tableName: "kanban_cards";
|
|
1163
|
+
dataType: "string";
|
|
1164
|
+
columnType: "PgText";
|
|
1165
|
+
data: string;
|
|
1166
|
+
driverParam: string;
|
|
1167
|
+
notNull: true;
|
|
1168
|
+
hasDefault: true;
|
|
1169
|
+
isPrimaryKey: false;
|
|
1170
|
+
isAutoincrement: false;
|
|
1171
|
+
hasRuntimeDefault: false;
|
|
1172
|
+
enumValues: [string, ...string[]];
|
|
1173
|
+
baseColumn: never;
|
|
1174
|
+
identity: undefined;
|
|
1175
|
+
generated: undefined;
|
|
1176
|
+
}, {}, {}>;
|
|
1177
|
+
position: import("drizzle-orm/pg-core").PgColumn<{
|
|
1178
|
+
name: "position";
|
|
1179
|
+
tableName: "kanban_cards";
|
|
1180
|
+
dataType: "number";
|
|
1181
|
+
columnType: "PgReal";
|
|
1182
|
+
data: number;
|
|
1183
|
+
driverParam: string | number;
|
|
1184
|
+
notNull: true;
|
|
1185
|
+
hasDefault: true;
|
|
1186
|
+
isPrimaryKey: false;
|
|
1187
|
+
isAutoincrement: false;
|
|
1188
|
+
hasRuntimeDefault: false;
|
|
1189
|
+
enumValues: undefined;
|
|
1190
|
+
baseColumn: never;
|
|
1191
|
+
identity: undefined;
|
|
1192
|
+
generated: undefined;
|
|
1193
|
+
}, {}, {}>;
|
|
1194
|
+
labels: import("drizzle-orm/pg-core").PgColumn<{
|
|
1195
|
+
name: "labels";
|
|
1196
|
+
tableName: "kanban_cards";
|
|
1197
|
+
dataType: "json";
|
|
1198
|
+
columnType: "PgJsonb";
|
|
1199
|
+
data: unknown;
|
|
1200
|
+
driverParam: unknown;
|
|
1201
|
+
notNull: true;
|
|
1202
|
+
hasDefault: true;
|
|
1203
|
+
isPrimaryKey: false;
|
|
1204
|
+
isAutoincrement: false;
|
|
1205
|
+
hasRuntimeDefault: false;
|
|
1206
|
+
enumValues: undefined;
|
|
1207
|
+
baseColumn: never;
|
|
1208
|
+
identity: undefined;
|
|
1209
|
+
generated: undefined;
|
|
1210
|
+
}, {}, {}>;
|
|
1211
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
1212
|
+
name: "created_at";
|
|
1213
|
+
tableName: "kanban_cards";
|
|
1214
|
+
dataType: "date";
|
|
1215
|
+
columnType: "PgTimestamp";
|
|
1216
|
+
data: Date;
|
|
1217
|
+
driverParam: string;
|
|
1218
|
+
notNull: true;
|
|
1219
|
+
hasDefault: true;
|
|
1220
|
+
isPrimaryKey: false;
|
|
1221
|
+
isAutoincrement: false;
|
|
1222
|
+
hasRuntimeDefault: false;
|
|
1223
|
+
enumValues: undefined;
|
|
1224
|
+
baseColumn: never;
|
|
1225
|
+
identity: undefined;
|
|
1226
|
+
generated: undefined;
|
|
1227
|
+
}, {}, {}>;
|
|
1228
|
+
updatedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
1229
|
+
name: "updated_at";
|
|
1230
|
+
tableName: "kanban_cards";
|
|
1231
|
+
dataType: "date";
|
|
1232
|
+
columnType: "PgTimestamp";
|
|
1233
|
+
data: Date;
|
|
1234
|
+
driverParam: string;
|
|
1235
|
+
notNull: true;
|
|
1236
|
+
hasDefault: true;
|
|
1237
|
+
isPrimaryKey: false;
|
|
1238
|
+
isAutoincrement: false;
|
|
1239
|
+
hasRuntimeDefault: false;
|
|
1240
|
+
enumValues: undefined;
|
|
1241
|
+
baseColumn: never;
|
|
1242
|
+
identity: undefined;
|
|
1243
|
+
generated: undefined;
|
|
1244
|
+
}, {}, {}>;
|
|
1245
|
+
};
|
|
1246
|
+
dialect: "pg";
|
|
1247
|
+
}>;
|
|
953
1248
|
/** Type exports for insert/select operations. */
|
|
954
1249
|
export type ProjectRow = typeof projects.$inferSelect;
|
|
955
1250
|
export type ProjectInsert = typeof projects.$inferInsert;
|
|
@@ -963,3 +1258,7 @@ export type FileSnapshotRow = typeof fileSnapshots.$inferSelect;
|
|
|
963
1258
|
export type FileSnapshotInsert = typeof fileSnapshots.$inferInsert;
|
|
964
1259
|
export type ToolMetricRow = typeof toolMetrics.$inferSelect;
|
|
965
1260
|
export type ToolMetricInsert = typeof toolMetrics.$inferInsert;
|
|
1261
|
+
export type KanbanBoardRow = typeof kanbanBoards.$inferSelect;
|
|
1262
|
+
export type KanbanBoardInsert = typeof kanbanBoards.$inferInsert;
|
|
1263
|
+
export type KanbanCardRow = typeof kanbanCards.$inferSelect;
|
|
1264
|
+
export type KanbanCardInsert = typeof kanbanCards.$inferInsert;
|
package/dist/db/schema.js
CHANGED
|
@@ -110,3 +110,39 @@ export const toolMetrics = pgTable('tool_metrics', {
|
|
|
110
110
|
}, (table) => [
|
|
111
111
|
uniqueIndex('uq_tool_metrics_model_tool_mode').on(table.model, table.tool, table.mode),
|
|
112
112
|
]);
|
|
113
|
+
/**
|
|
114
|
+
* Kanban boards — one per project (unique projectId). Stores column/label
|
|
115
|
+
* configuration as JSON; cards live in kanban_cards.
|
|
116
|
+
*/
|
|
117
|
+
export const kanbanBoards = pgTable('kanban_boards', {
|
|
118
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
119
|
+
projectId: text('project_id')
|
|
120
|
+
.notNull()
|
|
121
|
+
.references(() => projects.id, { onDelete: 'cascade' }),
|
|
122
|
+
/** Column definitions: [{ id, name }] */
|
|
123
|
+
columns: jsonb('columns').notNull(),
|
|
124
|
+
/** Label definitions: [{ id, name, color }] */
|
|
125
|
+
labels: jsonb('labels').notNull().default([]),
|
|
126
|
+
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
127
|
+
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
128
|
+
}, (table) => [uniqueIndex('uq_kanban_boards_project').on(table.projectId)]);
|
|
129
|
+
/**
|
|
130
|
+
* Kanban cards — tasks on a board. position is a real for fractional indexing
|
|
131
|
+
* (insert between two cards without rewriting all positions). columnId
|
|
132
|
+
* references a column id in the parent board's columns JSON.
|
|
133
|
+
*/
|
|
134
|
+
export const kanbanCards = pgTable('kanban_cards', {
|
|
135
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
136
|
+
boardId: uuid('board_id')
|
|
137
|
+
.notNull()
|
|
138
|
+
.references(() => kanbanBoards.id, { onDelete: 'cascade' }),
|
|
139
|
+
title: text('title').notNull(),
|
|
140
|
+
description: text('description'),
|
|
141
|
+
columnId: text('column_id').notNull(),
|
|
142
|
+
priority: text('priority').notNull().default('medium'),
|
|
143
|
+
position: real('position').notNull().default(0),
|
|
144
|
+
/** Label ids referencing board.labels[].id */
|
|
145
|
+
labels: jsonb('labels').notNull().default([]),
|
|
146
|
+
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
147
|
+
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
148
|
+
}, (table) => [index('idx_kanban_cards_board').on(table.boardId, table.columnId, table.position)]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createKanbanStore } from './store.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createKanbanStore } from './store.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DB } from '../db/client.js';
|
|
2
|
+
import type { KanbanStore } from '../shared/types/kanban.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create a project-scoped KanbanStore backed by the given db handle.
|
|
5
|
+
* The board is created on first access (lazy) with the default 5 columns.
|
|
6
|
+
*/
|
|
7
|
+
declare function createKanbanStore(handle: DB, projectId: string): KanbanStore;
|
|
8
|
+
export { createKanbanStore };
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { and, asc, eq, max } from 'drizzle-orm';
|
|
2
|
+
import { kanbanBoards, kanbanCards } from '../db/schema.js';
|
|
3
|
+
import { DEFAULT_KANBAN_COLUMNS } from '../shared/types/kanban.js';
|
|
4
|
+
/** Default column when none is specified. */
|
|
5
|
+
const DEFAULT_COLUMN_ID = 'todo';
|
|
6
|
+
/** Position increment — large gap avoids frequent re-indexing on reorder. */
|
|
7
|
+
const POSITION_GAP = 1000;
|
|
8
|
+
// ── Row → API mappers ──────────────────────────────────────
|
|
9
|
+
function rowToBoard(row) {
|
|
10
|
+
return {
|
|
11
|
+
id: row.id,
|
|
12
|
+
projectId: row.projectId,
|
|
13
|
+
columns: row.columns,
|
|
14
|
+
labels: row.labels,
|
|
15
|
+
createdAt: row.createdAt.toISOString(),
|
|
16
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function rowToCard(row) {
|
|
20
|
+
return {
|
|
21
|
+
id: row.id,
|
|
22
|
+
boardId: row.boardId,
|
|
23
|
+
title: row.title,
|
|
24
|
+
description: row.description,
|
|
25
|
+
columnId: row.columnId,
|
|
26
|
+
priority: row.priority,
|
|
27
|
+
position: row.position,
|
|
28
|
+
labels: row.labels,
|
|
29
|
+
createdAt: row.createdAt.toISOString(),
|
|
30
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// ── Factory ────────────────────────────────────────────────
|
|
34
|
+
/**
|
|
35
|
+
* Create a project-scoped KanbanStore backed by the given db handle.
|
|
36
|
+
* The board is created on first access (lazy) with the default 5 columns.
|
|
37
|
+
*/
|
|
38
|
+
function createKanbanStore(handle, projectId) {
|
|
39
|
+
const db = handle.db;
|
|
40
|
+
/** Insert a default board if none exists (idempotent via unique projectId). */
|
|
41
|
+
async function getOrCreateBoardId() {
|
|
42
|
+
await db
|
|
43
|
+
.insert(kanbanBoards)
|
|
44
|
+
.values({
|
|
45
|
+
projectId,
|
|
46
|
+
columns: [...DEFAULT_KANBAN_COLUMNS],
|
|
47
|
+
labels: [],
|
|
48
|
+
})
|
|
49
|
+
.onConflictDoNothing({ target: kanbanBoards.projectId });
|
|
50
|
+
const [row] = await db
|
|
51
|
+
.select({ id: kanbanBoards.id })
|
|
52
|
+
.from(kanbanBoards)
|
|
53
|
+
.where(eq(kanbanBoards.projectId, projectId))
|
|
54
|
+
.limit(1);
|
|
55
|
+
// 行一定存在:上面 insert + onConflictDoNothing 保证了 projectId 对应的行已创建
|
|
56
|
+
return row.id;
|
|
57
|
+
}
|
|
58
|
+
/** Max position in a column (0 if empty). */
|
|
59
|
+
async function maxPos(boardId, columnId) {
|
|
60
|
+
const [row] = await db
|
|
61
|
+
.select({ m: max(kanbanCards.position) })
|
|
62
|
+
.from(kanbanCards)
|
|
63
|
+
.where(and(eq(kanbanCards.boardId, boardId), eq(kanbanCards.columnId, columnId)));
|
|
64
|
+
return row?.m ?? 0;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
async getBoard() {
|
|
68
|
+
const boardId = await getOrCreateBoardId();
|
|
69
|
+
const [boardRow] = await db
|
|
70
|
+
.select()
|
|
71
|
+
.from(kanbanBoards)
|
|
72
|
+
.where(eq(kanbanBoards.id, boardId))
|
|
73
|
+
.limit(1);
|
|
74
|
+
const board = boardRow;
|
|
75
|
+
const cards = await db
|
|
76
|
+
.select()
|
|
77
|
+
.from(kanbanCards)
|
|
78
|
+
.where(eq(kanbanCards.boardId, boardId))
|
|
79
|
+
.orderBy(asc(kanbanCards.columnId), asc(kanbanCards.position));
|
|
80
|
+
return { ...rowToBoard(board), cards: cards.map(rowToCard) };
|
|
81
|
+
},
|
|
82
|
+
async addCard(input) {
|
|
83
|
+
const boardId = await getOrCreateBoardId();
|
|
84
|
+
const columnId = input.columnId ?? DEFAULT_COLUMN_ID;
|
|
85
|
+
const position = (await maxPos(boardId, columnId)) + POSITION_GAP;
|
|
86
|
+
const [cardRow] = await db
|
|
87
|
+
.insert(kanbanCards)
|
|
88
|
+
.values({
|
|
89
|
+
boardId,
|
|
90
|
+
title: input.title,
|
|
91
|
+
description: input.description ?? null,
|
|
92
|
+
columnId,
|
|
93
|
+
priority: input.priority ?? 'medium',
|
|
94
|
+
position,
|
|
95
|
+
labels: input.labels ?? [],
|
|
96
|
+
})
|
|
97
|
+
.returning();
|
|
98
|
+
const row = cardRow;
|
|
99
|
+
return rowToCard(row);
|
|
100
|
+
},
|
|
101
|
+
async updateCard(id, patch) {
|
|
102
|
+
const [row] = await db
|
|
103
|
+
.update(kanbanCards)
|
|
104
|
+
.set({
|
|
105
|
+
...(patch.title !== undefined && { title: patch.title }),
|
|
106
|
+
...(patch.description !== undefined && { description: patch.description }),
|
|
107
|
+
...(patch.priority !== undefined && { priority: patch.priority }),
|
|
108
|
+
...(patch.labels !== undefined && { labels: patch.labels }),
|
|
109
|
+
updatedAt: new Date(),
|
|
110
|
+
})
|
|
111
|
+
.where(eq(kanbanCards.id, id))
|
|
112
|
+
.returning();
|
|
113
|
+
if (!row)
|
|
114
|
+
throw new Error(`Kanban card not found: ${id}`);
|
|
115
|
+
return rowToCard(row);
|
|
116
|
+
},
|
|
117
|
+
async moveCard(id, columnId, position) {
|
|
118
|
+
// If no explicit position, append to end of target column.
|
|
119
|
+
let newPos = position;
|
|
120
|
+
if (newPos === undefined) {
|
|
121
|
+
const [card] = await db
|
|
122
|
+
.select({ boardId: kanbanCards.boardId })
|
|
123
|
+
.from(kanbanCards)
|
|
124
|
+
.where(eq(kanbanCards.id, id))
|
|
125
|
+
.limit(1);
|
|
126
|
+
if (!card)
|
|
127
|
+
throw new Error(`Kanban card not found: ${id}`);
|
|
128
|
+
newPos = (await maxPos(card.boardId, columnId)) + POSITION_GAP;
|
|
129
|
+
}
|
|
130
|
+
const [row] = await db
|
|
131
|
+
.update(kanbanCards)
|
|
132
|
+
.set({ columnId, position: newPos, updatedAt: new Date() })
|
|
133
|
+
.where(eq(kanbanCards.id, id))
|
|
134
|
+
.returning();
|
|
135
|
+
if (!row)
|
|
136
|
+
throw new Error(`Kanban card not found: ${id}`);
|
|
137
|
+
return rowToCard(row);
|
|
138
|
+
},
|
|
139
|
+
async deleteCard(id) {
|
|
140
|
+
await db.delete(kanbanCards).where(eq(kanbanCards.id, id));
|
|
141
|
+
},
|
|
142
|
+
async updateBoard(patch) {
|
|
143
|
+
await getOrCreateBoardId();
|
|
144
|
+
const [boardRow] = await db
|
|
145
|
+
.update(kanbanBoards)
|
|
146
|
+
.set({
|
|
147
|
+
...(patch.columns !== undefined && { columns: patch.columns }),
|
|
148
|
+
...(patch.labels !== undefined && { labels: patch.labels }),
|
|
149
|
+
updatedAt: new Date(),
|
|
150
|
+
})
|
|
151
|
+
.where(eq(kanbanBoards.projectId, projectId))
|
|
152
|
+
.returning();
|
|
153
|
+
const row = boardRow;
|
|
154
|
+
return rowToBoard(row);
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export { createKanbanStore };
|
|
@@ -5,4 +5,79 @@ export type ResolvedProject = {
|
|
|
5
5
|
gitRemote: string | null;
|
|
6
6
|
gitBranch: string | null;
|
|
7
7
|
};
|
|
8
|
+
/** 文件 git 状态分类(用于文件树高亮)。按「最值得注意」优先级排列。 */
|
|
9
|
+
export type GitStatusCode = 'modified' | 'staged' | 'untracked' | 'conflict' | 'deleted' | 'ignored';
|
|
10
|
+
/** 目录聚合时的优先级(越大越优先展示)。ignored 最低:被忽略目录下若有真实变更仍显示变更态。 */
|
|
11
|
+
export declare const GIT_STATUS_PRIORITY: Record<GitStatusCode, number>;
|
|
12
|
+
export declare function getGitStatus(cwd: string): Record<string, GitStatusCode> | null;
|
|
13
|
+
/** 检查给定路径中哪些被 gitignore 规则覆盖。
|
|
14
|
+
*
|
|
15
|
+
* 用 git check-ignore 只查询传入的路径(不递归),返回被忽略路径的集合。
|
|
16
|
+
* 只检查当前展开目录的直接子项(通常 10-50 个),不递归进 node_modules 等。
|
|
17
|
+
* 非 git 仓库或无忽略文件时返回空集。
|
|
18
|
+
*
|
|
19
|
+
* 注意:git check-ignore 在「无路径被忽略」或「非 git 仓库」时退出码为 1,
|
|
20
|
+
* 这两种情况都返回空集。 */
|
|
21
|
+
export declare function checkIgnored(cwd: string, paths: string[]): Set<string>;
|
|
22
|
+
/** 追加条目到 .gitignore(去重,文件不存在则创建)。 */
|
|
23
|
+
export declare function appendToGitignore(cwd: string, patterns: string[]): void;
|
|
24
|
+
/**
|
|
25
|
+
* 取当前分支名(非 git 仓库返回 null)。
|
|
26
|
+
*/
|
|
27
|
+
export declare function getGitBranch(cwd: string): string | null;
|
|
28
|
+
/** 最后一次提交摘要(供分支名 hover tooltip)。 */
|
|
29
|
+
export interface GitLastCommit {
|
|
30
|
+
subject: string;
|
|
31
|
+
hash: string;
|
|
32
|
+
author: string;
|
|
33
|
+
date: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 取最后一次提交信息(subject/hash/author/相对时间)。
|
|
37
|
+
* 非 git 仓库或无提交(如全新仓库)返回 null。
|
|
38
|
+
*/
|
|
39
|
+
export declare function getGitLastCommit(cwd: string): GitLastCommit | null;
|
|
40
|
+
/**
|
|
41
|
+
* 取工作区变更摘要(供 LLM 生成 commit message)。
|
|
42
|
+
* 包含 staged + unstaged diff(相对 HEAD)和 untracked 文件名列表。
|
|
43
|
+
* 非 git 仓库或无变更返回 null。
|
|
44
|
+
*/
|
|
45
|
+
export declare function getGitDiffSummary(cwd: string): {
|
|
46
|
+
diff: string;
|
|
47
|
+
fileCount: number;
|
|
48
|
+
} | null;
|
|
49
|
+
/**
|
|
50
|
+
* 执行 git add -A + git commit。成功返回 commit 短 hash,失败返回 error 信息。
|
|
51
|
+
*/
|
|
52
|
+
export declare function performGitCommit(cwd: string, message: string): {
|
|
53
|
+
hash: string;
|
|
54
|
+
} | {
|
|
55
|
+
error: string;
|
|
56
|
+
};
|
|
57
|
+
/** 分支信息。 */
|
|
58
|
+
export interface GitBranchInfo {
|
|
59
|
+
name: string;
|
|
60
|
+
current: boolean;
|
|
61
|
+
lastSubject: string | null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 列出本地分支及当前分支标记(非 git 仓库返回 null)。
|
|
65
|
+
*/
|
|
66
|
+
export declare function listGitBranches(cwd: string): GitBranchInfo[] | null;
|
|
67
|
+
/**
|
|
68
|
+
* 切换到指定分支(git checkout)。成功返回分支名,失败返回 error。
|
|
69
|
+
*/
|
|
70
|
+
export declare function checkoutGitBranch(cwd: string, branch: string): {
|
|
71
|
+
branch: string;
|
|
72
|
+
} | {
|
|
73
|
+
error: string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* 创建并切换到新分支。成功返回分支名,失败返回 error。
|
|
77
|
+
*/
|
|
78
|
+
export declare function createGitBranch(cwd: string, name: string): {
|
|
79
|
+
branch: string;
|
|
80
|
+
} | {
|
|
81
|
+
error: string;
|
|
82
|
+
};
|
|
8
83
|
export declare function resolveProject(directory: string): ResolvedProject;
|