@axiomatic-labs/claudeflow 2.13.23 → 2.13.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/hook-overrides.js +23 -1
- package/lib/panel.js +124 -1
- package/package.json +1 -1
package/lib/hook-overrides.js
CHANGED
|
@@ -51,7 +51,7 @@ function normalizeHandlerPath(command) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function defaultOverrides() {
|
|
54
|
-
return { version: 1, disabledHandlers: [] };
|
|
54
|
+
return { version: 1, disabledHandlers: [], disabledReminders: [] };
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function readOverrides(projectRoot) {
|
|
@@ -60,6 +60,7 @@ function readOverrides(projectRoot) {
|
|
|
60
60
|
return {
|
|
61
61
|
version: data.version || 1,
|
|
62
62
|
disabledHandlers: Array.isArray(data.disabledHandlers) ? data.disabledHandlers : [],
|
|
63
|
+
disabledReminders: Array.isArray(data.disabledReminders) ? data.disabledReminders : [],
|
|
63
64
|
};
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -172,6 +173,26 @@ function toggleHookOverride(projectRoot, { event, matcher = '', handler, disable
|
|
|
172
173
|
return { state: 'enabled' };
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
// Toggle a single reminder by id. Same persistence file, same hot-reload
|
|
177
|
+
// semantics — `reload-reminder.js` re-reads overrides on every invocation.
|
|
178
|
+
function toggleReminderOverride(projectRoot, { id, disable }) {
|
|
179
|
+
if (typeof id !== 'string' || !id.trim()) return { state: 'error', reason: 'id-required' };
|
|
180
|
+
const overrides = readOverrides(projectRoot);
|
|
181
|
+
const list = overrides.disabledReminders;
|
|
182
|
+
const idx = list.indexOf(id);
|
|
183
|
+
|
|
184
|
+
if (disable) {
|
|
185
|
+
if (idx !== -1) return { state: 'noop', reason: 'already-disabled' };
|
|
186
|
+
list.push(id);
|
|
187
|
+
writeOverrides(projectRoot, overrides);
|
|
188
|
+
return { state: 'disabled' };
|
|
189
|
+
}
|
|
190
|
+
if (idx === -1) return { state: 'noop', reason: 'not-disabled' };
|
|
191
|
+
list.splice(idx, 1);
|
|
192
|
+
writeOverrides(projectRoot, overrides);
|
|
193
|
+
return { state: 'enabled' };
|
|
194
|
+
}
|
|
195
|
+
|
|
175
196
|
module.exports = {
|
|
176
197
|
OVERRIDES_REL,
|
|
177
198
|
SETTINGS_REL,
|
|
@@ -180,6 +201,7 @@ module.exports = {
|
|
|
180
201
|
readOverrides,
|
|
181
202
|
writeOverrides,
|
|
182
203
|
toggleHookOverride,
|
|
204
|
+
toggleReminderOverride,
|
|
183
205
|
normalizeHandlerPath,
|
|
184
206
|
matchesOverride,
|
|
185
207
|
};
|
package/lib/panel.js
CHANGED
|
@@ -24,9 +24,26 @@ const {
|
|
|
24
24
|
const {
|
|
25
25
|
readOverrides,
|
|
26
26
|
toggleHookOverride,
|
|
27
|
+
toggleReminderOverride,
|
|
27
28
|
normalizeHandlerPath,
|
|
28
29
|
} = require('./hook-overrides.js');
|
|
29
30
|
|
|
31
|
+
// Lazy-load the reminder catalog from the template hook helper. The path
|
|
32
|
+
// resolves the same way at install time and from the template repo.
|
|
33
|
+
function loadReminderHelpers(cwd) {
|
|
34
|
+
const candidates = [
|
|
35
|
+
path.join(cwd, '.claude', 'hooks', 'shared', 'reload-reminder.js'),
|
|
36
|
+
];
|
|
37
|
+
for (const c of candidates) {
|
|
38
|
+
if (fs.existsSync(c)) {
|
|
39
|
+
// Bypass require cache so updates to the template are picked up.
|
|
40
|
+
delete require.cache[require.resolve(c)];
|
|
41
|
+
return require(c);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
const IDLE_SHUTDOWN_MS = 30 * 60 * 1000; // 30 min
|
|
31
48
|
|
|
32
49
|
// ─── data collectors ─────────────────────────────────────────────
|
|
@@ -227,6 +244,29 @@ function getActiveRunInfo(cwd) {
|
|
|
227
244
|
};
|
|
228
245
|
}
|
|
229
246
|
|
|
247
|
+
function getRemindersInfo(cwd) {
|
|
248
|
+
const helpers = loadReminderHelpers(cwd);
|
|
249
|
+
if (!helpers || typeof helpers.listReminderCatalog !== 'function') {
|
|
250
|
+
return { available: false, reminders: [], disabledCount: 0 };
|
|
251
|
+
}
|
|
252
|
+
const catalog = helpers.listReminderCatalog();
|
|
253
|
+
const overrides = readOverrides(cwd);
|
|
254
|
+
const disabledSet = new Set(overrides.disabledReminders || []);
|
|
255
|
+
const reminders = catalog.map((r) => ({
|
|
256
|
+
id: r.id,
|
|
257
|
+
label: r.label,
|
|
258
|
+
description: r.description,
|
|
259
|
+
slot: r.slot,
|
|
260
|
+
disabled: disabledSet.has(r.id),
|
|
261
|
+
}));
|
|
262
|
+
return {
|
|
263
|
+
available: true,
|
|
264
|
+
reminders,
|
|
265
|
+
disabledCount: reminders.filter((r) => r.disabled).length,
|
|
266
|
+
activeCount: reminders.filter((r) => !r.disabled).length,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
230
270
|
function getDoctorInfo(cwd) {
|
|
231
271
|
const checks = [checkCdpPortMismatch(cwd), checkStaleLockfiles(cwd)];
|
|
232
272
|
const issues = checks.filter((c) => c.severity !== 'ok' && c.severity !== 'info');
|
|
@@ -247,6 +287,7 @@ function collectStatus(cwd) {
|
|
|
247
287
|
mcp: getMcpInfo(cwd),
|
|
248
288
|
setupContext: getSetupContextInfo(cwd),
|
|
249
289
|
activeRun: getActiveRunInfo(cwd),
|
|
290
|
+
reminders: getRemindersInfo(cwd),
|
|
250
291
|
doctor: getDoctorInfo(cwd),
|
|
251
292
|
};
|
|
252
293
|
}
|
|
@@ -338,6 +379,12 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
338
379
|
.toggle input { accent-color: var(--accent); }
|
|
339
380
|
.disabled-row { opacity: 0.55; }
|
|
340
381
|
.disabled-row .matcher { font-style: italic; }
|
|
382
|
+
.reminder-row { padding: 10px 0; border-bottom: 1px solid var(--border); }
|
|
383
|
+
.reminder-row:last-child { border-bottom: 0; }
|
|
384
|
+
.reminder-row .reminder-label { font-size: 13px; }
|
|
385
|
+
.reminder-row .reminder-desc { font-size: 12px; margin-left: 22px; margin-top: 2px; }
|
|
386
|
+
.reminder-row .reminder-id { font-size: 11px; margin-left: 22px; margin-top: 2px; }
|
|
387
|
+
.reminder-row code { background: var(--panel-2); padding: 1px 5px; border-radius: 3px; font-size: 11px; }
|
|
341
388
|
.toast { position: fixed; bottom: 18px; right: 18px; background: var(--panel); border: 1px solid var(--border); padding: 10px 14px; border-radius: 6px; font-size: 13px; box-shadow: 0 4px 12px rgba(0,0,0,0.4); }
|
|
342
389
|
.toast.err { border-color: var(--err); }
|
|
343
390
|
.toast.ok { border-color: var(--ok); }
|
|
@@ -363,6 +410,7 @@ const SECTIONS = [
|
|
|
363
410
|
{ id: 'overview', label: 'Overview' },
|
|
364
411
|
{ id: 'claudeMd', label: 'CLAUDE.md' },
|
|
365
412
|
{ id: 'hooks', label: 'Hooks' },
|
|
413
|
+
{ id: 'reminders', label: 'Reminders' },
|
|
366
414
|
{ id: 'mcp', label: 'MCP & observer' },
|
|
367
415
|
{ id: 'setupContext', label: 'Setup context' },
|
|
368
416
|
{ id: 'activeRun', label: 'Active run' },
|
|
@@ -446,6 +494,7 @@ function severityFor(id) {
|
|
|
446
494
|
case 'overview': return s.doctor.issueCount > 0 ? 'warn' : 'ok';
|
|
447
495
|
case 'claudeMd': return !s.claudeMd.exists ? 'err' : (s.claudeMd.optOut ? 'info' : 'ok');
|
|
448
496
|
case 'hooks': return s.hooks.warnings > 0 ? 'warn' : 'ok';
|
|
497
|
+
case 'reminders': return s.reminders && s.reminders.disabledCount > 0 ? 'info' : 'ok';
|
|
449
498
|
case 'mcp': return !s.mcp.configFound ? 'info' : (s.mcp.playwright.match && s.mcp.staleLockfiles.length === 0 ? 'ok' : 'warn');
|
|
450
499
|
case 'setupContext': return !s.setupContext.exists ? 'err' : (s.setupContext.toolingComplete ? 'ok' : 'warn');
|
|
451
500
|
case 'activeRun': return s.activeRun.active ? 'info' : 'info';
|
|
@@ -489,6 +538,7 @@ function renderContent() {
|
|
|
489
538
|
overview: renderOverview,
|
|
490
539
|
claudeMd: renderClaudeMd,
|
|
491
540
|
hooks: renderHooks,
|
|
541
|
+
reminders: renderReminders,
|
|
492
542
|
mcp: renderMcp,
|
|
493
543
|
setupContext: renderSetup,
|
|
494
544
|
activeRun: renderRun,
|
|
@@ -510,6 +560,9 @@ function renderOverview() {
|
|
|
510
560
|
const sc = !s.setupContext.exists ? 'missing' : (s.setupContext.toolingComplete ? 'complete' : 'incomplete (' + s.setupContext.missingTooling.join(', ') + ')');
|
|
511
561
|
const ar = s.activeRun.active ? s.activeRun.runId : 'none';
|
|
512
562
|
const dc = s.doctor.issueCount === 0 ? '0 issues' : s.doctor.issueCount + ' issue(s) — run \`claudeflow doctor\`';
|
|
563
|
+
const rm = s.reminders && s.reminders.available
|
|
564
|
+
? \`\${s.reminders.activeCount} active · \${s.reminders.disabledCount} disabled\`
|
|
565
|
+
: 'unavailable';
|
|
513
566
|
return \`<h2>Overview</h2>
|
|
514
567
|
<p class="sub">Snapshot at \${new Date(s.timestamp).toLocaleTimeString()}</p>
|
|
515
568
|
<div class="card">
|
|
@@ -519,6 +572,7 @@ function renderOverview() {
|
|
|
519
572
|
\${row('MCP & observer', mc, { kind: !s.mcp.configFound ? 'info' : (s.mcp.playwright.match ? 'ok' : 'warn'), text: !s.mcp.configFound ? '·' : (s.mcp.playwright.match ? '✓' : '!') })}
|
|
520
573
|
\${row('Setup context', sc, { kind: s.setupContext.exists ? (s.setupContext.toolingComplete ? 'ok' : 'warn') : 'err', text: s.setupContext.exists ? (s.setupContext.toolingComplete ? '✓' : '!') : '✗' })}
|
|
521
574
|
\${row('Active run', ar, { kind: 'info', text: s.activeRun.active ? '·' : '·' })}
|
|
575
|
+
\${row('Reminders', rm, { kind: s.reminders && s.reminders.available ? (s.reminders.disabledCount ? 'info' : 'ok') : 'err', text: s.reminders && s.reminders.disabledCount ? '·' : '✓' })}
|
|
522
576
|
\${row('Doctor', dc, { kind: s.doctor.issueCount === 0 ? 'ok' : 'warn', text: s.doctor.issueCount === 0 ? '✓' : '!' })}
|
|
523
577
|
</div>\`;
|
|
524
578
|
}
|
|
@@ -564,6 +618,35 @@ function renderHooks() {
|
|
|
564
618
|
\${items}\`;
|
|
565
619
|
}
|
|
566
620
|
|
|
621
|
+
function renderReminders() {
|
|
622
|
+
const r = state.reminders;
|
|
623
|
+
if (!r || !r.available) return '<h2>Reminders</h2><p class="error">Reminder catalog unavailable (template helper not found).</p>';
|
|
624
|
+
const slotLabel = { reads: 'Reads / anchors', rules: 'Rules', 'claude-md': 'CLAUDE.md content' };
|
|
625
|
+
const grouped = {};
|
|
626
|
+
for (const item of r.reminders) {
|
|
627
|
+
if (!grouped[item.slot]) grouped[item.slot] = [];
|
|
628
|
+
grouped[item.slot].push(item);
|
|
629
|
+
}
|
|
630
|
+
const sections = Object.entries(grouped).map(([slot, items]) => {
|
|
631
|
+
const rows = items.map(item => {
|
|
632
|
+
const checked = item.disabled ? '' : 'checked';
|
|
633
|
+
const dataset = \`data-id="\${escapeHtml(item.id)}"\`;
|
|
634
|
+
const status = item.disabled ? '<span class="badge info">disabled</span>' : '';
|
|
635
|
+
const cls = item.disabled ? 'reminder-row disabled-row' : 'reminder-row';
|
|
636
|
+
return \`<div class="\${cls}">
|
|
637
|
+
<label class="toggle"><input type="checkbox" class="reminder-toggle" \${dataset} \${checked} />
|
|
638
|
+
<span class="reminder-label"><strong>\${escapeHtml(item.label)}</strong> \${status}</span></label>
|
|
639
|
+
<div class="reminder-desc muted">\${escapeHtml(item.description)}</div>
|
|
640
|
+
<div class="reminder-id muted">id: <code>\${escapeHtml(item.id)}</code></div>
|
|
641
|
+
</div>\`;
|
|
642
|
+
}).join('');
|
|
643
|
+
return \`<details open><summary>\${escapeHtml(slotLabel[slot] || slot)} <span class="muted">(\${items.length})</span></summary>\${rows}</details>\`;
|
|
644
|
+
}).join('');
|
|
645
|
+
return \`<h2>Reminders</h2>
|
|
646
|
+
<p class="sub">\${r.activeCount} active · \${r.disabledCount} disabled. Toggles are read by <code>reload-reminder.js</code> on every hook invocation — disabling takes effect on the next user prompt without reloading Claude Code.</p>
|
|
647
|
+
\${sections}\`;
|
|
648
|
+
}
|
|
649
|
+
|
|
567
650
|
function renderMcp() {
|
|
568
651
|
const m = state.mcp;
|
|
569
652
|
if (!m.configFound) return '<h2>MCP & observer</h2><p class="muted">.mcp.json not found</p>';
|
|
@@ -644,14 +727,40 @@ async function toggleHook(event, matcher, handler, disable) {
|
|
|
644
727
|
await refresh();
|
|
645
728
|
}
|
|
646
729
|
|
|
730
|
+
async function toggleReminder(id, disable) {
|
|
731
|
+
try {
|
|
732
|
+
const r = await fetch('/api/reminders/toggle', {
|
|
733
|
+
method: 'POST',
|
|
734
|
+
headers: { 'Content-Type': 'application/json' },
|
|
735
|
+
body: JSON.stringify({ id, disable }),
|
|
736
|
+
});
|
|
737
|
+
const result = await r.json();
|
|
738
|
+
if (!r.ok || result.state === 'error') {
|
|
739
|
+
showToast('Toggle failed: ' + (result.reason || result.error || 'unknown'), 'err');
|
|
740
|
+
} else if (result.state === 'noop') {
|
|
741
|
+
showToast('Already in target state', 'ok');
|
|
742
|
+
} else {
|
|
743
|
+
showToast(disable ? 'Reminder silenced. Next prompt will skip it.' : 'Reminder restored. Next prompt will include it.', 'ok');
|
|
744
|
+
}
|
|
745
|
+
} catch (e) {
|
|
746
|
+
showToast('Network error: ' + e.message, 'err');
|
|
747
|
+
}
|
|
748
|
+
await refresh();
|
|
749
|
+
}
|
|
750
|
+
|
|
647
751
|
document.addEventListener('change', (e) => {
|
|
648
752
|
const t = e.target;
|
|
649
|
-
if (t
|
|
753
|
+
if (!t || !t.classList) return;
|
|
754
|
+
if (t.classList.contains('hook-toggle')) {
|
|
650
755
|
const event = t.dataset.event;
|
|
651
756
|
const matcher = t.dataset.matcher;
|
|
652
757
|
const handler = t.dataset.handler;
|
|
653
758
|
const disable = !t.checked;
|
|
654
759
|
toggleHook(event, matcher, handler, disable);
|
|
760
|
+
} else if (t.classList.contains('reminder-toggle')) {
|
|
761
|
+
const id = t.dataset.id;
|
|
762
|
+
const disable = !t.checked;
|
|
763
|
+
toggleReminder(id, disable);
|
|
655
764
|
}
|
|
656
765
|
});
|
|
657
766
|
|
|
@@ -733,6 +842,20 @@ function handler(cwd) {
|
|
|
733
842
|
return send(status, JSON.stringify(result), 'application/json');
|
|
734
843
|
}
|
|
735
844
|
|
|
845
|
+
if (req.method === 'POST' && route === '/api/reminders/toggle') {
|
|
846
|
+
const raw = await readRequestBody(req);
|
|
847
|
+
let payload;
|
|
848
|
+
try { payload = JSON.parse(raw); }
|
|
849
|
+
catch { return send(400, JSON.stringify({ error: 'invalid json' }), 'application/json'); }
|
|
850
|
+
const { id, disable } = payload;
|
|
851
|
+
if (typeof id !== 'string' || typeof disable !== 'boolean') {
|
|
852
|
+
return send(400, JSON.stringify({ error: 'id, disable required' }), 'application/json');
|
|
853
|
+
}
|
|
854
|
+
const result = toggleReminderOverride(cwd, { id, disable });
|
|
855
|
+
const status = result.state === 'error' ? 500 : 200;
|
|
856
|
+
return send(status, JSON.stringify(result), 'application/json');
|
|
857
|
+
}
|
|
858
|
+
|
|
736
859
|
send(404, JSON.stringify({ error: 'not found' }), 'application/json');
|
|
737
860
|
} catch (err) {
|
|
738
861
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.24",
|
|
4
4
|
"description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claudeflow": "./bin/cli.js"
|