coder-config 0.50.6-beta → 0.50.7-beta

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/config-loader.js CHANGED
@@ -31,6 +31,7 @@ const { getProjectsRegistryPath, loadProjectsRegistry, saveProjectsRegistry, pro
31
31
  const { getWorkstreamsPath, loadWorkstreams, saveWorkstreams, workstreamList, workstreamCreate, workstreamUpdate, workstreamDelete, workstreamUse, workstreamActive, workstreamAddProject, workstreamRemoveProject, workstreamInject, workstreamDetect, workstreamGet, getActiveWorkstream, countWorkstreamsForProject, workstreamInstallHook, workstreamInstallHookGemini, workstreamInstallHookCodex, workstreamDeactivate, workstreamCheckPath, getSettingsPath, loadSettings, saveSettings, workstreamAddTrigger, workstreamRemoveTrigger, workstreamSetAutoActivate, setGlobalAutoActivate, shouldAutoActivate, workstreamCheckFolder, workstreamInstallCdHook, workstreamUninstallCdHook, workstreamCdHookStatus, discoverSubProjects, generateRulesFromRepos, generateRulesWithClaude, generateRulesWithAI, getAvailableAITools, findAIBinary, AI_TOOLS, workstreamSetSandbox } = require('./lib/workstreams');
32
32
  const { getActivityPath, getDefaultActivity, loadActivity, saveActivity, detectProjectRoot, activityLog, activitySummary, generateWorkstreamName, activitySuggestWorkstreams, activityClear } = require('./lib/activity');
33
33
  const { getLoopsPath, loadLoops, saveLoops, loadLoopState, saveLoopState, loadHistory, saveHistory, loopList, loopCreate, loopGet, loopUpdate, loopDelete, loopStart, loopPause, loopResume, loopCancel, loopApprove, loopComplete, loopFail, loopStatus, loopHistory, loopConfig, getActiveLoop, recordIteration, saveClarifications, savePlan, loadClarifications, loadPlan, loopInject, archiveLoop } = require('./lib/loops');
34
+ const { heartbeat: runHeartbeat, formatReport, getExitCode, saveLastHeartbeat, shouldNotify, buildMacosNotification, loadHeartbeatConfig, getDefaultHeartbeatConfig } = require('./lib/heartbeat');
34
35
  const { getSessionStatus, showSessionStatus, flushContext, clearContext, installHooks: sessionInstallHooks, getFlushedContext, installFlushCommand, installAll: sessionInstallAll, SESSION_DIR, FLUSHED_CONTEXT_FILE } = require('./lib/sessions');
35
36
  const { runCli } = require('./lib/cli');
36
37
  const { shellStatus, shellInstall, shellUninstall, printShellStatus } = require('./lib/shell');
@@ -253,6 +254,54 @@ class ClaudeConfigManager {
253
254
  loopInject(silent) { return loopInject(this.installDir, silent); }
254
255
  archiveLoop(loopId) { return archiveLoop(this.installDir, loopId); }
255
256
 
257
+ loopHeartbeat(options = {}) {
258
+ const report = runHeartbeat(this.installDir);
259
+
260
+ if (options.json) {
261
+ console.log(JSON.stringify(report, null, 2));
262
+ } else if (options.quiet) {
263
+ if (getExitCode(report) === 1) {
264
+ console.log(formatReport(report));
265
+ }
266
+ } else {
267
+ console.log(formatReport(report));
268
+ }
269
+
270
+ if (options.notify) {
271
+ const config = loadHeartbeatConfig(this.installDir);
272
+ const newAlerts = report.alerts.filter(a =>
273
+ (a.severity === 'critical' || a.severity === 'warning') &&
274
+ shouldNotify(this.installDir, a, config.cooldownMinutes || 15)
275
+ );
276
+
277
+ if (newAlerts.length > 0) {
278
+ if (config.notifications?.macos?.enabled !== false) {
279
+ const cmd = buildMacosNotification(report);
280
+ try {
281
+ require('child_process').execSync(cmd);
282
+ } catch (e) {
283
+ // osascript may fail in non-GUI contexts
284
+ }
285
+ }
286
+
287
+ if (config.notifications?.slack?.enabled && config.notifications?.slack?.webhookUrl) {
288
+ const https = require('https');
289
+ const url = new URL(config.notifications.slack.webhookUrl);
290
+ const payload = JSON.stringify({ text: `Ralph Heartbeat: ${report.summary}` });
291
+ const req = https.request({ hostname: url.hostname, path: url.pathname, method: 'POST',
292
+ headers: { 'Content-Type': 'application/json' }
293
+ });
294
+ req.write(payload);
295
+ req.end();
296
+ }
297
+
298
+ saveLastHeartbeat(this.installDir, report);
299
+ }
300
+ }
301
+
302
+ return report;
303
+ }
304
+
256
305
  // Activity
257
306
  getActivityPath() { return getActivityPath(this.installDir); }
258
307
  loadActivity() { return loadActivity(this.installDir); }
package/lib/cli.js CHANGED
@@ -272,6 +272,16 @@ function runCli(manager) {
272
272
  } else {
273
273
  manager.loopConfig();
274
274
  }
275
+ } else if (args[1] === 'heartbeat') {
276
+ const options = {
277
+ notify: args.includes('--notify'),
278
+ quiet: args.includes('--quiet'),
279
+ json: args.includes('--json')
280
+ };
281
+ const report = manager.loopHeartbeat(options);
282
+ if (options.quiet) {
283
+ process.exitCode = report.alerts.some(a => a.severity === 'critical' || a.severity === 'warning') ? 1 : 0;
284
+ }
275
285
  } else if (args[1] === 'inject') {
276
286
  const silent = args.includes('--silent') || args.includes('-s');
277
287
  manager.loopInject(silent);
@@ -456,6 +466,9 @@ ${chalk.dim('Configuration manager for AI coding tools (Claude Code, Gemini CLI,
456
466
  cmd('loop cancel <id>', 'Cancel loop'),
457
467
  cmd('loop status [id]', 'Show loop status'),
458
468
  cmd('loop config', 'Show/set loop config'),
469
+ cmd('loop heartbeat', 'Check loop health'),
470
+ cmd('loop heartbeat --notify', 'Check + send notifications'),
471
+ cmd('loop heartbeat --quiet', 'Silent unless alerts'),
459
472
  ]));
460
473
  console.log();
461
474
  }
package/lib/constants.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Constants and tool path configurations
3
3
  */
4
4
 
5
- const VERSION = '0.50.6-beta';
5
+ const VERSION = '0.50.7-beta';
6
6
 
7
7
  // Tool-specific path configurations
8
8
  const TOOL_PATHS = {
package/lib/heartbeat.js CHANGED
@@ -277,6 +277,62 @@ function getExitCode(report) {
277
277
  return hasActionable ? 1 : 0;
278
278
  }
279
279
 
280
+ /**
281
+ * Format a heartbeat report for terminal display
282
+ * @param {object} report - heartbeat report object
283
+ * @returns {string} formatted string for console output
284
+ */
285
+ function formatReport(report) {
286
+ const chalk = require('chalk');
287
+ const lines = [];
288
+
289
+ lines.push(`♥ Loop Heartbeat — ${report.timestamp}`);
290
+ lines.push('');
291
+
292
+ const alerts = report.alerts || [];
293
+ const critical = alerts.filter(a => a.severity === 'critical');
294
+ const warning = alerts.filter(a => a.severity === 'warning');
295
+ const info = alerts.filter(a => a.severity === 'info');
296
+ const healthy = report.healthy || [];
297
+
298
+ if (critical.length > 0) {
299
+ lines.push(chalk.red('🔴 CRITICAL'));
300
+ for (const a of critical) {
301
+ lines.push(` ${chalk.red('✗')} ${a.name} — ${a.message}`);
302
+ }
303
+ lines.push('');
304
+ }
305
+
306
+ if (warning.length > 0) {
307
+ lines.push(chalk.yellow('🟡 WARNING'));
308
+ for (const a of warning) {
309
+ lines.push(` ${chalk.yellow('⚠')} ${a.name} — ${a.message}`);
310
+ }
311
+ lines.push('');
312
+ }
313
+
314
+ if (info.length > 0) {
315
+ lines.push(chalk.blue('🔵 INFO'));
316
+ for (const a of info) {
317
+ lines.push(` ${chalk.blue('ℹ')} ${a.name} — ${a.message}`);
318
+ }
319
+ lines.push('');
320
+ }
321
+
322
+ if (healthy.length > 0) {
323
+ lines.push(chalk.green('🟢 HEALTHY'));
324
+ for (const h of healthy) {
325
+ const phase = h.phase ? ` [${h.phase}]` : '';
326
+ lines.push(` ${chalk.green('●')} ${h.name}${phase} ${h.iteration}`);
327
+ }
328
+ lines.push('');
329
+ }
330
+
331
+ lines.push(`Summary: ${report.summary}`);
332
+
333
+ return lines.join('\n');
334
+ }
335
+
280
336
  module.exports = {
281
337
  heartbeat,
282
338
  getDefaultHeartbeatConfig,
@@ -287,5 +343,6 @@ module.exports = {
287
343
  loadLastHeartbeat,
288
344
  shouldNotify,
289
345
  buildMacosNotification,
290
- getExitCode
346
+ getExitCode,
347
+ formatReport
291
348
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-config",
3
- "version": "0.50.6-beta",
3
+ "version": "0.50.7-beta",
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",