@proletariat/cli 0.3.35 → 0.3.40
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 +37 -2
- package/bin/dev.js +0 -0
- package/dist/commands/agent/auth.d.ts +12 -2
- package/dist/commands/agent/auth.js +128 -4
- package/dist/commands/agent/list.js +16 -7
- package/dist/commands/agent/status.js +32 -4
- package/dist/commands/board/watch.js +6 -0
- package/dist/commands/branch/list.d.ts +1 -0
- package/dist/commands/branch/list.js +43 -12
- package/dist/commands/branch/where.js +9 -19
- package/dist/commands/category/list.d.ts +2 -1
- package/dist/commands/category/list.js +38 -13
- package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
- package/dist/commands/{claude.js → claude/index.js} +12 -12
- package/dist/commands/claude/open.d.ts +13 -0
- package/dist/commands/claude/open.js +175 -0
- package/dist/commands/diet.js +18 -2
- package/dist/commands/docker/logs.js +7 -3
- package/dist/commands/docker/shell.js +6 -0
- package/dist/commands/docker/start.js +20 -4
- package/dist/commands/docker/sync.d.ts +4 -0
- package/dist/commands/docker/sync.js +30 -2
- package/dist/commands/epic/show.d.ts +13 -0
- package/dist/commands/epic/show.js +16 -0
- package/dist/commands/epic/ticket.js +7 -24
- package/dist/commands/epic/view.js +27 -0
- package/dist/commands/execution/config.d.ts +0 -4
- package/dist/commands/execution/config.js +14 -46
- package/dist/commands/execution/index.js +2 -1
- package/dist/commands/execution/logs.js +7 -1
- package/dist/commands/execution/stop.js +2 -1
- package/dist/commands/execution/view.js +30 -26
- package/dist/commands/init.js +2 -19
- package/dist/commands/label/create.js +2 -1
- package/dist/commands/label/delete.js +2 -1
- package/dist/commands/label/group/create.js +2 -1
- package/dist/commands/label/group/list.js +2 -1
- package/dist/commands/label/list.js +2 -1
- package/dist/commands/mcp-server.js +27 -1
- package/dist/commands/phase/template/list.js +2 -1
- package/dist/commands/pmo/init.js +12 -40
- package/dist/commands/project/create.js +3 -4
- package/dist/commands/project/update.js +5 -6
- package/dist/commands/pull.js +24 -0
- package/dist/commands/qa/index.d.ts +54 -0
- package/dist/commands/qa/index.js +762 -0
- package/dist/commands/repo/view.js +2 -8
- package/dist/commands/session/attach.js +4 -4
- package/dist/commands/session/create.d.ts +19 -0
- package/dist/commands/session/create.js +102 -0
- package/dist/commands/session/health.js +4 -23
- package/dist/commands/session/index.js +14 -1
- package/dist/commands/session/list.js +9 -8
- package/dist/commands/session/peek.d.ts +38 -0
- package/dist/commands/session/peek.js +316 -0
- package/dist/commands/session/poke.d.ts +27 -0
- package/dist/commands/session/poke.js +219 -0
- package/dist/commands/spec/view.js +29 -0
- package/dist/commands/template/list.js +2 -1
- package/dist/commands/theme/add-names.d.ts +4 -0
- package/dist/commands/theme/add-names.js +11 -1
- package/dist/commands/theme/create.d.ts +2 -0
- package/dist/commands/theme/create.js +8 -0
- package/dist/commands/ticket/bulk.js +2 -2
- package/dist/commands/ticket/complete.js +2 -2
- package/dist/commands/ticket/create.js +21 -0
- package/dist/commands/ticket/delete.js +8 -0
- package/dist/commands/ticket/edit.js +25 -0
- package/dist/commands/ticket/epic.js +17 -43
- package/dist/commands/ticket/index.js +2 -2
- package/dist/commands/ticket/move.js +25 -2
- package/dist/commands/ticket/resolve.js +3 -4
- package/dist/commands/ticket/show.d.ts +13 -0
- package/dist/commands/ticket/show.js +16 -0
- package/dist/commands/ticket/template/list.js +2 -1
- package/dist/commands/ticket/view.d.ts +0 -1
- package/dist/commands/ticket/view.js +30 -1
- package/dist/commands/work/index.js +4 -0
- package/dist/commands/work/spawn-all.js +1 -1
- package/dist/commands/work/spawn.js +15 -4
- package/dist/commands/work/start.js +186 -103
- package/dist/commands/work/status.d.ts +14 -0
- package/dist/commands/work/status.js +60 -0
- package/dist/commands/work/watch.js +1 -1
- package/dist/commands/workflow/index.js +2 -1
- package/dist/commands/workflow/show.d.ts +13 -0
- package/dist/commands/workflow/show.js +16 -0
- package/dist/commands/workspace/add.js +15 -0
- package/dist/commands/workspace/list.js +2 -1
- package/dist/commands/workspace/prune.js +7 -7
- package/dist/hooks/init.js +10 -2
- package/dist/lib/agents/commands.d.ts +5 -0
- package/dist/lib/agents/commands.js +143 -97
- package/dist/lib/branch/index.d.ts +1 -0
- package/dist/lib/database/drizzle-schema.d.ts +465 -0
- package/dist/lib/database/drizzle-schema.js +53 -0
- package/dist/lib/database/index.d.ts +47 -1
- package/dist/lib/database/index.js +138 -20
- package/dist/lib/execution/config.d.ts +15 -1
- package/dist/lib/execution/config.js +28 -0
- package/dist/lib/execution/runners.d.ts +45 -0
- package/dist/lib/execution/runners.js +187 -26
- package/dist/lib/execution/session-utils.d.ts +16 -1
- package/dist/lib/execution/session-utils.js +71 -4
- package/dist/lib/execution/spawner.js +15 -2
- package/dist/lib/execution/storage.d.ts +6 -1
- package/dist/lib/execution/storage.js +35 -5
- package/dist/lib/execution/types.d.ts +3 -0
- package/dist/lib/mcp/tools/board.js +4 -6
- package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
- package/dist/lib/mcp/tools/epic.js +8 -3
- package/dist/lib/mcp/tools/index.d.ts +1 -0
- package/dist/lib/mcp/tools/index.js +1 -0
- package/dist/lib/mcp/tools/spec.js +1 -1
- package/dist/lib/mcp/tools/ticket.js +11 -9
- package/dist/lib/mcp/tools/tmux.d.ts +16 -0
- package/dist/lib/mcp/tools/tmux.js +182 -0
- package/dist/lib/mcp/tools/work.js +148 -6
- package/dist/lib/mcp/types.d.ts +10 -0
- package/dist/lib/multiline-input.js +2 -1
- package/dist/lib/pmo/base-command.js +4 -4
- package/dist/lib/pmo/schema.d.ts +1 -1
- package/dist/lib/pmo/schema.js +1 -0
- package/dist/lib/pmo/storage/actions.js +1 -1
- package/dist/lib/pmo/storage/base.js +402 -50
- package/dist/lib/pmo/storage/dependencies.d.ts +1 -0
- package/dist/lib/pmo/storage/dependencies.js +11 -3
- package/dist/lib/pmo/storage/epics.js +1 -1
- package/dist/lib/pmo/storage/helpers.d.ts +4 -4
- package/dist/lib/pmo/storage/helpers.js +36 -26
- package/dist/lib/pmo/storage/projects.d.ts +2 -0
- package/dist/lib/pmo/storage/projects.js +207 -119
- package/dist/lib/pmo/storage/specs.d.ts +2 -0
- package/dist/lib/pmo/storage/specs.js +274 -188
- package/dist/lib/pmo/storage/tickets.d.ts +2 -0
- package/dist/lib/pmo/storage/tickets.js +350 -290
- package/dist/lib/pmo/storage/types.d.ts +1 -0
- package/dist/lib/pmo/storage/views.d.ts +2 -0
- package/dist/lib/pmo/storage/views.js +183 -130
- package/dist/lib/prompt-command.d.ts +20 -0
- package/dist/lib/prompt-command.js +38 -2
- package/dist/lib/prompt-json.d.ts +41 -4
- package/dist/lib/prompt-json.js +138 -7
- package/dist/lib/styles.d.ts +37 -0
- package/dist/lib/styles.js +73 -0
- package/oclif.manifest.json +4046 -3385
- package/package.json +11 -6
- package/LICENSE +0 -190
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project operations.
|
|
3
3
|
* Board columns are now derived from workflow statuses (single source of truth).
|
|
4
|
+
*
|
|
5
|
+
* This module uses Drizzle ORM for type-safe database queries.
|
|
4
6
|
*/
|
|
5
|
-
import {
|
|
7
|
+
import { eq, and, like, or, asc, desc, sql, count } from 'drizzle-orm';
|
|
8
|
+
import { pmoProjects, pmoTickets, pmoWorkflows, pmoWorkflowStatuses, } from '../../database/drizzle-schema.js';
|
|
6
9
|
import { PMOError, } from '../types.js';
|
|
7
10
|
import { generateEntityId, slugify } from '../utils.js';
|
|
8
11
|
import { generateBoardMarkdown } from '../markdown.js';
|
|
9
12
|
import { rowToTicket, wrapSqliteError } from './helpers.js';
|
|
10
|
-
const T = PMO_TABLES;
|
|
11
13
|
export class ProjectStorage {
|
|
12
14
|
ctx;
|
|
13
15
|
constructor(ctx) {
|
|
@@ -29,34 +31,42 @@ export class ProjectStorage {
|
|
|
29
31
|
if (!identifier)
|
|
30
32
|
return null;
|
|
31
33
|
// 1. Exact ID match
|
|
32
|
-
const exactMatch = this.ctx.
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
const exactMatch = this.ctx.drizzle
|
|
35
|
+
.select({ id: pmoProjects.id })
|
|
36
|
+
.from(pmoProjects)
|
|
37
|
+
.where(eq(pmoProjects.id, identifier))
|
|
38
|
+
.get();
|
|
35
39
|
if (exactMatch)
|
|
36
40
|
return exactMatch.id;
|
|
37
41
|
// 2. Case-insensitive ID match
|
|
38
|
-
const caseInsensitiveId = this.ctx.
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
const caseInsensitiveId = this.ctx.drizzle
|
|
43
|
+
.select({ id: pmoProjects.id })
|
|
44
|
+
.from(pmoProjects)
|
|
45
|
+
.where(sql `LOWER(${pmoProjects.id}) = LOWER(${identifier})`)
|
|
46
|
+
.get();
|
|
41
47
|
if (caseInsensitiveId)
|
|
42
48
|
return caseInsensitiveId.id;
|
|
43
49
|
// 3. Exact name match
|
|
44
|
-
const nameMatch = this.ctx.
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
const nameMatch = this.ctx.drizzle
|
|
51
|
+
.select({ id: pmoProjects.id })
|
|
52
|
+
.from(pmoProjects)
|
|
53
|
+
.where(eq(pmoProjects.name, identifier))
|
|
54
|
+
.get();
|
|
47
55
|
if (nameMatch)
|
|
48
56
|
return nameMatch.id;
|
|
49
57
|
// 4. Case-insensitive name match
|
|
50
|
-
const caseInsensitiveName = this.ctx.
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
const caseInsensitiveName = this.ctx.drizzle
|
|
59
|
+
.select({ id: pmoProjects.id })
|
|
60
|
+
.from(pmoProjects)
|
|
61
|
+
.where(sql `LOWER(${pmoProjects.name}) = LOWER(${identifier})`)
|
|
62
|
+
.get();
|
|
53
63
|
if (caseInsensitiveName)
|
|
54
64
|
return caseInsensitiveName.id;
|
|
55
65
|
// 5. Slugified name match - check if identifier is a slug of any project name
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
const allProjects = this.ctx.drizzle
|
|
67
|
+
.select({ id: pmoProjects.id, name: pmoProjects.name })
|
|
68
|
+
.from(pmoProjects)
|
|
69
|
+
.all();
|
|
60
70
|
const identifierLower = identifier.toLowerCase();
|
|
61
71
|
for (const project of allProjects) {
|
|
62
72
|
const projectSlug = slugify(project.name);
|
|
@@ -72,12 +82,27 @@ export class ProjectStorage {
|
|
|
72
82
|
*/
|
|
73
83
|
async init(projectId, config) {
|
|
74
84
|
const projectName = config.name || 'Project Board';
|
|
75
|
-
const now = Date.now();
|
|
85
|
+
const now = String(Date.now());
|
|
76
86
|
// Create or update project with default workflow
|
|
77
|
-
this.ctx.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
this.ctx.drizzle
|
|
88
|
+
.insert(pmoProjects)
|
|
89
|
+
.values({
|
|
90
|
+
id: projectId,
|
|
91
|
+
name: projectName,
|
|
92
|
+
template: 'kanban',
|
|
93
|
+
workflowId: 'default',
|
|
94
|
+
updatedAt: now,
|
|
95
|
+
})
|
|
96
|
+
.onConflictDoUpdate({
|
|
97
|
+
target: pmoProjects.id,
|
|
98
|
+
set: {
|
|
99
|
+
name: projectName,
|
|
100
|
+
template: 'kanban',
|
|
101
|
+
workflowId: 'default',
|
|
102
|
+
updatedAt: now,
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
.run();
|
|
81
106
|
return this.getBoard(projectId);
|
|
82
107
|
}
|
|
83
108
|
/**
|
|
@@ -94,19 +119,27 @@ export class ProjectStorage {
|
|
|
94
119
|
throw new PMOError('NOT_FOUND', `Project not found: ${projectIdOrName}. Run init() first.`);
|
|
95
120
|
}
|
|
96
121
|
// Get project metadata with workflow using resolved ID
|
|
97
|
-
const projectRow = this.ctx.
|
|
98
|
-
|
|
99
|
-
|
|
122
|
+
const projectRow = this.ctx.drizzle
|
|
123
|
+
.select({
|
|
124
|
+
id: pmoProjects.id,
|
|
125
|
+
name: pmoProjects.name,
|
|
126
|
+
workflowId: pmoProjects.workflowId,
|
|
127
|
+
updatedAt: pmoProjects.updatedAt,
|
|
128
|
+
})
|
|
129
|
+
.from(pmoProjects)
|
|
130
|
+
.where(eq(pmoProjects.id, resolvedId))
|
|
131
|
+
.get();
|
|
100
132
|
if (!projectRow) {
|
|
101
133
|
throw new PMOError('NOT_FOUND', `Project not found: ${projectIdOrName}. Run init() first.`);
|
|
102
134
|
}
|
|
103
135
|
// Get workflow statuses as columns
|
|
104
|
-
const workflowId = projectRow.
|
|
105
|
-
const statusRows = this.ctx.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
136
|
+
const workflowId = projectRow.workflowId || 'default';
|
|
137
|
+
const statusRows = this.ctx.drizzle
|
|
138
|
+
.select()
|
|
139
|
+
.from(pmoWorkflowStatuses)
|
|
140
|
+
.where(eq(pmoWorkflowStatuses.workflowId, workflowId))
|
|
141
|
+
.orderBy(asc(pmoWorkflowStatuses.position))
|
|
142
|
+
.all();
|
|
110
143
|
// Build columns from statuses, with tickets sorted by priority then created_at
|
|
111
144
|
const columns = await Promise.all(statusRows.map(async (status) => ({
|
|
112
145
|
id: status.id,
|
|
@@ -119,7 +152,7 @@ export class ProjectStorage {
|
|
|
119
152
|
id: projectRow.id,
|
|
120
153
|
name: projectRow.name,
|
|
121
154
|
columns,
|
|
122
|
-
updatedAt: new Date(projectRow.
|
|
155
|
+
updatedAt: new Date(projectRow.updatedAt),
|
|
123
156
|
};
|
|
124
157
|
}
|
|
125
158
|
/**
|
|
@@ -138,17 +171,38 @@ export class ProjectStorage {
|
|
|
138
171
|
const workflowId = project.template || 'default';
|
|
139
172
|
const now = Date.now();
|
|
140
173
|
// Try to find a workflow with matching ID
|
|
141
|
-
const workflow = this.ctx.
|
|
142
|
-
|
|
143
|
-
|
|
174
|
+
const workflow = this.ctx.drizzle
|
|
175
|
+
.select({ id: pmoWorkflows.id })
|
|
176
|
+
.from(pmoWorkflows)
|
|
177
|
+
.where(eq(pmoWorkflows.id, workflowId))
|
|
178
|
+
.get();
|
|
144
179
|
// Use the requested workflow if it exists, otherwise fall back to default
|
|
145
180
|
const finalWorkflowId = workflow ? workflowId : 'default';
|
|
146
181
|
// Insert project with workflow
|
|
147
182
|
try {
|
|
148
|
-
this.ctx.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
183
|
+
this.ctx.drizzle
|
|
184
|
+
.insert(pmoProjects)
|
|
185
|
+
.values({
|
|
186
|
+
id,
|
|
187
|
+
name: project.name,
|
|
188
|
+
template: workflowId,
|
|
189
|
+
description: project.description || null,
|
|
190
|
+
workflowId: finalWorkflowId,
|
|
191
|
+
createdAt: String(now),
|
|
192
|
+
updatedAt: String(now),
|
|
193
|
+
})
|
|
194
|
+
.onConflictDoUpdate({
|
|
195
|
+
target: pmoProjects.id,
|
|
196
|
+
set: {
|
|
197
|
+
name: project.name,
|
|
198
|
+
template: workflowId,
|
|
199
|
+
description: project.description || null,
|
|
200
|
+
workflowId: finalWorkflowId,
|
|
201
|
+
createdAt: String(now),
|
|
202
|
+
updatedAt: String(now),
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
.run();
|
|
152
206
|
}
|
|
153
207
|
catch (err) {
|
|
154
208
|
wrapSqliteError('Project', 'create', err);
|
|
@@ -164,19 +218,29 @@ export class ProjectStorage {
|
|
|
164
218
|
if (!resolvedId) {
|
|
165
219
|
return null;
|
|
166
220
|
}
|
|
167
|
-
const projectRow = this.ctx.
|
|
168
|
-
|
|
169
|
-
|
|
221
|
+
const projectRow = this.ctx.drizzle
|
|
222
|
+
.select({
|
|
223
|
+
id: pmoProjects.id,
|
|
224
|
+
name: pmoProjects.name,
|
|
225
|
+
template: pmoProjects.template,
|
|
226
|
+
description: pmoProjects.description,
|
|
227
|
+
workflowId: pmoProjects.workflowId,
|
|
228
|
+
updatedAt: pmoProjects.updatedAt,
|
|
229
|
+
})
|
|
230
|
+
.from(pmoProjects)
|
|
231
|
+
.where(eq(pmoProjects.id, resolvedId))
|
|
232
|
+
.get();
|
|
170
233
|
if (!projectRow) {
|
|
171
234
|
return null;
|
|
172
235
|
}
|
|
173
236
|
// Get workflow statuses as columns
|
|
174
|
-
const workflowId = projectRow.
|
|
175
|
-
const statusRows = this.ctx.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
237
|
+
const workflowId = projectRow.workflowId || 'default';
|
|
238
|
+
const statusRows = this.ctx.drizzle
|
|
239
|
+
.select()
|
|
240
|
+
.from(pmoWorkflowStatuses)
|
|
241
|
+
.where(eq(pmoWorkflowStatuses.workflowId, workflowId))
|
|
242
|
+
.orderBy(asc(pmoWorkflowStatuses.position))
|
|
243
|
+
.all();
|
|
180
244
|
const columns = await Promise.all(statusRows.map(async (status) => ({
|
|
181
245
|
id: status.id,
|
|
182
246
|
name: status.name,
|
|
@@ -188,7 +252,7 @@ export class ProjectStorage {
|
|
|
188
252
|
id: projectRow.id,
|
|
189
253
|
name: projectRow.name,
|
|
190
254
|
columns,
|
|
191
|
-
updatedAt: new Date(projectRow.
|
|
255
|
+
updatedAt: new Date(projectRow.updatedAt),
|
|
192
256
|
};
|
|
193
257
|
}
|
|
194
258
|
/**
|
|
@@ -196,34 +260,60 @@ export class ProjectStorage {
|
|
|
196
260
|
* Tickets are sorted by position (force-ranked) then created_at as tiebreaker.
|
|
197
261
|
*/
|
|
198
262
|
async getTicketsForStatus(statusId, projectId) {
|
|
199
|
-
const ticketRows = this.ctx.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
263
|
+
const ticketRows = this.ctx.drizzle
|
|
264
|
+
.select({
|
|
265
|
+
id: pmoTickets.id,
|
|
266
|
+
project_id: pmoTickets.projectId,
|
|
267
|
+
title: pmoTickets.title,
|
|
268
|
+
description: pmoTickets.description,
|
|
269
|
+
priority: pmoTickets.priority,
|
|
270
|
+
category: pmoTickets.category,
|
|
271
|
+
status_id: pmoTickets.statusId,
|
|
272
|
+
owner: pmoTickets.owner,
|
|
273
|
+
assignee: pmoTickets.assignee,
|
|
274
|
+
branch: pmoTickets.branch,
|
|
275
|
+
spec_id: pmoTickets.specId,
|
|
276
|
+
epic_id: pmoTickets.epicId,
|
|
277
|
+
labels: pmoTickets.labels,
|
|
278
|
+
position: pmoTickets.position,
|
|
279
|
+
created_at: pmoTickets.createdAt,
|
|
280
|
+
updated_at: pmoTickets.updatedAt,
|
|
281
|
+
last_synced_from_spec: pmoTickets.lastSyncedFromSpec,
|
|
282
|
+
last_synced_from_board: pmoTickets.lastSyncedFromBoard,
|
|
283
|
+
column_id: sql `NULL`,
|
|
284
|
+
column_name: sql `NULL`,
|
|
285
|
+
project_name: sql `NULL`,
|
|
286
|
+
})
|
|
287
|
+
.from(pmoTickets)
|
|
288
|
+
.leftJoin(pmoWorkflowStatuses, eq(pmoTickets.statusId, pmoWorkflowStatuses.id))
|
|
289
|
+
.where(and(eq(pmoTickets.statusId, statusId), eq(pmoTickets.projectId, projectId)))
|
|
290
|
+
.orderBy(asc(pmoTickets.position), asc(pmoTickets.createdAt))
|
|
291
|
+
.all();
|
|
292
|
+
return Promise.all(ticketRows.map((row) => rowToTicket(this.ctx.drizzle, row)));
|
|
209
293
|
}
|
|
210
294
|
/**
|
|
211
295
|
* List project summaries.
|
|
212
296
|
*/
|
|
213
297
|
async listProjectSummaries() {
|
|
214
|
-
const projects = this.ctx.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
298
|
+
const projects = this.ctx.drizzle
|
|
299
|
+
.select({
|
|
300
|
+
id: pmoProjects.id,
|
|
301
|
+
name: pmoProjects.name,
|
|
302
|
+
template: pmoProjects.template,
|
|
303
|
+
description: pmoProjects.description,
|
|
304
|
+
ticketCount: count(pmoTickets.id),
|
|
305
|
+
})
|
|
306
|
+
.from(pmoProjects)
|
|
307
|
+
.leftJoin(pmoTickets, eq(pmoProjects.id, pmoTickets.projectId))
|
|
308
|
+
.groupBy(pmoProjects.id)
|
|
309
|
+
.orderBy(asc(pmoProjects.createdAt))
|
|
310
|
+
.all();
|
|
221
311
|
return projects.map((p) => ({
|
|
222
312
|
id: p.id,
|
|
223
313
|
name: p.name,
|
|
224
314
|
template: p.template,
|
|
225
315
|
description: p.description,
|
|
226
|
-
ticketCount: p.
|
|
316
|
+
ticketCount: p.ticketCount,
|
|
227
317
|
}));
|
|
228
318
|
}
|
|
229
319
|
/**
|
|
@@ -239,7 +329,10 @@ export class ProjectStorage {
|
|
|
239
329
|
throw new PMOError('INVALID', 'Cannot delete the default project');
|
|
240
330
|
}
|
|
241
331
|
try {
|
|
242
|
-
const result = this.ctx.
|
|
332
|
+
const result = this.ctx.drizzle
|
|
333
|
+
.delete(pmoProjects)
|
|
334
|
+
.where(eq(pmoProjects.id, resolvedId))
|
|
335
|
+
.run();
|
|
243
336
|
if (result.changes === 0) {
|
|
244
337
|
throw new PMOError('NOT_FOUND', `Project not found: ${projectIdOrName}`);
|
|
245
338
|
}
|
|
@@ -259,7 +352,11 @@ export class ProjectStorage {
|
|
|
259
352
|
const resolvedId = this.resolveProjectId(idOrName);
|
|
260
353
|
if (!resolvedId)
|
|
261
354
|
return null;
|
|
262
|
-
const row = this.ctx.
|
|
355
|
+
const row = this.ctx.drizzle
|
|
356
|
+
.select()
|
|
357
|
+
.from(pmoProjects)
|
|
358
|
+
.where(eq(pmoProjects.id, resolvedId))
|
|
359
|
+
.get();
|
|
263
360
|
if (!row)
|
|
264
361
|
return null;
|
|
265
362
|
return this.rowToProject(row);
|
|
@@ -274,68 +371,59 @@ export class ProjectStorage {
|
|
|
274
371
|
}
|
|
275
372
|
// Use the resolved ID for the update
|
|
276
373
|
const resolvedId = existing.id;
|
|
277
|
-
const updates =
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
updates.push('phase_id = ?');
|
|
293
|
-
params.push(changes.phaseId || null);
|
|
294
|
-
}
|
|
295
|
-
if (changes.workflowId !== undefined) {
|
|
296
|
-
updates.push('workflow_id = ?');
|
|
297
|
-
params.push(changes.workflowId || null);
|
|
298
|
-
}
|
|
299
|
-
if (changes.isArchived !== undefined) {
|
|
300
|
-
updates.push('is_archived = ?');
|
|
301
|
-
params.push(changes.isArchived ? 1 : 0);
|
|
302
|
-
}
|
|
374
|
+
const updates = {
|
|
375
|
+
updatedAt: String(Date.now()),
|
|
376
|
+
};
|
|
377
|
+
if (changes.name !== undefined)
|
|
378
|
+
updates.name = changes.name;
|
|
379
|
+
if (changes.description !== undefined)
|
|
380
|
+
updates.description = changes.description || null;
|
|
381
|
+
if (changes.status !== undefined)
|
|
382
|
+
updates.status = changes.status;
|
|
383
|
+
if (changes.phaseId !== undefined)
|
|
384
|
+
updates.phaseId = changes.phaseId || null;
|
|
385
|
+
if (changes.workflowId !== undefined)
|
|
386
|
+
updates.workflowId = changes.workflowId || null;
|
|
387
|
+
if (changes.isArchived !== undefined)
|
|
388
|
+
updates.isArchived = changes.isArchived;
|
|
303
389
|
if (changes.targetDate !== undefined) {
|
|
304
|
-
updates.
|
|
305
|
-
params.push(changes.targetDate ? changes.targetDate.toISOString() : null);
|
|
390
|
+
updates.targetDate = changes.targetDate ? changes.targetDate.toISOString() : null;
|
|
306
391
|
}
|
|
307
|
-
|
|
308
|
-
|
|
392
|
+
this.ctx.drizzle
|
|
393
|
+
.update(pmoProjects)
|
|
394
|
+
.set(updates)
|
|
395
|
+
.where(eq(pmoProjects.id, resolvedId))
|
|
396
|
+
.run();
|
|
309
397
|
return (await this.getProject(resolvedId));
|
|
310
398
|
}
|
|
311
399
|
/**
|
|
312
400
|
* List projects with optional filter.
|
|
313
401
|
*/
|
|
314
402
|
async listProjects(filter) {
|
|
315
|
-
let
|
|
403
|
+
let query = this.ctx.drizzle
|
|
404
|
+
.select()
|
|
405
|
+
.from(pmoProjects)
|
|
406
|
+
.$dynamic();
|
|
316
407
|
const conditions = [];
|
|
317
|
-
const params = [];
|
|
318
408
|
// Filter by archived status if explicitly specified
|
|
319
409
|
if (filter?.isArchived === true) {
|
|
320
|
-
conditions.push(
|
|
410
|
+
conditions.push(eq(pmoProjects.isArchived, true));
|
|
321
411
|
}
|
|
322
412
|
else if (filter?.isArchived === false) {
|
|
323
|
-
conditions.push(
|
|
413
|
+
conditions.push(eq(pmoProjects.isArchived, false));
|
|
324
414
|
}
|
|
325
415
|
if (filter?.phaseId) {
|
|
326
|
-
conditions.push(
|
|
327
|
-
params.push(filter.phaseId);
|
|
416
|
+
conditions.push(eq(pmoProjects.phaseId, filter.phaseId));
|
|
328
417
|
}
|
|
329
418
|
if (filter?.search) {
|
|
330
|
-
conditions.push(
|
|
331
|
-
const searchTerm = `%${filter.search}%`;
|
|
332
|
-
params.push(searchTerm, searchTerm);
|
|
419
|
+
conditions.push(or(like(pmoProjects.name, `%${filter.search}%`), like(pmoProjects.description, `%${filter.search}%`)));
|
|
333
420
|
}
|
|
334
421
|
if (conditions.length > 0) {
|
|
335
|
-
|
|
422
|
+
query = query.where(and(...conditions));
|
|
336
423
|
}
|
|
337
|
-
|
|
338
|
-
|
|
424
|
+
const rows = query
|
|
425
|
+
.orderBy(desc(pmoProjects.updatedAt))
|
|
426
|
+
.all();
|
|
339
427
|
return rows.map((row) => this.rowToProject(row));
|
|
340
428
|
}
|
|
341
429
|
/**
|
|
@@ -371,13 +459,13 @@ export class ProjectStorage {
|
|
|
371
459
|
template: row.template || undefined,
|
|
372
460
|
description: row.description || undefined,
|
|
373
461
|
status: (row.status || 'active'),
|
|
374
|
-
phaseId: row.
|
|
375
|
-
workflowId: row.
|
|
376
|
-
isArchived: row.
|
|
377
|
-
targetDate: row.
|
|
378
|
-
initiativeId: row.
|
|
379
|
-
createdAt: new Date(row.
|
|
380
|
-
updatedAt: new Date(row.
|
|
462
|
+
phaseId: row.phaseId || undefined,
|
|
463
|
+
workflowId: row.workflowId || undefined,
|
|
464
|
+
isArchived: row.isArchived ?? false,
|
|
465
|
+
targetDate: row.targetDate ? new Date(row.targetDate) : undefined,
|
|
466
|
+
initiativeId: row.initiativeId || undefined,
|
|
467
|
+
createdAt: new Date(row.createdAt || Date.now()),
|
|
468
|
+
updatedAt: new Date(row.updatedAt || Date.now()),
|
|
381
469
|
};
|
|
382
470
|
}
|
|
383
471
|
}
|