brainclaw 0.23.1 → 0.25.3
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 +1 -0
- package/dist/commands/accept.js +5 -2
- package/dist/commands/claim.js +7 -5
- package/dist/commands/context-diff.js +62 -4
- package/dist/commands/export.js +40 -2
- package/dist/commands/init.js +41 -0
- package/dist/commands/instruction.js +5 -4
- package/dist/commands/mcp.js +64 -5
- package/dist/commands/prune.js +5 -4
- package/dist/commands/reflect.js +2 -0
- package/dist/commands/release-claim.js +4 -4
- package/dist/commands/release-claims.js +5 -4
- package/dist/commands/runtime-note.js +1 -0
- package/dist/core/ai-surface-tasks.js +3 -2
- package/dist/core/audit.js +3 -2
- package/dist/core/bootstrap.js +7 -6
- package/dist/core/candidates.js +4 -3
- package/dist/core/claims.js +3 -2
- package/dist/core/instructions.js +3 -2
- package/dist/core/io.js +1 -0
- package/dist/core/lock.js +2 -2
- package/dist/core/markdown.js +18 -0
- package/dist/core/mutation-pipeline.js +39 -0
- package/dist/core/runtime.js +4 -3
- package/dist/core/schema.js +8 -0
- package/dist/core/state.js +5 -4
- package/docs/cli.md +7 -5
- package/docs/concepts/memory.md +4 -3
- package/docs/concepts/plans-and-claims.md +10 -0
- package/docs/integrations/agents.md +2 -1
- package/docs/integrations/claude-code.md +1 -1
- package/docs/integrations/codex.md +1 -1
- package/docs/integrations/copilot.md +1 -1
- package/docs/integrations/cursor.md +1 -1
- package/docs/integrations/mcp.md +16 -6
- package/docs/integrations/overview.md +2 -2
- package/docs/quickstart.md +3 -1
- package/docs/storage.md +51 -24
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -963,6 +963,7 @@ program
|
|
|
963
963
|
.description('Export memory as instructions for IDE/AI tools')
|
|
964
964
|
.option('--format <format>', 'Format: copilot-instructions, cursor-rules, agents-md, claude-md, gemini-md, windsurf, cline, roo, continue')
|
|
965
965
|
.option('--detect', 'Auto-detect agent environment and write to its native file')
|
|
966
|
+
.option('--all', 'Write all known agent instruction files at once (claude-md, agents-md, copilot-instructions, cursor-rules, etc.)')
|
|
966
967
|
.option('--write', 'Write to canonical file path instead of stdout (when --format is given); local files are gitignored by default')
|
|
967
968
|
.option('--shared', 'Keep the main exported instruction file versionable instead of auto-ignoring it (companions remain local)')
|
|
968
969
|
.option('--output <file>', 'Write to a specific file path instead of stdout')
|
package/dist/commands/accept.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { memoryExists
|
|
1
|
+
import { memoryExists } from '../core/io.js';
|
|
2
|
+
import { mutate } from '../core/mutation-pipeline.js';
|
|
3
|
+
import { rebuildProjectMd } from '../core/markdown.js';
|
|
2
4
|
import { loadCandidate, archiveCandidate, resolveIdOrAlias } from '../core/candidates.js';
|
|
3
5
|
import { loadState, persistState } from '../core/state.js';
|
|
4
6
|
import { generateIdWithLabel, nowISO } from '../core/ids.js';
|
|
@@ -36,7 +38,7 @@ export function acceptCandidate(id, by, cwd, byId) {
|
|
|
36
38
|
requireMinimumTrustLevel(actorIdentity, 'trusted');
|
|
37
39
|
const actor = actorIdentity.agent_name;
|
|
38
40
|
let promotedItemId = '';
|
|
39
|
-
|
|
41
|
+
mutate({ cwd }, () => {
|
|
40
42
|
const state = loadState(cwd);
|
|
41
43
|
switch (candidate.type) {
|
|
42
44
|
case 'constraint': {
|
|
@@ -136,6 +138,7 @@ export function acceptCandidate(id, by, cwd, byId) {
|
|
|
136
138
|
after: { type: candidate.type, text: candidate.text },
|
|
137
139
|
reason: 'trusted-agent',
|
|
138
140
|
}, cwd);
|
|
141
|
+
rebuildProjectMd(loadState(cwd), cwd);
|
|
139
142
|
});
|
|
140
143
|
return {
|
|
141
144
|
candidate_id: resolvedId,
|
package/dist/commands/claim.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { buildOperationalIdentity } from '../core/identity.js';
|
|
2
|
-
import { memoryExists
|
|
2
|
+
import { memoryExists } from '../core/io.js';
|
|
3
|
+
import { mutate } from '../core/mutation-pipeline.js';
|
|
3
4
|
import { saveClaim, generateClaimId, listClaims } from '../core/claims.js';
|
|
4
|
-
import {
|
|
5
|
+
import { rebuildProjectMd } from '../core/markdown.js';
|
|
5
6
|
import { loadState, saveState } from '../core/state.js';
|
|
6
7
|
import { nowISO } from '../core/ids.js';
|
|
7
|
-
import { requireMinimumTrustLevel, requireRegisteredAgentIdentity } from '../core/agent-registry.js';
|
|
8
|
+
import { requireMinimumTrustLevel, requireRegisteredAgentIdentity, resolveCurrentModel } from '../core/agent-registry.js';
|
|
8
9
|
import { validateCliTtl } from '../core/input-validation.js';
|
|
9
10
|
import { resolveTargetStore } from '../core/store-resolution.js';
|
|
10
11
|
function parseTtl(ttl) {
|
|
@@ -71,8 +72,9 @@ export function runClaim(description, options) {
|
|
|
71
72
|
plan_id: options.plan,
|
|
72
73
|
status: 'active',
|
|
73
74
|
expires_at: options.ttl ? parseTtl(options.ttl) : undefined,
|
|
75
|
+
model: resolveCurrentModel(options.cwd),
|
|
74
76
|
};
|
|
75
|
-
|
|
77
|
+
mutate({ cwd: options.cwd }, () => {
|
|
76
78
|
if (plan) {
|
|
77
79
|
if (!plan.assignee) {
|
|
78
80
|
plan.assignee = actor.agent;
|
|
@@ -84,7 +86,7 @@ export function runClaim(description, options) {
|
|
|
84
86
|
saveState(state, options.cwd);
|
|
85
87
|
}
|
|
86
88
|
saveClaim(claim, options.cwd);
|
|
87
|
-
|
|
89
|
+
rebuildProjectMd(plan ? state : loadState(options.cwd), options.cwd);
|
|
88
90
|
});
|
|
89
91
|
const planInfo = claim.plan_id ? ` [plan ${claim.plan_id}]` : '';
|
|
90
92
|
const ttlInfo = claim.expires_at ? ` (expires ${claim.expires_at.slice(0, 16).replace('T', ' ')})` : '';
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { buildContextDiff, resolveContextDiffSince } from '../core/context-diff.js';
|
|
2
|
+
import { listClaims, isClaimExpired } from '../core/claims.js';
|
|
3
|
+
import { loadInstructions, resolveInstructions } from '../core/instructions.js';
|
|
2
4
|
import { memoryExists } from '../core/io.js';
|
|
5
|
+
import { loadState } from '../core/state.js';
|
|
6
|
+
import { isTrapActive } from '../core/traps.js';
|
|
7
|
+
/**
|
|
8
|
+
* Hybrid context-diff: always includes critical anchors (active claims,
|
|
9
|
+
* top traps, instructions) so the agent stays grounded, plus the memory
|
|
10
|
+
* delta since last context read.
|
|
11
|
+
*/
|
|
3
12
|
export function runContextDiff(options = {}) {
|
|
4
13
|
if (!memoryExists(options.cwd)) {
|
|
5
14
|
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
@@ -20,13 +29,62 @@ export function runContextDiff(options = {}) {
|
|
|
20
29
|
process.exit(1);
|
|
21
30
|
}
|
|
22
31
|
if (options.json) {
|
|
23
|
-
|
|
32
|
+
const anchors = buildCriticalAnchors(options.cwd);
|
|
33
|
+
console.log(JSON.stringify({ ...diff, anchors }, null, 2));
|
|
24
34
|
return;
|
|
25
35
|
}
|
|
36
|
+
const lines = [];
|
|
37
|
+
// --- Critical anchors (always present) ---
|
|
38
|
+
const anchors = buildCriticalAnchors(options.cwd);
|
|
39
|
+
if (anchors.claims.length > 0) {
|
|
40
|
+
lines.push('Active claims:');
|
|
41
|
+
for (const c of anchors.claims) {
|
|
42
|
+
lines.push(`- [${c.id}] ${c.agent} → ${c.scope}: ${c.description}`);
|
|
43
|
+
}
|
|
44
|
+
lines.push('');
|
|
45
|
+
}
|
|
46
|
+
if (anchors.instructions.length > 0) {
|
|
47
|
+
lines.push('Instructions:');
|
|
48
|
+
for (const ins of anchors.instructions) {
|
|
49
|
+
const scope = ins.scope ? `:${ins.scope}` : '';
|
|
50
|
+
lines.push(`- [${ins.id}] <${ins.layer}${scope}> ${ins.text}`);
|
|
51
|
+
}
|
|
52
|
+
lines.push('');
|
|
53
|
+
}
|
|
54
|
+
if (anchors.traps.length > 0) {
|
|
55
|
+
lines.push('Active traps:');
|
|
56
|
+
for (const t of anchors.traps) {
|
|
57
|
+
lines.push(`- [${t.id}] (${t.severity}) ${t.text}`);
|
|
58
|
+
}
|
|
59
|
+
lines.push('');
|
|
60
|
+
}
|
|
61
|
+
// --- Memory delta ---
|
|
26
62
|
if (diff.counts.total === 0) {
|
|
27
|
-
|
|
28
|
-
return;
|
|
63
|
+
lines.push(`Memory: no changes since ${diff.since?.slice(0, 16).replace('T', ' ')}.`);
|
|
29
64
|
}
|
|
30
|
-
|
|
65
|
+
else {
|
|
66
|
+
lines.push(`Memory delta (${diff.summary}):`);
|
|
67
|
+
for (const item of diff.changed_items ?? []) {
|
|
68
|
+
lines.push(`- [${item.section}] [${item.id}] ${item.text}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
console.log(lines.join('\n'));
|
|
72
|
+
}
|
|
73
|
+
function buildCriticalAnchors(cwd) {
|
|
74
|
+
const activeClaims = listClaims(cwd)
|
|
75
|
+
.filter((c) => c.status === 'active' && !isClaimExpired(c))
|
|
76
|
+
.map((c) => ({ id: c.id, agent: c.agent, scope: c.scope, description: c.description }));
|
|
77
|
+
const instructions = resolveInstructions(loadInstructions(cwd))
|
|
78
|
+
.map((ins) => ({ id: ins.id, layer: ins.layer, scope: ins.scope, text: ins.text }));
|
|
79
|
+
const state = loadState(cwd);
|
|
80
|
+
const traps = state.known_traps
|
|
81
|
+
.filter((t) => isTrapActive(t))
|
|
82
|
+
.sort((a, b) => {
|
|
83
|
+
const severityOrder = { high: 0, medium: 1, low: 2 };
|
|
84
|
+
return (severityOrder[a.severity] ?? 1) - (severityOrder[b.severity] ?? 1);
|
|
85
|
+
})
|
|
86
|
+
.slice(0, 5)
|
|
87
|
+
.map((t) => ({ id: t.id, severity: t.severity, text: t.text }));
|
|
88
|
+
return { claims: activeClaims, instructions, traps };
|
|
31
89
|
}
|
|
32
90
|
//# sourceMappingURL=context-diff.js.map
|
package/dist/commands/export.js
CHANGED
|
@@ -6,7 +6,7 @@ import { loadConfig, saveConfig } from '../core/config.js';
|
|
|
6
6
|
import { isAgentIntegrationName, upsertAgentIntegrationDeclaration } from '../core/agent-integrations.js';
|
|
7
7
|
import { resolveInstructions, loadInstructions } from '../core/instructions.js';
|
|
8
8
|
import { detectAiAgent } from '../core/ai-agent-detection.js';
|
|
9
|
-
import { resolveExportTarget, resolveExportTargetByFormat, writeExportFile, buildHygieneSection, describeAutoConfigWrite, writeExportCompanionFiles, collectExportGitignoreEntries, ensureGitignoreEntries, } from '../core/agent-files.js';
|
|
9
|
+
import { AGENT_EXPORT_REGISTRY, resolveExportTarget, resolveExportTargetByFormat, writeExportFile, buildHygieneSection, describeAutoConfigWrite, writeExportCompanionFiles, collectExportGitignoreEntries, ensureGitignoreEntries, } from '../core/agent-files.js';
|
|
10
10
|
import { logger } from '../core/logger.js';
|
|
11
11
|
import { getAgentCapabilityProfile } from '../core/agent-capability.js';
|
|
12
12
|
import { renderBrainclawSection } from '../core/instruction-templates.js';
|
|
@@ -17,6 +17,10 @@ export function runExport(options) {
|
|
|
17
17
|
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
|
+
if (options.all) {
|
|
21
|
+
runExportAll(cwd, options);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
20
24
|
if (options.detect) {
|
|
21
25
|
if (options.shared) {
|
|
22
26
|
console.error('Error: --shared cannot be used with --detect. Use `brainclaw export --format <format> --write --shared` to publish a specific instruction file.');
|
|
@@ -26,7 +30,7 @@ export function runExport(options) {
|
|
|
26
30
|
return;
|
|
27
31
|
}
|
|
28
32
|
if (!options.format) {
|
|
29
|
-
console.error('Error: --format or --
|
|
33
|
+
console.error('Error: --format, --detect, or --all is required.');
|
|
30
34
|
process.exit(1);
|
|
31
35
|
}
|
|
32
36
|
const content = generateExport(options.format, options, cwd);
|
|
@@ -90,6 +94,40 @@ function runExportDetect(cwd, options) {
|
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
}
|
|
97
|
+
function runExportAll(cwd, options) {
|
|
98
|
+
// Deduplicate by format (e.g. codex and opencode both use agents-md)
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
const targets = AGENT_EXPORT_REGISTRY.filter((t) => {
|
|
101
|
+
if (seen.has(t.format))
|
|
102
|
+
return false;
|
|
103
|
+
seen.add(t.format);
|
|
104
|
+
return true;
|
|
105
|
+
});
|
|
106
|
+
let written = 0;
|
|
107
|
+
const allGitignoreEntries = [];
|
|
108
|
+
for (const target of targets) {
|
|
109
|
+
try {
|
|
110
|
+
const content = generateExport(target.format, options, cwd);
|
|
111
|
+
const result = writeExportFile(content, target.relativePath, cwd);
|
|
112
|
+
const autoConfigs = writeExportCompanionFiles(target.format, cwd);
|
|
113
|
+
const gitignoreEntries = collectExportGitignoreEntries(cwd, target.relativePath, autoConfigs);
|
|
114
|
+
allGitignoreEntries.push(...gitignoreEntries);
|
|
115
|
+
declareAgentIntegrationFromTarget(cwd, target.agentName, 'manual');
|
|
116
|
+
console.log(`✔ ${target.relativePath} (${result.created ? 'created' : 'updated'})`);
|
|
117
|
+
written++;
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
logger.debug(`Failed to export ${target.format}:`, err);
|
|
121
|
+
console.warn(`⚠ Skipped ${target.format}: ${err instanceof Error ? err.message : String(err)}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Consolidate gitignore entries
|
|
125
|
+
if (allGitignoreEntries.length > 0) {
|
|
126
|
+
ensureGitignoreEntries(cwd, [...new Set(allGitignoreEntries)]);
|
|
127
|
+
console.log('✔ Updated .gitignore');
|
|
128
|
+
}
|
|
129
|
+
console.log(`✔ Exported ${written} agent file(s)`);
|
|
130
|
+
}
|
|
93
131
|
export function writeAgentExportForAgent(agentName, cwd) {
|
|
94
132
|
const rendered = renderAgentExportForAgent(agentName, cwd);
|
|
95
133
|
if (!rendered) {
|
package/dist/commands/init.js
CHANGED
|
@@ -245,6 +245,8 @@ export async function runInit(options = {}) {
|
|
|
245
245
|
if (initMemoryRepo(cwd)) {
|
|
246
246
|
console.log('✔ Initialized memory git repo for versioning');
|
|
247
247
|
}
|
|
248
|
+
// Install post-merge hook for auto-release of claims after merge
|
|
249
|
+
installPostMergeHookIfMissing(cwd);
|
|
248
250
|
const onboardingPreflight = runBootstrapProfile({ cwd, refresh: true });
|
|
249
251
|
console.log('');
|
|
250
252
|
console.log('Onboarding preflight:');
|
|
@@ -268,6 +270,45 @@ export async function runInit(options = {}) {
|
|
|
268
270
|
console.log('');
|
|
269
271
|
console.log(`Tip: run 'brainclaw context --json' to load the shared memory into your agent session.`);
|
|
270
272
|
}
|
|
273
|
+
function installPostMergeHookIfMissing(cwd) {
|
|
274
|
+
try {
|
|
275
|
+
let dir = path.resolve(cwd);
|
|
276
|
+
let gitRoot;
|
|
277
|
+
while (true) {
|
|
278
|
+
if (fs.existsSync(path.join(dir, '.git'))) {
|
|
279
|
+
gitRoot = dir;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
const parent = path.dirname(dir);
|
|
283
|
+
if (parent === dir)
|
|
284
|
+
break;
|
|
285
|
+
dir = parent;
|
|
286
|
+
}
|
|
287
|
+
if (!gitRoot)
|
|
288
|
+
return;
|
|
289
|
+
const hooksDir = path.join(gitRoot, '.git', 'hooks');
|
|
290
|
+
const hookPath = path.join(hooksDir, 'post-merge');
|
|
291
|
+
if (fs.existsSync(hookPath))
|
|
292
|
+
return; // don't overwrite existing hooks
|
|
293
|
+
if (!fs.existsSync(hooksDir))
|
|
294
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
295
|
+
const script = [
|
|
296
|
+
'#!/bin/sh',
|
|
297
|
+
'# brainclaw post-merge hook — auto-release claims on merged files',
|
|
298
|
+
'BCLAW_CMD=""',
|
|
299
|
+
'if command -v brainclaw >/dev/null 2>&1; then BCLAW_CMD="brainclaw"',
|
|
300
|
+
'elif command -v bclaw >/dev/null 2>&1; then BCLAW_CMD="bclaw"',
|
|
301
|
+
'else BCLAW_CMD="npx --no brainclaw"; fi',
|
|
302
|
+
'$BCLAW_CMD release-claims --from-git-diff 2>/dev/null || true',
|
|
303
|
+
'',
|
|
304
|
+
].join('\n');
|
|
305
|
+
fs.writeFileSync(hookPath, script, { encoding: 'utf-8', mode: 0o755 });
|
|
306
|
+
console.log('✔ Installed post-merge hook for auto-release of claims');
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// Non-critical — skip silently
|
|
310
|
+
}
|
|
311
|
+
}
|
|
271
312
|
function resolveStorageDir(storageDir) {
|
|
272
313
|
const candidate = (storageDir ?? MEMORY_DIR).trim();
|
|
273
314
|
if (candidate !== MEMORY_DIR) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { resolveAgentScope, resolveCurrentAgentName } from '../core/agent-registry.js';
|
|
2
2
|
import { loadConfig } from '../core/config.js';
|
|
3
3
|
import { createInstruction } from '../core/instructions.js';
|
|
4
|
-
import { memoryExists
|
|
5
|
-
import {
|
|
4
|
+
import { memoryExists } from '../core/io.js';
|
|
5
|
+
import { mutate } from '../core/mutation-pipeline.js';
|
|
6
|
+
import { rebuildProjectMd } from '../core/markdown.js';
|
|
6
7
|
import { loadState } from '../core/state.js';
|
|
7
8
|
import { scanText } from '../core/security.js';
|
|
8
9
|
import { validateCliInput } from '../core/input-validation.js';
|
|
@@ -26,7 +27,7 @@ export function runInstruction(text, options = {}) {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
let entry;
|
|
29
|
-
|
|
30
|
+
mutate({ cwd }, () => {
|
|
30
31
|
entry = createInstruction(text, {
|
|
31
32
|
layer,
|
|
32
33
|
scope,
|
|
@@ -34,7 +35,7 @@ export function runInstruction(text, options = {}) {
|
|
|
34
35
|
author: options.author ?? resolveCurrentAgentName(cwd),
|
|
35
36
|
supersedes: options.supersedes,
|
|
36
37
|
}, cwd);
|
|
37
|
-
|
|
38
|
+
rebuildProjectMd(loadState(cwd), cwd);
|
|
38
39
|
});
|
|
39
40
|
if (!entry) {
|
|
40
41
|
console.error('Error: failed to persist instruction.');
|
package/dist/commands/mcp.js
CHANGED
|
@@ -18,7 +18,7 @@ import { acceptCandidate } from './accept.js';
|
|
|
18
18
|
import { rejectCandidate } from './reject.js';
|
|
19
19
|
import { startSession } from './session-start.js';
|
|
20
20
|
import { endSession } from './session-end.js';
|
|
21
|
-
import { agentCanWriteDirect, AgentIdentityResolutionError, AgentTrustError, listAgentIdentities, requireMinimumTrustLevel, requireRegisteredAgentIdentity, resolveAgentScope, resolveCurrentAgentIdentity, resolveCurrentAgentName, } from '../core/agent-registry.js';
|
|
21
|
+
import { agentCanWriteDirect, AgentIdentityResolutionError, AgentTrustError, listAgentIdentities, requireMinimumTrustLevel, requireRegisteredAgentIdentity, resolveAgentScope, resolveCurrentAgentIdentity, resolveCurrentAgentName, resolveCurrentModel, } from '../core/agent-registry.js';
|
|
22
22
|
import { appendAuditEntry } from '../core/audit.js';
|
|
23
23
|
import { nowISO, generateIdWithLabel, generateId } from '../core/ids.js';
|
|
24
24
|
import { inferProjectFromTarget, loadInstructions, resolveInstructions } from '../core/instructions.js';
|
|
@@ -156,6 +156,10 @@ export const MCP_READ_TOOLS = [
|
|
|
156
156
|
type: { type: 'string', description: 'Filter by plan type.' },
|
|
157
157
|
assignee: { type: 'string', description: 'Filter by assignee name.' },
|
|
158
158
|
project: { type: 'string', description: 'Filter by project namespace.' },
|
|
159
|
+
id: { type: 'string', description: 'Get a single plan by ID (exact match).' },
|
|
160
|
+
limit: { type: 'number', description: 'Maximum number of plans to return (default: 20).' },
|
|
161
|
+
offset: { type: 'number', description: 'Number of plans to skip (for pagination).' },
|
|
162
|
+
compact: { type: 'boolean', description: 'Return only key fields (id, short_label, text, status, priority) to reduce output size.' },
|
|
159
163
|
},
|
|
160
164
|
},
|
|
161
165
|
},
|
|
@@ -667,6 +671,11 @@ export class McpTaskRunner {
|
|
|
667
671
|
onInternalError;
|
|
668
672
|
active;
|
|
669
673
|
queue = [];
|
|
674
|
+
_totalExecuted = 0;
|
|
675
|
+
_totalCancelled = 0;
|
|
676
|
+
_peakQueueDepth = 0;
|
|
677
|
+
_lastDurationMs = 0;
|
|
678
|
+
_lastWaitMs = 0;
|
|
670
679
|
constructor(options) {
|
|
671
680
|
this.executeTool = options.executeTool;
|
|
672
681
|
this.onResult = options.onResult;
|
|
@@ -678,19 +687,35 @@ export class McpTaskRunner {
|
|
|
678
687
|
get queuedRequestIds() {
|
|
679
688
|
return this.queue.map((task) => task.requestId);
|
|
680
689
|
}
|
|
690
|
+
/** Current single-writer queue metrics. */
|
|
691
|
+
get metrics() {
|
|
692
|
+
return {
|
|
693
|
+
totalExecuted: this._totalExecuted,
|
|
694
|
+
totalCancelled: this._totalCancelled,
|
|
695
|
+
queueDepth: this.queue.length,
|
|
696
|
+
peakQueueDepth: this._peakQueueDepth,
|
|
697
|
+
lastDurationMs: this._lastDurationMs,
|
|
698
|
+
lastWaitMs: this._lastWaitMs,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
681
701
|
enqueue(requestId, payload) {
|
|
682
702
|
this.queue.push({
|
|
683
703
|
requestId,
|
|
684
704
|
payload,
|
|
685
705
|
controller: new AbortController(),
|
|
686
706
|
cancelled: false,
|
|
707
|
+
enqueuedAt: performance.now(),
|
|
687
708
|
});
|
|
709
|
+
if (this.queue.length > this._peakQueueDepth) {
|
|
710
|
+
this._peakQueueDepth = this.queue.length;
|
|
711
|
+
}
|
|
688
712
|
this.drain();
|
|
689
713
|
}
|
|
690
714
|
cancel(requestId) {
|
|
691
715
|
if (this.active && this.active.requestId === requestId) {
|
|
692
716
|
this.active.cancelled = true;
|
|
693
717
|
this.active.controller.abort();
|
|
718
|
+
this._totalCancelled++;
|
|
694
719
|
return 'active';
|
|
695
720
|
}
|
|
696
721
|
const index = this.queue.findIndex((task) => task.requestId === requestId);
|
|
@@ -698,6 +723,7 @@ export class McpTaskRunner {
|
|
|
698
723
|
const [task] = this.queue.splice(index, 1);
|
|
699
724
|
task.cancelled = true;
|
|
700
725
|
task.controller.abort();
|
|
726
|
+
this._totalCancelled++;
|
|
701
727
|
return 'queued';
|
|
702
728
|
}
|
|
703
729
|
return 'missing';
|
|
@@ -722,6 +748,7 @@ export class McpTaskRunner {
|
|
|
722
748
|
return;
|
|
723
749
|
}
|
|
724
750
|
if (next.cancelled) {
|
|
751
|
+
this._totalCancelled++;
|
|
725
752
|
this.drain();
|
|
726
753
|
return;
|
|
727
754
|
}
|
|
@@ -729,6 +756,8 @@ export class McpTaskRunner {
|
|
|
729
756
|
void this.runTask(next);
|
|
730
757
|
}
|
|
731
758
|
async runTask(task) {
|
|
759
|
+
const startedAt = performance.now();
|
|
760
|
+
this._lastWaitMs = startedAt - task.enqueuedAt;
|
|
732
761
|
try {
|
|
733
762
|
const outcome = await this.executeTool(task.payload, task.controller.signal);
|
|
734
763
|
if (!task.cancelled) {
|
|
@@ -741,6 +770,8 @@ export class McpTaskRunner {
|
|
|
741
770
|
}
|
|
742
771
|
}
|
|
743
772
|
finally {
|
|
773
|
+
this._lastDurationMs = performance.now() - startedAt;
|
|
774
|
+
this._totalExecuted++;
|
|
744
775
|
if (this.active === task) {
|
|
745
776
|
this.active = undefined;
|
|
746
777
|
}
|
|
@@ -1281,6 +1312,18 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1281
1312
|
}
|
|
1282
1313
|
if (name === 'bclaw_list_plans') {
|
|
1283
1314
|
let plans = loadState(cwd).plan_items;
|
|
1315
|
+
// Direct lookup by ID
|
|
1316
|
+
if (args.id) {
|
|
1317
|
+
const plan = plans.find((p) => p.id === String(args.id) || p.short_label === String(args.id));
|
|
1318
|
+
if (!plan) {
|
|
1319
|
+
return { content: [{ type: 'text', text: `Plan '${args.id}' not found.` }], structuredContent: { total: 0, plans: [] } };
|
|
1320
|
+
}
|
|
1321
|
+
return {
|
|
1322
|
+
content: [{ type: 'text', text: `[${plan.id}] ${plan.text} (${plan.status}, ${plan.priority})` }],
|
|
1323
|
+
structuredContent: { total: 1, plans: [plan] },
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
// Filters
|
|
1284
1327
|
if (!args.all) {
|
|
1285
1328
|
plans = plans.filter((plan) => plan.status !== 'done' && plan.status !== 'dropped');
|
|
1286
1329
|
}
|
|
@@ -1298,11 +1341,16 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1298
1341
|
const project = String(args.project).toLowerCase();
|
|
1299
1342
|
plans = plans.filter((plan) => plan.project?.toLowerCase() === project);
|
|
1300
1343
|
}
|
|
1301
|
-
const
|
|
1344
|
+
const totalFiltered = plans.length;
|
|
1345
|
+
// Pagination
|
|
1346
|
+
const offset = Math.max(0, Number(args.offset) || 0);
|
|
1347
|
+
const limit = Math.max(1, Number(args.limit) || 20);
|
|
1348
|
+
const paginated = plans.slice(offset, offset + limit);
|
|
1349
|
+
const lines = paginated.length === 0
|
|
1302
1350
|
? ['No plan items found.']
|
|
1303
1351
|
: [
|
|
1304
|
-
`${
|
|
1305
|
-
...
|
|
1352
|
+
`${totalFiltered} plan(s)${totalFiltered > paginated.length ? ` (showing ${offset + 1}-${offset + paginated.length})` : ''}:`,
|
|
1353
|
+
...paginated.map((plan) => {
|
|
1306
1354
|
const meta = [plan.type ?? 'feat', plan.status, plan.priority];
|
|
1307
1355
|
if (plan.assignee)
|
|
1308
1356
|
meta.push(`assignee ${plan.assignee}`);
|
|
@@ -1314,9 +1362,15 @@ export function handleMcpReadToolCall(name, args = {}, context = {}) {
|
|
|
1314
1362
|
return `[${plan.id}] ${plan.text} (${meta.join(' · ')})${tags}`;
|
|
1315
1363
|
}),
|
|
1316
1364
|
];
|
|
1365
|
+
// Compact mode: strip heavy fields
|
|
1366
|
+
const outputPlans = args.compact
|
|
1367
|
+
? paginated.map(({ id, short_label, text, status, priority, tags, assignee, type }) => ({
|
|
1368
|
+
id, short_label, text, status, priority, tags, assignee, type,
|
|
1369
|
+
}))
|
|
1370
|
+
: paginated;
|
|
1317
1371
|
return {
|
|
1318
1372
|
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1319
|
-
structuredContent: { total:
|
|
1373
|
+
structuredContent: { total: totalFiltered, offset, limit, plans: outputPlans },
|
|
1320
1374
|
};
|
|
1321
1375
|
}
|
|
1322
1376
|
if (name === 'bclaw_list_claims') {
|
|
@@ -1584,6 +1638,8 @@ export async function executeMcpToolCall(payload) {
|
|
|
1584
1638
|
response: toolResponse(handleMcpReadToolCall(name, args, { cwd })),
|
|
1585
1639
|
};
|
|
1586
1640
|
}
|
|
1641
|
+
// Resolve model once for all write operations
|
|
1642
|
+
const currentModel = resolveCurrentModel(cwd);
|
|
1587
1643
|
if (name === 'bclaw_setup') {
|
|
1588
1644
|
const step = args.step;
|
|
1589
1645
|
const choice = args.choice ?? '';
|
|
@@ -1767,6 +1823,7 @@ export async function executeMcpToolCall(payload) {
|
|
|
1767
1823
|
autoReflect: args.autoReflect,
|
|
1768
1824
|
cwd,
|
|
1769
1825
|
sessionId: connectionSessionId,
|
|
1826
|
+
model: currentModel,
|
|
1770
1827
|
}, false);
|
|
1771
1828
|
return {
|
|
1772
1829
|
response: toolResponse({
|
|
@@ -1819,6 +1876,7 @@ export async function executeMcpToolCall(payload) {
|
|
|
1819
1876
|
category: type === 'constraint' ? args.category : undefined,
|
|
1820
1877
|
outcome: type === 'decision' ? args.outcome : undefined,
|
|
1821
1878
|
plan_id: candidatePlanId,
|
|
1879
|
+
model: currentModel,
|
|
1822
1880
|
star_count: 0,
|
|
1823
1881
|
starred_by: [],
|
|
1824
1882
|
usage_count: 0,
|
|
@@ -1922,6 +1980,7 @@ export async function executeMcpToolCall(payload) {
|
|
|
1922
1980
|
created_at: nowISO(),
|
|
1923
1981
|
status: 'active',
|
|
1924
1982
|
plan_id: args.planId,
|
|
1983
|
+
model: currentModel,
|
|
1925
1984
|
}, claimCwd);
|
|
1926
1985
|
appendAuditEntry({ actor: resolvedIdentity.agent_name, actor_id: resolvedIdentity.agent_id, action: 'claim', item_id: claimId, item_type: 'claim' }, claimCwd);
|
|
1927
1986
|
const postClaimItems = getTriggeredItems('trigger:post-claim', claimCwd);
|
package/dist/commands/prune.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { loadState, saveState } from '../core/state.js';
|
|
2
|
-
import { memoryExists
|
|
3
|
-
import {
|
|
2
|
+
import { memoryExists } from '../core/io.js';
|
|
3
|
+
import { mutate } from '../core/mutation-pipeline.js';
|
|
4
|
+
import { rebuildProjectMd } from '../core/markdown.js';
|
|
4
5
|
import { deleteRuntimeNote, listRuntimeNotes } from '../core/runtime.js';
|
|
5
6
|
import { expireStaleActiveClaims } from '../core/claims.js';
|
|
6
7
|
export function runPrune(options = {}) {
|
|
@@ -13,7 +14,7 @@ export function runPrune(options = {}) {
|
|
|
13
14
|
let prunedCount = 0;
|
|
14
15
|
let expiredClaimsCount = 0;
|
|
15
16
|
let expiredNotesCount = 0;
|
|
16
|
-
|
|
17
|
+
mutate({ cwd }, () => {
|
|
17
18
|
const state = loadState(cwd);
|
|
18
19
|
const originalLength = state.active_constraints.length;
|
|
19
20
|
for (const c of state.active_constraints) {
|
|
@@ -38,7 +39,7 @@ export function runPrune(options = {}) {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
+
rebuildProjectMd(loadState(cwd), cwd);
|
|
42
43
|
});
|
|
43
44
|
if (options.expired) {
|
|
44
45
|
console.log(`✔ Pruned ${prunedCount} expired constraints, ${expiredNotesCount} expired runtime notes, ${expiredClaimsCount} expired claims.`);
|
package/dist/commands/reflect.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { memoryExists } from '../core/io.js';
|
|
3
|
+
import { rebuildProjectMd } from '../core/markdown.js';
|
|
3
4
|
import { loadConfig } from '../core/config.js';
|
|
4
5
|
import { buildOperationalIdentity } from '../core/identity.js';
|
|
5
6
|
import { loadState, persistState } from '../core/state.js';
|
|
@@ -265,6 +266,7 @@ function promoteCandidateToState(candidate, cwd) {
|
|
|
265
266
|
}
|
|
266
267
|
}
|
|
267
268
|
persistState(state, cwd);
|
|
269
|
+
rebuildProjectMd(loadState(cwd), cwd);
|
|
268
270
|
return promotedItemId;
|
|
269
271
|
}
|
|
270
272
|
export function mapEventTypeToCandidateType(eventType) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { memoryExists } from '../core/io.js';
|
|
2
|
-
import {
|
|
2
|
+
import { mutate } from '../core/mutation-pipeline.js';
|
|
3
3
|
import { loadClaim, listClaims, releaseClaim } from '../core/claims.js';
|
|
4
|
-
import {
|
|
4
|
+
import { rebuildProjectMd } from '../core/markdown.js';
|
|
5
5
|
import { loadState, saveState } from '../core/state.js';
|
|
6
6
|
export function runReleaseClaim(id, options = {}) {
|
|
7
7
|
if (!memoryExists(options.cwd)) {
|
|
@@ -10,7 +10,7 @@ export function runReleaseClaim(id, options = {}) {
|
|
|
10
10
|
}
|
|
11
11
|
try {
|
|
12
12
|
let claim = loadClaim(id, options.cwd);
|
|
13
|
-
|
|
13
|
+
mutate({ cwd: options.cwd }, () => {
|
|
14
14
|
const existing = loadClaim(id, options.cwd);
|
|
15
15
|
claim = releaseClaim(id, options.cwd);
|
|
16
16
|
let state = loadState(options.cwd);
|
|
@@ -31,7 +31,7 @@ export function runReleaseClaim(id, options = {}) {
|
|
|
31
31
|
saveState(state, options.cwd);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
rebuildProjectMd(state, options.cwd);
|
|
35
35
|
});
|
|
36
36
|
console.log(`✔ Claim [${id}] released (was: ${claim.agent} → ${claim.scope})`);
|
|
37
37
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
|
-
import { memoryExists
|
|
2
|
+
import { memoryExists } from '../core/io.js';
|
|
3
|
+
import { mutate } from '../core/mutation-pipeline.js';
|
|
3
4
|
import { listClaims, releaseClaim } from '../core/claims.js';
|
|
4
|
-
import {
|
|
5
|
+
import { rebuildProjectMd } from '../core/markdown.js';
|
|
5
6
|
import { loadState, saveState } from '../core/state.js';
|
|
6
7
|
function normPath(p) {
|
|
7
8
|
return p.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
@@ -36,7 +37,7 @@ export function runReleaseClaims(options = {}) {
|
|
|
36
37
|
if (toRelease.length === 0)
|
|
37
38
|
process.exit(0);
|
|
38
39
|
let released = 0;
|
|
39
|
-
|
|
40
|
+
mutate({ cwd: options.cwd }, () => {
|
|
40
41
|
let state = loadState(options.cwd);
|
|
41
42
|
for (const claim of toRelease) {
|
|
42
43
|
try {
|
|
@@ -59,7 +60,7 @@ export function runReleaseClaims(options = {}) {
|
|
|
59
60
|
catch { /* skip individual failures */ }
|
|
60
61
|
}
|
|
61
62
|
state = loadState(options.cwd);
|
|
62
|
-
|
|
63
|
+
rebuildProjectMd(state, options.cwd);
|
|
63
64
|
});
|
|
64
65
|
if (released > 0) {
|
|
65
66
|
console.log(`brainclaw: ${released} claim(s) auto-released after merge.`);
|
|
@@ -61,6 +61,7 @@ export function createRuntimeNote(text, options, printSuccess = false) {
|
|
|
61
61
|
host_id: hostId,
|
|
62
62
|
expires_at: expiresAt,
|
|
63
63
|
note_type: 'observation',
|
|
64
|
+
model: options.model,
|
|
64
65
|
};
|
|
65
66
|
saveRuntimeNote(note, options.cwd);
|
|
66
67
|
const result = maybeAutoReflectRuntimeNote(note, options);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { JsonStore } from './json-store.js';
|
|
3
|
-
import { resolveEntityDir
|
|
3
|
+
import { resolveEntityDir } from './io.js';
|
|
4
|
+
import { mutate } from './mutation-pipeline.js';
|
|
4
5
|
import { AiSurfaceTaskRequestSchema } from './schema.js';
|
|
5
6
|
function surfaceTasksDir(cwd, mode = 'read') {
|
|
6
7
|
return resolveEntityDir('surface-tasks', cwd ?? process.cwd(), mode);
|
|
@@ -20,7 +21,7 @@ export function ensureAiSurfaceTasksDir(cwd) {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
export function saveAiSurfaceTask(task, cwd) {
|
|
23
|
-
|
|
24
|
+
mutate({ cwd }, () => {
|
|
24
25
|
ensureAiSurfaceTasksDir(cwd);
|
|
25
26
|
const writeStore = new JsonStore({
|
|
26
27
|
dirPath: surfaceTasksDir(cwd, 'write'),
|
package/dist/core/audit.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { memoryDir
|
|
3
|
+
import { memoryDir } from './io.js';
|
|
4
|
+
import { mutate } from './mutation-pipeline.js';
|
|
4
5
|
import { nowISO } from './ids.js';
|
|
5
6
|
import { logger } from './logger.js';
|
|
6
7
|
import { appendEvent } from './event-log.js';
|
|
@@ -23,7 +24,7 @@ function auditLogPath(cwd) {
|
|
|
23
24
|
}
|
|
24
25
|
export function appendAuditEntry(entry, cwd) {
|
|
25
26
|
try {
|
|
26
|
-
|
|
27
|
+
mutate({ cwd }, () => {
|
|
27
28
|
const full = {
|
|
28
29
|
timestamp: nowISO(),
|
|
29
30
|
actor: entry.actor,
|