brainclaw 0.25.3 → 0.27.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/dist/cli.js +32 -2
- package/dist/commands/capability.js +21 -32
- package/dist/commands/check-events.js +36 -0
- package/dist/commands/discover.js +21 -0
- package/dist/commands/doctor.js +67 -11
- package/dist/commands/explore.js +7 -10
- package/dist/commands/hooks.js +33 -17
- package/dist/commands/mcp.js +380 -70
- package/dist/commands/migrate.js +75 -0
- package/dist/commands/tool.js +29 -39
- package/dist/core/agent-files.js +13 -0
- package/dist/core/context.js +102 -8
- package/dist/core/coordination.js +25 -0
- package/dist/core/io.js +2 -0
- package/dist/core/migration.js +3 -1
- package/dist/core/project-discovery.js +236 -0
- package/dist/core/registries.js +120 -0
- package/dist/core/schema.js +6 -1
- package/package.json +7 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { loadState, persistState } from '../core/state.js';
|
|
2
|
+
import { memoryExists } from '../core/io.js';
|
|
3
|
+
import { resolveTargetStore } from '../core/store-resolution.js';
|
|
4
|
+
import { appendAuditEntry } from '../core/audit.js';
|
|
5
|
+
import { resolveCurrentAgentName } from '../core/agent-registry.js';
|
|
6
|
+
export function runMigrate(options = {}) {
|
|
7
|
+
const cwd = options.cwd ?? process.cwd();
|
|
8
|
+
if (!memoryExists(cwd)) {
|
|
9
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
if (options.promoteMachineItems) {
|
|
13
|
+
promoteMachineItems(cwd, options.dryRun ?? false);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.log('Usage: brainclaw migrate --promote-machine-items [--dry-run]');
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log('Moves items tagged scope:machine from project store to user store (~/.brainclaw/).');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function promoteMachineItems(cwd, dryRun) {
|
|
22
|
+
const state = loadState(cwd);
|
|
23
|
+
const agent = resolveCurrentAgentName(cwd);
|
|
24
|
+
const machineConstraints = state.active_constraints.filter((c) => c.scope === 'machine');
|
|
25
|
+
const machineDecisions = state.recent_decisions.filter((d) => d.scope === 'machine');
|
|
26
|
+
const machineTraps = state.known_traps.filter((t) => t.scope === 'machine');
|
|
27
|
+
const total = machineConstraints.length + machineDecisions.length + machineTraps.length;
|
|
28
|
+
if (total === 0) {
|
|
29
|
+
console.log('No machine-scoped items found in project store.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
console.log(`Found ${total} machine-scoped item(s) in project store:\n`);
|
|
33
|
+
for (const c of machineConstraints)
|
|
34
|
+
console.log(` [constraint] ${c.id} — ${c.text.slice(0, 80)}`);
|
|
35
|
+
for (const d of machineDecisions)
|
|
36
|
+
console.log(` [decision] ${d.id} — ${d.text.slice(0, 80)}`);
|
|
37
|
+
for (const t of machineTraps)
|
|
38
|
+
console.log(` [trap] ${t.id} — ${t.text.slice(0, 80)}`);
|
|
39
|
+
if (dryRun) {
|
|
40
|
+
console.log(`\n(dry-run) Would move ${total} item(s) to user store.`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Resolve user store
|
|
44
|
+
let userCwd;
|
|
45
|
+
try {
|
|
46
|
+
userCwd = resolveTargetStore(cwd, 'user');
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
console.error('Error: cannot resolve user store. Run `brainclaw setup` first.');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const userState = loadState(userCwd);
|
|
53
|
+
// Move constraints
|
|
54
|
+
for (const c of machineConstraints) {
|
|
55
|
+
userState.active_constraints.push(c);
|
|
56
|
+
state.active_constraints = state.active_constraints.filter((x) => x.id !== c.id);
|
|
57
|
+
appendAuditEntry({ actor: agent, action: 'update', item_id: c.id, item_type: 'constraint', reason: 'promote to user store (machine scope)' }, cwd);
|
|
58
|
+
}
|
|
59
|
+
// Move decisions
|
|
60
|
+
for (const d of machineDecisions) {
|
|
61
|
+
userState.recent_decisions.push(d);
|
|
62
|
+
state.recent_decisions = state.recent_decisions.filter((x) => x.id !== d.id);
|
|
63
|
+
appendAuditEntry({ actor: agent, action: 'update', item_id: d.id, item_type: 'decision', reason: 'promote to user store (machine scope)' }, cwd);
|
|
64
|
+
}
|
|
65
|
+
// Move traps
|
|
66
|
+
for (const t of machineTraps) {
|
|
67
|
+
userState.known_traps.push(t);
|
|
68
|
+
state.known_traps = state.known_traps.filter((x) => x.id !== t.id);
|
|
69
|
+
appendAuditEntry({ actor: agent, action: 'update', item_id: t.id, item_type: 'trap', reason: 'promote to user store (machine scope)' }, cwd);
|
|
70
|
+
}
|
|
71
|
+
persistState(userState, userCwd);
|
|
72
|
+
persistState(state, cwd);
|
|
73
|
+
console.log(`\n✔ Promoted ${total} item(s) to user store (${userCwd})`);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=migrate.js.map
|
package/dist/commands/tool.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { loadState, persistState } from '../core/state.js';
|
|
2
1
|
import { resolveCurrentAgentName } from '../core/agent-registry.js';
|
|
3
2
|
import { memoryExists } from '../core/io.js';
|
|
4
|
-
import { generateIdWithLabel, nowISO } from '../core/ids.js';
|
|
5
3
|
import { loadConfig } from '../core/config.js';
|
|
6
4
|
import { scanText } from '../core/security.js';
|
|
7
5
|
import { validateCliInput } from '../core/input-validation.js';
|
|
8
6
|
import { resolveTargetStore } from '../core/store-resolution.js';
|
|
7
|
+
import { listTools, createTool } from '../core/registries.js';
|
|
9
8
|
export function runTool(subcommand, args, options = {}) {
|
|
10
9
|
const cwd = resolveTargetStore(options.cwd ?? process.cwd(), options.store ?? 'local');
|
|
11
10
|
if (!memoryExists(cwd)) {
|
|
@@ -46,10 +45,7 @@ export function runTool(subcommand, args, options = {}) {
|
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
function runToolList(cwd) {
|
|
49
|
-
const
|
|
50
|
-
const tools = state.recent_decisions
|
|
51
|
-
.filter((d) => d.tags.includes('tool'))
|
|
52
|
-
.map((d) => ({ id: d.id, name: d.text.split('\n')[0], type: d.tags.find((t) => t !== 'tool') }));
|
|
48
|
+
const tools = listTools(cwd);
|
|
53
49
|
if (tools.length === 0) {
|
|
54
50
|
console.log('No tools registered yet.');
|
|
55
51
|
return;
|
|
@@ -57,9 +53,7 @@ function runToolList(cwd) {
|
|
|
57
53
|
console.log(`\n${tools.length} tool(s):\n`);
|
|
58
54
|
tools.forEach((tool) => {
|
|
59
55
|
console.log(` [${tool.id}] ${tool.name}`);
|
|
60
|
-
|
|
61
|
-
console.log(` type: ${tool.type}`);
|
|
62
|
-
}
|
|
56
|
+
console.log(` type: ${tool.type}`);
|
|
63
57
|
});
|
|
64
58
|
console.log('');
|
|
65
59
|
}
|
|
@@ -74,50 +68,46 @@ function runToolAdd(name, description, options, cwd) {
|
|
|
74
68
|
process.exit(1);
|
|
75
69
|
}
|
|
76
70
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
short_label,
|
|
83
|
-
text: name,
|
|
84
|
-
created_at: nowISO(),
|
|
71
|
+
const tool = createTool({
|
|
72
|
+
name,
|
|
73
|
+
description,
|
|
74
|
+
type: options.type,
|
|
75
|
+
tags: options.tag,
|
|
85
76
|
author: options.author ?? resolveCurrentAgentName(cwd),
|
|
86
|
-
|
|
87
|
-
};
|
|
88
|
-
// For now, store as decision to avoid schema migration
|
|
89
|
-
// Will migrate to separate tool storage in v0.16
|
|
90
|
-
state.recent_decisions.push(entry);
|
|
91
|
-
persistState(state, cwd);
|
|
92
|
-
console.log(`✔ Tool added: [${id}] ${name}`);
|
|
93
|
-
console.log(' (Stored in decisions for now; will move to dedicated registry in v0.16)');
|
|
77
|
+
}, cwd);
|
|
78
|
+
console.log(`✔ Tool added: [${tool.id}] ${name} (${tool.type})`);
|
|
94
79
|
}
|
|
95
80
|
function runToolDescribe(toolId, cwd) {
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
if (!
|
|
81
|
+
const tools = listTools(cwd);
|
|
82
|
+
const tool = tools.find((t) => t.id === toolId || t.id.startsWith(toolId));
|
|
83
|
+
if (!tool) {
|
|
99
84
|
console.error(`Error: tool '${toolId}' not found`);
|
|
100
85
|
process.exit(1);
|
|
101
86
|
}
|
|
102
|
-
console.log(`\nTool: ${
|
|
103
|
-
console.log(`
|
|
104
|
-
console.log(`
|
|
105
|
-
console.log(`
|
|
106
|
-
console.log(`
|
|
87
|
+
console.log(`\nTool: ${tool.name}`);
|
|
88
|
+
console.log(`Description: ${tool.description}`);
|
|
89
|
+
console.log(`ID: ${tool.id}`);
|
|
90
|
+
console.log(`Type: ${tool.type}`);
|
|
91
|
+
console.log(`Author: ${tool.author}`);
|
|
92
|
+
console.log(`Created: ${tool.created_at}`);
|
|
93
|
+
if (tool.tags.length > 0) {
|
|
94
|
+
console.log(`Tags: ${tool.tags.join(', ')}`);
|
|
95
|
+
}
|
|
107
96
|
console.log('');
|
|
108
97
|
}
|
|
109
98
|
function runToolSearch(query, cwd) {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const results = tools.filter((tool) => tool.
|
|
113
|
-
tool.
|
|
99
|
+
const tools = listTools(cwd);
|
|
100
|
+
const queryLower = query.toLowerCase();
|
|
101
|
+
const results = tools.filter((tool) => tool.name.toLowerCase().includes(queryLower) ||
|
|
102
|
+
tool.description.toLowerCase().includes(queryLower) ||
|
|
103
|
+
tool.tags.some((tag) => tag.toLowerCase().includes(queryLower)));
|
|
114
104
|
if (results.length === 0) {
|
|
115
105
|
console.log(`No tools found matching '${query}'`);
|
|
116
106
|
return;
|
|
117
107
|
}
|
|
118
108
|
console.log(`\n${results.length} tool(s) matching '${query}':\n`);
|
|
119
|
-
results.forEach((
|
|
120
|
-
console.log(` [${
|
|
109
|
+
results.forEach((tool) => {
|
|
110
|
+
console.log(` [${tool.id}] ${tool.name} (${tool.type})`);
|
|
121
111
|
});
|
|
122
112
|
console.log('');
|
|
123
113
|
}
|
package/dist/core/agent-files.js
CHANGED
|
@@ -441,6 +441,12 @@ function buildCommandHookEntry(command) {
|
|
|
441
441
|
hooks: [{ type: 'command', command }],
|
|
442
442
|
};
|
|
443
443
|
}
|
|
444
|
+
function buildMatchedCommandHookEntry(matcher, command) {
|
|
445
|
+
return {
|
|
446
|
+
matcher,
|
|
447
|
+
hooks: [{ type: 'command', command }],
|
|
448
|
+
};
|
|
449
|
+
}
|
|
444
450
|
function containsCommandHook(entries, command) {
|
|
445
451
|
return entries.some((entry) => isJsonObject(entry) &&
|
|
446
452
|
Array.isArray(entry.hooks) &&
|
|
@@ -582,6 +588,13 @@ export function ensureClaudeCodeSettings(cwd) {
|
|
|
582
588
|
stopHooks.push(buildCommandHookEntry(stopCommand));
|
|
583
589
|
}
|
|
584
590
|
hooks.Stop = stopHooks;
|
|
591
|
+
// PostToolUse — check for unseen events after any brainclaw MCP tool call
|
|
592
|
+
const checkEventsCommand = 'npx brainclaw check-events 2>/dev/null';
|
|
593
|
+
const postToolHooks = Array.isArray(hooks.PostToolUse) ? [...hooks.PostToolUse] : [];
|
|
594
|
+
if (!containsCommandHook(postToolHooks, checkEventsCommand)) {
|
|
595
|
+
postToolHooks.push(buildMatchedCommandHookEntry('mcp__brainclaw__', checkEventsCommand));
|
|
596
|
+
}
|
|
597
|
+
hooks.PostToolUse = postToolHooks;
|
|
585
598
|
const { created, updated } = writeJsonFileIfChanged(filePath, {
|
|
586
599
|
...existing,
|
|
587
600
|
permissions,
|
package/dist/core/context.js
CHANGED
|
@@ -13,7 +13,7 @@ import { inferProjectFromTarget, loadInstructions, resolveInstructions } from '.
|
|
|
13
13
|
import { buildCurrentAgentResumeSummary, buildReputationRankingLookup } from './reputation.js';
|
|
14
14
|
import { loadState } from './state.js';
|
|
15
15
|
import { listCandidates } from './candidates.js';
|
|
16
|
-
import { listClaims } from './claims.js';
|
|
16
|
+
import { listClaims, isClaimExpired } from './claims.js';
|
|
17
17
|
import { listRuntimeNotes } from './runtime.js';
|
|
18
18
|
import { isTrapActive, listOperationalTraps } from './traps.js';
|
|
19
19
|
import { buildEstimationReport } from '../commands/estimation-report.js';
|
|
@@ -67,6 +67,7 @@ export function buildContext(options = {}) {
|
|
|
67
67
|
score: 0,
|
|
68
68
|
reasons: [],
|
|
69
69
|
extra: meta.join(', '),
|
|
70
|
+
provenance: { actor: plan.author },
|
|
70
71
|
});
|
|
71
72
|
}
|
|
72
73
|
for (const c of state.active_constraints) {
|
|
@@ -316,10 +317,36 @@ export function buildContext(options = {}) {
|
|
|
316
317
|
items.splice(0, items.length, ...items.filter((i) => allowed.includes(i.section)));
|
|
317
318
|
}
|
|
318
319
|
const queryTerms = tokenise(target);
|
|
320
|
+
// Agent-layer scoring: boost items related to the current agent's claims
|
|
321
|
+
const agentName = agent;
|
|
322
|
+
const agentId = currentAgentIdentity?.agent_id;
|
|
323
|
+
const allClaims = [...listClaims(contextCwd), ...parentStoreClaims];
|
|
324
|
+
const myClaims = allClaims.filter((c) => c.status === 'active' && (agentId ? c.agent_id === agentId : c.agent === agentName));
|
|
325
|
+
const myClaimScopes = myClaims.map((c) => c.scope);
|
|
326
|
+
const otherActiveClaims = allClaims.filter((c) => c.status === 'active' && !(agentId ? c.agent_id === agentId : c.agent === agentName));
|
|
319
327
|
for (const item of items) {
|
|
320
328
|
const relevance = computeRelevance(item, queryTerms, profile, target);
|
|
321
329
|
item.score = relevance.score;
|
|
322
330
|
item.reasons = relevance.reasons;
|
|
331
|
+
// Layer 1: boost items in my claimed scope (+6)
|
|
332
|
+
if (item.score >= 0 && myClaimScopes.length > 0 && item.related_paths) {
|
|
333
|
+
const overlaps = item.related_paths.some((p) => myClaimScopes.some((scope) => p.includes(scope) || scope.includes(p)));
|
|
334
|
+
if (overlaps) {
|
|
335
|
+
item.score += 6;
|
|
336
|
+
item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my claimed scope']);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Layer 1: boost plans assigned to me (+5)
|
|
340
|
+
if (item.score >= 0 && item.section === 'plan' && item.extra?.includes(`assignee:${agentName}`)) {
|
|
341
|
+
item.score += 5;
|
|
342
|
+
item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my assigned plan']);
|
|
343
|
+
}
|
|
344
|
+
// Layer 2: boost items authored by me (+2)
|
|
345
|
+
if (item.score >= 0 && item.provenance?.actor === agentName) {
|
|
346
|
+
item.score += 2;
|
|
347
|
+
item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my authored item']);
|
|
348
|
+
}
|
|
349
|
+
// Reputation signal
|
|
323
350
|
if (item.score >= 0 && item.provenance) {
|
|
324
351
|
const trustBonus = rankingLookup.getRankingBonus(item.provenance.actor_id, item.provenance.actor);
|
|
325
352
|
if (trustBonus > 0) {
|
|
@@ -327,6 +354,14 @@ export function buildContext(options = {}) {
|
|
|
327
354
|
item.reasons = uniqueReasons([...item.reasons, `reputation signal:+${trustBonus.toFixed(2)}`]);
|
|
328
355
|
}
|
|
329
356
|
}
|
|
357
|
+
// Layer 3: boost machine-scoped items for broader visibility (+1)
|
|
358
|
+
if (item.score >= 0) {
|
|
359
|
+
const itemScope = item.scope;
|
|
360
|
+
if (itemScope === 'machine') {
|
|
361
|
+
item.score += 1;
|
|
362
|
+
item.reasons = uniqueReasons([...item.reasons, 'machine-scope signal']);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
330
365
|
}
|
|
331
366
|
const ranked = items
|
|
332
367
|
.filter(item => item.score >= 0)
|
|
@@ -384,18 +419,15 @@ export function buildContext(options = {}) {
|
|
|
384
419
|
? summariseAgentTooling(rawAgentTooling)
|
|
385
420
|
: undefined;
|
|
386
421
|
// Build open_work: active claims and in_progress plans owned by the current agent
|
|
422
|
+
// Reuses myClaims computed in agent-layer scoring above
|
|
387
423
|
let openWork;
|
|
388
424
|
if (currentAgentIdentity || agent) {
|
|
389
|
-
const
|
|
390
|
-
const agentId = currentAgentIdentity?.agent_id;
|
|
391
|
-
const allClaims = [...listClaims(contextCwd), ...parentStoreClaims];
|
|
392
|
-
const activeClaims = allClaims.filter((c) => c.status === 'active' && (agentId ? c.agent_id === agentId : c.agent === agentName));
|
|
393
|
-
const claimPlanIds = new Set(activeClaims.map((c) => c.plan_id).filter(Boolean));
|
|
425
|
+
const claimPlanIds = new Set(myClaims.map((c) => c.plan_id).filter(Boolean));
|
|
394
426
|
const inProgressPlans = state.plan_items.filter((p) => p.status === 'in_progress' &&
|
|
395
427
|
(p.assignee === agentName || claimPlanIds.has(p.id)));
|
|
396
|
-
if (
|
|
428
|
+
if (myClaims.length > 0 || inProgressPlans.length > 0) {
|
|
397
429
|
openWork = {
|
|
398
|
-
active_claims:
|
|
430
|
+
active_claims: myClaims.map(({ id, scope, description, created_at, plan_id, expires_at }) => ({ id, scope, description, created_at, plan_id, expires_at })),
|
|
399
431
|
in_progress_plans: inProgressPlans.map(({ id, text, assignee }) => ({ id, text, assignee })),
|
|
400
432
|
};
|
|
401
433
|
}
|
|
@@ -471,6 +503,8 @@ export function buildContext(options = {}) {
|
|
|
471
503
|
}
|
|
472
504
|
})(),
|
|
473
505
|
cross_project_items: crossProjectItems.length > 0 ? crossProjectItems : undefined,
|
|
506
|
+
claim_conflicts: detectClaimConflicts(myClaims, otherActiveClaims),
|
|
507
|
+
workflow_hints: buildWorkflowHints(myClaims, openWork, state.plan_items),
|
|
474
508
|
selected,
|
|
475
509
|
};
|
|
476
510
|
if (options.digest) {
|
|
@@ -1232,4 +1266,64 @@ function applyCharBudget(items, maxChars) {
|
|
|
1232
1266
|
}
|
|
1233
1267
|
return selected;
|
|
1234
1268
|
}
|
|
1269
|
+
// --- Claim conflict detection ---
|
|
1270
|
+
function detectClaimConflicts(myClaims, otherClaims) {
|
|
1271
|
+
if (myClaims.length === 0 || otherClaims.length === 0)
|
|
1272
|
+
return undefined;
|
|
1273
|
+
const conflicts = [];
|
|
1274
|
+
for (const mine of myClaims) {
|
|
1275
|
+
for (const other of otherClaims) {
|
|
1276
|
+
if (isClaimExpired(other))
|
|
1277
|
+
continue;
|
|
1278
|
+
const overlap = scopesOverlap(mine.scope, other.scope);
|
|
1279
|
+
if (overlap) {
|
|
1280
|
+
conflicts.push({
|
|
1281
|
+
my_claim_id: mine.id,
|
|
1282
|
+
my_scope: mine.scope,
|
|
1283
|
+
other_claim_id: other.id,
|
|
1284
|
+
other_agent: other.agent,
|
|
1285
|
+
other_scope: other.scope,
|
|
1286
|
+
overlap_reason: overlap,
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
return conflicts.length > 0 ? conflicts : undefined;
|
|
1292
|
+
}
|
|
1293
|
+
function scopesOverlap(a, b) {
|
|
1294
|
+
const aParts = a.replace(/\\/g, '/').split(/\s+/);
|
|
1295
|
+
const bParts = b.replace(/\\/g, '/').split(/\s+/);
|
|
1296
|
+
for (const ap of aParts) {
|
|
1297
|
+
for (const bp of bParts) {
|
|
1298
|
+
if (ap === bp)
|
|
1299
|
+
return `exact match: ${ap}`;
|
|
1300
|
+
if (ap.startsWith(bp + '/') || bp.startsWith(ap + '/'))
|
|
1301
|
+
return `path overlap: ${ap} ↔ ${bp}`;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return null;
|
|
1305
|
+
}
|
|
1306
|
+
// --- Workflow hints ---
|
|
1307
|
+
function buildWorkflowHints(myClaims, openWork, plans) {
|
|
1308
|
+
const hints = [];
|
|
1309
|
+
// No claims — suggest claiming before editing
|
|
1310
|
+
if (myClaims.length === 0) {
|
|
1311
|
+
const todoPlans = plans.filter((p) => p.status === 'todo' && p.priority === 'high');
|
|
1312
|
+
if (todoPlans.length > 0) {
|
|
1313
|
+
hints.push(`${todoPlans.length} high-priority plan(s) available — consider claiming one with bclaw_claim`);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
// Multiple unclosed claims — suggest releasing finished ones
|
|
1317
|
+
if (myClaims.length > 2) {
|
|
1318
|
+
hints.push(`You have ${myClaims.length} active claims — consider releasing finished ones with bclaw_release_claim`);
|
|
1319
|
+
}
|
|
1320
|
+
// In-progress plans without claims
|
|
1321
|
+
if (openWork) {
|
|
1322
|
+
const unclaimedInProgress = openWork.in_progress_plans.filter((p) => !openWork.active_claims.some((c) => c.plan_id === p.id));
|
|
1323
|
+
if (unclaimedInProgress.length > 0) {
|
|
1324
|
+
hints.push(`${unclaimedInProgress.length} in-progress plan(s) without a claim — consider claiming the scope you're editing`);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
return hints.length > 0 ? hints : undefined;
|
|
1328
|
+
}
|
|
1235
1329
|
//# sourceMappingURL=context.js.map
|
|
@@ -81,6 +81,31 @@ export function buildCoordinationSnapshot(options = {}) {
|
|
|
81
81
|
resolved_instructions: instructions,
|
|
82
82
|
reputation_summary: reputationSummary,
|
|
83
83
|
agent_reputation: agentReputation,
|
|
84
|
+
other_agents: buildOtherAgentsSummary(filteredClaims, filteredNotes, agent),
|
|
84
85
|
};
|
|
85
86
|
}
|
|
87
|
+
function buildOtherAgentsSummary(claims, notes, currentAgent) {
|
|
88
|
+
const agentMap = new Map();
|
|
89
|
+
for (const claim of claims) {
|
|
90
|
+
if (claim.agent === currentAgent)
|
|
91
|
+
continue;
|
|
92
|
+
const existing = agentMap.get(claim.agent) ?? { name: claim.agent, claim_count: 0, scopes: [] };
|
|
93
|
+
existing.claim_count++;
|
|
94
|
+
existing.scopes.push(claim.scope);
|
|
95
|
+
if (!existing.last_active || claim.created_at > existing.last_active) {
|
|
96
|
+
existing.last_active = claim.created_at;
|
|
97
|
+
}
|
|
98
|
+
agentMap.set(claim.agent, existing);
|
|
99
|
+
}
|
|
100
|
+
for (const note of notes) {
|
|
101
|
+
if (note.agent === currentAgent)
|
|
102
|
+
continue;
|
|
103
|
+
const existing = agentMap.get(note.agent);
|
|
104
|
+
if (existing && (!existing.last_active || note.created_at > existing.last_active)) {
|
|
105
|
+
existing.last_active = note.created_at;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const result = [...agentMap.values()];
|
|
109
|
+
return result.length > 0 ? result : undefined;
|
|
110
|
+
}
|
|
86
111
|
//# sourceMappingURL=coordination.js.map
|
package/dist/core/io.js
CHANGED
|
@@ -35,6 +35,8 @@ const ENTITY_DIR_MAP = {
|
|
|
35
35
|
// discovery/ — Project entity: what's available
|
|
36
36
|
'bootstrap': 'discovery/bootstrap',
|
|
37
37
|
'bootstrap/seeds': 'discovery/bootstrap/seeds',
|
|
38
|
+
'capabilities': 'discovery/capabilities',
|
|
39
|
+
'tools': 'discovery/tools',
|
|
38
40
|
// agents/ — stays at top level (already entity-aligned)
|
|
39
41
|
'agents': 'agents',
|
|
40
42
|
};
|
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, AiSurfaceTaskRequestSchema, } from './schema.js';
|
|
5
|
+
import { BootstrapApplicationReceiptSchema, BootstrapImportPlanDocumentSchema, AgentIdentityDocumentSchema, BootstrapProfileDocumentSchema, CandidateSchema, ClaimSchema, ConfigSchema, MemorySeedDocumentSchema, ConstraintSchema, CurrentSessionStateSchema, DecisionSchema, HandoffSchema, InstructionEntrySchema, PlanItemSchema, ProjectIdentityDocumentSchema, RuntimeNoteSchema, SessionSnapshotSchema, TrapSchema, AiSurfaceTaskRequestSchema, ProjectCapabilitySchema, ProjectToolSchema, } from './schema.js';
|
|
6
6
|
export class MigrationError extends Error {
|
|
7
7
|
kind;
|
|
8
8
|
documentType;
|
|
@@ -33,6 +33,8 @@ const registry = {
|
|
|
33
33
|
runtime_note: createRegistryEntry(RuntimeNoteSchema),
|
|
34
34
|
ai_surface_task: createRegistryEntry(AiSurfaceTaskRequestSchema),
|
|
35
35
|
session_snapshot: createRegistryEntry(SessionSnapshotSchema),
|
|
36
|
+
capability: createRegistryEntry(ProjectCapabilitySchema),
|
|
37
|
+
tool: createRegistryEntry(ProjectToolSchema),
|
|
36
38
|
trap: createRegistryEntry(TrapSchema),
|
|
37
39
|
};
|
|
38
40
|
function createRegistryEntry(schema) {
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project discovery — unified workspace inventory that composes existing
|
|
3
|
+
* scan functions into a single structured profile.
|
|
4
|
+
*
|
|
5
|
+
* Boundary: discovery describes what exists in the workspace RIGHT NOW.
|
|
6
|
+
* It is NOT canonical memory (decisions, traps, plans). It is NOT
|
|
7
|
+
* machine profile (shells, SSH keys, WSL distros). It is the project-level
|
|
8
|
+
* answer to "what MCP servers, skills, hooks, instruction files, and
|
|
9
|
+
* agent integrations are available in this workspace?"
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { buildAgentToolingContext } from './agent-context.js';
|
|
14
|
+
import { assessAgentIntegrationReadiness } from './agent-integrations.js';
|
|
15
|
+
import { loadConfig } from './config.js';
|
|
16
|
+
import { memoryDir } from './io.js';
|
|
17
|
+
import { nowISO } from './ids.js';
|
|
18
|
+
// --- Native instruction file discovery (extracted from bootstrap.ts) ---
|
|
19
|
+
const NATIVE_INSTRUCTION_FILES = [
|
|
20
|
+
'AGENTS.md',
|
|
21
|
+
'CLAUDE.md',
|
|
22
|
+
'GEMINI.md',
|
|
23
|
+
'.windsurfrules',
|
|
24
|
+
'.github/copilot-instructions.md',
|
|
25
|
+
];
|
|
26
|
+
const NATIVE_INSTRUCTION_DIRS = [
|
|
27
|
+
'.cursor/rules',
|
|
28
|
+
'.roo/rules',
|
|
29
|
+
'.continue/rules',
|
|
30
|
+
'.clinerules',
|
|
31
|
+
];
|
|
32
|
+
// MCP config files that agents use
|
|
33
|
+
const MCP_CONFIG_FILES = [
|
|
34
|
+
'.mcp.json',
|
|
35
|
+
'opencode.json',
|
|
36
|
+
'.cursor/mcp.json',
|
|
37
|
+
'.roo/mcp.json',
|
|
38
|
+
'.continue/config.json',
|
|
39
|
+
];
|
|
40
|
+
// Hook config files
|
|
41
|
+
const HOOK_CONFIG_FILES = [
|
|
42
|
+
'.claude/settings.local.json',
|
|
43
|
+
'.cursor/rules/brainclaw-session.mdc',
|
|
44
|
+
];
|
|
45
|
+
export function buildProjectDiscovery(options = {}) {
|
|
46
|
+
const cwd = options.cwd ?? process.cwd();
|
|
47
|
+
const env = options.env ?? process.env;
|
|
48
|
+
// 1. Agent tooling (AGENTS.md, skills, MCP servers)
|
|
49
|
+
const agentTooling = buildAgentToolingContext({ cwd, env });
|
|
50
|
+
// 2. Native instruction files
|
|
51
|
+
const instructionFiles = discoverFiles(cwd, NATIVE_INSTRUCTION_FILES, NATIVE_INSTRUCTION_DIRS);
|
|
52
|
+
// 3. MCP config files
|
|
53
|
+
const mcpConfigs = discoverStaticFiles(cwd, MCP_CONFIG_FILES);
|
|
54
|
+
// 4. Hook config files
|
|
55
|
+
const hookConfigs = discoverStaticFiles(cwd, HOOK_CONFIG_FILES);
|
|
56
|
+
// 5. Integration readiness
|
|
57
|
+
let integrations = [];
|
|
58
|
+
try {
|
|
59
|
+
const config = loadConfig(cwd);
|
|
60
|
+
integrations = assessAgentIntegrationReadiness(config, cwd, env);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// config may not exist yet
|
|
64
|
+
}
|
|
65
|
+
const foundInstructions = instructionFiles.filter(f => f.exists);
|
|
66
|
+
const foundMcpConfigs = mcpConfigs.filter(f => f.exists);
|
|
67
|
+
const foundHookConfigs = hookConfigs.filter(f => f.exists);
|
|
68
|
+
return {
|
|
69
|
+
discovered_at: nowISO(),
|
|
70
|
+
workspace_root: cwd,
|
|
71
|
+
agent_tooling: agentTooling,
|
|
72
|
+
instruction_files: foundInstructions,
|
|
73
|
+
mcp_configs: foundMcpConfigs,
|
|
74
|
+
hook_configs: foundHookConfigs,
|
|
75
|
+
integrations,
|
|
76
|
+
summary: {
|
|
77
|
+
total_instruction_files: foundInstructions.length,
|
|
78
|
+
total_mcp_servers: agentTooling.mcp_servers.length,
|
|
79
|
+
total_skills: agentTooling.skills.length,
|
|
80
|
+
total_mcp_configs: foundMcpConfigs.length,
|
|
81
|
+
total_hook_configs: foundHookConfigs.length,
|
|
82
|
+
integrations_ready: integrations.filter(i => i.ready).length,
|
|
83
|
+
integrations_total: integrations.length,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// --- Persistence ---
|
|
88
|
+
const DISCOVERY_PROFILE_FILE = 'discovery-profile.json';
|
|
89
|
+
export function saveDiscoveryProfile(profile, cwd) {
|
|
90
|
+
const dir = path.join(memoryDir(cwd), 'discovery');
|
|
91
|
+
if (!fs.existsSync(dir)) {
|
|
92
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
const filePath = path.join(dir, DISCOVERY_PROFILE_FILE);
|
|
95
|
+
fs.writeFileSync(filePath, JSON.stringify(profile, null, 2), 'utf-8');
|
|
96
|
+
}
|
|
97
|
+
export function loadDiscoveryProfile(cwd) {
|
|
98
|
+
const filePath = path.join(memoryDir(cwd), 'discovery', DISCOVERY_PROFILE_FILE);
|
|
99
|
+
if (!fs.existsSync(filePath))
|
|
100
|
+
return undefined;
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// --- Rendering ---
|
|
109
|
+
export function renderDiscoverySummary(profile) {
|
|
110
|
+
const lines = [];
|
|
111
|
+
const s = profile.summary;
|
|
112
|
+
lines.push('# Project Discovery');
|
|
113
|
+
lines.push(`Discovered at: ${profile.discovered_at}`);
|
|
114
|
+
lines.push('');
|
|
115
|
+
// Instruction files
|
|
116
|
+
if (profile.instruction_files.length > 0) {
|
|
117
|
+
lines.push(`## Instruction Files (${s.total_instruction_files})`);
|
|
118
|
+
for (const f of profile.instruction_files) {
|
|
119
|
+
const managed = f.managed_by_brainclaw ? ' (managed)' : '';
|
|
120
|
+
lines.push(` ${f.path}${managed}`);
|
|
121
|
+
}
|
|
122
|
+
lines.push('');
|
|
123
|
+
}
|
|
124
|
+
// MCP configs
|
|
125
|
+
if (profile.mcp_configs.length > 0) {
|
|
126
|
+
lines.push(`## MCP Configs (${s.total_mcp_configs})`);
|
|
127
|
+
for (const f of profile.mcp_configs) {
|
|
128
|
+
lines.push(` ${f.path}`);
|
|
129
|
+
}
|
|
130
|
+
lines.push('');
|
|
131
|
+
}
|
|
132
|
+
// MCP servers (from agent tooling)
|
|
133
|
+
if (s.total_mcp_servers > 0) {
|
|
134
|
+
lines.push(`## MCP Servers (${s.total_mcp_servers})`);
|
|
135
|
+
for (const server of profile.agent_tooling.mcp_servers) {
|
|
136
|
+
lines.push(` ${server.name} (${server.transport}, ${server.availability})`);
|
|
137
|
+
}
|
|
138
|
+
lines.push('');
|
|
139
|
+
}
|
|
140
|
+
// Skills
|
|
141
|
+
if (s.total_skills > 0) {
|
|
142
|
+
lines.push(`## Skills (${s.total_skills})`);
|
|
143
|
+
for (const skill of profile.agent_tooling.skills.slice(0, 10)) {
|
|
144
|
+
lines.push(` ${skill.name}${skill.description ? `: ${skill.description}` : ''}`);
|
|
145
|
+
}
|
|
146
|
+
if (s.total_skills > 10) {
|
|
147
|
+
lines.push(` ... and ${s.total_skills - 10} more`);
|
|
148
|
+
}
|
|
149
|
+
lines.push('');
|
|
150
|
+
}
|
|
151
|
+
// Hook configs
|
|
152
|
+
if (profile.hook_configs.length > 0) {
|
|
153
|
+
lines.push(`## Hook Configs (${s.total_hook_configs})`);
|
|
154
|
+
for (const f of profile.hook_configs) {
|
|
155
|
+
lines.push(` ${f.path}`);
|
|
156
|
+
}
|
|
157
|
+
lines.push('');
|
|
158
|
+
}
|
|
159
|
+
// Integrations
|
|
160
|
+
if (profile.integrations.length > 0) {
|
|
161
|
+
lines.push(`## Agent Integrations (${s.integrations_ready}/${s.integrations_total} ready)`);
|
|
162
|
+
for (const integ of profile.integrations) {
|
|
163
|
+
const status = integ.ready ? '✔' : '✗';
|
|
164
|
+
const missing = integ.missing_surfaces.length > 0
|
|
165
|
+
? ` — missing: ${integ.missing_surfaces.map(s => s.kind).join(', ')}`
|
|
166
|
+
: '';
|
|
167
|
+
lines.push(` ${status} ${integ.agent_name}${missing}`);
|
|
168
|
+
}
|
|
169
|
+
lines.push('');
|
|
170
|
+
}
|
|
171
|
+
if (s.total_instruction_files + s.total_mcp_servers + s.total_skills === 0) {
|
|
172
|
+
lines.push('No integration surfaces detected.');
|
|
173
|
+
}
|
|
174
|
+
return lines.join('\n');
|
|
175
|
+
}
|
|
176
|
+
// --- Helpers ---
|
|
177
|
+
function discoverStaticFiles(cwd, files) {
|
|
178
|
+
const results = [];
|
|
179
|
+
for (const relativePath of files) {
|
|
180
|
+
const fullPath = path.join(cwd, relativePath);
|
|
181
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
182
|
+
const stat = fs.statSync(fullPath);
|
|
183
|
+
results.push({
|
|
184
|
+
path: relativePath,
|
|
185
|
+
exists: true,
|
|
186
|
+
size: stat.size,
|
|
187
|
+
managed_by_brainclaw: isManagedByBrainclaw(fullPath),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return results;
|
|
192
|
+
}
|
|
193
|
+
function discoverFiles(cwd, files, dirs) {
|
|
194
|
+
const results = [];
|
|
195
|
+
for (const relativePath of files) {
|
|
196
|
+
const fullPath = path.join(cwd, relativePath);
|
|
197
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
198
|
+
results.push({
|
|
199
|
+
path: relativePath,
|
|
200
|
+
exists: true,
|
|
201
|
+
size: fs.statSync(fullPath).size,
|
|
202
|
+
managed_by_brainclaw: isManagedByBrainclaw(fullPath),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
for (const relativeDir of dirs) {
|
|
207
|
+
const dir = path.join(cwd, relativeDir);
|
|
208
|
+
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory())
|
|
209
|
+
continue;
|
|
210
|
+
for (const entry of fs.readdirSync(dir).sort()) {
|
|
211
|
+
if (!/\.(md|mdc)$/i.test(entry))
|
|
212
|
+
continue;
|
|
213
|
+
const relativePath = path.posix.join(relativeDir.replace(/\\/g, '/'), entry);
|
|
214
|
+
const fullPath = path.join(cwd, relativePath);
|
|
215
|
+
results.push({
|
|
216
|
+
path: relativePath,
|
|
217
|
+
exists: true,
|
|
218
|
+
size: fs.statSync(fullPath).size,
|
|
219
|
+
managed_by_brainclaw: isManagedByBrainclaw(fullPath),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return results;
|
|
224
|
+
}
|
|
225
|
+
function isManagedByBrainclaw(filePath) {
|
|
226
|
+
try {
|
|
227
|
+
const content = fs.readFileSync(filePath, 'utf-8').slice(0, 200);
|
|
228
|
+
return content.includes('brainclaw') && (content.includes('Managed by brainclaw') ||
|
|
229
|
+
content.includes('BRAINCLAW_SECTION') ||
|
|
230
|
+
content.includes('brainclaw export'));
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=project-discovery.js.map
|