brainclaw 0.22.1 → 0.23.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/dist/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import path from 'node:path';
2
3
  import { Command } from 'commander';
3
4
  import { runInit } from './commands/init.js';
4
5
  import { runSetup } from './commands/setup.js';
@@ -78,6 +79,8 @@ import { runExplore } from './commands/explore.js';
78
79
  import { getInstalledBrainclawVersion } from './core/brainclaw-version.js';
79
80
  import { cleanOrphanFiles, memoryDir } from './core/io.js';
80
81
  import { initLogLevel, logger } from './core/logger.js';
82
+ import { resolveEffectiveCwd } from './core/store-resolution.js';
83
+ import { runSwitch } from './commands/switch.js';
81
84
  const program = new Command();
82
85
  function collect(value, previous) {
83
86
  return [...previous, value];
@@ -88,12 +91,30 @@ program
88
91
  .version(getInstalledBrainclawVersion())
89
92
  .option('--verbose', 'Show info-level log messages on stderr')
90
93
  .option('--debug', 'Show debug-level log messages on stderr')
94
+ .option('--cwd <path>', 'Override working directory for this invocation')
91
95
  .hook('preAction', (_thisCommand, actionCommand) => {
92
96
  const root = actionCommand.optsWithGlobals();
93
97
  initLogLevel({ verbose: root.verbose, debug: root.debug });
94
- const removed = cleanOrphanFiles(memoryDir());
95
- if (removed > 0) {
96
- logger.info(`Cleaned ${removed} orphan lock/tmp file(s) in ${memoryDir()}`);
98
+ // Skip effective cwd resolution for commands that create the store
99
+ const cmdName = actionCommand.name();
100
+ const skipResolution = cmdName === 'init' || cmdName === 'setup';
101
+ if (!skipResolution) {
102
+ // Resolve effective cwd (--cwd > BRAINCLAW_PROJECT > active-project > process.cwd)
103
+ const effectiveCwd = resolveEffectiveCwd({ explicitCwd: root.cwd });
104
+ if (effectiveCwd !== process.cwd()) {
105
+ // Change process.cwd() so all commands resolve the correct store
106
+ // without needing individual --cwd plumbing
107
+ process.chdir(effectiveCwd);
108
+ logger.info(`Resolved effective cwd: ${effectiveCwd}`);
109
+ }
110
+ const removed = cleanOrphanFiles(memoryDir());
111
+ if (removed > 0) {
112
+ logger.info(`Cleaned ${removed} orphan lock/tmp file(s) in ${memoryDir()}`);
113
+ }
114
+ }
115
+ else if (root.cwd) {
116
+ // For init/setup, still respect explicit --cwd but nothing else
117
+ process.chdir(path.resolve(root.cwd));
97
118
  }
98
119
  });
99
120
  // --- init ---
@@ -1090,6 +1111,21 @@ program
1090
1111
  .action((options) => {
1091
1112
  runExplore({ query: options.query });
1092
1113
  });
1114
+ program
1115
+ .command('switch [project]')
1116
+ .description('Set the active project for subsequent commands')
1117
+ .option('--list', 'List available projects in the workspace')
1118
+ .option('--clear', 'Clear the active project (revert to cwd)')
1119
+ .option('--json', 'Output as JSON')
1120
+ .action((project, options) => {
1121
+ const globalOpts = options.parent?.parent ? program.opts() : {};
1122
+ runSwitch(project, {
1123
+ list: options.list,
1124
+ clear: options.clear,
1125
+ json: options.json,
1126
+ cwd: globalOpts.cwd,
1127
+ });
1128
+ });
1093
1129
  program.parseAsync(process.argv).catch((err) => {
1094
1130
  console.error(err);
1095
1131
  process.exit(1);
@@ -29,7 +29,7 @@ import { validateMcpInput, validateMcpField } from '../core/input-validation.js'
29
29
  import { buildEstimationReport } from './estimation-report.js';
30
30
  import { detectAiAgent } from '../core/ai-agent-detection.js';
31
31
  import { checkGitPresence, scanGitRepos, parseRoots, parseRepoSelection, parseAgentSelection, runGlobalInstall, initReposAndConfigureAgents, readSetupState, ALL_KNOWN_AGENTS, } from './setup.js';
32
- import { resolveTargetStore, resolveStoreChain } from '../core/store-resolution.js';
32
+ import { resolveEffectiveCwd, resolveTargetStore, resolveStoreChain } from '../core/store-resolution.js';
33
33
  import { probeForQuickSetup, buildQuickSetupProbeResponse, buildOnboardingPreview } from '../core/setup-flow.js';
34
34
  import { ensureUserStore } from '../core/setup-state.js';
35
35
  import { readUnseenEvents, buildNotificationSummary } from '../core/event-log.js';
@@ -888,7 +888,7 @@ export class McpServerConnection {
888
888
  }
889
889
  }
890
890
  export function runMcp() {
891
- const cwd = process.cwd();
891
+ const cwd = resolveEffectiveCwd();
892
892
  if (!memoryExists(cwd)) {
893
893
  console.error('Project memory not initialized. Run `brainclaw init` first.');
894
894
  process.exit(1);
@@ -991,7 +991,7 @@ function getReviewAssignee(tags) {
991
991
  return undefined;
992
992
  }
993
993
  export function handleMcpReadToolCall(name, args = {}, context = {}) {
994
- const cwd = context.cwd ?? process.cwd();
994
+ const cwd = context.cwd ?? resolveEffectiveCwd();
995
995
  if (name === 'bclaw_get_context') {
996
996
  const result = buildContext({
997
997
  target: args.path,
@@ -0,0 +1,141 @@
1
+ import path from 'node:path';
2
+ import { loadActiveProject, saveActiveProject, clearActiveProject } from '../core/active-project.js';
3
+ import { memoryExists } from '../core/io.js';
4
+ import { resolveProjectRef, resolveWorkspaceRoot } from '../core/store-resolution.js';
5
+ import { scanNestedBrainclawProjects } from '../core/workspace-projects.js';
6
+ import { loadConfig } from '../core/config.js';
7
+ export function runSwitch(projectRef, options = {}) {
8
+ const cwd = options.cwd ?? process.cwd();
9
+ const wsRoot = resolveWorkspaceRoot(cwd);
10
+ if (!wsRoot) {
11
+ console.error('Error: no brainclaw workspace found. Run `brainclaw init` first.');
12
+ process.exit(1);
13
+ }
14
+ // --list: show available projects
15
+ if (options.list) {
16
+ listProjects(wsRoot, options.json ?? false);
17
+ return;
18
+ }
19
+ // --clear: remove active project
20
+ if (options.clear) {
21
+ clearActiveProject(wsRoot);
22
+ if (options.json) {
23
+ console.log(JSON.stringify({ cleared: true }));
24
+ }
25
+ else {
26
+ console.log('✔ Active project cleared. Commands will use current directory.');
27
+ }
28
+ return;
29
+ }
30
+ // No argument: show current active project
31
+ if (!projectRef) {
32
+ showCurrent(wsRoot, options.json ?? false);
33
+ return;
34
+ }
35
+ // Switch to project
36
+ const resolved = resolveProjectRef(projectRef, cwd);
37
+ if (!resolved) {
38
+ console.error(`Error: cannot resolve project "${projectRef}".`);
39
+ console.error('Use `brainclaw switch --list` to see available projects.');
40
+ process.exit(1);
41
+ }
42
+ let projectName;
43
+ try {
44
+ const config = loadConfig(resolved);
45
+ projectName = config.project_name;
46
+ }
47
+ catch {
48
+ // name is optional
49
+ }
50
+ saveActiveProject(wsRoot, {
51
+ path: resolved,
52
+ name: projectName,
53
+ switched_at: new Date().toISOString(),
54
+ switched_by: process.env.BRAINCLAW_AGENT_NAME ?? process.env.USER ?? 'unknown',
55
+ });
56
+ if (options.json) {
57
+ console.log(JSON.stringify({ switched: true, path: resolved, name: projectName }));
58
+ }
59
+ else {
60
+ const rel = path.relative(wsRoot, resolved) || '.';
61
+ console.log(`✔ Switched to ${projectName ? `"${projectName}" (${rel})` : rel}`);
62
+ }
63
+ }
64
+ function showCurrent(wsRoot, json) {
65
+ const active = loadActiveProject(wsRoot);
66
+ if (!active) {
67
+ if (json) {
68
+ console.log(JSON.stringify({ active: false }));
69
+ }
70
+ else {
71
+ console.log('No active project. Commands use current directory.');
72
+ console.log('Use `brainclaw switch <project>` to set one.');
73
+ }
74
+ return;
75
+ }
76
+ const rel = path.relative(wsRoot, active.path) || '.';
77
+ if (json) {
78
+ console.log(JSON.stringify({ active: true, ...active, relative_path: rel }));
79
+ }
80
+ else {
81
+ console.log(`Active project: ${active.name ? `"${active.name}" (${rel})` : rel}`);
82
+ console.log(` switched at: ${active.switched_at}`);
83
+ if (active.switched_by)
84
+ console.log(` switched by: ${active.switched_by}`);
85
+ }
86
+ }
87
+ function listProjects(wsRoot, json) {
88
+ const active = loadActiveProject(wsRoot);
89
+ const projects = [];
90
+ // Add workspace root itself
91
+ if (memoryExists(wsRoot)) {
92
+ try {
93
+ const config = loadConfig(wsRoot);
94
+ projects.push({
95
+ name: config.project_name,
96
+ path: wsRoot,
97
+ relative_path: '.',
98
+ active: active?.path === wsRoot,
99
+ });
100
+ }
101
+ catch {
102
+ projects.push({
103
+ path: wsRoot,
104
+ relative_path: '.',
105
+ active: active?.path === wsRoot,
106
+ });
107
+ }
108
+ }
109
+ // Discover child projects (depth 7 covers deep workspace layouts like /srv/dev/repos/global/applications/*/...)
110
+ const children = scanNestedBrainclawProjects(wsRoot, 7);
111
+ for (const child of children) {
112
+ const childPath = path.resolve(child.path);
113
+ if (childPath === wsRoot)
114
+ continue;
115
+ const rel = path.relative(wsRoot, childPath) || '.';
116
+ projects.push({
117
+ name: child.project_name,
118
+ path: childPath,
119
+ relative_path: rel,
120
+ active: active?.path === childPath,
121
+ });
122
+ }
123
+ if (json) {
124
+ console.log(JSON.stringify({ workspace: wsRoot, projects }, null, 2));
125
+ return;
126
+ }
127
+ if (projects.length === 0) {
128
+ console.log('No brainclaw projects found in this workspace.');
129
+ return;
130
+ }
131
+ console.log(`Projects in ${wsRoot}:\n`);
132
+ for (const p of projects) {
133
+ const marker = p.active ? '→ ' : ' ';
134
+ const name = p.name ? `${p.name} (${p.relative_path})` : p.relative_path;
135
+ console.log(`${marker}${name}`);
136
+ }
137
+ if (!active) {
138
+ console.log('\nNo active project. Use `brainclaw switch <project>` to set one.');
139
+ }
140
+ }
141
+ //# sourceMappingURL=switch.js.map
@@ -0,0 +1,50 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { MEMORY_DIR } from './io.js';
4
+ const ACTIVE_PROJECT_FILE = 'active-project.json';
5
+ /**
6
+ * Load the active project for a workspace.
7
+ * Returns undefined when no active project is set or the file is unreadable.
8
+ */
9
+ export function loadActiveProject(workspaceRoot) {
10
+ const filePath = path.join(workspaceRoot, MEMORY_DIR, ACTIVE_PROJECT_FILE);
11
+ if (!fs.existsSync(filePath)) {
12
+ return undefined;
13
+ }
14
+ try {
15
+ const raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
16
+ if (typeof raw.path !== 'string' || !raw.path) {
17
+ return undefined;
18
+ }
19
+ return {
20
+ path: raw.path,
21
+ name: typeof raw.name === 'string' ? raw.name : undefined,
22
+ switched_at: typeof raw.switched_at === 'string' ? raw.switched_at : new Date().toISOString(),
23
+ switched_by: typeof raw.switched_by === 'string' ? raw.switched_by : undefined,
24
+ };
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
30
+ /**
31
+ * Persist the active project for a workspace.
32
+ */
33
+ export function saveActiveProject(workspaceRoot, project) {
34
+ const dir = path.join(workspaceRoot, MEMORY_DIR);
35
+ if (!fs.existsSync(dir)) {
36
+ fs.mkdirSync(dir, { recursive: true });
37
+ }
38
+ const filePath = path.join(dir, ACTIVE_PROJECT_FILE);
39
+ fs.writeFileSync(filePath, JSON.stringify(project, null, 2) + '\n', 'utf-8');
40
+ }
41
+ /**
42
+ * Clear the active project (revert to process.cwd() default).
43
+ */
44
+ export function clearActiveProject(workspaceRoot) {
45
+ const filePath = path.join(workspaceRoot, MEMORY_DIR, ACTIVE_PROJECT_FILE);
46
+ if (fs.existsSync(filePath)) {
47
+ fs.unlinkSync(filePath);
48
+ }
49
+ }
50
+ //# sourceMappingURL=active-project.js.map
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
+ import { loadActiveProject } from './active-project.js';
4
5
  import { loadConfig } from './config.js';
5
6
  import { MEMORY_DIR } from './io.js';
6
7
  import { summarizeWorkspaceProjects } from './workspace-projects.js';
@@ -84,6 +85,98 @@ export function resolveTargetStore(cwd = process.cwd(), target = 'local', option
84
85
  const match = chain.find((s) => s.role === 'user');
85
86
  return match?.cwd ?? os.homedir();
86
87
  }
88
+ /**
89
+ * Single source of truth for the effective working directory.
90
+ *
91
+ * Priority:
92
+ * 1. explicitCwd (--cwd flag)
93
+ * 2. BRAINCLAW_PROJECT env var → resolved by name/path from workspace
94
+ * 3. active-project.json in workspace root
95
+ * 4. process.cwd()
96
+ */
97
+ export function resolveEffectiveCwd(options = {}) {
98
+ // 1. Explicit --cwd flag
99
+ if (options.explicitCwd) {
100
+ return path.resolve(options.explicitCwd);
101
+ }
102
+ // 2. BRAINCLAW_PROJECT env var
103
+ const envProject = process.env.BRAINCLAW_PROJECT;
104
+ if (envProject) {
105
+ const resolved = resolveProjectRef(envProject, process.cwd(), options.storeChainOptions);
106
+ if (resolved)
107
+ return resolved;
108
+ }
109
+ // 3. active-project.json from workspace root
110
+ const wsRoot = resolveWorkspaceRoot(process.cwd(), options.storeChainOptions);
111
+ if (wsRoot) {
112
+ const active = loadActiveProject(wsRoot);
113
+ if (active && fs.existsSync(path.join(active.path, MEMORY_DIR, 'config.yaml'))) {
114
+ return active.path;
115
+ }
116
+ }
117
+ // 4. Default
118
+ return process.cwd();
119
+ }
120
+ /**
121
+ * Find the workspace root (farthest store in the chain, or the one with
122
+ * role=workspace). Returns undefined when no store exists.
123
+ */
124
+ export function resolveWorkspaceRoot(cwd = process.cwd(), options = {}) {
125
+ const chain = resolveStoreChain(cwd, options);
126
+ if (chain.length === 0)
127
+ return undefined;
128
+ const ws = chain.find((s) => s.role === 'workspace');
129
+ return ws?.cwd ?? chain[chain.length - 1].cwd;
130
+ }
131
+ /**
132
+ * Resolve a project reference (name or relative path) to an absolute path.
133
+ * Returns undefined when the reference cannot be resolved to a valid brainclaw project.
134
+ */
135
+ export function resolveProjectRef(ref, cwd = process.cwd(), storeChainOptions) {
136
+ const wsRoot = resolveWorkspaceRoot(cwd, storeChainOptions);
137
+ if (!wsRoot)
138
+ return undefined;
139
+ // Try as absolute path
140
+ if (path.isAbsolute(ref)) {
141
+ return fs.existsSync(path.join(ref, MEMORY_DIR, 'config.yaml')) ? ref : undefined;
142
+ }
143
+ // Try as relative path from workspace root
144
+ const asPath = path.resolve(wsRoot, ref);
145
+ if (fs.existsSync(path.join(asPath, MEMORY_DIR, 'config.yaml'))) {
146
+ return asPath;
147
+ }
148
+ // Try by project name: scan child stores for matching project_name
149
+ const chain = resolveStoreChain(wsRoot, storeChainOptions);
150
+ for (const store of chain) {
151
+ if (store.cwd === wsRoot)
152
+ continue; // skip workspace itself
153
+ try {
154
+ const config = loadConfig(store.cwd);
155
+ if (config.project_name === ref)
156
+ return store.cwd;
157
+ }
158
+ catch {
159
+ // skip unreadable configs
160
+ }
161
+ }
162
+ // Try discovering child projects by scanning filesystem
163
+ try {
164
+ const wsConfig = loadConfig(wsRoot);
165
+ const summary = summarizeWorkspaceProjects(wsRoot, wsConfig);
166
+ for (const project of summary.discovered_projects) {
167
+ const projectPath = path.resolve(wsRoot, project.path);
168
+ if (project.project_name === ref) {
169
+ if (fs.existsSync(path.join(projectPath, MEMORY_DIR, 'config.yaml'))) {
170
+ return projectPath;
171
+ }
172
+ }
173
+ }
174
+ }
175
+ catch {
176
+ // fall through
177
+ }
178
+ return undefined;
179
+ }
87
180
  /**
88
181
  * Resolve the most specific child store that should answer a context request.
89
182
  *
@@ -257,10 +257,29 @@ Likely persisted fields:
257
257
 
258
258
  This should stay clearly separate from canonical memory items such as decisions and constraints.
259
259
 
260
+ ## Current Implementation (v0.23.0)
261
+
262
+ `brainclaw switch` provides the first layer of project navigation:
263
+
264
+ - `brainclaw switch <name-or-path>` — set active project by name or relative path
265
+ - `brainclaw switch --list` — discover available projects in workspace
266
+ - `resolveProjectRef()` — shared resolver for CLI and MCP (name, path, registry lookup)
267
+ - `resolveEffectiveCwd()` — single source of truth: `--cwd` > `BRAINCLAW_PROJECT` env > active-project > cwd
268
+ - MCP tools automatically resolve the active project
269
+
270
+ This covers the most common agent friction (cwd-dependent resolution) without requiring the full `project_ref` model yet.
271
+
260
272
  ## Migration Strategy
261
273
 
262
274
  The migration should be low-risk and incremental.
263
275
 
276
+ Phase 0 (done):
277
+
278
+ - `brainclaw switch` with name/path resolution
279
+ - global `--cwd` option
280
+ - `BRAINCLAW_PROJECT` environment variable
281
+ - `resolveEffectiveCwd()` used by CLI and MCP
282
+
264
283
  Phase 1:
265
284
 
266
285
  - introduce `project_ref` in workspace discovery/registry
package/docs/cli.md CHANGED
@@ -11,6 +11,53 @@ For capable coding agents, prefer MCP for dynamic runtime state:
11
11
 
12
12
  Use the CLI when a human operator is driving the workflow, when you are scripting setup or release operations, or when MCP is not the integration path.
13
13
 
14
+ ## Global Options
15
+
16
+ All commands support these global options:
17
+
18
+ | Option | Description |
19
+ |---|---|
20
+ | `--cwd <path>` | Override working directory for this invocation. Bypasses active project and env var resolution. |
21
+ | `--verbose` | Show info-level log messages on stderr |
22
+ | `--debug` | Show debug-level log messages on stderr |
23
+
24
+ **Effective cwd resolution priority** (highest wins):
25
+
26
+ 1. `--cwd` flag
27
+ 2. `BRAINCLAW_PROJECT` environment variable (project name or path)
28
+ 3. Active project set via `brainclaw switch`
29
+ 4. `process.cwd()` (shell working directory)
30
+
31
+ ---
32
+
33
+ ## Multi-Project Navigation
34
+
35
+ ### `brainclaw switch [project]`
36
+
37
+ Set the active project for subsequent CLI and MCP commands. This eliminates the need to `cd` into a subproject directory in multi-project workspaces. The active project is persisted per-workspace in `.brainclaw/active-project.json`.
38
+
39
+ | Option | Description |
40
+ |---|---|
41
+ | `--list` | List all available projects in the workspace |
42
+ | `--clear` | Clear the active project (revert to cwd default) |
43
+ | `--json` | Output as JSON |
44
+
45
+ The `<project>` argument accepts:
46
+ - **Project name** — matched against the global registry and workspace config
47
+ - **Relative path** — resolved from the workspace root (e.g. `apps/lodestar`)
48
+ - **Absolute path** — used directly
49
+
50
+ ```bash
51
+ brainclaw switch --list # discover available projects
52
+ brainclaw switch lodestar # switch by project name
53
+ brainclaw switch apps/lodestar # switch by relative path
54
+ brainclaw switch # show current active project
55
+ brainclaw switch --clear # clear, revert to cwd
56
+ brainclaw --cwd /other/path status # one-off override without switching
57
+ ```
58
+
59
+ **MCP usage:** The active project also affects MCP tools. When `bclaw_get_context()` is called without an explicit path, it resolves context from the active project's store. Agents can also use `BRAINCLAW_PROJECT=<name>` environment variable for the same effect.
60
+
14
61
  ---
15
62
 
16
63
  ## Initialize and Inspect
@@ -38,3 +38,44 @@ If shared memory is absent, that should not always be interpreted as "there is n
38
38
  It may simply mean the workspace has not been onboarded yet.
39
39
 
40
40
  This lets a single machine support multiple very different workspaces without forcing one static instruction layer to fit all of them equally well.
41
+
42
+ ## Multi-project workspaces
43
+
44
+ A workspace may contain multiple brainclaw-initialized child projects (each with its own `.brainclaw/` store). In this topology:
45
+
46
+ - The workspace root holds shared instructions, constraints, and coordination state
47
+ - Each child project holds project-specific memory (decisions, traps, plans)
48
+ - The store chain walks upward: child → repo → workspace → user
49
+
50
+ ### Working with child projects
51
+
52
+ Agents and operators can address child projects without `cd`:
53
+
54
+ ```bash
55
+ brainclaw switch apps/lodestar # set active project
56
+ brainclaw plan list # now targets lodestar's store
57
+ brainclaw switch --clear # back to workspace root
58
+ ```
59
+
60
+ Or use environment variables:
61
+
62
+ ```bash
63
+ export BRAINCLAW_PROJECT=lodestar
64
+ brainclaw context # resolves lodestar's store
65
+ ```
66
+
67
+ Or one-off overrides:
68
+
69
+ ```bash
70
+ brainclaw --cwd apps/lodestar plan list
71
+ ```
72
+
73
+ ### Project discovery
74
+
75
+ `brainclaw switch --list` discovers child projects via:
76
+
77
+ 1. Global project registry
78
+ 2. Workspace config `projects.known`
79
+ 3. Filesystem scan for subdirectories containing `.brainclaw/`
80
+
81
+ The bootstrap analysis (`analyzeRepository`) also detects brainclaw-native workspace complexity (child stores, folder strategy, known projects) alongside classic monorepo markers.
@@ -101,6 +101,41 @@ brainclaw adds all generated files to `.gitignore` automatically during init.
101
101
 
102
102
  Exception: use `--shared` if you intentionally want the main instruction file (e.g., CLAUDE.md) versioned for the whole team.
103
103
 
104
+ ## Multi-project workspaces
105
+
106
+ When a workspace contains multiple brainclaw-initialized child projects, agents need to target the correct project store. There are three mechanisms (from most to least ergonomic):
107
+
108
+ ### 1. `brainclaw switch` (persistent)
109
+
110
+ An operator or agent sets the active project once, and all subsequent commands resolve against it:
111
+
112
+ ```bash
113
+ brainclaw switch apps/lodestar # set active project
114
+ brainclaw plan list # targets lodestar
115
+ bclaw_get_context() # MCP also targets lodestar
116
+ brainclaw switch --clear # back to workspace root
117
+ ```
118
+
119
+ ### 2. `BRAINCLAW_PROJECT` environment variable
120
+
121
+ Set in the shell or agent configuration. Useful for CI/CD or when the agent can control its environment:
122
+
123
+ ```bash
124
+ export BRAINCLAW_PROJECT=lodestar
125
+ ```
126
+
127
+ ### 3. `--cwd` flag (one-off override)
128
+
129
+ For a single command without changing the active project:
130
+
131
+ ```bash
132
+ brainclaw --cwd apps/lodestar plan list
133
+ ```
134
+
135
+ **Priority**: `--cwd` > `BRAINCLAW_PROJECT` > active project > shell cwd.
136
+
137
+ **Discovery**: use `brainclaw switch --list` to see all available projects in the workspace.
138
+
104
139
  ## Session lifecycle
105
140
 
106
141
  ### Starting work
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainclaw",
3
- "version": "0.22.1",
3
+ "version": "0.23.1",
4
4
  "description": "Shared project memory for humans and coding agents.",
5
5
  "type": "module",
6
6
  "bin": {