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.
- package/README.md +27 -11
- package/dist/cli.js +11 -0
- package/dist/commands/context.js +3 -1
- package/dist/commands/export.js +44 -0
- package/dist/commands/init.js +7 -6
- package/dist/commands/mcp.js +86 -5
- package/dist/commands/setup.js +1 -0
- package/dist/commands/uninstall.js +145 -0
- package/dist/core/agent-capability.js +196 -0
- package/dist/core/agent-context.js +24 -6
- package/dist/core/agent-integrations.js +3 -0
- package/dist/core/ai-agent-detection.js +34 -19
- package/dist/core/bootstrap.js +177 -0
- package/dist/core/context.js +47 -24
- package/dist/core/instruction-templates.js +308 -0
- package/dist/core/schema.js +10 -0
- package/dist/core/setup-flow.js +191 -0
- package/dist/core/setup-state.js +30 -1
- package/dist/core/store-resolution.js +58 -0
- package/docs/architecture/project-refs.md +305 -0
- package/docs/cli.md +2 -1
- package/docs/integrations/agents.md +102 -150
- package/docs/integrations/openclaw.md +98 -0
- package/docs/integrations/overview.md +73 -45
- package/docs/quickstart.md +43 -147
- package/package.json +1 -1
|
@@ -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
|
package/dist/core/setup-state.js
CHANGED
|
@@ -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
|