@proletariat/cli 0.3.9 → 0.3.11
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 +25 -0
- package/bin/dev.js +0 -0
- package/dist/commands/action/index.js +1 -1
- package/dist/commands/action/run.js +8 -12
- package/dist/commands/agent/auth.d.ts +30 -0
- package/dist/commands/agent/auth.js +172 -0
- package/dist/commands/agent/discover.d.ts +9 -0
- package/dist/commands/agent/discover.js +67 -0
- package/dist/commands/agent/index.js +47 -12
- package/dist/commands/agent/list.d.ts +4 -1
- package/dist/commands/agent/list.js +78 -16
- package/dist/commands/agent/login.js +35 -31
- package/dist/commands/agent/restart.js +2 -0
- package/dist/commands/agent/shell.js +78 -19
- package/dist/commands/agent/staff/add.js +1 -12
- package/dist/commands/agent/staff/remove.js +9 -7
- package/dist/commands/agent/status.js +17 -4
- package/dist/commands/agent/temp/cleanup.js +7 -3
- package/dist/commands/agent/themes/index.js +4 -5
- package/dist/commands/agent/themes/list.js +5 -5
- package/dist/commands/agent/visit.js +17 -4
- package/dist/commands/branch/create.d.ts +4 -0
- package/dist/commands/branch/create.js +16 -8
- package/dist/commands/branch/index.js +1 -1
- package/dist/commands/branch/where.js +1 -0
- package/dist/commands/claude.d.ts +38 -0
- package/dist/commands/claude.js +899 -0
- package/dist/commands/commit.js +1 -1
- package/dist/commands/config/index.d.ts +12 -0
- package/dist/commands/config/index.js +271 -0
- package/dist/commands/docker/clean.js +2 -2
- package/dist/commands/docker/index.js +2 -2
- package/dist/commands/docker/list.js +3 -8
- package/dist/commands/docker/logs.js +2 -2
- package/dist/commands/docker/prune.js +1 -1
- package/dist/commands/docker/restart.js +2 -2
- package/dist/commands/docker/shell.js +2 -2
- package/dist/commands/docker/start.js +2 -2
- package/dist/commands/docker/status.js +1 -1
- package/dist/commands/docker/stop.js +2 -2
- package/dist/commands/docker/sync.js +2 -2
- package/dist/commands/epic/index.js +1 -1
- package/dist/commands/epic/link/index.js +25 -14
- package/dist/commands/epic/link/remove.js +2 -0
- package/dist/commands/epic/list.js +5 -5
- package/dist/commands/epic/progress.js +10 -4
- package/dist/commands/epic/spec.js +2 -0
- package/dist/commands/epic/ticket.js +3 -0
- package/dist/commands/execution/stop.js +1 -0
- package/dist/commands/init.js +4 -4
- package/dist/commands/project/index.js +1 -1
- package/dist/commands/project/spec.js +7 -0
- package/dist/commands/repo/add.js +1 -0
- package/dist/commands/repo/remove.js +1 -0
- package/dist/commands/roadmap/add-project.d.ts +18 -0
- package/dist/commands/roadmap/add-project.js +135 -0
- package/dist/commands/roadmap/create.d.ts +22 -0
- package/dist/commands/roadmap/create.js +156 -0
- package/dist/commands/roadmap/delete.d.ts +17 -0
- package/dist/commands/roadmap/delete.js +104 -0
- package/dist/commands/roadmap/generate.d.ts +22 -0
- package/dist/commands/roadmap/generate.js +201 -0
- package/dist/commands/roadmap/index.d.ts +13 -0
- package/dist/commands/roadmap/index.js +61 -0
- package/dist/commands/roadmap/list.d.ts +12 -0
- package/dist/commands/roadmap/list.js +42 -0
- package/dist/commands/roadmap/remove-project.d.ts +18 -0
- package/dist/commands/roadmap/remove-project.js +147 -0
- package/dist/commands/roadmap/reorder.d.ts +17 -0
- package/dist/commands/roadmap/reorder.js +157 -0
- package/dist/commands/roadmap/update.d.ts +19 -0
- package/dist/commands/roadmap/update.js +136 -0
- package/dist/commands/roadmap/view.d.ts +16 -0
- package/dist/commands/roadmap/view.js +103 -0
- package/dist/commands/spec/index.js +1 -1
- package/dist/commands/spec/link/index.js +24 -13
- package/dist/commands/spec/link/remove.js +2 -0
- package/dist/commands/status/index.js +1 -1
- package/dist/commands/status/list.js +0 -8
- package/dist/commands/template/delete.js +2 -0
- package/dist/commands/terminal/title.d.ts +12 -0
- package/dist/commands/terminal/title.js +48 -0
- package/dist/commands/ticket/complete.js +2 -0
- package/dist/commands/ticket/create.js +4 -2
- package/dist/commands/ticket/delete.js +2 -0
- package/dist/commands/ticket/edit.js +8 -2
- package/dist/commands/ticket/link/index.js +17 -3
- package/dist/commands/ticket/link/remove.js +2 -0
- package/dist/commands/ticket/list.js +1 -2
- package/dist/commands/ticket/move.js +2 -0
- package/dist/commands/ticket/project.js +3 -1
- package/dist/commands/ticket/reassign.js +2 -0
- package/dist/commands/ticket/spec.js +4 -2
- package/dist/commands/ticket/template/apply.js +4 -3
- package/dist/commands/ticket/template/create.js +2 -0
- package/dist/commands/ticket/template/index.js +1 -1
- package/dist/commands/ticket/update.js +2 -0
- package/dist/commands/work/index.js +1 -1
- package/dist/commands/work/revise.js +7 -1
- package/dist/commands/work/spawn.d.ts +2 -1
- package/dist/commands/work/spawn.js +131 -36
- package/dist/commands/work/start.d.ts +2 -1
- package/dist/commands/work/start.js +349 -69
- package/dist/commands/work/watch.js +10 -2
- package/dist/commands/workflow/create.js +3 -3
- package/dist/commands/workflow/switch.js +2 -1
- package/dist/commands/workspace/remove.js +0 -8
- package/dist/commands/workspace/use.js +1 -9
- package/dist/lib/agents/commands.js +18 -13
- package/dist/lib/database/index.d.ts +19 -12
- package/dist/lib/database/index.js +158 -42
- package/dist/lib/docker/resolve.js +1 -1
- package/dist/lib/execution/config.d.ts +6 -0
- package/dist/lib/execution/config.js +15 -2
- package/dist/lib/execution/devcontainer.d.ts +2 -0
- package/dist/lib/execution/devcontainer.js +41 -9
- package/dist/lib/execution/runners.d.ts +85 -3
- package/dist/lib/execution/runners.js +925 -228
- package/dist/lib/execution/spawner.d.ts +2 -2
- package/dist/lib/execution/spawner.js +4 -3
- package/dist/lib/execution/storage.d.ts +2 -1
- package/dist/lib/execution/storage.js +9 -13
- package/dist/lib/execution/types.d.ts +10 -1
- package/dist/lib/execution/types.js +3 -1
- package/dist/lib/init/index.js +1 -0
- package/dist/lib/machine-config.js +1 -1
- package/dist/lib/pmo/base-command.js +5 -9
- package/dist/lib/pmo/index.js +2 -0
- package/dist/lib/pmo/schema.d.ts +6 -0
- package/dist/lib/pmo/schema.js +36 -0
- package/dist/lib/pmo/storage/base.js +3 -3
- package/dist/lib/pmo/storage/index.d.ts +16 -1
- package/dist/lib/pmo/storage/index.js +45 -0
- package/dist/lib/pmo/storage/roadmaps.d.ts +62 -0
- package/dist/lib/pmo/storage/roadmaps.js +301 -0
- package/dist/lib/pmo/storage/specs.js +2 -0
- package/dist/lib/pmo/storage/types.d.ts +14 -0
- package/dist/lib/pmo/sync-manager.d.ts +1 -1
- package/dist/lib/pmo/sync-manager.js +1 -1
- package/dist/lib/pmo/types.d.ts +41 -0
- package/dist/lib/pmo/utils.d.ts +2 -0
- package/dist/lib/pmo/utils.js +22 -1
- package/dist/lib/repos/index.js +7 -1
- package/dist/lib/terminal.d.ts +31 -0
- package/dist/lib/terminal.js +48 -0
- package/dist/lib/themes.d.ts +21 -3
- package/dist/lib/themes.js +80 -23
- package/dist/lib/workspace-config.d.ts +80 -0
- package/dist/lib/workspace-config.js +100 -0
- package/oclif.manifest.json +4065 -3225
- package/package.json +10 -6
- package/LICENSE +0 -21
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roadmap operations for PMO.
|
|
3
|
+
* Roadmaps are curated collections of projects for documentation/visualization.
|
|
4
|
+
*/
|
|
5
|
+
import { PMO_TABLES } from '../schema.js';
|
|
6
|
+
import { PMOError } from '../types.js';
|
|
7
|
+
const T = PMO_TABLES;
|
|
8
|
+
export class RoadmapStorage {
|
|
9
|
+
ctx;
|
|
10
|
+
constructor(ctx) {
|
|
11
|
+
this.ctx = ctx;
|
|
12
|
+
}
|
|
13
|
+
// ===== Roadmap CRUD =====
|
|
14
|
+
/**
|
|
15
|
+
* Create a new roadmap.
|
|
16
|
+
*/
|
|
17
|
+
async createRoadmap(roadmap) {
|
|
18
|
+
const id = roadmap.id || `roadmap-${Date.now()}`;
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
// If setting as default, unset other defaults first
|
|
21
|
+
if (roadmap.isDefault) {
|
|
22
|
+
this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET is_default = 0`).run();
|
|
23
|
+
}
|
|
24
|
+
this.ctx.db.prepare(`
|
|
25
|
+
INSERT INTO ${T.roadmaps} (id, name, description, is_default, created_at, updated_at)
|
|
26
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
27
|
+
`).run(id, roadmap.name, roadmap.description || null, roadmap.isDefault ? 1 : 0, now, now);
|
|
28
|
+
return {
|
|
29
|
+
id,
|
|
30
|
+
name: roadmap.name,
|
|
31
|
+
description: roadmap.description,
|
|
32
|
+
isDefault: roadmap.isDefault || false,
|
|
33
|
+
createdAt: new Date(now),
|
|
34
|
+
updatedAt: new Date(now),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get a roadmap by ID.
|
|
39
|
+
*/
|
|
40
|
+
async getRoadmap(id) {
|
|
41
|
+
const row = this.ctx.db.prepare(`
|
|
42
|
+
SELECT * FROM ${T.roadmaps} WHERE id = ?
|
|
43
|
+
`).get(id);
|
|
44
|
+
if (!row)
|
|
45
|
+
return null;
|
|
46
|
+
return this.rowToRoadmap(row);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* List roadmaps with optional filters.
|
|
50
|
+
*/
|
|
51
|
+
async listRoadmaps(filter) {
|
|
52
|
+
let query = `SELECT * FROM ${T.roadmaps} WHERE 1=1`;
|
|
53
|
+
const params = [];
|
|
54
|
+
if (filter?.search) {
|
|
55
|
+
query += ' AND (name LIKE ? OR description LIKE ?)';
|
|
56
|
+
params.push(`%${filter.search}%`, `%${filter.search}%`);
|
|
57
|
+
}
|
|
58
|
+
if (filter?.isDefault !== undefined) {
|
|
59
|
+
query += ' AND is_default = ?';
|
|
60
|
+
params.push(filter.isDefault ? 1 : 0);
|
|
61
|
+
}
|
|
62
|
+
query += ' ORDER BY is_default DESC, name ASC';
|
|
63
|
+
const rows = this.ctx.db.prepare(query).all(...params);
|
|
64
|
+
return rows.map(row => this.rowToRoadmap(row));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Update a roadmap.
|
|
68
|
+
*/
|
|
69
|
+
async updateRoadmap(id, changes) {
|
|
70
|
+
const roadmap = await this.getRoadmap(id);
|
|
71
|
+
if (!roadmap) {
|
|
72
|
+
throw new PMOError('NOT_FOUND', `Roadmap not found: ${id}`);
|
|
73
|
+
}
|
|
74
|
+
const updates = [];
|
|
75
|
+
const params = [];
|
|
76
|
+
if (changes.name !== undefined) {
|
|
77
|
+
updates.push('name = ?');
|
|
78
|
+
params.push(changes.name);
|
|
79
|
+
}
|
|
80
|
+
if (changes.description !== undefined) {
|
|
81
|
+
updates.push('description = ?');
|
|
82
|
+
params.push(changes.description || null);
|
|
83
|
+
}
|
|
84
|
+
if (changes.isDefault !== undefined) {
|
|
85
|
+
// If setting as default, unset other defaults first
|
|
86
|
+
if (changes.isDefault) {
|
|
87
|
+
this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET is_default = 0`).run();
|
|
88
|
+
}
|
|
89
|
+
updates.push('is_default = ?');
|
|
90
|
+
params.push(changes.isDefault ? 1 : 0);
|
|
91
|
+
}
|
|
92
|
+
if (updates.length > 0) {
|
|
93
|
+
updates.push('updated_at = ?');
|
|
94
|
+
params.push(Date.now());
|
|
95
|
+
params.push(id);
|
|
96
|
+
this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET ${updates.join(', ')} WHERE id = ?`).run(...params);
|
|
97
|
+
}
|
|
98
|
+
return (await this.getRoadmap(id));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Delete a roadmap.
|
|
102
|
+
*/
|
|
103
|
+
async deleteRoadmap(id) {
|
|
104
|
+
const roadmap = await this.getRoadmap(id);
|
|
105
|
+
if (!roadmap) {
|
|
106
|
+
throw new PMOError('NOT_FOUND', `Roadmap not found: ${id}`);
|
|
107
|
+
}
|
|
108
|
+
// Delete roadmap (cascades to roadmap_projects)
|
|
109
|
+
this.ctx.db.prepare(`DELETE FROM ${T.roadmaps} WHERE id = ?`).run(id);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get the default roadmap.
|
|
113
|
+
*/
|
|
114
|
+
async getDefaultRoadmap() {
|
|
115
|
+
const row = this.ctx.db.prepare(`
|
|
116
|
+
SELECT * FROM ${T.roadmaps} WHERE is_default = 1 LIMIT 1
|
|
117
|
+
`).get();
|
|
118
|
+
if (!row)
|
|
119
|
+
return null;
|
|
120
|
+
return this.rowToRoadmap(row);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Set a roadmap as the default.
|
|
124
|
+
*/
|
|
125
|
+
async setDefaultRoadmap(id) {
|
|
126
|
+
return this.updateRoadmap(id, { isDefault: true });
|
|
127
|
+
}
|
|
128
|
+
// ===== Roadmap Projects =====
|
|
129
|
+
/**
|
|
130
|
+
* List projects in a roadmap, ordered by position.
|
|
131
|
+
*/
|
|
132
|
+
async listRoadmapProjects(roadmapId) {
|
|
133
|
+
const rows = this.ctx.db.prepare(`
|
|
134
|
+
SELECT p.* FROM ${T.projects} p
|
|
135
|
+
JOIN ${T.roadmap_projects} rp ON p.id = rp.project_id
|
|
136
|
+
WHERE rp.roadmap_id = ?
|
|
137
|
+
ORDER BY rp.position ASC
|
|
138
|
+
`).all(roadmapId);
|
|
139
|
+
return rows.map(row => this.rowToProject(row));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Add a project to a roadmap.
|
|
143
|
+
*/
|
|
144
|
+
async addProjectToRoadmap(roadmapId, projectId, position) {
|
|
145
|
+
// Verify roadmap exists
|
|
146
|
+
const roadmap = await this.getRoadmap(roadmapId);
|
|
147
|
+
if (!roadmap) {
|
|
148
|
+
throw new PMOError('NOT_FOUND', `Roadmap not found: ${roadmapId}`);
|
|
149
|
+
}
|
|
150
|
+
// Check if project is already in roadmap
|
|
151
|
+
const existing = this.ctx.db.prepare(`
|
|
152
|
+
SELECT * FROM ${T.roadmap_projects} WHERE roadmap_id = ? AND project_id = ?
|
|
153
|
+
`).get(roadmapId, projectId);
|
|
154
|
+
if (existing) {
|
|
155
|
+
throw new PMOError('CONFLICT', `Project ${projectId} is already in roadmap ${roadmapId}`);
|
|
156
|
+
}
|
|
157
|
+
// Get next position if not provided
|
|
158
|
+
if (position === undefined) {
|
|
159
|
+
const maxPos = this.ctx.db.prepare(`
|
|
160
|
+
SELECT COALESCE(MAX(position), -1) as max_pos FROM ${T.roadmap_projects} WHERE roadmap_id = ?
|
|
161
|
+
`).get(roadmapId);
|
|
162
|
+
position = maxPos.max_pos + 1;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Shift existing projects at or after this position
|
|
166
|
+
this.ctx.db.prepare(`
|
|
167
|
+
UPDATE ${T.roadmap_projects}
|
|
168
|
+
SET position = position + 1
|
|
169
|
+
WHERE roadmap_id = ? AND position >= ?
|
|
170
|
+
`).run(roadmapId, position);
|
|
171
|
+
}
|
|
172
|
+
const now = Date.now();
|
|
173
|
+
this.ctx.db.prepare(`
|
|
174
|
+
INSERT INTO ${T.roadmap_projects} (roadmap_id, project_id, position, created_at)
|
|
175
|
+
VALUES (?, ?, ?, ?)
|
|
176
|
+
`).run(roadmapId, projectId, position, now);
|
|
177
|
+
// Update roadmap timestamp
|
|
178
|
+
this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET updated_at = ? WHERE id = ?`).run(now, roadmapId);
|
|
179
|
+
return {
|
|
180
|
+
roadmapId,
|
|
181
|
+
projectId,
|
|
182
|
+
position,
|
|
183
|
+
createdAt: new Date(now),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Remove a project from a roadmap.
|
|
188
|
+
*/
|
|
189
|
+
async removeProjectFromRoadmap(roadmapId, projectId) {
|
|
190
|
+
// Get current position for reordering
|
|
191
|
+
const current = this.ctx.db.prepare(`
|
|
192
|
+
SELECT position FROM ${T.roadmap_projects} WHERE roadmap_id = ? AND project_id = ?
|
|
193
|
+
`).get(roadmapId, projectId);
|
|
194
|
+
if (!current) {
|
|
195
|
+
throw new PMOError('NOT_FOUND', `Project ${projectId} not in roadmap ${roadmapId}`);
|
|
196
|
+
}
|
|
197
|
+
// Delete the association
|
|
198
|
+
this.ctx.db.prepare(`
|
|
199
|
+
DELETE FROM ${T.roadmap_projects} WHERE roadmap_id = ? AND project_id = ?
|
|
200
|
+
`).run(roadmapId, projectId);
|
|
201
|
+
// Reorder remaining projects to fill the gap
|
|
202
|
+
this.ctx.db.prepare(`
|
|
203
|
+
UPDATE ${T.roadmap_projects}
|
|
204
|
+
SET position = position - 1
|
|
205
|
+
WHERE roadmap_id = ? AND position > ?
|
|
206
|
+
`).run(roadmapId, current.position);
|
|
207
|
+
// Update roadmap timestamp
|
|
208
|
+
this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET updated_at = ? WHERE id = ?`).run(Date.now(), roadmapId);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Reorder a project within a roadmap.
|
|
212
|
+
*/
|
|
213
|
+
async reorderRoadmapProject(roadmapId, projectId, newPosition) {
|
|
214
|
+
// Get current position
|
|
215
|
+
const current = this.ctx.db.prepare(`
|
|
216
|
+
SELECT position, created_at FROM ${T.roadmap_projects} WHERE roadmap_id = ? AND project_id = ?
|
|
217
|
+
`).get(roadmapId, projectId);
|
|
218
|
+
if (!current) {
|
|
219
|
+
throw new PMOError('NOT_FOUND', `Project ${projectId} not in roadmap ${roadmapId}`);
|
|
220
|
+
}
|
|
221
|
+
const oldPosition = current.position;
|
|
222
|
+
if (oldPosition === newPosition) {
|
|
223
|
+
return {
|
|
224
|
+
roadmapId,
|
|
225
|
+
projectId,
|
|
226
|
+
position: newPosition,
|
|
227
|
+
createdAt: new Date(current.created_at),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
// Shift positions (same pattern as EpicStorage.reorderEpic)
|
|
231
|
+
if (newPosition < oldPosition) {
|
|
232
|
+
// Moving up: increment positions in between
|
|
233
|
+
this.ctx.db.prepare(`
|
|
234
|
+
UPDATE ${T.roadmap_projects}
|
|
235
|
+
SET position = position + 1
|
|
236
|
+
WHERE roadmap_id = ? AND position >= ? AND position < ?
|
|
237
|
+
`).run(roadmapId, newPosition, oldPosition);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// Moving down: decrement positions in between
|
|
241
|
+
this.ctx.db.prepare(`
|
|
242
|
+
UPDATE ${T.roadmap_projects}
|
|
243
|
+
SET position = position - 1
|
|
244
|
+
WHERE roadmap_id = ? AND position > ? AND position <= ?
|
|
245
|
+
`).run(roadmapId, oldPosition, newPosition);
|
|
246
|
+
}
|
|
247
|
+
// Update the target project's position
|
|
248
|
+
this.ctx.db.prepare(`
|
|
249
|
+
UPDATE ${T.roadmap_projects}
|
|
250
|
+
SET position = ?
|
|
251
|
+
WHERE roadmap_id = ? AND project_id = ?
|
|
252
|
+
`).run(newPosition, roadmapId, projectId);
|
|
253
|
+
// Update roadmap timestamp
|
|
254
|
+
this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET updated_at = ? WHERE id = ?`).run(Date.now(), roadmapId);
|
|
255
|
+
return {
|
|
256
|
+
roadmapId,
|
|
257
|
+
projectId,
|
|
258
|
+
position: newPosition,
|
|
259
|
+
createdAt: new Date(current.created_at),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Get all roadmaps that contain a project.
|
|
264
|
+
*/
|
|
265
|
+
async getRoadmapsForProject(projectId) {
|
|
266
|
+
const rows = this.ctx.db.prepare(`
|
|
267
|
+
SELECT r.* FROM ${T.roadmaps} r
|
|
268
|
+
JOIN ${T.roadmap_projects} rp ON r.id = rp.roadmap_id
|
|
269
|
+
WHERE rp.project_id = ?
|
|
270
|
+
ORDER BY r.name ASC
|
|
271
|
+
`).all(projectId);
|
|
272
|
+
return rows.map(row => this.rowToRoadmap(row));
|
|
273
|
+
}
|
|
274
|
+
// ===== Helpers =====
|
|
275
|
+
rowToRoadmap(row) {
|
|
276
|
+
return {
|
|
277
|
+
id: row.id,
|
|
278
|
+
name: row.name,
|
|
279
|
+
description: row.description || undefined,
|
|
280
|
+
isDefault: row.is_default === 1,
|
|
281
|
+
createdAt: new Date(row.created_at),
|
|
282
|
+
updatedAt: new Date(row.updated_at),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
rowToProject(row) {
|
|
286
|
+
return {
|
|
287
|
+
id: row.id,
|
|
288
|
+
name: row.name,
|
|
289
|
+
template: row.template || undefined,
|
|
290
|
+
description: row.description || undefined,
|
|
291
|
+
status: (row.status || 'active'),
|
|
292
|
+
phaseId: row.phase_id || undefined,
|
|
293
|
+
workflowId: row.workflow_id || undefined,
|
|
294
|
+
isArchived: row.is_archived === 1,
|
|
295
|
+
targetDate: row.target_date ? new Date(row.target_date) : undefined,
|
|
296
|
+
initiativeId: row.initiative_id || undefined,
|
|
297
|
+
createdAt: new Date(row.created_at),
|
|
298
|
+
updatedAt: new Date(row.updated_at),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -294,6 +294,7 @@ export class SpecStorage {
|
|
|
294
294
|
`).all(specId);
|
|
295
295
|
const specs = [];
|
|
296
296
|
for (const row of rows) {
|
|
297
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential lookup for relationship chain
|
|
297
298
|
const spec = await this.getSpec(row.depends_on);
|
|
298
299
|
if (spec)
|
|
299
300
|
specs.push(spec);
|
|
@@ -309,6 +310,7 @@ export class SpecStorage {
|
|
|
309
310
|
`).all(specId);
|
|
310
311
|
const specs = [];
|
|
311
312
|
for (const row of rows) {
|
|
313
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential lookup for relationship chain
|
|
312
314
|
const spec = await this.getSpec(row.spec_id);
|
|
313
315
|
if (spec)
|
|
314
316
|
specs.push(spec);
|
|
@@ -198,3 +198,17 @@ export interface TicketTemplateRow {
|
|
|
198
198
|
is_builtin: number;
|
|
199
199
|
created_at: string;
|
|
200
200
|
}
|
|
201
|
+
export interface RoadmapRow {
|
|
202
|
+
id: string;
|
|
203
|
+
name: string;
|
|
204
|
+
description: string | null;
|
|
205
|
+
is_default: number;
|
|
206
|
+
created_at: string;
|
|
207
|
+
updated_at: string;
|
|
208
|
+
}
|
|
209
|
+
export interface RoadmapProjectRow {
|
|
210
|
+
roadmap_id: string;
|
|
211
|
+
project_id: string;
|
|
212
|
+
position: number;
|
|
213
|
+
created_at: string;
|
|
214
|
+
}
|
|
@@ -85,7 +85,7 @@ export declare function getWorkspaceDbPath(pmoPath: string): string;
|
|
|
85
85
|
* - 'git' mode enables multi-machine sync via git push/pull
|
|
86
86
|
* - 'sqlite' mode is local-only (no multi-machine sync)
|
|
87
87
|
*/
|
|
88
|
-
export declare function getStorageWithAutoSync(pmoPath: string,
|
|
88
|
+
export declare function getStorageWithAutoSync(pmoPath: string, _storageType: 'sqlite' | 'git', _logger?: (msg: string) => void): SQLiteStorage;
|
|
89
89
|
/**
|
|
90
90
|
* Wrapper for write operations that auto-exports to board.md after
|
|
91
91
|
*/
|
|
@@ -207,7 +207,7 @@ export function getWorkspaceDbPath(pmoPath) {
|
|
|
207
207
|
* - 'git' mode enables multi-machine sync via git push/pull
|
|
208
208
|
* - 'sqlite' mode is local-only (no multi-machine sync)
|
|
209
209
|
*/
|
|
210
|
-
export function getStorageWithAutoSync(pmoPath,
|
|
210
|
+
export function getStorageWithAutoSync(pmoPath, _storageType, _logger) {
|
|
211
211
|
// Storage is always workspace.db (unified PMO tables with foreign keys to agents)
|
|
212
212
|
const dbPath = getWorkspaceDbPath(pmoPath);
|
|
213
213
|
if (!fs.existsSync(dbPath)) {
|
package/dist/lib/pmo/types.d.ts
CHANGED
|
@@ -217,6 +217,29 @@ export interface PhaseTemplatePhase {
|
|
|
217
217
|
description?: string;
|
|
218
218
|
isDefault?: boolean;
|
|
219
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Roadmap - a curated collection of projects for documentation/visualization.
|
|
222
|
+
* Roadmaps group projects in a specific order for generating roadmap documents.
|
|
223
|
+
* Projects can appear in multiple roadmaps (e.g., "Public Roadmap" vs "Internal Roadmap").
|
|
224
|
+
*/
|
|
225
|
+
export interface Roadmap {
|
|
226
|
+
id: string;
|
|
227
|
+
name: string;
|
|
228
|
+
description?: string;
|
|
229
|
+
isDefault: boolean;
|
|
230
|
+
createdAt: Date;
|
|
231
|
+
updatedAt: Date;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* RoadmapProject - association between a roadmap and a project with ordering.
|
|
235
|
+
* Projects can be in multiple roadmaps with different positions.
|
|
236
|
+
*/
|
|
237
|
+
export interface RoadmapProject {
|
|
238
|
+
roadmapId: string;
|
|
239
|
+
projectId: string;
|
|
240
|
+
position: number;
|
|
241
|
+
createdAt: Date;
|
|
242
|
+
}
|
|
220
243
|
/**
|
|
221
244
|
* Work action - defines what an agent should do with a ticket.
|
|
222
245
|
* Actions are reusable prompts that can be applied to any ticket.
|
|
@@ -576,6 +599,10 @@ export interface BoardViewFilter {
|
|
|
576
599
|
isDefault?: boolean;
|
|
577
600
|
search?: string;
|
|
578
601
|
}
|
|
602
|
+
export interface RoadmapFilter {
|
|
603
|
+
isDefault?: boolean;
|
|
604
|
+
search?: string;
|
|
605
|
+
}
|
|
579
606
|
export interface SyncResult {
|
|
580
607
|
success: boolean;
|
|
581
608
|
changes: number;
|
|
@@ -702,6 +729,20 @@ export interface PMOStorage {
|
|
|
702
729
|
deleteBoardView(id: string): Promise<void>;
|
|
703
730
|
getDefaultBoardView(projectId: string): Promise<BoardView | null>;
|
|
704
731
|
getBoardWithView(projectId: string, viewId?: string, filters?: BoardViewFilters): Promise<Board>;
|
|
732
|
+
listRoadmaps(filter?: RoadmapFilter): Promise<Roadmap[]>;
|
|
733
|
+
getRoadmap(id: string): Promise<Roadmap | null>;
|
|
734
|
+
createRoadmap(roadmap: Partial<Roadmap> & {
|
|
735
|
+
name: string;
|
|
736
|
+
}): Promise<Roadmap>;
|
|
737
|
+
updateRoadmap(id: string, changes: Partial<Roadmap>): Promise<Roadmap>;
|
|
738
|
+
deleteRoadmap(id: string): Promise<void>;
|
|
739
|
+
getDefaultRoadmap(): Promise<Roadmap | null>;
|
|
740
|
+
setDefaultRoadmap(id: string): Promise<Roadmap>;
|
|
741
|
+
listRoadmapProjects(roadmapId: string): Promise<Project[]>;
|
|
742
|
+
addProjectToRoadmap(roadmapId: string, projectId: string, position?: number): Promise<RoadmapProject>;
|
|
743
|
+
removeProjectFromRoadmap(roadmapId: string, projectId: string): Promise<void>;
|
|
744
|
+
reorderRoadmapProject(roadmapId: string, projectId: string, newPosition: number): Promise<RoadmapProject>;
|
|
745
|
+
getRoadmapsForProject(projectId: string): Promise<Roadmap[]>;
|
|
705
746
|
pull(): Promise<SyncResult>;
|
|
706
747
|
push(): Promise<SyncResult>;
|
|
707
748
|
status(): Promise<SyncStatus>;
|
package/dist/lib/pmo/utils.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ interface DatabaseLike {
|
|
|
44
44
|
* Uses pmo_settings table to track the next ID for each entity type.
|
|
45
45
|
* IDs are zero-padded to 3 digits (001-999), then expand (1000+).
|
|
46
46
|
*
|
|
47
|
+
* Self-healing: If counter is behind MAX(id) in the table, auto-corrects.
|
|
48
|
+
*
|
|
47
49
|
* @param db - Database instance with prepare method
|
|
48
50
|
* @param entityType - Type of entity (ticket, epic, spec, project)
|
|
49
51
|
* @returns Generated ID like "TKT-001"
|
package/dist/lib/pmo/utils.js
CHANGED
|
@@ -41,6 +41,15 @@ export const ENTITY_PREFIXES = {
|
|
|
41
41
|
spec: 'SPEC',
|
|
42
42
|
project: 'PROJ',
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Entity type to table name mapping for self-healing ID generation
|
|
46
|
+
*/
|
|
47
|
+
const ENTITY_TABLES = {
|
|
48
|
+
ticket: 'pmo_tickets',
|
|
49
|
+
epic: 'pmo_epics',
|
|
50
|
+
spec: 'pmo_specs',
|
|
51
|
+
project: 'pmo_projects',
|
|
52
|
+
};
|
|
44
53
|
/**
|
|
45
54
|
* Generate a sequential ID for an entity.
|
|
46
55
|
*
|
|
@@ -49,16 +58,28 @@ export const ENTITY_PREFIXES = {
|
|
|
49
58
|
* Uses pmo_settings table to track the next ID for each entity type.
|
|
50
59
|
* IDs are zero-padded to 3 digits (001-999), then expand (1000+).
|
|
51
60
|
*
|
|
61
|
+
* Self-healing: If counter is behind MAX(id) in the table, auto-corrects.
|
|
62
|
+
*
|
|
52
63
|
* @param db - Database instance with prepare method
|
|
53
64
|
* @param entityType - Type of entity (ticket, epic, spec, project)
|
|
54
65
|
* @returns Generated ID like "TKT-001"
|
|
55
66
|
*/
|
|
56
67
|
export function generateEntityId(db, entityType) {
|
|
57
68
|
const typePrefix = ENTITY_PREFIXES[entityType];
|
|
69
|
+
const tableName = ENTITY_TABLES[entityType];
|
|
58
70
|
const settingKey = `next_${entityType}_id`;
|
|
71
|
+
// Get MAX(id) from the entity table for self-healing
|
|
72
|
+
// Extract numeric part: TKT-001 → 1, EPIC-042 → 42
|
|
73
|
+
const prefixLen = typePrefix.length + 1; // e.g., "TKT-" = 4 chars
|
|
74
|
+
const maxResult = db.prepare(`SELECT MAX(CAST(SUBSTR(id, ${prefixLen + 1}) AS INTEGER)) as max_num FROM ${tableName}`).get();
|
|
75
|
+
const maxExistingId = maxResult?.max_num || 0;
|
|
59
76
|
// Get current counter
|
|
60
77
|
const row = db.prepare(`SELECT value FROM pmo_settings WHERE key = ?`).get(settingKey);
|
|
61
|
-
|
|
78
|
+
let nextNum = row ? parseInt(row.value, 10) : 1;
|
|
79
|
+
// Self-healing: if counter is behind existing IDs, fix it
|
|
80
|
+
if (nextNum <= maxExistingId) {
|
|
81
|
+
nextNum = maxExistingId + 1;
|
|
82
|
+
}
|
|
62
83
|
// Update counter
|
|
63
84
|
db.prepare(`
|
|
64
85
|
INSERT INTO pmo_settings (key, value) VALUES (?, ?)
|
package/dist/lib/repos/index.js
CHANGED
|
@@ -76,9 +76,10 @@ export async function promptForRepositories(currentDir = process.cwd(), existing
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
// Loop to add more repos
|
|
79
|
+
// Loop to add more repos - user interaction requires sequential processing
|
|
80
80
|
let addingRepos = true;
|
|
81
81
|
while (addingRepos) {
|
|
82
|
+
// eslint-disable-next-line no-await-in-loop
|
|
82
83
|
const { repoAction } = await inquirer.prompt([{
|
|
83
84
|
type: 'list',
|
|
84
85
|
name: 'repoAction',
|
|
@@ -98,6 +99,7 @@ export async function promptForRepositories(currentDir = process.cwd(), existing
|
|
|
98
99
|
break;
|
|
99
100
|
}
|
|
100
101
|
if (repoAction === 'create') {
|
|
102
|
+
// eslint-disable-next-line no-await-in-loop
|
|
101
103
|
const newRepo = await createNewRepository();
|
|
102
104
|
if (newRepo) {
|
|
103
105
|
repos.push(newRepo);
|
|
@@ -105,10 +107,12 @@ export async function promptForRepositories(currentDir = process.cwd(), existing
|
|
|
105
107
|
continue;
|
|
106
108
|
}
|
|
107
109
|
if (repoAction === 'search') {
|
|
110
|
+
// eslint-disable-next-line no-await-in-loop
|
|
108
111
|
const foundRepos = await searchForRepositories();
|
|
109
112
|
repos.push(...foundRepos);
|
|
110
113
|
// After search, ask if they want to add more (unless they selected nothing)
|
|
111
114
|
if (foundRepos.length > 0) {
|
|
115
|
+
// eslint-disable-next-line no-await-in-loop
|
|
112
116
|
const { addMoreAfterSearch } = await inquirer.prompt([{
|
|
113
117
|
type: 'list',
|
|
114
118
|
name: 'addMoreAfterSearch',
|
|
@@ -127,6 +131,7 @@ export async function promptForRepositories(currentDir = process.cwd(), existing
|
|
|
127
131
|
continue;
|
|
128
132
|
}
|
|
129
133
|
// Manual entry (existing logic)
|
|
134
|
+
// eslint-disable-next-line no-await-in-loop
|
|
130
135
|
const { repoPath } = await inquirer.prompt([{
|
|
131
136
|
type: 'input',
|
|
132
137
|
name: 'repoPath',
|
|
@@ -152,6 +157,7 @@ export async function promptForRepositories(currentDir = process.cwd(), existing
|
|
|
152
157
|
repos.push({ path: resolvedPath, action: 'clone' });
|
|
153
158
|
}
|
|
154
159
|
else {
|
|
160
|
+
// eslint-disable-next-line no-await-in-loop
|
|
155
161
|
const { action } = await inquirer.prompt([{
|
|
156
162
|
type: 'list',
|
|
157
163
|
name: 'action',
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal utilities for controlling terminal behavior.
|
|
3
|
+
*
|
|
4
|
+
* Provides ANSI escape sequence utilities for:
|
|
5
|
+
* - Setting terminal tab/window titles
|
|
6
|
+
* - Future: clearing screen, notifications, etc.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Generate shell commands to set the terminal tab/window title.
|
|
10
|
+
* Uses ANSI escape sequences that work across most terminal emulators.
|
|
11
|
+
*
|
|
12
|
+
* \033]0;Title\007 - Sets both window and tab title (most compatible)
|
|
13
|
+
* \033]1;Title\007 - Sets tab title only (iTerm2, some others)
|
|
14
|
+
* \033]2;Title\007 - Sets window title only
|
|
15
|
+
*
|
|
16
|
+
* @param title - The title to set
|
|
17
|
+
* @returns Shell commands to execute
|
|
18
|
+
*/
|
|
19
|
+
export declare function getSetTitleCommands(title: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Set the terminal title directly using ANSI escape sequences.
|
|
22
|
+
* Writes directly to stdout.
|
|
23
|
+
*
|
|
24
|
+
* @param title - The title to set (empty string to reset)
|
|
25
|
+
*/
|
|
26
|
+
export declare function setTerminalTitle(title: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Reset the terminal title to default (shell will set its own).
|
|
29
|
+
* This sends an empty title which most terminals interpret as "use default".
|
|
30
|
+
*/
|
|
31
|
+
export declare function resetTerminalTitle(): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal utilities for controlling terminal behavior.
|
|
3
|
+
*
|
|
4
|
+
* Provides ANSI escape sequence utilities for:
|
|
5
|
+
* - Setting terminal tab/window titles
|
|
6
|
+
* - Future: clearing screen, notifications, etc.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Generate shell commands to set the terminal tab/window title.
|
|
10
|
+
* Uses ANSI escape sequences that work across most terminal emulators.
|
|
11
|
+
*
|
|
12
|
+
* \033]0;Title\007 - Sets both window and tab title (most compatible)
|
|
13
|
+
* \033]1;Title\007 - Sets tab title only (iTerm2, some others)
|
|
14
|
+
* \033]2;Title\007 - Sets window title only
|
|
15
|
+
*
|
|
16
|
+
* @param title - The title to set
|
|
17
|
+
* @returns Shell commands to execute
|
|
18
|
+
*/
|
|
19
|
+
export function getSetTitleCommands(title) {
|
|
20
|
+
// Escape any special characters in the title
|
|
21
|
+
const safeTitle = title.replace(/[\\'"]/g, '');
|
|
22
|
+
return `
|
|
23
|
+
# Set terminal tab/window title
|
|
24
|
+
echo -ne "\\033]0;${safeTitle}\\007"
|
|
25
|
+
echo -ne "\\033]1;${safeTitle}\\007"
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Set the terminal title directly using ANSI escape sequences.
|
|
30
|
+
* Writes directly to stdout.
|
|
31
|
+
*
|
|
32
|
+
* @param title - The title to set (empty string to reset)
|
|
33
|
+
*/
|
|
34
|
+
export function setTerminalTitle(title) {
|
|
35
|
+
// Escape any special characters
|
|
36
|
+
const safeTitle = title.replace(/[\\'"]/g, '');
|
|
37
|
+
// OSC 0 - Set both window and tab title (most compatible)
|
|
38
|
+
process.stdout.write(`\u001B]0;${safeTitle}\u0007`);
|
|
39
|
+
// OSC 1 - Set tab title (iTerm2 and others)
|
|
40
|
+
process.stdout.write(`\u001B]1;${safeTitle}\u0007`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Reset the terminal title to default (shell will set its own).
|
|
44
|
+
* This sends an empty title which most terminals interpret as "use default".
|
|
45
|
+
*/
|
|
46
|
+
export function resetTerminalTitle() {
|
|
47
|
+
setTerminalTitle('');
|
|
48
|
+
}
|
package/dist/lib/themes.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Agent } from './database/index.js';
|
|
1
2
|
/**
|
|
2
3
|
* Default workspace directory for persistent agents (fallback if no theme)
|
|
3
4
|
*/
|
|
@@ -111,11 +112,16 @@ export interface GenerateEphemeralNameOptions {
|
|
|
111
112
|
* Optional callback for logging/messaging when conflicts are detected
|
|
112
113
|
*/
|
|
113
114
|
onConflictSkipped?: (name: string, reason: string) => void;
|
|
115
|
+
/**
|
|
116
|
+
* Optional set of base names currently in use by active agents.
|
|
117
|
+
* If provided, the generator will prefer base names NOT in this set.
|
|
118
|
+
*/
|
|
119
|
+
inUseBaseNames?: Set<string>;
|
|
114
120
|
}
|
|
115
121
|
/**
|
|
116
122
|
* Generate a unique ephemeral agent name.
|
|
117
|
-
* Format: {adjective}-{theme_name}-{number}
|
|
118
|
-
* Example: "bold-bezos
|
|
123
|
+
* Format: {adjective}-{theme_name} or {adjective}-{theme_name}-{number}
|
|
124
|
+
* Example: "bold-bezos", "keen-camry", "bold-bezos-2" (if bold-bezos exists)
|
|
119
125
|
*
|
|
120
126
|
* @param existingNames - Set of existing agent names (for uniqueness checking)
|
|
121
127
|
* @param options - Optional configuration for name generation
|
|
@@ -123,6 +129,18 @@ export interface GenerateEphemeralNameOptions {
|
|
|
123
129
|
export declare function generateEphemeralAgentName(existingNames: Set<string>, options?: GenerateEphemeralNameOptions | string): string;
|
|
124
130
|
/**
|
|
125
131
|
* Check if a name looks like an ephemeral agent name.
|
|
126
|
-
* Ephemeral names follow pattern: {adjective}-{name}-{number}
|
|
132
|
+
* Ephemeral names follow pattern: {adjective}-{name} or {adjective}-{name}-{number}
|
|
127
133
|
*/
|
|
128
134
|
export declare function isEphemeralAgentName(name: string): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Extract the base name from an ephemeral agent name.
|
|
137
|
+
* Handles both formats: "bold-bezos" -> "bezos", "bold-bezos-2" -> "bezos"
|
|
138
|
+
*/
|
|
139
|
+
export declare function extractBaseName(agentName: string): string;
|
|
140
|
+
/**
|
|
141
|
+
* Get the base name for any agent type.
|
|
142
|
+
* - If agent has explicit base_name, use it
|
|
143
|
+
* - If ephemeral, extract from name pattern
|
|
144
|
+
* - Otherwise (staff), use agent name directly
|
|
145
|
+
*/
|
|
146
|
+
export declare function getAgentBaseName(agent: Agent): string;
|