@allpepper/task-orchestrator-tui 1.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/README.md +78 -0
- package/package.json +54 -0
- package/src/tui/app.tsx +308 -0
- package/src/tui/components/column-filter-bar.tsx +52 -0
- package/src/tui/components/confirm-dialog.tsx +45 -0
- package/src/tui/components/dependency-list.tsx +115 -0
- package/src/tui/components/empty-state.tsx +28 -0
- package/src/tui/components/entity-table.tsx +120 -0
- package/src/tui/components/error-message.tsx +41 -0
- package/src/tui/components/feature-kanban-card.tsx +216 -0
- package/src/tui/components/footer.tsx +34 -0
- package/src/tui/components/form-dialog.tsx +338 -0
- package/src/tui/components/header.tsx +54 -0
- package/src/tui/components/index.ts +16 -0
- package/src/tui/components/kanban-board.tsx +335 -0
- package/src/tui/components/kanban-card.tsx +70 -0
- package/src/tui/components/kanban-column.tsx +173 -0
- package/src/tui/components/priority-badge.tsx +16 -0
- package/src/tui/components/section-list.tsx +96 -0
- package/src/tui/components/status-actions.tsx +87 -0
- package/src/tui/components/status-badge.tsx +22 -0
- package/src/tui/components/tree-view.tsx +295 -0
- package/src/tui/components/view-mode-chips.tsx +23 -0
- package/src/tui/index.tsx +33 -0
- package/src/tui/screens/dashboard.tsx +248 -0
- package/src/tui/screens/feature-detail.tsx +312 -0
- package/src/tui/screens/index.ts +6 -0
- package/src/tui/screens/kanban-view.tsx +251 -0
- package/src/tui/screens/project-detail.tsx +305 -0
- package/src/tui/screens/project-view.tsx +498 -0
- package/src/tui/screens/search.tsx +257 -0
- package/src/tui/screens/task-detail.tsx +294 -0
- package/src/ui/adapters/direct.ts +429 -0
- package/src/ui/adapters/index.ts +14 -0
- package/src/ui/adapters/types.ts +269 -0
- package/src/ui/context/adapter-context.tsx +31 -0
- package/src/ui/context/theme-context.tsx +43 -0
- package/src/ui/hooks/index.ts +20 -0
- package/src/ui/hooks/use-data.ts +919 -0
- package/src/ui/hooks/use-debounce.ts +37 -0
- package/src/ui/hooks/use-feature-kanban.ts +151 -0
- package/src/ui/hooks/use-kanban.ts +96 -0
- package/src/ui/hooks/use-navigation.tsx +94 -0
- package/src/ui/index.ts +73 -0
- package/src/ui/lib/colors.ts +79 -0
- package/src/ui/lib/format.ts +114 -0
- package/src/ui/lib/types.ts +157 -0
- package/src/ui/themes/dark.ts +63 -0
- package/src/ui/themes/light.ts +63 -0
- package/src/ui/themes/types.ts +71 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DirectAdapter - In-memory data adapter
|
|
3
|
+
*
|
|
4
|
+
* Implements DataAdapter by directly importing and calling repository functions.
|
|
5
|
+
* This is used when the UI is running in the same process as the domain layer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
DataAdapter,
|
|
10
|
+
Result,
|
|
11
|
+
SearchParams,
|
|
12
|
+
FeatureSearchParams,
|
|
13
|
+
TaskSearchParams,
|
|
14
|
+
} from './types';
|
|
15
|
+
import type {
|
|
16
|
+
Task,
|
|
17
|
+
Feature,
|
|
18
|
+
Project,
|
|
19
|
+
TaskStatus,
|
|
20
|
+
ProjectStatus,
|
|
21
|
+
FeatureStatus,
|
|
22
|
+
Priority,
|
|
23
|
+
Section,
|
|
24
|
+
EntityType,
|
|
25
|
+
} from 'task-orchestrator-bun/src/domain/types';
|
|
26
|
+
import type {
|
|
27
|
+
SearchResults,
|
|
28
|
+
DependencyInfo,
|
|
29
|
+
ProjectOverview,
|
|
30
|
+
FeatureOverview,
|
|
31
|
+
} from '../lib/types';
|
|
32
|
+
|
|
33
|
+
// Import repos individually since barrel export is incomplete
|
|
34
|
+
import * as projects from 'task-orchestrator-bun/src/repos/projects';
|
|
35
|
+
import * as features from 'task-orchestrator-bun/src/repos/features';
|
|
36
|
+
import * as tasks from 'task-orchestrator-bun/src/repos/tasks';
|
|
37
|
+
import * as sections from 'task-orchestrator-bun/src/repos/sections';
|
|
38
|
+
import * as dependencies from 'task-orchestrator-bun/src/repos/dependencies';
|
|
39
|
+
import {
|
|
40
|
+
getAllowedTransitions,
|
|
41
|
+
type ContainerType,
|
|
42
|
+
} from 'task-orchestrator-bun/src/services/status-validator';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* DirectAdapter implementation
|
|
46
|
+
*
|
|
47
|
+
* Wraps synchronous repo calls in Promise.resolve() since repos return sync Results.
|
|
48
|
+
*/
|
|
49
|
+
export class DirectAdapter implements DataAdapter {
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// Projects
|
|
52
|
+
// ============================================================================
|
|
53
|
+
|
|
54
|
+
async getProjects(params?: SearchParams): Promise<Result<Project[]>> {
|
|
55
|
+
return Promise.resolve(
|
|
56
|
+
projects.searchProjects({
|
|
57
|
+
query: params?.query,
|
|
58
|
+
status: params?.status,
|
|
59
|
+
tags: params?.tags?.join(','),
|
|
60
|
+
limit: params?.limit,
|
|
61
|
+
offset: params?.offset,
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getProject(id: string): Promise<Result<Project>> {
|
|
67
|
+
return Promise.resolve(projects.getProject(id));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getProjectOverview(id: string): Promise<Result<ProjectOverview>> {
|
|
71
|
+
const result = projects.getProjectOverview(id);
|
|
72
|
+
|
|
73
|
+
if (!result.success) {
|
|
74
|
+
return Promise.resolve(result as Result<ProjectOverview>);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Transform repo format to UI format
|
|
78
|
+
const overview: ProjectOverview = {
|
|
79
|
+
project: {
|
|
80
|
+
id: result.data.project.id,
|
|
81
|
+
name: result.data.project.name,
|
|
82
|
+
summary: result.data.project.summary,
|
|
83
|
+
status: result.data.project.status,
|
|
84
|
+
},
|
|
85
|
+
taskCounts: result.data.taskCounts,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return Promise.resolve({ success: true, data: overview });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async createProject(params: {
|
|
92
|
+
name: string;
|
|
93
|
+
summary: string;
|
|
94
|
+
description?: string;
|
|
95
|
+
status?: ProjectStatus;
|
|
96
|
+
tags?: string[];
|
|
97
|
+
}): Promise<Result<Project>> {
|
|
98
|
+
return Promise.resolve(projects.createProject(params));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async updateProject(
|
|
102
|
+
id: string,
|
|
103
|
+
params: {
|
|
104
|
+
name?: string;
|
|
105
|
+
summary?: string;
|
|
106
|
+
description?: string;
|
|
107
|
+
status?: ProjectStatus;
|
|
108
|
+
tags?: string[];
|
|
109
|
+
version: number;
|
|
110
|
+
}
|
|
111
|
+
): Promise<Result<Project>> {
|
|
112
|
+
return Promise.resolve(projects.updateProject(id, params));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async deleteProject(id: string): Promise<Result<boolean>> {
|
|
116
|
+
return Promise.resolve(projects.deleteProject(id));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// Features
|
|
121
|
+
// ============================================================================
|
|
122
|
+
|
|
123
|
+
async getFeatures(params?: FeatureSearchParams): Promise<Result<Feature[]>> {
|
|
124
|
+
return Promise.resolve(
|
|
125
|
+
features.searchFeatures({
|
|
126
|
+
query: params?.query,
|
|
127
|
+
status: params?.status,
|
|
128
|
+
priority: params?.priority,
|
|
129
|
+
projectId: params?.projectId,
|
|
130
|
+
tags: params?.tags?.join(','),
|
|
131
|
+
limit: params?.limit,
|
|
132
|
+
offset: params?.offset,
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async getFeature(id: string): Promise<Result<Feature>> {
|
|
138
|
+
return Promise.resolve(features.getFeature(id));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async getFeatureOverview(id: string): Promise<Result<FeatureOverview>> {
|
|
142
|
+
const result = features.getFeatureOverview(id);
|
|
143
|
+
|
|
144
|
+
if (!result.success) {
|
|
145
|
+
return Promise.resolve(result as Result<FeatureOverview>);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Transform repo format to UI format
|
|
149
|
+
const overview: FeatureOverview = {
|
|
150
|
+
feature: {
|
|
151
|
+
id: result.data.feature.id,
|
|
152
|
+
name: result.data.feature.name,
|
|
153
|
+
summary: result.data.feature.summary,
|
|
154
|
+
status: result.data.feature.status,
|
|
155
|
+
priority: result.data.feature.priority,
|
|
156
|
+
},
|
|
157
|
+
taskCounts: result.data.taskCounts,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return Promise.resolve({ success: true, data: overview });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async createFeature(params: {
|
|
164
|
+
projectId?: string;
|
|
165
|
+
name: string;
|
|
166
|
+
summary: string;
|
|
167
|
+
description?: string;
|
|
168
|
+
status?: FeatureStatus;
|
|
169
|
+
priority: Priority;
|
|
170
|
+
tags?: string[];
|
|
171
|
+
}): Promise<Result<Feature>> {
|
|
172
|
+
return Promise.resolve(features.createFeature(params));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async updateFeature(
|
|
176
|
+
id: string,
|
|
177
|
+
params: {
|
|
178
|
+
name?: string;
|
|
179
|
+
summary?: string;
|
|
180
|
+
description?: string;
|
|
181
|
+
status?: FeatureStatus;
|
|
182
|
+
priority?: Priority;
|
|
183
|
+
projectId?: string;
|
|
184
|
+
tags?: string[];
|
|
185
|
+
version: number;
|
|
186
|
+
}
|
|
187
|
+
): Promise<Result<Feature>> {
|
|
188
|
+
return Promise.resolve(features.updateFeature(id, params));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async deleteFeature(id: string): Promise<Result<boolean>> {
|
|
192
|
+
return Promise.resolve(features.deleteFeature(id));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============================================================================
|
|
196
|
+
// Tasks
|
|
197
|
+
// ============================================================================
|
|
198
|
+
|
|
199
|
+
async getTasks(params?: TaskSearchParams): Promise<Result<Task[]>> {
|
|
200
|
+
return Promise.resolve(
|
|
201
|
+
tasks.searchTasks({
|
|
202
|
+
query: params?.query,
|
|
203
|
+
status: params?.status,
|
|
204
|
+
priority: params?.priority,
|
|
205
|
+
projectId: params?.projectId,
|
|
206
|
+
featureId: params?.featureId,
|
|
207
|
+
tags: params?.tags?.join(','),
|
|
208
|
+
limit: params?.limit,
|
|
209
|
+
offset: params?.offset,
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async getTask(id: string): Promise<Result<Task>> {
|
|
215
|
+
return Promise.resolve(tasks.getTask(id));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async createTask(params: {
|
|
219
|
+
projectId?: string;
|
|
220
|
+
featureId?: string;
|
|
221
|
+
title: string;
|
|
222
|
+
summary: string;
|
|
223
|
+
description?: string;
|
|
224
|
+
status?: TaskStatus;
|
|
225
|
+
priority: Priority;
|
|
226
|
+
complexity: number;
|
|
227
|
+
tags?: string[];
|
|
228
|
+
}): Promise<Result<Task>> {
|
|
229
|
+
return Promise.resolve(tasks.createTask(params));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async updateTask(
|
|
233
|
+
id: string,
|
|
234
|
+
params: {
|
|
235
|
+
title?: string;
|
|
236
|
+
summary?: string;
|
|
237
|
+
description?: string;
|
|
238
|
+
status?: TaskStatus;
|
|
239
|
+
priority?: Priority;
|
|
240
|
+
complexity?: number;
|
|
241
|
+
projectId?: string;
|
|
242
|
+
featureId?: string;
|
|
243
|
+
lastModifiedBy?: string;
|
|
244
|
+
tags?: string[];
|
|
245
|
+
version: number;
|
|
246
|
+
}
|
|
247
|
+
): Promise<Result<Task>> {
|
|
248
|
+
return Promise.resolve(tasks.updateTask(id, params));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async deleteTask(id: string): Promise<Result<boolean>> {
|
|
252
|
+
return Promise.resolve(tasks.deleteTask(id));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async setTaskStatus(
|
|
256
|
+
id: string,
|
|
257
|
+
status: TaskStatus,
|
|
258
|
+
version: number
|
|
259
|
+
): Promise<Result<Task>> {
|
|
260
|
+
return Promise.resolve(tasks.setTaskStatus(id, status, version));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async setProjectStatus(
|
|
264
|
+
id: string,
|
|
265
|
+
status: ProjectStatus,
|
|
266
|
+
version: number
|
|
267
|
+
): Promise<Result<Project>> {
|
|
268
|
+
return Promise.resolve(projects.updateProject(id, { status, version }));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async setFeatureStatus(
|
|
272
|
+
id: string,
|
|
273
|
+
status: FeatureStatus,
|
|
274
|
+
version: number
|
|
275
|
+
): Promise<Result<Feature>> {
|
|
276
|
+
return Promise.resolve(features.updateFeature(id, { status, version }));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// Sections
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
async getSections(
|
|
284
|
+
entityType: EntityType,
|
|
285
|
+
entityId: string
|
|
286
|
+
): Promise<Result<Section[]>> {
|
|
287
|
+
return Promise.resolve(sections.getSections(entityId, entityType));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ============================================================================
|
|
291
|
+
// Dependencies
|
|
292
|
+
// ============================================================================
|
|
293
|
+
|
|
294
|
+
async getDependencies(taskId: string): Promise<Result<DependencyInfo>> {
|
|
295
|
+
const result = dependencies.getDependencies(taskId, 'both');
|
|
296
|
+
|
|
297
|
+
if (!result.success) {
|
|
298
|
+
return Promise.resolve(result as Result<DependencyInfo>);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Transform to DependencyInfo format
|
|
302
|
+
// Dependencies returned have fromTaskId and toTaskId
|
|
303
|
+
// - If fromTaskId === taskId, it's a dependency this task creates (blocks something)
|
|
304
|
+
// - If toTaskId === taskId, it's a dependency blocking this task (blocked by)
|
|
305
|
+
|
|
306
|
+
const deps = result.data;
|
|
307
|
+
const blockedByTaskIds = deps
|
|
308
|
+
.filter((d) => d.toTaskId === taskId && d.type === 'BLOCKS')
|
|
309
|
+
.map((d) => d.fromTaskId);
|
|
310
|
+
const blocksTaskIds = deps
|
|
311
|
+
.filter((d) => d.fromTaskId === taskId && d.type === 'BLOCKS')
|
|
312
|
+
.map((d) => d.toTaskId);
|
|
313
|
+
|
|
314
|
+
// Fetch the actual task objects
|
|
315
|
+
const blockedByTasks: Task[] = [];
|
|
316
|
+
for (const id of blockedByTaskIds) {
|
|
317
|
+
const taskResult = tasks.getTask(id);
|
|
318
|
+
if (taskResult.success) {
|
|
319
|
+
blockedByTasks.push(taskResult.data);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const blocksTasks: Task[] = [];
|
|
324
|
+
for (const id of blocksTaskIds) {
|
|
325
|
+
const taskResult = tasks.getTask(id);
|
|
326
|
+
if (taskResult.success) {
|
|
327
|
+
blocksTasks.push(taskResult.data);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const dependencyInfo: DependencyInfo = {
|
|
332
|
+
blockedBy: blockedByTasks,
|
|
333
|
+
blocks: blocksTasks,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return Promise.resolve({ success: true, data: dependencyInfo });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async getBlockedTasks(params?: {
|
|
340
|
+
projectId?: string;
|
|
341
|
+
}): Promise<Result<Task[]>> {
|
|
342
|
+
return Promise.resolve(
|
|
343
|
+
dependencies.getBlockedTasks({
|
|
344
|
+
projectId: params?.projectId,
|
|
345
|
+
})
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async getNextTask(params?: {
|
|
350
|
+
projectId?: string;
|
|
351
|
+
}): Promise<Result<Task | null>> {
|
|
352
|
+
return Promise.resolve(
|
|
353
|
+
dependencies.getNextTask({
|
|
354
|
+
projectId: params?.projectId,
|
|
355
|
+
})
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ============================================================================
|
|
360
|
+
// Workflow
|
|
361
|
+
// ============================================================================
|
|
362
|
+
|
|
363
|
+
async getAllowedTransitions(
|
|
364
|
+
containerType: string,
|
|
365
|
+
status: string
|
|
366
|
+
): Promise<Result<string[]>> {
|
|
367
|
+
try {
|
|
368
|
+
// getAllowedTransitions returns string[] directly, not a Result
|
|
369
|
+
const transitions = getAllowedTransitions(
|
|
370
|
+
containerType as ContainerType,
|
|
371
|
+
status
|
|
372
|
+
);
|
|
373
|
+
return Promise.resolve({ success: true, data: transitions });
|
|
374
|
+
} catch (error) {
|
|
375
|
+
return Promise.resolve({
|
|
376
|
+
success: false,
|
|
377
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ============================================================================
|
|
383
|
+
// Search
|
|
384
|
+
// ============================================================================
|
|
385
|
+
|
|
386
|
+
async search(query: string): Promise<Result<SearchResults>> {
|
|
387
|
+
try {
|
|
388
|
+
// Search across all entity types
|
|
389
|
+
const projectsResult = projects.searchProjects({ query, limit: 10 });
|
|
390
|
+
const featuresResult = features.searchFeatures({ query, limit: 10 });
|
|
391
|
+
const tasksResult = tasks.searchTasks({ query, limit: 10 });
|
|
392
|
+
|
|
393
|
+
// Build search results
|
|
394
|
+
const results: SearchResults = {
|
|
395
|
+
projects: projectsResult.success
|
|
396
|
+
? projectsResult.data.map((p) => ({
|
|
397
|
+
id: p.id,
|
|
398
|
+
name: p.name,
|
|
399
|
+
summary: p.summary,
|
|
400
|
+
}))
|
|
401
|
+
: [],
|
|
402
|
+
features: featuresResult.success
|
|
403
|
+
? featuresResult.data.map((f) => ({
|
|
404
|
+
id: f.id,
|
|
405
|
+
name: f.name,
|
|
406
|
+
summary: f.summary,
|
|
407
|
+
projectId: f.projectId,
|
|
408
|
+
}))
|
|
409
|
+
: [],
|
|
410
|
+
tasks: tasksResult.success
|
|
411
|
+
? tasksResult.data.map((t) => ({
|
|
412
|
+
id: t.id,
|
|
413
|
+
title: t.title,
|
|
414
|
+
summary: t.summary,
|
|
415
|
+
projectId: t.projectId,
|
|
416
|
+
featureId: t.featureId,
|
|
417
|
+
}))
|
|
418
|
+
: [],
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
return Promise.resolve({ success: true, data: results });
|
|
422
|
+
} catch (error) {
|
|
423
|
+
return Promise.resolve({
|
|
424
|
+
success: false,
|
|
425
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Adapter Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines how the UI layer accesses data from the domain layer.
|
|
5
|
+
* Implementations can be in-memory, HTTP-based, or other transport mechanisms.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
Task,
|
|
10
|
+
Feature,
|
|
11
|
+
Project,
|
|
12
|
+
TaskStatus,
|
|
13
|
+
ProjectStatus,
|
|
14
|
+
FeatureStatus,
|
|
15
|
+
Section,
|
|
16
|
+
EntityType,
|
|
17
|
+
Priority,
|
|
18
|
+
} from 'task-orchestrator-bun/src/domain/types';
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
SearchResults,
|
|
22
|
+
DependencyInfo,
|
|
23
|
+
ProjectOverview,
|
|
24
|
+
FeatureOverview,
|
|
25
|
+
} from '../lib/types';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Result type for adapter operations
|
|
29
|
+
*/
|
|
30
|
+
export type Result<T> =
|
|
31
|
+
| { success: true; data: T }
|
|
32
|
+
| { success: false; error: string; code?: string };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Base search parameters
|
|
36
|
+
*/
|
|
37
|
+
export interface SearchParams {
|
|
38
|
+
query?: string;
|
|
39
|
+
status?: TaskStatus;
|
|
40
|
+
tags?: string[];
|
|
41
|
+
limit?: number;
|
|
42
|
+
offset?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Feature-specific search parameters
|
|
47
|
+
*/
|
|
48
|
+
export interface FeatureSearchParams extends SearchParams {
|
|
49
|
+
projectId?: string;
|
|
50
|
+
priority?: Priority;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Task-specific search parameters
|
|
55
|
+
*/
|
|
56
|
+
export interface TaskSearchParams extends FeatureSearchParams {
|
|
57
|
+
featureId?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Data Adapter Interface
|
|
62
|
+
*
|
|
63
|
+
* Provides unified access to domain entities and operations.
|
|
64
|
+
*/
|
|
65
|
+
export interface DataAdapter {
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Projects
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get all projects matching the search parameters
|
|
72
|
+
*/
|
|
73
|
+
getProjects(params?: SearchParams): Promise<Result<Project[]>>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get a single project by ID
|
|
77
|
+
*/
|
|
78
|
+
getProject(id: string): Promise<Result<Project>>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get project overview with aggregated statistics
|
|
82
|
+
*/
|
|
83
|
+
getProjectOverview(id: string): Promise<Result<ProjectOverview>>;
|
|
84
|
+
|
|
85
|
+
createProject(params: {
|
|
86
|
+
name: string;
|
|
87
|
+
summary: string;
|
|
88
|
+
description?: string;
|
|
89
|
+
status?: ProjectStatus;
|
|
90
|
+
tags?: string[];
|
|
91
|
+
}): Promise<Result<Project>>;
|
|
92
|
+
|
|
93
|
+
updateProject(
|
|
94
|
+
id: string,
|
|
95
|
+
params: {
|
|
96
|
+
name?: string;
|
|
97
|
+
summary?: string;
|
|
98
|
+
description?: string;
|
|
99
|
+
status?: ProjectStatus;
|
|
100
|
+
tags?: string[];
|
|
101
|
+
version: number;
|
|
102
|
+
}
|
|
103
|
+
): Promise<Result<Project>>;
|
|
104
|
+
|
|
105
|
+
deleteProject(id: string): Promise<Result<boolean>>;
|
|
106
|
+
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Features
|
|
109
|
+
// ============================================================================
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get all features matching the search parameters
|
|
113
|
+
*/
|
|
114
|
+
getFeatures(params?: FeatureSearchParams): Promise<Result<Feature[]>>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get a single feature by ID
|
|
118
|
+
*/
|
|
119
|
+
getFeature(id: string): Promise<Result<Feature>>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get feature overview with aggregated statistics
|
|
123
|
+
*/
|
|
124
|
+
getFeatureOverview(id: string): Promise<Result<FeatureOverview>>;
|
|
125
|
+
|
|
126
|
+
createFeature(params: {
|
|
127
|
+
projectId?: string;
|
|
128
|
+
name: string;
|
|
129
|
+
summary: string;
|
|
130
|
+
description?: string;
|
|
131
|
+
status?: FeatureStatus;
|
|
132
|
+
priority: Priority;
|
|
133
|
+
tags?: string[];
|
|
134
|
+
}): Promise<Result<Feature>>;
|
|
135
|
+
|
|
136
|
+
updateFeature(
|
|
137
|
+
id: string,
|
|
138
|
+
params: {
|
|
139
|
+
name?: string;
|
|
140
|
+
summary?: string;
|
|
141
|
+
description?: string;
|
|
142
|
+
status?: FeatureStatus;
|
|
143
|
+
priority?: Priority;
|
|
144
|
+
projectId?: string;
|
|
145
|
+
tags?: string[];
|
|
146
|
+
version: number;
|
|
147
|
+
}
|
|
148
|
+
): Promise<Result<Feature>>;
|
|
149
|
+
|
|
150
|
+
deleteFeature(id: string): Promise<Result<boolean>>;
|
|
151
|
+
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Tasks
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get all tasks matching the search parameters
|
|
158
|
+
*/
|
|
159
|
+
getTasks(params?: TaskSearchParams): Promise<Result<Task[]>>;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get a single task by ID
|
|
163
|
+
*/
|
|
164
|
+
getTask(id: string): Promise<Result<Task>>;
|
|
165
|
+
|
|
166
|
+
createTask(params: {
|
|
167
|
+
projectId?: string;
|
|
168
|
+
featureId?: string;
|
|
169
|
+
title: string;
|
|
170
|
+
summary: string;
|
|
171
|
+
description?: string;
|
|
172
|
+
status?: TaskStatus;
|
|
173
|
+
priority: Priority;
|
|
174
|
+
complexity: number;
|
|
175
|
+
tags?: string[];
|
|
176
|
+
}): Promise<Result<Task>>;
|
|
177
|
+
|
|
178
|
+
updateTask(
|
|
179
|
+
id: string,
|
|
180
|
+
params: {
|
|
181
|
+
title?: string;
|
|
182
|
+
summary?: string;
|
|
183
|
+
description?: string;
|
|
184
|
+
status?: TaskStatus;
|
|
185
|
+
priority?: Priority;
|
|
186
|
+
complexity?: number;
|
|
187
|
+
projectId?: string;
|
|
188
|
+
featureId?: string;
|
|
189
|
+
lastModifiedBy?: string;
|
|
190
|
+
tags?: string[];
|
|
191
|
+
version: number;
|
|
192
|
+
}
|
|
193
|
+
): Promise<Result<Task>>;
|
|
194
|
+
|
|
195
|
+
deleteTask(id: string): Promise<Result<boolean>>;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Update a task's status with optimistic concurrency control
|
|
199
|
+
*/
|
|
200
|
+
setTaskStatus(
|
|
201
|
+
id: string,
|
|
202
|
+
status: TaskStatus,
|
|
203
|
+
version: number
|
|
204
|
+
): Promise<Result<Task>>;
|
|
205
|
+
|
|
206
|
+
setProjectStatus(
|
|
207
|
+
id: string,
|
|
208
|
+
status: ProjectStatus,
|
|
209
|
+
version: number
|
|
210
|
+
): Promise<Result<Project>>;
|
|
211
|
+
|
|
212
|
+
setFeatureStatus(
|
|
213
|
+
id: string,
|
|
214
|
+
status: FeatureStatus,
|
|
215
|
+
version: number
|
|
216
|
+
): Promise<Result<Feature>>;
|
|
217
|
+
|
|
218
|
+
// ============================================================================
|
|
219
|
+
// Sections
|
|
220
|
+
// ============================================================================
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get all sections for a given entity (project, feature, or task)
|
|
224
|
+
*/
|
|
225
|
+
getSections(
|
|
226
|
+
entityType: EntityType,
|
|
227
|
+
entityId: string
|
|
228
|
+
): Promise<Result<Section[]>>;
|
|
229
|
+
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// Dependencies
|
|
232
|
+
// ============================================================================
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get dependency information for a task (blockers and blocked tasks)
|
|
236
|
+
*/
|
|
237
|
+
getDependencies(taskId: string): Promise<Result<DependencyInfo>>;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get all blocked tasks, optionally filtered by project
|
|
241
|
+
*/
|
|
242
|
+
getBlockedTasks(params?: { projectId?: string }): Promise<Result<Task[]>>;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get the next actionable task (no blockers, not completed)
|
|
246
|
+
*/
|
|
247
|
+
getNextTask(params?: { projectId?: string }): Promise<Result<Task | null>>;
|
|
248
|
+
|
|
249
|
+
// ============================================================================
|
|
250
|
+
// Workflow
|
|
251
|
+
// ============================================================================
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get allowed status transitions for a given container type and current status
|
|
255
|
+
*/
|
|
256
|
+
getAllowedTransitions(
|
|
257
|
+
containerType: string,
|
|
258
|
+
status: string
|
|
259
|
+
): Promise<Result<string[]>>;
|
|
260
|
+
|
|
261
|
+
// ============================================================================
|
|
262
|
+
// Search
|
|
263
|
+
// ============================================================================
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Full-text search across all entities
|
|
267
|
+
*/
|
|
268
|
+
search(query: string): Promise<Result<SearchResults>>;
|
|
269
|
+
}
|