@regression-io/claude-config 0.24.9 → 0.30.1

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
@@ -19,7 +19,7 @@ const fs = require('fs');
19
19
  const path = require('path');
20
20
  const { execSync } = require('child_process');
21
21
 
22
- const VERSION = '0.24.9';
22
+ const VERSION = '0.30.1';
23
23
 
24
24
  // Tool-specific path configurations
25
25
  const TOOL_PATHS = {
@@ -1803,6 +1803,329 @@ class ClaudeConfigManager {
1803
1803
  console.log(`✓ Removed project: ${removed.name}`);
1804
1804
  return true;
1805
1805
  }
1806
+
1807
+ // ===========================================================================
1808
+ // WORKSTREAMS
1809
+ // ===========================================================================
1810
+
1811
+ /**
1812
+ * Get workstreams file path
1813
+ */
1814
+ getWorkstreamsPath() {
1815
+ return path.join(this.installDir, 'workstreams.json');
1816
+ }
1817
+
1818
+ /**
1819
+ * Load workstreams
1820
+ */
1821
+ loadWorkstreams() {
1822
+ const wsPath = this.getWorkstreamsPath();
1823
+ if (fs.existsSync(wsPath)) {
1824
+ try {
1825
+ return JSON.parse(fs.readFileSync(wsPath, 'utf8'));
1826
+ } catch (e) {
1827
+ return { workstreams: [], activeId: null, lastUsedByProject: {} };
1828
+ }
1829
+ }
1830
+ return { workstreams: [], activeId: null, lastUsedByProject: {} };
1831
+ }
1832
+
1833
+ /**
1834
+ * Save workstreams
1835
+ */
1836
+ saveWorkstreams(data) {
1837
+ const wsPath = this.getWorkstreamsPath();
1838
+ const dir = path.dirname(wsPath);
1839
+ if (!fs.existsSync(dir)) {
1840
+ fs.mkdirSync(dir, { recursive: true });
1841
+ }
1842
+ fs.writeFileSync(wsPath, JSON.stringify(data, null, 2) + '\n');
1843
+ }
1844
+
1845
+ /**
1846
+ * List all workstreams
1847
+ */
1848
+ workstreamList() {
1849
+ const data = this.loadWorkstreams();
1850
+
1851
+ if (data.workstreams.length === 0) {
1852
+ console.log('\nNo workstreams defined.');
1853
+ console.log('Create one with: claude-config workstream create "Name"\n');
1854
+ return data.workstreams;
1855
+ }
1856
+
1857
+ console.log('\n📋 Workstreams:\n');
1858
+ for (const ws of data.workstreams) {
1859
+ const active = ws.id === data.activeId ? '● ' : '○ ';
1860
+ console.log(`${active}${ws.name}`);
1861
+ if (ws.projects && ws.projects.length > 0) {
1862
+ console.log(` Projects: ${ws.projects.map(p => path.basename(p)).join(', ')}`);
1863
+ }
1864
+ if (ws.rules) {
1865
+ const preview = ws.rules.substring(0, 60).replace(/\n/g, ' ');
1866
+ console.log(` Rules: ${preview}${ws.rules.length > 60 ? '...' : ''}`);
1867
+ }
1868
+ }
1869
+ console.log('');
1870
+ return data.workstreams;
1871
+ }
1872
+
1873
+ /**
1874
+ * Create a new workstream
1875
+ */
1876
+ workstreamCreate(name, projects = [], rules = '') {
1877
+ if (!name) {
1878
+ console.error('Usage: claude-config workstream create "Name"');
1879
+ return null;
1880
+ }
1881
+
1882
+ const data = this.loadWorkstreams();
1883
+
1884
+ // Check for duplicate name
1885
+ if (data.workstreams.some(ws => ws.name.toLowerCase() === name.toLowerCase())) {
1886
+ console.error(`Workstream "${name}" already exists`);
1887
+ return null;
1888
+ }
1889
+
1890
+ const workstream = {
1891
+ id: Date.now().toString(36) + Math.random().toString(36).substr(2, 5),
1892
+ name,
1893
+ projects: projects.map(p => path.resolve(p.replace(/^~/, process.env.HOME || ''))),
1894
+ rules: rules || '',
1895
+ createdAt: new Date().toISOString(),
1896
+ updatedAt: new Date().toISOString()
1897
+ };
1898
+
1899
+ data.workstreams.push(workstream);
1900
+
1901
+ // If first workstream, make it active
1902
+ if (!data.activeId) {
1903
+ data.activeId = workstream.id;
1904
+ }
1905
+
1906
+ this.saveWorkstreams(data);
1907
+ console.log(`✓ Created workstream: ${name}`);
1908
+ return workstream;
1909
+ }
1910
+
1911
+ /**
1912
+ * Update a workstream
1913
+ */
1914
+ workstreamUpdate(idOrName, updates) {
1915
+ const data = this.loadWorkstreams();
1916
+ const ws = data.workstreams.find(
1917
+ w => w.id === idOrName || w.name.toLowerCase() === idOrName.toLowerCase()
1918
+ );
1919
+
1920
+ if (!ws) {
1921
+ console.error(`Workstream not found: ${idOrName}`);
1922
+ return null;
1923
+ }
1924
+
1925
+ if (updates.name !== undefined) ws.name = updates.name;
1926
+ if (updates.projects !== undefined) {
1927
+ ws.projects = updates.projects.map(p =>
1928
+ path.resolve(p.replace(/^~/, process.env.HOME || ''))
1929
+ );
1930
+ }
1931
+ if (updates.rules !== undefined) ws.rules = updates.rules;
1932
+ ws.updatedAt = new Date().toISOString();
1933
+
1934
+ this.saveWorkstreams(data);
1935
+ console.log(`✓ Updated workstream: ${ws.name}`);
1936
+ return ws;
1937
+ }
1938
+
1939
+ /**
1940
+ * Delete a workstream
1941
+ */
1942
+ workstreamDelete(idOrName) {
1943
+ const data = this.loadWorkstreams();
1944
+ const idx = data.workstreams.findIndex(
1945
+ w => w.id === idOrName || w.name.toLowerCase() === idOrName.toLowerCase()
1946
+ );
1947
+
1948
+ if (idx === -1) {
1949
+ console.error(`Workstream not found: ${idOrName}`);
1950
+ return false;
1951
+ }
1952
+
1953
+ const removed = data.workstreams.splice(idx, 1)[0];
1954
+
1955
+ // If removed active workstream, select first remaining
1956
+ if (data.activeId === removed.id) {
1957
+ data.activeId = data.workstreams[0]?.id || null;
1958
+ }
1959
+
1960
+ this.saveWorkstreams(data);
1961
+ console.log(`✓ Deleted workstream: ${removed.name}`);
1962
+ return true;
1963
+ }
1964
+
1965
+ /**
1966
+ * Set active workstream
1967
+ */
1968
+ workstreamUse(idOrName) {
1969
+ const data = this.loadWorkstreams();
1970
+
1971
+ if (!idOrName) {
1972
+ // Show current active
1973
+ const active = data.workstreams.find(w => w.id === data.activeId);
1974
+ if (active) {
1975
+ console.log(`Active workstream: ${active.name}`);
1976
+ } else {
1977
+ console.log('No active workstream');
1978
+ }
1979
+ return active || null;
1980
+ }
1981
+
1982
+ const ws = data.workstreams.find(
1983
+ w => w.id === idOrName || w.name.toLowerCase() === idOrName.toLowerCase()
1984
+ );
1985
+
1986
+ if (!ws) {
1987
+ console.error(`Workstream not found: ${idOrName}`);
1988
+ return null;
1989
+ }
1990
+
1991
+ data.activeId = ws.id;
1992
+ this.saveWorkstreams(data);
1993
+ console.log(`✓ Switched to workstream: ${ws.name}`);
1994
+ return ws;
1995
+ }
1996
+
1997
+ /**
1998
+ * Get active workstream
1999
+ */
2000
+ workstreamActive() {
2001
+ const data = this.loadWorkstreams();
2002
+ return data.workstreams.find(w => w.id === data.activeId) || null;
2003
+ }
2004
+
2005
+ /**
2006
+ * Add project to workstream
2007
+ */
2008
+ workstreamAddProject(idOrName, projectPath) {
2009
+ const data = this.loadWorkstreams();
2010
+ const ws = data.workstreams.find(
2011
+ w => w.id === idOrName || w.name.toLowerCase() === idOrName.toLowerCase()
2012
+ );
2013
+
2014
+ if (!ws) {
2015
+ console.error(`Workstream not found: ${idOrName}`);
2016
+ return null;
2017
+ }
2018
+
2019
+ const absPath = path.resolve(projectPath.replace(/^~/, process.env.HOME || ''));
2020
+
2021
+ if (!ws.projects.includes(absPath)) {
2022
+ ws.projects.push(absPath);
2023
+ ws.updatedAt = new Date().toISOString();
2024
+ this.saveWorkstreams(data);
2025
+ console.log(`✓ Added ${path.basename(absPath)} to ${ws.name}`);
2026
+ } else {
2027
+ console.log(`Project already in workstream: ${path.basename(absPath)}`);
2028
+ }
2029
+
2030
+ return ws;
2031
+ }
2032
+
2033
+ /**
2034
+ * Remove project from workstream
2035
+ */
2036
+ workstreamRemoveProject(idOrName, projectPath) {
2037
+ const data = this.loadWorkstreams();
2038
+ const ws = data.workstreams.find(
2039
+ w => w.id === idOrName || w.name.toLowerCase() === idOrName.toLowerCase()
2040
+ );
2041
+
2042
+ if (!ws) {
2043
+ console.error(`Workstream not found: ${idOrName}`);
2044
+ return null;
2045
+ }
2046
+
2047
+ const absPath = path.resolve(projectPath.replace(/^~/, process.env.HOME || ''));
2048
+ const idx = ws.projects.indexOf(absPath);
2049
+
2050
+ if (idx !== -1) {
2051
+ ws.projects.splice(idx, 1);
2052
+ ws.updatedAt = new Date().toISOString();
2053
+ this.saveWorkstreams(data);
2054
+ console.log(`✓ Removed ${path.basename(absPath)} from ${ws.name}`);
2055
+ } else {
2056
+ console.log(`Project not in workstream: ${path.basename(absPath)}`);
2057
+ }
2058
+
2059
+ return ws;
2060
+ }
2061
+
2062
+ /**
2063
+ * Inject active workstream rules into Claude context
2064
+ * Called by pre-prompt hook
2065
+ */
2066
+ workstreamInject(silent = false) {
2067
+ const active = this.workstreamActive();
2068
+
2069
+ if (!active) {
2070
+ if (!silent) console.log('No active workstream');
2071
+ return null;
2072
+ }
2073
+
2074
+ if (!active.rules || active.rules.trim() === '') {
2075
+ if (!silent) console.log(`Workstream "${active.name}" has no rules defined`);
2076
+ return null;
2077
+ }
2078
+
2079
+ // Output rules to stdout for hook to capture
2080
+ const header = `## Active Workstream: ${active.name}\n\n`;
2081
+ const output = header + active.rules;
2082
+
2083
+ if (!silent) {
2084
+ console.log(output);
2085
+ }
2086
+
2087
+ return output;
2088
+ }
2089
+
2090
+ /**
2091
+ * Detect workstream from current directory
2092
+ */
2093
+ workstreamDetect(dir = process.cwd()) {
2094
+ const data = this.loadWorkstreams();
2095
+ const absDir = path.resolve(dir.replace(/^~/, process.env.HOME || ''));
2096
+
2097
+ // Find workstreams that contain this directory or a parent
2098
+ const matches = data.workstreams.filter(ws =>
2099
+ ws.projects.some(p => absDir.startsWith(p) || p.startsWith(absDir))
2100
+ );
2101
+
2102
+ if (matches.length === 0) {
2103
+ return null;
2104
+ }
2105
+
2106
+ if (matches.length === 1) {
2107
+ return matches[0];
2108
+ }
2109
+
2110
+ // Multiple matches - check lastUsedByProject
2111
+ if (data.lastUsedByProject && data.lastUsedByProject[absDir]) {
2112
+ const lastUsed = matches.find(ws => ws.id === data.lastUsedByProject[absDir]);
2113
+ if (lastUsed) return lastUsed;
2114
+ }
2115
+
2116
+ // Return most recently updated
2117
+ return matches.sort((a, b) =>
2118
+ new Date(b.updatedAt) - new Date(a.updatedAt)
2119
+ )[0];
2120
+ }
2121
+
2122
+ /**
2123
+ * Get workstream by ID
2124
+ */
2125
+ workstreamGet(id) {
2126
+ const data = this.loadWorkstreams();
2127
+ return data.workstreams.find(w => w.id === id) || null;
2128
+ }
1806
2129
  }
1807
2130
 
1808
2131
  // =============================================================================
@@ -1903,6 +2226,42 @@ if (require.main === module) {
1903
2226
  }
1904
2227
  break;
1905
2228
 
2229
+ // Workstreams
2230
+ case 'workstream':
2231
+ case 'ws':
2232
+ if (args[1] === 'create' || args[1] === 'new') {
2233
+ manager.workstreamCreate(args[2]);
2234
+ } else if (args[1] === 'delete' || args[1] === 'rm') {
2235
+ manager.workstreamDelete(args[2]);
2236
+ } else if (args[1] === 'use' || args[1] === 'switch') {
2237
+ manager.workstreamUse(args[2]);
2238
+ } else if (args[1] === 'add-project') {
2239
+ manager.workstreamAddProject(args[2], args[3]);
2240
+ } else if (args[1] === 'remove-project') {
2241
+ manager.workstreamRemoveProject(args[2], args[3]);
2242
+ } else if (args[1] === 'inject') {
2243
+ const silent = args.includes('--silent') || args.includes('-s');
2244
+ manager.workstreamInject(silent);
2245
+ } else if (args[1] === 'detect') {
2246
+ const ws = manager.workstreamDetect(args[2] || process.cwd());
2247
+ if (ws) {
2248
+ console.log(ws.name);
2249
+ }
2250
+ } else if (args[1] === 'active') {
2251
+ const ws = manager.workstreamActive();
2252
+ if (ws) {
2253
+ console.log(`Active: ${ws.name}`);
2254
+ if (ws.projects.length > 0) {
2255
+ console.log(`Projects: ${ws.projects.map(p => path.basename(p)).join(', ')}`);
2256
+ }
2257
+ } else {
2258
+ console.log('No active workstream');
2259
+ }
2260
+ } else {
2261
+ manager.workstreamList();
2262
+ }
2263
+ break;
2264
+
1906
2265
  // Maintenance
1907
2266
  case 'update':
1908
2267
  manager.update(args[1]);
@@ -1956,6 +2315,17 @@ Project Commands (for UI):
1956
2315
  project add [path] --name X Add with custom display name
1957
2316
  project remove <name|path> Remove project from registry
1958
2317
 
2318
+ Workstream Commands:
2319
+ workstream List all workstreams
2320
+ workstream create "Name" Create new workstream
2321
+ workstream delete <name> Delete workstream
2322
+ workstream use <name> Set active workstream
2323
+ workstream active Show current active workstream
2324
+ workstream add-project <ws> <path> Add project to workstream
2325
+ workstream remove-project <ws> <path> Remove project from workstream
2326
+ workstream inject [--silent] Output active workstream rules (for hooks)
2327
+ workstream detect [path] Detect workstream for directory
2328
+
1959
2329
  Registry Commands:
1960
2330
  registry-add <name> '<json>' Add MCP to global registry
1961
2331
  registry-remove <name> Remove MCP from registry
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@regression-io/claude-config",
3
- "version": "0.24.9",
3
+ "version": "0.30.1",
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",