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 +1 -1
- package/lib/heartbeat.js +84 -1
- package/package.json +1 -1
package/lib/constants.js
CHANGED
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.
|
|
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",
|