coder-config 0.44.22 → 0.44.24
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/lib/constants.js +1 -1
- package/package.json +1 -1
- package/ui/routes/loops.js +154 -87
- package/ui/server.cjs +16 -0
package/lib/constants.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coder-config",
|
|
3
|
-
"version": "0.44.
|
|
3
|
+
"version": "0.44.24",
|
|
4
4
|
"description": "Configuration manager for AI coding tools - Claude Code, Gemini CLI, Codex CLI, Antigravity. Manage MCPs, rules, permissions, memory, and workstreams.",
|
|
5
5
|
"author": "regression.io",
|
|
6
6
|
"main": "config-loader.js",
|
package/ui/routes/loops.js
CHANGED
|
@@ -115,6 +115,12 @@ function startLoop(manager, id) {
|
|
|
115
115
|
if (!loop) {
|
|
116
116
|
return { error: 'Loop not found or cannot be started' };
|
|
117
117
|
}
|
|
118
|
+
|
|
119
|
+
// Setup hooks for this project
|
|
120
|
+
if (loop.projectPath) {
|
|
121
|
+
setupLoopHooks(loop.projectPath);
|
|
122
|
+
}
|
|
123
|
+
|
|
118
124
|
return { success: true, loop };
|
|
119
125
|
}
|
|
120
126
|
|
|
@@ -139,6 +145,12 @@ function resumeLoop(manager, id) {
|
|
|
139
145
|
if (!loop) {
|
|
140
146
|
return { error: 'Loop not found or cannot be resumed' };
|
|
141
147
|
}
|
|
148
|
+
|
|
149
|
+
// Setup hooks for this project
|
|
150
|
+
if (loop.projectPath) {
|
|
151
|
+
setupLoopHooks(loop.projectPath);
|
|
152
|
+
}
|
|
153
|
+
|
|
142
154
|
return { success: true, loop };
|
|
143
155
|
}
|
|
144
156
|
|
|
@@ -241,56 +253,32 @@ function recordIteration(manager, id, iteration) {
|
|
|
241
253
|
}
|
|
242
254
|
|
|
243
255
|
/**
|
|
244
|
-
* Check if loop hooks are
|
|
256
|
+
* Check if loop hooks are available (scripts exist)
|
|
257
|
+
* Hooks are now installed per-project, not globally
|
|
245
258
|
*/
|
|
246
259
|
function getLoopHookStatus() {
|
|
247
|
-
const claudeSettingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
248
260
|
const claudeHooksDir = path.join(os.homedir(), '.claude', 'hooks');
|
|
249
261
|
const stopHookPath = path.join(claudeHooksDir, 'ralph-loop-stop.sh');
|
|
250
262
|
const prepromptHookPath = path.join(claudeHooksDir, 'ralph-loop-preprompt.sh');
|
|
251
263
|
|
|
252
|
-
// Check if hook files exist
|
|
264
|
+
// Check if hook script files exist
|
|
253
265
|
const stopHookFileExists = fs.existsSync(stopHookPath);
|
|
254
266
|
const prepromptHookFileExists = fs.existsSync(prepromptHookPath);
|
|
255
267
|
|
|
256
|
-
//
|
|
257
|
-
let stopHookRegistered = false;
|
|
258
|
-
let prepromptHookRegistered = false;
|
|
259
|
-
|
|
260
|
-
if (fs.existsSync(claudeSettingsPath)) {
|
|
261
|
-
try {
|
|
262
|
-
const settings = JSON.parse(fs.readFileSync(claudeSettingsPath, 'utf8'));
|
|
263
|
-
const hooks = settings.hooks || {};
|
|
264
|
-
|
|
265
|
-
// Check Stop hook registration
|
|
266
|
-
if (hooks.Stop) {
|
|
267
|
-
stopHookRegistered = hooks.Stop.some(entry =>
|
|
268
|
-
entry.hooks?.some(h => h.command?.includes('ralph-loop-stop.sh'))
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Check PreToolUse hook registration
|
|
273
|
-
if (hooks.PreToolUse) {
|
|
274
|
-
prepromptHookRegistered = hooks.PreToolUse.some(entry =>
|
|
275
|
-
entry.hooks?.some(h => h.command?.includes('ralph-loop-preprompt.sh'))
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
} catch (e) {
|
|
279
|
-
// Ignore parse errors
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
268
|
+
// Hooks are installed per-project now, so "registered" means scripts are available
|
|
283
269
|
const status = {
|
|
284
270
|
stopHook: {
|
|
285
271
|
path: stopHookPath,
|
|
286
272
|
exists: stopHookFileExists,
|
|
287
|
-
registered:
|
|
273
|
+
registered: stopHookFileExists // Scripts are ready to be used
|
|
288
274
|
},
|
|
289
275
|
prepromptHook: {
|
|
290
276
|
path: prepromptHookPath,
|
|
291
277
|
exists: prepromptHookFileExists,
|
|
292
|
-
registered:
|
|
293
|
-
}
|
|
278
|
+
registered: prepromptHookFileExists
|
|
279
|
+
},
|
|
280
|
+
mode: 'per-project',
|
|
281
|
+
note: 'Hooks are installed to project .claude/settings.local.json when loops start'
|
|
294
282
|
};
|
|
295
283
|
|
|
296
284
|
return status;
|
|
@@ -437,11 +425,11 @@ function fixRalphLoopPluginStructure() {
|
|
|
437
425
|
}
|
|
438
426
|
|
|
439
427
|
/**
|
|
440
|
-
* Install loop hooks
|
|
441
|
-
*
|
|
428
|
+
* Install loop hooks for a specific project directory
|
|
429
|
+
* Creates .claude/settings.local.json with hooks (not committed to git)
|
|
430
|
+
* This avoids the global trust prompt issue
|
|
442
431
|
*/
|
|
443
|
-
function installLoopHooks(manager) {
|
|
444
|
-
const claudeSettingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
432
|
+
function installLoopHooks(manager, projectPath = null) {
|
|
445
433
|
const claudeHooksDir = path.join(os.homedir(), '.claude', 'hooks');
|
|
446
434
|
|
|
447
435
|
// Ensure hooks directory exists
|
|
@@ -458,7 +446,7 @@ function installLoopHooks(manager) {
|
|
|
458
446
|
const prepromptHookDest = path.join(claudeHooksDir, 'ralph-loop-preprompt.sh');
|
|
459
447
|
|
|
460
448
|
try {
|
|
461
|
-
// Copy hook scripts
|
|
449
|
+
// Copy hook scripts to ~/.claude/hooks/
|
|
462
450
|
if (fs.existsSync(stopHookSource)) {
|
|
463
451
|
fs.copyFileSync(stopHookSource, stopHookDest);
|
|
464
452
|
fs.chmodSync(stopHookDest, '755');
|
|
@@ -468,76 +456,153 @@ function installLoopHooks(manager) {
|
|
|
468
456
|
fs.chmodSync(prepromptHookDest, '755');
|
|
469
457
|
}
|
|
470
458
|
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
if (fs.existsSync(claudeSettingsPath)) {
|
|
474
|
-
settings = JSON.parse(fs.readFileSync(claudeSettingsPath, 'utf8'));
|
|
475
|
-
}
|
|
459
|
+
// Remove hooks from global settings.json if they exist
|
|
460
|
+
removeGlobalHooks();
|
|
476
461
|
|
|
477
|
-
//
|
|
478
|
-
if (
|
|
479
|
-
|
|
480
|
-
|
|
462
|
+
// If project path provided, install hooks to project's .claude/settings.local.json
|
|
463
|
+
if (projectPath) {
|
|
464
|
+
const projectClaudeDir = path.join(projectPath, '.claude');
|
|
465
|
+
const projectSettingsLocalPath = path.join(projectClaudeDir, 'settings.local.json');
|
|
481
466
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
command: stopHookDest
|
|
487
|
-
}]
|
|
488
|
-
};
|
|
467
|
+
// Ensure project .claude directory exists
|
|
468
|
+
if (!fs.existsSync(projectClaudeDir)) {
|
|
469
|
+
fs.mkdirSync(projectClaudeDir, { recursive: true });
|
|
470
|
+
}
|
|
489
471
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (!hookExists) {
|
|
499
|
-
settings.hooks.Stop.push(stopHookEntry);
|
|
472
|
+
// Load or create project-local settings
|
|
473
|
+
let projectSettings = {};
|
|
474
|
+
if (fs.existsSync(projectSettingsLocalPath)) {
|
|
475
|
+
try {
|
|
476
|
+
projectSettings = JSON.parse(fs.readFileSync(projectSettingsLocalPath, 'utf8'));
|
|
477
|
+
} catch (e) {
|
|
478
|
+
projectSettings = {};
|
|
479
|
+
}
|
|
500
480
|
}
|
|
501
|
-
}
|
|
502
481
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
command: prepromptHookDest
|
|
508
|
-
}]
|
|
509
|
-
};
|
|
482
|
+
// Initialize hooks if needed
|
|
483
|
+
if (!projectSettings.hooks) {
|
|
484
|
+
projectSettings.hooks = {};
|
|
485
|
+
}
|
|
510
486
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
487
|
+
// Add Stop hook
|
|
488
|
+
const stopHookEntry = {
|
|
489
|
+
hooks: [{
|
|
490
|
+
type: 'command',
|
|
491
|
+
command: stopHookDest
|
|
492
|
+
}]
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
if (!projectSettings.hooks.Stop) {
|
|
496
|
+
projectSettings.hooks.Stop = [stopHookEntry];
|
|
497
|
+
} else {
|
|
498
|
+
const hookExists = projectSettings.hooks.Stop.some(entry =>
|
|
499
|
+
entry.hooks?.some(h => h.command?.includes('ralph-loop-stop.sh'))
|
|
500
|
+
);
|
|
501
|
+
if (!hookExists) {
|
|
502
|
+
projectSettings.hooks.Stop.push(stopHookEntry);
|
|
503
|
+
}
|
|
519
504
|
}
|
|
520
|
-
}
|
|
521
505
|
|
|
522
|
-
|
|
523
|
-
|
|
506
|
+
// Save project-local settings
|
|
507
|
+
fs.writeFileSync(projectSettingsLocalPath, JSON.stringify(projectSettings, null, 2) + '\n');
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
success: true,
|
|
511
|
+
message: 'Loop hooks installed to project',
|
|
512
|
+
projectPath,
|
|
513
|
+
settingsFile: projectSettingsLocalPath,
|
|
514
|
+
stopHook: stopHookDest,
|
|
515
|
+
note: 'Hooks installed to .claude/settings.local.json (not committed to git)'
|
|
516
|
+
};
|
|
517
|
+
}
|
|
524
518
|
|
|
525
519
|
return {
|
|
526
520
|
success: true,
|
|
527
|
-
message: '
|
|
521
|
+
message: 'Hook scripts copied to ~/.claude/hooks/',
|
|
528
522
|
stopHook: stopHookDest,
|
|
529
523
|
prepromptHook: prepromptHookDest,
|
|
530
|
-
note: '
|
|
524
|
+
note: 'Hooks will be installed to project directories when loops are started'
|
|
531
525
|
};
|
|
532
526
|
} catch (error) {
|
|
533
527
|
return {
|
|
534
528
|
success: false,
|
|
535
|
-
error: error.message
|
|
536
|
-
suggestion: 'Try manually copying hooks to ~/.claude/hooks/ and adding to ~/.claude/settings.json'
|
|
529
|
+
error: error.message
|
|
537
530
|
};
|
|
538
531
|
}
|
|
539
532
|
}
|
|
540
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Remove ralph-loop hooks from global ~/.claude/settings.json
|
|
536
|
+
*/
|
|
537
|
+
function removeGlobalHooks() {
|
|
538
|
+
const claudeSettingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
539
|
+
|
|
540
|
+
if (!fs.existsSync(claudeSettingsPath)) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
try {
|
|
545
|
+
const settings = JSON.parse(fs.readFileSync(claudeSettingsPath, 'utf8'));
|
|
546
|
+
|
|
547
|
+
if (!settings.hooks) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
let modified = false;
|
|
552
|
+
|
|
553
|
+
// Remove Stop hooks that reference ralph-loop-stop.sh
|
|
554
|
+
if (settings.hooks.Stop) {
|
|
555
|
+
const original = settings.hooks.Stop.length;
|
|
556
|
+
settings.hooks.Stop = settings.hooks.Stop.filter(entry =>
|
|
557
|
+
!entry.hooks?.some(h => h.command?.includes('ralph-loop-stop.sh'))
|
|
558
|
+
);
|
|
559
|
+
if (settings.hooks.Stop.length === 0) {
|
|
560
|
+
delete settings.hooks.Stop;
|
|
561
|
+
}
|
|
562
|
+
if (settings.hooks.Stop?.length !== original) {
|
|
563
|
+
modified = true;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Remove PreToolUse hooks that reference ralph-loop-preprompt.sh
|
|
568
|
+
if (settings.hooks.PreToolUse) {
|
|
569
|
+
const original = settings.hooks.PreToolUse.length;
|
|
570
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(entry =>
|
|
571
|
+
!entry.hooks?.some(h => h.command?.includes('ralph-loop-preprompt.sh'))
|
|
572
|
+
);
|
|
573
|
+
if (settings.hooks.PreToolUse.length === 0) {
|
|
574
|
+
delete settings.hooks.PreToolUse;
|
|
575
|
+
}
|
|
576
|
+
if (settings.hooks.PreToolUse?.length !== original) {
|
|
577
|
+
modified = true;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Clean up empty hooks object
|
|
582
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
583
|
+
delete settings.hooks;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (modified) {
|
|
587
|
+
fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
588
|
+
}
|
|
589
|
+
} catch (e) {
|
|
590
|
+
// Ignore errors
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Setup hooks for a specific loop run
|
|
596
|
+
* Called when starting/resuming a loop
|
|
597
|
+
*/
|
|
598
|
+
function setupLoopHooks(projectPath) {
|
|
599
|
+
if (!projectPath) {
|
|
600
|
+
return { success: false, error: 'No project path provided' };
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return installLoopHooks(null, projectPath);
|
|
604
|
+
}
|
|
605
|
+
|
|
541
606
|
module.exports = {
|
|
542
607
|
getLoops,
|
|
543
608
|
getActiveLoop,
|
|
@@ -559,6 +624,8 @@ module.exports = {
|
|
|
559
624
|
recordIteration,
|
|
560
625
|
getLoopHookStatus,
|
|
561
626
|
installLoopHooks,
|
|
627
|
+
setupLoopHooks,
|
|
628
|
+
removeGlobalHooks,
|
|
562
629
|
getRalphLoopPluginStatus,
|
|
563
630
|
installRalphLoopPlugin,
|
|
564
631
|
fixRalphLoopPluginStructure,
|
package/ui/server.cjs
CHANGED
|
@@ -252,6 +252,9 @@ class ConfigUIServer {
|
|
|
252
252
|
// ==================== HTTP Server ====================
|
|
253
253
|
|
|
254
254
|
start() {
|
|
255
|
+
// Run migrations
|
|
256
|
+
this.runMigrations();
|
|
257
|
+
|
|
255
258
|
const server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
256
259
|
this.terminalServer.attach(server);
|
|
257
260
|
|
|
@@ -262,6 +265,19 @@ class ConfigUIServer {
|
|
|
262
265
|
});
|
|
263
266
|
}
|
|
264
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Run migrations on server start
|
|
270
|
+
*/
|
|
271
|
+
runMigrations() {
|
|
272
|
+
try {
|
|
273
|
+
// Migration: Remove global ralph-loop hooks (v0.44.23+)
|
|
274
|
+
// Hooks are now installed per-project instead of globally
|
|
275
|
+
routes.loops.removeGlobalHooks();
|
|
276
|
+
} catch (e) {
|
|
277
|
+
// Ignore migration errors
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
265
281
|
async handleRequest(req, res) {
|
|
266
282
|
const parsedUrl = url.parse(req.url, true);
|
|
267
283
|
const pathname = parsedUrl.pathname;
|