coder-config 0.40.16 → 0.41.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.
- package/README.md +42 -0
- package/config-loader.js +32 -0
- package/hooks/ralph-loop-preprompt.sh +96 -0
- package/hooks/ralph-loop-stop.sh +101 -0
- package/lib/cli.js +81 -0
- package/lib/constants.js +1 -1
- package/lib/loops.js +849 -0
- package/package.json +1 -1
- package/ui/dist/assets/index-BX3EJoIY.css +32 -0
- package/ui/dist/assets/index-CGdqBV9k.js +3839 -0
- package/ui/dist/index.html +2 -2
- package/ui/routes/index.js +2 -0
- package/ui/routes/loops.js +427 -0
- package/ui/routes/projects.js +27 -0
- package/ui/server.cjs +56 -0
- package/ui/dist/assets/index-DZrd_FEC.js +0 -3204
- package/ui/dist/assets/index-DjLdm3Mr.css +0 -32
package/ui/dist/index.html
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
<!-- PWA Manifest -->
|
|
21
21
|
<link rel="manifest" href="/manifest.json">
|
|
22
|
-
<script type="module" crossorigin src="/assets/index-
|
|
23
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
22
|
+
<script type="module" crossorigin src="/assets/index-CGdqBV9k.js"></script>
|
|
23
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BX3EJoIY.css">
|
|
24
24
|
</head>
|
|
25
25
|
<body>
|
|
26
26
|
<div id="root"></div>
|
package/ui/routes/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const projects = require('./projects');
|
|
6
6
|
const workstreams = require('./workstreams');
|
|
7
|
+
const loops = require('./loops');
|
|
7
8
|
const activity = require('./activity');
|
|
8
9
|
const subprojects = require('./subprojects');
|
|
9
10
|
const registry = require('./registry');
|
|
@@ -23,6 +24,7 @@ const mcpDiscovery = require('./mcp-discovery');
|
|
|
23
24
|
module.exports = {
|
|
24
25
|
projects,
|
|
25
26
|
workstreams,
|
|
27
|
+
loops,
|
|
26
28
|
activity,
|
|
27
29
|
subprojects,
|
|
28
30
|
registry,
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loops Routes (Ralph Loop)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get all loops
|
|
11
|
+
*/
|
|
12
|
+
function getLoops(manager) {
|
|
13
|
+
if (!manager) return { error: 'Manager not available' };
|
|
14
|
+
const data = manager.loadLoops();
|
|
15
|
+
|
|
16
|
+
// Enrich with state data
|
|
17
|
+
const loops = (data.loops || []).map(loop => {
|
|
18
|
+
const state = manager.loadLoopState(loop.id);
|
|
19
|
+
return state || loop;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
loops,
|
|
24
|
+
activeId: data.activeId,
|
|
25
|
+
config: data.config || {}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get active loop
|
|
31
|
+
*/
|
|
32
|
+
function getActiveLoop(manager) {
|
|
33
|
+
if (!manager) return { error: 'Manager not available' };
|
|
34
|
+
const active = manager.getActiveLoop();
|
|
35
|
+
return { loop: active };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get a specific loop by ID
|
|
40
|
+
*/
|
|
41
|
+
function getLoop(manager, id) {
|
|
42
|
+
if (!manager) return { error: 'Manager not available' };
|
|
43
|
+
const loop = manager.loopGet(id);
|
|
44
|
+
if (!loop) {
|
|
45
|
+
return { error: 'Loop not found' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Also load clarifications and plan
|
|
49
|
+
const clarifications = manager.loadClarifications(loop.id);
|
|
50
|
+
const plan = manager.loadPlan(loop.id);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
loop,
|
|
54
|
+
clarifications,
|
|
55
|
+
plan
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a new loop
|
|
61
|
+
*/
|
|
62
|
+
function createLoop(manager, body) {
|
|
63
|
+
if (!manager) return { error: 'Manager not available' };
|
|
64
|
+
const { task, name, workstreamId, projectPath } = body;
|
|
65
|
+
|
|
66
|
+
if (!task) {
|
|
67
|
+
return { error: 'Task description is required' };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const loop = manager.loopCreate(task, {
|
|
71
|
+
name,
|
|
72
|
+
workstreamId,
|
|
73
|
+
projectPath
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!loop) {
|
|
77
|
+
return { error: 'Failed to create loop' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { success: true, loop };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Update a loop
|
|
85
|
+
*/
|
|
86
|
+
function updateLoop(manager, id, updates) {
|
|
87
|
+
if (!manager) return { error: 'Manager not available' };
|
|
88
|
+
const loop = manager.loopUpdate(id, updates);
|
|
89
|
+
if (!loop) {
|
|
90
|
+
return { error: 'Loop not found' };
|
|
91
|
+
}
|
|
92
|
+
return { success: true, loop };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Delete a loop
|
|
97
|
+
*/
|
|
98
|
+
function deleteLoop(manager, id) {
|
|
99
|
+
if (!manager) return { error: 'Manager not available' };
|
|
100
|
+
const success = manager.loopDelete(id);
|
|
101
|
+
if (!success) {
|
|
102
|
+
return { error: 'Loop not found' };
|
|
103
|
+
}
|
|
104
|
+
return { success: true };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Start a loop
|
|
109
|
+
*/
|
|
110
|
+
function startLoop(manager, id) {
|
|
111
|
+
if (!manager) return { error: 'Manager not available' };
|
|
112
|
+
const loop = manager.loopStart(id);
|
|
113
|
+
if (!loop) {
|
|
114
|
+
return { error: 'Loop not found or cannot be started' };
|
|
115
|
+
}
|
|
116
|
+
return { success: true, loop };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Pause a loop
|
|
121
|
+
*/
|
|
122
|
+
function pauseLoop(manager, id) {
|
|
123
|
+
if (!manager) return { error: 'Manager not available' };
|
|
124
|
+
const loop = manager.loopPause(id);
|
|
125
|
+
if (!loop) {
|
|
126
|
+
return { error: 'Loop not found' };
|
|
127
|
+
}
|
|
128
|
+
return { success: true, loop };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resume a loop
|
|
133
|
+
*/
|
|
134
|
+
function resumeLoop(manager, id) {
|
|
135
|
+
if (!manager) return { error: 'Manager not available' };
|
|
136
|
+
const loop = manager.loopResume(id);
|
|
137
|
+
if (!loop) {
|
|
138
|
+
return { error: 'Loop not found or cannot be resumed' };
|
|
139
|
+
}
|
|
140
|
+
return { success: true, loop };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Cancel a loop
|
|
145
|
+
*/
|
|
146
|
+
function cancelLoop(manager, id) {
|
|
147
|
+
if (!manager) return { error: 'Manager not available' };
|
|
148
|
+
const loop = manager.loopCancel(id);
|
|
149
|
+
if (!loop) {
|
|
150
|
+
return { error: 'Loop not found' };
|
|
151
|
+
}
|
|
152
|
+
return { success: true, loop };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Approve plan (phase 2)
|
|
157
|
+
*/
|
|
158
|
+
function approveLoop(manager, id) {
|
|
159
|
+
if (!manager) return { error: 'Manager not available' };
|
|
160
|
+
const loop = manager.loopApprove(id);
|
|
161
|
+
if (!loop) {
|
|
162
|
+
return { error: 'Loop not found or not in plan phase' };
|
|
163
|
+
}
|
|
164
|
+
return { success: true, loop };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Mark loop as complete
|
|
169
|
+
*/
|
|
170
|
+
function completeLoop(manager, id) {
|
|
171
|
+
if (!manager) return { error: 'Manager not available' };
|
|
172
|
+
const loop = manager.loopComplete(id);
|
|
173
|
+
if (!loop) {
|
|
174
|
+
return { error: 'Loop not found' };
|
|
175
|
+
}
|
|
176
|
+
return { success: true, loop };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get loop history
|
|
181
|
+
*/
|
|
182
|
+
function getLoopHistory(manager) {
|
|
183
|
+
if (!manager) return { error: 'Manager not available' };
|
|
184
|
+
const history = manager.loadHistory();
|
|
185
|
+
return { completed: history.completed || [] };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get/update loop configuration
|
|
190
|
+
*/
|
|
191
|
+
function getLoopConfig(manager) {
|
|
192
|
+
if (!manager) return { error: 'Manager not available' };
|
|
193
|
+
const data = manager.loadLoops();
|
|
194
|
+
return { config: data.config || {} };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function updateLoopConfig(manager, updates) {
|
|
198
|
+
if (!manager) return { error: 'Manager not available' };
|
|
199
|
+
const config = manager.loopConfig(updates);
|
|
200
|
+
return { success: true, config };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Save clarifications to a loop
|
|
205
|
+
*/
|
|
206
|
+
function saveClarifications(manager, id, content) {
|
|
207
|
+
if (!manager) return { error: 'Manager not available' };
|
|
208
|
+
const loop = manager.loopGet(id);
|
|
209
|
+
if (!loop) {
|
|
210
|
+
return { error: 'Loop not found' };
|
|
211
|
+
}
|
|
212
|
+
manager.saveClarifications(id, content);
|
|
213
|
+
return { success: true };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Save plan to a loop
|
|
218
|
+
*/
|
|
219
|
+
function savePlan(manager, id, content) {
|
|
220
|
+
if (!manager) return { error: 'Manager not available' };
|
|
221
|
+
const loop = manager.loopGet(id);
|
|
222
|
+
if (!loop) {
|
|
223
|
+
return { error: 'Loop not found' };
|
|
224
|
+
}
|
|
225
|
+
manager.savePlan(id, content);
|
|
226
|
+
return { success: true };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Record an iteration
|
|
231
|
+
*/
|
|
232
|
+
function recordIteration(manager, id, iteration) {
|
|
233
|
+
if (!manager) return { error: 'Manager not available' };
|
|
234
|
+
const loop = manager.recordIteration(id, iteration);
|
|
235
|
+
if (!loop) {
|
|
236
|
+
return { error: 'Loop not found' };
|
|
237
|
+
}
|
|
238
|
+
return { success: true, loop };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Check if loop hooks are installed
|
|
243
|
+
*/
|
|
244
|
+
function getLoopHookStatus() {
|
|
245
|
+
const hooksDir = path.join(os.homedir(), '.claude', 'hooks');
|
|
246
|
+
const stopHookPath = path.join(hooksDir, 'ralph-loop-stop.sh');
|
|
247
|
+
const prepromptHookPath = path.join(hooksDir, 'ralph-loop-preprompt.sh');
|
|
248
|
+
|
|
249
|
+
const status = {
|
|
250
|
+
hooksDir,
|
|
251
|
+
dirExists: fs.existsSync(hooksDir),
|
|
252
|
+
stopHook: {
|
|
253
|
+
path: stopHookPath,
|
|
254
|
+
exists: fs.existsSync(stopHookPath)
|
|
255
|
+
},
|
|
256
|
+
prepromptHook: {
|
|
257
|
+
path: prepromptHookPath,
|
|
258
|
+
exists: fs.existsSync(prepromptHookPath)
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
return status;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Install loop hooks
|
|
267
|
+
*/
|
|
268
|
+
function installLoopHooks(manager) {
|
|
269
|
+
const hooksDir = path.join(os.homedir(), '.claude', 'hooks');
|
|
270
|
+
const coderConfigDir = manager ? path.dirname(manager.getLoopsPath()) : path.join(os.homedir(), '.coder-config');
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
if (!fs.existsSync(hooksDir)) {
|
|
274
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Stop hook
|
|
278
|
+
const stopHookContent = `#!/bin/bash
|
|
279
|
+
# Ralph Loop continuation hook
|
|
280
|
+
# Called after each Claude response
|
|
281
|
+
|
|
282
|
+
LOOP_ID="\$CODER_LOOP_ID"
|
|
283
|
+
if [[ -z "\$LOOP_ID" ]]; then
|
|
284
|
+
exit 0
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
STATE_FILE="\$HOME/.coder-config/loops/\$LOOP_ID/state.json"
|
|
288
|
+
if [[ ! -f "\$STATE_FILE" ]]; then
|
|
289
|
+
exit 0
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
# Check if loop is still active
|
|
293
|
+
STATUS=$(jq -r '.status' "\$STATE_FILE")
|
|
294
|
+
if [[ "\$STATUS" != "running" ]]; then
|
|
295
|
+
exit 0
|
|
296
|
+
fi
|
|
297
|
+
|
|
298
|
+
# Check budget limits
|
|
299
|
+
CURRENT_ITER=$(jq -r '.iterations.current' "\$STATE_FILE")
|
|
300
|
+
MAX_ITER=$(jq -r '.iterations.max' "\$STATE_FILE")
|
|
301
|
+
CURRENT_COST=$(jq -r '.budget.currentCost' "\$STATE_FILE")
|
|
302
|
+
MAX_COST=$(jq -r '.budget.maxCost' "\$STATE_FILE")
|
|
303
|
+
|
|
304
|
+
if (( CURRENT_ITER >= MAX_ITER )); then
|
|
305
|
+
echo "Loop paused: max iterations reached (\$MAX_ITER)"
|
|
306
|
+
jq '.status = "paused" | .pauseReason = "max_iterations"' "\$STATE_FILE" > tmp && mv tmp "\$STATE_FILE"
|
|
307
|
+
exit 0
|
|
308
|
+
fi
|
|
309
|
+
|
|
310
|
+
if (( $(echo "\$CURRENT_COST >= \$MAX_COST" | bc -l) )); then
|
|
311
|
+
echo "Loop paused: budget exceeded (\$\$MAX_COST)"
|
|
312
|
+
jq '.status = "paused" | .pauseReason = "budget"' "\$STATE_FILE" > tmp && mv tmp "\$STATE_FILE"
|
|
313
|
+
exit 0
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
# Update iteration count
|
|
317
|
+
jq ".iterations.current = $((CURRENT_ITER + 1))" "\$STATE_FILE" > tmp && mv tmp "\$STATE_FILE"
|
|
318
|
+
|
|
319
|
+
# Check if task is complete (Claude sets this flag)
|
|
320
|
+
PHASE=$(jq -r '.phase' "\$STATE_FILE")
|
|
321
|
+
TASK_COMPLETE=$(jq -r '.taskComplete // false' "\$STATE_FILE")
|
|
322
|
+
|
|
323
|
+
if [[ "\$TASK_COMPLETE" == "true" ]]; then
|
|
324
|
+
jq '.status = "completed" | .completedAt = now' "\$STATE_FILE" > tmp && mv tmp "\$STATE_FILE"
|
|
325
|
+
echo "Loop completed successfully!"
|
|
326
|
+
exit 0
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
# Continue loop - output continuation prompt
|
|
330
|
+
PHASE_PROMPT=""
|
|
331
|
+
case "\$PHASE" in
|
|
332
|
+
"clarify")
|
|
333
|
+
PHASE_PROMPT="Continue clarifying requirements. If requirements are clear, advance to planning phase by setting phase='plan'."
|
|
334
|
+
;;
|
|
335
|
+
"plan")
|
|
336
|
+
PHASE_PROMPT="Continue developing the implementation plan. When plan is complete and approved, advance to execution phase."
|
|
337
|
+
;;
|
|
338
|
+
"execute")
|
|
339
|
+
PHASE_PROMPT="Continue executing the plan. When task is complete, set taskComplete=true."
|
|
340
|
+
;;
|
|
341
|
+
esac
|
|
342
|
+
|
|
343
|
+
echo ""
|
|
344
|
+
echo "---"
|
|
345
|
+
echo "[Ralph Loop iteration $((CURRENT_ITER + 1))/\$MAX_ITER]"
|
|
346
|
+
echo "\$PHASE_PROMPT"
|
|
347
|
+
echo "---"
|
|
348
|
+
`;
|
|
349
|
+
|
|
350
|
+
const stopHookPath = path.join(hooksDir, 'ralph-loop-stop.sh');
|
|
351
|
+
fs.writeFileSync(stopHookPath, stopHookContent);
|
|
352
|
+
fs.chmodSync(stopHookPath, '755');
|
|
353
|
+
|
|
354
|
+
// Pre-prompt hook
|
|
355
|
+
const prepromptHookContent = `#!/bin/bash
|
|
356
|
+
# Ralph Loop context injection
|
|
357
|
+
|
|
358
|
+
LOOP_ID="\$CODER_LOOP_ID"
|
|
359
|
+
if [[ -z "\$LOOP_ID" ]]; then
|
|
360
|
+
exit 0
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
STATE_FILE="\$HOME/.coder-config/loops/\$LOOP_ID/state.json"
|
|
364
|
+
PLAN_FILE="\$HOME/.coder-config/loops/\$LOOP_ID/plan.md"
|
|
365
|
+
CLARIFY_FILE="\$HOME/.coder-config/loops/\$LOOP_ID/clarifications.md"
|
|
366
|
+
|
|
367
|
+
if [[ ! -f "\$STATE_FILE" ]]; then
|
|
368
|
+
exit 0
|
|
369
|
+
fi
|
|
370
|
+
|
|
371
|
+
echo "<ralph-loop-context>"
|
|
372
|
+
echo "Loop: $(jq -r '.name' "\$STATE_FILE")"
|
|
373
|
+
echo "Phase: $(jq -r '.phase' "\$STATE_FILE")"
|
|
374
|
+
echo "Iteration: $(jq -r '.iterations.current' "\$STATE_FILE")/$(jq -r '.iterations.max' "\$STATE_FILE")"
|
|
375
|
+
|
|
376
|
+
if [[ -f "\$CLARIFY_FILE" ]]; then
|
|
377
|
+
echo ""
|
|
378
|
+
echo "## Clarifications"
|
|
379
|
+
cat "\$CLARIFY_FILE"
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
if [[ -f "\$PLAN_FILE" ]]; then
|
|
383
|
+
echo ""
|
|
384
|
+
echo "## Plan"
|
|
385
|
+
cat "\$PLAN_FILE"
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
echo "</ralph-loop-context>"
|
|
389
|
+
`;
|
|
390
|
+
|
|
391
|
+
const prepromptHookPath = path.join(hooksDir, 'ralph-loop-preprompt.sh');
|
|
392
|
+
fs.writeFileSync(prepromptHookPath, prepromptHookContent);
|
|
393
|
+
fs.chmodSync(prepromptHookPath, '755');
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
success: true,
|
|
397
|
+
message: 'Loop hooks installed successfully',
|
|
398
|
+
stopHook: stopHookPath,
|
|
399
|
+
prepromptHook: prepromptHookPath
|
|
400
|
+
};
|
|
401
|
+
} catch (e) {
|
|
402
|
+
return { error: e.message };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
module.exports = {
|
|
407
|
+
getLoops,
|
|
408
|
+
getActiveLoop,
|
|
409
|
+
getLoop,
|
|
410
|
+
createLoop,
|
|
411
|
+
updateLoop,
|
|
412
|
+
deleteLoop,
|
|
413
|
+
startLoop,
|
|
414
|
+
pauseLoop,
|
|
415
|
+
resumeLoop,
|
|
416
|
+
cancelLoop,
|
|
417
|
+
approveLoop,
|
|
418
|
+
completeLoop,
|
|
419
|
+
getLoopHistory,
|
|
420
|
+
getLoopConfig,
|
|
421
|
+
updateLoopConfig,
|
|
422
|
+
saveClarifications,
|
|
423
|
+
savePlan,
|
|
424
|
+
recordIteration,
|
|
425
|
+
getLoopHookStatus,
|
|
426
|
+
installLoopHooks,
|
|
427
|
+
};
|
package/ui/routes/projects.js
CHANGED
|
@@ -157,6 +157,32 @@ function removeProject(manager, projectId, setProjectDir) {
|
|
|
157
157
|
return { success: true, removed };
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Update a project's name
|
|
162
|
+
*/
|
|
163
|
+
function updateProject(manager, projectId, updates) {
|
|
164
|
+
if (!manager) return { error: 'Manager not available' };
|
|
165
|
+
|
|
166
|
+
const registry = manager.loadProjectsRegistry();
|
|
167
|
+
const project = registry.projects.find(p => p.id === projectId);
|
|
168
|
+
|
|
169
|
+
if (!project) {
|
|
170
|
+
return { error: 'Project not found' };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Only allow updating the name for now
|
|
174
|
+
if (updates.name && typeof updates.name === 'string' && updates.name.trim()) {
|
|
175
|
+
project.name = updates.name.trim();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
manager.saveProjectsRegistry(registry);
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
success: true,
|
|
182
|
+
project
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
160
186
|
/**
|
|
161
187
|
* Set active project and switch server context
|
|
162
188
|
*/
|
|
@@ -193,6 +219,7 @@ module.exports = {
|
|
|
193
219
|
getProjects,
|
|
194
220
|
getActiveProject,
|
|
195
221
|
addProject,
|
|
222
|
+
updateProject,
|
|
196
223
|
removeProject,
|
|
197
224
|
setActiveProject,
|
|
198
225
|
};
|
package/ui/server.cjs
CHANGED
|
@@ -666,6 +666,33 @@ class ConfigUIServer {
|
|
|
666
666
|
if (req.method === 'POST') return this.json(res, routes.workstreams.installWorkstreamHook());
|
|
667
667
|
break;
|
|
668
668
|
|
|
669
|
+
// Loops (Ralph Loop)
|
|
670
|
+
case '/api/loops':
|
|
671
|
+
if (req.method === 'GET') return this.json(res, routes.loops.getLoops(this.manager));
|
|
672
|
+
if (req.method === 'POST') return this.json(res, routes.loops.createLoop(this.manager, body));
|
|
673
|
+
break;
|
|
674
|
+
|
|
675
|
+
case '/api/loops/active':
|
|
676
|
+
if (req.method === 'GET') return this.json(res, routes.loops.getActiveLoop(this.manager));
|
|
677
|
+
break;
|
|
678
|
+
|
|
679
|
+
case '/api/loops/history':
|
|
680
|
+
if (req.method === 'GET') return this.json(res, routes.loops.getLoopHistory(this.manager));
|
|
681
|
+
break;
|
|
682
|
+
|
|
683
|
+
case '/api/loops/config':
|
|
684
|
+
if (req.method === 'GET') return this.json(res, routes.loops.getLoopConfig(this.manager));
|
|
685
|
+
if (req.method === 'PUT') return this.json(res, routes.loops.updateLoopConfig(this.manager, body));
|
|
686
|
+
break;
|
|
687
|
+
|
|
688
|
+
case '/api/loops/hook-status':
|
|
689
|
+
if (req.method === 'GET') return this.json(res, routes.loops.getLoopHookStatus());
|
|
690
|
+
break;
|
|
691
|
+
|
|
692
|
+
case '/api/loops/install-hooks':
|
|
693
|
+
if (req.method === 'POST') return this.json(res, routes.loops.installLoopHooks(this.manager));
|
|
694
|
+
break;
|
|
695
|
+
|
|
669
696
|
case '/api/activity':
|
|
670
697
|
if (req.method === 'GET') return this.json(res, routes.activity.getActivitySummary(this.manager));
|
|
671
698
|
if (req.method === 'DELETE') return this.json(res, routes.activity.clearActivity(this.manager, body.olderThanDays || 30));
|
|
@@ -725,6 +752,35 @@ class ConfigUIServer {
|
|
|
725
752
|
}
|
|
726
753
|
}
|
|
727
754
|
|
|
755
|
+
if (pathname.startsWith('/api/projects/') && req.method === 'PUT') {
|
|
756
|
+
const projectId = pathname.split('/').pop();
|
|
757
|
+
if (projectId && projectId !== 'active') {
|
|
758
|
+
return this.json(res, routes.projects.updateProject(this.manager, projectId, body));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Dynamic loops routes
|
|
763
|
+
if (pathname.startsWith('/api/loops/') && !pathname.includes('/active') && !pathname.includes('/history') && !pathname.includes('/config') && !pathname.includes('/hook')) {
|
|
764
|
+
const parts = pathname.split('/');
|
|
765
|
+
const loopId = parts[3];
|
|
766
|
+
const action = parts[4];
|
|
767
|
+
|
|
768
|
+
if (loopId) {
|
|
769
|
+
if (req.method === 'GET' && !action) return this.json(res, routes.loops.getLoop(this.manager, loopId));
|
|
770
|
+
if (req.method === 'PUT' && !action) return this.json(res, routes.loops.updateLoop(this.manager, loopId, body));
|
|
771
|
+
if (req.method === 'DELETE' && !action) return this.json(res, routes.loops.deleteLoop(this.manager, loopId));
|
|
772
|
+
if (req.method === 'POST' && action === 'start') return this.json(res, routes.loops.startLoop(this.manager, loopId));
|
|
773
|
+
if (req.method === 'POST' && action === 'pause') return this.json(res, routes.loops.pauseLoop(this.manager, loopId));
|
|
774
|
+
if (req.method === 'POST' && action === 'resume') return this.json(res, routes.loops.resumeLoop(this.manager, loopId));
|
|
775
|
+
if (req.method === 'POST' && action === 'cancel') return this.json(res, routes.loops.cancelLoop(this.manager, loopId));
|
|
776
|
+
if (req.method === 'POST' && action === 'approve') return this.json(res, routes.loops.approveLoop(this.manager, loopId));
|
|
777
|
+
if (req.method === 'POST' && action === 'complete') return this.json(res, routes.loops.completeLoop(this.manager, loopId));
|
|
778
|
+
if (req.method === 'POST' && action === 'clarifications') return this.json(res, routes.loops.saveClarifications(this.manager, loopId, body.content));
|
|
779
|
+
if (req.method === 'POST' && action === 'plan') return this.json(res, routes.loops.savePlan(this.manager, loopId, body.content));
|
|
780
|
+
if (req.method === 'POST' && action === 'iteration') return this.json(res, routes.loops.recordIteration(this.manager, loopId, body));
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
728
784
|
res.writeHead(404);
|
|
729
785
|
res.end(JSON.stringify({ error: 'Not found' }));
|
|
730
786
|
}
|