coder-config 0.50.5-beta → 0.50.6-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/lib/constants.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Constants and tool path configurations
3
3
  */
4
4
 
5
- const VERSION = '0.50.5-beta';
5
+ const VERSION = '0.50.6-beta';
6
6
 
7
7
  // Tool-specific path configurations
8
8
  const TOOL_PATHS = {
package/lib/heartbeat.js CHANGED
@@ -199,10 +199,93 @@ function heartbeat(installDir, config) {
199
199
  };
200
200
  }
201
201
 
202
+ /**
203
+ * Save the last heartbeat report to disk for deduplication
204
+ * @param {string} installDir - path to coder-config install dir
205
+ * @param {object} report - heartbeat report with alerts array
206
+ */
207
+ function saveLastHeartbeat(installDir, report) {
208
+ const loopsDir = path.join(installDir, 'loops');
209
+ fs.mkdirSync(loopsDir, { recursive: true });
210
+ const alertHashes = {};
211
+ const now = Date.now();
212
+ for (const alert of (report.alerts || [])) {
213
+ const key = `${alert.loopId}:${alert.type}`;
214
+ alertHashes[key] = now;
215
+ }
216
+ const data = { alertHashes, timestamp: now };
217
+ fs.writeFileSync(path.join(loopsDir, 'last-heartbeat.json'), JSON.stringify(data, null, 2));
218
+ }
219
+
220
+ /**
221
+ * Load the last saved heartbeat from disk
222
+ * @param {string} installDir - path to coder-config install dir
223
+ * @returns {object|null} saved heartbeat data or null if not found
224
+ */
225
+ function loadLastHeartbeat(installDir) {
226
+ const filePath = path.join(installDir, 'loops', 'last-heartbeat.json');
227
+ try {
228
+ const raw = fs.readFileSync(filePath, 'utf8');
229
+ return JSON.parse(raw);
230
+ } catch {
231
+ return null;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Determine whether an alert should fire (not within cooldown)
237
+ * @param {string} installDir - path to coder-config install dir
238
+ * @param {object} alert - alert object with loopId and type
239
+ * @param {number} cooldownMinutes - cooldown in minutes
240
+ * @returns {boolean} true if should notify, false if within cooldown
241
+ */
242
+ function shouldNotify(installDir, alert, cooldownMinutes) {
243
+ const last = loadLastHeartbeat(installDir);
244
+ if (!last || !last.alertHashes) return true;
245
+ const key = `${alert.loopId}:${alert.type}`;
246
+ const lastTime = last.alertHashes[key];
247
+ if (lastTime == null) return true;
248
+ const ageMinutes = (Date.now() - lastTime) / 60000;
249
+ return ageMinutes >= cooldownMinutes;
250
+ }
251
+
252
+ /**
253
+ * Build a macOS osascript notification command for a heartbeat report
254
+ * @param {object} report - heartbeat report
255
+ * @returns {string} shell command string
256
+ */
257
+ function buildMacosNotification(report) {
258
+ const alerts = report.alerts || [];
259
+ const criticalCount = alerts.filter(a => a.severity === 'critical').length;
260
+ const warningCount = alerts.filter(a => a.severity === 'warning').length;
261
+ const parts = [];
262
+ if (criticalCount > 0) parts.push(`${criticalCount} critical`);
263
+ if (warningCount > 0) parts.push(`${warningCount} warning${warningCount !== 1 ? 's' : ''}`);
264
+ const subtitle = parts.length > 0 ? parts.join(', ') : 'all healthy';
265
+ const body = (report.summary || subtitle).replace(/'/g, "\\'");
266
+ return `osascript -e 'display notification "${body}" with title "Ralph Heartbeat" subtitle "${subtitle}"'`;
267
+ }
268
+
269
+ /**
270
+ * Get exit code for a heartbeat report
271
+ * @param {object} report - heartbeat report with alerts array
272
+ * @returns {number} 0 if healthy or only info, 1 if any warning or critical
273
+ */
274
+ function getExitCode(report) {
275
+ const alerts = report.alerts || [];
276
+ const hasActionable = alerts.some(a => a.severity === 'critical' || a.severity === 'warning');
277
+ return hasActionable ? 1 : 0;
278
+ }
279
+
202
280
  module.exports = {
203
281
  heartbeat,
204
282
  getDefaultHeartbeatConfig,
205
283
  loadHeartbeatConfig,
206
284
  evaluateLoop,
207
- buildSummary
285
+ buildSummary,
286
+ saveLastHeartbeat,
287
+ loadLastHeartbeat,
288
+ shouldNotify,
289
+ buildMacosNotification,
290
+ getExitCode
208
291
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-config",
3
- "version": "0.50.5-beta",
3
+ "version": "0.50.6-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",