brainclaw 0.19.12 → 0.20.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 +42 -11
- package/dist/cli.js +55 -1
- package/dist/commands/claude-desktop-extension.js +18 -0
- package/dist/commands/context.js +3 -1
- package/dist/commands/doctor.js +12 -5
- package/dist/commands/export.js +44 -0
- package/dist/commands/init.js +22 -6
- package/dist/commands/list-surface-tasks.js +39 -0
- package/dist/commands/mcp.js +86 -5
- package/dist/commands/reconcile.js +138 -0
- package/dist/commands/setup.js +19 -0
- package/dist/commands/status.js +17 -12
- package/dist/commands/surface-task-resource.js +35 -0
- package/dist/commands/surface-task.js +57 -0
- package/dist/commands/uninstall.js +145 -0
- package/dist/commands/update-surface-task.js +30 -0
- package/dist/core/agent-capability.js +184 -0
- package/dist/core/agent-context.js +24 -6
- package/dist/core/agent-files.js +18 -18
- package/dist/core/ai-surface-inventory.js +321 -0
- package/dist/core/ai-surface-tasks.js +40 -0
- package/dist/core/bootstrap.js +177 -0
- package/dist/core/claude-desktop-extension.js +224 -0
- package/dist/core/context.js +47 -24
- package/dist/core/ids.js +1 -0
- package/dist/core/instruction-templates.js +308 -0
- package/dist/core/io.js +1 -0
- package/dist/core/machine-profile.js +7 -1
- package/dist/core/migration.js +3 -1
- package/dist/core/schema.js +34 -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/dist/core/workspace-projects.js +115 -0
- package/docs/architecture/project-refs.md +305 -0
- package/docs/cli.md +133 -1
- package/docs/integrations/agents.md +102 -150
- package/docs/integrations/overview.md +71 -45
- package/docs/quickstart.md +44 -111
- package/package.json +1 -1
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive instruction file templates — generates brainclaw section content
|
|
3
|
+
* based on the agent's capability profile (tier A/B/C).
|
|
4
|
+
*
|
|
5
|
+
* Core (static) vs Run (dynamic) separation:
|
|
6
|
+
* Core: protocol, "why brainclaw", constraints, instructions, estimation rule
|
|
7
|
+
* Run: traps, plans, decisions, claims, handoffs, runtime notes
|
|
8
|
+
*
|
|
9
|
+
* Tier A (MCP + hooks): lightweight — hooks inject run content automatically
|
|
10
|
+
* Tier B (MCP, no hooks): directive — includes top traps, forces MCP calls
|
|
11
|
+
* Tier C (no MCP): rich — includes plans, traps, decisions (only source)
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Render the brainclaw section content for an instruction file,
|
|
15
|
+
* adapted to the agent's capability profile.
|
|
16
|
+
*/
|
|
17
|
+
export function renderBrainclawSection(input) {
|
|
18
|
+
const { profile } = input;
|
|
19
|
+
switch (profile.templateTier) {
|
|
20
|
+
case 'A': return renderTierA(input);
|
|
21
|
+
case 'B': return renderTierB(input);
|
|
22
|
+
case 'C': return renderTierC(input);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// ─── Tier A: Full integration (MCP + hooks) ─────────────────────────────────
|
|
26
|
+
function renderTierA(input) {
|
|
27
|
+
const sections = [];
|
|
28
|
+
const included = [];
|
|
29
|
+
sections.push(renderHeader(input));
|
|
30
|
+
included.push('header');
|
|
31
|
+
sections.push(renderWhySection(input.profile));
|
|
32
|
+
included.push('why');
|
|
33
|
+
sections.push(renderProtocolTierA());
|
|
34
|
+
included.push('protocol');
|
|
35
|
+
sections.push(renderEstimationRule());
|
|
36
|
+
included.push('estimation');
|
|
37
|
+
sections.push(renderVersionCheckRule());
|
|
38
|
+
included.push('version-check');
|
|
39
|
+
const constraints = renderConstraints(input.state);
|
|
40
|
+
if (constraints) {
|
|
41
|
+
sections.push(constraints);
|
|
42
|
+
included.push('constraints');
|
|
43
|
+
}
|
|
44
|
+
const instructions = renderInstructions(input.resolvedInstructions);
|
|
45
|
+
if (instructions) {
|
|
46
|
+
sections.push(instructions);
|
|
47
|
+
included.push('instructions');
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
content: sections.join('\n\n'),
|
|
51
|
+
tier: 'A',
|
|
52
|
+
sectionsIncluded: included,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// ─── Tier B: Standard integration (MCP, no hooks) ───────────────────────────
|
|
56
|
+
function renderTierB(input) {
|
|
57
|
+
const sections = [];
|
|
58
|
+
const included = [];
|
|
59
|
+
sections.push(renderHeader(input));
|
|
60
|
+
included.push('header');
|
|
61
|
+
sections.push(renderWhySection(input.profile));
|
|
62
|
+
included.push('why');
|
|
63
|
+
sections.push(renderProtocolTierB());
|
|
64
|
+
included.push('protocol');
|
|
65
|
+
sections.push(renderEstimationRule());
|
|
66
|
+
included.push('estimation');
|
|
67
|
+
sections.push(renderVersionCheckRule());
|
|
68
|
+
included.push('version-check');
|
|
69
|
+
const constraints = renderConstraints(input.state);
|
|
70
|
+
if (constraints) {
|
|
71
|
+
sections.push(constraints);
|
|
72
|
+
included.push('constraints');
|
|
73
|
+
}
|
|
74
|
+
const instructions = renderInstructions(input.resolvedInstructions);
|
|
75
|
+
if (instructions) {
|
|
76
|
+
sections.push(instructions);
|
|
77
|
+
included.push('instructions');
|
|
78
|
+
}
|
|
79
|
+
// Tier B includes top traps statically (agent has no hooks to get them)
|
|
80
|
+
const traps = renderTopTraps(input.state, input.maxTraps ?? 5);
|
|
81
|
+
if (traps) {
|
|
82
|
+
sections.push(traps);
|
|
83
|
+
included.push('traps');
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
content: sections.join('\n\n'),
|
|
87
|
+
tier: 'B',
|
|
88
|
+
sectionsIncluded: included,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// ─── Tier C: Limited integration (no MCP) ────────────────────────────────────
|
|
92
|
+
function renderTierC(input) {
|
|
93
|
+
const sections = [];
|
|
94
|
+
const included = [];
|
|
95
|
+
sections.push(renderHeader(input));
|
|
96
|
+
included.push('header');
|
|
97
|
+
sections.push(renderWhySection(input.profile));
|
|
98
|
+
included.push('why');
|
|
99
|
+
sections.push(renderProtocolTierC(input.profile));
|
|
100
|
+
included.push('protocol');
|
|
101
|
+
sections.push(renderEstimationRule());
|
|
102
|
+
included.push('estimation');
|
|
103
|
+
const constraints = renderConstraints(input.state);
|
|
104
|
+
if (constraints) {
|
|
105
|
+
sections.push(constraints);
|
|
106
|
+
included.push('constraints');
|
|
107
|
+
}
|
|
108
|
+
const instructions = renderInstructions(input.resolvedInstructions);
|
|
109
|
+
if (instructions) {
|
|
110
|
+
sections.push(instructions);
|
|
111
|
+
included.push('instructions');
|
|
112
|
+
}
|
|
113
|
+
// Tier C includes everything statically — it's the only source of context
|
|
114
|
+
const traps = renderTopTraps(input.state, input.maxTraps ?? 10);
|
|
115
|
+
if (traps) {
|
|
116
|
+
sections.push(traps);
|
|
117
|
+
included.push('traps');
|
|
118
|
+
}
|
|
119
|
+
const plans = renderActivePlans(input.state, input.maxPlans ?? 10);
|
|
120
|
+
if (plans) {
|
|
121
|
+
sections.push(plans);
|
|
122
|
+
included.push('plans');
|
|
123
|
+
}
|
|
124
|
+
const decisions = renderRecentDecisions(input.state);
|
|
125
|
+
if (decisions) {
|
|
126
|
+
sections.push(decisions);
|
|
127
|
+
included.push('decisions');
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
content: sections.join('\n\n'),
|
|
131
|
+
tier: 'C',
|
|
132
|
+
sectionsIncluded: included,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// ─── Shared section renderers ────────────────────────────────────────────────
|
|
136
|
+
function renderHeader(input) {
|
|
137
|
+
return [
|
|
138
|
+
`> Managed by brainclaw v${input.brainclawVersion} — do not edit manually.`,
|
|
139
|
+
`> Regenerate: brainclaw export --format ${formatForAgent(input.profile.name)} --write`,
|
|
140
|
+
].join('\n');
|
|
141
|
+
}
|
|
142
|
+
function renderWhySection(profile) {
|
|
143
|
+
const lines = [
|
|
144
|
+
'## brainclaw — why this matters',
|
|
145
|
+
'',
|
|
146
|
+
'This project uses brainclaw for shared memory and multi-agent coordination.',
|
|
147
|
+
];
|
|
148
|
+
if (profile.hasMcp) {
|
|
149
|
+
lines.push('Other agents and developers work in this repo. Without brainclaw context:', '- You may edit files another agent is actively working on', '- You will miss known traps and architectural decisions', '- You will duplicate or contradict planned work');
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
lines.push('Project context is maintained in brainclaw memory. This includes:', '- Active constraints that must be respected', '- Known traps to avoid repeating mistakes', '- Plans and decisions for ongoing work coordination');
|
|
153
|
+
}
|
|
154
|
+
return lines.join('\n');
|
|
155
|
+
}
|
|
156
|
+
function renderProtocolTierA() {
|
|
157
|
+
return [
|
|
158
|
+
'## brainclaw — session protocol',
|
|
159
|
+
'',
|
|
160
|
+
'Brainclaw context is injected automatically via hooks at each prompt.',
|
|
161
|
+
'',
|
|
162
|
+
'1. Read the injected board state before acting',
|
|
163
|
+
'2. Call `bclaw_get_context(target)` for deeper memory on a specific scope',
|
|
164
|
+
'3. Call `bclaw_claim(scope)` before editing if other agents are active',
|
|
165
|
+
'4. Call `bclaw_session_end(auto_release: true)` when done',
|
|
166
|
+
].join('\n');
|
|
167
|
+
}
|
|
168
|
+
function renderProtocolTierB() {
|
|
169
|
+
return [
|
|
170
|
+
'## brainclaw — session protocol (REQUIRED)',
|
|
171
|
+
'',
|
|
172
|
+
'You MUST follow this protocol before any work in this project.',
|
|
173
|
+
'',
|
|
174
|
+
'1. Call `bclaw_session_start` — identify yourself and see the active board',
|
|
175
|
+
'2. Call `bclaw_get_context(target: "<file or dir>")` — load relevant memory',
|
|
176
|
+
'3. Call `bclaw_get_execution_context` — check for brainclaw updates',
|
|
177
|
+
'4. Check that no other agent has claimed your target scope',
|
|
178
|
+
'5. Call `bclaw_claim(scope)` before editing',
|
|
179
|
+
'6. Call `bclaw_write_note(text)` to record observations',
|
|
180
|
+
'7. Call `bclaw_session_end(auto_release: true)` when done',
|
|
181
|
+
].join('\n');
|
|
182
|
+
}
|
|
183
|
+
function renderProtocolTierC(profile) {
|
|
184
|
+
const lines = [
|
|
185
|
+
'## brainclaw — project coordination',
|
|
186
|
+
'',
|
|
187
|
+
];
|
|
188
|
+
if (profile.hasSkills) {
|
|
189
|
+
lines.push('Use the brainclaw-context skill to refresh project memory.', 'Trigger: "refresh project memory", "load brainclaw context", "inspect active plans"', '');
|
|
190
|
+
}
|
|
191
|
+
lines.push('Before working:', '- Review the constraints and active plans below', '- Check the known traps section for pitfalls in your scope', '', 'The sections below are regenerated from brainclaw memory.', 'For the freshest state, use the brainclaw skill or ask the developer to run `brainclaw context`.');
|
|
192
|
+
return lines.join('\n');
|
|
193
|
+
}
|
|
194
|
+
function renderEstimationRule() {
|
|
195
|
+
return [
|
|
196
|
+
'## brainclaw — plans and estimation',
|
|
197
|
+
'',
|
|
198
|
+
'When creating a plan or step, always estimate duration in minutes.',
|
|
199
|
+
'When completing, report actual effort. This calibrates future estimates.',
|
|
200
|
+
].join('\n');
|
|
201
|
+
}
|
|
202
|
+
function renderVersionCheckRule() {
|
|
203
|
+
return [
|
|
204
|
+
'## brainclaw — version check',
|
|
205
|
+
'',
|
|
206
|
+
'Call `bclaw_get_execution_context` at session start to check for brainclaw updates.',
|
|
207
|
+
'If a newer version is available, inform the developer and suggest updating.',
|
|
208
|
+
'Brainclaw updates may include new features, bug fixes, and improved coordination.',
|
|
209
|
+
].join('\n');
|
|
210
|
+
}
|
|
211
|
+
function renderConstraints(state) {
|
|
212
|
+
const active = state.active_constraints.filter((c) => c.status === 'active');
|
|
213
|
+
if (active.length === 0)
|
|
214
|
+
return undefined;
|
|
215
|
+
return [
|
|
216
|
+
'## brainclaw — active constraints',
|
|
217
|
+
'',
|
|
218
|
+
...active.map((c) => `- ${c.text}`),
|
|
219
|
+
].join('\n');
|
|
220
|
+
}
|
|
221
|
+
function renderInstructions(instructions) {
|
|
222
|
+
if (instructions.length === 0)
|
|
223
|
+
return undefined;
|
|
224
|
+
return [
|
|
225
|
+
'## brainclaw — active instructions',
|
|
226
|
+
'',
|
|
227
|
+
...instructions.map((text) => `- ${text}`),
|
|
228
|
+
].join('\n');
|
|
229
|
+
}
|
|
230
|
+
function renderTopTraps(state, limit) {
|
|
231
|
+
const traps = state.known_traps
|
|
232
|
+
.filter((t) => t.visibility === 'shared' && (!t.status || t.status === 'active') && !t.platform_scope)
|
|
233
|
+
.sort((a, b) => severityOrder(b.severity) - severityOrder(a.severity))
|
|
234
|
+
.slice(0, limit);
|
|
235
|
+
if (traps.length === 0)
|
|
236
|
+
return undefined;
|
|
237
|
+
return [
|
|
238
|
+
'## brainclaw — known traps',
|
|
239
|
+
'',
|
|
240
|
+
...traps.map((t) => `- [${t.severity}] ${t.text}`),
|
|
241
|
+
].join('\n');
|
|
242
|
+
}
|
|
243
|
+
function renderActivePlans(state, limit) {
|
|
244
|
+
const active = state.plan_items
|
|
245
|
+
.filter((p) => p.status === 'in_progress' || p.status === 'todo')
|
|
246
|
+
.sort((a, b) => {
|
|
247
|
+
if (a.status === 'in_progress' && b.status !== 'in_progress')
|
|
248
|
+
return -1;
|
|
249
|
+
if (b.status === 'in_progress' && a.status !== 'in_progress')
|
|
250
|
+
return 1;
|
|
251
|
+
return priorityOrder(b.priority) - priorityOrder(a.priority);
|
|
252
|
+
})
|
|
253
|
+
.slice(0, limit);
|
|
254
|
+
if (active.length === 0)
|
|
255
|
+
return undefined;
|
|
256
|
+
return [
|
|
257
|
+
'## brainclaw — active plans',
|
|
258
|
+
'',
|
|
259
|
+
...active.map((p) => {
|
|
260
|
+
const assignee = p.assignee ? ` (@${p.assignee})` : '';
|
|
261
|
+
return `- [${p.status}] ${p.text}${assignee}`;
|
|
262
|
+
}),
|
|
263
|
+
].join('\n');
|
|
264
|
+
}
|
|
265
|
+
function renderRecentDecisions(state) {
|
|
266
|
+
const decisions = state.recent_decisions.slice(-5);
|
|
267
|
+
if (decisions.length === 0)
|
|
268
|
+
return undefined;
|
|
269
|
+
return [
|
|
270
|
+
'## brainclaw — recent decisions',
|
|
271
|
+
'',
|
|
272
|
+
...decisions.map((d) => `- ${d.text}`),
|
|
273
|
+
].join('\n');
|
|
274
|
+
}
|
|
275
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
276
|
+
function severityOrder(severity) {
|
|
277
|
+
switch (severity) {
|
|
278
|
+
case 'high': return 3;
|
|
279
|
+
case 'medium': return 2;
|
|
280
|
+
case 'low': return 1;
|
|
281
|
+
default: return 0;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function priorityOrder(priority) {
|
|
285
|
+
switch (priority) {
|
|
286
|
+
case 'critical': return 4;
|
|
287
|
+
case 'high': return 3;
|
|
288
|
+
case 'medium': return 2;
|
|
289
|
+
case 'low': return 1;
|
|
290
|
+
default: return 0;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function formatForAgent(agentName) {
|
|
294
|
+
switch (agentName) {
|
|
295
|
+
case 'claude-code': return 'claude-md';
|
|
296
|
+
case 'cursor': return 'cursor-rules';
|
|
297
|
+
case 'github-copilot': return 'copilot-instructions';
|
|
298
|
+
case 'opencode':
|
|
299
|
+
case 'codex': return 'agents-md';
|
|
300
|
+
case 'antigravity': return 'gemini-md';
|
|
301
|
+
case 'windsurf': return 'windsurf';
|
|
302
|
+
case 'cline': return 'cline';
|
|
303
|
+
case 'roo': return 'roo';
|
|
304
|
+
case 'continue': return 'continue';
|
|
305
|
+
default: return 'agents-md';
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=instruction-templates.js.map
|
package/dist/core/io.js
CHANGED
|
@@ -30,6 +30,7 @@ const ENTITY_DIR_MAP = {
|
|
|
30
30
|
'runtime': 'coordination/runtime',
|
|
31
31
|
'runtime-hosts': 'coordination/runtime-hosts',
|
|
32
32
|
'runtime-private': 'coordination/runtime-private',
|
|
33
|
+
'surface-tasks': 'coordination/surface-tasks',
|
|
33
34
|
// discovery/ — Project entity: what's available
|
|
34
35
|
'bootstrap': 'discovery/bootstrap',
|
|
35
36
|
'bootstrap/seeds': 'discovery/bootstrap/seeds',
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import yaml from 'yaml';
|
|
6
6
|
import { MEMORY_DIR } from './io.js';
|
|
7
|
+
import { buildAiSurfaceInventory, renderAiSurfaceSummary } from './ai-surface-inventory.js';
|
|
7
8
|
// ── Detection Functions ────────────────────────────────────────────────────────
|
|
8
9
|
const TOOLCHAINS = [
|
|
9
10
|
{ name: 'node', command: 'node', versionArgs: ['--version'] },
|
|
@@ -227,7 +228,7 @@ function detectWslDistros() {
|
|
|
227
228
|
*/
|
|
228
229
|
export function buildMachineProfile() {
|
|
229
230
|
return {
|
|
230
|
-
schema_version:
|
|
231
|
+
schema_version: 2,
|
|
231
232
|
generated_at: new Date().toISOString(),
|
|
232
233
|
hostname: os.hostname(),
|
|
233
234
|
os_user: os.userInfo().username,
|
|
@@ -241,6 +242,7 @@ export function buildMachineProfile() {
|
|
|
241
242
|
ssh_keys: detectSshKeys(),
|
|
242
243
|
toolchains: detectToolchains(),
|
|
243
244
|
wsl_distros: detectWslDistros(),
|
|
245
|
+
ai_surfaces: buildAiSurfaceInventory(),
|
|
244
246
|
};
|
|
245
247
|
}
|
|
246
248
|
/**
|
|
@@ -283,6 +285,7 @@ export function loadMachineProfile() {
|
|
|
283
285
|
*/
|
|
284
286
|
export function renderMachineProfileSummary(profile) {
|
|
285
287
|
const lines = [];
|
|
288
|
+
const aiSurfaces = profile.ai_surfaces ?? [];
|
|
286
289
|
lines.push(`Machine: ${profile.hostname} (user: ${profile.os_user})`);
|
|
287
290
|
lines.push(`Home: ${profile.home_dir}`);
|
|
288
291
|
lines.push(`OS: ${profile.os_variant} (${profile.platform} ${profile.os_release}, ${profile.arch})`);
|
|
@@ -326,6 +329,9 @@ export function renderMachineProfileSummary(profile) {
|
|
|
326
329
|
lines.push(` - ${d.name}${d.default ? ' (default)' : ''}: ${nodeInfo}`);
|
|
327
330
|
}
|
|
328
331
|
}
|
|
332
|
+
if (aiSurfaces.length > 0) {
|
|
333
|
+
lines.push(...renderAiSurfaceSummary(aiSurfaces));
|
|
334
|
+
}
|
|
329
335
|
lines.push(`Profile generated: ${profile.generated_at}`);
|
|
330
336
|
return lines.join('\n');
|
|
331
337
|
}
|
package/dist/core/migration.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import YAML from 'yaml';
|
|
4
4
|
import { memoryDir, memoryPath, readFileSync, writeFileAtomic, resolveEntityDir } from './io.js';
|
|
5
|
-
import { BootstrapApplicationReceiptSchema, BootstrapImportPlanDocumentSchema, AgentIdentityDocumentSchema, BootstrapProfileDocumentSchema, CandidateSchema, ClaimSchema, ConfigSchema, MemorySeedDocumentSchema, ConstraintSchema, CurrentSessionStateSchema, DecisionSchema, HandoffSchema, InstructionEntrySchema, PlanItemSchema, ProjectIdentityDocumentSchema, RuntimeNoteSchema, SessionSnapshotSchema, TrapSchema, } from './schema.js';
|
|
5
|
+
import { BootstrapApplicationReceiptSchema, BootstrapImportPlanDocumentSchema, AgentIdentityDocumentSchema, BootstrapProfileDocumentSchema, CandidateSchema, ClaimSchema, ConfigSchema, MemorySeedDocumentSchema, ConstraintSchema, CurrentSessionStateSchema, DecisionSchema, HandoffSchema, InstructionEntrySchema, PlanItemSchema, ProjectIdentityDocumentSchema, RuntimeNoteSchema, SessionSnapshotSchema, TrapSchema, AiSurfaceTaskRequestSchema, } from './schema.js';
|
|
6
6
|
export class MigrationError extends Error {
|
|
7
7
|
kind;
|
|
8
8
|
documentType;
|
|
@@ -31,6 +31,7 @@ const registry = {
|
|
|
31
31
|
plan: createRegistryEntry(PlanItemSchema),
|
|
32
32
|
project_identity: createRegistryEntry(ProjectIdentityDocumentSchema),
|
|
33
33
|
runtime_note: createRegistryEntry(RuntimeNoteSchema),
|
|
34
|
+
ai_surface_task: createRegistryEntry(AiSurfaceTaskRequestSchema),
|
|
34
35
|
session_snapshot: createRegistryEntry(SessionSnapshotSchema),
|
|
35
36
|
trap: createRegistryEntry(TrapSchema),
|
|
36
37
|
};
|
|
@@ -164,6 +165,7 @@ export function scanMigrationStatus(cwd) {
|
|
|
164
165
|
collectDirectory(entries, resolveEntityDir('runtime', effectiveCwd, 'read'), 'runtime_note', true);
|
|
165
166
|
collectDirectory(entries, resolveEntityDir('runtime-hosts', effectiveCwd, 'read'), 'runtime_note', true);
|
|
166
167
|
collectDirectory(entries, resolveEntityDir('runtime-private', effectiveCwd, 'read'), 'runtime_note', true);
|
|
168
|
+
collectDirectory(entries, resolveEntityDir('surface-tasks', effectiveCwd, 'read'), 'ai_surface_task');
|
|
167
169
|
collectDirectory(entries, resolveEntityDir('instructions', effectiveCwd, 'read'), 'instruction');
|
|
168
170
|
collectDirectory(entries, path.join(resolveEntityDir('bootstrap', effectiveCwd, 'read'), 'seeds'), 'memory_seed');
|
|
169
171
|
collectDirectory(entries, resolveEntityDir('agents', effectiveCwd, 'read'), 'agent_identity');
|
package/dist/core/schema.js
CHANGED
|
@@ -96,6 +96,7 @@ export const TrapSchema = z.object({
|
|
|
96
96
|
visibility: MemoryVisibilitySchema.default('shared'),
|
|
97
97
|
host_id: z.string().optional(),
|
|
98
98
|
expires_at: z.string().optional(),
|
|
99
|
+
platform_scope: z.string().optional(),
|
|
99
100
|
});
|
|
100
101
|
export const HandoffSchema = z.object({
|
|
101
102
|
schema_version: z.number().int().positive().optional(),
|
|
@@ -344,6 +345,31 @@ export const RuntimeNoteSchema = z.object({
|
|
|
344
345
|
expires_at: z.string().optional(),
|
|
345
346
|
note_type: z.enum(['observation', 'session_start', 'session_end']).default('observation'),
|
|
346
347
|
});
|
|
348
|
+
// --- AI surface task request schemas ---
|
|
349
|
+
export const AiSurfaceTaskStatusSchema = z.enum(['queued', 'in_progress', 'completed', 'cancelled', 'failed']);
|
|
350
|
+
export const AiSurfaceTaskKindSchema = z.enum(['visual_asset', 'draft', 'summary', 'analysis', 'research', 'custom']);
|
|
351
|
+
export const AiSurfaceTaskRequestSchema = z.object({
|
|
352
|
+
schema_version: z.number().int().positive().optional(),
|
|
353
|
+
id: z.string(),
|
|
354
|
+
short_label: z.string().optional(),
|
|
355
|
+
title: z.string(),
|
|
356
|
+
instructions: z.string(),
|
|
357
|
+
target_surface: z.string(),
|
|
358
|
+
kind: AiSurfaceTaskKindSchema.default('custom'),
|
|
359
|
+
created_at: z.string(),
|
|
360
|
+
updated_at: z.string(),
|
|
361
|
+
author: z.string(),
|
|
362
|
+
author_id: z.string().optional(),
|
|
363
|
+
project_id: z.string().optional(),
|
|
364
|
+
session_id: z.string().optional(),
|
|
365
|
+
status: AiSurfaceTaskStatusSchema.default('queued'),
|
|
366
|
+
requested_outputs: z.array(z.string()).default([]),
|
|
367
|
+
related_paths: z.array(z.string()).optional(),
|
|
368
|
+
tags: z.array(z.string()).default([]),
|
|
369
|
+
claimed_at: z.string().optional(),
|
|
370
|
+
completed_at: z.string().optional(),
|
|
371
|
+
result_note: z.string().optional(),
|
|
372
|
+
});
|
|
347
373
|
// --- Runtime event schemas ---
|
|
348
374
|
export const RuntimeEventTypeSchema = z.enum([
|
|
349
375
|
'task_started',
|
|
@@ -459,6 +485,12 @@ export const MemorySeedSourceKindSchema = z.enum([
|
|
|
459
485
|
'machine',
|
|
460
486
|
'skill',
|
|
461
487
|
'mcp',
|
|
488
|
+
'ci_config',
|
|
489
|
+
'contributing',
|
|
490
|
+
'changelog',
|
|
491
|
+
'docker',
|
|
492
|
+
'env_example',
|
|
493
|
+
'adr',
|
|
462
494
|
]);
|
|
463
495
|
export const MemorySeedConfidenceSchema = z.enum(['low', 'medium', 'high']);
|
|
464
496
|
export const MemorySeedDocumentSchema = z.object({
|
|
@@ -601,9 +633,11 @@ export const AgentIntegrationSurfaceSchema = z.object({
|
|
|
601
633
|
location: AgentIntegrationLocationSchema,
|
|
602
634
|
path: z.string().optional(),
|
|
603
635
|
});
|
|
636
|
+
export const AgentIntegrationLevelSchema = z.enum(['full', 'standard', 'limited', 'custom']);
|
|
604
637
|
export const AgentIntegrationDeclarationSchema = z.object({
|
|
605
638
|
agent_name: AgentIntegrationNameSchema,
|
|
606
639
|
declaration_source: AgentIntegrationDeclarationSourceSchema.default('manual'),
|
|
640
|
+
level: AgentIntegrationLevelSchema.optional(),
|
|
607
641
|
surfaces: z.array(AgentIntegrationSurfaceSchema).default([]),
|
|
608
642
|
notes: z.string().optional(),
|
|
609
643
|
});
|
|
@@ -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
|