@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.
Files changed (50) hide show
  1. package/README.md +78 -0
  2. package/package.json +54 -0
  3. package/src/tui/app.tsx +308 -0
  4. package/src/tui/components/column-filter-bar.tsx +52 -0
  5. package/src/tui/components/confirm-dialog.tsx +45 -0
  6. package/src/tui/components/dependency-list.tsx +115 -0
  7. package/src/tui/components/empty-state.tsx +28 -0
  8. package/src/tui/components/entity-table.tsx +120 -0
  9. package/src/tui/components/error-message.tsx +41 -0
  10. package/src/tui/components/feature-kanban-card.tsx +216 -0
  11. package/src/tui/components/footer.tsx +34 -0
  12. package/src/tui/components/form-dialog.tsx +338 -0
  13. package/src/tui/components/header.tsx +54 -0
  14. package/src/tui/components/index.ts +16 -0
  15. package/src/tui/components/kanban-board.tsx +335 -0
  16. package/src/tui/components/kanban-card.tsx +70 -0
  17. package/src/tui/components/kanban-column.tsx +173 -0
  18. package/src/tui/components/priority-badge.tsx +16 -0
  19. package/src/tui/components/section-list.tsx +96 -0
  20. package/src/tui/components/status-actions.tsx +87 -0
  21. package/src/tui/components/status-badge.tsx +22 -0
  22. package/src/tui/components/tree-view.tsx +295 -0
  23. package/src/tui/components/view-mode-chips.tsx +23 -0
  24. package/src/tui/index.tsx +33 -0
  25. package/src/tui/screens/dashboard.tsx +248 -0
  26. package/src/tui/screens/feature-detail.tsx +312 -0
  27. package/src/tui/screens/index.ts +6 -0
  28. package/src/tui/screens/kanban-view.tsx +251 -0
  29. package/src/tui/screens/project-detail.tsx +305 -0
  30. package/src/tui/screens/project-view.tsx +498 -0
  31. package/src/tui/screens/search.tsx +257 -0
  32. package/src/tui/screens/task-detail.tsx +294 -0
  33. package/src/ui/adapters/direct.ts +429 -0
  34. package/src/ui/adapters/index.ts +14 -0
  35. package/src/ui/adapters/types.ts +269 -0
  36. package/src/ui/context/adapter-context.tsx +31 -0
  37. package/src/ui/context/theme-context.tsx +43 -0
  38. package/src/ui/hooks/index.ts +20 -0
  39. package/src/ui/hooks/use-data.ts +919 -0
  40. package/src/ui/hooks/use-debounce.ts +37 -0
  41. package/src/ui/hooks/use-feature-kanban.ts +151 -0
  42. package/src/ui/hooks/use-kanban.ts +96 -0
  43. package/src/ui/hooks/use-navigation.tsx +94 -0
  44. package/src/ui/index.ts +73 -0
  45. package/src/ui/lib/colors.ts +79 -0
  46. package/src/ui/lib/format.ts +114 -0
  47. package/src/ui/lib/types.ts +157 -0
  48. package/src/ui/themes/dark.ts +63 -0
  49. package/src/ui/themes/light.ts +63 -0
  50. 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,14 @@
1
+ /**
2
+ * Data Adapters
3
+ *
4
+ * Barrel export for adapter implementations
5
+ */
6
+
7
+ export { DirectAdapter } from './direct';
8
+ export type {
9
+ DataAdapter,
10
+ Result,
11
+ SearchParams,
12
+ FeatureSearchParams,
13
+ TaskSearchParams,
14
+ } from './types';
@@ -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
+ }