@sudocode-ai/local-server 0.1.0 → 0.1.1

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 (115) hide show
  1. package/dist/cli.js +6 -104
  2. package/dist/cli.js.map +1 -7
  3. package/dist/execution/engine/engine.js +10 -0
  4. package/dist/execution/engine/engine.js.map +1 -0
  5. package/dist/execution/engine/simple-engine.js +611 -0
  6. package/dist/execution/engine/simple-engine.js.map +1 -0
  7. package/dist/execution/engine/types.js +10 -0
  8. package/dist/execution/engine/types.js.map +1 -0
  9. package/dist/execution/output/ag-ui-adapter.js +438 -0
  10. package/dist/execution/output/ag-ui-adapter.js.map +1 -0
  11. package/dist/execution/output/ag-ui-integration.js +96 -0
  12. package/dist/execution/output/ag-ui-integration.js.map +1 -0
  13. package/dist/execution/output/claude-code-output-processor.js +769 -0
  14. package/dist/execution/output/claude-code-output-processor.js.map +1 -0
  15. package/dist/execution/output/index.js +15 -0
  16. package/dist/execution/output/index.js.map +1 -0
  17. package/dist/execution/output/types.js +22 -0
  18. package/dist/execution/output/types.js.map +1 -0
  19. package/dist/execution/process/builders/claude.js +59 -0
  20. package/dist/execution/process/builders/claude.js.map +1 -0
  21. package/dist/execution/process/index.js +15 -0
  22. package/dist/execution/process/index.js.map +1 -0
  23. package/dist/execution/process/manager.js +10 -0
  24. package/dist/execution/process/manager.js.map +1 -0
  25. package/dist/execution/process/simple-manager.js +336 -0
  26. package/dist/execution/process/simple-manager.js.map +1 -0
  27. package/dist/execution/process/types.js +10 -0
  28. package/dist/execution/process/types.js.map +1 -0
  29. package/dist/execution/process/utils.js +97 -0
  30. package/dist/execution/process/utils.js.map +1 -0
  31. package/dist/execution/resilience/circuit-breaker.js +291 -0
  32. package/dist/execution/resilience/circuit-breaker.js.map +1 -0
  33. package/dist/execution/resilience/executor.js +10 -0
  34. package/dist/execution/resilience/executor.js.map +1 -0
  35. package/dist/execution/resilience/index.js +15 -0
  36. package/dist/execution/resilience/index.js.map +1 -0
  37. package/dist/execution/resilience/resilient-executor.js +261 -0
  38. package/dist/execution/resilience/resilient-executor.js.map +1 -0
  39. package/dist/execution/resilience/retry.js +234 -0
  40. package/dist/execution/resilience/retry.js.map +1 -0
  41. package/dist/execution/resilience/types.js +30 -0
  42. package/dist/execution/resilience/types.js.map +1 -0
  43. package/dist/execution/transport/event-buffer.js +208 -0
  44. package/dist/execution/transport/event-buffer.js.map +1 -0
  45. package/dist/execution/transport/index.js +10 -0
  46. package/dist/execution/transport/index.js.map +1 -0
  47. package/dist/execution/transport/sse-transport.js +282 -0
  48. package/dist/execution/transport/sse-transport.js.map +1 -0
  49. package/dist/execution/transport/transport-manager.js +231 -0
  50. package/dist/execution/transport/transport-manager.js.map +1 -0
  51. package/dist/execution/workflow/index.js +13 -0
  52. package/dist/execution/workflow/index.js.map +1 -0
  53. package/dist/execution/workflow/linear-orchestrator.js +683 -0
  54. package/dist/execution/workflow/linear-orchestrator.js.map +1 -0
  55. package/dist/execution/workflow/memory-storage.js +68 -0
  56. package/dist/execution/workflow/memory-storage.js.map +1 -0
  57. package/dist/execution/workflow/orchestrator.js +9 -0
  58. package/dist/execution/workflow/orchestrator.js.map +1 -0
  59. package/dist/execution/workflow/types.js +9 -0
  60. package/dist/execution/workflow/types.js.map +1 -0
  61. package/dist/execution/workflow/utils.js +152 -0
  62. package/dist/execution/workflow/utils.js.map +1 -0
  63. package/dist/execution/worktree/config.js +280 -0
  64. package/dist/execution/worktree/config.js.map +1 -0
  65. package/dist/execution/worktree/git-cli.js +189 -0
  66. package/dist/execution/worktree/git-cli.js.map +1 -0
  67. package/dist/execution/worktree/index.js +15 -0
  68. package/dist/execution/worktree/index.js.map +1 -0
  69. package/dist/execution/worktree/manager.js +452 -0
  70. package/dist/execution/worktree/manager.js.map +1 -0
  71. package/dist/execution/worktree/types.js +42 -0
  72. package/dist/execution/worktree/types.js.map +1 -0
  73. package/dist/index.js +356 -104
  74. package/dist/index.js.map +1 -7
  75. package/dist/routes/executions-stream.js +55 -0
  76. package/dist/routes/executions-stream.js.map +1 -0
  77. package/dist/routes/executions.js +267 -0
  78. package/dist/routes/executions.js.map +1 -0
  79. package/dist/routes/feedback.js +329 -0
  80. package/dist/routes/feedback.js.map +1 -0
  81. package/dist/routes/issues.js +280 -0
  82. package/dist/routes/issues.js.map +1 -0
  83. package/dist/routes/relationships.js +308 -0
  84. package/dist/routes/relationships.js.map +1 -0
  85. package/dist/routes/specs.js +270 -0
  86. package/dist/routes/specs.js.map +1 -0
  87. package/dist/services/db.js +85 -0
  88. package/dist/services/db.js.map +1 -0
  89. package/dist/services/execution-lifecycle.js +286 -0
  90. package/dist/services/execution-lifecycle.js.map +1 -0
  91. package/dist/services/execution-service.js +676 -0
  92. package/dist/services/execution-service.js.map +1 -0
  93. package/dist/services/executions.js +164 -0
  94. package/dist/services/executions.js.map +1 -0
  95. package/dist/services/export.js +106 -0
  96. package/dist/services/export.js.map +1 -0
  97. package/dist/services/feedback.js +54 -0
  98. package/dist/services/feedback.js.map +1 -0
  99. package/dist/services/issues.js +35 -0
  100. package/dist/services/issues.js.map +1 -0
  101. package/dist/services/prompt-template-engine.js +212 -0
  102. package/dist/services/prompt-template-engine.js.map +1 -0
  103. package/dist/services/prompt-templates.js +236 -0
  104. package/dist/services/prompt-templates.js.map +1 -0
  105. package/dist/services/relationships.js +42 -0
  106. package/dist/services/relationships.js.map +1 -0
  107. package/dist/services/specs.js +35 -0
  108. package/dist/services/specs.js.map +1 -0
  109. package/dist/services/watcher.js +69 -0
  110. package/dist/services/watcher.js.map +1 -0
  111. package/dist/services/websocket.js +389 -0
  112. package/dist/services/websocket.js.map +1 -0
  113. package/dist/utils/sudocode-dir.js +9 -0
  114. package/dist/utils/sudocode-dir.js.map +1 -0
  115. package/package.json +4 -6
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Execution Stream Routes
3
+ *
4
+ * SSE endpoint for streaming execution events to clients.
5
+ * Integrates with TransportManager to broadcast AG-UI events.
6
+ *
7
+ * @module routes/executions-stream
8
+ */
9
+ import { Router } from "express";
10
+ import { randomUUID } from "crypto";
11
+ /**
12
+ * Create execution stream routes
13
+ *
14
+ * @param transportManager - Transport manager for SSE connections
15
+ * @returns Express router with SSE endpoints
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const router = createExecutionStreamRoutes(transportManager);
20
+ * app.use('/api/executions', router);
21
+ * ```
22
+ */
23
+ export function createExecutionStreamRoutes(transportManager) {
24
+ const router = Router();
25
+ /**
26
+ * SSE endpoint for execution event stream
27
+ *
28
+ * GET /api/executions/:executionId/stream
29
+ *
30
+ * Establishes SSE connection and streams execution events to client.
31
+ * Events are filtered to only include events for the specified execution.
32
+ *
33
+ * @param executionId - Execution ID to stream events for
34
+ */
35
+ router.get("/:executionId/stream", (req, res) => {
36
+ const { executionId } = req.params;
37
+ // TODO: Add authentication/authorization check
38
+ // Verify user has permission to access this execution
39
+ // Generate unique client ID
40
+ const clientId = randomUUID();
41
+ // Get buffered events for replay
42
+ const bufferedEvents = transportManager.getBufferedEvents(executionId);
43
+ const replayEvents = bufferedEvents.map((buffered) => ({
44
+ event: buffered.event.type,
45
+ data: buffered.event,
46
+ }));
47
+ // Establish SSE connection through transport manager
48
+ // This will set appropriate headers, send connection acknowledgment, and replay buffered events
49
+ transportManager
50
+ .getSseTransport()
51
+ .handleConnection(clientId, res, executionId, replayEvents);
52
+ });
53
+ return router;
54
+ }
55
+ //# sourceMappingURL=executions-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executions-stream.js","sourceRoot":"","sources":["../../src/routes/executions-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGpC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,2BAA2B,CACzC,gBAAkC;IAElC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB;;;;;;;;;OASG;IACH,MAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QACjE,MAAM,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;QAEnC,+CAA+C;QAC/C,sDAAsD;QAEtD,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC;QAE9B,iCAAiC;QACjC,MAAM,cAAc,GAAG,gBAAgB,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACvE,MAAM,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACrD,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI;YAC1B,IAAI,EAAE,QAAQ,CAAC,KAAK;SACrB,CAAC,CAAC,CAAC;QAEJ,qDAAqD;QACrD,gGAAgG;QAChG,gBAAgB;aACb,eAAe,EAAE;aACjB,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Executions API routes (mapped to /api)
3
+ *
4
+ * Provides REST API for managing issue executions.
5
+ */
6
+ import { Router } from "express";
7
+ import { ExecutionService } from "../services/execution-service.js";
8
+ /**
9
+ * Create executions router
10
+ *
11
+ * @param db - Database instance
12
+ * @param repoPath - Path to git repository
13
+ * @param transportManager - Optional transport manager for SSE streaming
14
+ * @returns Express router with execution endpoints
15
+ */
16
+ export function createExecutionsRouter(db, repoPath, transportManager, executionService) {
17
+ const router = Router();
18
+ const service = executionService ||
19
+ new ExecutionService(db, repoPath, undefined, transportManager);
20
+ /**
21
+ * POST /api/issues/:issueId/executions/prepare
22
+ *
23
+ * Prepare an execution - render template and show preview
24
+ */
25
+ router.post("/issues/:issueId/executions/prepare", async (req, res) => {
26
+ try {
27
+ const { issueId } = req.params;
28
+ const options = req.body || {};
29
+ const result = await service.prepareExecution(issueId, options);
30
+ res.json({
31
+ success: true,
32
+ data: result,
33
+ });
34
+ }
35
+ catch (error) {
36
+ console.error("[API Route] ERROR: Failed to prepare execution:", error);
37
+ res.status(500).json({
38
+ success: false,
39
+ data: null,
40
+ error_data: error instanceof Error ? error.message : String(error),
41
+ message: "Failed to prepare execution",
42
+ });
43
+ }
44
+ });
45
+ /**
46
+ * POST /api/issues/:issueId/executions
47
+ *
48
+ * Create and start a new execution
49
+ */
50
+ router.post("/issues/:issueId/executions", async (req, res) => {
51
+ try {
52
+ const { issueId } = req.params;
53
+ const { config, prompt } = req.body;
54
+ // Validate required fields
55
+ if (!prompt) {
56
+ res.status(400).json({
57
+ success: false,
58
+ data: null,
59
+ message: "Prompt is required",
60
+ });
61
+ return;
62
+ }
63
+ const execution = await service.createExecution(issueId, config || {}, prompt);
64
+ res.status(201).json({
65
+ success: true,
66
+ data: execution,
67
+ });
68
+ }
69
+ catch (error) {
70
+ console.error("[API Route] ERROR: Failed to create execution:", error);
71
+ // Handle specific error cases
72
+ const errorMessage = error instanceof Error ? error.message : String(error);
73
+ const statusCode = errorMessage.includes("not found") ? 404 : 500;
74
+ res.status(statusCode).json({
75
+ success: false,
76
+ data: null,
77
+ error_data: errorMessage,
78
+ message: "Failed to create execution",
79
+ });
80
+ }
81
+ });
82
+ /**
83
+ * GET /api/executions/:executionId
84
+ *
85
+ * Get a specific execution by ID
86
+ */
87
+ router.get("/executions/:executionId", (req, res) => {
88
+ try {
89
+ const { executionId } = req.params;
90
+ const execution = service.getExecution(executionId);
91
+ if (!execution) {
92
+ res.status(404).json({
93
+ success: false,
94
+ data: null,
95
+ message: `Execution not found: ${executionId}`,
96
+ });
97
+ return;
98
+ }
99
+ res.json({
100
+ success: true,
101
+ data: execution,
102
+ });
103
+ }
104
+ catch (error) {
105
+ console.error("Error getting execution:", error);
106
+ res.status(500).json({
107
+ success: false,
108
+ data: null,
109
+ error_data: error instanceof Error ? error.message : String(error),
110
+ message: "Failed to get execution",
111
+ });
112
+ }
113
+ });
114
+ /**
115
+ * GET /api/issues/:issueId/executions
116
+ *
117
+ * List all executions for an issue
118
+ */
119
+ router.get("/issues/:issueId/executions", (req, res) => {
120
+ try {
121
+ const { issueId } = req.params;
122
+ const executions = service.listExecutions(issueId);
123
+ res.json({
124
+ success: true,
125
+ data: executions,
126
+ });
127
+ }
128
+ catch (error) {
129
+ console.error("Error listing executions:", error);
130
+ res.status(500).json({
131
+ success: false,
132
+ data: null,
133
+ error_data: error instanceof Error ? error.message : String(error),
134
+ message: "Failed to list executions",
135
+ });
136
+ }
137
+ });
138
+ /**
139
+ * POST /api/executions/:executionId/follow-up
140
+ *
141
+ * Create a follow-up execution that reuses the parent's worktree
142
+ */
143
+ router.post("/executions/:executionId/follow-up", async (req, res) => {
144
+ try {
145
+ const { executionId } = req.params;
146
+ const { feedback } = req.body;
147
+ // Validate required fields
148
+ if (!feedback) {
149
+ res.status(400).json({
150
+ success: false,
151
+ data: null,
152
+ message: "Feedback is required",
153
+ });
154
+ return;
155
+ }
156
+ const followUpExecution = await service.createFollowUp(executionId, feedback);
157
+ res.status(201).json({
158
+ success: true,
159
+ data: followUpExecution,
160
+ });
161
+ }
162
+ catch (error) {
163
+ console.error("Error creating follow-up execution:", error);
164
+ // Handle specific error cases
165
+ const errorMessage = error instanceof Error ? error.message : String(error);
166
+ const statusCode = errorMessage.includes("not found") ||
167
+ errorMessage.includes("no worktree")
168
+ ? 404
169
+ : 500;
170
+ res.status(statusCode).json({
171
+ success: false,
172
+ data: null,
173
+ error_data: errorMessage,
174
+ message: "Failed to create follow-up execution",
175
+ });
176
+ }
177
+ });
178
+ /**
179
+ * DELETE /api/executions/:executionId
180
+ *
181
+ * Cancel a running execution
182
+ */
183
+ router.delete("/executions/:executionId", async (req, res) => {
184
+ try {
185
+ const { executionId } = req.params;
186
+ await service.cancelExecution(executionId);
187
+ res.json({
188
+ success: true,
189
+ data: { executionId },
190
+ message: "Execution cancelled successfully",
191
+ });
192
+ }
193
+ catch (error) {
194
+ console.error("Error cancelling execution:", error);
195
+ // Handle specific error cases
196
+ const errorMessage = error instanceof Error ? error.message : String(error);
197
+ const statusCode = errorMessage.includes("not found") ? 404 : 500;
198
+ res.status(statusCode).json({
199
+ success: false,
200
+ data: null,
201
+ error_data: errorMessage,
202
+ message: "Failed to cancel execution",
203
+ });
204
+ }
205
+ });
206
+ /**
207
+ * GET /api/executions/:executionId/worktree
208
+ *
209
+ * Check if worktree exists for an execution
210
+ */
211
+ router.get("/executions/:executionId/worktree", async (req, res) => {
212
+ try {
213
+ const { executionId } = req.params;
214
+ const exists = await service.worktreeExists(executionId);
215
+ res.json({
216
+ success: true,
217
+ data: { exists },
218
+ });
219
+ }
220
+ catch (error) {
221
+ console.error("Error checking worktree:", error);
222
+ res.status(500).json({
223
+ success: false,
224
+ data: null,
225
+ error_data: error instanceof Error ? error.message : String(error),
226
+ message: "Failed to check worktree status",
227
+ });
228
+ }
229
+ });
230
+ /**
231
+ * DELETE /api/executions/:executionId/worktree
232
+ *
233
+ * Delete the worktree for an execution
234
+ */
235
+ router.delete("/executions/:executionId/worktree", async (req, res) => {
236
+ try {
237
+ const { executionId } = req.params;
238
+ await service.deleteWorktree(executionId);
239
+ res.json({
240
+ success: true,
241
+ data: { executionId },
242
+ message: "Worktree deleted successfully",
243
+ });
244
+ }
245
+ catch (error) {
246
+ console.error("Error deleting worktree:", error);
247
+ // Handle specific error cases
248
+ const errorMessage = error instanceof Error ? error.message : String(error);
249
+ let statusCode = 500;
250
+ if (errorMessage.includes("not found")) {
251
+ statusCode = 404;
252
+ }
253
+ else if (errorMessage.includes("has no worktree") ||
254
+ errorMessage.includes("Cannot delete worktree")) {
255
+ statusCode = 400;
256
+ }
257
+ res.status(statusCode).json({
258
+ success: false,
259
+ data: null,
260
+ error_data: errorMessage,
261
+ message: "Failed to delete worktree",
262
+ });
263
+ }
264
+ });
265
+ return router;
266
+ }
267
+ //# sourceMappingURL=executions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executions.js","sourceRoot":"","sources":["../../src/routes/executions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Feedback API routes (mapped to /api/feedback)
3
+ */
4
+ import { Router } from "express";
5
+ import { createNewFeedback, getFeedbackById, updateExistingFeedback, deleteExistingFeedback, getAllFeedback, } from "../services/feedback.js";
6
+ import { broadcastFeedbackUpdate } from "../services/websocket.js";
7
+ export function createFeedbackRouter(db) {
8
+ const router = Router();
9
+ /**
10
+ * GET /api/feedback - List all feedback with optional filters
11
+ * Query params: spec_id, issue_id, feedback_type, dismissed, limit, offset
12
+ */
13
+ router.get("/", (req, res) => {
14
+ try {
15
+ const options = {};
16
+ if (req.query.spec_id) {
17
+ options.spec_id = req.query.spec_id;
18
+ }
19
+ if (req.query.issue_id) {
20
+ options.issue_id = req.query.issue_id;
21
+ }
22
+ if (req.query.feedback_type) {
23
+ options.feedback_type = req.query.feedback_type;
24
+ }
25
+ if (req.query.dismissed !== undefined) {
26
+ options.dismissed = req.query.dismissed === "true";
27
+ }
28
+ if (req.query.limit) {
29
+ options.limit = parseInt(req.query.limit, 10);
30
+ }
31
+ if (req.query.offset) {
32
+ options.offset = parseInt(req.query.offset, 10);
33
+ }
34
+ const feedback = getAllFeedback(db, options);
35
+ res.json({
36
+ success: true,
37
+ data: feedback,
38
+ });
39
+ }
40
+ catch (error) {
41
+ console.error("Error listing feedback:", error);
42
+ res.status(500).json({
43
+ success: false,
44
+ data: null,
45
+ error_data: error instanceof Error ? error.message : String(error),
46
+ message: "Failed to list feedback",
47
+ });
48
+ }
49
+ });
50
+ /**
51
+ * GET /api/feedback/:id - Get a specific feedback entry
52
+ */
53
+ router.get("/:id", (req, res) => {
54
+ try {
55
+ const { id } = req.params;
56
+ const feedback = getFeedbackById(db, id);
57
+ if (!feedback) {
58
+ res.status(404).json({
59
+ success: false,
60
+ data: null,
61
+ message: `Feedback not found: ${id}`,
62
+ });
63
+ return;
64
+ }
65
+ res.json({
66
+ success: true,
67
+ data: feedback,
68
+ });
69
+ }
70
+ catch (error) {
71
+ console.error("Error getting feedback:", error);
72
+ res.status(500).json({
73
+ success: false,
74
+ data: null,
75
+ error_data: error instanceof Error ? error.message : String(error),
76
+ message: "Failed to get feedback",
77
+ });
78
+ }
79
+ });
80
+ /**
81
+ * POST /api/feedback - Create a new feedback entry
82
+ */
83
+ router.post("/", (req, res) => {
84
+ try {
85
+ const { issue_id, spec_id, feedback_type, content, agent, anchor, dismissed, } = req.body;
86
+ // Validate required fields
87
+ if (!issue_id || typeof issue_id !== "string") {
88
+ res.status(400).json({
89
+ success: false,
90
+ data: null,
91
+ message: "issue_id is required and must be a string",
92
+ });
93
+ return;
94
+ }
95
+ if (!spec_id || typeof spec_id !== "string") {
96
+ res.status(400).json({
97
+ success: false,
98
+ data: null,
99
+ message: "spec_id is required and must be a string",
100
+ });
101
+ return;
102
+ }
103
+ if (!feedback_type || typeof feedback_type !== "string") {
104
+ res.status(400).json({
105
+ success: false,
106
+ data: null,
107
+ message: "feedback_type is required and must be a string",
108
+ });
109
+ return;
110
+ }
111
+ // Validate feedback_type
112
+ const validTypes = ["comment", "suggestion", "request"];
113
+ if (!validTypes.includes(feedback_type)) {
114
+ res.status(400).json({
115
+ success: false,
116
+ data: null,
117
+ message: `Invalid feedback_type. Must be one of: ${validTypes.join(", ")}`,
118
+ });
119
+ return;
120
+ }
121
+ if (!content || typeof content !== "string") {
122
+ res.status(400).json({
123
+ success: false,
124
+ data: null,
125
+ message: "content is required and must be a string",
126
+ });
127
+ return;
128
+ }
129
+ // Validate anchor if provided
130
+ if (anchor !== undefined && anchor !== null) {
131
+ if (typeof anchor !== "object") {
132
+ res.status(400).json({
133
+ success: false,
134
+ data: null,
135
+ message: "anchor must be an object if provided",
136
+ });
137
+ return;
138
+ }
139
+ // Validate anchor structure if provided
140
+ if (anchor.anchor_status) {
141
+ const validAnchorStatuses = ["valid", "relocated", "stale"];
142
+ if (!validAnchorStatuses.includes(anchor.anchor_status)) {
143
+ res.status(400).json({
144
+ success: false,
145
+ data: null,
146
+ message: `Invalid anchor.anchor_status. Must be one of: ${validAnchorStatuses.join(", ")}`,
147
+ });
148
+ return;
149
+ }
150
+ }
151
+ }
152
+ // Create feedback using CLI operation
153
+ const feedback = createNewFeedback(db, {
154
+ issue_id,
155
+ spec_id,
156
+ feedback_type: feedback_type,
157
+ content,
158
+ agent: agent || undefined,
159
+ anchor: anchor,
160
+ dismissed: dismissed || false,
161
+ });
162
+ // Broadcast feedback creation to WebSocket clients
163
+ broadcastFeedbackUpdate("created", feedback);
164
+ res.status(201).json({
165
+ success: true,
166
+ data: feedback,
167
+ });
168
+ }
169
+ catch (error) {
170
+ console.error("Error creating feedback:", error);
171
+ // Handle specific errors
172
+ if (error instanceof Error) {
173
+ if (error.message.includes("not found")) {
174
+ res.status(404).json({
175
+ success: false,
176
+ data: null,
177
+ message: error.message,
178
+ });
179
+ return;
180
+ }
181
+ if (error.message.includes("Constraint violation")) {
182
+ res.status(409).json({
183
+ success: false,
184
+ data: null,
185
+ message: error.message,
186
+ });
187
+ return;
188
+ }
189
+ }
190
+ res.status(500).json({
191
+ success: false,
192
+ data: null,
193
+ error_data: error instanceof Error ? error.message : String(error),
194
+ message: "Failed to create feedback",
195
+ });
196
+ }
197
+ });
198
+ /**
199
+ * PUT /api/feedback/:id - Update an existing feedback entry
200
+ */
201
+ router.put("/:id", (req, res) => {
202
+ try {
203
+ const { id } = req.params;
204
+ const { content, dismissed, anchor } = req.body;
205
+ // Validate that at least one field is provided
206
+ if (content === undefined &&
207
+ dismissed === undefined &&
208
+ anchor === undefined) {
209
+ res.status(400).json({
210
+ success: false,
211
+ data: null,
212
+ message: "At least one field must be provided for update",
213
+ });
214
+ return;
215
+ }
216
+ // Validate anchor if provided
217
+ if (anchor !== undefined) {
218
+ if (typeof anchor !== "object") {
219
+ res.status(400).json({
220
+ success: false,
221
+ data: null,
222
+ message: "anchor must be an object",
223
+ });
224
+ return;
225
+ }
226
+ if (!anchor.anchor_status || typeof anchor.anchor_status !== "string") {
227
+ res.status(400).json({
228
+ success: false,
229
+ data: null,
230
+ message: "anchor.anchor_status is required and must be a string",
231
+ });
232
+ return;
233
+ }
234
+ const validAnchorStatuses = ["valid", "relocated", "stale"];
235
+ if (!validAnchorStatuses.includes(anchor.anchor_status)) {
236
+ res.status(400).json({
237
+ success: false,
238
+ data: null,
239
+ message: `Invalid anchor.anchor_status. Must be one of: ${validAnchorStatuses.join(", ")}`,
240
+ });
241
+ return;
242
+ }
243
+ }
244
+ // Build update input
245
+ const updateInput = {};
246
+ if (content !== undefined)
247
+ updateInput.content = content;
248
+ if (dismissed !== undefined)
249
+ updateInput.dismissed = dismissed;
250
+ if (anchor !== undefined)
251
+ updateInput.anchor = anchor;
252
+ // Update feedback using CLI operation
253
+ const feedback = updateExistingFeedback(db, id, updateInput);
254
+ // Broadcast feedback update to WebSocket clients
255
+ broadcastFeedbackUpdate("updated", feedback);
256
+ res.json({
257
+ success: true,
258
+ data: feedback,
259
+ });
260
+ }
261
+ catch (error) {
262
+ console.error("Error updating feedback:", error);
263
+ // Handle "not found" errors
264
+ if (error instanceof Error && error.message.includes("not found")) {
265
+ res.status(404).json({
266
+ success: false,
267
+ data: null,
268
+ message: error.message,
269
+ });
270
+ return;
271
+ }
272
+ res.status(500).json({
273
+ success: false,
274
+ data: null,
275
+ error_data: error instanceof Error ? error.message : String(error),
276
+ message: "Failed to update feedback",
277
+ });
278
+ }
279
+ });
280
+ /**
281
+ * DELETE /api/feedback/:id - Delete a feedback entry
282
+ */
283
+ router.delete("/:id", (req, res) => {
284
+ try {
285
+ const { id } = req.params;
286
+ // Check if feedback exists first
287
+ const existingFeedback = getFeedbackById(db, id);
288
+ if (!existingFeedback) {
289
+ res.status(404).json({
290
+ success: false,
291
+ data: null,
292
+ message: `Feedback not found: ${id}`,
293
+ });
294
+ return;
295
+ }
296
+ // Delete feedback using CLI operation
297
+ const deleted = deleteExistingFeedback(db, id);
298
+ if (deleted) {
299
+ // Broadcast feedback deletion to WebSocket clients
300
+ broadcastFeedbackUpdate("deleted", { id });
301
+ res.json({
302
+ success: true,
303
+ data: {
304
+ id,
305
+ deleted: true,
306
+ },
307
+ });
308
+ }
309
+ else {
310
+ res.status(500).json({
311
+ success: false,
312
+ data: null,
313
+ message: "Failed to delete feedback",
314
+ });
315
+ }
316
+ }
317
+ catch (error) {
318
+ console.error("Error deleting feedback:", error);
319
+ res.status(500).json({
320
+ success: false,
321
+ data: null,
322
+ error_data: error instanceof Error ? error.message : String(error),
323
+ message: "Failed to delete feedback",
324
+ });
325
+ }
326
+ });
327
+ return router;
328
+ }
329
+ //# sourceMappingURL=feedback.js.map