Skip to content

Commit e628c4f

Browse files
author
Marvin Zhang
committed
refactor: update Devlog API client and schemas for improved filter handling; remove deprecated filter types and enhance pagination support
1 parent 32e4d00 commit e628c4f

File tree

15 files changed

+105
-333
lines changed

15 files changed

+105
-333
lines changed

.github/copilot-instructions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
## 📁 Development Workflow
2020

2121
- **Temp files**: Use `tmp/` folder for experiments (gitignored)
22-
- **Build testing**: Use `pnpm build:test` (doesn't break dev servers)
22+
- **Build packages**: Use `pnpm build` (builds all packages)
2323
- **Containers**: `docker compose -f docker-compose.dev.yml up web-dev -d --wait`
24-
- **Build order**: Core → MCP → Web (dependency chain)
24+
- **Testing**: Use `pnpm test`
2525

2626
### Task Tracking
2727
- **Always start by checking**: Search related devlogs before starting ANY new work
28-
- **Must create devlogs**: For features, refactoring, or multi-step work (>30min)
28+
- **Must create devlogs**: For features, refactoring, or multistep work (>30min)
2929
- **Required progress updates**: Add notes after successful builds, major changes, or blockers
3030
- **Always complete**: Document learnings and close devlogs when work is finished
3131

packages/core/src/services/devlog-service.ts

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,12 @@ export class DevlogService {
390390
return await this.handleList(projectFilter, queryBuilder, pagination, sortOptions);
391391
}
392392

393-
async search(query: string, filter?: DevlogFilter): Promise<PaginatedResult<DevlogEntry>> {
393+
async search(
394+
query: string,
395+
filter?: DevlogFilter,
396+
pagination?: PaginationMeta,
397+
sortOptions?: SortOptions,
398+
): Promise<PaginatedResult<DevlogEntry>> {
394399
await this.ensureInitialized();
395400

396401
const { projectFilter, queryBuilder } = this.prepareListQuery(filter);
@@ -402,7 +407,7 @@ export class DevlogService {
402407
.orWhere('devlog.businessContext LIKE :query', { query: `%${query}%` })
403408
.orWhere('devlog.technicalContext LIKE :query', { query: `%${query}%` });
404409

405-
return await this.handleList(projectFilter, queryBuilder);
410+
return await this.handleList(projectFilter, queryBuilder, pagination, sortOptions);
406411
}
407412

408413
/**
@@ -838,53 +843,53 @@ export class DevlogService {
838843
*/
839844
private async applySearchFilters(
840845
queryBuilder: SelectQueryBuilder<DevlogEntryEntity>,
841-
projectFilter: DevlogFilter,
846+
filter: DevlogFilter,
842847
): Promise<void> {
843848
// Apply project filter
844-
if (projectFilter.projectId !== undefined) {
849+
if (filter.projectId !== undefined) {
845850
queryBuilder.andWhere('devlog.projectId = :projectId', {
846-
projectId: projectFilter.projectId,
851+
projectId: filter.projectId,
847852
});
848853
}
849854

850855
// Apply status filter
851-
if (projectFilter.status && projectFilter.status.length > 0) {
852-
queryBuilder.andWhere('devlog.status IN (:...statuses)', { statuses: projectFilter.status });
856+
if (filter.status && filter.status.length > 0) {
857+
queryBuilder.andWhere('devlog.status IN (:...statuses)', { statuses: filter.status });
853858
}
854859

855860
// Apply type filter
856-
if (projectFilter.type && projectFilter.type.length > 0) {
857-
queryBuilder.andWhere('devlog.type IN (:...types)', { types: projectFilter.type });
861+
if (filter.type && filter.type.length > 0) {
862+
queryBuilder.andWhere('devlog.type IN (:...types)', { types: filter.type });
858863
}
859864

860865
// Apply priority filter
861-
if (projectFilter.priority && projectFilter.priority.length > 0) {
866+
if (filter.priority && filter.priority.length > 0) {
862867
queryBuilder.andWhere('devlog.priority IN (:...priorities)', {
863-
priorities: projectFilter.priority,
868+
priorities: filter.priority,
864869
});
865870
}
866871

867872
// Apply assignee filter
868-
if (projectFilter.assignee !== undefined) {
869-
if (projectFilter.assignee === null) {
873+
if (filter.assignee !== undefined) {
874+
if (filter.assignee === null) {
870875
queryBuilder.andWhere('devlog.assignee IS NULL');
871876
} else {
872-
queryBuilder.andWhere('devlog.assignee = :assignee', { assignee: projectFilter.assignee });
877+
queryBuilder.andWhere('devlog.assignee = :assignee', { assignee: filter.assignee });
873878
}
874879
}
875880

876881
// Apply archived filter
877-
if (projectFilter.archived !== undefined) {
878-
queryBuilder.andWhere('devlog.archived = :archived', { archived: projectFilter.archived });
882+
if (filter.archived !== undefined) {
883+
queryBuilder.andWhere('devlog.archived = :archived', { archived: filter.archived });
879884
}
880885

881886
// Apply date range filters
882-
if (projectFilter.fromDate) {
883-
queryBuilder.andWhere('devlog.createdAt >= :fromDate', { fromDate: projectFilter.fromDate });
887+
if (filter.fromDate) {
888+
queryBuilder.andWhere('devlog.createdAt >= :fromDate', { fromDate: filter.fromDate });
884889
}
885890

886-
if (projectFilter.toDate) {
887-
queryBuilder.andWhere('devlog.createdAt <= :toDate', { toDate: projectFilter.toDate });
891+
if (filter.toDate) {
892+
queryBuilder.andWhere('devlog.createdAt <= :toDate', { toDate: filter.toDate });
888893
}
889894
}
890895

packages/core/src/types/core.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ export interface Dependency {
197197
}
198198

199199
export interface DevlogFilter {
200+
/**
201+
* @deprecated
202+
*/
200203
filterType?: FilterType; // New filter dimension for status grouping
201204
status?: DevlogStatus[];
202205
type?: DevlogType[];

packages/core/src/utils/filter-mapping.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { DevlogStatus, FilterType } from '../types/index.js';
1010

1111
/**
1212
* Mapping of filter types to their corresponding status arrays
13+
* @deprecated
1314
*
1415
* This is the single source of truth for status groupings:
1516
* - 'open': Statuses representing active/in-progress work
@@ -35,6 +36,7 @@ export const FILTER_TYPE_TO_STATUSES: Record<FilterType, DevlogStatus[]> = {
3536

3637
/**
3738
* Get the status array for a given filter type
39+
* @deprecated
3840
*
3941
* @param filterType - The filter type to map to statuses
4042
* @returns Array of DevlogStatus values corresponding to the filter type
@@ -54,6 +56,7 @@ export function getStatusesForFilterType(filterType: FilterType): DevlogStatus[]
5456

5557
/**
5658
* Check if a status belongs to a specific filter type category
59+
* @deprecated
5760
*
5861
* @param status - The DevlogStatus to check
5962
* @param filterType - The FilterType category to check against
@@ -121,6 +124,7 @@ export function isClosedStatus(status: DevlogStatus): boolean {
121124

122125
/**
123126
* Get the filter type category for a given status
127+
* @deprecated
124128
*
125129
* @param status - The DevlogStatus to categorize
126130
* @returns The FilterType category ('open' or 'closed') for the status
@@ -141,6 +145,7 @@ export function getFilterTypeForStatus(status: DevlogStatus): 'open' | 'closed'
141145
/**
142146
* Convert a FilterType to an appropriate status array for filtering operations
143147
* This is the main function to use when applying filters in storage/manager layers
148+
* @deprecated
144149
*
145150
* @param filterType - The FilterType to convert
146151
* @returns Array of DevlogStatus values for filtering, or undefined if 'total' (no filtering needed)

packages/core/src/validation/devlog-schemas.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,6 @@ export const DevlogIdSchema = z.number().int().positive('Devlog ID must be a pos
9494
* Devlog filter validation schema
9595
*/
9696
export const DevlogFilterSchema = z.object({
97-
filterType: z
98-
.enum([
99-
'new',
100-
'in-progress',
101-
'blocked',
102-
'in-review',
103-
'testing',
104-
'done',
105-
'cancelled',
106-
'total',
107-
'open',
108-
'closed',
109-
])
110-
.optional(),
11197
status: z
11298
.array(z.enum(['new', 'in-progress', 'blocked', 'in-review', 'testing', 'done', 'cancelled']))
11399
.optional(),
@@ -121,7 +107,7 @@ export const DevlogFilterSchema = z.object({
121107
projectId: z.number().int().positive().optional(),
122108
pagination: z
123109
.object({
124-
page: z.number().int().min(1).optional(),
110+
page: z.number().int().min(1).default(20).optional(),
125111
limit: z.number().int().min(1).max(100).optional(),
126112
})
127113
.optional(),

packages/mcp/src/adapters/mcp-adapter.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ export class MCPAdapter {
216216
status: args.status ? [args.status] : undefined,
217217
type: args.type ? [args.type] : undefined,
218218
priority: args.priority ? [args.priority] : undefined,
219-
pagination: args.limit ? { limit: args.limit } : undefined,
219+
page: args.page || 1,
220+
limit: args.limit || 10,
220221
});
221222

222223
const entries = result.items.map((entry: any) => ({
@@ -274,8 +275,10 @@ export class MCPAdapter {
274275

275276
try {
276277
const searchTerms = [args.description, ...(args.keywords || [])].join(' ');
277-
const result = await this.apiClient.searchDevlogs(searchTerms, {
278+
const result = await this.apiClient.searchDevlogs({
279+
query: searchTerms,
278280
type: args.type ? [args.type] : undefined,
281+
limit: args.limit,
279282
});
280283

281284
const hasRelated = result.items.length > 0;

packages/mcp/src/api/devlog-api-client.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import type {
88
DevlogEntry,
99
DevlogFilter,
1010
PaginatedResult,
11+
PaginationMeta,
12+
SortOptions,
1113
UpdateDevlogRequest,
1214
} from '@codervisor/devlog-core';
1315

@@ -232,25 +234,36 @@ export class DevlogApiClient {
232234
return this.unwrapApiResponse<void>(response);
233235
}
234236

235-
async listDevlogs(filter?: DevlogFilter): Promise<PaginatedResult<DevlogEntry>> {
237+
async listDevlogs(
238+
args?: Partial<DevlogFilter & PaginationMeta & SortOptions>,
239+
): Promise<PaginatedResult<DevlogEntry>> {
236240
const params = new URLSearchParams();
237241

238-
if (filter) {
239-
this.applyFilterToURLSearchParams(filter, params);
240-
if (filter.pagination?.sortBy) params.append('sortBy', filter.pagination.sortBy);
241-
if (filter.pagination?.sortOrder) params.append('sortOrder', filter.pagination.sortOrder);
242+
if (args) {
243+
this.applyFilterToURLSearchParams(args, params);
244+
245+
if (args.page) params.append('page', args.page.toString());
246+
if (args.limit) params.append('limit', args.limit.toString());
247+
248+
if (args.sortBy) params.append('sortBy', args.sortBy);
249+
if (args.sortOrder) params.append('sortOrder', args.sortOrder);
242250
}
243251

244252
const query = params.toString() ? `?${params.toString()}` : '';
245253
const response = await this.get(`${this.getProjectEndpoint()}/devlogs${query}`);
246254
return this.unwrapApiResponse<PaginatedResult<DevlogEntry>>(response);
247255
}
248256

249-
async searchDevlogs(query: string, filter?: DevlogFilter): Promise<PaginatedResult<DevlogEntry>> {
257+
async searchDevlogs({
258+
query,
259+
...args
260+
}: { query: string } & Partial<DevlogFilter & PaginationMeta>): Promise<
261+
PaginatedResult<DevlogEntry>
262+
> {
250263
const params = new URLSearchParams({ q: query });
251264

252-
if (filter) {
253-
this.applyFilterToURLSearchParams(filter, params);
265+
if (args) {
266+
this.applyFilterToURLSearchParams(args, params);
254267
}
255268

256269
const response = await this.get(
@@ -319,7 +332,5 @@ export class DevlogApiClient {
319332
if (filter.type?.length) params.append('type', filter.type.join(','));
320333
if (filter.priority?.length) params.append('priority', filter.priority.join(','));
321334
if (filter.archived !== undefined) params.append('archived', String(filter.archived));
322-
if (filter.pagination?.page) params.append('page', String(filter.pagination.page));
323-
if (filter.pagination?.limit) params.append('limit', String(filter.pagination.limit));
324335
}
325336
}

packages/mcp/src/schemas/base.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,8 @@ export const LimitSchema = z.number().int().min(1).max(50).default(10).optional(
5252

5353
export const PageSchema = z.number().int().min(1).optional();
5454

55-
export const SortBySchema = z
55+
export const DevlogsSortBySchema = z
5656
.enum(['createdAt', 'updatedAt', 'priority', 'status', 'title'])
5757
.optional();
5858

5959
export const SortOrderSchema = z.enum(['asc', 'desc']).optional();
60-
61-
// === TYPE EXPORTS ===
62-
export type DevlogId = z.infer<typeof DevlogIdSchema>;
63-
export type ProjectId = z.infer<typeof ProjectIdSchema>;
64-
export type DevlogType = z.infer<typeof DevlogTypeSchema>;
65-
export type DevlogStatus = z.infer<typeof DevlogStatusSchema>;
66-
export type DevlogPriority = z.infer<typeof DevlogPrioritySchema>;
67-
export type NoteCategory = z.infer<typeof NoteCategorySchema>;

packages/mcp/src/schemas/devlog-schemas.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import {
1818
BusinessContextSchema,
1919
TechnicalContextSchema,
2020
AcceptanceCriteriaSchema,
21+
PageSchema,
22+
DevlogsSortBySchema,
23+
SortOrderSchema,
2124
} from './base.js';
2225

2326
// === CREATE DEVLOG ===
@@ -53,7 +56,10 @@ export const ListDevlogSchema = z.object({
5356
status: DevlogStatusSchema.optional(),
5457
type: DevlogTypeSchema.optional(),
5558
priority: DevlogPrioritySchema.optional(),
59+
page: PageSchema,
5660
limit: LimitSchema,
61+
sortBy: DevlogsSortBySchema,
62+
sortOrder: SortOrderSchema,
5763
});
5864

5965
// === ADD NOTE ===
@@ -75,6 +81,7 @@ export const FindRelatedSchema = z.object({
7581
description: DescriptionSchema,
7682
type: DevlogTypeSchema.optional(),
7783
keywords: KeywordsSchema,
84+
limit: LimitSchema,
7885
});
7986

8087
// === TYPE EXPORTS ===

0 commit comments

Comments
 (0)