@compilr-dev/sdk 0.2.2 → 0.2.4

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.
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Database Schema for compilr-dev workflow system
3
+ *
4
+ * Shared between CLI and Desktop — both access ~/.compilr-dev/projects.db
5
+ * Schema version must be kept in sync across all consumers.
6
+ */
7
+ export const SCHEMA_VERSION = 6;
8
+ export const SCHEMA_SQL = `
9
+ -- Schema version tracking
10
+ CREATE TABLE IF NOT EXISTS schema_version (
11
+ version INTEGER PRIMARY KEY,
12
+ applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
13
+ );
14
+
15
+ -- Projects table
16
+ CREATE TABLE IF NOT EXISTS projects (
17
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
18
+ name TEXT UNIQUE NOT NULL,
19
+ display_name TEXT NOT NULL,
20
+ description TEXT,
21
+ type TEXT DEFAULT 'general',
22
+ status TEXT DEFAULT 'active',
23
+ path TEXT NOT NULL,
24
+ docs_path TEXT,
25
+ repo_pattern TEXT DEFAULT 'single',
26
+ language TEXT,
27
+ framework TEXT,
28
+ package_manager TEXT,
29
+ runtime_version TEXT,
30
+ commands TEXT,
31
+ git_remote TEXT,
32
+ git_branch TEXT DEFAULT 'main',
33
+ workflow_mode TEXT DEFAULT 'flexible',
34
+ lifecycle_state TEXT DEFAULT 'setup',
35
+ current_item_id TEXT,
36
+ last_context TEXT,
37
+ metadata TEXT,
38
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
39
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
40
+ last_activity_at DATETIME
41
+ );
42
+
43
+ -- Work items (backlog items, tasks, bugs)
44
+ CREATE TABLE IF NOT EXISTS work_items (
45
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
46
+ project_id INTEGER NOT NULL,
47
+ item_number INTEGER NOT NULL,
48
+ item_id TEXT NOT NULL,
49
+ type TEXT NOT NULL,
50
+ status TEXT DEFAULT 'backlog',
51
+ priority TEXT DEFAULT 'medium',
52
+ guided_step TEXT,
53
+ owner TEXT,
54
+ title TEXT NOT NULL,
55
+ description TEXT,
56
+ estimated_effort TEXT,
57
+ actual_minutes INTEGER,
58
+ completed_at DATETIME,
59
+ completed_by TEXT,
60
+ commit_hash TEXT,
61
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
62
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
63
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
64
+ UNIQUE (project_id, item_id)
65
+ );
66
+
67
+ -- Project documents (PRD, architecture, plans, etc.)
68
+ CREATE TABLE IF NOT EXISTS project_documents (
69
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
70
+ project_id INTEGER NOT NULL,
71
+ doc_type TEXT NOT NULL,
72
+ title TEXT NOT NULL,
73
+ content TEXT NOT NULL,
74
+ status TEXT,
75
+ work_item_id INTEGER,
76
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
77
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
78
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
79
+ FOREIGN KEY (work_item_id) REFERENCES work_items(id) ON DELETE SET NULL
80
+ );
81
+
82
+ -- Work item history (audit trail)
83
+ CREATE TABLE IF NOT EXISTS work_item_history (
84
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
85
+ work_item_id INTEGER NOT NULL,
86
+ project_id INTEGER NOT NULL,
87
+ action TEXT NOT NULL,
88
+ old_value TEXT,
89
+ new_value TEXT,
90
+ notes TEXT,
91
+ changed_by TEXT,
92
+ changed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
93
+ FOREIGN KEY (work_item_id) REFERENCES work_items(id) ON DELETE CASCADE,
94
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
95
+ );
96
+
97
+ -- Indexes
98
+ CREATE INDEX IF NOT EXISTS idx_projects_path ON projects(path);
99
+ CREATE INDEX IF NOT EXISTS idx_projects_docs_path ON projects(docs_path);
100
+ CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);
101
+ CREATE INDEX IF NOT EXISTS idx_work_items_project ON work_items(project_id);
102
+ CREATE INDEX IF NOT EXISTS idx_work_items_status ON work_items(status);
103
+ CREATE INDEX IF NOT EXISTS idx_work_items_priority ON work_items(priority);
104
+ CREATE INDEX IF NOT EXISTS idx_work_items_owner ON work_items(owner);
105
+ CREATE INDEX IF NOT EXISTS idx_project_documents_project ON project_documents(project_id);
106
+ CREATE INDEX IF NOT EXISTS idx_project_documents_type ON project_documents(doc_type);
107
+ CREATE INDEX IF NOT EXISTS idx_project_documents_status ON project_documents(status);
108
+ CREATE INDEX IF NOT EXISTS idx_project_documents_work_item ON project_documents(work_item_id);
109
+ CREATE INDEX IF NOT EXISTS idx_work_item_history_item ON work_item_history(work_item_id);
110
+
111
+ -- Terminal sessions (multi-terminal awareness)
112
+ CREATE TABLE IF NOT EXISTS terminal_sessions (
113
+ id TEXT PRIMARY KEY,
114
+ project_id INTEGER,
115
+ pid INTEGER NOT NULL,
116
+ tty_path TEXT,
117
+ label TEXT,
118
+ started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
119
+ last_heartbeat DATETIME DEFAULT CURRENT_TIMESTAMP,
120
+ active_agent TEXT DEFAULT 'default',
121
+ agents_json TEXT DEFAULT '[]',
122
+ status TEXT DEFAULT 'active',
123
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL
124
+ );
125
+ CREATE INDEX IF NOT EXISTS idx_terminal_sessions_project ON terminal_sessions(project_id);
126
+ CREATE INDEX IF NOT EXISTS idx_terminal_sessions_status ON terminal_sessions(status);
127
+
128
+ -- File locks (multi-terminal file lock awareness)
129
+ CREATE TABLE IF NOT EXISTS file_locks (
130
+ path TEXT NOT NULL,
131
+ project_id INTEGER NOT NULL,
132
+ session_id TEXT NOT NULL,
133
+ agent_id TEXT NOT NULL,
134
+ locked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
135
+ PRIMARY KEY (path, project_id),
136
+ FOREIGN KEY (session_id) REFERENCES terminal_sessions(id) ON DELETE CASCADE,
137
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
138
+ );
139
+ CREATE INDEX IF NOT EXISTS idx_file_locks_session ON file_locks(session_id);
140
+
141
+ -- Session notifications (cross-session notifications)
142
+ CREATE TABLE IF NOT EXISTS session_notifications (
143
+ id TEXT PRIMARY KEY,
144
+ project_id INTEGER NOT NULL,
145
+ from_session_id TEXT NOT NULL,
146
+ to_session_id TEXT,
147
+ type TEXT NOT NULL,
148
+ title TEXT NOT NULL,
149
+ message TEXT,
150
+ payload_json TEXT,
151
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
152
+ read_at DATETIME,
153
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
154
+ FOREIGN KEY (from_session_id) REFERENCES terminal_sessions(id) ON DELETE CASCADE
155
+ );
156
+ CREATE INDEX IF NOT EXISTS idx_session_notifications_project ON session_notifications(project_id);
157
+ CREATE INDEX IF NOT EXISTS idx_session_notifications_to_session ON session_notifications(to_session_id);
158
+ CREATE INDEX IF NOT EXISTS idx_session_notifications_unread ON session_notifications(read_at);
159
+ `;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * SQLite Work Item Repository — Concrete implementation of IWorkItemRepository.
3
+ */
4
+ import type Database from 'better-sqlite3';
5
+ import type { IWorkItemRepository } from '../repositories.js';
6
+ import type { WorkItem, WorkItemType, WorkItemStatus, HistoryEntry, CreateWorkItemInput, UpdateWorkItemInput, QueryWorkItemsInput, WorkItemQueryResult, BulkCreateItem } from '../types.js';
7
+ export declare class SQLiteWorkItemRepository implements IWorkItemRepository {
8
+ private readonly db;
9
+ constructor(db: Database.Database);
10
+ create(input: CreateWorkItemInput): Promise<WorkItem>;
11
+ getById(id: number): Promise<WorkItem | null>;
12
+ getByItemId(projectId: number, itemId: string): Promise<WorkItem | null>;
13
+ query(input: QueryWorkItemsInput): Promise<WorkItemQueryResult>;
14
+ getNext(projectId: number, type?: WorkItemType): Promise<WorkItem | null>;
15
+ update(id: number, input: UpdateWorkItemInput): Promise<WorkItem | null>;
16
+ delete(id: number): Promise<boolean>;
17
+ getByOwner(projectId: number, owner: string, status?: WorkItemStatus): Promise<WorkItem[]>;
18
+ getOwnerCounts(projectId: number): Promise<Record<string, number>>;
19
+ getStatusCounts(projectId: number): Promise<Record<WorkItemStatus, number>>;
20
+ getHistory(workItemId: number): Promise<HistoryEntry[]>;
21
+ bulkCreate(projectId: number, items: BulkCreateItem[]): Promise<WorkItem[]>;
22
+ private recordHistory;
23
+ }
@@ -0,0 +1,350 @@
1
+ /**
2
+ * SQLite Work Item Repository — Concrete implementation of IWorkItemRepository.
3
+ */
4
+ function recordToWorkItem(record) {
5
+ return {
6
+ id: record.id,
7
+ projectId: record.project_id,
8
+ itemNumber: record.item_number,
9
+ itemId: record.item_id,
10
+ type: record.type,
11
+ status: record.status,
12
+ priority: record.priority,
13
+ guidedStep: record.guided_step,
14
+ owner: record.owner,
15
+ title: record.title,
16
+ description: record.description,
17
+ estimatedEffort: record.estimated_effort,
18
+ actualMinutes: record.actual_minutes,
19
+ completedAt: record.completed_at ? new Date(record.completed_at) : null,
20
+ completedBy: record.completed_by,
21
+ commitHash: record.commit_hash,
22
+ createdAt: new Date(record.created_at),
23
+ updatedAt: new Date(record.updated_at),
24
+ };
25
+ }
26
+ function getTypePrefix(type) {
27
+ const prefixes = {
28
+ feature: 'REQ',
29
+ bug: 'BUG',
30
+ 'tech-debt': 'TEC',
31
+ chore: 'CHR',
32
+ };
33
+ return prefixes[type];
34
+ }
35
+ const PRIORITY_ORDER = `
36
+ CASE priority
37
+ WHEN 'critical' THEN 1
38
+ WHEN 'high' THEN 2
39
+ WHEN 'medium' THEN 3
40
+ WHEN 'low' THEN 4
41
+ ELSE 5
42
+ END
43
+ `;
44
+ export class SQLiteWorkItemRepository {
45
+ db;
46
+ constructor(db) {
47
+ this.db = db;
48
+ }
49
+ create(input) {
50
+ const now = new Date().toISOString();
51
+ const maxResult = this.db
52
+ .prepare('SELECT MAX(item_number) as max_num FROM work_items WHERE project_id = ?')
53
+ .get(input.project_id);
54
+ const itemNumber = (maxResult.max_num ?? 0) + 1;
55
+ const itemId = `${getTypePrefix(input.type)}-${String(itemNumber).padStart(3, '0')}`;
56
+ const result = this.db
57
+ .prepare(`INSERT INTO work_items (
58
+ project_id, item_number, item_id, type, status, priority,
59
+ owner, title, description, estimated_effort,
60
+ created_at, updated_at
61
+ ) VALUES (
62
+ @project_id, @item_number, @item_id, @type, @status, @priority,
63
+ @owner, @title, @description, @estimated_effort,
64
+ @created_at, @updated_at
65
+ )`)
66
+ .run({
67
+ project_id: input.project_id,
68
+ item_number: itemNumber,
69
+ item_id: itemId,
70
+ type: input.type,
71
+ status: 'backlog',
72
+ priority: input.priority ?? 'medium',
73
+ owner: input.owner ?? null,
74
+ title: input.title,
75
+ description: input.description ?? null,
76
+ estimated_effort: input.estimated_effort ?? null,
77
+ created_at: now,
78
+ updated_at: now,
79
+ });
80
+ const record = this.db
81
+ .prepare('SELECT * FROM work_items WHERE id = ?')
82
+ .get(Number(result.lastInsertRowid));
83
+ if (!record)
84
+ throw new Error('Failed to create work item');
85
+ return Promise.resolve(recordToWorkItem(record));
86
+ }
87
+ getById(id) {
88
+ const record = this.db.prepare('SELECT * FROM work_items WHERE id = ?').get(id);
89
+ return Promise.resolve(record ? recordToWorkItem(record) : null);
90
+ }
91
+ getByItemId(projectId, itemId) {
92
+ const record = this.db
93
+ .prepare('SELECT * FROM work_items WHERE project_id = ? AND item_id = ?')
94
+ .get(projectId, itemId);
95
+ return Promise.resolve(record ? recordToWorkItem(record) : null);
96
+ }
97
+ query(input) {
98
+ const conditions = [];
99
+ const params = {};
100
+ if (input.project_id) {
101
+ conditions.push('project_id = @project_id');
102
+ params.project_id = input.project_id;
103
+ }
104
+ if (input.status && input.status !== 'all') {
105
+ conditions.push('status = @status');
106
+ params.status = input.status;
107
+ }
108
+ if (input.type && input.type !== 'all') {
109
+ conditions.push('type = @type');
110
+ params.type = input.type;
111
+ }
112
+ if (input.priority && input.priority !== 'all') {
113
+ conditions.push('priority = @priority');
114
+ params.priority = input.priority;
115
+ }
116
+ if (input.owner && input.owner !== 'all') {
117
+ if (input.owner === 'unassigned') {
118
+ conditions.push('owner IS NULL');
119
+ }
120
+ else {
121
+ conditions.push('owner = @owner');
122
+ params.owner = input.owner;
123
+ }
124
+ }
125
+ if (input.search) {
126
+ conditions.push('(title LIKE @search OR description LIKE @search)');
127
+ params.search = `%${input.search}%`;
128
+ }
129
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
130
+ const countResult = this.db
131
+ .prepare(`SELECT COUNT(*) as count FROM work_items ${whereClause}`)
132
+ .get(params);
133
+ const limit = input.limit ?? 50;
134
+ const offset = input.offset ?? 0;
135
+ const query = `
136
+ SELECT * FROM work_items ${whereClause}
137
+ ORDER BY
138
+ CASE status WHEN 'in_progress' THEN 0 ELSE 1 END,
139
+ ${PRIORITY_ORDER},
140
+ created_at ASC
141
+ LIMIT @limit OFFSET @offset
142
+ `;
143
+ params.limit = limit;
144
+ params.offset = offset;
145
+ const records = this.db.prepare(query).all(params);
146
+ return Promise.resolve({
147
+ items: records.map(recordToWorkItem),
148
+ total: countResult.count,
149
+ hasMore: offset + records.length < countResult.count,
150
+ });
151
+ }
152
+ getNext(projectId, type) {
153
+ let query = `SELECT * FROM work_items WHERE project_id = ? AND status = 'backlog'`;
154
+ const params = [projectId];
155
+ if (type) {
156
+ query += ' AND type = ?';
157
+ params.push(type);
158
+ }
159
+ query += ` ORDER BY ${PRIORITY_ORDER}, created_at ASC LIMIT 1`;
160
+ const record = this.db.prepare(query).get(...params);
161
+ return Promise.resolve(record ? recordToWorkItem(record) : null);
162
+ }
163
+ update(id, input) {
164
+ const oldRecord = this.db.prepare('SELECT * FROM work_items WHERE id = ?').get(id);
165
+ if (!oldRecord)
166
+ return Promise.resolve(null);
167
+ const oldItem = recordToWorkItem(oldRecord);
168
+ const updates = [];
169
+ const params = { id };
170
+ if (input.type !== undefined) {
171
+ updates.push('type = @type');
172
+ params.type = input.type;
173
+ }
174
+ if (input.status !== undefined) {
175
+ updates.push('status = @status');
176
+ params.status = input.status;
177
+ if (input.status === 'completed' && oldItem.status !== 'completed') {
178
+ updates.push('completed_at = @completed_at');
179
+ params.completed_at = new Date().toISOString();
180
+ }
181
+ }
182
+ if (input.priority !== undefined) {
183
+ updates.push('priority = @priority');
184
+ params.priority = input.priority;
185
+ }
186
+ if (input.guided_step !== undefined) {
187
+ updates.push('guided_step = @guided_step');
188
+ params.guided_step = input.guided_step;
189
+ }
190
+ if (input.owner !== undefined) {
191
+ updates.push('owner = @owner');
192
+ params.owner = input.owner;
193
+ }
194
+ if (input.title !== undefined) {
195
+ updates.push('title = @title');
196
+ params.title = input.title;
197
+ }
198
+ if (input.description !== undefined) {
199
+ updates.push('description = @description');
200
+ params.description = input.description;
201
+ }
202
+ if (input.estimated_effort !== undefined) {
203
+ updates.push('estimated_effort = @estimated_effort');
204
+ params.estimated_effort = input.estimated_effort;
205
+ }
206
+ if (input.actual_minutes !== undefined) {
207
+ updates.push('actual_minutes = @actual_minutes');
208
+ params.actual_minutes = input.actual_minutes;
209
+ }
210
+ if (input.commit_hash !== undefined) {
211
+ updates.push('commit_hash = @commit_hash');
212
+ params.commit_hash = input.commit_hash;
213
+ }
214
+ if (updates.length === 0)
215
+ return Promise.resolve(oldItem);
216
+ updates.push('updated_at = @updated_at');
217
+ params.updated_at = new Date().toISOString();
218
+ this.db.prepare(`UPDATE work_items SET ${updates.join(', ')} WHERE id = @id`).run(params);
219
+ // Record history
220
+ this.recordHistory(id, oldItem.projectId, input, oldItem);
221
+ const record = this.db.prepare('SELECT * FROM work_items WHERE id = ?').get(id);
222
+ return Promise.resolve(record ? recordToWorkItem(record) : null);
223
+ }
224
+ delete(id) {
225
+ const result = this.db.prepare('DELETE FROM work_items WHERE id = ?').run(id);
226
+ return Promise.resolve(result.changes > 0);
227
+ }
228
+ getByOwner(projectId, owner, status) {
229
+ let query = 'SELECT * FROM work_items WHERE project_id = ?';
230
+ const params = [projectId];
231
+ if (owner === 'unassigned') {
232
+ query += ' AND owner IS NULL';
233
+ }
234
+ else {
235
+ query += ' AND owner = ?';
236
+ params.push(owner);
237
+ }
238
+ if (status) {
239
+ query += ' AND status = ?';
240
+ params.push(status);
241
+ }
242
+ query += ' ORDER BY priority, created_at ASC';
243
+ const records = this.db.prepare(query).all(...params);
244
+ return Promise.resolve(records.map(recordToWorkItem));
245
+ }
246
+ getOwnerCounts(projectId) {
247
+ const results = this.db
248
+ .prepare(`SELECT COALESCE(owner, 'unassigned') as owner, COUNT(*) as count
249
+ FROM work_items
250
+ WHERE project_id = ? AND status != 'completed' AND status != 'skipped'
251
+ GROUP BY owner`)
252
+ .all(projectId);
253
+ const counts = {};
254
+ for (const row of results) {
255
+ counts[row.owner] = row.count;
256
+ }
257
+ return Promise.resolve(counts);
258
+ }
259
+ getStatusCounts(projectId) {
260
+ const results = this.db
261
+ .prepare('SELECT status, COUNT(*) as count FROM work_items WHERE project_id = ? GROUP BY status')
262
+ .all(projectId);
263
+ const counts = {
264
+ backlog: 0,
265
+ in_progress: 0,
266
+ completed: 0,
267
+ skipped: 0,
268
+ };
269
+ for (const row of results) {
270
+ counts[row.status] = row.count;
271
+ }
272
+ return Promise.resolve(counts);
273
+ }
274
+ getHistory(workItemId) {
275
+ const records = this.db
276
+ .prepare('SELECT * FROM work_item_history WHERE work_item_id = ? ORDER BY changed_at DESC')
277
+ .all(workItemId);
278
+ return Promise.resolve(records.map((r) => ({
279
+ action: r.action,
280
+ oldValue: r.old_value,
281
+ newValue: r.new_value,
282
+ notes: r.notes,
283
+ changedBy: r.changed_by,
284
+ changedAt: new Date(r.changed_at),
285
+ })));
286
+ }
287
+ bulkCreate(projectId, items) {
288
+ const now = new Date().toISOString();
289
+ const maxResult = this.db
290
+ .prepare('SELECT MAX(item_number) as max_num FROM work_items WHERE project_id = ?')
291
+ .get(projectId);
292
+ let itemNumber = (maxResult.max_num ?? 0) + 1;
293
+ const createdIds = [];
294
+ const insertStmt = this.db.prepare(`
295
+ INSERT INTO work_items (
296
+ project_id, item_number, item_id, type, status, priority,
297
+ owner, title, description, created_at, updated_at
298
+ ) VALUES (
299
+ @project_id, @item_number, @item_id, @type, @status, @priority,
300
+ @owner, @title, @description, @created_at, @updated_at
301
+ )
302
+ `);
303
+ const insertAll = this.db.transaction(() => {
304
+ for (const item of items) {
305
+ const itemId = `${getTypePrefix(item.type)}-${String(itemNumber).padStart(3, '0')}`;
306
+ const result = insertStmt.run({
307
+ project_id: projectId,
308
+ item_number: itemNumber,
309
+ item_id: itemId,
310
+ type: item.type,
311
+ status: 'backlog',
312
+ priority: item.priority ?? 'medium',
313
+ owner: item.owner ?? null,
314
+ title: item.title,
315
+ description: item.description ?? null,
316
+ created_at: now,
317
+ updated_at: now,
318
+ });
319
+ createdIds.push(Number(result.lastInsertRowid));
320
+ itemNumber++;
321
+ }
322
+ });
323
+ insertAll();
324
+ const results = createdIds
325
+ .map((rowId) => {
326
+ const record = this.db.prepare('SELECT * FROM work_items WHERE id = ?').get(rowId);
327
+ return record ? recordToWorkItem(record) : null;
328
+ })
329
+ .filter((item) => item !== null);
330
+ return Promise.resolve(results);
331
+ }
332
+ // Internal: record history entries
333
+ recordHistory(workItemId, projectId, changes, oldItem) {
334
+ const now = new Date().toISOString();
335
+ const stmt = this.db.prepare(`INSERT INTO work_item_history (work_item_id, project_id, action, old_value, new_value, changed_by, changed_at)
336
+ VALUES (?, ?, ?, ?, ?, 'agent', ?)`);
337
+ if (changes.status && changes.status !== oldItem.status) {
338
+ stmt.run(workItemId, projectId, 'status_change', oldItem.status, changes.status, now);
339
+ }
340
+ if (changes.priority && changes.priority !== oldItem.priority) {
341
+ stmt.run(workItemId, projectId, 'priority_change', oldItem.priority, changes.priority, now);
342
+ }
343
+ if (changes.guided_step !== undefined && changes.guided_step !== oldItem.guidedStep) {
344
+ stmt.run(workItemId, projectId, 'step_advance', oldItem.guidedStep ?? 'none', changes.guided_step ?? 'none', now);
345
+ }
346
+ if (changes.owner !== undefined && changes.owner !== oldItem.owner) {
347
+ stmt.run(workItemId, projectId, 'owner_change', oldItem.owner ?? 'unassigned', changes.owner ?? 'unassigned', now);
348
+ }
349
+ }
350
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Universal agent runtime for building AI-powered applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -56,15 +56,22 @@
56
56
  "ajv": "^6.14.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "@compilr-dev/agents": "^0.3.16",
60
- "@compilr-dev/agents-coding": "^1.0.2"
59
+ "@compilr-dev/agents": "^0.3.25",
60
+ "@compilr-dev/agents-coding": "^1.0.2",
61
+ "better-sqlite3": "^11.0.0 || ^12.0.0"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "better-sqlite3": {
65
+ "optional": true
66
+ }
61
67
  },
62
68
  "devDependencies": {
63
69
  "@anthropic-ai/sdk": "^0.78.0",
64
- "@compilr-dev/agents": "^0.3.16",
70
+ "@compilr-dev/agents": "^0.3.25",
65
71
  "@compilr-dev/agents-coding": "^1.0.2",
66
72
  "@eslint/js": "^9.39.1",
67
73
  "@opentelemetry/api": "^1.9.0",
74
+ "@types/better-sqlite3": "^7.6.13",
68
75
  "@types/node": "^25.2.3",
69
76
  "@vitest/coverage-v8": "^4.0.18",
70
77
  "eslint": "^9.39.1",