@proletariat/cli 0.3.65 → 0.3.67

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 (79) hide show
  1. package/dist/commands/agents/index.d.ts +11 -0
  2. package/dist/commands/agents/index.js +105 -0
  3. package/dist/commands/agents/index.js.map +1 -0
  4. package/dist/commands/pr/checks.d.ts +20 -0
  5. package/dist/commands/pr/checks.js +154 -0
  6. package/dist/commands/pr/checks.js.map +1 -0
  7. package/dist/commands/pr/index.js +10 -2
  8. package/dist/commands/pr/index.js.map +1 -1
  9. package/dist/commands/pr/merge.d.ts +22 -0
  10. package/dist/commands/pr/merge.js +172 -0
  11. package/dist/commands/pr/merge.js.map +1 -0
  12. package/dist/commands/version/bump.d.ts +13 -0
  13. package/dist/commands/version/bump.js +125 -0
  14. package/dist/commands/version/bump.js.map +1 -0
  15. package/dist/commands/work/hooks/add.d.ts +16 -0
  16. package/dist/commands/work/hooks/add.js +169 -0
  17. package/dist/commands/work/hooks/add.js.map +1 -0
  18. package/dist/commands/work/hooks/index.d.ts +11 -0
  19. package/dist/commands/work/hooks/index.js +44 -0
  20. package/dist/commands/work/hooks/index.js.map +1 -0
  21. package/dist/commands/work/hooks/list.d.ts +12 -0
  22. package/dist/commands/work/hooks/list.js +81 -0
  23. package/dist/commands/work/hooks/list.js.map +1 -0
  24. package/dist/commands/work/hooks/remove.d.ts +14 -0
  25. package/dist/commands/work/hooks/remove.js +94 -0
  26. package/dist/commands/work/hooks/remove.js.map +1 -0
  27. package/dist/commands/work/hooks/toggle.d.ts +16 -0
  28. package/dist/commands/work/hooks/toggle.js +79 -0
  29. package/dist/commands/work/hooks/toggle.js.map +1 -0
  30. package/dist/commands/work/index.js +4 -0
  31. package/dist/commands/work/index.js.map +1 -1
  32. package/dist/commands/work/start.js +25 -0
  33. package/dist/commands/work/start.js.map +1 -1
  34. package/dist/lib/database/migrations/0002_work_hooks.d.ts +8 -0
  35. package/dist/lib/database/migrations/0002_work_hooks.js +16 -0
  36. package/dist/lib/database/migrations/0002_work_hooks.js.map +1 -0
  37. package/dist/lib/database/migrations/index.js +2 -0
  38. package/dist/lib/database/migrations/index.js.map +1 -1
  39. package/dist/lib/events/events.d.ts +2 -1
  40. package/dist/lib/execution/runners.js +14 -6
  41. package/dist/lib/execution/runners.js.map +1 -1
  42. package/dist/lib/external-issues/outbound-sync.d.ts +14 -0
  43. package/dist/lib/external-issues/outbound-sync.js +94 -0
  44. package/dist/lib/external-issues/outbound-sync.js.map +1 -1
  45. package/dist/lib/linear/sync.d.ts +5 -0
  46. package/dist/lib/linear/sync.js +25 -0
  47. package/dist/lib/linear/sync.js.map +1 -1
  48. package/dist/lib/pmo/schema.d.ts +2 -0
  49. package/dist/lib/pmo/schema.js +17 -0
  50. package/dist/lib/pmo/schema.js.map +1 -1
  51. package/dist/lib/pmo/sync-manager.js +3 -0
  52. package/dist/lib/pmo/sync-manager.js.map +1 -1
  53. package/dist/lib/pr/index.d.ts +28 -0
  54. package/dist/lib/pr/index.js +53 -1
  55. package/dist/lib/pr/index.js.map +1 -1
  56. package/dist/lib/registry/index.d.ts +52 -0
  57. package/dist/lib/registry/index.js +169 -0
  58. package/dist/lib/registry/index.js.map +1 -0
  59. package/dist/lib/work-lifecycle/events.d.ts +11 -0
  60. package/dist/lib/work-lifecycle/hooks/executor.d.ts +15 -0
  61. package/dist/lib/work-lifecycle/hooks/executor.js +102 -0
  62. package/dist/lib/work-lifecycle/hooks/executor.js.map +1 -0
  63. package/dist/lib/work-lifecycle/hooks/index.d.ts +12 -0
  64. package/dist/lib/work-lifecycle/hooks/index.js +12 -0
  65. package/dist/lib/work-lifecycle/hooks/index.js.map +1 -0
  66. package/dist/lib/work-lifecycle/hooks/manager.d.ts +43 -0
  67. package/dist/lib/work-lifecycle/hooks/manager.js +93 -0
  68. package/dist/lib/work-lifecycle/hooks/manager.js.map +1 -0
  69. package/dist/lib/work-lifecycle/hooks/storage.d.ts +58 -0
  70. package/dist/lib/work-lifecycle/hooks/storage.js +117 -0
  71. package/dist/lib/work-lifecycle/hooks/storage.js.map +1 -0
  72. package/dist/lib/work-lifecycle/hooks/types.d.ts +68 -0
  73. package/dist/lib/work-lifecycle/hooks/types.js +17 -0
  74. package/dist/lib/work-lifecycle/hooks/types.js.map +1 -0
  75. package/dist/lib/work-lifecycle/index.d.ts +1 -0
  76. package/dist/lib/work-lifecycle/index.js +1 -0
  77. package/dist/lib/work-lifecycle/index.js.map +1 -1
  78. package/oclif.manifest.json +6245 -5676
  79. package/package.json +1 -1
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Machine-level Agent Registry
3
+ *
4
+ * Tracks all agents across all projects in a single SQLite database
5
+ * at ~/.prlt/agents.db. This provides a global view of agent activity
6
+ * regardless of which workspace they belong to.
7
+ */
8
+ import Database from 'better-sqlite3';
9
+ import * as fs from 'node:fs';
10
+ import * as path from 'node:path';
11
+ import * as os from 'node:os';
12
+ import { throwIfNativeBindingError } from '../database/native-validation.js';
13
+ // =============================================================================
14
+ // Schema
15
+ // =============================================================================
16
+ const REGISTRY_SCHEMA = `
17
+ CREATE TABLE IF NOT EXISTS agents (
18
+ agent_name TEXT NOT NULL,
19
+ project_path TEXT NOT NULL,
20
+ session_id TEXT,
21
+ ticket_id TEXT,
22
+ status TEXT NOT NULL DEFAULT 'running' CHECK (status IN ('running', 'idle', 'completed')),
23
+ spawned_at TEXT NOT NULL,
24
+ last_seen_at TEXT NOT NULL,
25
+ PRIMARY KEY (agent_name, project_path)
26
+ );
27
+
28
+ CREATE INDEX IF NOT EXISTS idx_registry_status ON agents(status);
29
+ CREATE INDEX IF NOT EXISTS idx_registry_project ON agents(project_path);
30
+ `;
31
+ // =============================================================================
32
+ // Helpers
33
+ // =============================================================================
34
+ function rowToMachineAgent(row) {
35
+ return {
36
+ agentName: row.agent_name,
37
+ projectPath: row.project_path,
38
+ sessionId: row.session_id,
39
+ ticketId: row.ticket_id,
40
+ status: row.status,
41
+ spawnedAt: row.spawned_at,
42
+ lastSeenAt: row.last_seen_at,
43
+ };
44
+ }
45
+ function getMachineRegistryPath() {
46
+ return path.join(os.homedir(), '.prlt', 'agents.db');
47
+ }
48
+ // =============================================================================
49
+ // Database Access
50
+ // =============================================================================
51
+ /**
52
+ * Open (or create) the machine-level agent registry at ~/.prlt/agents.db.
53
+ * Ensures the ~/.prlt directory and schema exist.
54
+ */
55
+ export function openMachineRegistry() {
56
+ const dbPath = getMachineRegistryPath();
57
+ const dir = path.dirname(dbPath);
58
+ if (!fs.existsSync(dir)) {
59
+ fs.mkdirSync(dir, { recursive: true });
60
+ }
61
+ try {
62
+ const db = new Database(dbPath);
63
+ db.pragma('journal_mode = WAL');
64
+ db.pragma('busy_timeout = 3000');
65
+ db.exec(REGISTRY_SCHEMA);
66
+ return db;
67
+ }
68
+ catch (error) {
69
+ throwIfNativeBindingError(error, 'machine agent registry');
70
+ throw error;
71
+ }
72
+ }
73
+ // =============================================================================
74
+ // Write Operations
75
+ // =============================================================================
76
+ /**
77
+ * Register an agent spawn in the machine registry.
78
+ * Uses INSERT OR REPLACE so re-spawning the same agent updates the record.
79
+ */
80
+ export function registerAgent(params) {
81
+ const db = openMachineRegistry();
82
+ try {
83
+ const now = new Date().toISOString();
84
+ db.prepare(`
85
+ INSERT OR REPLACE INTO agents (agent_name, project_path, session_id, ticket_id, status, spawned_at, last_seen_at)
86
+ VALUES (?, ?, ?, ?, 'running', ?, ?)
87
+ `).run(params.agentName, params.projectPath, params.sessionId ?? null, params.ticketId ?? null, now, now);
88
+ }
89
+ finally {
90
+ db.close();
91
+ }
92
+ }
93
+ /**
94
+ * Update the status of an agent in the machine registry.
95
+ */
96
+ export function updateAgentStatus(agentName, projectPath, status) {
97
+ const db = openMachineRegistry();
98
+ try {
99
+ const now = new Date().toISOString();
100
+ db.prepare(`
101
+ UPDATE agents SET status = ?, last_seen_at = ?
102
+ WHERE agent_name = ? AND project_path = ?
103
+ `).run(status, now, agentName, projectPath);
104
+ }
105
+ finally {
106
+ db.close();
107
+ }
108
+ }
109
+ /**
110
+ * Touch the last_seen_at timestamp for a running agent.
111
+ */
112
+ export function touchAgentLastSeen(agentName, projectPath) {
113
+ const db = openMachineRegistry();
114
+ try {
115
+ const now = new Date().toISOString();
116
+ db.prepare(`
117
+ UPDATE agents SET last_seen_at = ?
118
+ WHERE agent_name = ? AND project_path = ?
119
+ `).run(now, agentName, projectPath);
120
+ }
121
+ finally {
122
+ db.close();
123
+ }
124
+ }
125
+ // =============================================================================
126
+ // Read Operations
127
+ // =============================================================================
128
+ /**
129
+ * Get all agents in the machine registry, optionally filtered.
130
+ */
131
+ export function getMachineAgents(filters) {
132
+ const db = openMachineRegistry();
133
+ try {
134
+ let sql = 'SELECT * FROM agents';
135
+ const conditions = [];
136
+ const params = [];
137
+ if (filters?.status) {
138
+ conditions.push('status = ?');
139
+ params.push(filters.status);
140
+ }
141
+ if (filters?.projectPath) {
142
+ conditions.push('project_path = ?');
143
+ params.push(filters.projectPath);
144
+ }
145
+ if (conditions.length > 0) {
146
+ sql += ' WHERE ' + conditions.join(' AND ');
147
+ }
148
+ sql += ' ORDER BY last_seen_at DESC';
149
+ const rows = db.prepare(sql).all(...params);
150
+ return rows.map(rowToMachineAgent);
151
+ }
152
+ finally {
153
+ db.close();
154
+ }
155
+ }
156
+ /**
157
+ * Get a single agent by name and project path.
158
+ */
159
+ export function getMachineAgent(agentName, projectPath) {
160
+ const db = openMachineRegistry();
161
+ try {
162
+ const row = db.prepare('SELECT * FROM agents WHERE agent_name = ? AND project_path = ?').get(agentName, projectPath);
163
+ return row ? rowToMachineAgent(row) : undefined;
164
+ }
165
+ finally {
166
+ db.close();
167
+ }
168
+ }
169
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/registry/index.ts"],"names":[],"mappings":"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"}
@@ -42,6 +42,17 @@ export interface WorkPRCreatedEvent {
42
42
  prTitle: string | null;
43
43
  timestamp: Date;
44
44
  }
45
+ /** Emitted when a PR linked to a work item is merged. */
46
+ export interface WorkPRMergedEvent {
47
+ workItemId: string;
48
+ source: WorkEventSource;
49
+ projectId?: string;
50
+ prNumber: number;
51
+ prTitle: string | null;
52
+ prUrl: string | null;
53
+ mergeMethod: string;
54
+ timestamp: Date;
55
+ }
45
56
  /** Emitted when work on an item is completed (status moved to 'completed' category). */
46
57
  export interface WorkCompletedEvent {
47
58
  workItemId: string;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Hook Executor
3
+ *
4
+ * Executes hook actions (shell commands, webhooks, log messages) when
5
+ * work lifecycle events fire. Each action type has its own execution logic.
6
+ *
7
+ * Shell commands receive event data as environment variables prefixed with
8
+ * PRLT_HOOK_. Webhook actions POST event data as JSON. Log actions print
9
+ * interpolated messages to stdout.
10
+ */
11
+ import type { WorkHookConfig, HookExecutionResult } from './types.js';
12
+ /**
13
+ * Execute a single hook action and return the result.
14
+ */
15
+ export declare function executeHook(hook: WorkHookConfig, eventName: string, eventData: Record<string, unknown>): HookExecutionResult;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Hook Executor
3
+ *
4
+ * Executes hook actions (shell commands, webhooks, log messages) when
5
+ * work lifecycle events fire. Each action type has its own execution logic.
6
+ *
7
+ * Shell commands receive event data as environment variables prefixed with
8
+ * PRLT_HOOK_. Webhook actions POST event data as JSON. Log actions print
9
+ * interpolated messages to stdout.
10
+ */
11
+ import { execSync } from 'node:child_process';
12
+ /**
13
+ * Build environment variables from event data for shell hook execution.
14
+ * All keys are uppercased and prefixed with PRLT_HOOK_.
15
+ */
16
+ function buildEnvVars(eventName, eventData) {
17
+ const env = {
18
+ PRLT_HOOK_EVENT: eventName,
19
+ };
20
+ for (const [key, value] of Object.entries(eventData)) {
21
+ if (value === null || value === undefined)
22
+ continue;
23
+ const envKey = `PRLT_HOOK_${key.replace(/([A-Z])/g, '_$1').toUpperCase()}`;
24
+ if (value instanceof Date) {
25
+ env[envKey] = value.toISOString();
26
+ }
27
+ else {
28
+ env[envKey] = String(value);
29
+ }
30
+ }
31
+ return env;
32
+ }
33
+ /**
34
+ * Interpolate {{variable}} placeholders in a string with event data.
35
+ */
36
+ function interpolate(template, eventName, eventData) {
37
+ let result = template.replace(/\{\{event\}\}/g, eventName);
38
+ for (const [key, value] of Object.entries(eventData)) {
39
+ if (value === null || value === undefined)
40
+ continue;
41
+ const strValue = value instanceof Date ? value.toISOString() : String(value);
42
+ result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), strValue);
43
+ }
44
+ return result;
45
+ }
46
+ /**
47
+ * Execute a single hook action and return the result.
48
+ */
49
+ export function executeHook(hook, eventName, eventData) {
50
+ const start = Date.now();
51
+ try {
52
+ switch (hook.actionType) {
53
+ case 'shell': {
54
+ const env = {
55
+ ...process.env,
56
+ ...buildEnvVars(eventName, eventData),
57
+ };
58
+ execSync(hook.actionValue, {
59
+ env,
60
+ timeout: 30_000, // 30 second timeout
61
+ stdio: 'pipe',
62
+ });
63
+ break;
64
+ }
65
+ case 'webhook': {
66
+ // Fire-and-forget POST to the configured URL
67
+ const body = JSON.stringify({
68
+ event: eventName,
69
+ data: eventData,
70
+ hook: { id: hook.id, name: hook.name },
71
+ timestamp: new Date().toISOString(),
72
+ });
73
+ // Use synchronous fetch via execSync to keep it simple
74
+ // This avoids async complexity in the event handler
75
+ execSync(`curl -s -X POST -H "Content-Type: application/json" -d '${body.replace(/'/g, "'\\''")}' '${hook.actionValue}'`, { timeout: 10_000, stdio: 'pipe' });
76
+ break;
77
+ }
78
+ case 'log': {
79
+ const message = interpolate(hook.actionValue, eventName, eventData);
80
+ // eslint-disable-next-line no-console
81
+ console.log(`[hook:${hook.name}] ${message}`);
82
+ break;
83
+ }
84
+ }
85
+ return {
86
+ hookId: hook.id,
87
+ hookName: hook.name,
88
+ success: true,
89
+ durationMs: Date.now() - start,
90
+ };
91
+ }
92
+ catch (err) {
93
+ return {
94
+ hookId: hook.id,
95
+ hookName: hook.name,
96
+ success: false,
97
+ error: err instanceof Error ? err.message : String(err),
98
+ durationMs: Date.now() - start,
99
+ };
100
+ }
101
+ }
102
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../src/lib/work-lifecycle/hooks/executor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Work Lifecycle Hooks — public API.
3
+ *
4
+ * Configurable event-driven actions for work lifecycle events.
5
+ * Hooks subscribe to the EventBus and execute user-configured
6
+ * actions (shell commands, webhooks, log messages) when events fire.
7
+ */
8
+ export type { HookableEvent, HookActionType, WorkHookConfig, WorkHookRow, HookExecutionResult, } from './types.js';
9
+ export { HOOKABLE_EVENTS } from './types.js';
10
+ export { WorkHookStorage, HOOKS_TABLE, HOOKS_TABLE_SCHEMA, HOOKS_TABLE_INDEX, ensureHooksTable, } from './storage.js';
11
+ export { executeHook } from './executor.js';
12
+ export { HookManager, initHookManager, stopHookManager, } from './manager.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Work Lifecycle Hooks — public API.
3
+ *
4
+ * Configurable event-driven actions for work lifecycle events.
5
+ * Hooks subscribe to the EventBus and execute user-configured
6
+ * actions (shell commands, webhooks, log messages) when events fire.
7
+ */
8
+ export { HOOKABLE_EVENTS } from './types.js';
9
+ export { WorkHookStorage, HOOKS_TABLE, HOOKS_TABLE_SCHEMA, HOOKS_TABLE_INDEX, ensureHooksTable, } from './storage.js';
10
+ export { executeHook } from './executor.js';
11
+ export { HookManager, initHookManager, stopHookManager, } from './manager.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/work-lifecycle/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAE5C,OAAO,EACL,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,OAAO,EACL,WAAW,EACX,eAAe,EACf,eAAe,GAChB,MAAM,cAAc,CAAA"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Work Lifecycle Hook Manager
3
+ *
4
+ * Subscribes to work lifecycle events on the global EventBus and
5
+ * executes matching hook configurations from the database.
6
+ *
7
+ * Hooks are fire-and-forget: execution failures are logged but never
8
+ * block the caller or break the event emission chain.
9
+ */
10
+ import type Database from 'better-sqlite3';
11
+ /**
12
+ * HookManager loads hook configs from the database and subscribes to
13
+ * the global EventBus. When a hookable event fires, it finds all
14
+ * enabled hooks for that event and executes them.
15
+ */
16
+ export declare class HookManager {
17
+ private unsubscribers;
18
+ private hookStorage;
19
+ constructor(db: Database.Database);
20
+ /**
21
+ * Start listening for hookable events on the global EventBus.
22
+ * For each event, looks up enabled hooks and executes them.
23
+ */
24
+ start(): void;
25
+ /**
26
+ * Stop listening for events and clean up subscriptions.
27
+ */
28
+ stop(): void;
29
+ /**
30
+ * Handle a hookable event by executing all matching enabled hooks.
31
+ * Hook failures are swallowed to avoid breaking the event chain.
32
+ */
33
+ private handleEvent;
34
+ }
35
+ /**
36
+ * Initialize and start the hook manager.
37
+ * Safe to call multiple times — subsequent calls are no-ops.
38
+ */
39
+ export declare function initHookManager(db: Database.Database): HookManager;
40
+ /**
41
+ * Stop the hook manager (primarily for testing).
42
+ */
43
+ export declare function stopHookManager(): void;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Work Lifecycle Hook Manager
3
+ *
4
+ * Subscribes to work lifecycle events on the global EventBus and
5
+ * executes matching hook configurations from the database.
6
+ *
7
+ * Hooks are fire-and-forget: execution failures are logged but never
8
+ * block the caller or break the event emission chain.
9
+ */
10
+ import { getEventBus } from '../../events/event-bus.js';
11
+ import { WorkHookStorage } from './storage.js';
12
+ import { executeHook } from './executor.js';
13
+ import { HOOKABLE_EVENTS } from './types.js';
14
+ /**
15
+ * HookManager loads hook configs from the database and subscribes to
16
+ * the global EventBus. When a hookable event fires, it finds all
17
+ * enabled hooks for that event and executes them.
18
+ */
19
+ export class HookManager {
20
+ unsubscribers = [];
21
+ hookStorage;
22
+ constructor(db) {
23
+ this.hookStorage = new WorkHookStorage(db);
24
+ }
25
+ /**
26
+ * Start listening for hookable events on the global EventBus.
27
+ * For each event, looks up enabled hooks and executes them.
28
+ */
29
+ start() {
30
+ const bus = getEventBus();
31
+ for (const eventName of HOOKABLE_EVENTS) {
32
+ this.unsubscribers.push(bus.on(eventName, (payload) => {
33
+ this.handleEvent(eventName, payload);
34
+ }));
35
+ }
36
+ }
37
+ /**
38
+ * Stop listening for events and clean up subscriptions.
39
+ */
40
+ stop() {
41
+ for (const unsub of this.unsubscribers) {
42
+ unsub();
43
+ }
44
+ this.unsubscribers = [];
45
+ }
46
+ /**
47
+ * Handle a hookable event by executing all matching enabled hooks.
48
+ * Hook failures are swallowed to avoid breaking the event chain.
49
+ */
50
+ handleEvent(eventName, eventData) {
51
+ try {
52
+ const hooks = this.hookStorage.list({ event: eventName, enabled: true });
53
+ if (hooks.length === 0)
54
+ return;
55
+ for (const hook of hooks) {
56
+ try {
57
+ executeHook(hook, eventName, eventData);
58
+ }
59
+ catch {
60
+ // Hook execution errors are non-fatal
61
+ }
62
+ }
63
+ }
64
+ catch {
65
+ // Storage errors are non-fatal
66
+ }
67
+ }
68
+ }
69
+ // =============================================================================
70
+ // Singleton
71
+ // =============================================================================
72
+ let _manager;
73
+ /**
74
+ * Initialize and start the hook manager.
75
+ * Safe to call multiple times — subsequent calls are no-ops.
76
+ */
77
+ export function initHookManager(db) {
78
+ if (!_manager) {
79
+ _manager = new HookManager(db);
80
+ _manager.start();
81
+ }
82
+ return _manager;
83
+ }
84
+ /**
85
+ * Stop the hook manager (primarily for testing).
86
+ */
87
+ export function stopHookManager() {
88
+ if (_manager) {
89
+ _manager.stop();
90
+ _manager = undefined;
91
+ }
92
+ }
93
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../../src/lib/work-lifecycle/hooks/manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAsB,MAAM,YAAY,CAAA;AAEhE;;;;GAIG;AACH,MAAM,OAAO,WAAW;IACd,aAAa,GAAsB,EAAE,CAAA;IACrC,WAAW,CAAiB;IAEpC,YAAY,EAAqB;QAC/B,IAAI,CAAC,WAAW,GAAG,IAAI,eAAe,CAAC,EAAE,CAAC,CAAA;IAC5C,CAAC;IAED;;;OAGG;IACH,KAAK;QACH,MAAM,GAAG,GAAG,WAAW,EAAE,CAAA;QAEzB,KAAK,MAAM,SAAS,IAAI,eAAe,EAAE,CAAC;YACxC,IAAI,CAAC,aAAa,CAAC,IAAI,CACrB,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE;gBAC5B,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,OAA6C,CAAC,CAAA;YAC5E,CAAC,CAAC,CACH,CAAA;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,IAAI;QACF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvC,KAAK,EAAE,CAAA;QACT,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,EAAE,CAAA;IACzB,CAAC;IAED;;;OAGG;IACK,WAAW,CAAC,SAAwB,EAAE,SAAkC;QAC9E,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;YACxE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAM;YAE9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC;oBACH,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,CAAA;gBACzC,CAAC;gBAAC,MAAM,CAAC;oBACP,sCAAsC;gBACxC,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,+BAA+B;QACjC,CAAC;IACH,CAAC;CACF;AAED,gFAAgF;AAChF,YAAY;AACZ,gFAAgF;AAEhF,IAAI,QAAiC,CAAA;AAErC;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,EAAqB;IACnD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,QAAQ,GAAG,IAAI,WAAW,CAAC,EAAE,CAAC,CAAA;QAC9B,QAAQ,CAAC,KAAK,EAAE,CAAA;IAClB,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,IAAI,EAAE,CAAA;QACf,QAAQ,GAAG,SAAS,CAAA;IACtB,CAAC;AACH,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Work Lifecycle Hook Storage
3
+ *
4
+ * CRUD operations for hook configurations in the workspace database.
5
+ * Hooks are stored in the pmo_work_hooks table.
6
+ */
7
+ import type Database from 'better-sqlite3';
8
+ import type { WorkHookConfig, HookableEvent, HookActionType } from './types.js';
9
+ /** Table name for work hooks. */
10
+ export declare const HOOKS_TABLE = "pmo_work_hooks";
11
+ /** SQL to create the hooks table. */
12
+ export declare const HOOKS_TABLE_SCHEMA = "\n CREATE TABLE IF NOT EXISTS pmo_work_hooks (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n event TEXT NOT NULL,\n action_type TEXT NOT NULL CHECK (action_type IN ('shell', 'webhook', 'log')),\n action_value TEXT NOT NULL,\n enabled INTEGER NOT NULL DEFAULT 1,\n description TEXT,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )\n";
13
+ export declare const HOOKS_TABLE_INDEX = "\n CREATE INDEX IF NOT EXISTS idx_pmo_work_hooks_event ON pmo_work_hooks(event);\n CREATE INDEX IF NOT EXISTS idx_pmo_work_hooks_enabled ON pmo_work_hooks(enabled);\n";
14
+ /**
15
+ * Ensure the hooks table exists in the database.
16
+ * Safe to call multiple times.
17
+ */
18
+ export declare function ensureHooksTable(db: Database.Database): void;
19
+ /**
20
+ * WorkHookStorage provides CRUD operations for hook configurations.
21
+ */
22
+ export declare class WorkHookStorage {
23
+ private db;
24
+ constructor(db: Database.Database);
25
+ /**
26
+ * List all hooks, optionally filtered by event name or enabled status.
27
+ */
28
+ list(options?: {
29
+ event?: HookableEvent;
30
+ enabled?: boolean;
31
+ }): WorkHookConfig[];
32
+ /**
33
+ * Get a single hook by ID.
34
+ */
35
+ getById(id: string): WorkHookConfig | null;
36
+ /**
37
+ * Get a single hook by name.
38
+ */
39
+ getByName(name: string): WorkHookConfig | null;
40
+ /**
41
+ * Create a new hook configuration.
42
+ */
43
+ create(params: {
44
+ name: string;
45
+ event: HookableEvent;
46
+ actionType: HookActionType;
47
+ actionValue: string;
48
+ description?: string;
49
+ }): WorkHookConfig;
50
+ /**
51
+ * Delete a hook by ID.
52
+ */
53
+ delete(id: string): boolean;
54
+ /**
55
+ * Enable or disable a hook.
56
+ */
57
+ setEnabled(id: string, enabled: boolean): boolean;
58
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Work Lifecycle Hook Storage
3
+ *
4
+ * CRUD operations for hook configurations in the workspace database.
5
+ * Hooks are stored in the pmo_work_hooks table.
6
+ */
7
+ import { randomUUID } from 'node:crypto';
8
+ /** Table name for work hooks. */
9
+ export const HOOKS_TABLE = 'pmo_work_hooks';
10
+ /** SQL to create the hooks table. */
11
+ export const HOOKS_TABLE_SCHEMA = `
12
+ CREATE TABLE IF NOT EXISTS ${HOOKS_TABLE} (
13
+ id TEXT PRIMARY KEY,
14
+ name TEXT NOT NULL UNIQUE,
15
+ event TEXT NOT NULL,
16
+ action_type TEXT NOT NULL CHECK (action_type IN ('shell', 'webhook', 'log')),
17
+ action_value TEXT NOT NULL,
18
+ enabled INTEGER NOT NULL DEFAULT 1,
19
+ description TEXT,
20
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
21
+ )
22
+ `;
23
+ export const HOOKS_TABLE_INDEX = `
24
+ CREATE INDEX IF NOT EXISTS idx_pmo_work_hooks_event ON ${HOOKS_TABLE}(event);
25
+ CREATE INDEX IF NOT EXISTS idx_pmo_work_hooks_enabled ON ${HOOKS_TABLE}(enabled);
26
+ `;
27
+ /**
28
+ * Ensure the hooks table exists in the database.
29
+ * Safe to call multiple times.
30
+ */
31
+ export function ensureHooksTable(db) {
32
+ db.exec(HOOKS_TABLE_SCHEMA);
33
+ db.exec(HOOKS_TABLE_INDEX);
34
+ }
35
+ /**
36
+ * Convert a database row to a WorkHookConfig.
37
+ */
38
+ function rowToConfig(row) {
39
+ return {
40
+ id: row.id,
41
+ name: row.name,
42
+ event: row.event,
43
+ actionType: row.action_type,
44
+ actionValue: row.action_value,
45
+ enabled: row.enabled === 1,
46
+ description: row.description,
47
+ createdAt: row.created_at,
48
+ };
49
+ }
50
+ /**
51
+ * WorkHookStorage provides CRUD operations for hook configurations.
52
+ */
53
+ export class WorkHookStorage {
54
+ db;
55
+ constructor(db) {
56
+ this.db = db;
57
+ ensureHooksTable(db);
58
+ }
59
+ /**
60
+ * List all hooks, optionally filtered by event name or enabled status.
61
+ */
62
+ list(options) {
63
+ let sql = `SELECT * FROM ${HOOKS_TABLE} WHERE 1=1`;
64
+ const params = [];
65
+ if (options?.event) {
66
+ sql += ' AND event = ?';
67
+ params.push(options.event);
68
+ }
69
+ if (options?.enabled !== undefined) {
70
+ sql += ' AND enabled = ?';
71
+ params.push(options.enabled ? 1 : 0);
72
+ }
73
+ sql += ' ORDER BY created_at ASC';
74
+ const rows = this.db.prepare(sql).all(...params);
75
+ return rows.map(rowToConfig);
76
+ }
77
+ /**
78
+ * Get a single hook by ID.
79
+ */
80
+ getById(id) {
81
+ const row = this.db.prepare(`SELECT * FROM ${HOOKS_TABLE} WHERE id = ?`).get(id);
82
+ return row ? rowToConfig(row) : null;
83
+ }
84
+ /**
85
+ * Get a single hook by name.
86
+ */
87
+ getByName(name) {
88
+ const row = this.db.prepare(`SELECT * FROM ${HOOKS_TABLE} WHERE name = ?`).get(name);
89
+ return row ? rowToConfig(row) : null;
90
+ }
91
+ /**
92
+ * Create a new hook configuration.
93
+ */
94
+ create(params) {
95
+ const id = randomUUID();
96
+ this.db.prepare(`
97
+ INSERT INTO ${HOOKS_TABLE} (id, name, event, action_type, action_value, description)
98
+ VALUES (?, ?, ?, ?, ?, ?)
99
+ `).run(id, params.name, params.event, params.actionType, params.actionValue, params.description ?? null);
100
+ return this.getById(id);
101
+ }
102
+ /**
103
+ * Delete a hook by ID.
104
+ */
105
+ delete(id) {
106
+ const result = this.db.prepare(`DELETE FROM ${HOOKS_TABLE} WHERE id = ?`).run(id);
107
+ return result.changes > 0;
108
+ }
109
+ /**
110
+ * Enable or disable a hook.
111
+ */
112
+ setEnabled(id, enabled) {
113
+ const result = this.db.prepare(`UPDATE ${HOOKS_TABLE} SET enabled = ? WHERE id = ?`).run(enabled ? 1 : 0, id);
114
+ return result.changes > 0;
115
+ }
116
+ }
117
+ //# sourceMappingURL=storage.js.map