@proletariat/cli 0.3.8 → 0.3.9
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/LICENSE +21 -0
- package/bin/dev.js +0 -0
- package/dist/commands/branch/where.d.ts +21 -0
- package/dist/commands/branch/where.js +213 -0
- package/dist/commands/pmo/init.js +23 -5
- package/dist/commands/project/create.js +9 -10
- package/dist/commands/whoami.d.ts +1 -0
- package/dist/commands/whoami.js +36 -6
- package/dist/commands/work/start.js +11 -0
- package/dist/lib/database/index.d.ts +6 -0
- package/dist/lib/database/index.js +38 -0
- package/dist/lib/execution/devcontainer.js +3 -0
- package/dist/lib/execution/runners.js +3 -2
- package/dist/lib/execution/spawner.d.ts +3 -1
- package/dist/lib/execution/spawner.js +9 -2
- package/dist/lib/execution/storage.d.ts +14 -0
- package/dist/lib/execution/storage.js +88 -0
- package/dist/lib/pmo/index.d.ts +7 -21
- package/dist/lib/pmo/index.js +22 -101
- package/dist/lib/pmo/storage/base.d.ts +2 -2
- package/dist/lib/pmo/storage/base.js +14 -89
- package/dist/lib/pmo/templates-builtin.d.ts +66 -0
- package/dist/lib/pmo/templates-builtin.js +192 -0
- package/dist/lib/themes.js +4 -4
- package/oclif.manifest.json +3378 -3331
- package/package.json +4 -6
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Database operations for agent_work table.
|
|
5
5
|
*/
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
6
7
|
import { PMO_TABLES } from '../pmo/schema.js';
|
|
7
8
|
const T = PMO_TABLES;
|
|
8
9
|
// =============================================================================
|
|
@@ -190,6 +191,93 @@ export class ExecutionStorage {
|
|
|
190
191
|
.get(agentName);
|
|
191
192
|
return count.count === 0;
|
|
192
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Clean up stale executions where the tmux session no longer exists.
|
|
196
|
+
* This fixes the bug where agents appear "busy" after sessions terminate unexpectedly.
|
|
197
|
+
* Returns the number of stale executions cleaned up.
|
|
198
|
+
*/
|
|
199
|
+
cleanupStaleExecutions() {
|
|
200
|
+
// Get all "running" or "starting" executions
|
|
201
|
+
const activeExecutions = this.listExecutions({ status: 'running' })
|
|
202
|
+
.concat(this.listExecutions({ status: 'starting' }));
|
|
203
|
+
if (activeExecutions.length === 0) {
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
// Get list of actual tmux sessions on host
|
|
207
|
+
const hostTmuxSessions = this.getHostTmuxSessionNames();
|
|
208
|
+
// Get map of container -> tmux sessions
|
|
209
|
+
const containerTmuxSessions = this.getContainerTmuxSessionMap();
|
|
210
|
+
let cleanedCount = 0;
|
|
211
|
+
for (const exec of activeExecutions) {
|
|
212
|
+
if (!exec.sessionId) {
|
|
213
|
+
// Executions without sessionId might be stale from early termination
|
|
214
|
+
// Check if they're older than 5 minutes and mark as stopped
|
|
215
|
+
const ageMs = Date.now() - exec.startedAt.getTime();
|
|
216
|
+
if (ageMs > 5 * 60 * 1000) {
|
|
217
|
+
this.updateStatus(exec.id, 'stopped');
|
|
218
|
+
cleanedCount++;
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
let sessionExists = false;
|
|
223
|
+
if (exec.environment === 'devcontainer' && exec.containerId) {
|
|
224
|
+
// Check if session exists in container
|
|
225
|
+
const containerSessions = containerTmuxSessions.get(exec.containerId);
|
|
226
|
+
sessionExists = containerSessions?.includes(exec.sessionId) ?? false;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
// Check if session exists on host
|
|
230
|
+
sessionExists = hostTmuxSessions.includes(exec.sessionId);
|
|
231
|
+
}
|
|
232
|
+
if (!sessionExists) {
|
|
233
|
+
// Session doesn't exist, mark execution as stopped
|
|
234
|
+
this.updateStatus(exec.id, 'stopped');
|
|
235
|
+
cleanedCount++;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return cleanedCount;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get list of host tmux session names
|
|
242
|
+
*/
|
|
243
|
+
getHostTmuxSessionNames() {
|
|
244
|
+
try {
|
|
245
|
+
execSync('which tmux', { stdio: 'pipe' });
|
|
246
|
+
const output = execSync('tmux list-sessions -F "#{session_name}"', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
247
|
+
if (!output)
|
|
248
|
+
return [];
|
|
249
|
+
return output.split('\n');
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get map of containerId -> tmux session names
|
|
257
|
+
*/
|
|
258
|
+
getContainerTmuxSessionMap() {
|
|
259
|
+
const sessionMap = new Map();
|
|
260
|
+
try {
|
|
261
|
+
const containersOutput = execSync('docker ps --filter "label=devcontainer.local_folder" --format "{{.ID}}"', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
262
|
+
if (!containersOutput)
|
|
263
|
+
return sessionMap;
|
|
264
|
+
for (const containerId of containersOutput.split('\n')) {
|
|
265
|
+
try {
|
|
266
|
+
const tmuxOutput = execSync(`docker exec ${containerId} tmux list-sessions -F "#{session_name}" 2>/dev/null`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
267
|
+
if (tmuxOutput) {
|
|
268
|
+
sessionMap.set(containerId, tmuxOutput.split('\n'));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// Container has no tmux sessions
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// Docker not available
|
|
278
|
+
}
|
|
279
|
+
return sessionMap;
|
|
280
|
+
}
|
|
193
281
|
/**
|
|
194
282
|
* Get total execution count for an agent (historical)
|
|
195
283
|
* Used by least-busy agent selection strategy.
|
package/dist/lib/pmo/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SQLiteStorage } from './storage-sqlite.js';
|
|
1
2
|
export * from './types.js';
|
|
2
3
|
export * from './utils.js';
|
|
3
4
|
export { parseBoard, generateBoardMarkdown, findAddedTickets, findRemovedTickets, findModifiedTickets, } from './markdown.js';
|
|
@@ -9,26 +10,14 @@ export { findPMO } from './find-pmo.js';
|
|
|
9
10
|
export { getPMOContext, type PMOContext, type GetPMOContextOptions } from './pmo-context.js';
|
|
10
11
|
export { PMOCommand, pmoBaseFlags } from './base-command.js';
|
|
11
12
|
export { PMO_TABLES, PMO_TABLE_SCHEMAS, PMO_INDEXES, PMO_SCHEMA_SQL, EXPECTED_TICKET_COLUMNS, validateTicketSchema, } from './schema.js';
|
|
13
|
+
export { BUILTIN_TEMPLATES, getBuiltinTemplate, getPickerTemplates, getColumnsForTemplate, getColumnSettingsForTemplate, } from './templates-builtin.js';
|
|
12
14
|
/**
|
|
13
|
-
* Get available board templates
|
|
15
|
+
* Get available board templates (backward compatibility wrapper).
|
|
16
|
+
* @deprecated Use BUILTIN_TEMPLATES or getColumnsForTemplate() instead
|
|
14
17
|
*/
|
|
15
18
|
export declare function getBoardTemplates(): {
|
|
16
19
|
[key: string]: string[];
|
|
17
20
|
};
|
|
18
|
-
/**
|
|
19
|
-
* Get column settings for work commands based on board template.
|
|
20
|
-
* These settings determine which columns are used for:
|
|
21
|
-
* - work start: moves ticket to column_in_progress
|
|
22
|
-
* - work ready: moves ticket to column_review
|
|
23
|
-
* - work complete: moves ticket to column_done
|
|
24
|
-
*
|
|
25
|
-
* For custom templates, we try to find matching columns by keyword.
|
|
26
|
-
*/
|
|
27
|
-
export declare function getColumnSettingsForTemplate(template: string, columns: string[]): {
|
|
28
|
-
column_planned: string;
|
|
29
|
-
column_in_progress: string;
|
|
30
|
-
column_done: string;
|
|
31
|
-
};
|
|
32
21
|
export type PMOStorageType = 'sqlite' | 'git';
|
|
33
22
|
export type PMOLocation = 'separate' | `repo:${string}`;
|
|
34
23
|
export interface PMOSetupResult {
|
|
@@ -51,9 +40,10 @@ export declare function detectRepos(hqRoot: string): Array<{
|
|
|
51
40
|
*/
|
|
52
41
|
export declare function promptForPMOLocation(hqRoot: string | null): Promise<PMOLocation>;
|
|
53
42
|
/**
|
|
54
|
-
* Prompt for board template
|
|
43
|
+
* Prompt for board template.
|
|
44
|
+
* Uses BUILTIN_TEMPLATES as the single source of truth.
|
|
55
45
|
*/
|
|
56
|
-
export declare function promptForBoardTemplate(): Promise<string>;
|
|
46
|
+
export declare function promptForBoardTemplate(_storage?: SQLiteStorage): Promise<string>;
|
|
57
47
|
/**
|
|
58
48
|
* Prompt for custom columns
|
|
59
49
|
*/
|
|
@@ -68,10 +58,6 @@ export declare function promptForBoardName(defaultName?: string): Promise<string
|
|
|
68
58
|
* PMO is included by default (no prompt) per TKT-469 requirements.
|
|
69
59
|
*/
|
|
70
60
|
export declare function promptForPMOSetup(hqRoot: string | null, hqName?: string): Promise<PMOSetupResult>;
|
|
71
|
-
/**
|
|
72
|
-
* Get columns for a board template
|
|
73
|
-
*/
|
|
74
|
-
export declare function getColumnsForTemplate(template: string): string[];
|
|
75
61
|
/**
|
|
76
62
|
* Create board content for Obsidian Kanban
|
|
77
63
|
*/
|
package/dist/lib/pmo/index.js
CHANGED
|
@@ -18,94 +18,20 @@ export { findPMO } from './find-pmo.js';
|
|
|
18
18
|
export { getPMOContext } from './pmo-context.js';
|
|
19
19
|
export { PMOCommand, pmoBaseFlags } from './base-command.js';
|
|
20
20
|
export { PMO_TABLES, PMO_TABLE_SCHEMAS, PMO_INDEXES, PMO_SCHEMA_SQL, EXPECTED_TICKET_COLUMNS, validateTicketSchema, } from './schema.js';
|
|
21
|
+
// Re-export template utilities from shared definitions
|
|
22
|
+
export { BUILTIN_TEMPLATES, getBuiltinTemplate, getPickerTemplates, getColumnsForTemplate, getColumnSettingsForTemplate, } from './templates-builtin.js';
|
|
23
|
+
import { BUILTIN_TEMPLATES, getColumnsForTemplate, getColumnSettingsForTemplate, } from './templates-builtin.js';
|
|
21
24
|
/**
|
|
22
|
-
* Get available board templates
|
|
25
|
+
* Get available board templates (backward compatibility wrapper).
|
|
26
|
+
* @deprecated Use BUILTIN_TEMPLATES or getColumnsForTemplate() instead
|
|
23
27
|
*/
|
|
24
28
|
export function getBoardTemplates() {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// Linear-style with Triage and Review columns
|
|
29
|
-
linear: ['Backlog', 'Triage', 'Todo', 'In Progress', 'In Review', 'Done'],
|
|
30
|
-
// Bug tracking workflow
|
|
31
|
-
'bug-smash': ['Reported', 'Confirmed', 'Fixing', 'Verifying', 'Fixed'],
|
|
32
|
-
// Founder template: 5-tool backlogs (by work type) + workflow stages
|
|
33
|
-
'5-tool-founder': [
|
|
34
|
-
'Ship', 'Grow', 'Support', 'Strategy', 'BizOps',
|
|
35
|
-
'In Progress', 'Review', 'Done'
|
|
36
|
-
],
|
|
37
|
-
// Go-to-market workflow
|
|
38
|
-
'gtm': ['Ideation', 'Planning', 'In Development', 'Ready to Launch', 'Launched'],
|
|
39
|
-
// Legacy alias for founder
|
|
40
|
-
founder: [
|
|
41
|
-
'SHIP BL', 'GROW BL', 'SUPPORT BL', 'BIZOPS BL', 'STRATEGY BL',
|
|
42
|
-
'Planned', 'In Progress', 'Done', 'Dropped'
|
|
43
|
-
],
|
|
44
|
-
custom: [] // Will be handled separately
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Get column settings for work commands based on board template.
|
|
49
|
-
* These settings determine which columns are used for:
|
|
50
|
-
* - work start: moves ticket to column_in_progress
|
|
51
|
-
* - work ready: moves ticket to column_review
|
|
52
|
-
* - work complete: moves ticket to column_done
|
|
53
|
-
*
|
|
54
|
-
* For custom templates, we try to find matching columns by keyword.
|
|
55
|
-
*/
|
|
56
|
-
export function getColumnSettingsForTemplate(template, columns) {
|
|
57
|
-
// Template-specific mappings (Linear-style: planned -> in_progress -> done)
|
|
58
|
-
const templateMappings = {
|
|
59
|
-
kanban: {
|
|
60
|
-
column_planned: 'Planned',
|
|
61
|
-
column_in_progress: 'In Progress',
|
|
62
|
-
column_done: 'Done',
|
|
63
|
-
},
|
|
64
|
-
linear: {
|
|
65
|
-
column_planned: 'Todo',
|
|
66
|
-
column_in_progress: 'In Progress',
|
|
67
|
-
column_done: 'Done',
|
|
68
|
-
},
|
|
69
|
-
'bug-smash': {
|
|
70
|
-
column_planned: 'Confirmed',
|
|
71
|
-
column_in_progress: 'Fixing',
|
|
72
|
-
column_done: 'Fixed',
|
|
73
|
-
},
|
|
74
|
-
'5-tool-founder': {
|
|
75
|
-
column_planned: 'Ship',
|
|
76
|
-
column_in_progress: 'In Progress',
|
|
77
|
-
column_done: 'Done',
|
|
78
|
-
},
|
|
79
|
-
'gtm': {
|
|
80
|
-
column_planned: 'Planning',
|
|
81
|
-
column_in_progress: 'In Development',
|
|
82
|
-
column_done: 'Launched',
|
|
83
|
-
},
|
|
84
|
-
founder: {
|
|
85
|
-
column_planned: 'Planned',
|
|
86
|
-
column_in_progress: 'In Progress',
|
|
87
|
-
column_done: 'Done',
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
// Use template mapping if available
|
|
91
|
-
if (templateMappings[template]) {
|
|
92
|
-
return templateMappings[template];
|
|
29
|
+
const result = {};
|
|
30
|
+
for (const template of BUILTIN_TEMPLATES) {
|
|
31
|
+
result[template.id] = template.columns;
|
|
93
32
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const lowerColumns = columns.map(c => c.toLowerCase());
|
|
97
|
-
for (const keyword of keywords) {
|
|
98
|
-
const idx = lowerColumns.findIndex(c => c.includes(keyword));
|
|
99
|
-
if (idx !== -1)
|
|
100
|
-
return columns[idx];
|
|
101
|
-
}
|
|
102
|
-
return fallback;
|
|
103
|
-
};
|
|
104
|
-
return {
|
|
105
|
-
column_planned: findColumn(['planned', 'ready', 'scheduled', 'todo'], columns[1] || 'Planned'),
|
|
106
|
-
column_in_progress: findColumn(['progress', 'active', 'doing', 'working'], columns[2] || 'In Progress'),
|
|
107
|
-
column_done: findColumn(['done', 'complete', 'finished', 'published', 'shipped'], columns[columns.length - 1] || 'Done'),
|
|
108
|
-
};
|
|
33
|
+
result['custom'] = []; // Special case for custom templates
|
|
34
|
+
return result;
|
|
109
35
|
}
|
|
110
36
|
/**
|
|
111
37
|
* Detect repositories in an HQ
|
|
@@ -175,21 +101,23 @@ export async function promptForPMOLocation(hqRoot) {
|
|
|
175
101
|
}
|
|
176
102
|
}
|
|
177
103
|
/**
|
|
178
|
-
* Prompt for board template
|
|
104
|
+
* Prompt for board template.
|
|
105
|
+
* Uses BUILTIN_TEMPLATES as the single source of truth.
|
|
179
106
|
*/
|
|
180
|
-
export async function promptForBoardTemplate() {
|
|
107
|
+
export async function promptForBoardTemplate(_storage) {
|
|
108
|
+
// Use builtin templates directly - single source of truth
|
|
109
|
+
const pickerTemplates = BUILTIN_TEMPLATES.filter(t => t.showInPicker);
|
|
110
|
+
const choices = pickerTemplates.map(t => ({
|
|
111
|
+
name: `${t.name} (${t.columns.slice(0, 4).join(', ')}${t.columns.length > 4 ? '...' : ''})`,
|
|
112
|
+
value: t.id,
|
|
113
|
+
}));
|
|
114
|
+
// Add custom option
|
|
115
|
+
choices.push({ name: 'Custom (define your own columns)', value: 'custom' });
|
|
181
116
|
const { template } = await inquirer.prompt([{
|
|
182
117
|
type: 'list',
|
|
183
118
|
name: 'template',
|
|
184
119
|
message: 'Choose board template:',
|
|
185
|
-
choices
|
|
186
|
-
{ name: 'Kanban - Backlog → Planned → In Progress → Done', value: 'kanban' },
|
|
187
|
-
{ name: 'Linear - Backlog, Triage, Todo, In Progress, In Review, Done', value: 'linear' },
|
|
188
|
-
{ name: 'Bug Smash - Reported → Confirmed → Fixing → Verifying → Fixed', value: 'bug-smash' },
|
|
189
|
-
{ name: '5-Tool Founder - Ship, Grow, Support, Strategy, BizOps → In Progress → Review → Done', value: '5-tool-founder' },
|
|
190
|
-
{ name: 'GTM - Ideation → Planning → In Development → Ready to Launch → Launched', value: 'gtm' },
|
|
191
|
-
{ name: 'Custom (define your own columns)', value: 'custom' },
|
|
192
|
-
],
|
|
120
|
+
choices,
|
|
193
121
|
default: 'kanban',
|
|
194
122
|
}]);
|
|
195
123
|
return template;
|
|
@@ -253,13 +181,6 @@ export async function promptForPMOSetup(hqRoot, hqName) {
|
|
|
253
181
|
columns,
|
|
254
182
|
};
|
|
255
183
|
}
|
|
256
|
-
/**
|
|
257
|
-
* Get columns for a board template
|
|
258
|
-
*/
|
|
259
|
-
export function getColumnsForTemplate(template) {
|
|
260
|
-
const templates = getBoardTemplates();
|
|
261
|
-
return templates[template] || templates.kanban;
|
|
262
|
-
}
|
|
263
184
|
/**
|
|
264
185
|
* Create board content for Obsidian Kanban
|
|
265
186
|
*/
|
|
@@ -13,8 +13,8 @@ export declare function initializePMOTables(db: Database.Database): void;
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function runMigrations(db: Database.Database): void;
|
|
15
15
|
/**
|
|
16
|
-
* Seed built-in workflows (
|
|
17
|
-
* Creates workflows from
|
|
16
|
+
* Seed built-in workflows from BUILTIN_TEMPLATES (single source of truth).
|
|
17
|
+
* Creates workflows from template definitions for reuse across projects.
|
|
18
18
|
*/
|
|
19
19
|
export declare function seedBuiltinWorkflows(db: Database.Database): void;
|
|
20
20
|
/**
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* This module handles database setup and provides shared utilities.
|
|
4
4
|
*/
|
|
5
5
|
import { PMO_TABLES, PMO_SCHEMA_SQL, validateTicketSchema } from '../schema.js';
|
|
6
|
+
import { BUILTIN_TEMPLATES } from '../templates-builtin.js';
|
|
6
7
|
const T = PMO_TABLES;
|
|
7
8
|
/**
|
|
8
9
|
* Initialize PMO tables in the database.
|
|
@@ -218,92 +219,10 @@ export function runMigrations(db) {
|
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
/**
|
|
221
|
-
* Seed built-in workflows (
|
|
222
|
-
* Creates workflows from
|
|
222
|
+
* Seed built-in workflows from BUILTIN_TEMPLATES (single source of truth).
|
|
223
|
+
* Creates workflows from template definitions for reuse across projects.
|
|
223
224
|
*/
|
|
224
225
|
export function seedBuiltinWorkflows(db) {
|
|
225
|
-
// Define built-in workflows based on the template definitions
|
|
226
|
-
const builtinWorkflows = [
|
|
227
|
-
{
|
|
228
|
-
id: 'default',
|
|
229
|
-
name: 'Default',
|
|
230
|
-
description: 'Default workflow: Backlog → Ready → In Progress → Review → Done',
|
|
231
|
-
statuses: [
|
|
232
|
-
{ name: 'Backlog', category: 'backlog', position: 0, isDefault: true },
|
|
233
|
-
{ name: 'Ready', category: 'unstarted', position: 1 },
|
|
234
|
-
{ name: 'In Progress', category: 'started', position: 2 },
|
|
235
|
-
{ name: 'Review', category: 'started', position: 3 },
|
|
236
|
-
{ name: 'Done', category: 'completed', position: 4 },
|
|
237
|
-
],
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
id: 'kanban',
|
|
241
|
-
name: 'Kanban',
|
|
242
|
-
description: 'Simple kanban workflow: Backlog → To Do → In Progress → Done',
|
|
243
|
-
statuses: [
|
|
244
|
-
{ name: 'Backlog', category: 'backlog', position: 0, isDefault: true },
|
|
245
|
-
{ name: 'To Do', category: 'unstarted', position: 1 },
|
|
246
|
-
{ name: 'In Progress', category: 'started', position: 2 },
|
|
247
|
-
{ name: 'Done', category: 'completed', position: 3 },
|
|
248
|
-
{ name: 'Canceled', category: 'canceled', position: 4 },
|
|
249
|
-
],
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
id: 'linear',
|
|
253
|
-
name: 'Linear',
|
|
254
|
-
description: 'Linear-style workflow with backlog, triage, and review stages',
|
|
255
|
-
statuses: [
|
|
256
|
-
{ name: 'Backlog', category: 'backlog', position: 0, isDefault: true },
|
|
257
|
-
{ name: 'Triage', category: 'backlog', position: 1 },
|
|
258
|
-
{ name: 'Todo', category: 'unstarted', position: 2 },
|
|
259
|
-
{ name: 'In Progress', category: 'started', position: 3 },
|
|
260
|
-
{ name: 'In Review', category: 'started', position: 4 },
|
|
261
|
-
{ name: 'Done', category: 'completed', position: 5 },
|
|
262
|
-
{ name: 'Canceled', category: 'canceled', position: 6 },
|
|
263
|
-
],
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
id: 'bug-smash',
|
|
267
|
-
name: 'Bug Smash',
|
|
268
|
-
description: 'Bug tracking workflow with verification stages',
|
|
269
|
-
statuses: [
|
|
270
|
-
{ name: 'Reported', category: 'backlog', position: 0, isDefault: true },
|
|
271
|
-
{ name: 'Confirmed', category: 'unstarted', position: 1 },
|
|
272
|
-
{ name: 'Fixing', category: 'started', position: 2 },
|
|
273
|
-
{ name: 'Verifying', category: 'started', position: 3 },
|
|
274
|
-
{ name: 'Fixed', category: 'completed', position: 4 },
|
|
275
|
-
{ name: "Won't Fix", category: 'canceled', position: 5 },
|
|
276
|
-
],
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
id: '5-tool-founder',
|
|
280
|
-
name: '5-Tool Founder',
|
|
281
|
-
description: 'Founder workflow: Ship, Grow, Support, Strategy, BizOps backlogs → In Progress → Review → Done',
|
|
282
|
-
statuses: [
|
|
283
|
-
{ name: 'Ship', category: 'backlog', position: 0, isDefault: true },
|
|
284
|
-
{ name: 'Grow', category: 'backlog', position: 1 },
|
|
285
|
-
{ name: 'Support', category: 'backlog', position: 2 },
|
|
286
|
-
{ name: 'Strategy', category: 'backlog', position: 3 },
|
|
287
|
-
{ name: 'BizOps', category: 'backlog', position: 4 },
|
|
288
|
-
{ name: 'In Progress', category: 'started', position: 5 },
|
|
289
|
-
{ name: 'Review', category: 'started', position: 6 },
|
|
290
|
-
{ name: 'Done', category: 'completed', position: 7 },
|
|
291
|
-
],
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
id: 'gtm',
|
|
295
|
-
name: 'GTM',
|
|
296
|
-
description: 'Go-to-market workflow for launches and campaigns',
|
|
297
|
-
statuses: [
|
|
298
|
-
{ name: 'Ideation', category: 'backlog', position: 0, isDefault: true },
|
|
299
|
-
{ name: 'Planning', category: 'unstarted', position: 1 },
|
|
300
|
-
{ name: 'In Development', category: 'started', position: 2 },
|
|
301
|
-
{ name: 'Ready to Launch', category: 'started', position: 3 },
|
|
302
|
-
{ name: 'Launched', category: 'completed', position: 4 },
|
|
303
|
-
{ name: 'Retired', category: 'canceled', position: 5 },
|
|
304
|
-
],
|
|
305
|
-
},
|
|
306
|
-
];
|
|
307
226
|
const now = new Date().toISOString();
|
|
308
227
|
const insertWorkflow = db.prepare(`
|
|
309
228
|
INSERT OR IGNORE INTO ${T.workflows} (id, name, description, is_builtin, created_at, updated_at)
|
|
@@ -313,11 +232,17 @@ export function seedBuiltinWorkflows(db) {
|
|
|
313
232
|
INSERT OR IGNORE INTO ${T.workflow_statuses} (id, workflow_id, name, category, position, color, description, is_default, created_at)
|
|
314
233
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
315
234
|
`);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
235
|
+
// Read from BUILTIN_TEMPLATES - the single source of truth
|
|
236
|
+
for (const template of BUILTIN_TEMPLATES) {
|
|
237
|
+
insertWorkflow.run(template.id, template.name, template.description, now, now);
|
|
238
|
+
for (let i = 0; i < template.statuses.length; i++) {
|
|
239
|
+
const status = template.statuses[i];
|
|
240
|
+
const statusId = `${template.id}-${status.name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '')}`;
|
|
241
|
+
// First status is the default
|
|
242
|
+
const isDefault = i === 0;
|
|
243
|
+
insertStatus.run(statusId, template.id, status.name, status.category, status.position, null, // color
|
|
244
|
+
null, // description
|
|
245
|
+
isDefault ? 1 : 0, now);
|
|
321
246
|
}
|
|
322
247
|
}
|
|
323
248
|
// Assign default workflow to any projects without a workflow
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builtin workflow templates - single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* This file defines all builtin templates used by:
|
|
5
|
+
* - UI prompts (template selection menu)
|
|
6
|
+
* - Database seeding (pmo_templates table)
|
|
7
|
+
* - Board column creation
|
|
8
|
+
* - Work command column mappings
|
|
9
|
+
*/
|
|
10
|
+
import { StateCategory } from './types.js';
|
|
11
|
+
export interface WorkflowTemplateStatus {
|
|
12
|
+
name: string;
|
|
13
|
+
category: StateCategory;
|
|
14
|
+
position: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ColumnSettings {
|
|
17
|
+
column_planned: string;
|
|
18
|
+
column_in_progress: string;
|
|
19
|
+
column_done: string;
|
|
20
|
+
}
|
|
21
|
+
export interface BuiltinTemplate {
|
|
22
|
+
/** Unique identifier used in code and database */
|
|
23
|
+
id: string;
|
|
24
|
+
/** Display name shown in UI */
|
|
25
|
+
name: string;
|
|
26
|
+
/** Description of the workflow */
|
|
27
|
+
description: string;
|
|
28
|
+
/** Column names for the board UI */
|
|
29
|
+
columns: string[];
|
|
30
|
+
/** Workflow statuses for state tracking */
|
|
31
|
+
statuses: WorkflowTemplateStatus[];
|
|
32
|
+
/** Column mappings for work commands (start, ready, complete) */
|
|
33
|
+
columnSettings: ColumnSettings;
|
|
34
|
+
/** Whether to show in the init template picker (false for specialized templates) */
|
|
35
|
+
showInPicker: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* All builtin workflow templates.
|
|
39
|
+
*
|
|
40
|
+
* This is the SINGLE SOURCE OF TRUTH for:
|
|
41
|
+
* - Database seeding (seedBuiltinWorkflows reads from this)
|
|
42
|
+
* - UI template picker
|
|
43
|
+
* - Board column creation
|
|
44
|
+
* - Work command column mappings
|
|
45
|
+
*
|
|
46
|
+
* To add a new template:
|
|
47
|
+
* 1. Add it to this array
|
|
48
|
+
* 2. That's it - UI and DB will pick it up automatically
|
|
49
|
+
*/
|
|
50
|
+
export declare const BUILTIN_TEMPLATES: BuiltinTemplate[];
|
|
51
|
+
/**
|
|
52
|
+
* Get a template by ID.
|
|
53
|
+
*/
|
|
54
|
+
export declare function getBuiltinTemplate(id: string): BuiltinTemplate | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Get templates that should be shown in the init picker.
|
|
57
|
+
*/
|
|
58
|
+
export declare function getPickerTemplates(): BuiltinTemplate[];
|
|
59
|
+
/**
|
|
60
|
+
* Get columns for a template (for backward compatibility).
|
|
61
|
+
*/
|
|
62
|
+
export declare function getColumnsForTemplate(templateId: string): string[];
|
|
63
|
+
/**
|
|
64
|
+
* Get column settings for a template (for backward compatibility).
|
|
65
|
+
*/
|
|
66
|
+
export declare function getColumnSettingsForTemplate(templateId: string, columns: string[]): ColumnSettings;
|