brainclaw 0.27.0 → 0.29.2
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 +14 -1
- package/dist/cli.js +11 -0
- package/dist/commands/mcp.js +49 -0
- package/dist/commands/switch.js +31 -8
- package/dist/commands/who.js +96 -0
- package/dist/core/agent-capability.js +55 -110
- package/dist/core/agent-integrations.js +17 -0
- package/dist/core/bootstrap.js +61 -10
- package/dist/core/context.js +73 -0
- package/dist/core/event-log.js +1 -0
- package/dist/core/identity.js +156 -17
- package/dist/core/repo-analysis.js +67 -0
- package/dist/core/schema.js +60 -16
- package/dist/core/store-resolution.js +14 -4
- package/docs/integrations/agents.md +10 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -66,6 +66,8 @@ If you are documenting or integrating an agent workflow, treat MCP as the primar
|
|
|
66
66
|
|
|
67
67
|
brainclaw is designed to sit alongside the coding agents teams are already using, not behind a separate hosted control plane.
|
|
68
68
|
|
|
69
|
+
### Code Agents
|
|
70
|
+
|
|
69
71
|
| Logo | Agent | Integration | What brainclaw configures |
|
|
70
72
|
|---|---|---|---|
|
|
71
73
|
| [](https://github.com/anthropics/claude-code) | **[Claude Code](https://github.com/anthropics/claude-code)** | Full | MCP + CLAUDE.md + hooks + permissions + /brainclaw command |
|
|
@@ -78,10 +80,21 @@ brainclaw is designed to sit alongside the coding agents teams are already using
|
|
|
78
80
|
| [](https://github.com/continuedev/continue) | **[Continue](https://github.com/continuedev/continue)** | Standard | MCP + .continue/rules/ |
|
|
79
81
|
| [](https://github.com/google-gemini/gemini-cli) | **[Antigravity / Gemini CLI](https://github.com/google-gemini/gemini-cli)** | Standard | MCP + GEMINI.md |
|
|
80
82
|
| [](https://github.com/features/copilot) | **[GitHub Copilot](https://github.com/features/copilot)** | Limited | copilot-instructions.md + brainclaw-context skill |
|
|
81
|
-
| [](https://github.com/openclaw/openclaw) | **[OpenClaw](https://github.com/openclaw/openclaw)** | Limited | brainclaw skill (SKILL.md) for structured project memory |
|
|
82
83
|
|
|
83
84
|
**Full** = MCP + hooks + auto-approve (context injected every prompt). **Standard** = MCP + instruction file (agent must call tools explicitly). **Limited** = skill-based (agent calls brainclaw CLI via skill instructions).
|
|
84
85
|
|
|
86
|
+
### Autonomous Agents
|
|
87
|
+
|
|
88
|
+
| Logo | Agent | Workflow | What brainclaw configures |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| [](https://github.com/openclaw/openclaw) | **[OpenClaw](https://github.com/openclaw/openclaw)** | Task-based | brainclaw skill (SKILL.md) for structured project memory |
|
|
91
|
+
| [](https://github.com/qwibitai/nanoclaw) | **[NanoClaw](https://github.com/qwibitai/nanoclaw)** | Task-based | brainclaw skill — messaging agent (WhatsApp, Telegram, Slack) |
|
|
92
|
+
| [](https://github.com/NVIDIA/NemoClaw) | **[NemoClaw](https://github.com/NVIDIA/NemoClaw)** | Task-based | brainclaw skill — NVIDIA enterprise agent stack |
|
|
93
|
+
| [](https://github.com/sipeed/picoclaw) | **[PicoClaw](https://github.com/sipeed/picoclaw)** | Scheduled | brainclaw skill — edge/IoT agent (Go, <10MB RAM) |
|
|
94
|
+
| [](https://github.com/zeroclaw-labs/zeroclaw) | **[ZeroClaw](https://github.com/zeroclaw-labs/zeroclaw)** | Task-based | brainclaw skill — ultra-lightweight Rust agent (20+ channels) |
|
|
95
|
+
|
|
96
|
+
Autonomous agents use brainclaw via CLI skills with `--profile compact` for short sessions and constrained resources.
|
|
97
|
+
|
|
85
98
|
brainclaw is most effective today when one agent works at a time in a given checkout and the next agent resumes from shared context, claims, and handoffs.
|
|
86
99
|
|
|
87
100
|
---
|
package/dist/cli.js
CHANGED
|
@@ -1157,6 +1157,17 @@ program
|
|
|
1157
1157
|
cwd: globalOpts.cwd,
|
|
1158
1158
|
});
|
|
1159
1159
|
});
|
|
1160
|
+
program
|
|
1161
|
+
.command('who')
|
|
1162
|
+
.description('Show active agent sessions on this workspace')
|
|
1163
|
+
.option('--json', 'Output as JSON')
|
|
1164
|
+
.option('--all', 'Include stale sessions')
|
|
1165
|
+
.option('--gc', 'Remove stale sessions')
|
|
1166
|
+
.action(async (options) => {
|
|
1167
|
+
const globalOpts = program.opts();
|
|
1168
|
+
const { runWho } = await import('./commands/who.js');
|
|
1169
|
+
runWho({ json: options.json, all: options.all, gc: options.gc, cwd: globalOpts.cwd });
|
|
1170
|
+
});
|
|
1160
1171
|
program.parseAsync(process.argv).catch((err) => {
|
|
1161
1172
|
console.error(err);
|
|
1162
1173
|
process.exit(1);
|
package/dist/commands/mcp.js
CHANGED
|
@@ -9,6 +9,7 @@ import { buildContext, renderContextMarkdown, renderContextPromptTemplate } from
|
|
|
9
9
|
import { buildExecutionContext, renderExecutionContextSummary } from '../core/execution-context.js';
|
|
10
10
|
import { checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice } from '../core/brainclaw-version.js';
|
|
11
11
|
import { loadConfig } from '../core/config.js';
|
|
12
|
+
import { loadAllSessions, gcStaleSessions } from '../core/identity.js';
|
|
12
13
|
import { loadState, persistState, saveState } from '../core/state.js';
|
|
13
14
|
import { memoryExists } from '../core/io.js';
|
|
14
15
|
import { generateCandidateIdWithLabel, listArchivedCandidates, listCandidates, saveCandidate } from '../core/candidates.js';
|
|
@@ -307,6 +308,17 @@ export const MCP_READ_TOOLS = [
|
|
|
307
308
|
},
|
|
308
309
|
},
|
|
309
310
|
},
|
|
311
|
+
{
|
|
312
|
+
name: 'bclaw_who',
|
|
313
|
+
description: 'List all active agent sessions on this workspace. Shows user, agent, active project, claims, and last activity for each session.',
|
|
314
|
+
inputSchema: {
|
|
315
|
+
type: 'object',
|
|
316
|
+
properties: {
|
|
317
|
+
all: { type: 'boolean', description: 'Include stale sessions (default: false).' },
|
|
318
|
+
gc: { type: 'boolean', description: 'Remove stale sessions and return count.' },
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
310
322
|
];
|
|
311
323
|
const MCP_WRITE_TOOLS = [
|
|
312
324
|
{
|
|
@@ -1767,6 +1779,42 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1767
1779
|
structuredContent: { agent: currentAgentName, conflicts, total: conflicts.length, schema_version: SCHEMA_VERSION },
|
|
1768
1780
|
};
|
|
1769
1781
|
}
|
|
1782
|
+
if (name === 'bclaw_who') {
|
|
1783
|
+
// loadAllSessions and gcStaleSessions imported at top of file
|
|
1784
|
+
const doGc = args.gc === true;
|
|
1785
|
+
const showAll = args.all === true;
|
|
1786
|
+
if (doGc) {
|
|
1787
|
+
const removed = gcStaleSessions(cwd);
|
|
1788
|
+
return {
|
|
1789
|
+
content: [{ type: 'text', text: `✔ Removed ${removed} stale session(s).` }],
|
|
1790
|
+
structuredContent: { gc: true, removed, schema_version: SCHEMA_VERSION },
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
const allSessions = loadAllSessions(cwd);
|
|
1794
|
+
const ttlMs = 4 * 60 * 60 * 1000;
|
|
1795
|
+
const now = Date.now();
|
|
1796
|
+
const sessions = showAll
|
|
1797
|
+
? allSessions
|
|
1798
|
+
: allSessions.filter((s) => (now - Date.parse(s.last_seen_at)) <= ttlMs);
|
|
1799
|
+
const activeClaims = listClaims(cwd).filter((c) => c.status === 'active');
|
|
1800
|
+
const output = sessions.map((s) => ({
|
|
1801
|
+
session_id: s.session_id,
|
|
1802
|
+
user: s.user ?? 'unknown',
|
|
1803
|
+
agent: s.agent,
|
|
1804
|
+
agent_id: s.agent_id,
|
|
1805
|
+
project: s.active_project?.name ?? s.active_project?.path ?? null,
|
|
1806
|
+
claims: activeClaims.filter((c) => c.agent_id === s.agent_id).length,
|
|
1807
|
+
last_seen_at: s.last_seen_at,
|
|
1808
|
+
stale: (now - Date.parse(s.last_seen_at)) > ttlMs,
|
|
1809
|
+
}));
|
|
1810
|
+
const lines = sessions.length === 0
|
|
1811
|
+
? 'No active sessions.'
|
|
1812
|
+
: output.map((s) => `${s.user} | ${s.agent} | ${s.project ?? '(root)'} | ${s.claims} claims | ${s.stale ? 'stale' : 'active'}`).join('\n');
|
|
1813
|
+
return {
|
|
1814
|
+
content: [{ type: 'text', text: lines }],
|
|
1815
|
+
structuredContent: { sessions: output, total: output.length, schema_version: SCHEMA_VERSION },
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1770
1818
|
if (name === 'bclaw_doctor') {
|
|
1771
1819
|
// Capture doctor JSON output by redirecting console.log
|
|
1772
1820
|
const captured = [];
|
|
@@ -2188,6 +2236,7 @@ export async function executeMcpToolCall(payload) {
|
|
|
2188
2236
|
id: claimId,
|
|
2189
2237
|
agent: identity.agent,
|
|
2190
2238
|
agent_id: identity.agent_id,
|
|
2239
|
+
user: process.env.USER || process.env.USERNAME || undefined,
|
|
2191
2240
|
project_id: identity.project_id,
|
|
2192
2241
|
host_id: identity.host_id,
|
|
2193
2242
|
session_id: identity.session_id,
|
package/dist/commands/switch.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { loadActiveProject, saveActiveProject, clearActiveProject } from '../core/active-project.js';
|
|
3
|
+
import { loadCurrentSession, saveCurrentSession } from '../core/identity.js';
|
|
3
4
|
import { memoryExists } from '../core/io.js';
|
|
4
5
|
import { resolveProjectRef, resolveWorkspaceRoot } from '../core/store-resolution.js';
|
|
5
6
|
import { scanNestedBrainclawProjects } from '../core/workspace-projects.js';
|
|
@@ -18,6 +19,11 @@ export function runSwitch(projectRef, options = {}) {
|
|
|
18
19
|
}
|
|
19
20
|
// --clear: remove active project
|
|
20
21
|
if (options.clear) {
|
|
22
|
+
const session = loadCurrentSession(cwd);
|
|
23
|
+
if (session?.active_project) {
|
|
24
|
+
const { active_project: _removed, ...rest } = session;
|
|
25
|
+
saveCurrentSession(rest, cwd);
|
|
26
|
+
}
|
|
21
27
|
clearActiveProject(wsRoot);
|
|
22
28
|
if (options.json) {
|
|
23
29
|
console.log(JSON.stringify({ cleared: true }));
|
|
@@ -47,18 +53,35 @@ export function runSwitch(projectRef, options = {}) {
|
|
|
47
53
|
catch {
|
|
48
54
|
// name is optional
|
|
49
55
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
const now = new Date().toISOString();
|
|
57
|
+
const session = loadCurrentSession(cwd);
|
|
58
|
+
const scopedToSession = options.session ?? !!session;
|
|
59
|
+
let scope;
|
|
60
|
+
if (scopedToSession && session) {
|
|
61
|
+
// Write to session state — only this agent sees this switch
|
|
62
|
+
saveCurrentSession({
|
|
63
|
+
...session,
|
|
64
|
+
active_project: { path: resolved, name: projectName, switched_at: now },
|
|
65
|
+
}, cwd);
|
|
66
|
+
scope = 'session';
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Fall back to global active-project.json
|
|
70
|
+
saveActiveProject(wsRoot, {
|
|
71
|
+
path: resolved,
|
|
72
|
+
name: projectName,
|
|
73
|
+
switched_at: now,
|
|
74
|
+
switched_by: process.env.BRAINCLAW_AGENT_NAME ?? process.env.USER ?? 'unknown',
|
|
75
|
+
});
|
|
76
|
+
scope = 'global';
|
|
77
|
+
}
|
|
56
78
|
if (options.json) {
|
|
57
|
-
console.log(JSON.stringify({ switched: true, path: resolved, name: projectName }));
|
|
79
|
+
console.log(JSON.stringify({ switched: true, path: resolved, name: projectName, scope }));
|
|
58
80
|
}
|
|
59
81
|
else {
|
|
60
82
|
const rel = path.relative(wsRoot, resolved) || '.';
|
|
61
|
-
|
|
83
|
+
const scopeHint = scope === 'session' ? ' (session-scoped)' : '';
|
|
84
|
+
console.log(`✔ Switched to ${projectName ? `"${projectName}" (${rel})` : rel}${scopeHint}`);
|
|
62
85
|
}
|
|
63
86
|
}
|
|
64
87
|
function showCurrent(wsRoot, json) {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { loadAllSessions, gcStaleSessions } from '../core/identity.js';
|
|
2
|
+
import { listClaims } from '../core/claims.js';
|
|
3
|
+
export function runWho(options = {}) {
|
|
4
|
+
const cwd = options.cwd ?? process.cwd();
|
|
5
|
+
if (options.gc) {
|
|
6
|
+
const removed = gcStaleSessions(cwd);
|
|
7
|
+
if (options.json) {
|
|
8
|
+
console.log(JSON.stringify({ gc: true, removed }));
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
console.log(`✔ Removed ${removed} stale session(s).`);
|
|
12
|
+
}
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const allSessions = loadAllSessions(cwd);
|
|
16
|
+
const ttlMs = 4 * 60 * 60 * 1000; // 4h default
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
const sessions = options.all
|
|
19
|
+
? allSessions
|
|
20
|
+
: allSessions.filter(s => (now - Date.parse(s.last_seen_at)) <= ttlMs);
|
|
21
|
+
const activeClaims = listClaims(cwd).filter(c => c.status === 'active');
|
|
22
|
+
const enriched = sessions.map(s => {
|
|
23
|
+
const age = now - Date.parse(s.last_seen_at);
|
|
24
|
+
const stale = age > ttlMs;
|
|
25
|
+
const dead = s.pid ? !isPidAlive(s.pid) : false;
|
|
26
|
+
const status = dead ? 'dead' : stale ? 'stale' : 'active';
|
|
27
|
+
return {
|
|
28
|
+
session_id: s.session_id,
|
|
29
|
+
user: s.user ?? 'unknown',
|
|
30
|
+
agent: s.agent,
|
|
31
|
+
agent_id: s.agent_id,
|
|
32
|
+
host_id: s.host_id,
|
|
33
|
+
project: s.active_project?.name ?? s.active_project?.path ?? null,
|
|
34
|
+
claims: activeClaims.filter(c => c.agent_id === s.agent_id).length,
|
|
35
|
+
started_at: s.started_at,
|
|
36
|
+
last_seen_at: s.last_seen_at,
|
|
37
|
+
status,
|
|
38
|
+
pid: s.pid,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
if (options.json) {
|
|
42
|
+
console.log(JSON.stringify({ sessions: enriched, total: enriched.length }, null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (enriched.length === 0) {
|
|
46
|
+
console.log('No active sessions.');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Table header
|
|
50
|
+
const header = ['USER', 'AGENT', 'PROJECT', 'CLAIMS', 'STATUS', 'LAST SEEN'];
|
|
51
|
+
const rows = [];
|
|
52
|
+
for (const s of enriched) {
|
|
53
|
+
const age = now - Date.parse(s.last_seen_at);
|
|
54
|
+
const project = s.project ?? '(workspace root)';
|
|
55
|
+
rows.push([
|
|
56
|
+
s.user,
|
|
57
|
+
s.agent,
|
|
58
|
+
project.length > 25 ? project.slice(0, 22) + '...' : project,
|
|
59
|
+
String(s.claims),
|
|
60
|
+
s.status,
|
|
61
|
+
formatAge(age),
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
// Calculate column widths
|
|
65
|
+
const widths = header.map((h, i) => Math.max(h.length, ...rows.map(r => r[i].length)));
|
|
66
|
+
console.log('Active sessions:\n');
|
|
67
|
+
console.log(' ' + header.map((h, i) => h.padEnd(widths[i])).join(' '));
|
|
68
|
+
console.log(' ' + widths.map(w => '─'.repeat(w)).join(' '));
|
|
69
|
+
for (const row of rows) {
|
|
70
|
+
console.log(' ' + row.map((cell, i) => cell.padEnd(widths[i])).join(' '));
|
|
71
|
+
}
|
|
72
|
+
console.log(`\n${enriched.length} session(s).`);
|
|
73
|
+
}
|
|
74
|
+
function isPidAlive(pid) {
|
|
75
|
+
try {
|
|
76
|
+
// process.kill(pid, 0) throws if process doesn't exist
|
|
77
|
+
process.kill(pid, 0);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function formatAge(ms) {
|
|
85
|
+
const minutes = Math.floor(ms / 60_000);
|
|
86
|
+
if (minutes < 1)
|
|
87
|
+
return 'just now';
|
|
88
|
+
if (minutes < 60)
|
|
89
|
+
return `${minutes}min ago`;
|
|
90
|
+
const hours = Math.floor(minutes / 60);
|
|
91
|
+
if (hours < 24)
|
|
92
|
+
return `${hours}h ago`;
|
|
93
|
+
const days = Math.floor(hours / 24);
|
|
94
|
+
return `${days}d ago`;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=who.js.map
|
|
@@ -9,137 +9,82 @@
|
|
|
9
9
|
* C (limited) — no MCP → rich static content (plans, traps, decisions)
|
|
10
10
|
*/
|
|
11
11
|
const PROFILES = {
|
|
12
|
+
// --- Code agents (interactive, IDE-driven) ---
|
|
12
13
|
'claude-code': {
|
|
13
|
-
name: 'claude-code',
|
|
14
|
-
hasMcp: true,
|
|
15
|
-
|
|
16
|
-
hasAutoApprove: true,
|
|
17
|
-
hasSkills: true,
|
|
18
|
-
hasRules: true,
|
|
19
|
-
instructionFile: 'CLAUDE.md',
|
|
20
|
-
sharedInstructionFile: true,
|
|
21
|
-
mcpConfigScope: 'both',
|
|
22
|
-
templateTier: 'A',
|
|
14
|
+
name: 'claude-code', category: 'code-agent', workflowModel: 'interactive',
|
|
15
|
+
hasMcp: true, hasHooks: true, hasAutoApprove: true, hasSkills: true, hasRules: true,
|
|
16
|
+
instructionFile: 'CLAUDE.md', sharedInstructionFile: true, mcpConfigScope: 'both', templateTier: 'A',
|
|
23
17
|
},
|
|
24
18
|
cursor: {
|
|
25
|
-
name: 'cursor',
|
|
26
|
-
hasMcp: true,
|
|
27
|
-
|
|
28
|
-
hasAutoApprove: false,
|
|
29
|
-
hasSkills: false,
|
|
30
|
-
hasRules: true,
|
|
31
|
-
instructionFile: '.cursor/rules/brainclaw.md',
|
|
32
|
-
sharedInstructionFile: false,
|
|
33
|
-
mcpConfigScope: 'machine',
|
|
34
|
-
templateTier: 'B',
|
|
19
|
+
name: 'cursor', category: 'code-agent', workflowModel: 'interactive',
|
|
20
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
|
|
21
|
+
instructionFile: '.cursor/rules/brainclaw.md', sharedInstructionFile: false, mcpConfigScope: 'machine', templateTier: 'B',
|
|
35
22
|
},
|
|
36
23
|
windsurf: {
|
|
37
|
-
name: 'windsurf',
|
|
38
|
-
hasMcp: true,
|
|
39
|
-
|
|
40
|
-
hasAutoApprove: false,
|
|
41
|
-
hasSkills: false,
|
|
42
|
-
hasRules: true,
|
|
43
|
-
instructionFile: '.windsurfrules',
|
|
44
|
-
sharedInstructionFile: true,
|
|
45
|
-
mcpConfigScope: 'machine',
|
|
46
|
-
templateTier: 'B',
|
|
24
|
+
name: 'windsurf', category: 'code-agent', workflowModel: 'interactive',
|
|
25
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
|
|
26
|
+
instructionFile: '.windsurfrules', sharedInstructionFile: true, mcpConfigScope: 'machine', templateTier: 'B',
|
|
47
27
|
},
|
|
48
28
|
cline: {
|
|
49
|
-
name: 'cline',
|
|
50
|
-
hasMcp: true,
|
|
51
|
-
|
|
52
|
-
hasAutoApprove: true,
|
|
53
|
-
hasSkills: false,
|
|
54
|
-
hasRules: true,
|
|
55
|
-
instructionFile: '.clinerules/brainclaw.md',
|
|
56
|
-
sharedInstructionFile: false,
|
|
57
|
-
mcpConfigScope: 'project',
|
|
58
|
-
templateTier: 'B',
|
|
29
|
+
name: 'cline', category: 'code-agent', workflowModel: 'interactive',
|
|
30
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: true, hasSkills: false, hasRules: true,
|
|
31
|
+
instructionFile: '.clinerules/brainclaw.md', sharedInstructionFile: false, mcpConfigScope: 'project', templateTier: 'B',
|
|
59
32
|
},
|
|
60
33
|
roo: {
|
|
61
|
-
name: 'roo',
|
|
62
|
-
hasMcp: true,
|
|
63
|
-
|
|
64
|
-
hasAutoApprove: true,
|
|
65
|
-
hasSkills: false,
|
|
66
|
-
hasRules: true,
|
|
67
|
-
instructionFile: '.roo/rules/brainclaw.md',
|
|
68
|
-
sharedInstructionFile: false,
|
|
69
|
-
mcpConfigScope: 'project',
|
|
70
|
-
templateTier: 'B',
|
|
34
|
+
name: 'roo', category: 'code-agent', workflowModel: 'interactive',
|
|
35
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: true, hasSkills: false, hasRules: true,
|
|
36
|
+
instructionFile: '.roo/rules/brainclaw.md', sharedInstructionFile: false, mcpConfigScope: 'project', templateTier: 'B',
|
|
71
37
|
},
|
|
72
38
|
continue: {
|
|
73
|
-
name: 'continue',
|
|
74
|
-
hasMcp: true,
|
|
75
|
-
|
|
76
|
-
hasAutoApprove: false,
|
|
77
|
-
hasSkills: false,
|
|
78
|
-
hasRules: true,
|
|
79
|
-
instructionFile: '.continue/rules/brainclaw.md',
|
|
80
|
-
sharedInstructionFile: false,
|
|
81
|
-
mcpConfigScope: 'both',
|
|
82
|
-
templateTier: 'B',
|
|
39
|
+
name: 'continue', category: 'code-agent', workflowModel: 'interactive',
|
|
40
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
|
|
41
|
+
instructionFile: '.continue/rules/brainclaw.md', sharedInstructionFile: false, mcpConfigScope: 'both', templateTier: 'B',
|
|
83
42
|
},
|
|
84
43
|
opencode: {
|
|
85
|
-
name: 'opencode',
|
|
86
|
-
hasMcp: true,
|
|
87
|
-
|
|
88
|
-
hasAutoApprove: false,
|
|
89
|
-
hasSkills: false,
|
|
90
|
-
hasRules: true,
|
|
91
|
-
instructionFile: 'AGENTS.md',
|
|
92
|
-
sharedInstructionFile: true,
|
|
93
|
-
mcpConfigScope: 'project',
|
|
94
|
-
templateTier: 'B',
|
|
44
|
+
name: 'opencode', category: 'code-agent', workflowModel: 'interactive',
|
|
45
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
|
|
46
|
+
instructionFile: 'AGENTS.md', sharedInstructionFile: true, mcpConfigScope: 'project', templateTier: 'B',
|
|
95
47
|
},
|
|
96
48
|
codex: {
|
|
97
|
-
name: 'codex',
|
|
98
|
-
hasMcp: true,
|
|
99
|
-
|
|
100
|
-
hasAutoApprove: false,
|
|
101
|
-
hasSkills: false,
|
|
102
|
-
hasRules: true,
|
|
103
|
-
instructionFile: 'AGENTS.md',
|
|
104
|
-
sharedInstructionFile: true,
|
|
105
|
-
mcpConfigScope: 'machine',
|
|
106
|
-
templateTier: 'B',
|
|
49
|
+
name: 'codex', category: 'code-agent', workflowModel: 'task-based',
|
|
50
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
|
|
51
|
+
instructionFile: 'AGENTS.md', sharedInstructionFile: true, mcpConfigScope: 'machine', templateTier: 'B',
|
|
107
52
|
},
|
|
108
53
|
antigravity: {
|
|
109
|
-
name: 'antigravity',
|
|
110
|
-
hasMcp: true,
|
|
111
|
-
|
|
112
|
-
hasAutoApprove: false,
|
|
113
|
-
hasSkills: false,
|
|
114
|
-
hasRules: true,
|
|
115
|
-
instructionFile: 'GEMINI.md',
|
|
116
|
-
sharedInstructionFile: true,
|
|
117
|
-
mcpConfigScope: 'machine',
|
|
118
|
-
templateTier: 'B',
|
|
54
|
+
name: 'antigravity', category: 'code-agent', workflowModel: 'interactive',
|
|
55
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
|
|
56
|
+
instructionFile: 'GEMINI.md', sharedInstructionFile: true, mcpConfigScope: 'machine', templateTier: 'B',
|
|
119
57
|
},
|
|
120
58
|
'github-copilot': {
|
|
121
|
-
name: 'github-copilot',
|
|
122
|
-
hasMcp: false,
|
|
123
|
-
|
|
124
|
-
hasAutoApprove: false,
|
|
125
|
-
hasSkills: true,
|
|
126
|
-
hasRules: true,
|
|
127
|
-
instructionFile: '.github/copilot-instructions.md',
|
|
128
|
-
sharedInstructionFile: true,
|
|
129
|
-
mcpConfigScope: 'none',
|
|
130
|
-
templateTier: 'C',
|
|
59
|
+
name: 'github-copilot', category: 'code-agent', workflowModel: 'interactive',
|
|
60
|
+
hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: true,
|
|
61
|
+
instructionFile: '.github/copilot-instructions.md', sharedInstructionFile: true, mcpConfigScope: 'none', templateTier: 'C',
|
|
131
62
|
},
|
|
63
|
+
// --- Autonomous agents (headless, task-based or scheduled) ---
|
|
132
64
|
openclaw: {
|
|
133
|
-
name: 'openclaw',
|
|
134
|
-
hasMcp: false,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
sharedInstructionFile: false,
|
|
141
|
-
|
|
142
|
-
|
|
65
|
+
name: 'openclaw', category: 'autonomous-agent', workflowModel: 'task-based',
|
|
66
|
+
hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: false,
|
|
67
|
+
instructionFile: 'skills/openclaw/SKILL.md', sharedInstructionFile: false, mcpConfigScope: 'none', templateTier: 'C',
|
|
68
|
+
},
|
|
69
|
+
nanoclaw: {
|
|
70
|
+
name: 'nanoclaw', category: 'autonomous-agent', workflowModel: 'task-based',
|
|
71
|
+
hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: false,
|
|
72
|
+
instructionFile: 'skills/nanoclaw/SKILL.md', sharedInstructionFile: false, mcpConfigScope: 'none', templateTier: 'C',
|
|
73
|
+
},
|
|
74
|
+
nemoclaw: {
|
|
75
|
+
name: 'nemoclaw', category: 'autonomous-agent', workflowModel: 'task-based',
|
|
76
|
+
hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: false,
|
|
77
|
+
instructionFile: 'skills/nemoclaw/SKILL.md', sharedInstructionFile: false, mcpConfigScope: 'none', templateTier: 'C',
|
|
78
|
+
},
|
|
79
|
+
picoclaw: {
|
|
80
|
+
name: 'picoclaw', category: 'autonomous-agent', workflowModel: 'scheduled',
|
|
81
|
+
hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: false,
|
|
82
|
+
instructionFile: 'skills/picoclaw/SKILL.md', sharedInstructionFile: false, mcpConfigScope: 'none', templateTier: 'C',
|
|
83
|
+
},
|
|
84
|
+
zeroclaw: {
|
|
85
|
+
name: 'zeroclaw', category: 'autonomous-agent', workflowModel: 'task-based',
|
|
86
|
+
hasMcp: false, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: false,
|
|
87
|
+
instructionFile: 'skills/zeroclaw/SKILL.md', sharedInstructionFile: false, mcpConfigScope: 'none', templateTier: 'C',
|
|
143
88
|
},
|
|
144
89
|
};
|
|
145
90
|
/**
|
|
@@ -12,6 +12,11 @@ const SUPPORTED_AGENT_INTEGRATION_NAMES = new Set([
|
|
|
12
12
|
'antigravity',
|
|
13
13
|
'continue',
|
|
14
14
|
'roo',
|
|
15
|
+
'openclaw',
|
|
16
|
+
'nanoclaw',
|
|
17
|
+
'nemoclaw',
|
|
18
|
+
'picoclaw',
|
|
19
|
+
'zeroclaw',
|
|
15
20
|
]);
|
|
16
21
|
const DEFAULT_SURFACES = {
|
|
17
22
|
'github-copilot': [
|
|
@@ -60,6 +65,18 @@ const DEFAULT_SURFACES = {
|
|
|
60
65
|
'openclaw': [
|
|
61
66
|
{ kind: 'skill', location: 'machine', path: '.openclaw/workspace/skills/brainclaw/SKILL.md' },
|
|
62
67
|
],
|
|
68
|
+
'nanoclaw': [
|
|
69
|
+
{ kind: 'skill', location: 'workspace', path: 'skills/nanoclaw/SKILL.md' },
|
|
70
|
+
],
|
|
71
|
+
'nemoclaw': [
|
|
72
|
+
{ kind: 'skill', location: 'workspace', path: 'skills/nemoclaw/SKILL.md' },
|
|
73
|
+
],
|
|
74
|
+
'picoclaw': [
|
|
75
|
+
{ kind: 'skill', location: 'workspace', path: 'skills/picoclaw/SKILL.md' },
|
|
76
|
+
],
|
|
77
|
+
'zeroclaw': [
|
|
78
|
+
{ kind: 'skill', location: 'workspace', path: 'skills/zeroclaw/SKILL.md' },
|
|
79
|
+
],
|
|
63
80
|
};
|
|
64
81
|
function mergeSurfaces(current, next) {
|
|
65
82
|
const merged = [...current];
|
package/dist/core/bootstrap.js
CHANGED
|
@@ -8,7 +8,7 @@ import { resolveEntityDir } from './io.js';
|
|
|
8
8
|
import { mutate } from './mutation-pipeline.js';
|
|
9
9
|
import { BootstrapApplicationReceiptSchema, BootstrapInterviewAnswerSchema, BootstrapInterviewPlanSchema, BootstrapInterviewQuestionSchema, BootstrapImportPlanDocumentSchema, BootstrapProfileDocumentSchema, BootstrapSuggestionDocumentSchema, MemorySeedDocumentSchema, } from './schema.js';
|
|
10
10
|
import { loadVersionedJsonFile, saveVersionedJsonFile } from './migration.js';
|
|
11
|
-
import { analyzeRepository } from './repo-analysis.js';
|
|
11
|
+
import { analyzeRepository, findNestedAgentsFiles } from './repo-analysis.js';
|
|
12
12
|
import { buildExecutionContext, compactExecutionContext } from './execution-context.js';
|
|
13
13
|
import { buildAgentToolingContext } from './agent-context.js';
|
|
14
14
|
import { createInstruction, loadInstructions, saveInstruction } from './instructions.js';
|
|
@@ -101,6 +101,7 @@ export function runBootstrapProfile(options = {}) {
|
|
|
101
101
|
importPlan,
|
|
102
102
|
lastApplication,
|
|
103
103
|
reusedProfile: false,
|
|
104
|
+
subProjects: artifacts.subProjects,
|
|
104
105
|
};
|
|
105
106
|
}
|
|
106
107
|
export function listBootstrapSeeds(cwd) {
|
|
@@ -183,6 +184,18 @@ export function renderBootstrapSummary(result) {
|
|
|
183
184
|
if (result.lastApplication && !result.lastApplication.uninstalled_at) {
|
|
184
185
|
lines.push(`Last bootstrap import: ${result.lastApplication.managed_artifacts.length} managed artifact(s) from ${result.lastApplication.applied_at}`);
|
|
185
186
|
}
|
|
187
|
+
if (result.subProjects && result.subProjects.length > 0) {
|
|
188
|
+
lines.push('');
|
|
189
|
+
lines.push(`Sub-projects discovered (${result.subProjects.length}):`);
|
|
190
|
+
for (const sp of result.subProjects.slice(0, 20)) {
|
|
191
|
+
lines.push(` ${sp}`);
|
|
192
|
+
}
|
|
193
|
+
if (result.subProjects.length > 20) {
|
|
194
|
+
lines.push(` ... and ${result.subProjects.length - 20} more`);
|
|
195
|
+
}
|
|
196
|
+
lines.push('');
|
|
197
|
+
lines.push('Use: brainclaw bootstrap --for <sub-project-path> --refresh');
|
|
198
|
+
}
|
|
186
199
|
if (result.importPlan.suggestions.length > 0) {
|
|
187
200
|
lines.push('');
|
|
188
201
|
lines.push('Import proposal:');
|
|
@@ -231,14 +244,19 @@ export function renderBootstrapInterview(result, audience = 'any') {
|
|
|
231
244
|
function buildBootstrapArtifacts(input) {
|
|
232
245
|
const sourcesScanned = [];
|
|
233
246
|
const seeds = [];
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
247
|
+
// When target is an absolute directory path, use it as the scan root
|
|
248
|
+
// so that instruction files, README, and AGENTS.md are discovered from
|
|
249
|
+
// the target scope — not from the workspace root. This fixes bootstrap
|
|
250
|
+
// returning wrong signals for monorepo sub-projects.
|
|
251
|
+
const scanRoot = resolveBootstrapScanRoot(input.cwd, input.target);
|
|
252
|
+
const workspace = classifyWorkspace(scanRoot);
|
|
253
|
+
const nativeInstructionFiles = discoverNativeInstructionFiles(scanRoot);
|
|
254
|
+
const readmePath = findFirstExisting(scanRoot, README_CANDIDATES);
|
|
237
255
|
if (readmePath) {
|
|
238
256
|
sourcesScanned.push('README');
|
|
239
257
|
seeds.push(...extractReadmeSeeds(readmePath, input.target));
|
|
240
258
|
}
|
|
241
|
-
const agentsPath = path.join(
|
|
259
|
+
const agentsPath = path.join(scanRoot, 'AGENTS.md');
|
|
242
260
|
const agentsPresent = fs.existsSync(agentsPath);
|
|
243
261
|
if (agentsPresent) {
|
|
244
262
|
sourcesScanned.push('AGENTS.md');
|
|
@@ -246,9 +264,9 @@ function buildBootstrapArtifacts(input) {
|
|
|
246
264
|
}
|
|
247
265
|
if (nativeInstructionFiles.length > 0) {
|
|
248
266
|
sourcesScanned.push('native_instructions');
|
|
249
|
-
seeds.push(...extractNativeInstructionSeeds(nativeInstructionFiles.map((relativePath) => path.join(
|
|
267
|
+
seeds.push(...extractNativeInstructionSeeds(nativeInstructionFiles.map((relativePath) => path.join(scanRoot, relativePath)), scanRoot, input.target));
|
|
250
268
|
}
|
|
251
|
-
const manifestResult = extractManifestSeeds(
|
|
269
|
+
const manifestResult = extractManifestSeeds(scanRoot, input.target);
|
|
252
270
|
if (manifestResult.seeds.length > 0) {
|
|
253
271
|
sourcesScanned.push(...manifestResult.sources);
|
|
254
272
|
seeds.push(...manifestResult.seeds);
|
|
@@ -265,16 +283,16 @@ function buildBootstrapArtifacts(input) {
|
|
|
265
283
|
sourcesScanned.push('local_mcp');
|
|
266
284
|
seeds.push(...extractMcpSeeds(agentTooling.mcp_servers, input.target));
|
|
267
285
|
}
|
|
268
|
-
const repoAnalysis = analyzeRepository(
|
|
286
|
+
const repoAnalysis = analyzeRepository(scanRoot);
|
|
269
287
|
sourcesScanned.push('repo-analysis');
|
|
270
288
|
seeds.push(...extractRepoAnalysisSeeds(repoAnalysis, input.target));
|
|
271
289
|
// Additional brownfield sources (step 12)
|
|
272
|
-
const additionalSeeds = extractAdditionalBrownfieldSeeds(
|
|
290
|
+
const additionalSeeds = extractAdditionalBrownfieldSeeds(scanRoot, input.target);
|
|
273
291
|
if (additionalSeeds.seeds.length > 0) {
|
|
274
292
|
sourcesScanned.push(...additionalSeeds.sources);
|
|
275
293
|
seeds.push(...additionalSeeds.seeds);
|
|
276
294
|
}
|
|
277
|
-
const gitProbe = probeGit(
|
|
295
|
+
const gitProbe = probeGit(scanRoot, input.target);
|
|
278
296
|
if (gitProbe.available) {
|
|
279
297
|
sourcesScanned.push('git');
|
|
280
298
|
seeds.push(...gitProbe.hotspotSeeds);
|
|
@@ -342,6 +360,13 @@ function buildBootstrapArtifacts(input) {
|
|
|
342
360
|
schema_version: DERIVED_SCHEMA_VERSION,
|
|
343
361
|
})),
|
|
344
362
|
importPlan,
|
|
363
|
+
// For multi-project workspaces without a target, list discovered sub-projects
|
|
364
|
+
subProjects: (!input.target && repoAnalysis.recommendedMode === 'multi-project')
|
|
365
|
+
? findNestedAgentsFiles(scanRoot, 8)
|
|
366
|
+
.filter((p) => p !== 'AGENTS.md') // exclude root AGENTS.md
|
|
367
|
+
.map((p) => path.dirname(p))
|
|
368
|
+
.filter((d) => d !== '.')
|
|
369
|
+
: undefined,
|
|
345
370
|
};
|
|
346
371
|
}
|
|
347
372
|
function extractReadmeSeeds(filepath, target) {
|
|
@@ -846,6 +871,9 @@ function buildSummary(input) {
|
|
|
846
871
|
parts.push(`Onboarding mode: ${input.onboardingMode}.`);
|
|
847
872
|
parts.push(`Confidence: ${input.confidence}.`);
|
|
848
873
|
parts.push(`Repository mode looks ${input.repoAnalysis.recommendedMode}.`);
|
|
874
|
+
if (input.repoAnalysis.recommendedMode === 'multi-project' && !input.target) {
|
|
875
|
+
parts.push('This is a multi-project workspace — use --for <path> to bootstrap a specific sub-project.');
|
|
876
|
+
}
|
|
849
877
|
if (input.agentsPresent) {
|
|
850
878
|
parts.push('AGENTS.md detected and summarized.');
|
|
851
879
|
}
|
|
@@ -1639,6 +1667,29 @@ function normalizeTarget(target) {
|
|
|
1639
1667
|
const trimmed = target?.trim();
|
|
1640
1668
|
return trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
1641
1669
|
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Resolve where to scan for project files (README, AGENTS.md, manifests).
|
|
1672
|
+
* If target is an absolute directory path, scan from there.
|
|
1673
|
+
* Otherwise fall back to cwd (the workspace/store root).
|
|
1674
|
+
*/
|
|
1675
|
+
function resolveBootstrapScanRoot(cwd, target) {
|
|
1676
|
+
if (!target)
|
|
1677
|
+
return cwd;
|
|
1678
|
+
const resolved = path.isAbsolute(target) ? target : path.resolve(cwd, target);
|
|
1679
|
+
try {
|
|
1680
|
+
if (fs.statSync(resolved).isDirectory())
|
|
1681
|
+
return resolved;
|
|
1682
|
+
}
|
|
1683
|
+
catch { /* not a directory or doesn't exist */ }
|
|
1684
|
+
// Target is a file path or glob — use its parent directory if it exists
|
|
1685
|
+
const parent = path.dirname(resolved);
|
|
1686
|
+
try {
|
|
1687
|
+
if (fs.statSync(parent).isDirectory())
|
|
1688
|
+
return parent;
|
|
1689
|
+
}
|
|
1690
|
+
catch { /* fall back to cwd */ }
|
|
1691
|
+
return cwd;
|
|
1692
|
+
}
|
|
1642
1693
|
// ─── Step 12: Additional brownfield sources ──────────────────────────────────
|
|
1643
1694
|
const CI_WORKFLOW_DIRS = ['.github/workflows', '.gitlab'];
|
|
1644
1695
|
const CI_FILES = ['.gitlab-ci.yml', 'Jenkinsfile', '.circleci/config.yml'];
|