@axiomatic-labs/claudeflow 2.13.24 → 2.13.25
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 +18 -1
- package/lib/panel.js +129 -4
- 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: [], disabledReminders: [] };
|
|
54
|
+
return { version: 1, disabledHandlers: [], disabledReminders: [], enforcementDisabled: false };
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function readOverrides(projectRoot) {
|
|
@@ -61,6 +61,7 @@ function readOverrides(projectRoot) {
|
|
|
61
61
|
version: data.version || 1,
|
|
62
62
|
disabledHandlers: Array.isArray(data.disabledHandlers) ? data.disabledHandlers : [],
|
|
63
63
|
disabledReminders: Array.isArray(data.disabledReminders) ? data.disabledReminders : [],
|
|
64
|
+
enforcementDisabled: data.enforcementDisabled === true,
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -173,6 +174,21 @@ function toggleHookOverride(projectRoot, { event, matcher = '', handler, disable
|
|
|
173
174
|
return { state: 'enabled' };
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
// Master enforcement switch. When OFF, the wrapper silences every hook
|
|
178
|
+
// listed in `.claude/hooks/shared/enforcement-catalog.js` regardless of
|
|
179
|
+
// per-handler manual toggles. Per-handler toggles outside the catalog
|
|
180
|
+
// remain in effect — they are independent layers.
|
|
181
|
+
function toggleEnforcementMode(projectRoot, { disable }) {
|
|
182
|
+
if (typeof disable !== 'boolean') return { state: 'error', reason: 'disable-required' };
|
|
183
|
+
const overrides = readOverrides(projectRoot);
|
|
184
|
+
if (overrides.enforcementDisabled === disable) {
|
|
185
|
+
return { state: 'noop', reason: disable ? 'already-off' : 'already-on' };
|
|
186
|
+
}
|
|
187
|
+
overrides.enforcementDisabled = disable;
|
|
188
|
+
writeOverrides(projectRoot, overrides);
|
|
189
|
+
return { state: disable ? 'off' : 'on' };
|
|
190
|
+
}
|
|
191
|
+
|
|
176
192
|
// Toggle a single reminder by id. Same persistence file, same hot-reload
|
|
177
193
|
// semantics — `reload-reminder.js` re-reads overrides on every invocation.
|
|
178
194
|
function toggleReminderOverride(projectRoot, { id, disable }) {
|
|
@@ -202,6 +218,7 @@ module.exports = {
|
|
|
202
218
|
writeOverrides,
|
|
203
219
|
toggleHookOverride,
|
|
204
220
|
toggleReminderOverride,
|
|
221
|
+
toggleEnforcementMode,
|
|
205
222
|
normalizeHandlerPath,
|
|
206
223
|
matchesOverride,
|
|
207
224
|
};
|
package/lib/panel.js
CHANGED
|
@@ -25,9 +25,21 @@ const {
|
|
|
25
25
|
readOverrides,
|
|
26
26
|
toggleHookOverride,
|
|
27
27
|
toggleReminderOverride,
|
|
28
|
+
toggleEnforcementMode,
|
|
28
29
|
normalizeHandlerPath,
|
|
29
30
|
} = require('./hook-overrides.js');
|
|
30
31
|
|
|
32
|
+
function loadEnforcementCatalog(cwd) {
|
|
33
|
+
const p = path.join(cwd, '.claude', 'hooks', 'shared', 'enforcement-catalog.js');
|
|
34
|
+
if (!fs.existsSync(p)) return null;
|
|
35
|
+
try {
|
|
36
|
+
delete require.cache[require.resolve(p)];
|
|
37
|
+
return require(p);
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
// Lazy-load the reminder catalog from the template hook helper. The path
|
|
32
44
|
// resolves the same way at install time and from the template repo.
|
|
33
45
|
function loadReminderHelpers(cwd) {
|
|
@@ -112,6 +124,11 @@ function getHooksInfo(cwd) {
|
|
|
112
124
|
for (const d of overrides.disabledHandlers || []) {
|
|
113
125
|
disabledIndex.set(`${d.event}|${d.matcher || ''}|${d.handler}`, d);
|
|
114
126
|
}
|
|
127
|
+
const catalog = loadEnforcementCatalog(cwd);
|
|
128
|
+
const isEnforcement = catalog && typeof catalog.isEnforcementHook === 'function'
|
|
129
|
+
? catalog.isEnforcementHook
|
|
130
|
+
: () => false;
|
|
131
|
+
const enforcementDisabled = overrides.enforcementDisabled === true;
|
|
115
132
|
|
|
116
133
|
// settings.json lists every hook (with each command wrapped through
|
|
117
134
|
// run-with-override.js). The wrapper checks overrides at invocation time,
|
|
@@ -133,14 +150,21 @@ function getHooksInfo(cwd) {
|
|
|
133
150
|
const isEmpty = !handlerPath;
|
|
134
151
|
if (isEmpty) warnings++;
|
|
135
152
|
const key = `${event}|${matcher}|${handlerPath}`;
|
|
136
|
-
const
|
|
153
|
+
const enforcement = isEnforcement(event, matcher, handlerPath);
|
|
154
|
+
const masterDisabled = enforcement && enforcementDisabled;
|
|
155
|
+
const manualDisabled = disabledIndex.has(key);
|
|
156
|
+
const isDisabled = manualDisabled || masterDisabled;
|
|
137
157
|
if (isDisabled) disabledCount++;
|
|
138
158
|
handlers.push({
|
|
139
159
|
handler: handlerPath || '(empty command)',
|
|
140
160
|
matcher,
|
|
141
161
|
matcherDisplay: matcher || '(any)',
|
|
142
162
|
empty: isEmpty,
|
|
163
|
+
enforcement,
|
|
143
164
|
disabled: isDisabled,
|
|
165
|
+
disabledBy: masterDisabled && !manualDisabled ? 'enforcement-master'
|
|
166
|
+
: manualDisabled ? 'manual'
|
|
167
|
+
: null,
|
|
144
168
|
rawCommand: cmd,
|
|
145
169
|
});
|
|
146
170
|
totalHandlers++;
|
|
@@ -244,6 +268,24 @@ function getActiveRunInfo(cwd) {
|
|
|
244
268
|
};
|
|
245
269
|
}
|
|
246
270
|
|
|
271
|
+
function getEnforcementInfo(cwd) {
|
|
272
|
+
const catalog = loadEnforcementCatalog(cwd);
|
|
273
|
+
const overrides = readOverrides(cwd);
|
|
274
|
+
if (!catalog || !Array.isArray(catalog.ENFORCEMENT_HOOKS)) {
|
|
275
|
+
return { available: false, on: !overrides.enforcementDisabled, count: 0 };
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
available: true,
|
|
279
|
+
on: !overrides.enforcementDisabled,
|
|
280
|
+
count: catalog.ENFORCEMENT_HOOKS.length,
|
|
281
|
+
hooks: catalog.ENFORCEMENT_HOOKS.map((h) => ({
|
|
282
|
+
event: h.event,
|
|
283
|
+
handler: h.handler,
|
|
284
|
+
matcher: h.matcher,
|
|
285
|
+
})),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
247
289
|
function getRemindersInfo(cwd) {
|
|
248
290
|
const helpers = loadReminderHelpers(cwd);
|
|
249
291
|
if (!helpers || typeof helpers.listReminderCatalog !== 'function') {
|
|
@@ -288,6 +330,7 @@ function collectStatus(cwd) {
|
|
|
288
330
|
setupContext: getSetupContextInfo(cwd),
|
|
289
331
|
activeRun: getActiveRunInfo(cwd),
|
|
290
332
|
reminders: getRemindersInfo(cwd),
|
|
333
|
+
enforcement: getEnforcementInfo(cwd),
|
|
291
334
|
doctor: getDoctorInfo(cwd),
|
|
292
335
|
};
|
|
293
336
|
}
|
|
@@ -326,6 +369,19 @@ header .cwd { color: var(--muted); font-family: var(--mono); font-size: 12px; fl
|
|
|
326
369
|
header .actions { display: flex; gap: 12px; align-items: center; color: var(--muted); font-size: 12px; }
|
|
327
370
|
header button { background: var(--panel-2); border: 1px solid var(--border); color: var(--fg); padding: 6px 12px; border-radius: 6px; cursor: pointer; font: inherit; }
|
|
328
371
|
header button:hover { border-color: var(--accent); }
|
|
372
|
+
.enforce-pill { display: inline-flex; align-items: center; gap: 8px; padding: 5px 12px; border-radius: 999px; border: 1px solid var(--border); background: var(--panel-2); font-size: 12px; transition: all 0.2s; }
|
|
373
|
+
.enforce-pill .enforce-label { color: var(--muted); font-family: var(--mono); letter-spacing: 0.5px; text-transform: uppercase; font-size: 11px; }
|
|
374
|
+
.enforce-pill .enforce-state { font-family: var(--mono); font-weight: 600; font-size: 12px; min-width: 26px; text-align: left; }
|
|
375
|
+
.enforce-pill[data-state="on"] { border-color: rgba(63, 185, 80, 0.5); background: rgba(63, 185, 80, 0.08); }
|
|
376
|
+
.enforce-pill[data-state="on"] .enforce-state { color: var(--ok); }
|
|
377
|
+
.enforce-pill[data-state="off"] { border-color: rgba(248, 81, 73, 0.6); background: rgba(248, 81, 73, 0.12); box-shadow: 0 0 0 1px rgba(248, 81, 73, 0.3); }
|
|
378
|
+
.enforce-pill[data-state="off"] .enforce-state { color: var(--err); }
|
|
379
|
+
.enforce-switch { position: relative; display: inline-block; width: 32px; height: 18px; }
|
|
380
|
+
.enforce-switch input { opacity: 0; width: 0; height: 0; }
|
|
381
|
+
.enforce-slider { position: absolute; cursor: pointer; inset: 0; background: var(--border); border-radius: 999px; transition: background 0.2s; }
|
|
382
|
+
.enforce-slider::before { content: ""; position: absolute; height: 14px; width: 14px; left: 2px; top: 2px; background: var(--fg); border-radius: 50%; transition: transform 0.2s; }
|
|
383
|
+
.enforce-switch input:checked + .enforce-slider { background: var(--ok); }
|
|
384
|
+
.enforce-switch input:checked + .enforce-slider::before { transform: translateX(14px); }
|
|
329
385
|
main { display: flex; min-height: calc(100vh - 56px); }
|
|
330
386
|
nav { width: 220px; flex-shrink: 0; border-right: 1px solid var(--border); background: var(--panel); padding: 12px 0; }
|
|
331
387
|
nav a { display: flex; align-items: center; gap: 10px; padding: 10px 22px; color: var(--fg); text-decoration: none; cursor: pointer; border-left: 3px solid transparent; font-size: 13px; }
|
|
@@ -397,6 +453,11 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
397
453
|
<span class="version" id="version">…</span>
|
|
398
454
|
<span class="cwd" id="cwd">…</span>
|
|
399
455
|
<span class="actions">
|
|
456
|
+
<span class="enforce-pill" id="enforce-pill" title="Master switch — toggles every blocking enforcement hook (freeze-step-contract, enforce-task-creation, stack-debt-guard, ecosystem-artifacts, workflow-enforcement). Validators and reminders are unaffected.">
|
|
457
|
+
<span class="enforce-label">Enforcement</span>
|
|
458
|
+
<label class="enforce-switch"><input type="checkbox" id="enforce-toggle" /><span class="enforce-slider"></span></label>
|
|
459
|
+
<span class="enforce-state" id="enforce-state">…</span>
|
|
460
|
+
</span>
|
|
400
461
|
<label class="toggle"><input type="checkbox" id="auto" checked /> auto-refresh 5s</label>
|
|
401
462
|
<button id="refresh">Refresh</button>
|
|
402
463
|
</span>
|
|
@@ -486,6 +547,21 @@ async function refresh() {
|
|
|
486
547
|
function renderHeader() {
|
|
487
548
|
document.getElementById('version').textContent = 'v' + state.version.installed;
|
|
488
549
|
document.getElementById('cwd').textContent = state.cwd;
|
|
550
|
+
const ef = state.enforcement;
|
|
551
|
+
const pill = document.getElementById('enforce-pill');
|
|
552
|
+
const cb = document.getElementById('enforce-toggle');
|
|
553
|
+
const lbl = document.getElementById('enforce-state');
|
|
554
|
+
if (ef && ef.available) {
|
|
555
|
+
pill.style.display = '';
|
|
556
|
+
pill.dataset.state = ef.on ? 'on' : 'off';
|
|
557
|
+
cb.checked = ef.on;
|
|
558
|
+
lbl.textContent = ef.on ? 'ON' : 'OFF';
|
|
559
|
+
pill.title = ef.on
|
|
560
|
+
? 'Enforcement is ON — ' + ef.count + ' blocking hook(s) active. Click to disable all.'
|
|
561
|
+
: 'Enforcement is OFF — ' + ef.count + ' blocking hook(s) silenced. Reminders + validators still run.';
|
|
562
|
+
} else {
|
|
563
|
+
pill.style.display = 'none';
|
|
564
|
+
}
|
|
489
565
|
}
|
|
490
566
|
|
|
491
567
|
function severityFor(id) {
|
|
@@ -563,6 +639,9 @@ function renderOverview() {
|
|
|
563
639
|
const rm = s.reminders && s.reminders.available
|
|
564
640
|
? \`\${s.reminders.activeCount} active · \${s.reminders.disabledCount} disabled\`
|
|
565
641
|
: 'unavailable';
|
|
642
|
+
const ef = s.enforcement && s.enforcement.available
|
|
643
|
+
? (s.enforcement.on ? \`ON (\${s.enforcement.count} blocking hooks)\` : \`OFF — \${s.enforcement.count} blocking hooks silenced\`)
|
|
644
|
+
: 'unavailable';
|
|
566
645
|
return \`<h2>Overview</h2>
|
|
567
646
|
<p class="sub">Snapshot at \${new Date(s.timestamp).toLocaleTimeString()}</p>
|
|
568
647
|
<div class="card">
|
|
@@ -573,6 +652,7 @@ function renderOverview() {
|
|
|
573
652
|
\${row('Setup context', sc, { kind: s.setupContext.exists ? (s.setupContext.toolingComplete ? 'ok' : 'warn') : 'err', text: s.setupContext.exists ? (s.setupContext.toolingComplete ? '✓' : '!') : '✗' })}
|
|
574
653
|
\${row('Active run', ar, { kind: 'info', text: s.activeRun.active ? '·' : '·' })}
|
|
575
654
|
\${row('Reminders', rm, { kind: s.reminders && s.reminders.available ? (s.reminders.disabledCount ? 'info' : 'ok') : 'err', text: s.reminders && s.reminders.disabledCount ? '·' : '✓' })}
|
|
655
|
+
\${row('Enforcement', ef, { kind: s.enforcement && s.enforcement.available ? (s.enforcement.on ? 'ok' : 'err') : 'err', text: s.enforcement && s.enforcement.available ? (s.enforcement.on ? '✓' : '✗') : '·' })}
|
|
576
656
|
\${row('Doctor', dc, { kind: s.doctor.issueCount === 0 ? 'ok' : 'warn', text: s.doctor.issueCount === 0 ? '✓' : '!' })}
|
|
577
657
|
</div>\`;
|
|
578
658
|
}
|
|
@@ -603,10 +683,13 @@ function renderHooks() {
|
|
|
603
683
|
const cls = hd.disabled ? 'handler disabled-row' : 'handler';
|
|
604
684
|
const checked = hd.disabled ? '' : 'checked';
|
|
605
685
|
const dataset = \`data-event="\${escapeHtml(ev.event)}" data-matcher="\${escapeHtml(hd.matcher)}" data-handler="\${escapeHtml(hd.handler)}"\`;
|
|
606
|
-
const
|
|
686
|
+
const enforcementBadge = hd.enforcement ? '<span class="badge warn" title="Member of the enforcement catalog — affected by the master switch">enforcement</span>' : '';
|
|
687
|
+
const masterBadge = hd.disabledBy === 'enforcement-master' ? '<span class="badge err" title="Silenced by the master Enforcement OFF switch in the header">master OFF</span>' : '';
|
|
688
|
+
const manualBadge = hd.disabledBy === 'manual' ? '<span class="badge info">disabled</span>' : '';
|
|
689
|
+
const lockedByMaster = hd.disabledBy === 'enforcement-master';
|
|
607
690
|
return \`<div class="\${cls}">
|
|
608
|
-
<label class="toggle"><input type="checkbox" class="hook-toggle" \${dataset} \${checked} \${hd.empty ? 'disabled' : ''} />
|
|
609
|
-
<span>\${escapeHtml(hd.handler)} \${
|
|
691
|
+
<label class="toggle"><input type="checkbox" class="hook-toggle" \${dataset} \${checked} \${hd.empty || lockedByMaster ? 'disabled' : ''} />
|
|
692
|
+
<span>\${escapeHtml(hd.handler)} \${enforcementBadge} \${masterBadge} \${manualBadge} \${mark}</span></label>
|
|
610
693
|
<span class="matcher">\${escapeHtml(hd.matcherDisplay)}</span>
|
|
611
694
|
</div>\`;
|
|
612
695
|
}).join('');
|
|
@@ -773,6 +856,34 @@ document.addEventListener('toggle', (e) => {
|
|
|
773
856
|
}
|
|
774
857
|
}, true);
|
|
775
858
|
|
|
859
|
+
async function toggleEnforcement(disable) {
|
|
860
|
+
try {
|
|
861
|
+
const r = await fetch('/api/enforcement/toggle', {
|
|
862
|
+
method: 'POST',
|
|
863
|
+
headers: { 'Content-Type': 'application/json' },
|
|
864
|
+
body: JSON.stringify({ disable }),
|
|
865
|
+
});
|
|
866
|
+
const result = await r.json();
|
|
867
|
+
if (!r.ok || result.state === 'error') {
|
|
868
|
+
showToast('Toggle failed: ' + (result.reason || result.error || 'unknown'), 'err');
|
|
869
|
+
} else if (result.state === 'noop') {
|
|
870
|
+
showToast('Enforcement already in target state', 'ok');
|
|
871
|
+
} else {
|
|
872
|
+
showToast(disable
|
|
873
|
+
? 'Enforcement OFF — blocking hooks silenced. Validators + reminders still run.'
|
|
874
|
+
: 'Enforcement ON — full blocking restored.',
|
|
875
|
+
disable ? 'err' : 'ok');
|
|
876
|
+
}
|
|
877
|
+
} catch (e) {
|
|
878
|
+
showToast('Network error: ' + e.message, 'err');
|
|
879
|
+
}
|
|
880
|
+
await refresh();
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
document.getElementById('enforce-toggle').onchange = (e) => {
|
|
884
|
+
toggleEnforcement(!e.target.checked);
|
|
885
|
+
};
|
|
886
|
+
|
|
776
887
|
document.getElementById('refresh').onclick = refresh;
|
|
777
888
|
document.getElementById('auto').onchange = (e) => {
|
|
778
889
|
if (timer) clearInterval(timer);
|
|
@@ -842,6 +953,20 @@ function handler(cwd) {
|
|
|
842
953
|
return send(status, JSON.stringify(result), 'application/json');
|
|
843
954
|
}
|
|
844
955
|
|
|
956
|
+
if (req.method === 'POST' && route === '/api/enforcement/toggle') {
|
|
957
|
+
const raw = await readRequestBody(req);
|
|
958
|
+
let payload;
|
|
959
|
+
try { payload = JSON.parse(raw); }
|
|
960
|
+
catch { return send(400, JSON.stringify({ error: 'invalid json' }), 'application/json'); }
|
|
961
|
+
const { disable } = payload;
|
|
962
|
+
if (typeof disable !== 'boolean') {
|
|
963
|
+
return send(400, JSON.stringify({ error: 'disable required' }), 'application/json');
|
|
964
|
+
}
|
|
965
|
+
const result = toggleEnforcementMode(cwd, { disable });
|
|
966
|
+
const status = result.state === 'error' ? 500 : 200;
|
|
967
|
+
return send(status, JSON.stringify(result), 'application/json');
|
|
968
|
+
}
|
|
969
|
+
|
|
845
970
|
if (req.method === 'POST' && route === '/api/reminders/toggle') {
|
|
846
971
|
const raw = await readRequestBody(req);
|
|
847
972
|
let payload;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.25",
|
|
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"
|