@sudocode-ai/cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/feedback-commands.d.ts.map +1 -0
- package/dist/cli/feedback-commands.js +274 -0
- package/dist/cli/feedback-commands.js.map +1 -0
- package/dist/cli/init-commands.d.ts.map +1 -0
- package/dist/cli/init-commands.js +148 -0
- package/dist/cli/init-commands.js.map +1 -0
- package/dist/cli/issue-commands.d.ts.map +1 -0
- package/dist/cli/issue-commands.js +310 -0
- package/dist/cli/issue-commands.js.map +1 -0
- package/dist/cli/query-commands.d.ts.map +1 -0
- package/dist/cli/query-commands.js +61 -0
- package/dist/cli/query-commands.js.map +1 -0
- package/dist/cli/reference-commands.d.ts.map +1 -0
- package/dist/cli/reference-commands.js +136 -0
- package/dist/cli/reference-commands.js.map +1 -0
- package/dist/cli/relationship-commands.d.ts.map +1 -0
- package/dist/cli/relationship-commands.js +76 -0
- package/dist/cli/relationship-commands.js.map +1 -0
- package/dist/cli/server-commands.d.ts.map +1 -0
- package/dist/cli/server-commands.js +99 -0
- package/dist/cli/server-commands.js.map +1 -0
- package/dist/cli/spec-commands.d.ts.map +1 -0
- package/dist/cli/spec-commands.js +321 -0
- package/dist/cli/spec-commands.js.map +1 -0
- package/dist/cli/status-commands.d.ts.map +1 -0
- package/dist/cli/status-commands.js +131 -0
- package/dist/cli/status-commands.js.map +1 -0
- package/dist/cli/sync-commands.d.ts.map +1 -0
- package/dist/cli/sync-commands.js +416 -0
- package/dist/cli/sync-commands.js.map +1 -0
- package/dist/cli/update-commands.d.ts.map +1 -0
- package/dist/cli/update-commands.js +78 -0
- package/dist/cli/update-commands.js.map +1 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +425 -195
- package/dist/cli.js.map +1 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +54 -0
- package/dist/db.js.map +1 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +195 -0
- package/dist/export.js.map +1 -0
- package/dist/filename-generator.d.ts.map +1 -0
- package/dist/filename-generator.js +93 -0
- package/dist/filename-generator.js.map +1 -0
- package/dist/id-generator.d.ts.map +1 -0
- package/dist/id-generator.js +123 -0
- package/dist/id-generator.js.map +1 -0
- package/dist/import.d.ts.map +1 -0
- package/dist/import.js +608 -0
- package/dist/import.js.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -189
- package/dist/index.js.map +1 -0
- package/dist/jsonl.d.ts.map +1 -0
- package/dist/jsonl.js +333 -0
- package/dist/jsonl.js.map +1 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +357 -0
- package/dist/markdown.js.map +1 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +57 -0
- package/dist/migrations.js.map +1 -0
- package/dist/operations/events.d.ts.map +1 -0
- package/dist/operations/events.js +108 -0
- package/dist/operations/events.js.map +1 -0
- package/dist/operations/feedback-anchors.d.ts.map +1 -0
- package/dist/operations/feedback-anchors.js +444 -0
- package/dist/operations/feedback-anchors.js.map +1 -0
- package/dist/operations/feedback.d.ts.map +1 -0
- package/dist/operations/feedback.js +234 -0
- package/dist/operations/feedback.js.map +1 -0
- package/dist/operations/index.d.ts.map +1 -0
- package/dist/operations/index.js +10 -0
- package/dist/operations/index.js.map +1 -0
- package/dist/operations/issues.d.ts.map +1 -0
- package/dist/operations/issues.js +411 -0
- package/dist/operations/issues.js.map +1 -0
- package/dist/operations/references.d.ts.map +1 -0
- package/dist/operations/references.js +117 -0
- package/dist/operations/references.js.map +1 -0
- package/dist/operations/relationships.d.ts.map +1 -0
- package/dist/operations/relationships.js +236 -0
- package/dist/operations/relationships.js.map +1 -0
- package/dist/operations/specs.d.ts.map +1 -0
- package/dist/operations/specs.js +290 -0
- package/dist/operations/specs.js.map +1 -0
- package/dist/operations/tags.d.ts.map +1 -0
- package/dist/operations/tags.js +127 -0
- package/dist/operations/tags.js.map +1 -0
- package/dist/operations/transactions.d.ts.map +1 -0
- package/dist/operations/transactions.js +111 -0
- package/dist/operations/transactions.js.map +1 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +442 -0
- package/dist/sync.js.map +1 -0
- package/dist/test-schema.d.ts.map +1 -0
- package/dist/test-schema.js +46 -0
- package/dist/test-schema.js.map +1 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/update-checker.d.ts.map +1 -0
- package/dist/update-checker.js +151 -0
- package/dist/update-checker.js.map +1 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +438 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +4 -7
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRUD operations for Issue Feedback
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Convert raw database row to IssueFeedback with proper types
|
|
6
|
+
* SQLite stores booleans as integers (0/1), so we need to convert
|
|
7
|
+
*/
|
|
8
|
+
function convertDbRowToFeedback(row) {
|
|
9
|
+
return {
|
|
10
|
+
...row,
|
|
11
|
+
dismissed: row.dismissed === 1,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generate next feedback ID (FB-001, FB-002, etc.)
|
|
16
|
+
*/
|
|
17
|
+
export function generateFeedbackId(db) {
|
|
18
|
+
const stmt = db.prepare(`
|
|
19
|
+
SELECT id FROM issue_feedback ORDER BY id DESC LIMIT 1
|
|
20
|
+
`);
|
|
21
|
+
const lastFeedback = stmt.get();
|
|
22
|
+
if (!lastFeedback) {
|
|
23
|
+
return "FB-001";
|
|
24
|
+
}
|
|
25
|
+
const match = lastFeedback.id.match(/^FB-(\d+)$/);
|
|
26
|
+
if (!match) {
|
|
27
|
+
return "FB-001";
|
|
28
|
+
}
|
|
29
|
+
const nextNum = parseInt(match[1], 10) + 1;
|
|
30
|
+
return `FB-${String(nextNum).padStart(3, "0")}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a new feedback entry
|
|
34
|
+
*/
|
|
35
|
+
export function createFeedback(db, input) {
|
|
36
|
+
const id = input.id || generateFeedbackId(db);
|
|
37
|
+
const anchorJson = input.anchor ? JSON.stringify(input.anchor) : null;
|
|
38
|
+
const agent = input.agent || "user";
|
|
39
|
+
// Get issue_uuid and spec_uuid
|
|
40
|
+
const issue = db.prepare(`SELECT uuid FROM issues WHERE id = ?`).get(input.issue_id);
|
|
41
|
+
if (!issue) {
|
|
42
|
+
throw new Error(`Issue not found: ${input.issue_id}`);
|
|
43
|
+
}
|
|
44
|
+
const spec = db.prepare(`SELECT uuid FROM specs WHERE id = ?`).get(input.spec_id);
|
|
45
|
+
if (!spec) {
|
|
46
|
+
throw new Error(`Spec not found: ${input.spec_id}`);
|
|
47
|
+
}
|
|
48
|
+
const stmt = db.prepare(`
|
|
49
|
+
INSERT INTO issue_feedback (
|
|
50
|
+
id, issue_id, issue_uuid, spec_id, spec_uuid, feedback_type, content, agent, anchor, dismissed
|
|
51
|
+
) VALUES (
|
|
52
|
+
@id, @issue_id, @issue_uuid, @spec_id, @spec_uuid, @feedback_type, @content, @agent, @anchor, @dismissed
|
|
53
|
+
)
|
|
54
|
+
`);
|
|
55
|
+
try {
|
|
56
|
+
stmt.run({
|
|
57
|
+
id,
|
|
58
|
+
issue_id: input.issue_id,
|
|
59
|
+
issue_uuid: issue.uuid,
|
|
60
|
+
spec_id: input.spec_id,
|
|
61
|
+
spec_uuid: spec.uuid,
|
|
62
|
+
feedback_type: input.feedback_type,
|
|
63
|
+
content: input.content,
|
|
64
|
+
agent: agent,
|
|
65
|
+
anchor: anchorJson,
|
|
66
|
+
dismissed: input.dismissed !== undefined ? (input.dismissed ? 1 : 0) : 0,
|
|
67
|
+
});
|
|
68
|
+
const feedback = getFeedback(db, id);
|
|
69
|
+
if (!feedback) {
|
|
70
|
+
throw new Error(`Failed to create feedback ${id}`);
|
|
71
|
+
}
|
|
72
|
+
return feedback;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error.code && error.code.startsWith("SQLITE_CONSTRAINT")) {
|
|
76
|
+
throw new Error(`Constraint violation: ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get a feedback entry by ID
|
|
83
|
+
*/
|
|
84
|
+
export function getFeedback(db, id) {
|
|
85
|
+
const stmt = db.prepare(`
|
|
86
|
+
SELECT * FROM issue_feedback WHERE id = ?
|
|
87
|
+
`);
|
|
88
|
+
const row = stmt.get(id);
|
|
89
|
+
return row ? convertDbRowToFeedback(row) : null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Update a feedback entry
|
|
93
|
+
*/
|
|
94
|
+
export function updateFeedback(db, id, input) {
|
|
95
|
+
const existing = getFeedback(db, id);
|
|
96
|
+
if (!existing) {
|
|
97
|
+
throw new Error(`Feedback not found: ${id}`);
|
|
98
|
+
}
|
|
99
|
+
const updates = [];
|
|
100
|
+
const params = { id };
|
|
101
|
+
if (input.content !== undefined) {
|
|
102
|
+
updates.push("content = @content");
|
|
103
|
+
params.content = input.content;
|
|
104
|
+
}
|
|
105
|
+
if (input.dismissed !== undefined) {
|
|
106
|
+
updates.push("dismissed = @dismissed");
|
|
107
|
+
params.dismissed = input.dismissed ? 1 : 0;
|
|
108
|
+
}
|
|
109
|
+
if (input.anchor !== undefined) {
|
|
110
|
+
updates.push("anchor = @anchor");
|
|
111
|
+
params.anchor = JSON.stringify(input.anchor);
|
|
112
|
+
}
|
|
113
|
+
updates.push("updated_at = CURRENT_TIMESTAMP");
|
|
114
|
+
if (updates.length === 1) {
|
|
115
|
+
// Only updated_at changed, return existing
|
|
116
|
+
return existing;
|
|
117
|
+
}
|
|
118
|
+
const stmt = db.prepare(`
|
|
119
|
+
UPDATE issue_feedback SET ${updates.join(", ")} WHERE id = @id
|
|
120
|
+
`);
|
|
121
|
+
try {
|
|
122
|
+
stmt.run(params);
|
|
123
|
+
const updated = getFeedback(db, id);
|
|
124
|
+
if (!updated) {
|
|
125
|
+
throw new Error(`Failed to update feedback ${id}`);
|
|
126
|
+
}
|
|
127
|
+
return updated;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (error.code && error.code.startsWith("SQLITE_CONSTRAINT")) {
|
|
131
|
+
throw new Error(`Constraint violation: ${error.message}`);
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Delete a feedback entry
|
|
138
|
+
*/
|
|
139
|
+
export function deleteFeedback(db, id) {
|
|
140
|
+
const stmt = db.prepare(`DELETE FROM issue_feedback WHERE id = ?`);
|
|
141
|
+
const result = stmt.run(id);
|
|
142
|
+
return result.changes > 0;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Dismiss feedback (convenience method)
|
|
146
|
+
*/
|
|
147
|
+
export function dismissFeedback(db, id) {
|
|
148
|
+
return updateFeedback(db, id, { dismissed: true });
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* List feedback entries with optional filters
|
|
152
|
+
*/
|
|
153
|
+
export function listFeedback(db, options = {}) {
|
|
154
|
+
const conditions = [];
|
|
155
|
+
const params = {};
|
|
156
|
+
if (options.issue_id !== undefined) {
|
|
157
|
+
conditions.push("issue_id = @issue_id");
|
|
158
|
+
params.issue_id = options.issue_id;
|
|
159
|
+
}
|
|
160
|
+
if (options.spec_id !== undefined) {
|
|
161
|
+
conditions.push("spec_id = @spec_id");
|
|
162
|
+
params.spec_id = options.spec_id;
|
|
163
|
+
}
|
|
164
|
+
if (options.feedback_type !== undefined) {
|
|
165
|
+
conditions.push("feedback_type = @feedback_type");
|
|
166
|
+
params.feedback_type = options.feedback_type;
|
|
167
|
+
}
|
|
168
|
+
if (options.dismissed !== undefined) {
|
|
169
|
+
conditions.push("dismissed = @dismissed");
|
|
170
|
+
params.dismissed = options.dismissed ? 1 : 0;
|
|
171
|
+
}
|
|
172
|
+
let query = "SELECT * FROM issue_feedback";
|
|
173
|
+
if (conditions.length > 0) {
|
|
174
|
+
query += " WHERE " + conditions.join(" AND ");
|
|
175
|
+
}
|
|
176
|
+
query += " ORDER BY created_at DESC";
|
|
177
|
+
if (options.limit !== undefined) {
|
|
178
|
+
query += " LIMIT @limit";
|
|
179
|
+
params.limit = options.limit;
|
|
180
|
+
}
|
|
181
|
+
if (options.offset !== undefined) {
|
|
182
|
+
query += " OFFSET @offset";
|
|
183
|
+
params.offset = options.offset;
|
|
184
|
+
}
|
|
185
|
+
const stmt = db.prepare(query);
|
|
186
|
+
const rows = stmt.all(params);
|
|
187
|
+
return rows.map(convertDbRowToFeedback);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get all feedback for a specific issue
|
|
191
|
+
*/
|
|
192
|
+
export function getFeedbackForIssue(db, issue_id) {
|
|
193
|
+
return listFeedback(db, { issue_id });
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get all feedback for a specific spec
|
|
197
|
+
*/
|
|
198
|
+
export function getFeedbackForSpec(db, spec_id) {
|
|
199
|
+
return listFeedback(db, { spec_id });
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get active feedback for a spec (not dismissed)
|
|
203
|
+
*/
|
|
204
|
+
export function getActiveFeedbackForSpec(db, spec_id) {
|
|
205
|
+
return listFeedback(db, { spec_id, dismissed: false });
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Count feedback by dismissed status
|
|
209
|
+
*/
|
|
210
|
+
export function countFeedbackByDismissed(db, spec_id) {
|
|
211
|
+
let query = "SELECT dismissed, COUNT(*) as count FROM issue_feedback";
|
|
212
|
+
const params = {};
|
|
213
|
+
if (spec_id) {
|
|
214
|
+
query += " WHERE spec_id = @spec_id";
|
|
215
|
+
params.spec_id = spec_id;
|
|
216
|
+
}
|
|
217
|
+
query += " GROUP BY dismissed";
|
|
218
|
+
const stmt = db.prepare(query);
|
|
219
|
+
const rows = stmt.all(params);
|
|
220
|
+
const counts = {
|
|
221
|
+
active: 0,
|
|
222
|
+
dismissed: 0,
|
|
223
|
+
};
|
|
224
|
+
for (const row of rows) {
|
|
225
|
+
if (row.dismissed === 0) {
|
|
226
|
+
counts.active = row.count;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
counts.dismissed = row.count;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return counts;
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=feedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feedback.js","sourceRoot":"","sources":["../../src/operations/feedback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/operations/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main entry point for all database operations
|
|
3
|
+
*/
|
|
4
|
+
export * from './specs.js';
|
|
5
|
+
export * from './issues.js';
|
|
6
|
+
export * from './relationships.js';
|
|
7
|
+
export * from './tags.js';
|
|
8
|
+
export * from './events.js';
|
|
9
|
+
export * from './references.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/operations/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../src/operations/issues.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRUD operations for Issues
|
|
3
|
+
*/
|
|
4
|
+
import { generateUUID } from "../id-generator.js";
|
|
5
|
+
import { getIncomingRelationships } from "./relationships.js";
|
|
6
|
+
/**
|
|
7
|
+
* Create a new issue
|
|
8
|
+
*/
|
|
9
|
+
export function createIssue(db, input) {
|
|
10
|
+
// Validate parent_id exists if provided and get parent_uuid
|
|
11
|
+
let parent_uuid = null;
|
|
12
|
+
if (input.parent_id) {
|
|
13
|
+
const parent = getIssue(db, input.parent_id);
|
|
14
|
+
if (!parent) {
|
|
15
|
+
throw new Error(`Parent issue not found: ${input.parent_id}`);
|
|
16
|
+
}
|
|
17
|
+
parent_uuid = parent.uuid;
|
|
18
|
+
}
|
|
19
|
+
const uuid = input.uuid || generateUUID();
|
|
20
|
+
// Build INSERT statement with optional timestamp fields
|
|
21
|
+
const columns = [
|
|
22
|
+
"id",
|
|
23
|
+
"uuid",
|
|
24
|
+
"title",
|
|
25
|
+
"content",
|
|
26
|
+
"status",
|
|
27
|
+
"priority",
|
|
28
|
+
"assignee",
|
|
29
|
+
"parent_id",
|
|
30
|
+
"parent_uuid",
|
|
31
|
+
"archived",
|
|
32
|
+
];
|
|
33
|
+
const values = [
|
|
34
|
+
"@id",
|
|
35
|
+
"@uuid",
|
|
36
|
+
"@title",
|
|
37
|
+
"@content",
|
|
38
|
+
"@status",
|
|
39
|
+
"@priority",
|
|
40
|
+
"@assignee",
|
|
41
|
+
"@parent_id",
|
|
42
|
+
"@parent_uuid",
|
|
43
|
+
"@archived",
|
|
44
|
+
];
|
|
45
|
+
if (input.created_at) {
|
|
46
|
+
columns.push("created_at");
|
|
47
|
+
values.push("@created_at");
|
|
48
|
+
}
|
|
49
|
+
if (input.updated_at) {
|
|
50
|
+
columns.push("updated_at");
|
|
51
|
+
values.push("@updated_at");
|
|
52
|
+
}
|
|
53
|
+
if (input.closed_at !== undefined) {
|
|
54
|
+
columns.push("closed_at");
|
|
55
|
+
values.push("@closed_at");
|
|
56
|
+
}
|
|
57
|
+
if (input.archived_at !== undefined) {
|
|
58
|
+
columns.push("archived_at");
|
|
59
|
+
values.push("@archived_at");
|
|
60
|
+
}
|
|
61
|
+
const stmt = db.prepare(`
|
|
62
|
+
INSERT INTO issues (
|
|
63
|
+
${columns.join(", ")}
|
|
64
|
+
) VALUES (
|
|
65
|
+
${values.join(", ")}
|
|
66
|
+
)
|
|
67
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
68
|
+
uuid = excluded.uuid,
|
|
69
|
+
title = excluded.title,
|
|
70
|
+
content = excluded.content,
|
|
71
|
+
status = excluded.status,
|
|
72
|
+
priority = excluded.priority,
|
|
73
|
+
assignee = excluded.assignee,
|
|
74
|
+
parent_id = excluded.parent_id,
|
|
75
|
+
parent_uuid = excluded.parent_uuid,
|
|
76
|
+
archived = excluded.archived,
|
|
77
|
+
archived_at = excluded.archived_at,
|
|
78
|
+
${input.created_at ? "created_at = excluded.created_at," : ""}
|
|
79
|
+
${input.updated_at ? "updated_at = excluded.updated_at" : "updated_at = CURRENT_TIMESTAMP"}
|
|
80
|
+
`);
|
|
81
|
+
try {
|
|
82
|
+
const params = {
|
|
83
|
+
id: input.id,
|
|
84
|
+
uuid: uuid,
|
|
85
|
+
title: input.title,
|
|
86
|
+
content: input.content || "",
|
|
87
|
+
status: input.status || "open",
|
|
88
|
+
priority: input.priority ?? 2,
|
|
89
|
+
assignee: input.assignee ?? null,
|
|
90
|
+
parent_id: input.parent_id ?? null,
|
|
91
|
+
parent_uuid: parent_uuid,
|
|
92
|
+
archived: input.archived ? 1 : 0,
|
|
93
|
+
};
|
|
94
|
+
// Add optional timestamp parameters
|
|
95
|
+
if (input.created_at) {
|
|
96
|
+
params.created_at = input.created_at;
|
|
97
|
+
}
|
|
98
|
+
if (input.updated_at) {
|
|
99
|
+
params.updated_at = input.updated_at;
|
|
100
|
+
}
|
|
101
|
+
if (input.closed_at !== undefined) {
|
|
102
|
+
params.closed_at = input.closed_at;
|
|
103
|
+
}
|
|
104
|
+
if (input.archived_at !== undefined) {
|
|
105
|
+
params.archived_at = input.archived_at;
|
|
106
|
+
}
|
|
107
|
+
stmt.run(params);
|
|
108
|
+
const issue = getIssue(db, input.id);
|
|
109
|
+
if (!issue) {
|
|
110
|
+
throw new Error(`Failed to create issue ${input.id}`);
|
|
111
|
+
}
|
|
112
|
+
return issue;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (error.code && error.code.startsWith("SQLITE_CONSTRAINT")) {
|
|
116
|
+
throw new Error(`Constraint violation: ${error.message}`);
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get an issue by ID
|
|
123
|
+
*/
|
|
124
|
+
export function getIssue(db, id) {
|
|
125
|
+
const stmt = db.prepare(`
|
|
126
|
+
SELECT * FROM issues WHERE id = ?
|
|
127
|
+
`);
|
|
128
|
+
return stmt.get(id) ?? null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Update an issue
|
|
132
|
+
*/
|
|
133
|
+
export function updateIssue(db, id, input) {
|
|
134
|
+
const existing = getIssue(db, id);
|
|
135
|
+
if (!existing) {
|
|
136
|
+
throw new Error(`Issue not found: ${id}`);
|
|
137
|
+
}
|
|
138
|
+
// Validate parent_id exists if provided
|
|
139
|
+
if (input.parent_id) {
|
|
140
|
+
const parent = getIssue(db, input.parent_id);
|
|
141
|
+
if (!parent) {
|
|
142
|
+
throw new Error(`Parent issue not found: ${input.parent_id}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const updates = [];
|
|
146
|
+
const params = { id };
|
|
147
|
+
if (input.title !== undefined && input.title !== existing.title) {
|
|
148
|
+
updates.push("title = @title");
|
|
149
|
+
params.title = input.title;
|
|
150
|
+
}
|
|
151
|
+
if (input.content !== undefined && input.content !== existing.content) {
|
|
152
|
+
updates.push("content = @content");
|
|
153
|
+
params.content = input.content;
|
|
154
|
+
}
|
|
155
|
+
if (input.status !== undefined && input.status !== existing.status) {
|
|
156
|
+
updates.push("status = @status");
|
|
157
|
+
params.status = input.status;
|
|
158
|
+
// Handle closed_at based on status changes
|
|
159
|
+
// Use input.closed_at if provided, otherwise auto-set based on status
|
|
160
|
+
if (input.closed_at !== undefined) {
|
|
161
|
+
// Explicit closed_at provided - use it
|
|
162
|
+
updates.push("closed_at = @closed_at");
|
|
163
|
+
params.closed_at = input.closed_at;
|
|
164
|
+
}
|
|
165
|
+
else if (input.status === "closed" && existing.status !== "closed") {
|
|
166
|
+
// Status changing to 'closed' - set timestamp
|
|
167
|
+
updates.push("closed_at = CURRENT_TIMESTAMP");
|
|
168
|
+
}
|
|
169
|
+
else if (input.status !== "closed" && existing.status === "closed") {
|
|
170
|
+
// Reopening - clear timestamp
|
|
171
|
+
updates.push("closed_at = NULL");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if (input.closed_at !== undefined &&
|
|
175
|
+
input.closed_at !== existing.closed_at) {
|
|
176
|
+
// closed_at provided without status change
|
|
177
|
+
updates.push("closed_at = @closed_at");
|
|
178
|
+
params.closed_at = input.closed_at;
|
|
179
|
+
}
|
|
180
|
+
if (input.priority !== undefined && input.priority !== existing.priority) {
|
|
181
|
+
updates.push("priority = @priority");
|
|
182
|
+
params.priority = input.priority;
|
|
183
|
+
}
|
|
184
|
+
if (input.assignee !== undefined && input.assignee !== existing.assignee) {
|
|
185
|
+
updates.push("assignee = @assignee");
|
|
186
|
+
params.assignee = input.assignee;
|
|
187
|
+
}
|
|
188
|
+
if (input.parent_id !== undefined && input.parent_id !== existing.parent_id) {
|
|
189
|
+
updates.push("parent_id = @parent_id");
|
|
190
|
+
params.parent_id = input.parent_id;
|
|
191
|
+
}
|
|
192
|
+
if (input.archived !== undefined &&
|
|
193
|
+
(input.archived ? 1 : 0) !== existing.archived) {
|
|
194
|
+
updates.push("archived = @archived");
|
|
195
|
+
params.archived = input.archived ? 1 : 0;
|
|
196
|
+
// Handle archived_at based on archived changes
|
|
197
|
+
// Use input.archived_at if provided, otherwise auto-set based on archived
|
|
198
|
+
if (input.archived_at !== undefined) {
|
|
199
|
+
// Explicit archived_at provided - use it
|
|
200
|
+
updates.push("archived_at = @archived_at");
|
|
201
|
+
params.archived_at = input.archived_at;
|
|
202
|
+
}
|
|
203
|
+
else if (input.archived && !existing.archived) {
|
|
204
|
+
// Archiving - set timestamp
|
|
205
|
+
updates.push("archived_at = CURRENT_TIMESTAMP");
|
|
206
|
+
}
|
|
207
|
+
else if (!input.archived && existing.archived) {
|
|
208
|
+
// Unarchiving - clear timestamp
|
|
209
|
+
updates.push("archived_at = NULL");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else if (input.archived_at !== undefined &&
|
|
213
|
+
input.archived_at !== existing.archived_at) {
|
|
214
|
+
// archived_at provided without archived change
|
|
215
|
+
updates.push("archived_at = @archived_at");
|
|
216
|
+
params.archived_at = input.archived_at;
|
|
217
|
+
}
|
|
218
|
+
// Handle updated_at - use provided value or set to current timestamp
|
|
219
|
+
if (input.updated_at !== undefined) {
|
|
220
|
+
updates.push("updated_at = @updated_at");
|
|
221
|
+
params.updated_at = input.updated_at;
|
|
222
|
+
}
|
|
223
|
+
else if (updates.length > 0) {
|
|
224
|
+
// Only update timestamp if there are actual changes
|
|
225
|
+
updates.push("updated_at = CURRENT_TIMESTAMP");
|
|
226
|
+
}
|
|
227
|
+
if (updates.length === 0) {
|
|
228
|
+
return existing;
|
|
229
|
+
}
|
|
230
|
+
const stmt = db.prepare(`
|
|
231
|
+
UPDATE issues SET ${updates.join(", ")} WHERE id = @id
|
|
232
|
+
`);
|
|
233
|
+
try {
|
|
234
|
+
stmt.run(params);
|
|
235
|
+
const updated = getIssue(db, id);
|
|
236
|
+
if (!updated) {
|
|
237
|
+
throw new Error(`Failed to update issue ${id}`);
|
|
238
|
+
}
|
|
239
|
+
// If status changed to 'closed', update any dependent blocked issues
|
|
240
|
+
if (input.status === "closed" && existing.status !== "closed") {
|
|
241
|
+
updateDependentBlockedIssues(db, id);
|
|
242
|
+
}
|
|
243
|
+
return updated;
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
if (error.code && error.code.startsWith("SQLITE_CONSTRAINT")) {
|
|
247
|
+
throw new Error(`Constraint violation: ${error.message}`);
|
|
248
|
+
}
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Update status of issues that were blocked by the given issue
|
|
254
|
+
* Called when a blocker issue is closed
|
|
255
|
+
*/
|
|
256
|
+
function updateDependentBlockedIssues(db, closedIssueId) {
|
|
257
|
+
// Find all issues that are blocked by this issue
|
|
258
|
+
// (issues that have a 'blocks' relationship pointing to this issue)
|
|
259
|
+
const dependentRelationships = getIncomingRelationships(db, closedIssueId, "issue", "blocks");
|
|
260
|
+
for (const rel of dependentRelationships) {
|
|
261
|
+
const blockedIssueId = rel.from_id;
|
|
262
|
+
const blockedIssue = getIssue(db, blockedIssueId);
|
|
263
|
+
// Only update if the issue is currently marked as 'blocked'
|
|
264
|
+
if (!blockedIssue || blockedIssue.status !== "blocked") {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
// Check if this issue has any other open/in_progress/blocked blockers
|
|
268
|
+
const hasOtherBlockers = hasOpenBlockers(db, blockedIssueId, closedIssueId);
|
|
269
|
+
// If no other blockers, update status from 'blocked' to 'open'
|
|
270
|
+
if (!hasOtherBlockers) {
|
|
271
|
+
const updateStmt = db.prepare(`
|
|
272
|
+
UPDATE issues
|
|
273
|
+
SET status = 'open', updated_at = CURRENT_TIMESTAMP
|
|
274
|
+
WHERE id = ?
|
|
275
|
+
`);
|
|
276
|
+
updateStmt.run(blockedIssueId);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Check if an issue has any open blockers (excluding the specified blocker)
|
|
282
|
+
*/
|
|
283
|
+
function hasOpenBlockers(db, issueId, excludeBlockerId) {
|
|
284
|
+
const stmt = db.prepare(`
|
|
285
|
+
SELECT COUNT(*) as count
|
|
286
|
+
FROM relationships r
|
|
287
|
+
JOIN issues blocker ON r.to_id = blocker.id AND r.to_type = 'issue'
|
|
288
|
+
WHERE r.from_id = ?
|
|
289
|
+
AND r.from_type = 'issue'
|
|
290
|
+
AND r.relationship_type = 'blocks'
|
|
291
|
+
AND blocker.status IN ('open', 'in_progress', 'blocked')
|
|
292
|
+
${excludeBlockerId ? "AND blocker.id != ?" : ""}
|
|
293
|
+
`);
|
|
294
|
+
const params = excludeBlockerId ? [issueId, excludeBlockerId] : [issueId];
|
|
295
|
+
const result = stmt.get(...params);
|
|
296
|
+
return result.count > 0;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Delete an issue
|
|
300
|
+
*/
|
|
301
|
+
export function deleteIssue(db, id) {
|
|
302
|
+
const stmt = db.prepare(`DELETE FROM issues WHERE id = ?`);
|
|
303
|
+
const result = stmt.run(id);
|
|
304
|
+
return result.changes > 0;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Close an issue (convenience method)
|
|
308
|
+
*/
|
|
309
|
+
export function closeIssue(db, id) {
|
|
310
|
+
return updateIssue(db, id, { status: "closed" });
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Reopen an issue (convenience method)
|
|
314
|
+
*/
|
|
315
|
+
export function reopenIssue(db, id) {
|
|
316
|
+
return updateIssue(db, id, { status: "open" });
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* List issues with optional filters
|
|
320
|
+
*/
|
|
321
|
+
export function listIssues(db, options = {}) {
|
|
322
|
+
const conditions = [];
|
|
323
|
+
const params = {};
|
|
324
|
+
if (options.status !== undefined) {
|
|
325
|
+
conditions.push("status = @status");
|
|
326
|
+
params.status = options.status;
|
|
327
|
+
}
|
|
328
|
+
if (options.priority !== undefined) {
|
|
329
|
+
conditions.push("priority = @priority");
|
|
330
|
+
params.priority = options.priority;
|
|
331
|
+
}
|
|
332
|
+
if (options.assignee !== undefined) {
|
|
333
|
+
conditions.push("assignee = @assignee");
|
|
334
|
+
params.assignee = options.assignee;
|
|
335
|
+
}
|
|
336
|
+
if (options.parent_id !== undefined) {
|
|
337
|
+
conditions.push("parent_id = @parent_id");
|
|
338
|
+
params.parent_id = options.parent_id;
|
|
339
|
+
}
|
|
340
|
+
if (options.archived !== undefined) {
|
|
341
|
+
conditions.push("archived = @archived");
|
|
342
|
+
params.archived = options.archived ? 1 : 0;
|
|
343
|
+
}
|
|
344
|
+
let query = "SELECT * FROM issues";
|
|
345
|
+
if (conditions.length > 0) {
|
|
346
|
+
query += " WHERE " + conditions.join(" AND ");
|
|
347
|
+
}
|
|
348
|
+
query += " ORDER BY priority DESC, created_at DESC";
|
|
349
|
+
if (options.limit !== undefined) {
|
|
350
|
+
query += " LIMIT @limit";
|
|
351
|
+
params.limit = options.limit;
|
|
352
|
+
}
|
|
353
|
+
if (options.offset !== undefined) {
|
|
354
|
+
query += " OFFSET @offset";
|
|
355
|
+
params.offset = options.offset;
|
|
356
|
+
}
|
|
357
|
+
const stmt = db.prepare(query);
|
|
358
|
+
return stmt.all(params);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Get ready issues (no blockers)
|
|
362
|
+
*/
|
|
363
|
+
export function getReadyIssues(db) {
|
|
364
|
+
const stmt = db.prepare("SELECT * FROM ready_issues ORDER BY priority DESC, created_at DESC");
|
|
365
|
+
return stmt.all();
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Get blocked issues
|
|
369
|
+
*/
|
|
370
|
+
export function getBlockedIssues(db) {
|
|
371
|
+
const stmt = db.prepare("SELECT * FROM blocked_issues ORDER BY priority DESC, created_at DESC");
|
|
372
|
+
return stmt.all();
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Search issues by title or content
|
|
376
|
+
*/
|
|
377
|
+
export function searchIssues(db, query, options = {}) {
|
|
378
|
+
const conditions = ["(title LIKE @query OR content LIKE @query)"];
|
|
379
|
+
const params = { query: `%${query}%` };
|
|
380
|
+
if (options.status !== undefined) {
|
|
381
|
+
conditions.push("status = @status");
|
|
382
|
+
params.status = options.status;
|
|
383
|
+
}
|
|
384
|
+
if (options.priority !== undefined) {
|
|
385
|
+
conditions.push("priority = @priority");
|
|
386
|
+
params.priority = options.priority;
|
|
387
|
+
}
|
|
388
|
+
if (options.assignee !== undefined) {
|
|
389
|
+
conditions.push("assignee = @assignee");
|
|
390
|
+
params.assignee = options.assignee;
|
|
391
|
+
}
|
|
392
|
+
if (options.parent_id !== undefined) {
|
|
393
|
+
conditions.push("parent_id = @parent_id");
|
|
394
|
+
params.parent_id = options.parent_id;
|
|
395
|
+
}
|
|
396
|
+
if (options.archived !== undefined) {
|
|
397
|
+
conditions.push("archived = @archived");
|
|
398
|
+
params.archived = options.archived ? 1 : 0;
|
|
399
|
+
}
|
|
400
|
+
let sql = `SELECT * FROM issues WHERE ${conditions.join(" AND ")} ORDER BY priority DESC, created_at DESC`;
|
|
401
|
+
if (options.limit !== undefined) {
|
|
402
|
+
sql += " LIMIT @limit";
|
|
403
|
+
params.limit = options.limit;
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
sql += " LIMIT 50";
|
|
407
|
+
}
|
|
408
|
+
const stmt = db.prepare(sql);
|
|
409
|
+
return stmt.all(params);
|
|
410
|
+
}
|
|
411
|
+
//# sourceMappingURL=issues.js.map
|