@allpepper/task-orchestrator-tui 1.2.1 → 2.0.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/package.json +2 -2
- package/src/tui/components/status-actions.tsx +59 -21
- package/src/tui/screens/dashboard.tsx +9 -69
- package/src/tui/screens/feature-detail.tsx +77 -49
- package/src/tui/screens/project-detail.tsx +8 -65
- package/src/tui/screens/project-view.tsx +76 -80
- package/src/tui/screens/task-detail.tsx +46 -19
- package/src/ui/adapters/direct.ts +323 -94
- package/src/ui/adapters/types.ts +75 -72
- package/src/ui/hooks/use-data.ts +106 -193
- package/src/ui/hooks/use-feature-kanban.ts +45 -30
- package/src/ui/hooks/use-kanban.ts +35 -27
- package/src/ui/index.ts +1 -0
- package/src/ui/lib/colors.ts +27 -24
- package/src/ui/lib/markdown.tsx +5 -5
- package/src/ui/lib/types.ts +0 -1
- package/src/ui/themes/dark.ts +10 -28
- package/src/ui/themes/light.ts +16 -34
- package/src/ui/themes/types.ts +14 -26
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements DataAdapter by directly importing and calling repository functions.
|
|
5
5
|
* This is used when the UI is running in the same process as the domain layer.
|
|
6
|
+
*
|
|
7
|
+
* v2 changes:
|
|
8
|
+
* - Projects are stateless (no status)
|
|
9
|
+
* - Status transitions via advance/revert/terminate
|
|
10
|
+
* - Dependencies are field-based (blockedBy/relatedTo on entities)
|
|
11
|
+
* - No separate dependencies repo
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
14
|
import type {
|
|
@@ -11,17 +17,16 @@ import type {
|
|
|
11
17
|
SearchParams,
|
|
12
18
|
FeatureSearchParams,
|
|
13
19
|
TaskSearchParams,
|
|
20
|
+
WorkflowState,
|
|
21
|
+
TransitionResult,
|
|
14
22
|
} from './types';
|
|
15
23
|
import type {
|
|
16
24
|
Task,
|
|
17
25
|
Feature,
|
|
18
26
|
Project,
|
|
19
|
-
TaskStatus,
|
|
20
|
-
ProjectStatus,
|
|
21
|
-
FeatureStatus,
|
|
22
|
-
Priority,
|
|
23
27
|
Section,
|
|
24
28
|
EntityType,
|
|
29
|
+
Priority,
|
|
25
30
|
} from '@allpepper/task-orchestrator';
|
|
26
31
|
import type {
|
|
27
32
|
SearchResults,
|
|
@@ -30,16 +35,25 @@ import type {
|
|
|
30
35
|
FeatureOverview,
|
|
31
36
|
} from '../lib/types';
|
|
32
37
|
|
|
33
|
-
// Import repos individually since barrel export is incomplete
|
|
34
38
|
import * as projects from '@allpepper/task-orchestrator/src/repos/projects';
|
|
35
39
|
import * as features from '@allpepper/task-orchestrator/src/repos/features';
|
|
36
40
|
import * as tasks from '@allpepper/task-orchestrator/src/repos/tasks';
|
|
37
41
|
import * as sections from '@allpepper/task-orchestrator/src/repos/sections';
|
|
38
|
-
import * as dependencies from '@allpepper/task-orchestrator/src/repos/dependencies';
|
|
39
42
|
import {
|
|
40
43
|
getAllowedTransitions,
|
|
44
|
+
isValidTransition,
|
|
41
45
|
type ContainerType,
|
|
42
46
|
} from '@allpepper/task-orchestrator/src/services/status-validator';
|
|
47
|
+
import {
|
|
48
|
+
getWorkflowState as getWorkflowStateFn,
|
|
49
|
+
} from '@allpepper/task-orchestrator/src/services/workflow';
|
|
50
|
+
import {
|
|
51
|
+
getNextState,
|
|
52
|
+
getPrevState,
|
|
53
|
+
getPipelinePosition,
|
|
54
|
+
EXIT_STATE,
|
|
55
|
+
} from '@allpepper/task-orchestrator/src/config';
|
|
56
|
+
import { queryAll, queryOne, execute, now } from '@allpepper/task-orchestrator/src/repos/base';
|
|
43
57
|
|
|
44
58
|
/**
|
|
45
59
|
* DirectAdapter implementation
|
|
@@ -48,14 +62,13 @@ import {
|
|
|
48
62
|
*/
|
|
49
63
|
export class DirectAdapter implements DataAdapter {
|
|
50
64
|
// ============================================================================
|
|
51
|
-
// Projects
|
|
65
|
+
// Projects (stateless in v2)
|
|
52
66
|
// ============================================================================
|
|
53
67
|
|
|
54
68
|
async getProjects(params?: SearchParams): Promise<Result<Project[]>> {
|
|
55
69
|
return Promise.resolve(
|
|
56
70
|
projects.searchProjects({
|
|
57
71
|
query: params?.query,
|
|
58
|
-
status: params?.status,
|
|
59
72
|
tags: params?.tags?.join(','),
|
|
60
73
|
limit: params?.limit,
|
|
61
74
|
offset: params?.offset,
|
|
@@ -74,13 +87,11 @@ export class DirectAdapter implements DataAdapter {
|
|
|
74
87
|
return Promise.resolve(result as Result<ProjectOverview>);
|
|
75
88
|
}
|
|
76
89
|
|
|
77
|
-
// Transform repo format to UI format
|
|
78
90
|
const overview: ProjectOverview = {
|
|
79
91
|
project: {
|
|
80
92
|
id: result.data.project.id,
|
|
81
93
|
name: result.data.project.name,
|
|
82
94
|
summary: result.data.project.summary,
|
|
83
|
-
status: result.data.project.status,
|
|
84
95
|
},
|
|
85
96
|
taskCounts: result.data.taskCounts,
|
|
86
97
|
};
|
|
@@ -92,7 +103,6 @@ export class DirectAdapter implements DataAdapter {
|
|
|
92
103
|
name: string;
|
|
93
104
|
summary: string;
|
|
94
105
|
description?: string;
|
|
95
|
-
status?: ProjectStatus;
|
|
96
106
|
tags?: string[];
|
|
97
107
|
}): Promise<Result<Project>> {
|
|
98
108
|
return Promise.resolve(projects.createProject(params));
|
|
@@ -104,7 +114,6 @@ export class DirectAdapter implements DataAdapter {
|
|
|
104
114
|
name?: string;
|
|
105
115
|
summary?: string;
|
|
106
116
|
description?: string;
|
|
107
|
-
status?: ProjectStatus;
|
|
108
117
|
tags?: string[];
|
|
109
118
|
version: number;
|
|
110
119
|
}
|
|
@@ -145,7 +154,6 @@ export class DirectAdapter implements DataAdapter {
|
|
|
145
154
|
return Promise.resolve(result as Result<FeatureOverview>);
|
|
146
155
|
}
|
|
147
156
|
|
|
148
|
-
// Transform repo format to UI format
|
|
149
157
|
const overview: FeatureOverview = {
|
|
150
158
|
feature: {
|
|
151
159
|
id: result.data.feature.id,
|
|
@@ -165,7 +173,6 @@ export class DirectAdapter implements DataAdapter {
|
|
|
165
173
|
name: string;
|
|
166
174
|
summary: string;
|
|
167
175
|
description?: string;
|
|
168
|
-
status?: FeatureStatus;
|
|
169
176
|
priority: Priority;
|
|
170
177
|
tags?: string[];
|
|
171
178
|
}): Promise<Result<Feature>> {
|
|
@@ -178,7 +185,6 @@ export class DirectAdapter implements DataAdapter {
|
|
|
178
185
|
name?: string;
|
|
179
186
|
summary?: string;
|
|
180
187
|
description?: string;
|
|
181
|
-
status?: FeatureStatus;
|
|
182
188
|
priority?: Priority;
|
|
183
189
|
projectId?: string;
|
|
184
190
|
tags?: string[];
|
|
@@ -220,7 +226,6 @@ export class DirectAdapter implements DataAdapter {
|
|
|
220
226
|
title: string;
|
|
221
227
|
summary: string;
|
|
222
228
|
description?: string;
|
|
223
|
-
status?: TaskStatus;
|
|
224
229
|
priority: Priority;
|
|
225
230
|
complexity: number;
|
|
226
231
|
tags?: string[];
|
|
@@ -234,7 +239,6 @@ export class DirectAdapter implements DataAdapter {
|
|
|
234
239
|
title?: string;
|
|
235
240
|
summary?: string;
|
|
236
241
|
description?: string;
|
|
237
|
-
status?: TaskStatus;
|
|
238
242
|
priority?: Priority;
|
|
239
243
|
complexity?: number;
|
|
240
244
|
projectId?: string;
|
|
@@ -251,28 +255,220 @@ export class DirectAdapter implements DataAdapter {
|
|
|
251
255
|
return Promise.resolve(tasks.deleteTask(id));
|
|
252
256
|
}
|
|
253
257
|
|
|
254
|
-
|
|
258
|
+
// ============================================================================
|
|
259
|
+
// Pipeline Operations (v2)
|
|
260
|
+
// ============================================================================
|
|
261
|
+
|
|
262
|
+
async advance(
|
|
263
|
+
containerType: 'task' | 'feature',
|
|
255
264
|
id: string,
|
|
256
|
-
status: TaskStatus,
|
|
257
265
|
version: number
|
|
258
|
-
): Promise<Result<
|
|
259
|
-
|
|
266
|
+
): Promise<Result<TransitionResult>> {
|
|
267
|
+
try {
|
|
268
|
+
const entity = containerType === 'task'
|
|
269
|
+
? tasks.getTask(id)
|
|
270
|
+
: features.getFeature(id);
|
|
271
|
+
|
|
272
|
+
if (!entity.success) {
|
|
273
|
+
return { success: false, error: entity.error, code: entity.code };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (entity.data.version !== version) {
|
|
277
|
+
return { success: false, error: `Version conflict: expected ${version}, found ${entity.data.version}`, code: 'CONFLICT' };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const currentStatus = entity.data.status;
|
|
281
|
+
const nextState = getNextState(containerType, currentStatus);
|
|
282
|
+
|
|
283
|
+
if (!nextState) {
|
|
284
|
+
return { success: false, error: `Cannot advance: no next state from ${currentStatus}`, code: 'INVALID_OPERATION' };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Check if blocked
|
|
288
|
+
if ('blockedBy' in entity.data && entity.data.blockedBy.length > 0) {
|
|
289
|
+
return { success: false, error: `Cannot advance: entity is blocked`, code: 'BLOCKED' };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const table = containerType === 'task' ? 'tasks' : 'features';
|
|
293
|
+
const timestamp = now();
|
|
294
|
+
execute(
|
|
295
|
+
`UPDATE ${table} SET status = ?, version = version + 1, modified_at = ? WHERE id = ?`,
|
|
296
|
+
[nextState, timestamp, id]
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// Re-fetch entity
|
|
300
|
+
const updated = containerType === 'task'
|
|
301
|
+
? tasks.getTask(id)
|
|
302
|
+
: features.getFeature(id);
|
|
303
|
+
|
|
304
|
+
if (!updated.success) {
|
|
305
|
+
return { success: false, error: updated.error, code: updated.code };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
success: true,
|
|
310
|
+
data: {
|
|
311
|
+
entity: updated.data,
|
|
312
|
+
oldStatus: currentStatus,
|
|
313
|
+
newStatus: nextState,
|
|
314
|
+
pipelinePosition: getPipelinePosition(containerType, nextState),
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
} catch (error) {
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
321
|
+
};
|
|
322
|
+
}
|
|
260
323
|
}
|
|
261
324
|
|
|
262
|
-
async
|
|
325
|
+
async revert(
|
|
326
|
+
containerType: 'task' | 'feature',
|
|
263
327
|
id: string,
|
|
264
|
-
status: ProjectStatus,
|
|
265
328
|
version: number
|
|
266
|
-
): Promise<Result<
|
|
267
|
-
|
|
329
|
+
): Promise<Result<TransitionResult>> {
|
|
330
|
+
try {
|
|
331
|
+
const entity = containerType === 'task'
|
|
332
|
+
? tasks.getTask(id)
|
|
333
|
+
: features.getFeature(id);
|
|
334
|
+
|
|
335
|
+
if (!entity.success) {
|
|
336
|
+
return { success: false, error: entity.error, code: entity.code };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (entity.data.version !== version) {
|
|
340
|
+
return { success: false, error: `Version conflict: expected ${version}, found ${entity.data.version}`, code: 'CONFLICT' };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const currentStatus = entity.data.status;
|
|
344
|
+
const prevState = getPrevState(containerType, currentStatus);
|
|
345
|
+
|
|
346
|
+
if (!prevState) {
|
|
347
|
+
return { success: false, error: `Cannot revert: no previous state from ${currentStatus}`, code: 'INVALID_OPERATION' };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const table = containerType === 'task' ? 'tasks' : 'features';
|
|
351
|
+
const timestamp = now();
|
|
352
|
+
execute(
|
|
353
|
+
`UPDATE ${table} SET status = ?, version = version + 1, modified_at = ? WHERE id = ?`,
|
|
354
|
+
[prevState, timestamp, id]
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const updated = containerType === 'task'
|
|
358
|
+
? tasks.getTask(id)
|
|
359
|
+
: features.getFeature(id);
|
|
360
|
+
|
|
361
|
+
if (!updated.success) {
|
|
362
|
+
return { success: false, error: updated.error, code: updated.code };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
success: true,
|
|
367
|
+
data: {
|
|
368
|
+
entity: updated.data,
|
|
369
|
+
oldStatus: currentStatus,
|
|
370
|
+
newStatus: prevState,
|
|
371
|
+
pipelinePosition: getPipelinePosition(containerType, prevState),
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
} catch (error) {
|
|
375
|
+
return {
|
|
376
|
+
success: false,
|
|
377
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
378
|
+
};
|
|
379
|
+
}
|
|
268
380
|
}
|
|
269
381
|
|
|
270
|
-
async
|
|
382
|
+
async terminate(
|
|
383
|
+
containerType: 'task' | 'feature',
|
|
271
384
|
id: string,
|
|
272
|
-
status: FeatureStatus,
|
|
273
385
|
version: number
|
|
274
|
-
): Promise<Result<
|
|
275
|
-
|
|
386
|
+
): Promise<Result<TransitionResult>> {
|
|
387
|
+
try {
|
|
388
|
+
const entity = containerType === 'task'
|
|
389
|
+
? tasks.getTask(id)
|
|
390
|
+
: features.getFeature(id);
|
|
391
|
+
|
|
392
|
+
if (!entity.success) {
|
|
393
|
+
return { success: false, error: entity.error, code: entity.code };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (entity.data.version !== version) {
|
|
397
|
+
return { success: false, error: `Version conflict: expected ${version}, found ${entity.data.version}`, code: 'CONFLICT' };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const currentStatus = entity.data.status;
|
|
401
|
+
const table = containerType === 'task' ? 'tasks' : 'features';
|
|
402
|
+
const timestamp = now();
|
|
403
|
+
|
|
404
|
+
execute(
|
|
405
|
+
`UPDATE ${table} SET status = ?, version = version + 1, modified_at = ? WHERE id = ?`,
|
|
406
|
+
[EXIT_STATE, timestamp, id]
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const updated = containerType === 'task'
|
|
410
|
+
? tasks.getTask(id)
|
|
411
|
+
: features.getFeature(id);
|
|
412
|
+
|
|
413
|
+
if (!updated.success) {
|
|
414
|
+
return { success: false, error: updated.error, code: updated.code };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
success: true,
|
|
419
|
+
data: {
|
|
420
|
+
entity: updated.data,
|
|
421
|
+
oldStatus: currentStatus,
|
|
422
|
+
newStatus: EXIT_STATE,
|
|
423
|
+
pipelinePosition: null,
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
} catch (error) {
|
|
427
|
+
return {
|
|
428
|
+
success: false,
|
|
429
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async getWorkflowState(
|
|
435
|
+
containerType: 'task' | 'feature',
|
|
436
|
+
id: string
|
|
437
|
+
): Promise<Result<WorkflowState>> {
|
|
438
|
+
try {
|
|
439
|
+
const result = getWorkflowStateFn(containerType, id);
|
|
440
|
+
if (!result.success) {
|
|
441
|
+
return { success: false, error: result.error, code: result.code };
|
|
442
|
+
}
|
|
443
|
+
return { success: true, data: result.data as WorkflowState };
|
|
444
|
+
} catch (error) {
|
|
445
|
+
return {
|
|
446
|
+
success: false,
|
|
447
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ============================================================================
|
|
453
|
+
// Workflow
|
|
454
|
+
// ============================================================================
|
|
455
|
+
|
|
456
|
+
async getAllowedTransitions(
|
|
457
|
+
containerType: string,
|
|
458
|
+
status: string
|
|
459
|
+
): Promise<Result<string[]>> {
|
|
460
|
+
try {
|
|
461
|
+
const transitions = getAllowedTransitions(
|
|
462
|
+
containerType as ContainerType,
|
|
463
|
+
status
|
|
464
|
+
);
|
|
465
|
+
return Promise.resolve({ success: true, data: transitions });
|
|
466
|
+
} catch (error) {
|
|
467
|
+
return Promise.resolve({
|
|
468
|
+
success: false,
|
|
469
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
470
|
+
});
|
|
471
|
+
}
|
|
276
472
|
}
|
|
277
473
|
|
|
278
474
|
// ============================================================================
|
|
@@ -287,94 +483,129 @@ export class DirectAdapter implements DataAdapter {
|
|
|
287
483
|
}
|
|
288
484
|
|
|
289
485
|
// ============================================================================
|
|
290
|
-
// Dependencies
|
|
486
|
+
// Dependencies (field-based in v2)
|
|
291
487
|
// ============================================================================
|
|
292
488
|
|
|
293
489
|
async getDependencies(taskId: string): Promise<Result<DependencyInfo>> {
|
|
294
|
-
|
|
490
|
+
try {
|
|
491
|
+
const taskResult = tasks.getTask(taskId);
|
|
492
|
+
if (!taskResult.success) {
|
|
493
|
+
return { success: false, error: taskResult.error, code: taskResult.code };
|
|
494
|
+
}
|
|
295
495
|
|
|
296
|
-
|
|
297
|
-
return Promise.resolve(result as Result<DependencyInfo>);
|
|
298
|
-
}
|
|
496
|
+
const task = taskResult.data;
|
|
299
497
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
.filter((d) => d.toTaskId === taskId && d.type === 'BLOCKS')
|
|
308
|
-
.map((d) => d.fromTaskId);
|
|
309
|
-
const blocksTaskIds = deps
|
|
310
|
-
.filter((d) => d.fromTaskId === taskId && d.type === 'BLOCKS')
|
|
311
|
-
.map((d) => d.toTaskId);
|
|
312
|
-
|
|
313
|
-
// Fetch the actual task objects
|
|
314
|
-
const blockedByTasks: Task[] = [];
|
|
315
|
-
for (const id of blockedByTaskIds) {
|
|
316
|
-
const taskResult = tasks.getTask(id);
|
|
317
|
-
if (taskResult.success) {
|
|
318
|
-
blockedByTasks.push(taskResult.data);
|
|
498
|
+
// blockedBy: fetch each task that blocks this one
|
|
499
|
+
const blockedByTasks: Task[] = [];
|
|
500
|
+
for (const blockerId of task.blockedBy) {
|
|
501
|
+
const blockerResult = tasks.getTask(blockerId);
|
|
502
|
+
if (blockerResult.success) {
|
|
503
|
+
blockedByTasks.push(blockerResult.data);
|
|
504
|
+
}
|
|
319
505
|
}
|
|
320
|
-
}
|
|
321
506
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
507
|
+
// blocks: find all tasks that have this task in their blockedBy
|
|
508
|
+
const blocksTasks: Task[] = [];
|
|
509
|
+
const blockedRows = queryAll<{ id: string; blocked_by: string }>(
|
|
510
|
+
`SELECT id, blocked_by FROM tasks WHERE EXISTS (SELECT 1 FROM json_each(tasks.blocked_by) WHERE value = ?)`,
|
|
511
|
+
[taskId]
|
|
512
|
+
);
|
|
513
|
+
for (const row of blockedRows) {
|
|
514
|
+
const blockedTaskResult = tasks.getTask(row.id);
|
|
515
|
+
if (blockedTaskResult.success) {
|
|
516
|
+
blocksTasks.push(blockedTaskResult.data);
|
|
517
|
+
}
|
|
327
518
|
}
|
|
328
|
-
}
|
|
329
519
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
520
|
+
const dependencyInfo: DependencyInfo = {
|
|
521
|
+
blockedBy: blockedByTasks,
|
|
522
|
+
blocks: blocksTasks,
|
|
523
|
+
};
|
|
334
524
|
|
|
335
|
-
|
|
525
|
+
return { success: true, data: dependencyInfo };
|
|
526
|
+
} catch (error) {
|
|
527
|
+
return {
|
|
528
|
+
success: false,
|
|
529
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
530
|
+
};
|
|
531
|
+
}
|
|
336
532
|
}
|
|
337
533
|
|
|
338
534
|
async getBlockedTasks(params?: {
|
|
339
535
|
projectId?: string;
|
|
340
536
|
}): Promise<Result<Task[]>> {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
537
|
+
try {
|
|
538
|
+
const conditions: string[] = ["blocked_by != '[]'"];
|
|
539
|
+
const values: any[] = [];
|
|
540
|
+
|
|
541
|
+
if (params?.projectId) {
|
|
542
|
+
conditions.push('project_id = ?');
|
|
543
|
+
values.push(params.projectId);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const sql = `SELECT * FROM tasks
|
|
547
|
+
WHERE ${conditions.join(' AND ')}
|
|
548
|
+
ORDER BY
|
|
549
|
+
CASE priority WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 WHEN 'LOW' THEN 3 END ASC,
|
|
550
|
+
created_at ASC`;
|
|
551
|
+
|
|
552
|
+
const rows = queryAll<any>(sql, values);
|
|
553
|
+
|
|
554
|
+
// Convert rows to Task objects via getTask for proper mapping
|
|
555
|
+
const blockedTasks: Task[] = [];
|
|
556
|
+
for (const row of rows) {
|
|
557
|
+
const taskResult = tasks.getTask(row.id);
|
|
558
|
+
if (taskResult.success) {
|
|
559
|
+
blockedTasks.push(taskResult.data);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return { success: true, data: blockedTasks };
|
|
564
|
+
} catch (error) {
|
|
565
|
+
return {
|
|
566
|
+
success: false,
|
|
567
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
568
|
+
};
|
|
569
|
+
}
|
|
346
570
|
}
|
|
347
571
|
|
|
348
572
|
async getNextTask(params?: {
|
|
349
573
|
projectId?: string;
|
|
350
574
|
}): Promise<Result<Task | null>> {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
})
|
|
355
|
-
);
|
|
356
|
-
}
|
|
575
|
+
try {
|
|
576
|
+
const conditions: string[] = ["status = 'NEW'", "blocked_by = '[]'"];
|
|
577
|
+
const values: any[] = [];
|
|
357
578
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
579
|
+
if (params?.projectId) {
|
|
580
|
+
conditions.push('project_id = ?');
|
|
581
|
+
values.push(params.projectId);
|
|
582
|
+
}
|
|
361
583
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
584
|
+
const sql = `SELECT * FROM tasks
|
|
585
|
+
WHERE ${conditions.join(' AND ')}
|
|
586
|
+
ORDER BY
|
|
587
|
+
CASE priority WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 WHEN 'LOW' THEN 3 END ASC,
|
|
588
|
+
complexity ASC,
|
|
589
|
+
created_at ASC
|
|
590
|
+
LIMIT 1`;
|
|
591
|
+
|
|
592
|
+
const row = queryOne<any>(sql, values);
|
|
593
|
+
|
|
594
|
+
if (!row) {
|
|
595
|
+
return { success: true, data: null };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const taskResult = tasks.getTask(row.id);
|
|
599
|
+
if (!taskResult.success) {
|
|
600
|
+
return { success: true, data: null };
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return { success: true, data: taskResult.data };
|
|
373
604
|
} catch (error) {
|
|
374
|
-
return
|
|
605
|
+
return {
|
|
375
606
|
success: false,
|
|
376
607
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
377
|
-
}
|
|
608
|
+
};
|
|
378
609
|
}
|
|
379
610
|
}
|
|
380
611
|
|
|
@@ -384,12 +615,10 @@ export class DirectAdapter implements DataAdapter {
|
|
|
384
615
|
|
|
385
616
|
async search(query: string): Promise<Result<SearchResults>> {
|
|
386
617
|
try {
|
|
387
|
-
// Search across all entity types
|
|
388
618
|
const projectsResult = projects.searchProjects({ query, limit: 10 });
|
|
389
619
|
const featuresResult = features.searchFeatures({ query, limit: 10 });
|
|
390
620
|
const tasksResult = tasks.searchTasks({ query, limit: 10 });
|
|
391
621
|
|
|
392
|
-
// Build search results
|
|
393
622
|
const results: SearchResults = {
|
|
394
623
|
projects: projectsResult.success
|
|
395
624
|
? projectsResult.data.map((p) => ({
|