@regression-io/claude-config 0.36.18 → 0.37.3

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/config-loader.js CHANGED
@@ -28,7 +28,7 @@ const { init, show } = require('./lib/init');
28
28
  const { memoryList, memoryInit, memoryAdd, memorySearch } = require('./lib/memory');
29
29
  const { envList, envSet, envUnset } = require('./lib/env');
30
30
  const { getProjectsRegistryPath, loadProjectsRegistry, saveProjectsRegistry, projectList, projectAdd, projectRemove } = require('./lib/projects');
31
- const { getWorkstreamsPath, loadWorkstreams, saveWorkstreams, workstreamList, workstreamCreate, workstreamUpdate, workstreamDelete, workstreamUse, workstreamActive, workstreamAddProject, workstreamRemoveProject, workstreamInject, workstreamDetect, workstreamGet } = require('./lib/workstreams');
31
+ const { getWorkstreamsPath, loadWorkstreams, saveWorkstreams, workstreamList, workstreamCreate, workstreamUpdate, workstreamDelete, workstreamUse, workstreamActive, workstreamAddProject, workstreamRemoveProject, workstreamInject, workstreamDetect, workstreamGet, getActiveWorkstream, countWorkstreamsForProject, workstreamInstallHook, workstreamDeactivate } = require('./lib/workstreams');
32
32
  const { getActivityPath, getDefaultActivity, loadActivity, saveActivity, detectProjectRoot, activityLog, activitySummary, generateWorkstreamName, activitySuggestWorkstreams, activityClear } = require('./lib/activity');
33
33
  const { getSmartSyncPath, loadSmartSyncPrefs, saveSmartSyncPrefs, smartSyncRememberChoice, smartSyncDismissNudge, smartSyncUpdateSettings, smartSyncDetect, smartSyncCheckNudge, smartSyncHandleAction, smartSyncStatus } = require('./lib/smart-sync');
34
34
  const { runCli } = require('./lib/cli');
@@ -128,6 +128,10 @@ class ClaudeConfigManager {
128
128
  workstreamInject(silent) { return workstreamInject(this.installDir, silent); }
129
129
  workstreamDetect(dir) { return workstreamDetect(this.installDir, dir); }
130
130
  workstreamGet(id) { return workstreamGet(this.installDir, id); }
131
+ getActiveWorkstream() { return getActiveWorkstream(this.installDir); }
132
+ countWorkstreamsForProject(projectPath) { return countWorkstreamsForProject(this.installDir, projectPath); }
133
+ workstreamInstallHook() { return workstreamInstallHook(); }
134
+ workstreamDeactivate() { return workstreamDeactivate(); }
131
135
 
132
136
  // Activity
133
137
  getActivityPath() { return getActivityPath(this.installDir); }
package/lib/cli.js CHANGED
@@ -112,12 +112,16 @@ function runCli(manager) {
112
112
  const ws = manager.workstreamActive();
113
113
  if (ws) {
114
114
  console.log(`Active: ${ws.name}`);
115
- if (ws.projects.length > 0) {
115
+ if (ws.projects && ws.projects.length > 0) {
116
116
  console.log(`Projects: ${ws.projects.map(p => path.basename(p)).join(', ')}`);
117
117
  }
118
118
  } else {
119
119
  console.log('No active workstream');
120
120
  }
121
+ } else if (args[1] === 'install-hook') {
122
+ manager.workstreamInstallHook();
123
+ } else if (args[1] === 'deactivate') {
124
+ manager.workstreamDeactivate();
121
125
  } else {
122
126
  manager.workstreamList();
123
127
  }
@@ -189,12 +193,17 @@ Workstream Commands:
189
193
  workstream List all workstreams
190
194
  workstream create "Name" Create new workstream
191
195
  workstream delete <name> Delete workstream
192
- workstream use <name> Set active workstream
196
+ workstream use <name> Set active workstream (global)
193
197
  workstream active Show current active workstream
198
+ workstream deactivate Show how to deactivate workstream
194
199
  workstream add <ws> <path> Add project to workstream
195
200
  workstream remove <ws> <path> Remove project from workstream
196
- workstream inject [--silent] Output active workstream rules (for hooks)
201
+ workstream inject [--silent] Output restriction + context (for hooks)
197
202
  workstream detect [path] Detect workstream for directory
203
+ workstream install-hook Install pre-prompt hook for injection
204
+
205
+ Per-session activation (enables parallel work):
206
+ export CLAUDE_WORKSTREAM=<name-or-id>
198
207
 
199
208
  Registry Commands:
200
209
  registry List MCPs in global registry
package/lib/constants.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Constants and tool path configurations
3
3
  */
4
4
 
5
- const VERSION = '0.36.17';
5
+ const VERSION = '0.37.2';
6
6
 
7
7
  // Tool-specific path configurations
8
8
  const TOOL_PATHS = {
@@ -188,11 +188,10 @@ function workstreamUse(installDir, idOrName) {
188
188
  }
189
189
 
190
190
  /**
191
- * Get active workstream
191
+ * Get active workstream (uses env var or file-based activeId)
192
192
  */
193
193
  function workstreamActive(installDir) {
194
- const data = loadWorkstreams(installDir);
195
- return data.workstreams.find(w => w.id === data.activeId) || null;
194
+ return getActiveWorkstream(installDir);
196
195
  }
197
196
 
198
197
  /**
@@ -253,23 +252,81 @@ function workstreamRemoveProject(installDir, idOrName, projectPath) {
253
252
  }
254
253
 
255
254
  /**
256
- * Inject active workstream rules into Claude context
255
+ * Get active workstream - checks env var first, then falls back to file
256
+ */
257
+ function getActiveWorkstream(installDir) {
258
+ const data = loadWorkstreams(installDir);
259
+
260
+ // Check env var first (per-session activation)
261
+ const envWorkstream = process.env.CLAUDE_WORKSTREAM;
262
+ if (envWorkstream) {
263
+ const ws = data.workstreams.find(
264
+ w => w.id === envWorkstream || w.name.toLowerCase() === envWorkstream.toLowerCase()
265
+ );
266
+ if (ws) return ws;
267
+ }
268
+
269
+ // Fall back to file-based activeId
270
+ return data.workstreams.find(w => w.id === data.activeId) || null;
271
+ }
272
+
273
+ /**
274
+ * Inject active workstream context into Claude - includes restriction and context
257
275
  */
258
276
  function workstreamInject(installDir, silent = false) {
259
- const active = workstreamActive(installDir);
277
+ const active = getActiveWorkstream(installDir);
260
278
 
261
279
  if (!active) {
262
280
  if (!silent) console.log('No active workstream');
263
281
  return null;
264
282
  }
265
283
 
266
- if (!active.rules || active.rules.trim() === '') {
267
- if (!silent) console.log(`Workstream "${active.name}" has no rules defined`);
268
- return null;
284
+ // Build the injection output
285
+ const lines = [];
286
+
287
+ // Header
288
+ lines.push(`## Active Workstream: ${active.name}`);
289
+ lines.push('');
290
+
291
+ // Restriction section (always include if there are projects)
292
+ if (active.projects && active.projects.length > 0) {
293
+ lines.push('### Restriction');
294
+ lines.push('');
295
+ lines.push('You are working within a scoped workstream. You may ONLY access files within these directories:');
296
+ lines.push('');
297
+ for (const p of active.projects) {
298
+ const displayPath = p.replace(process.env.HOME || '', '~');
299
+ lines.push(`- ${displayPath}`);
300
+ }
301
+ lines.push('');
302
+ lines.push('**Do NOT read, write, search, or reference files outside these directories.**');
303
+ lines.push('');
269
304
  }
270
305
 
271
- const header = `## Active Workstream: ${active.name}\n\n`;
272
- const output = header + active.rules;
306
+ // Context section (user-defined context/rules)
307
+ const context = active.context || active.rules || '';
308
+ if (context.trim()) {
309
+ lines.push('### Context');
310
+ lines.push('');
311
+ lines.push(context.trim());
312
+ lines.push('');
313
+ }
314
+
315
+ // Repositories table
316
+ if (active.projects && active.projects.length > 0) {
317
+ lines.push('### Repositories in this Workstream');
318
+ lines.push('');
319
+ lines.push('| Repository | Path |');
320
+ lines.push('|------------|------|');
321
+ for (const p of active.projects) {
322
+ const name = path.basename(p);
323
+ const displayPath = p.replace(process.env.HOME || '', '~');
324
+ lines.push(`| ${name} | ${displayPath} |`);
325
+ }
326
+ lines.push('');
327
+ }
328
+
329
+ const output = lines.join('\n');
273
330
 
274
331
  if (!silent) {
275
332
  console.log(output);
@@ -315,6 +372,76 @@ function workstreamGet(installDir, id) {
315
372
  return data.workstreams.find(w => w.id === id) || null;
316
373
  }
317
374
 
375
+ /**
376
+ * Count how many workstreams include a given project path
377
+ */
378
+ function countWorkstreamsForProject(installDir, projectPath) {
379
+ const data = loadWorkstreams(installDir);
380
+ const absPath = path.resolve(projectPath.replace(/^~/, process.env.HOME || ''));
381
+ return data.workstreams.filter(ws =>
382
+ ws.projects && ws.projects.includes(absPath)
383
+ ).length;
384
+ }
385
+
386
+ /**
387
+ * Install the pre-prompt hook for workstream injection
388
+ */
389
+ function workstreamInstallHook() {
390
+ const hookDir = path.join(process.env.HOME || '', '.claude', 'hooks');
391
+ const hookPath = path.join(hookDir, 'pre-prompt.sh');
392
+
393
+ // Ensure hooks directory exists
394
+ if (!fs.existsSync(hookDir)) {
395
+ fs.mkdirSync(hookDir, { recursive: true });
396
+ }
397
+
398
+ const hookContent = `#!/bin/bash
399
+ # Claude Code pre-prompt hook for workstream injection
400
+ # Installed by claude-config
401
+
402
+ # Check for active workstream via env var or file
403
+ if [ -n "$CLAUDE_WORKSTREAM" ] || claude-config workstream active >/dev/null 2>&1; then
404
+ claude-config workstream inject --silent
405
+ fi
406
+ `;
407
+
408
+ // Check if hook already exists
409
+ if (fs.existsSync(hookPath)) {
410
+ const existing = fs.readFileSync(hookPath, 'utf8');
411
+ if (existing.includes('claude-config workstream inject')) {
412
+ console.log('✓ Workstream hook already installed');
413
+ return true;
414
+ }
415
+ // Append to existing hook
416
+ fs.appendFileSync(hookPath, '\n' + hookContent);
417
+ console.log('✓ Appended workstream injection to existing pre-prompt hook');
418
+ } else {
419
+ fs.writeFileSync(hookPath, hookContent);
420
+ fs.chmodSync(hookPath, '755');
421
+ console.log('✓ Installed pre-prompt hook at ~/.claude/hooks/pre-prompt.sh');
422
+ }
423
+
424
+ console.log('\nWorkstream injection is now active. When a workstream is active,');
425
+ console.log('Claude will see the restriction and context at the start of each prompt.');
426
+ console.log('\nTo activate a workstream for this session:');
427
+ console.log(' export CLAUDE_WORKSTREAM=<name-or-id>');
428
+ console.log('\nOr use the global active workstream:');
429
+ console.log(' claude-config workstream use <name>');
430
+
431
+ return true;
432
+ }
433
+
434
+ /**
435
+ * Deactivate workstream (output shell command to unset env var)
436
+ */
437
+ function workstreamDeactivate() {
438
+ console.log('To deactivate the workstream for this session, run:');
439
+ console.log(' unset CLAUDE_WORKSTREAM');
440
+ console.log('\nOr to clear the global active workstream:');
441
+ console.log(' claude-config workstream use --clear');
442
+ return true;
443
+ }
444
+
318
445
  module.exports = {
319
446
  getWorkstreamsPath,
320
447
  loadWorkstreams,
@@ -330,4 +457,8 @@ module.exports = {
330
457
  workstreamInject,
331
458
  workstreamDetect,
332
459
  workstreamGet,
460
+ getActiveWorkstream,
461
+ countWorkstreamsForProject,
462
+ workstreamInstallHook,
463
+ workstreamDeactivate,
333
464
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@regression-io/claude-config",
3
- "version": "0.36.18",
3
+ "version": "0.37.3",
4
4
  "description": "Configuration management UI for Claude Code and Antigravity - manage MCPs, rules, commands, memory, and project folders",
5
5
  "author": "regression.io",
6
6
  "main": "config-loader.js",