brainclaw 0.19.14 → 0.21.0

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.
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Setup flow logic for the new onboarding experience.
3
+ *
4
+ * Two modes:
5
+ * - Quick: init the current repo (1-2 MCP calls)
6
+ * - Batch: scan roots and init multiple repos (legacy 4-step flow)
7
+ *
8
+ * Quick flow:
9
+ * 1. Auto-detect repo, agent, nearby stores → ask project type + topology
10
+ * 2. Init + optional bootstrap → done
11
+ */
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import { memoryExists } from './io.js';
15
+ import { detectAiAgent } from './ai-agent-detection.js';
16
+ import { resolveStoreChain } from './store-resolution.js';
17
+ import { analyzeRepository } from './repo-analysis.js';
18
+ import { getAgentCapabilityProfile, getAllAgentCapabilityProfiles } from './agent-capability.js';
19
+ import { describeAgentSurfaces } from './agent-capability.js';
20
+ import { loadState } from './state.js';
21
+ /**
22
+ * Probe the current working directory to understand what we're working with.
23
+ * This is the first step of the quick setup flow — no questions yet, just detection.
24
+ */
25
+ export function probeForQuickSetup(cwd = process.cwd()) {
26
+ const isGitRepo = fs.existsSync(path.join(cwd, '.git'));
27
+ const alreadyInitialized = memoryExists(cwd);
28
+ const repoName = path.basename(cwd);
29
+ // Detect agent
30
+ const detectedAi = detectAiAgent();
31
+ const detectedAgent = detectedAi
32
+ ? { name: detectedAi.name, profile: getAgentCapabilityProfile(detectedAi.name) }
33
+ : undefined;
34
+ // Find other known agent profiles (for info)
35
+ const otherAgents = detectedAgent
36
+ ? getAllAgentCapabilityProfiles().filter((p) => p.name !== detectedAgent.name)
37
+ : getAllAgentCapabilityProfiles();
38
+ // Scan nearby stores
39
+ const nearbyStores = resolveStoreChain(cwd);
40
+ // Has content?
41
+ const IGNORED = new Set(['.git', '.brainclaw', '.gitignore', '.gitattributes', '.DS_Store', 'Thumbs.db']);
42
+ let hasContent = false;
43
+ try {
44
+ const entries = fs.readdirSync(cwd);
45
+ hasContent = entries.some((e) => !IGNORED.has(e));
46
+ }
47
+ catch {
48
+ // empty or unreadable
49
+ }
50
+ // Analyze repo for project type suggestion
51
+ let suggestedProjectType = 'standalone';
52
+ let analysisReasons = [];
53
+ try {
54
+ const analysis = analyzeRepository(cwd);
55
+ if (analysis.recommendedMode === 'multi-project') {
56
+ suggestedProjectType = 'workspace';
57
+ }
58
+ analysisReasons = analysis.reasons;
59
+ }
60
+ catch {
61
+ // analysis failed — default to standalone
62
+ }
63
+ // If there are nearby stores, suggest linking
64
+ const parentStores = nearbyStores.filter((s) => s.depth > 0 && s.role === 'workspace');
65
+ if (parentStores.length > 0 && !alreadyInitialized) {
66
+ suggestedProjectType = 'linked';
67
+ }
68
+ // Build repo summary
69
+ const summaryParts = [];
70
+ if (isGitRepo)
71
+ summaryParts.push('git repo');
72
+ if (hasContent)
73
+ summaryParts.push(`"${repoName}"`);
74
+ if (analysisReasons.length > 0)
75
+ summaryParts.push(analysisReasons[0]);
76
+ const repoSummary = summaryParts.join(', ') || 'empty directory';
77
+ return {
78
+ cwd,
79
+ isGitRepo,
80
+ alreadyInitialized,
81
+ repoName,
82
+ repoSummary,
83
+ detectedAgent,
84
+ otherAgents,
85
+ nearbyStores,
86
+ hasContent,
87
+ suggestedProjectType,
88
+ analysisReasons,
89
+ };
90
+ }
91
+ /**
92
+ * Build the structured response for the initial probe step of quick setup.
93
+ * Returns data the agent can use to present choices to the user.
94
+ */
95
+ export function buildQuickSetupProbeResponse(probe) {
96
+ const lines = [];
97
+ if (probe.alreadyInitialized) {
98
+ lines.push(`This project (${probe.repoName}) is already initialized with brainclaw.`);
99
+ lines.push('Use `brainclaw export --detect --write` to regenerate agent files, or `brainclaw upgrade` to migrate.');
100
+ return {
101
+ text: lines.join('\n'),
102
+ structured: {
103
+ already_initialized: true,
104
+ cwd: probe.cwd,
105
+ repo_name: probe.repoName,
106
+ },
107
+ };
108
+ }
109
+ lines.push(`Detected: ${probe.repoSummary}`);
110
+ if (probe.detectedAgent) {
111
+ lines.push(`Agent: ${probe.detectedAgent.name} (${probe.detectedAgent.profile.templateTier === 'A' ? 'full integration' : probe.detectedAgent.profile.templateTier === 'B' ? 'standard integration' : 'limited integration'})`);
112
+ lines.push(`Surfaces: ${describeAgentSurfaces(probe.detectedAgent.name).join(', ')}`);
113
+ }
114
+ if (probe.nearbyStores.length > 0) {
115
+ lines.push('');
116
+ lines.push('Nearby brainclaw stores:');
117
+ for (const store of probe.nearbyStores) {
118
+ lines.push(` - ${store.role} at ${store.cwd} (depth ${store.depth})`);
119
+ }
120
+ }
121
+ lines.push('');
122
+ lines.push('Ask the user:');
123
+ lines.push('');
124
+ lines.push('1. What kind of project is this?');
125
+ lines.push(` - Standalone project (single .brainclaw/ for the whole repo)${probe.suggestedProjectType === 'standalone' ? ' ← suggested' : ''}`);
126
+ lines.push(` - Workspace with sub-projects (monorepo)${probe.suggestedProjectType === 'workspace' ? ' ← suggested' : ''}`);
127
+ if (probe.nearbyStores.some((s) => s.role === 'workspace')) {
128
+ lines.push(` - Linked to an existing workspace${probe.suggestedProjectType === 'linked' ? ' ← suggested' : ''}`);
129
+ }
130
+ lines.push('');
131
+ lines.push('2. Should the memory be shared with the team?');
132
+ lines.push(' - Yes, shared via git (.brainclaw/ tracked in git) ← recommended');
133
+ lines.push(' - No, local only (.brainclaw/ gitignored)');
134
+ return {
135
+ text: lines.join('\n'),
136
+ structured: {
137
+ pending_question: 'quick_init',
138
+ probe: {
139
+ cwd: probe.cwd,
140
+ repo_name: probe.repoName,
141
+ repo_summary: probe.repoSummary,
142
+ is_git_repo: probe.isGitRepo,
143
+ has_content: probe.hasContent,
144
+ detected_agent: probe.detectedAgent?.name ?? null,
145
+ agent_surfaces: probe.detectedAgent ? describeAgentSurfaces(probe.detectedAgent.name) : [],
146
+ nearby_stores: probe.nearbyStores.map((s) => ({ role: s.role, path: s.cwd, depth: s.depth })),
147
+ suggested_project_type: probe.suggestedProjectType,
148
+ },
149
+ choices: {
150
+ project_type: ['standalone', 'workspace', ...(probe.nearbyStores.some((s) => s.role === 'workspace') ? ['linked'] : [])],
151
+ topology: ['embedded', 'sidecar'],
152
+ },
153
+ },
154
+ };
155
+ }
156
+ /**
157
+ * Generate a "moment aha" preview after init — shows what an agent
158
+ * would see when calling bclaw_get_context on this project.
159
+ */
160
+ export function buildOnboardingPreview(cwd) {
161
+ try {
162
+ const state = loadState(cwd);
163
+ const constraints = state.active_constraints.filter((c) => c.status === 'active');
164
+ const traps = state.known_traps.filter((t) => t.visibility === 'shared' && (!t.status || t.status === 'active'));
165
+ const plans = state.plan_items.filter((p) => p.status === 'in_progress' || p.status === 'todo');
166
+ if (constraints.length === 0 && traps.length === 0 && plans.length === 0) {
167
+ return 'Memory is empty. Run bclaw_bootstrap to extract initial context from this repo.';
168
+ }
169
+ const lines = ['Here is what your agent will see:'];
170
+ if (constraints.length > 0) {
171
+ lines.push(` Constraints: ${constraints.length} active`);
172
+ for (const c of constraints.slice(0, 3))
173
+ lines.push(` - ${c.text}`);
174
+ }
175
+ if (traps.length > 0) {
176
+ lines.push(` Traps: ${traps.length} known`);
177
+ for (const t of traps.slice(0, 3))
178
+ lines.push(` - [${t.severity}] ${t.text}`);
179
+ }
180
+ if (plans.length > 0) {
181
+ lines.push(` Plans: ${plans.length} active`);
182
+ for (const p of plans.slice(0, 3))
183
+ lines.push(` - [${p.status}] ${p.text}`);
184
+ }
185
+ return lines.join('\n');
186
+ }
187
+ catch {
188
+ return 'Memory is empty. Run bclaw_bootstrap to extract initial context from this repo.';
189
+ }
190
+ }
191
+ //# sourceMappingURL=setup-flow.js.map
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { writeFileAtomic } from './io.js';
3
+ import { ensureMemoryDir, writeFileAtomic } from './io.js';
4
+ import { defaultConfig, saveConfig } from './config.js';
4
5
  export function resolveHomeDir(env = process.env) {
5
6
  return env.HOME?.trim() || env.USERPROFILE?.trim() || undefined;
6
7
  }
@@ -47,4 +48,32 @@ export function hasCompletedSetup(env = process.env) {
47
48
  const configPath = userStoreConfigPath(env);
48
49
  return configPath ? fs.existsSync(configPath) : false;
49
50
  }
51
+ /**
52
+ * Ensure the user-global store (~/.brainclaw/) exists, creating it implicitly
53
+ * if absent. This replaces the old "setup required before init" guard —
54
+ * init can now auto-create the minimal user store on first run.
55
+ *
56
+ * Idempotent: returns immediately if the user store already exists.
57
+ * Non-fatal: logs a warning if creation fails but does not throw.
58
+ */
59
+ export function ensureUserStore(env = process.env) {
60
+ const home = resolveHomeDir(env);
61
+ if (!home)
62
+ return false;
63
+ const configPath = path.join(home, '.brainclaw', 'config.yaml');
64
+ if (fs.existsSync(configPath)) {
65
+ return true; // already exists
66
+ }
67
+ try {
68
+ ensureMemoryDir(home);
69
+ const cfg = defaultConfig('user-global');
70
+ saveConfig(cfg, home);
71
+ fs.appendFileSync(configPath, 'store_type: user\n');
72
+ return true;
73
+ }
74
+ catch (err) {
75
+ console.warn(`Warning: could not create user store at ${path.join(home, '.brainclaw')}:`, err instanceof Error ? err.message : String(err));
76
+ return false;
77
+ }
78
+ }
50
79
  //# sourceMappingURL=setup-state.js.map
@@ -1,7 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
+ import { loadConfig } from './config.js';
4
5
  import { MEMORY_DIR } from './io.js';
6
+ import { summarizeWorkspaceProjects } from './workspace-projects.js';
5
7
  /**
6
8
  * Walk up the filesystem from `cwd`, collecting every `.brainclaw/` directory
7
9
  * found along the way, up to (and including) `boundary`.
@@ -82,6 +84,49 @@ export function resolveTargetStore(cwd = process.cwd(), target = 'local', option
82
84
  const match = chain.find((s) => s.role === 'user');
83
85
  return match?.cwd ?? os.homedir();
84
86
  }
87
+ /**
88
+ * Resolve the most specific child store that should answer a context request.
89
+ *
90
+ * This keeps the current cwd by default, but when `target` clearly points inside
91
+ * a nested Brainclaw project (for example from a workspace root in folder mode),
92
+ * it returns that child store cwd instead.
93
+ */
94
+ export function resolveContextStoreCwd(cwd = process.cwd(), target) {
95
+ const trimmedTarget = target?.trim();
96
+ if (!trimmedTarget) {
97
+ return cwd;
98
+ }
99
+ const primary = resolvePrimaryStore(cwd);
100
+ if (!primary) {
101
+ return cwd;
102
+ }
103
+ const absoluteTarget = resolveAbsoluteTargetPath(cwd, trimmedTarget);
104
+ if (!absoluteTarget) {
105
+ return cwd;
106
+ }
107
+ let config;
108
+ try {
109
+ config = loadConfig(primary.cwd);
110
+ }
111
+ catch {
112
+ return cwd;
113
+ }
114
+ const summary = summarizeWorkspaceProjects(primary.cwd, config);
115
+ if (summary.discovered_projects.length === 0) {
116
+ return cwd;
117
+ }
118
+ const candidates = summary.discovered_projects
119
+ .map((project) => path.resolve(primary.cwd, project.path))
120
+ .filter((candidatePath) => candidatePath !== primary.cwd)
121
+ .filter((candidatePath) => fs.existsSync(path.join(candidatePath, MEMORY_DIR)))
122
+ .sort((a, b) => b.length - a.length);
123
+ for (const candidate of candidates) {
124
+ if (isAtOrBelow(absoluteTarget, candidate)) {
125
+ return candidate;
126
+ }
127
+ }
128
+ return cwd;
129
+ }
85
130
  /**
86
131
  * Return true if `dir` is at or below `ancestor` in the filesystem hierarchy.
87
132
  */
@@ -90,6 +135,19 @@ function isAtOrBelow(dir, ancestor) {
90
135
  // If relative path starts with '..', dir is above ancestor
91
136
  return !rel.startsWith('..');
92
137
  }
138
+ function resolveAbsoluteTargetPath(cwd, target) {
139
+ if (path.isAbsolute(target)) {
140
+ return path.resolve(target);
141
+ }
142
+ const joined = path.resolve(cwd, target);
143
+ if (fs.existsSync(joined)) {
144
+ return joined;
145
+ }
146
+ if (target.includes('/') || target.includes('\\') || target.startsWith('.')) {
147
+ return joined;
148
+ }
149
+ return undefined;
150
+ }
93
151
  /**
94
152
  * Infer the store role from config.yaml store_type field, or fall back to
95
153
  * heuristics (presence of .git sibling = repo, no parent store = workspace).
@@ -0,0 +1,305 @@
1
+ # Project Refs for Multi-Project Navigation
2
+
3
+ ## Goal
4
+
5
+ In a multi-project workspace, agents should be able to address any known project from anywhere in the workspace without depending on the current working directory.
6
+
7
+ The core primitive for this is a stable `project_ref`.
8
+
9
+ Examples:
10
+
11
+ - `dev/repos/global`
12
+ - `applications/lodestar`
13
+ - `core_services/postgres`
14
+
15
+ This lets Brainclaw support direct project navigation commands such as:
16
+
17
+ - `brainclaw projects`
18
+ - `brainclaw project applications/lodestar`
19
+ - `brainclaw context --project applications/lodestar`
20
+ - `brainclaw children dev/repos/global`
21
+ - `brainclaw ancestors applications/lodestar`
22
+ - `brainclaw dependencies applications/lodestar`
23
+ - `brainclaw locate dev/repos/global/applications/lodestar/src/app.ts`
24
+
25
+ The same addressing model must exist over MCP.
26
+
27
+ ## Why
28
+
29
+ Current workspace-centric behavior is still too dependent on `cwd` and path heuristics:
30
+
31
+ - `brainclaw context --for <path>` can help rerank context
32
+ - in folder-mode workspaces it can now resolve a child store for some path-shaped targets
33
+ - but the mental model is still "where am I on disk?"
34
+
35
+ For agents, this is weaker than "which project am I addressing?"
36
+
37
+ `project_ref` should become the natural addressing unit for:
38
+
39
+ - context lookup
40
+ - workspace navigation
41
+ - parent/child traversal
42
+ - dependency traversal
43
+ - future project-scoped MCP tools
44
+
45
+ ## Core Model
46
+
47
+ Each known project keeps its internal `project_id`.
48
+
49
+ Each project also gets a stable human-readable `project_ref`:
50
+
51
+ - derived from the workspace-relative path
52
+ - normalized with forward slashes
53
+ - unique within the workspace
54
+
55
+ Examples:
56
+
57
+ - workspace root: `.`
58
+ - child repo: `dev/repos/global`
59
+ - nested project: `dev/repos/global/applications/lodestar`
60
+
61
+ The registry for a workspace project should expose at least:
62
+
63
+ - `project_id`
64
+ - `project_ref`
65
+ - `project_name`
66
+ - `relative_path`
67
+ - `absolute_path`
68
+ - `workspace_root`
69
+ - `parent_ref`
70
+ - `aliases`
71
+ - `store_present`
72
+ - `source`
73
+ - `dependencies`
74
+
75
+ ## Naming Rules
76
+
77
+ Canonical rule:
78
+
79
+ - `project_ref` is the workspace-relative path to the project root
80
+
81
+ Short aliases:
82
+
83
+ - the final path segment may be used as an alias
84
+ - only when unique within the workspace
85
+ - ambiguous aliases must fail with a disambiguation error
86
+
87
+ Examples:
88
+
89
+ - `applications/lodestar` may have alias `lodestar`
90
+ - if two projects end with `api`, `brainclaw project api` must fail and suggest full refs
91
+
92
+ This keeps the model simple:
93
+
94
+ - canonical ref is always deterministic
95
+ - short names are optional ergonomics, not identity
96
+
97
+ ## Project Resolution
98
+
99
+ Resolution should work from anywhere in the workspace.
100
+
101
+ The resolver accepts:
102
+
103
+ - exact `project_ref`
104
+ - exact `project_id`
105
+ - unique short alias
106
+ - absolute path to a project root
107
+ - absolute or relative path inside a project
108
+
109
+ The resolver returns:
110
+
111
+ - the matched project record
112
+ - the resolved project root
113
+ - the matched method: `ref`, `id`, `alias`, or `path`
114
+
115
+ Resolution priority:
116
+
117
+ 1. exact `project_ref`
118
+ 2. exact `project_id`
119
+ 3. unique alias
120
+ 4. path-to-project containment
121
+
122
+ If multiple matches remain, Brainclaw must stop and return a clear ambiguity error.
123
+
124
+ ## CLI Surface
125
+
126
+ The minimal agent-first CLI surface should be:
127
+
128
+ ### `brainclaw projects`
129
+
130
+ List known projects in the current workspace.
131
+
132
+ Fields:
133
+
134
+ - `project_ref`
135
+ - `project_name`
136
+ - `relative_path`
137
+ - `parent_ref`
138
+ - `store_present`
139
+
140
+ ### `brainclaw project <ref>`
141
+
142
+ Show a compact project card.
143
+
144
+ Fields:
145
+
146
+ - `project_id`
147
+ - `project_ref`
148
+ - `aliases`
149
+ - `absolute_path`
150
+ - `parent_ref`
151
+ - `children`
152
+ - `dependencies`
153
+ - store health summary
154
+
155
+ ### `brainclaw context --project <ref>`
156
+
157
+ Resolve the target project first, then build context from that store.
158
+
159
+ Notes:
160
+
161
+ - this is stronger than `context --for`
162
+ - `--for` still helps ranking within the resolved project
163
+
164
+ Example:
165
+
166
+ ```bash
167
+ brainclaw context --project applications/lodestar --for src/app.ts
168
+ ```
169
+
170
+ ### `brainclaw children <ref>`
171
+
172
+ Return direct child projects.
173
+
174
+ ### `brainclaw ancestors <ref>`
175
+
176
+ Return the project chain from parent to workspace root.
177
+
178
+ ### `brainclaw dependencies <ref>`
179
+
180
+ Return declared or detected project dependencies.
181
+
182
+ ### `brainclaw locate <path>`
183
+
184
+ Resolve any path to the owning project.
185
+
186
+ This is especially useful for agents:
187
+
188
+ - start from a changed file
189
+ - ask Brainclaw which project owns it
190
+ - then request context for that project
191
+
192
+ ## MCP Surface
193
+
194
+ The MCP equivalents should mirror the CLI rather than invent a second addressing model.
195
+
196
+ Suggested tools:
197
+
198
+ - `bclaw_list_projects`
199
+ - `bclaw_get_project`
200
+ - `bclaw_get_project_context`
201
+ - `bclaw_list_project_children`
202
+ - `bclaw_list_project_ancestors`
203
+ - `bclaw_list_project_dependencies`
204
+ - `bclaw_locate_project`
205
+
206
+ All of them should accept `project_ref` as the primary selector.
207
+
208
+ `bclaw_get_context` may continue to exist, but multi-project agents should prefer `bclaw_get_project_context`.
209
+
210
+ ## Hierarchy Semantics
211
+
212
+ Hierarchy comes from project roots on disk.
213
+
214
+ Definitions:
215
+
216
+ - parent: nearest known project root above the current project
217
+ - child: known project whose nearest known parent is the current project
218
+ - ancestor: repeated parent chain
219
+
220
+ This is path-derived and deterministic.
221
+
222
+ ## Dependency Semantics
223
+
224
+ Dependencies are different from hierarchy.
225
+
226
+ Dependencies should support two sources:
227
+
228
+ - declared links in Brainclaw metadata
229
+ - detected links from repo/workspace tooling when reliable
230
+
231
+ Examples:
232
+
233
+ - monorepo package dependencies
234
+ - service depends on shared database project
235
+ - app depends on auth service
236
+
237
+ The first iteration should allow declared dependencies first.
238
+
239
+ Auto-detection can remain additive.
240
+
241
+ ## Storage Direction
242
+
243
+ This does not require replacing current project ids or store layout.
244
+
245
+ It requires a workspace project registry that can be rebuilt or refreshed non-destructively.
246
+
247
+ Likely location:
248
+
249
+ - workspace-level discovery inventory
250
+
251
+ Likely persisted fields:
252
+
253
+ - stable `project_ref`
254
+ - alias set
255
+ - hierarchy links
256
+ - declared dependency links
257
+
258
+ This should stay clearly separate from canonical memory items such as decisions and constraints.
259
+
260
+ ## Migration Strategy
261
+
262
+ The migration should be low-risk and incremental.
263
+
264
+ Phase 1:
265
+
266
+ - introduce `project_ref` in workspace discovery/registry
267
+ - expose `brainclaw projects`
268
+ - expose `brainclaw locate`
269
+
270
+ Phase 2:
271
+
272
+ - add `context --project <ref>`
273
+ - keep current `context --for <path>` behavior
274
+ - when `--project` is present, it wins over cwd heuristics
275
+
276
+ Phase 3:
277
+
278
+ - add `children` and `ancestors`
279
+ - add MCP equivalents
280
+
281
+ Phase 4:
282
+
283
+ - add declared dependencies
284
+ - optionally add auto-detection
285
+
286
+ ## Non-Goals
287
+
288
+ This design does not require:
289
+
290
+ - replacing `project_id`
291
+ - removing cwd-based commands
292
+ - making aliases mandatory
293
+ - making dependency inference perfect in the first release
294
+
295
+ ## Recommendation
296
+
297
+ The first implementation slice should be:
298
+
299
+ 1. add `project_ref` to workspace project discovery
300
+ 2. add a project resolver shared by CLI and MCP
301
+ 3. add `brainclaw projects`
302
+ 4. add `brainclaw context --project <ref>`
303
+ 5. add `brainclaw locate <path>`
304
+
305
+ That is enough to give agents a stable and simple project-addressing model without a risky refactor.
package/docs/cli.md CHANGED
@@ -918,7 +918,7 @@ Generate compact, prompt-ready context for agents.
918
918
 
919
919
  | Option | Description |
920
920
  |---|---|
921
- | `--for <path>` | Scope context to a file or path |
921
+ | `--for <path>` | Scope context to a file or path; in folder-mode workspaces, child project paths resolve the matching nested store automatically |
922
922
  | `--project <name>` | Filter by project |
923
923
  | `--agent <name>` | Filter by agent |
924
924
  | `--host <name>` | Filter by host |
@@ -938,6 +938,7 @@ Generate compact, prompt-ready context for agents.
938
938
 
939
939
  ```bash
940
940
  brainclaw context --for src/auth/routes.ts --digest
941
+ brainclaw context --for applications/lodestar/src/app.ts --json
941
942
  brainclaw context --json --max-chars 1200
942
943
  brainclaw context --explain
943
944
  brainclaw context --since-session --max-items 20