@proletariat/cli 0.3.50 → 0.3.52

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 (87) hide show
  1. package/dist/commands/agent/status.js +1 -0
  2. package/dist/commands/asana/connect.d.ts +15 -0
  3. package/dist/commands/asana/connect.js +267 -0
  4. package/dist/commands/asana/sync.d.ts +15 -0
  5. package/dist/commands/asana/sync.js +189 -0
  6. package/dist/commands/config/index.js +7 -1
  7. package/dist/commands/execution/list.js +3 -0
  8. package/dist/commands/execution/view.js +10 -0
  9. package/dist/commands/monday/connect.d.ts +16 -0
  10. package/dist/commands/monday/connect.js +212 -0
  11. package/dist/commands/monday/sync.d.ts +14 -0
  12. package/dist/commands/monday/sync.js +178 -0
  13. package/dist/commands/orchestrator/start.d.ts +6 -0
  14. package/dist/commands/orchestrator/start.js +149 -11
  15. package/dist/commands/session/list.js +6 -5
  16. package/dist/commands/work/index.js +7 -0
  17. package/dist/commands/work/jira.d.ts +28 -0
  18. package/dist/commands/work/jira.js +225 -0
  19. package/dist/commands/work/source/set.d.ts +12 -0
  20. package/dist/commands/work/source/set.js +52 -0
  21. package/dist/commands/work/source.d.ts +11 -0
  22. package/dist/commands/work/source.js +53 -0
  23. package/dist/commands/work/spawn.d.ts +1 -0
  24. package/dist/commands/work/spawn.js +73 -8
  25. package/dist/commands/work/start.d.ts +8 -0
  26. package/dist/commands/work/start.js +241 -3
  27. package/dist/lib/asana/client.d.ts +15 -0
  28. package/dist/lib/asana/client.js +120 -0
  29. package/dist/lib/asana/config.d.ts +9 -0
  30. package/dist/lib/asana/config.js +61 -0
  31. package/dist/lib/asana/index.d.ts +5 -0
  32. package/dist/lib/asana/index.js +4 -0
  33. package/dist/lib/asana/mapper.d.ts +13 -0
  34. package/dist/lib/asana/mapper.js +70 -0
  35. package/dist/lib/asana/sync.d.ts +13 -0
  36. package/dist/lib/asana/sync.js +36 -0
  37. package/dist/lib/asana/types.d.ts +40 -0
  38. package/dist/lib/asana/types.js +1 -0
  39. package/dist/lib/database/drizzle-schema.d.ts +393 -0
  40. package/dist/lib/database/drizzle-schema.js +45 -0
  41. package/dist/lib/execution/config.d.ts +10 -0
  42. package/dist/lib/execution/config.js +19 -0
  43. package/dist/lib/execution/runners.d.ts +10 -0
  44. package/dist/lib/execution/runners.js +110 -1
  45. package/dist/lib/execution/spawner.js +26 -0
  46. package/dist/lib/execution/storage.d.ts +4 -0
  47. package/dist/lib/execution/storage.js +8 -3
  48. package/dist/lib/execution/types.d.ts +4 -0
  49. package/dist/lib/external-issues/adapters.d.ts +18 -1
  50. package/dist/lib/external-issues/adapters.js +49 -1
  51. package/dist/lib/external-issues/index.d.ts +4 -1
  52. package/dist/lib/external-issues/index.js +5 -0
  53. package/dist/lib/external-issues/jira.d.ts +23 -0
  54. package/dist/lib/external-issues/jira.js +223 -0
  55. package/dist/lib/external-issues/linear.js +4 -3
  56. package/dist/lib/external-issues/mapper.d.ts +3 -2
  57. package/dist/lib/external-issues/mapper.js +5 -2
  58. package/dist/lib/external-issues/mapping-store.d.ts +12 -0
  59. package/dist/lib/external-issues/mapping-store.js +164 -0
  60. package/dist/lib/external-issues/types.d.ts +34 -0
  61. package/dist/lib/external-issues/validation.js +11 -0
  62. package/dist/lib/external-issues/work-start.d.ts +10 -0
  63. package/dist/lib/external-issues/work-start.js +12 -0
  64. package/dist/lib/linear/mapper.d.ts +2 -0
  65. package/dist/lib/linear/mapper.js +66 -2
  66. package/dist/lib/monday/client.d.ts +14 -0
  67. package/dist/lib/monday/client.js +113 -0
  68. package/dist/lib/monday/config.d.ts +10 -0
  69. package/dist/lib/monday/config.js +64 -0
  70. package/dist/lib/monday/index.d.ts +5 -0
  71. package/dist/lib/monday/index.js +4 -0
  72. package/dist/lib/monday/mapper.d.ts +14 -0
  73. package/dist/lib/monday/mapper.js +89 -0
  74. package/dist/lib/monday/sync.d.ts +13 -0
  75. package/dist/lib/monday/sync.js +45 -0
  76. package/dist/lib/monday/types.d.ts +38 -0
  77. package/dist/lib/monday/types.js +4 -0
  78. package/dist/lib/pmo/schema.d.ts +10 -1
  79. package/dist/lib/pmo/schema.js +73 -0
  80. package/dist/lib/pmo/storage/base.js +32 -0
  81. package/dist/lib/prompt-json.d.ts +11 -0
  82. package/dist/lib/work-source/config.d.ts +14 -0
  83. package/dist/lib/work-source/config.js +70 -0
  84. package/dist/lib/work-source/index.d.ts +1 -0
  85. package/dist/lib/work-source/index.js +1 -0
  86. package/oclif.manifest.json +2531 -1964
  87. package/package.json +1 -1
@@ -0,0 +1,89 @@
1
+ import { PMO_TABLES } from '../pmo/schema.js';
2
+ export class MondayMapper {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ this.ensureTable();
7
+ }
8
+ ensureTable() {
9
+ this.db.exec(`
10
+ CREATE TABLE IF NOT EXISTS ${PMO_TABLES.monday_item_map} (
11
+ pmo_ticket_id TEXT NOT NULL REFERENCES ${PMO_TABLES.tickets}(id) ON DELETE CASCADE,
12
+ monday_board_id TEXT NOT NULL,
13
+ monday_item_id TEXT NOT NULL,
14
+ monday_item_name TEXT NOT NULL,
15
+ monday_item_url TEXT,
16
+ sync_direction TEXT NOT NULL DEFAULT 'outbound',
17
+ last_synced_at TIMESTAMP,
18
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
19
+ PRIMARY KEY (pmo_ticket_id),
20
+ UNIQUE (monday_item_id)
21
+ )
22
+ `);
23
+ this.db.exec(`
24
+ CREATE INDEX IF NOT EXISTS idx_pmo_monday_item_map_item_id
25
+ ON ${PMO_TABLES.monday_item_map}(monday_item_id)
26
+ `);
27
+ this.db.exec(`
28
+ CREATE INDEX IF NOT EXISTS idx_pmo_monday_item_map_board_id
29
+ ON ${PMO_TABLES.monday_item_map}(monday_board_id)
30
+ `);
31
+ }
32
+ createOrUpdateMapping(map) {
33
+ this.db.prepare(`
34
+ INSERT INTO ${PMO_TABLES.monday_item_map}
35
+ (pmo_ticket_id, monday_board_id, monday_item_id, monday_item_name, monday_item_url, sync_direction, last_synced_at, created_at)
36
+ VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
37
+ ON CONFLICT(pmo_ticket_id) DO UPDATE SET
38
+ monday_board_id = excluded.monday_board_id,
39
+ monday_item_id = excluded.monday_item_id,
40
+ monday_item_name = excluded.monday_item_name,
41
+ monday_item_url = excluded.monday_item_url,
42
+ sync_direction = excluded.sync_direction,
43
+ last_synced_at = CURRENT_TIMESTAMP
44
+ `).run(map.pmoTicketId, map.mondayBoardId, map.mondayItemId, map.mondayItemName, map.mondayItemUrl ?? null, map.syncDirection);
45
+ }
46
+ getByTicketId(ticketId) {
47
+ const row = this.db.prepare(`
48
+ SELECT * FROM ${PMO_TABLES.monday_item_map} WHERE pmo_ticket_id = ?
49
+ `).get(ticketId);
50
+ return row ? this.rowToMap(row) : null;
51
+ }
52
+ getByMondayItemId(itemId) {
53
+ const row = this.db.prepare(`
54
+ SELECT * FROM ${PMO_TABLES.monday_item_map} WHERE monday_item_id = ?
55
+ `).get(itemId);
56
+ return row ? this.rowToMap(row) : null;
57
+ }
58
+ listMappings() {
59
+ const rows = this.db.prepare(`
60
+ SELECT * FROM ${PMO_TABLES.monday_item_map} ORDER BY created_at DESC
61
+ `).all();
62
+ return rows.map((row) => this.rowToMap(row));
63
+ }
64
+ updateSyncTimestamp(ticketId) {
65
+ this.db.prepare(`
66
+ UPDATE ${PMO_TABLES.monday_item_map}
67
+ SET last_synced_at = CURRENT_TIMESTAMP
68
+ WHERE pmo_ticket_id = ?
69
+ `).run(ticketId);
70
+ }
71
+ deleteMapping(ticketId) {
72
+ this.db.prepare(`
73
+ DELETE FROM ${PMO_TABLES.monday_item_map}
74
+ WHERE pmo_ticket_id = ?
75
+ `).run(ticketId);
76
+ }
77
+ rowToMap(row) {
78
+ return {
79
+ pmoTicketId: row.pmo_ticket_id,
80
+ mondayBoardId: row.monday_board_id,
81
+ mondayItemId: row.monday_item_id,
82
+ mondayItemName: row.monday_item_name,
83
+ mondayItemUrl: row.monday_item_url ?? undefined,
84
+ syncDirection: row.sync_direction,
85
+ lastSyncedAt: row.last_synced_at ? new Date(row.last_synced_at) : undefined,
86
+ createdAt: new Date(row.created_at),
87
+ };
88
+ }
89
+ }
@@ -0,0 +1,13 @@
1
+ import type { Ticket } from '../pmo/types.js';
2
+ import { MondayClient } from './client.js';
3
+ import { MondayMapper } from './mapper.js';
4
+ export declare class MondaySync {
5
+ private client;
6
+ private mapper;
7
+ constructor(client: MondayClient, mapper: MondayMapper);
8
+ syncTicket(ticket: Ticket, boardId: string): Promise<{
9
+ created: boolean;
10
+ itemId: string;
11
+ }>;
12
+ ticketToItemName(ticket: Ticket): string;
13
+ }
@@ -0,0 +1,45 @@
1
+ export class MondaySync {
2
+ client;
3
+ mapper;
4
+ constructor(client, mapper) {
5
+ this.client = client;
6
+ this.mapper = mapper;
7
+ }
8
+ async syncTicket(ticket, boardId) {
9
+ const itemName = this.ticketToItemName(ticket);
10
+ const mapping = this.mapper.getByTicketId(ticket.id);
11
+ if (mapping && mapping.mondayBoardId === boardId) {
12
+ if (mapping.mondayItemName !== itemName) {
13
+ await this.client.updateItemName(boardId, mapping.mondayItemId, itemName);
14
+ }
15
+ this.mapper.createOrUpdateMapping({
16
+ pmoTicketId: ticket.id,
17
+ mondayBoardId: boardId,
18
+ mondayItemId: mapping.mondayItemId,
19
+ mondayItemName: itemName,
20
+ mondayItemUrl: mapping.mondayItemUrl,
21
+ syncDirection: 'outbound',
22
+ });
23
+ return {
24
+ created: false,
25
+ itemId: mapping.mondayItemId,
26
+ };
27
+ }
28
+ const createdItem = await this.client.createItem(boardId, itemName);
29
+ this.mapper.createOrUpdateMapping({
30
+ pmoTicketId: ticket.id,
31
+ mondayBoardId: boardId,
32
+ mondayItemId: createdItem.id,
33
+ mondayItemName: createdItem.name,
34
+ mondayItemUrl: createdItem.url,
35
+ syncDirection: 'outbound',
36
+ });
37
+ return {
38
+ created: true,
39
+ itemId: createdItem.id,
40
+ };
41
+ }
42
+ ticketToItemName(ticket) {
43
+ return `${ticket.id}: ${ticket.title}`;
44
+ }
45
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Monday.com Integration Types
3
+ */
4
+ export interface MondayBoard {
5
+ id: string;
6
+ name: string;
7
+ url?: string;
8
+ }
9
+ export interface MondayItem {
10
+ id: string;
11
+ name: string;
12
+ url?: string;
13
+ }
14
+ export interface MondayConfig {
15
+ apiToken: string;
16
+ boardId?: string;
17
+ boardName?: string;
18
+ accountName?: string;
19
+ }
20
+ export interface MondayItemMap {
21
+ pmoTicketId: string;
22
+ mondayBoardId: string;
23
+ mondayItemId: string;
24
+ mondayItemName: string;
25
+ mondayItemUrl?: string;
26
+ syncDirection: 'outbound' | 'bidirectional';
27
+ lastSyncedAt?: Date;
28
+ createdAt: Date;
29
+ }
30
+ export interface MondaySyncResult {
31
+ synced: number;
32
+ created: number;
33
+ skipped: number;
34
+ errors: Array<{
35
+ ticketId: string;
36
+ error: string;
37
+ }>;
38
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Monday.com Integration Types
3
+ */
4
+ export {};
@@ -39,6 +39,11 @@ export declare const PMO_TABLES: {
39
39
  readonly labels: "pmo_labels";
40
40
  readonly ticket_labels: "pmo_ticket_labels";
41
41
  readonly linear_issue_map: "pmo_linear_issue_map";
42
+ readonly external_execution_map: "pmo_external_execution_map";
43
+ readonly external_execution_links: "pmo_external_execution_links";
44
+ readonly external_execution_prs: "pmo_external_execution_prs";
45
+ readonly monday_item_map: "pmo_monday_item_map";
46
+ readonly asana_task_map: "pmo_asana_task_map";
42
47
  readonly columns: "pmo_columns";
43
48
  readonly board_tickets: "pmo_board_tickets";
44
49
  readonly statuses: "pmo_statuses";
@@ -67,7 +72,7 @@ export declare const PMO_TABLE_SCHEMAS: {
67
72
  readonly project_specs: "\n CREATE TABLE IF NOT EXISTS pmo_project_specs (\n project_id TEXT NOT NULL REFERENCES pmo_projects(id) ON DELETE CASCADE,\n spec_id TEXT NOT NULL REFERENCES pmo_specs(id) ON DELETE CASCADE,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (project_id, spec_id)\n )";
68
73
  readonly cache_metadata: "\n CREATE TABLE IF NOT EXISTS pmo_cache_metadata (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n )";
69
74
  readonly settings: "\n CREATE TABLE IF NOT EXISTS pmo_settings (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n )";
70
- readonly agent_work: "\n CREATE TABLE IF NOT EXISTS agent_work (\n id TEXT PRIMARY KEY,\n ticket_id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n executor TEXT NOT NULL,\n environment TEXT NOT NULL DEFAULT 'host',\n display_mode TEXT NOT NULL DEFAULT 'terminal',\n permission_mode TEXT NOT NULL DEFAULT 'safe',\n status TEXT NOT NULL DEFAULT 'starting',\n branch TEXT,\n pid TEXT,\n container_id TEXT,\n session_id TEXT,\n host TEXT,\n log_path TEXT,\n started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n completed_at TIMESTAMP,\n exit_code INTEGER,\n error_message TEXT,\n FOREIGN KEY (ticket_id) REFERENCES pmo_tickets(id) ON DELETE CASCADE\n )";
75
+ readonly agent_work: "\n CREATE TABLE IF NOT EXISTS agent_work (\n id TEXT PRIMARY KEY,\n ticket_id TEXT NOT NULL,\n agent_name TEXT NOT NULL,\n executor TEXT NOT NULL,\n environment TEXT NOT NULL DEFAULT 'host',\n display_mode TEXT NOT NULL DEFAULT 'terminal',\n permission_mode TEXT NOT NULL DEFAULT 'safe',\n status TEXT NOT NULL DEFAULT 'starting',\n branch TEXT,\n pid TEXT,\n container_id TEXT,\n session_id TEXT,\n host TEXT,\n log_path TEXT,\n external_source TEXT,\n external_key TEXT,\n external_id TEXT,\n external_url TEXT,\n started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n completed_at TIMESTAMP,\n exit_code INTEGER,\n error_message TEXT,\n FOREIGN KEY (ticket_id) REFERENCES pmo_tickets(id) ON DELETE CASCADE\n )";
71
76
  readonly containers: "\n CREATE TABLE IF NOT EXISTS containers (\n id TEXT PRIMARY KEY,\n agent_name TEXT NOT NULL,\n docker_id TEXT NOT NULL,\n docker_name TEXT,\n image TEXT,\n status TEXT NOT NULL DEFAULT 'unknown',\n current_execution_id TEXT,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n last_seen_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (agent_name) REFERENCES agents(name) ON DELETE CASCADE,\n FOREIGN KEY (current_execution_id) REFERENCES agent_work(id) ON DELETE SET NULL\n )";
72
77
  readonly id_sequences: "\n CREATE TABLE IF NOT EXISTS id_sequences (\n table_name TEXT PRIMARY KEY,\n next_id INTEGER NOT NULL DEFAULT 1\n )";
73
78
  readonly statuses: "\n CREATE TABLE IF NOT EXISTS pmo_statuses (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n name TEXT NOT NULL,\n category TEXT NOT NULL,\n position INTEGER NOT NULL DEFAULT 0,\n color TEXT,\n description TEXT,\n is_default INTEGER NOT NULL DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (project_id) REFERENCES pmo_projects(id) ON DELETE CASCADE,\n UNIQUE(project_id, name)\n )";
@@ -81,6 +86,10 @@ export declare const PMO_TABLE_SCHEMAS: {
81
86
  readonly roadmaps: "\n CREATE TABLE IF NOT EXISTS pmo_roadmaps (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n is_default INTEGER NOT NULL DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )";
82
87
  readonly roadmap_projects: "\n CREATE TABLE IF NOT EXISTS pmo_roadmap_projects (\n roadmap_id TEXT NOT NULL REFERENCES pmo_roadmaps(id) ON DELETE CASCADE,\n project_id TEXT NOT NULL REFERENCES pmo_projects(id) ON DELETE CASCADE,\n position INTEGER NOT NULL DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (roadmap_id, project_id)\n )";
83
88
  readonly linear_issue_map: "\n CREATE TABLE IF NOT EXISTS pmo_linear_issue_map (\n pmo_ticket_id TEXT NOT NULL REFERENCES pmo_tickets(id) ON DELETE CASCADE,\n linear_issue_id TEXT NOT NULL,\n linear_identifier TEXT NOT NULL,\n linear_team_key TEXT NOT NULL,\n linear_url TEXT NOT NULL,\n sync_direction TEXT NOT NULL DEFAULT 'inbound',\n last_synced_at TIMESTAMP,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (pmo_ticket_id),\n UNIQUE (linear_issue_id)\n )";
89
+ readonly external_execution_map: "\n CREATE TABLE IF NOT EXISTS pmo_external_execution_map (\n provider TEXT NOT NULL CHECK (provider IN ('linear', 'jira', 'asana', 'monday', 'pmo')),\n external_id TEXT NOT NULL,\n external_key TEXT,\n canonical_url TEXT,\n latest_state_snapshot TEXT,\n last_synced_at TIMESTAMP,\n last_spawned_at TIMESTAMP,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (provider, external_id)\n )";
90
+ readonly external_execution_links: "\n CREATE TABLE IF NOT EXISTS pmo_external_execution_links (\n provider TEXT NOT NULL,\n external_id TEXT NOT NULL,\n execution_id TEXT NOT NULL,\n linked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (provider, external_id, execution_id),\n FOREIGN KEY (provider, external_id)\n REFERENCES pmo_external_execution_map(provider, external_id)\n ON DELETE CASCADE\n )";
91
+ readonly external_execution_prs: "\n CREATE TABLE IF NOT EXISTS pmo_external_execution_prs (\n provider TEXT NOT NULL,\n external_id TEXT NOT NULL,\n pr_url TEXT NOT NULL,\n linked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (provider, external_id, pr_url),\n FOREIGN KEY (provider, external_id)\n REFERENCES pmo_external_execution_map(provider, external_id)\n ON DELETE CASCADE\n )";
92
+ readonly monday_item_map: "\n CREATE TABLE IF NOT EXISTS pmo_monday_item_map (\n pmo_ticket_id TEXT NOT NULL REFERENCES pmo_tickets(id) ON DELETE CASCADE,\n monday_board_id TEXT NOT NULL,\n monday_item_id TEXT NOT NULL,\n monday_item_name TEXT NOT NULL,\n monday_item_url TEXT,\n sync_direction TEXT NOT NULL DEFAULT 'outbound',\n last_synced_at TIMESTAMP,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (pmo_ticket_id),\n UNIQUE (monday_item_id)\n )";
84
93
  };
85
94
  export declare const PMO_INDEXES: string;
86
95
  /**
@@ -47,6 +47,14 @@ export const PMO_TABLES = {
47
47
  ticket_labels: 'pmo_ticket_labels',
48
48
  // Linear integration tables
49
49
  linear_issue_map: 'pmo_linear_issue_map', // Linear issue ↔ PMO ticket mapping
50
+ // Provider-agnostic external issue/execution mapping
51
+ external_execution_map: 'pmo_external_execution_map',
52
+ external_execution_links: 'pmo_external_execution_links',
53
+ external_execution_prs: 'pmo_external_execution_prs',
54
+ // Monday.com integration tables
55
+ monday_item_map: 'pmo_monday_item_map', // Monday item ↔ PMO ticket mapping
56
+ // Asana integration tables
57
+ asana_task_map: 'pmo_asana_task_map', // Asana task ↔ PMO ticket mapping
50
58
  // Legacy tables (deprecated, kept for migration)
51
59
  columns: 'pmo_columns', // DEPRECATED: use workflow_statuses
52
60
  board_tickets: 'pmo_board_tickets', // DEPRECATED: tickets now use status_id directly
@@ -336,6 +344,10 @@ export const PMO_TABLE_SCHEMAS = {
336
344
  session_id TEXT,
337
345
  host TEXT,
338
346
  log_path TEXT,
347
+ external_source TEXT,
348
+ external_key TEXT,
349
+ external_id TEXT,
350
+ external_url TEXT,
339
351
  started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
340
352
  completed_at TIMESTAMP,
341
353
  exit_code INTEGER,
@@ -498,6 +510,58 @@ export const PMO_TABLE_SCHEMAS = {
498
510
  PRIMARY KEY (pmo_ticket_id),
499
511
  UNIQUE (linear_issue_id)
500
512
  )`,
513
+ // Provider-agnostic external issue ↔ execution mapping
514
+ external_execution_map: `
515
+ CREATE TABLE IF NOT EXISTS ${PMO_TABLES.external_execution_map} (
516
+ provider TEXT NOT NULL CHECK (provider IN ('linear', 'jira', 'asana', 'monday', 'pmo')),
517
+ external_id TEXT NOT NULL,
518
+ external_key TEXT,
519
+ canonical_url TEXT,
520
+ latest_state_snapshot TEXT,
521
+ last_synced_at TIMESTAMP,
522
+ last_spawned_at TIMESTAMP,
523
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
524
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
525
+ PRIMARY KEY (provider, external_id)
526
+ )`,
527
+ // Linked execution IDs for external mappings
528
+ external_execution_links: `
529
+ CREATE TABLE IF NOT EXISTS ${PMO_TABLES.external_execution_links} (
530
+ provider TEXT NOT NULL,
531
+ external_id TEXT NOT NULL,
532
+ execution_id TEXT NOT NULL,
533
+ linked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
534
+ PRIMARY KEY (provider, external_id, execution_id),
535
+ FOREIGN KEY (provider, external_id)
536
+ REFERENCES ${PMO_TABLES.external_execution_map}(provider, external_id)
537
+ ON DELETE CASCADE
538
+ )`,
539
+ // Linked PR URLs for external mappings
540
+ external_execution_prs: `
541
+ CREATE TABLE IF NOT EXISTS ${PMO_TABLES.external_execution_prs} (
542
+ provider TEXT NOT NULL,
543
+ external_id TEXT NOT NULL,
544
+ pr_url TEXT NOT NULL,
545
+ linked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
546
+ PRIMARY KEY (provider, external_id, pr_url),
547
+ FOREIGN KEY (provider, external_id)
548
+ REFERENCES ${PMO_TABLES.external_execution_map}(provider, external_id)
549
+ ON DELETE CASCADE
550
+ )`,
551
+ // Monday.com integration: item ↔ PMO ticket mapping
552
+ monday_item_map: `
553
+ CREATE TABLE IF NOT EXISTS ${PMO_TABLES.monday_item_map} (
554
+ pmo_ticket_id TEXT NOT NULL REFERENCES ${PMO_TABLES.tickets}(id) ON DELETE CASCADE,
555
+ monday_board_id TEXT NOT NULL,
556
+ monday_item_id TEXT NOT NULL,
557
+ monday_item_name TEXT NOT NULL,
558
+ monday_item_url TEXT,
559
+ sync_direction TEXT NOT NULL DEFAULT 'outbound',
560
+ last_synced_at TIMESTAMP,
561
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
562
+ PRIMARY KEY (pmo_ticket_id),
563
+ UNIQUE (monday_item_id)
564
+ )`,
501
565
  };
502
566
  // =============================================================================
503
567
  // Indexes
@@ -563,6 +627,11 @@ export const PMO_INDEXES = `
563
627
  CREATE INDEX IF NOT EXISTS idx_pmo_linear_issue_map_linear_id ON ${PMO_TABLES.linear_issue_map}(linear_issue_id);
564
628
  CREATE INDEX IF NOT EXISTS idx_pmo_linear_issue_map_identifier ON ${PMO_TABLES.linear_issue_map}(linear_identifier);
565
629
  CREATE INDEX IF NOT EXISTS idx_pmo_linear_issue_map_team ON ${PMO_TABLES.linear_issue_map}(linear_team_key);
630
+ CREATE INDEX IF NOT EXISTS idx_pmo_external_execution_map_external_key ON ${PMO_TABLES.external_execution_map}(provider, external_key);
631
+ CREATE INDEX IF NOT EXISTS idx_pmo_external_execution_links_execution_id ON ${PMO_TABLES.external_execution_links}(execution_id);
632
+ CREATE INDEX IF NOT EXISTS idx_pmo_external_execution_prs_pr_url ON ${PMO_TABLES.external_execution_prs}(pr_url);
633
+ CREATE INDEX IF NOT EXISTS idx_pmo_monday_item_map_item_id ON ${PMO_TABLES.monday_item_map}(monday_item_id);
634
+ CREATE INDEX IF NOT EXISTS idx_pmo_monday_item_map_board_id ON ${PMO_TABLES.monday_item_map}(monday_board_id);
566
635
  `;
567
636
  // =============================================================================
568
637
  // Combined Schema
@@ -607,6 +676,10 @@ export const PMO_SCHEMA_SQL = [
607
676
  PMO_TABLE_SCHEMAS.roadmaps, // Named roadmap definitions
608
677
  PMO_TABLE_SCHEMAS.roadmap_projects, // Roadmap-to-project associations
609
678
  PMO_TABLE_SCHEMAS.linear_issue_map, // Linear issue ↔ PMO ticket mapping
679
+ PMO_TABLE_SCHEMAS.external_execution_map, // External issue ↔ execution mapping
680
+ PMO_TABLE_SCHEMAS.external_execution_links, // External issue ↔ execution links
681
+ PMO_TABLE_SCHEMAS.external_execution_prs, // External issue ↔ PR links
682
+ PMO_TABLE_SCHEMAS.monday_item_map, // Monday item ↔ PMO ticket mapping
610
683
  // Legacy tables (kept for migration, will be dropped after data migrated)
611
684
  PMO_TABLE_SCHEMAS.columns, // DEPRECATED
612
685
  PMO_TABLE_SCHEMAS.board_tickets, // DEPRECATED
@@ -295,6 +295,38 @@ export function runMigrations(db) {
295
295
  // Column may already exist
296
296
  }
297
297
  }
298
+ if (!agentWorkColumnNames.has('external_source')) {
299
+ try {
300
+ db.exec(`ALTER TABLE ${T.agent_work} ADD COLUMN external_source TEXT`);
301
+ }
302
+ catch {
303
+ // Column may already exist
304
+ }
305
+ }
306
+ if (!agentWorkColumnNames.has('external_key')) {
307
+ try {
308
+ db.exec(`ALTER TABLE ${T.agent_work} ADD COLUMN external_key TEXT`);
309
+ }
310
+ catch {
311
+ // Column may already exist
312
+ }
313
+ }
314
+ if (!agentWorkColumnNames.has('external_id')) {
315
+ try {
316
+ db.exec(`ALTER TABLE ${T.agent_work} ADD COLUMN external_id TEXT`);
317
+ }
318
+ catch {
319
+ // Column may already exist
320
+ }
321
+ }
322
+ if (!agentWorkColumnNames.has('external_url')) {
323
+ try {
324
+ db.exec(`ALTER TABLE ${T.agent_work} ADD COLUMN external_url TEXT`);
325
+ }
326
+ catch {
327
+ // Column may already exist
328
+ }
329
+ }
298
330
  }
299
331
  // Migration: Reassign orphaned tickets (TKT-940)
300
332
  // Tickets with project_id that doesn't match any existing project are "orphaned".
@@ -92,6 +92,17 @@ export interface OutputMetadata {
92
92
  prModeSource?: string;
93
93
  /** Warning message when PR creation is disabled for code-modifying actions */
94
94
  prWarning?: string;
95
+ /** External issue context propagated by work start confirmation flow */
96
+ externalIssue?: {
97
+ source: string | null;
98
+ key: string | null;
99
+ id: string | null;
100
+ url: string | null;
101
+ };
102
+ /** Resolved mirror-to-PMO behavior for --from-issue flow */
103
+ mirrorToPmo?: boolean | null;
104
+ /** Source used to resolve mirror-to-PMO behavior */
105
+ mirrorToPmoSource?: string | null;
95
106
  }
96
107
  /**
97
108
  * JSON output when a prompt would be shown
@@ -0,0 +1,14 @@
1
+ import Database from 'better-sqlite3';
2
+ export declare const WORK_SOURCE_PROVIDERS: readonly ["pmo", "linear", "jira", "asana", "monday"];
3
+ export type WorkSourceProvider = typeof WORK_SOURCE_PROVIDERS[number];
4
+ export interface WorkSourceRef {
5
+ provider: WorkSourceProvider;
6
+ context?: string;
7
+ }
8
+ export declare function isWorkSourceProvider(value: string): value is WorkSourceProvider;
9
+ export declare function parseWorkSourceRef(input: string): WorkSourceRef;
10
+ export declare function formatWorkSourceRef(source: WorkSourceRef): string;
11
+ export declare function saveActiveWorkSource(db: Database.Database, source: WorkSourceRef): void;
12
+ export declare function clearActiveWorkSource(db: Database.Database): void;
13
+ export declare function loadActiveWorkSource(db: Database.Database): WorkSourceRef | null;
14
+ export declare function getRegisteredWorkSources(db: Database.Database): WorkSourceRef[];
@@ -0,0 +1,70 @@
1
+ import { loadLinearConfig } from '../linear/config.js';
2
+ const SETTINGS_TABLE = 'workspace_settings';
3
+ const ACTIVE_SOURCE_KEY = 'work.active_source';
4
+ export const WORK_SOURCE_PROVIDERS = ['pmo', 'linear', 'jira', 'asana', 'monday'];
5
+ function getSetting(db, key) {
6
+ const row = db
7
+ .prepare(`SELECT value FROM ${SETTINGS_TABLE} WHERE key = ?`)
8
+ .get(key);
9
+ return row?.value ?? null;
10
+ }
11
+ function setSetting(db, key, value) {
12
+ db.prepare(`
13
+ INSERT INTO ${SETTINGS_TABLE} (key, value)
14
+ VALUES (?, ?)
15
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
16
+ `).run(key, value);
17
+ }
18
+ function deleteSetting(db, key) {
19
+ db.prepare(`DELETE FROM ${SETTINGS_TABLE} WHERE key = ?`).run(key);
20
+ }
21
+ export function isWorkSourceProvider(value) {
22
+ return WORK_SOURCE_PROVIDERS.includes(value);
23
+ }
24
+ export function parseWorkSourceRef(input) {
25
+ const raw = input.trim();
26
+ if (!raw) {
27
+ throw new Error('Work source cannot be empty.');
28
+ }
29
+ const separatorIndex = raw.indexOf(':');
30
+ const providerText = separatorIndex === -1 ? raw : raw.slice(0, separatorIndex);
31
+ const provider = providerText.toLowerCase();
32
+ if (!isWorkSourceProvider(provider)) {
33
+ throw new Error(`Unsupported work source provider "${providerText}". Allowed: ${WORK_SOURCE_PROVIDERS.join(', ')}`);
34
+ }
35
+ const rawContext = separatorIndex === -1 ? '' : raw.slice(separatorIndex + 1).trim();
36
+ const context = rawContext.length > 0 ? rawContext : undefined;
37
+ return { provider, context };
38
+ }
39
+ export function formatWorkSourceRef(source) {
40
+ return source.context ? `${source.provider}:${source.context}` : source.provider;
41
+ }
42
+ export function saveActiveWorkSource(db, source) {
43
+ setSetting(db, ACTIVE_SOURCE_KEY, formatWorkSourceRef(source));
44
+ }
45
+ export function clearActiveWorkSource(db) {
46
+ deleteSetting(db, ACTIVE_SOURCE_KEY);
47
+ }
48
+ export function loadActiveWorkSource(db) {
49
+ const raw = getSetting(db, ACTIVE_SOURCE_KEY);
50
+ if (!raw)
51
+ return null;
52
+ try {
53
+ return parseWorkSourceRef(raw);
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ export function getRegisteredWorkSources(db) {
60
+ const providers = new Map();
61
+ providers.set('pmo', { provider: 'pmo' });
62
+ const linearConfig = loadLinearConfig(db);
63
+ if (linearConfig) {
64
+ providers.set('linear', {
65
+ provider: 'linear',
66
+ context: linearConfig.defaultTeamKey ?? undefined,
67
+ });
68
+ }
69
+ return Array.from(providers.values());
70
+ }
@@ -0,0 +1 @@
1
+ export { WORK_SOURCE_PROVIDERS, type WorkSourceProvider, type WorkSourceRef, isWorkSourceProvider, parseWorkSourceRef, formatWorkSourceRef, saveActiveWorkSource, clearActiveWorkSource, loadActiveWorkSource, getRegisteredWorkSources, } from './config.js';
@@ -0,0 +1 @@
1
+ export { WORK_SOURCE_PROVIDERS, isWorkSourceProvider, parseWorkSourceRef, formatWorkSourceRef, saveActiveWorkSource, clearActiveWorkSource, loadActiveWorkSource, getRegisteredWorkSources, } from './config.js';