@proletariat/cli 0.3.34 → 0.3.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/commands/agent/auth.d.ts +3 -1
  2. package/dist/commands/agent/auth.js +8 -11
  3. package/dist/commands/agent/index.js +11 -2
  4. package/dist/commands/agent/staff/add.d.ts +1 -0
  5. package/dist/commands/agent/staff/add.js +1 -0
  6. package/dist/commands/agent/staff/index.d.ts +15 -0
  7. package/dist/commands/agent/staff/index.js +83 -0
  8. package/dist/commands/agent/staff/list.d.ts +1 -0
  9. package/dist/commands/agent/staff/list.js +1 -0
  10. package/dist/commands/agent/staff/remove.d.ts +1 -0
  11. package/dist/commands/agent/staff/remove.js +1 -0
  12. package/dist/commands/agent/themes/add-names.d.ts +1 -0
  13. package/dist/commands/agent/themes/add-names.js +1 -0
  14. package/dist/commands/agent/themes/create.d.ts +1 -0
  15. package/dist/commands/agent/themes/create.js +1 -0
  16. package/dist/commands/agent/themes/index.d.ts +10 -0
  17. package/dist/commands/agent/themes/index.js +144 -0
  18. package/dist/commands/agent/themes/list.d.ts +1 -0
  19. package/dist/commands/agent/themes/list.js +1 -0
  20. package/dist/commands/agent/themes/set.d.ts +1 -0
  21. package/dist/commands/agent/themes/set.js +1 -0
  22. package/dist/commands/agents/themes/add-names.d.ts +1 -0
  23. package/dist/commands/agents/themes/add-names.js +1 -0
  24. package/dist/commands/agents/themes/create.d.ts +1 -0
  25. package/dist/commands/agents/themes/create.js +1 -0
  26. package/dist/commands/agents/themes/list.d.ts +1 -0
  27. package/dist/commands/agents/themes/list.js +1 -0
  28. package/dist/commands/category/list.js +1 -1
  29. package/dist/commands/label/create.d.ts +20 -0
  30. package/dist/commands/label/create.js +56 -0
  31. package/dist/commands/label/delete.d.ts +17 -0
  32. package/dist/commands/label/delete.js +31 -0
  33. package/dist/commands/label/group/create.d.ts +20 -0
  34. package/dist/commands/label/group/create.js +54 -0
  35. package/dist/commands/label/group/list.d.ts +14 -0
  36. package/dist/commands/label/group/list.js +51 -0
  37. package/dist/commands/label/index.d.ts +15 -0
  38. package/dist/commands/label/index.js +58 -0
  39. package/dist/commands/label/list.d.ts +16 -0
  40. package/dist/commands/label/list.js +82 -0
  41. package/dist/commands/link/list.js +3 -2
  42. package/dist/commands/mcp-server.js +2 -1
  43. package/dist/commands/phase/template/apply.d.ts +26 -0
  44. package/dist/commands/phase/template/apply.js +14 -0
  45. package/dist/commands/phase/template/create.d.ts +23 -0
  46. package/dist/commands/phase/template/create.js +14 -0
  47. package/dist/commands/phase/template/delete.d.ts +18 -0
  48. package/dist/commands/phase/template/delete.js +61 -0
  49. package/dist/commands/phase/template/list.d.ts +17 -0
  50. package/dist/commands/phase/template/list.js +88 -0
  51. package/dist/commands/phase/template/update.d.ts +1 -0
  52. package/dist/commands/phase/template/update.js +1 -0
  53. package/dist/commands/priority/add.js +1 -1
  54. package/dist/commands/project/update.js +0 -2
  55. package/dist/commands/roadmap/generate.js +1 -2
  56. package/dist/commands/session/health.js +1 -1
  57. package/dist/commands/spec/link/depends.d.ts +18 -0
  58. package/dist/commands/spec/link/depends.js +86 -0
  59. package/dist/commands/spec/link/index.d.ts +17 -0
  60. package/dist/commands/spec/link/index.js +92 -0
  61. package/dist/commands/spec/link/remove.d.ts +18 -0
  62. package/dist/commands/spec/link/remove.js +90 -0
  63. package/dist/commands/support/logs.js +2 -2
  64. package/dist/commands/template/apply.js +5 -4
  65. package/dist/commands/template/create.js +1 -1
  66. package/dist/commands/ticket/link/block.d.ts +15 -0
  67. package/dist/commands/ticket/link/block.js +95 -0
  68. package/dist/commands/ticket/link/index.d.ts +14 -0
  69. package/dist/commands/ticket/link/index.js +96 -0
  70. package/dist/commands/ticket/list.d.ts +1 -0
  71. package/dist/commands/ticket/list.js +6 -0
  72. package/dist/commands/ticket/resolve.js +1 -1
  73. package/dist/commands/ticket/template/apply.d.ts +26 -0
  74. package/dist/commands/ticket/template/apply.js +14 -0
  75. package/dist/commands/ticket/template/delete.d.ts +18 -0
  76. package/dist/commands/ticket/template/delete.js +61 -0
  77. package/dist/commands/ticket/template/list.d.ts +17 -0
  78. package/dist/commands/ticket/template/list.js +77 -0
  79. package/dist/commands/ticket/template/save.d.ts +17 -0
  80. package/dist/commands/ticket/template/save.js +97 -0
  81. package/dist/commands/ticket/view.d.ts +1 -0
  82. package/dist/commands/ticket/view.js +1 -0
  83. package/dist/commands/work/ready.js +17 -0
  84. package/dist/commands/work/resolve.js +1 -1
  85. package/dist/commands/work/spawn.js +4 -4
  86. package/dist/commands/work/start.d.ts +1 -0
  87. package/dist/commands/work/start.js +52 -17
  88. package/dist/lib/database/index.d.ts +1 -1
  89. package/dist/lib/database/index.js +20 -0
  90. package/dist/lib/execution/devcontainer.js +3 -1
  91. package/dist/lib/execution/runners.d.ts +7 -2
  92. package/dist/lib/execution/runners.js +18 -10
  93. package/dist/lib/execution/types.d.ts +1 -0
  94. package/dist/lib/flags/resolver.js +1 -0
  95. package/dist/lib/mcp/helpers.d.ts +1 -2
  96. package/dist/lib/mcp/tools/diet.js +1 -0
  97. package/dist/lib/mcp/tools/index.d.ts +1 -0
  98. package/dist/lib/mcp/tools/index.js +1 -0
  99. package/dist/lib/mcp/tools/label.d.ts +6 -0
  100. package/dist/lib/mcp/tools/label.js +338 -0
  101. package/dist/lib/mcp/tools/ticket.js +53 -17
  102. package/dist/lib/multiline-input.js +6 -18
  103. package/dist/lib/pmo/base-command.d.ts +0 -1
  104. package/dist/lib/pmo/base-command.js +0 -1
  105. package/dist/lib/pmo/schema.d.ts +6 -0
  106. package/dist/lib/pmo/schema.js +44 -0
  107. package/dist/lib/pmo/storage/base.d.ts +6 -0
  108. package/dist/lib/pmo/storage/base.js +116 -2
  109. package/dist/lib/pmo/storage/index.d.ts +23 -1
  110. package/dist/lib/pmo/storage/index.js +59 -1
  111. package/dist/lib/pmo/storage/labels.d.ts +55 -0
  112. package/dist/lib/pmo/storage/labels.js +346 -0
  113. package/dist/lib/pmo/storage/tickets.js +17 -0
  114. package/dist/lib/pmo/storage/types.d.ts +24 -0
  115. package/dist/lib/pmo/types.d.ts +44 -0
  116. package/dist/lib/pmo/utils.js +1 -1
  117. package/oclif.manifest.json +5702 -3660
  118. package/package.json +1 -1
@@ -41,6 +41,10 @@ export const PMO_TABLES = {
41
41
  // Roadmap tables (ordered collections of projects for documentation)
42
42
  roadmaps: 'pmo_roadmaps', // Named roadmap definitions
43
43
  roadmap_projects: 'pmo_roadmap_projects', // Many-to-many: roadmaps ↔ projects with ordering
44
+ // Label system tables
45
+ label_groups: 'pmo_label_groups',
46
+ labels: 'pmo_labels',
47
+ ticket_labels: 'pmo_ticket_labels',
44
48
  // Legacy tables (deprecated, kept for migration)
45
49
  columns: 'pmo_columns', // DEPRECATED: use workflow_statuses
46
50
  board_tickets: 'pmo_board_tickets', // DEPRECATED: tickets now use status_id directly
@@ -427,6 +431,37 @@ export const PMO_TABLE_SCHEMAS = {
427
431
  suggested_subtasks TEXT NOT NULL DEFAULT '[]',
428
432
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
429
433
  )`,
434
+ // Label groups (workspace-scoped grouping for labels)
435
+ label_groups: `
436
+ CREATE TABLE IF NOT EXISTS ${PMO_TABLES.label_groups} (
437
+ id TEXT PRIMARY KEY,
438
+ name TEXT NOT NULL UNIQUE,
439
+ description TEXT,
440
+ is_exclusive INTEGER NOT NULL DEFAULT 1,
441
+ is_required INTEGER NOT NULL DEFAULT 0,
442
+ position INTEGER NOT NULL DEFAULT 0,
443
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
444
+ )`,
445
+ // Labels (workspace-scoped, optionally grouped)
446
+ labels: `
447
+ CREATE TABLE IF NOT EXISTS ${PMO_TABLES.labels} (
448
+ id TEXT PRIMARY KEY,
449
+ name TEXT NOT NULL,
450
+ color TEXT,
451
+ description TEXT,
452
+ group_id TEXT REFERENCES ${PMO_TABLES.label_groups}(id) ON DELETE SET NULL,
453
+ position INTEGER NOT NULL DEFAULT 0,
454
+ is_builtin INTEGER NOT NULL DEFAULT 0,
455
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
456
+ UNIQUE(name, group_id)
457
+ )`,
458
+ // Junction table: ticket-label associations (replaces JSON labels field)
459
+ ticket_labels: `
460
+ CREATE TABLE IF NOT EXISTS ${PMO_TABLES.ticket_labels} (
461
+ ticket_id TEXT NOT NULL REFERENCES ${PMO_TABLES.tickets}(id) ON DELETE CASCADE,
462
+ label_id TEXT NOT NULL REFERENCES ${PMO_TABLES.labels}(id) ON DELETE CASCADE,
463
+ PRIMARY KEY (ticket_id, label_id)
464
+ )`,
430
465
  // Roadmap definitions (named collections of projects for documentation)
431
466
  roadmaps: `
432
467
  CREATE TABLE IF NOT EXISTS ${PMO_TABLES.roadmaps} (
@@ -502,6 +537,12 @@ export const PMO_INDEXES = `
502
537
  CREATE INDEX IF NOT EXISTS idx_pmo_roadmap_projects_position ON ${PMO_TABLES.roadmap_projects}(roadmap_id, position);
503
538
  CREATE INDEX IF NOT EXISTS idx_pmo_categories_type ON ${PMO_TABLES.categories}(type);
504
539
  CREATE INDEX IF NOT EXISTS idx_pmo_categories_position ON ${PMO_TABLES.categories}(type, position);
540
+ CREATE INDEX IF NOT EXISTS idx_pmo_label_groups_position ON ${PMO_TABLES.label_groups}(position);
541
+ CREATE INDEX IF NOT EXISTS idx_pmo_labels_group ON ${PMO_TABLES.labels}(group_id);
542
+ CREATE INDEX IF NOT EXISTS idx_pmo_labels_position ON ${PMO_TABLES.labels}(group_id, position);
543
+ CREATE INDEX IF NOT EXISTS idx_pmo_labels_builtin ON ${PMO_TABLES.labels}(is_builtin);
544
+ CREATE INDEX IF NOT EXISTS idx_pmo_ticket_labels_ticket ON ${PMO_TABLES.ticket_labels}(ticket_id);
545
+ CREATE INDEX IF NOT EXISTS idx_pmo_ticket_labels_label ON ${PMO_TABLES.ticket_labels}(label_id);
505
546
  `;
506
547
  // =============================================================================
507
548
  // Combined Schema
@@ -540,6 +581,9 @@ export const PMO_SCHEMA_SQL = [
540
581
  PMO_TABLE_SCHEMAS.id_sequences, // Sequence counters for ID generation
541
582
  PMO_TABLE_SCHEMAS.actions, // Work actions (reusable agent prompts)
542
583
  PMO_TABLE_SCHEMAS.ticket_templates, // Ticket templates for quick creation
584
+ PMO_TABLE_SCHEMAS.label_groups, // Label groups (before labels for FK)
585
+ PMO_TABLE_SCHEMAS.labels, // Labels (before ticket_labels for FK)
586
+ PMO_TABLE_SCHEMAS.ticket_labels, // Ticket-label junction table
543
587
  PMO_TABLE_SCHEMAS.roadmaps, // Named roadmap definitions
544
588
  PMO_TABLE_SCHEMAS.roadmap_projects, // Roadmap-to-project associations
545
589
  // Legacy tables (kept for migration, will be dropped after data migrated)
@@ -42,6 +42,12 @@ export declare function seedBuiltinCategories(db: Database.Database): void;
42
42
  * Preserves any existing user-defined priority scale.
43
43
  */
44
44
  export declare function seedDefaultPriorities(db: Database.Database): void;
45
+ /**
46
+ * Seed built-in label groups and labels.
47
+ * Creates Function, Type, and Area groups with their labels.
48
+ * Migrates existing ticket category values to Function labels.
49
+ */
50
+ export declare function seedBuiltinLabels(db: Database.Database): void;
45
51
  /**
46
52
  * Update board timestamp for a project.
47
53
  */
@@ -5,7 +5,7 @@
5
5
  import { PMO_TABLES, PMO_SCHEMA_SQL, validateTicketSchema } from '../schema.js';
6
6
  import { TICKET_CATEGORIES, STATE_CATEGORY_ORDER } from '../types.js';
7
7
  import { BUILTIN_TEMPLATES } from '../templates-builtin.js';
8
- import { getWorkspacePriorities, setWorkspacePriorities, DEFAULT_PRIORITIES } from '../utils.js';
8
+ import { setWorkspacePriorities, DEFAULT_PRIORITIES } from '../utils.js';
9
9
  const T = PMO_TABLES;
10
10
  /**
11
11
  * Initialize PMO tables in the database.
@@ -20,6 +20,7 @@ export function initializePMOTables(db) {
20
20
  seedBuiltinActions(db);
21
21
  seedBuiltinTicketTemplates(db);
22
22
  seedDefaultPriorities(db); // Seed default priority scale if not set
23
+ seedBuiltinLabels(db); // Seed built-in label groups and labels
23
24
  validateTicketSchema(db);
24
25
  }
25
26
  /**
@@ -538,6 +539,8 @@ Requirements:
538
539
  - Use \`--clear-subtasks\` if replacing existing subtasks
539
540
  - Use \`--clear-ac\` if replacing existing acceptance criteria
540
541
 
542
+ **Tip:** Use \`prlt ticket view <id>\` to see full ticket details at any time.
543
+
541
544
  After updating, output a brief summary of your grooming changes.`,
542
545
  suggestedForCategories: ['backlog'],
543
546
  defaultMoveToCategory: 'unstarted',
@@ -984,7 +987,6 @@ export function seedBuiltinCategories(db) {
984
987
  * Preserves any existing user-defined priority scale.
985
988
  */
986
989
  export function seedDefaultPriorities(db) {
987
- const existing = getWorkspacePriorities(db);
988
990
  // getWorkspacePriorities returns DEFAULT_PRIORITIES if not set,
989
991
  // but we need to check if it's actually stored in the DB
990
992
  const row = db.prepare(`SELECT value FROM ${T.settings} WHERE key = 'priorities'`).get();
@@ -993,6 +995,118 @@ export function seedDefaultPriorities(db) {
993
995
  setWorkspacePriorities(db, [...DEFAULT_PRIORITIES]);
994
996
  }
995
997
  }
998
+ /**
999
+ * Seed built-in label groups and labels.
1000
+ * Creates Function, Type, and Area groups with their labels.
1001
+ * Migrates existing ticket category values to Function labels.
1002
+ */
1003
+ export function seedBuiltinLabels(db) {
1004
+ const now = new Date().toISOString();
1005
+ const insertGroup = db.prepare(`
1006
+ INSERT OR IGNORE INTO ${T.label_groups} (id, name, description, is_exclusive, is_required, position, created_at)
1007
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1008
+ `);
1009
+ const insertLabel = db.prepare(`
1010
+ INSERT OR IGNORE INTO ${T.labels} (id, name, color, description, group_id, position, is_builtin, created_at)
1011
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?)
1012
+ `);
1013
+ // Built-in label groups
1014
+ const groups = [
1015
+ {
1016
+ id: 'function',
1017
+ name: 'Function',
1018
+ description: 'Business function category (diet enforcement)',
1019
+ isExclusive: true,
1020
+ isRequired: true,
1021
+ position: 0,
1022
+ labels: [
1023
+ { id: 'fn-ship', name: 'ship', description: 'Core product development', color: '#2da44e' },
1024
+ { id: 'fn-grow', name: 'grow', description: 'Marketing, adoption, community', color: '#bf8700' },
1025
+ { id: 'fn-support', name: 'support', description: 'Docs, error messages, onboarding', color: '#0969da' },
1026
+ { id: 'fn-bizops', name: 'bizops', description: 'Infrastructure, CI/CD, operations', color: '#8250df' },
1027
+ { id: 'fn-strategy', name: 'strategy', description: 'Design decisions, spikes, retros, planning', color: '#cf222e' },
1028
+ ],
1029
+ },
1030
+ {
1031
+ id: 'type',
1032
+ name: 'Type',
1033
+ description: 'Work type classification',
1034
+ isExclusive: true,
1035
+ isRequired: false,
1036
+ position: 1,
1037
+ labels: [
1038
+ { id: 'type-bug', name: 'bug', description: 'Bug fix', color: '#cf222e' },
1039
+ { id: 'type-feature', name: 'feature', description: 'New feature', color: '#2da44e' },
1040
+ { id: 'type-improvement', name: 'improvement', description: 'Enhancement to existing feature', color: '#0969da' },
1041
+ { id: 'type-task', name: 'task', description: 'General task', color: '#6e7781' },
1042
+ ],
1043
+ },
1044
+ {
1045
+ id: 'area',
1046
+ name: 'Area',
1047
+ description: 'System area (non-exclusive)',
1048
+ isExclusive: false,
1049
+ isRequired: false,
1050
+ position: 2,
1051
+ labels: [
1052
+ { id: 'area-cli', name: 'cli', description: 'CLI tool', color: '#0969da' },
1053
+ { id: 'area-pmo', name: 'pmo', description: 'Project management', color: '#8250df' },
1054
+ { id: 'area-agent', name: 'agent', description: 'Agent system', color: '#2da44e' },
1055
+ { id: 'area-docker', name: 'docker', description: 'Docker integration', color: '#bf8700' },
1056
+ { id: 'area-mcp', name: 'mcp', description: 'MCP server', color: '#cf222e' },
1057
+ { id: 'area-desktop', name: 'desktop', description: 'Desktop app', color: '#6e7781' },
1058
+ ],
1059
+ },
1060
+ ];
1061
+ for (const group of groups) {
1062
+ insertGroup.run(group.id, group.name, group.description, group.isExclusive ? 1 : 0, group.isRequired ? 1 : 0, group.position, now);
1063
+ for (let i = 0; i < group.labels.length; i++) {
1064
+ const label = group.labels[i];
1065
+ insertLabel.run(label.id, label.name, label.color, label.description, group.id, i, now);
1066
+ }
1067
+ }
1068
+ // Migrate existing category column values to Function label group
1069
+ // Only run if there are tickets with category set but no Function label yet
1070
+ migrateCategoryToFunctionLabels(db);
1071
+ }
1072
+ /**
1073
+ * Migrate existing ticket category values to Function label group entries.
1074
+ * Maps known category names to function labels (e.g., 'ship' -> fn-ship).
1075
+ * This is idempotent - only creates associations that don't exist yet.
1076
+ */
1077
+ function migrateCategoryToFunctionLabels(db) {
1078
+ // Map of old category values to function label IDs
1079
+ // The ticket's category field contains values like 'feature', 'bug', etc.
1080
+ // The diet system uses ship/grow/support/bizops/strategy which are different
1081
+ // from ticket categories. The diet categories map to the Function label group.
1082
+ // Since existing categories (feature, bug, etc.) don't map to Function labels,
1083
+ // we only migrate if the category field happens to contain a Function label name.
1084
+ const functionLabelMap = {
1085
+ ship: 'fn-ship',
1086
+ grow: 'fn-grow',
1087
+ support: 'fn-support',
1088
+ bizops: 'fn-bizops',
1089
+ strategy: 'fn-strategy',
1090
+ };
1091
+ const tickets = db.prepare(`
1092
+ SELECT id, category FROM ${T.tickets}
1093
+ WHERE category IS NOT NULL AND category != ''
1094
+ `).all();
1095
+ const insertTicketLabel = db.prepare(`
1096
+ INSERT OR IGNORE INTO ${T.ticket_labels} (ticket_id, label_id)
1097
+ VALUES (?, ?)
1098
+ `);
1099
+ for (const ticket of tickets) {
1100
+ const labelId = functionLabelMap[ticket.category.toLowerCase()];
1101
+ if (labelId) {
1102
+ // Check label exists before inserting
1103
+ const labelExists = db.prepare(`SELECT id FROM ${T.labels} WHERE id = ?`).get(labelId);
1104
+ if (labelExists) {
1105
+ insertTicketLabel.run(ticket.id, labelId);
1106
+ }
1107
+ }
1108
+ }
1109
+ }
996
1110
  /**
997
1111
  * Update board timestamp for a project.
998
1112
  */
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import Database from 'better-sqlite3';
11
11
  import { DrizzleDB } from '../../database/drizzle.js';
12
- import { AcceptanceCriterion, Board, BoardConfig, BoardView, BoardViewFilter, BoardViewFilters, Category, CategoryFilter, CategoryType, Column, CreateTicketInput, Epic, EpicDependency, EpicDependencyType, EpicFilter, PhaseFilter, PhaseTemplate, PhaseTemplateFilter, PMOStorage, Project, ProjectFilter, ProjectPhase, Roadmap, RoadmapFilter, RoadmapProject, Spec, SpecDependency, SpecDependencyType, SpecFilter, StateCategory, Subtask, SyncResult, SyncStatus, Ticket, TicketDependency, TicketDependencyType, TicketFilter, TicketTemplate, TicketTemplateFilter, WorkAction, WorkActionFilter, Workflow, WorkflowFilter, WorkflowStatus } from '../types.js';
12
+ import { AcceptanceCriterion, Board, BoardConfig, BoardView, BoardViewFilter, BoardViewFilters, Category, CategoryFilter, CategoryType, Column, CreateTicketInput, Epic, EpicDependency, EpicDependencyType, EpicFilter, PhaseFilter, PhaseTemplate, PhaseTemplateFilter, PMOStorage, Project, ProjectFilter, ProjectPhase, Roadmap, RoadmapFilter, RoadmapProject, Spec, SpecDependency, SpecDependencyType, SpecFilter, StateCategory, Subtask, SyncResult, SyncStatus, Ticket, TicketDependency, TicketDependencyType, TicketFilter, TicketTemplate, TicketTemplateFilter, WorkAction, WorkActionFilter, Workflow, WorkflowFilter, WorkflowStatus, Label, LabelFilter, LabelGroup, LabelGroupFilter } from '../types.js';
13
13
  export declare class SQLiteStorage implements PMOStorage {
14
14
  readonly type: "sqlite";
15
15
  private db;
@@ -29,6 +29,7 @@ export declare class SQLiteStorage implements PMOStorage {
29
29
  private viewStorage;
30
30
  private roadmapStorage;
31
31
  private categoryStorage;
32
+ private labelStorage;
32
33
  constructor(dbPath: string);
33
34
  /**
34
35
  * Get the underlying database connection.
@@ -203,6 +204,27 @@ export declare class SQLiteStorage implements PMOStorage {
203
204
  deleteCategory(id: string): Promise<void>;
204
205
  getCategoryNames(type: CategoryType): Promise<string[]>;
205
206
  isValidCategory(name: string, type: CategoryType): Promise<boolean>;
207
+ listLabelGroups(filter?: LabelGroupFilter): Promise<LabelGroup[]>;
208
+ getLabelGroup(id: string): Promise<LabelGroup | null>;
209
+ getLabelGroupByName(name: string): Promise<LabelGroup | null>;
210
+ createLabelGroup(group: Partial<LabelGroup> & {
211
+ name: string;
212
+ }): Promise<LabelGroup>;
213
+ updateLabelGroup(id: string, changes: Partial<LabelGroup>): Promise<LabelGroup>;
214
+ deleteLabelGroup(id: string): Promise<void>;
215
+ listLabels(filter?: LabelFilter): Promise<Label[]>;
216
+ getLabel(id: string): Promise<Label | null>;
217
+ getLabelByName(name: string, groupId?: string): Promise<Label | null>;
218
+ createLabel(label: Partial<Label> & {
219
+ name: string;
220
+ }): Promise<Label>;
221
+ updateLabel(id: string, changes: Partial<Label>): Promise<Label>;
222
+ deleteLabel(id: string): Promise<void>;
223
+ addLabelToTicket(ticketId: string, labelId: string): Promise<void>;
224
+ removeLabelFromTicket(ticketId: string, labelId: string): Promise<void>;
225
+ getLabelsForTicket(ticketId: string): Promise<Label[]>;
226
+ addLabelToTicketByName(ticketId: string, labelName: string): Promise<void>;
227
+ removeLabelFromTicketByName(ticketId: string, labelName: string): Promise<void>;
206
228
  pull(): Promise<SyncResult>;
207
229
  push(): Promise<SyncResult>;
208
230
  status(): Promise<SyncStatus>;
@@ -10,7 +10,7 @@
10
10
  import Database from 'better-sqlite3';
11
11
  import { createDrizzleConnection } from '../../database/drizzle.js';
12
12
  import { PMO_TABLES, PMO_SCHEMA_SQL, validateTicketSchema } from '../schema.js';
13
- import { runMigrations, seedBuiltinWorkflows, seedBuiltinPhases, seedBuiltinPhaseTemplates, seedBuiltinActions, seedBuiltinTicketTemplates, seedBuiltinCategories, updateBoardTimestamp, } from './base.js';
13
+ import { runMigrations, seedBuiltinWorkflows, seedBuiltinPhases, seedBuiltinPhaseTemplates, seedBuiltinActions, seedBuiltinTicketTemplates, seedBuiltinCategories, seedBuiltinLabels, updateBoardTimestamp, } from './base.js';
14
14
  import { ProjectStorage } from './projects.js';
15
15
  import { TicketStorage } from './tickets.js';
16
16
  import { SubtaskStorage, AcceptanceCriteriaStorage } from './subtasks.js';
@@ -24,6 +24,7 @@ import { ActionStorage } from './actions.js';
24
24
  import { ViewStorage } from './views.js';
25
25
  import { RoadmapStorage } from './roadmaps.js';
26
26
  import { CategoryStorage } from './categories.js';
27
+ import { LabelStorage } from './labels.js';
27
28
  const T = PMO_TABLES;
28
29
  export class SQLiteStorage {
29
30
  type = 'sqlite';
@@ -45,6 +46,7 @@ export class SQLiteStorage {
45
46
  viewStorage;
46
47
  roadmapStorage;
47
48
  categoryStorage;
49
+ labelStorage;
48
50
  constructor(dbPath) {
49
51
  this.dbPath = dbPath;
50
52
  // Open database (creates if doesn't exist)
@@ -74,6 +76,7 @@ export class SQLiteStorage {
74
76
  this.viewStorage = new ViewStorage(ctx);
75
77
  this.roadmapStorage = new RoadmapStorage(ctx);
76
78
  this.categoryStorage = new CategoryStorage(ctx);
79
+ this.labelStorage = new LabelStorage(ctx);
77
80
  // Ensure PMO tables exist
78
81
  this.ensurePMOTables();
79
82
  }
@@ -104,6 +107,7 @@ export class SQLiteStorage {
104
107
  seedBuiltinActions(this.db);
105
108
  seedBuiltinTicketTemplates(this.db);
106
109
  seedBuiltinCategories(this.db);
110
+ seedBuiltinLabels(this.db);
107
111
  // Validate schema
108
112
  validateTicketSchema(this.db);
109
113
  }
@@ -616,6 +620,60 @@ export class SQLiteStorage {
616
620
  return this.categoryStorage.isValidCategory(name, type);
617
621
  }
618
622
  // ===========================================================================
623
+ // Label Operations
624
+ // ===========================================================================
625
+ async listLabelGroups(filter) {
626
+ return this.labelStorage.listLabelGroups(filter);
627
+ }
628
+ async getLabelGroup(id) {
629
+ return this.labelStorage.getLabelGroup(id);
630
+ }
631
+ async getLabelGroupByName(name) {
632
+ return this.labelStorage.getLabelGroupByName(name);
633
+ }
634
+ async createLabelGroup(group) {
635
+ return this.labelStorage.createLabelGroup(group);
636
+ }
637
+ async updateLabelGroup(id, changes) {
638
+ return this.labelStorage.updateLabelGroup(id, changes);
639
+ }
640
+ async deleteLabelGroup(id) {
641
+ return this.labelStorage.deleteLabelGroup(id);
642
+ }
643
+ async listLabels(filter) {
644
+ return this.labelStorage.listLabels(filter);
645
+ }
646
+ async getLabel(id) {
647
+ return this.labelStorage.getLabel(id);
648
+ }
649
+ async getLabelByName(name, groupId) {
650
+ return this.labelStorage.getLabelByName(name, groupId);
651
+ }
652
+ async createLabel(label) {
653
+ return this.labelStorage.createLabel(label);
654
+ }
655
+ async updateLabel(id, changes) {
656
+ return this.labelStorage.updateLabel(id, changes);
657
+ }
658
+ async deleteLabel(id) {
659
+ return this.labelStorage.deleteLabel(id);
660
+ }
661
+ async addLabelToTicket(ticketId, labelId) {
662
+ return this.labelStorage.addLabelToTicket(ticketId, labelId);
663
+ }
664
+ async removeLabelFromTicket(ticketId, labelId) {
665
+ return this.labelStorage.removeLabelFromTicket(ticketId, labelId);
666
+ }
667
+ async getLabelsForTicket(ticketId) {
668
+ return this.labelStorage.getLabelsForTicket(ticketId);
669
+ }
670
+ async addLabelToTicketByName(ticketId, labelName) {
671
+ return this.labelStorage.addLabelToTicketByName(ticketId, labelName);
672
+ }
673
+ async removeLabelFromTicketByName(ticketId, labelName) {
674
+ return this.labelStorage.removeLabelFromTicketByName(ticketId, labelName);
675
+ }
676
+ // ===========================================================================
619
677
  // Sync Operations (no-op for pure SQLite)
620
678
  // ===========================================================================
621
679
  async pull() {
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Label storage operations for PMO.
3
+ * Handles CRUD for labels, label groups, and ticket-label associations.
4
+ * Enforces group exclusivity constraints.
5
+ */
6
+ import { Label, LabelFilter, LabelGroup, LabelGroupFilter } from '../types.js';
7
+ import { StorageContext } from './types.js';
8
+ export declare class LabelStorage {
9
+ private ctx;
10
+ constructor(ctx: StorageContext);
11
+ listLabelGroups(filter?: LabelGroupFilter): Promise<LabelGroup[]>;
12
+ getLabelGroup(id: string): Promise<LabelGroup | null>;
13
+ getLabelGroupByName(name: string): Promise<LabelGroup | null>;
14
+ createLabelGroup(group: Partial<LabelGroup> & {
15
+ name: string;
16
+ }): Promise<LabelGroup>;
17
+ updateLabelGroup(id: string, changes: Partial<LabelGroup>): Promise<LabelGroup>;
18
+ deleteLabelGroup(id: string): Promise<void>;
19
+ listLabels(filter?: LabelFilter): Promise<Label[]>;
20
+ getLabel(id: string): Promise<Label | null>;
21
+ getLabelByName(name: string, groupId?: string): Promise<Label | null>;
22
+ createLabel(label: Partial<Label> & {
23
+ name: string;
24
+ }): Promise<Label>;
25
+ updateLabel(id: string, changes: Partial<Label>): Promise<Label>;
26
+ deleteLabel(id: string): Promise<void>;
27
+ /**
28
+ * Add a label to a ticket.
29
+ * Enforces group exclusivity: if the label belongs to an exclusive group,
30
+ * removes any existing label from the same group first.
31
+ */
32
+ addLabelToTicket(ticketId: string, labelId: string): Promise<void>;
33
+ /**
34
+ * Remove a label from a ticket.
35
+ */
36
+ removeLabelFromTicket(ticketId: string, labelId: string): Promise<void>;
37
+ /**
38
+ * Get all labels for a ticket.
39
+ */
40
+ getLabelsForTicket(ticketId: string): Promise<Label[]>;
41
+ /**
42
+ * Add a label to a ticket by name.
43
+ * Resolves the label name to an ID first.
44
+ * If the label name matches a group:label pattern, resolves within that group.
45
+ */
46
+ addLabelToTicketByName(ticketId: string, labelName: string): Promise<void>;
47
+ /**
48
+ * Remove a label from a ticket by name.
49
+ */
50
+ removeLabelFromTicketByName(ticketId: string, labelName: string): Promise<void>;
51
+ /**
52
+ * Resolve a label by name or group:name pattern.
53
+ */
54
+ private resolveLabelByName;
55
+ }