@axiomatic-labs/claudeflow 2.13.65 → 2.13.66

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.
@@ -57,6 +57,7 @@ function defaultOverrides() {
57
57
  disabledReminders: [],
58
58
  enforcementDisabled: false,
59
59
  runToCompletionReminderEnabled: false,
60
+ cwdLockEnabled: false,
60
61
  };
61
62
  }
62
63
 
@@ -69,6 +70,7 @@ function readOverrides(projectRoot) {
69
70
  disabledReminders: Array.isArray(data.disabledReminders) ? data.disabledReminders : [],
70
71
  enforcementDisabled: data.enforcementDisabled === true,
71
72
  runToCompletionReminderEnabled: data.runToCompletionReminderEnabled === true,
73
+ cwdLockEnabled: data.cwdLockEnabled === true,
72
74
  };
73
75
  }
74
76
 
@@ -209,6 +211,20 @@ function toggleRunToCompletionReminder(projectRoot, { enable }) {
209
211
  return { state: enable ? 'on' : 'off' };
210
212
  }
211
213
 
214
+ // Toggle the cwd-lock hook. Independent of the master enforcement switch —
215
+ // the CwdChanged hook is not in the enforcement-catalog and ignores the
216
+ // master switch by design (you can lock cwd even with enforcement off).
217
+ function toggleCwdLock(projectRoot, { enable }) {
218
+ if (typeof enable !== 'boolean') return { state: 'error', reason: 'enable-required' };
219
+ const overrides = readOverrides(projectRoot);
220
+ if (overrides.cwdLockEnabled === enable) {
221
+ return { state: 'noop', reason: enable ? 'already-on' : 'already-off' };
222
+ }
223
+ overrides.cwdLockEnabled = enable;
224
+ writeOverrides(projectRoot, overrides);
225
+ return { state: enable ? 'on' : 'off' };
226
+ }
227
+
212
228
  // Toggle a single reminder by id. Same persistence file, same hot-reload
213
229
  // semantics — `reload-reminder.js` re-reads overrides on every invocation.
214
230
  function toggleReminderOverride(projectRoot, { id, disable }) {
@@ -240,6 +256,7 @@ module.exports = {
240
256
  toggleReminderOverride,
241
257
  toggleEnforcementMode,
242
258
  toggleRunToCompletionReminder,
259
+ toggleCwdLock,
243
260
  normalizeHandlerPath,
244
261
  matchesOverride,
245
262
  };
package/lib/panel.js CHANGED
@@ -30,6 +30,7 @@ const {
30
30
  toggleReminderOverride,
31
31
  toggleEnforcementMode,
32
32
  toggleRunToCompletionReminder,
33
+ toggleCwdLock,
33
34
  normalizeHandlerPath,
34
35
  } = require('./hook-overrides.js');
35
36
 
@@ -363,6 +364,14 @@ function getRunToCompletionInfo(cwd) {
363
364
  };
364
365
  }
365
366
 
367
+ function getCwdLockInfo(cwd) {
368
+ const overrides = readOverrides(cwd);
369
+ return {
370
+ on: overrides.cwdLockEnabled === true,
371
+ handler: 'CwdChanged/lock-cwd.js',
372
+ };
373
+ }
374
+
366
375
  function getActivePlanInfo(cwd) {
367
376
  const p = path.join(cwd, '.claudeflow', 'state', 'active-plan.json');
368
377
  let raw;
@@ -618,6 +627,7 @@ function collectStatus(cwd) {
618
627
  reminders: getRemindersInfo(cwd),
619
628
  enforcement: getEnforcementInfo(cwd),
620
629
  runToCompletion: getRunToCompletionInfo(cwd),
630
+ cwdLock: getCwdLockInfo(cwd),
621
631
  observer: getObserverInfo(cwd),
622
632
  activePlan: getActivePlanInfo(cwd),
623
633
  logs: getLogsInfo(cwd),
@@ -800,6 +810,11 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
800
810
  <label class="enforce-switch"><input type="checkbox" id="run-to-completion-toggle" /><span class="enforce-slider"></span></label>
801
811
  <span class="enforce-state" id="run-to-completion-state">…</span>
802
812
  </span>
813
+ <span class="enforce-pill" id="cwd-lock-pill" title="Hard block — fires on Claude Code CwdChanged event. When the agent runs cd Subdir + cmd and the cwd would persist away from the project root, the change is blocked with a guidance message. Use (cd Subdir + cmd) subshells or absolute paths instead.">
814
+ <span class="enforce-label">Lock cwd</span>
815
+ <label class="enforce-switch"><input type="checkbox" id="cwd-lock-toggle" /><span class="enforce-slider"></span></label>
816
+ <span class="enforce-state" id="cwd-lock-state">…</span>
817
+ </span>
803
818
  <label class="toggle"><input type="checkbox" id="auto" checked /> auto-refresh 5s</label>
804
819
  <button id="refresh">Refresh</button>
805
820
  </span>
@@ -936,6 +951,20 @@ function renderHeader() {
936
951
  } else if (rtcPill) {
937
952
  rtcPill.style.display = 'none';
938
953
  }
954
+ const lock = state.cwdLock;
955
+ const lockPill = document.getElementById('cwd-lock-pill');
956
+ const lockCb = document.getElementById('cwd-lock-toggle');
957
+ const lockLbl = document.getElementById('cwd-lock-state');
958
+ if (lock && lockPill) {
959
+ lockPill.dataset.state = lock.on ? 'on' : 'off';
960
+ lockCb.checked = lock.on;
961
+ lockLbl.textContent = lock.on ? 'ON' : 'OFF';
962
+ lockPill.title = lock.on
963
+ ? 'Lock cwd is ON — any persistent change of working directory away from the project root is blocked via the CwdChanged hook. Use (cd Subdir + cmd) subshells or absolute paths.'
964
+ : 'Lock cwd is OFF — the agent can cd into subdirectories and the cwd will persist across tool calls. Click to enforce a single project-root cwd.';
965
+ } else if (lockPill) {
966
+ lockPill.style.display = 'none';
967
+ }
939
968
  }
940
969
 
941
970
  function severityFor(id) {
@@ -1872,6 +1901,34 @@ document.getElementById('run-to-completion-toggle').onchange = (e) => {
1872
1901
  toggleRunToCompletion(e.target.checked);
1873
1902
  };
1874
1903
 
1904
+ async function toggleCwdLockUI(enable) {
1905
+ try {
1906
+ const r = await fetch('/api/cwd-lock/toggle', {
1907
+ method: 'POST',
1908
+ headers: { 'Content-Type': 'application/json' },
1909
+ body: JSON.stringify({ enable }),
1910
+ });
1911
+ const result = await r.json();
1912
+ if (!r.ok || result.state === 'error') {
1913
+ showToast('Toggle failed: ' + (result.reason || result.error || 'unknown'), 'err');
1914
+ } else if (result.state === 'noop') {
1915
+ showToast('Cwd lock already in target state', 'ok');
1916
+ } else {
1917
+ showToast(enable
1918
+ ? 'Cwd lock ON — cd away from project root will be BLOCKED on next CwdChanged.'
1919
+ : 'Cwd lock OFF — agent can freely change directory.',
1920
+ 'ok');
1921
+ }
1922
+ } catch (e) {
1923
+ showToast('Network error: ' + e.message, 'err');
1924
+ }
1925
+ await refresh();
1926
+ }
1927
+
1928
+ document.getElementById('cwd-lock-toggle').onchange = (e) => {
1929
+ toggleCwdLockUI(e.target.checked);
1930
+ };
1931
+
1875
1932
  document.getElementById('refresh').onclick = refresh;
1876
1933
  document.getElementById('auto').onchange = (e) => {
1877
1934
  if (timer) clearInterval(timer);
@@ -1981,6 +2038,20 @@ function handler(cwd) {
1981
2038
  return send(status, JSON.stringify(result), 'application/json');
1982
2039
  }
1983
2040
 
2041
+ if (req.method === 'POST' && route === '/api/cwd-lock/toggle') {
2042
+ const raw = await readRequestBody(req);
2043
+ let payload;
2044
+ try { payload = JSON.parse(raw); }
2045
+ catch { return send(400, JSON.stringify({ error: 'invalid json' }), 'application/json'); }
2046
+ const { enable } = payload;
2047
+ if (typeof enable !== 'boolean') {
2048
+ return send(400, JSON.stringify({ error: 'enable required' }), 'application/json');
2049
+ }
2050
+ const result = toggleCwdLock(cwd, { enable });
2051
+ const status = result.state === 'error' ? 500 : 200;
2052
+ return send(status, JSON.stringify(result), 'application/json');
2053
+ }
2054
+
1984
2055
  if (req.method === 'POST' && route === '/api/run-to-completion/toggle') {
1985
2056
  const raw = await readRequestBody(req);
1986
2057
  let payload;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiomatic-labs/claudeflow",
3
- "version": "2.13.65",
3
+ "version": "2.13.66",
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"