brainclaw 1.28.6 → 1.28.8
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/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +40 -2
- package/dist/core/io.js +21 -19
- package/dist/facts.js +7 -7
- package/dist/facts.json +6 -6
- package/docs/integrations/mcp.md +15 -0
- package/package.json +1 -1
|
Binary file
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { getInstalledBrainclawVersion } from './core/brainclaw-version.js';
|
|
6
|
-
import { cleanOrphanFiles, memoryDir } from './core/io.js';
|
|
6
|
+
import { cleanOrphanFiles, isSafeSessionId, memoryDir } from './core/io.js';
|
|
7
7
|
import { initLogLevel, logger } from './core/logger.js';
|
|
8
8
|
import { resolveEffectiveCwd } from './core/store-resolution.js';
|
|
9
9
|
import { resolveProjectCwd } from './core/cross-project.js';
|
|
@@ -82,6 +82,38 @@ function trailingGlobalOptionError(argv, actionCommand) {
|
|
|
82
82
|
}
|
|
83
83
|
return undefined;
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Hook subprocesses do not reliably inherit the MCP process environment. The
|
|
87
|
+
* agent integrations do, however, provide their session identity in the JSON
|
|
88
|
+
* event on stdin. Hydrate it before preAction resolves the effective project;
|
|
89
|
+
* otherwise a session-scoped switch silently falls back to the workspace root.
|
|
90
|
+
*
|
|
91
|
+
* This is deliberately best-effort: hooks are advisory, stdin may be empty or
|
|
92
|
+
* non-JSON, and an explicitly exported session id remains authoritative.
|
|
93
|
+
*/
|
|
94
|
+
function hydrateHookSessionFromStdin(argv) {
|
|
95
|
+
if (!argv.includes('--hook') || process.stdin.isTTY || process.env.BRAINCLAW_SESSION_ID)
|
|
96
|
+
return;
|
|
97
|
+
try {
|
|
98
|
+
const raw = fs.readFileSync(0, 'utf8').trim();
|
|
99
|
+
if (!raw)
|
|
100
|
+
return;
|
|
101
|
+
const event = JSON.parse(raw);
|
|
102
|
+
const sessionId = typeof event.session_id === 'string'
|
|
103
|
+
? event.session_id.trim()
|
|
104
|
+
: typeof event.sessionId === 'string'
|
|
105
|
+
? event.sessionId.trim()
|
|
106
|
+
: typeof event.metadata?.session === 'string'
|
|
107
|
+
? event.metadata.session.trim()
|
|
108
|
+
: '';
|
|
109
|
+
if (sessionId && isSafeSessionId(sessionId)) {
|
|
110
|
+
process.env.BRAINCLAW_SESSION_ID = sessionId;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Invalid hook input must never break the agent's prompt loop.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
85
117
|
/**
|
|
86
118
|
* Resolve the (possibly nested) subcommand named in argv without parsing.
|
|
87
119
|
* Used to run the trailing-global-option guard BEFORE Commander parses:
|
|
@@ -122,7 +154,9 @@ program
|
|
|
122
154
|
.option('--cwd <path>', 'Override working directory for this invocation')
|
|
123
155
|
.option('--project <name>', 'Run the command against a linked project (cross_project_links or workspace store-chain child). Resolves via resolveProjectCwd; mutually exclusive with --cwd.')
|
|
124
156
|
.hook('preAction', (_thisCommand, actionCommand) => {
|
|
125
|
-
const
|
|
157
|
+
const argv = process.argv.slice(2);
|
|
158
|
+
hydrateHookSessionFromStdin(argv);
|
|
159
|
+
const root = parseLeadingGlobalOptions(argv);
|
|
126
160
|
initLogLevel({ verbose: root.verbose, debug: root.debug });
|
|
127
161
|
// Skip effective cwd resolution for commands that create the store
|
|
128
162
|
const cmdName = actionCommand.name();
|
|
@@ -146,14 +180,18 @@ program
|
|
|
146
180
|
}
|
|
147
181
|
if (!skipResolution) {
|
|
148
182
|
// Resolve effective cwd (explicit > BRAINCLAW_PROJECT > active-project > process.cwd)
|
|
183
|
+
const resolutionStarted = performance.now();
|
|
149
184
|
const effectiveCwd = resolveEffectiveCwd({ explicitCwd });
|
|
185
|
+
logger.debug(`Startup project resolution: ${(performance.now() - resolutionStarted).toFixed(1)}ms`);
|
|
150
186
|
if (effectiveCwd !== process.cwd()) {
|
|
151
187
|
// Change process.cwd() so all commands resolve the correct store
|
|
152
188
|
// without needing individual --cwd plumbing
|
|
153
189
|
process.chdir(effectiveCwd);
|
|
154
190
|
logger.info(`Resolved effective cwd: ${effectiveCwd}`);
|
|
155
191
|
}
|
|
192
|
+
const cleanupStarted = performance.now();
|
|
156
193
|
const removed = cleanOrphanFiles(memoryDir());
|
|
194
|
+
logger.debug(`Startup orphan cleanup: ${(performance.now() - cleanupStarted).toFixed(1)}ms`);
|
|
157
195
|
if (removed > 0) {
|
|
158
196
|
logger.info(`Cleaned ${removed} orphan lock/tmp file(s) in ${memoryDir()}`);
|
|
159
197
|
}
|
package/dist/core/io.js
CHANGED
|
@@ -519,30 +519,32 @@ export function writeFileAtomic(filepath, content, options = {}) {
|
|
|
519
519
|
* Call once at CLI startup. Returns count of removed files.
|
|
520
520
|
*/
|
|
521
521
|
export function cleanOrphanFiles(dirPath) {
|
|
522
|
+
return cleanOrphanDirectory(dirPath, true);
|
|
523
|
+
}
|
|
524
|
+
function cleanOrphanDirectory(dirPath, root) {
|
|
522
525
|
let removed = 0;
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
//
|
|
526
|
+
// Historical/derived trees are not startup-maintenance targets. Their writers
|
|
527
|
+
// reclaim stale locks when acquiring them; scanning their contents here makes
|
|
528
|
+
// connection latency proportional to Git history and Code Map size.
|
|
529
|
+
const excluded = new Set(['.git', 'code', 'archive', 'gc-backups', 'migration-backups', 'recovery-backups']);
|
|
526
530
|
try {
|
|
527
|
-
for (const entry of fs.readdirSync(dirPath)) {
|
|
528
|
-
const full = path.join(dirPath, entry);
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
531
|
+
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
532
|
+
const full = path.join(dirPath, entry.name);
|
|
533
|
+
// Dirents avoid a stat per entity and do not follow symlinks/junctions.
|
|
534
|
+
if (entry.isDirectory()) {
|
|
535
|
+
const name = entry.name.toLowerCase();
|
|
536
|
+
if (name !== '.git' && !(root && excluded.has(name)))
|
|
537
|
+
removed += cleanOrphanDirectory(full, false);
|
|
532
538
|
}
|
|
533
|
-
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
|
-
if (entry.endsWith('.tmp') && stat.isFile() && shouldRemoveTmp(entry, stat)) {
|
|
539
|
+
else if (entry.isFile() && tempOwnerPid(entry.name)) {
|
|
537
540
|
try {
|
|
538
|
-
fs.
|
|
539
|
-
|
|
541
|
+
const stat = fs.lstatSync(full);
|
|
542
|
+
if (stat.isFile() && shouldRemoveTmp(entry.name, stat)) {
|
|
543
|
+
fs.unlinkSync(full);
|
|
544
|
+
removed++;
|
|
545
|
+
}
|
|
540
546
|
}
|
|
541
|
-
catch { /*
|
|
542
|
-
}
|
|
543
|
-
// Recurse into subdirectories
|
|
544
|
-
if (stat.isDirectory()) {
|
|
545
|
-
removed += cleanOrphanFiles(full);
|
|
547
|
+
catch { /* disappeared or unreadable — skip */ }
|
|
546
548
|
}
|
|
547
549
|
}
|
|
548
550
|
}
|
package/dist/facts.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Generated by scripts/emit-site-facts.mjs at build time. Do not edit manually.
|
|
2
|
-
// Source: brainclaw v1.28.
|
|
2
|
+
// Source: brainclaw v1.28.8 on 2026-09-09T12:16:44.520Z
|
|
3
3
|
export const FACTS = {
|
|
4
|
-
"version": "1.28.
|
|
5
|
-
"generated_at": "2026-
|
|
4
|
+
"version": "1.28.8",
|
|
5
|
+
"generated_at": "2026-09-09T12:16:44.520Z",
|
|
6
6
|
"tools": {
|
|
7
7
|
"count": 71,
|
|
8
8
|
"published_count": 69,
|
|
@@ -479,8 +479,8 @@ export const FACTS = {
|
|
|
479
479
|
},
|
|
480
480
|
"bench": {
|
|
481
481
|
"schema": "brainclaw.bench.v1",
|
|
482
|
-
"generated_at": "2026-
|
|
483
|
-
"node_version": "v24.
|
|
482
|
+
"generated_at": "2026-09-09T12:16:42.258Z",
|
|
483
|
+
"node_version": "v24.20.0",
|
|
484
484
|
"platform": "linux-x64",
|
|
485
485
|
"repeats": 3,
|
|
486
486
|
"scenarios": [
|
|
@@ -488,7 +488,7 @@ export const FACTS = {
|
|
|
488
488
|
"name": "cold_onboard",
|
|
489
489
|
"volume": "empty",
|
|
490
490
|
"description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
|
|
491
|
-
"duration_ms_median":
|
|
491
|
+
"duration_ms_median": 88,
|
|
492
492
|
"payload_chars_median": 1640,
|
|
493
493
|
"payload_tokens_est_median": 410
|
|
494
494
|
},
|
|
@@ -496,7 +496,7 @@ export const FACTS = {
|
|
|
496
496
|
"name": "warm_work",
|
|
497
497
|
"volume": "medium",
|
|
498
498
|
"description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
|
|
499
|
-
"duration_ms_median":
|
|
499
|
+
"duration_ms_median": 136,
|
|
500
500
|
"payload_chars_median": 2626,
|
|
501
501
|
"payload_tokens_est_median": 657
|
|
502
502
|
},
|
package/dist/facts.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.28.
|
|
3
|
-
"generated_at": "2026-
|
|
2
|
+
"version": "1.28.8",
|
|
3
|
+
"generated_at": "2026-09-09T12:16:44.520Z",
|
|
4
4
|
"tools": {
|
|
5
5
|
"count": 71,
|
|
6
6
|
"published_count": 69,
|
|
@@ -477,8 +477,8 @@
|
|
|
477
477
|
},
|
|
478
478
|
"bench": {
|
|
479
479
|
"schema": "brainclaw.bench.v1",
|
|
480
|
-
"generated_at": "2026-
|
|
481
|
-
"node_version": "v24.
|
|
480
|
+
"generated_at": "2026-09-09T12:16:42.258Z",
|
|
481
|
+
"node_version": "v24.20.0",
|
|
482
482
|
"platform": "linux-x64",
|
|
483
483
|
"repeats": 3,
|
|
484
484
|
"scenarios": [
|
|
@@ -486,7 +486,7 @@
|
|
|
486
486
|
"name": "cold_onboard",
|
|
487
487
|
"volume": "empty",
|
|
488
488
|
"description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
|
|
489
|
-
"duration_ms_median":
|
|
489
|
+
"duration_ms_median": 88,
|
|
490
490
|
"payload_chars_median": 1640,
|
|
491
491
|
"payload_tokens_est_median": 410
|
|
492
492
|
},
|
|
@@ -494,7 +494,7 @@
|
|
|
494
494
|
"name": "warm_work",
|
|
495
495
|
"volume": "medium",
|
|
496
496
|
"description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
|
|
497
|
-
"duration_ms_median":
|
|
497
|
+
"duration_ms_median": 136,
|
|
498
498
|
"payload_chars_median": 2626,
|
|
499
499
|
"payload_tokens_est_median": 657
|
|
500
500
|
},
|
package/docs/integrations/mcp.md
CHANGED
|
@@ -564,4 +564,19 @@ The CLI remains valuable for:
|
|
|
564
564
|
- release and packaging
|
|
565
565
|
- debugging and fallback access
|
|
566
566
|
|
|
567
|
+
### Diagnose MCP startup
|
|
568
|
+
|
|
569
|
+
Measure startup with a fresh subprocess rather than timing a tool call on an
|
|
570
|
+
existing connection:
|
|
571
|
+
|
|
572
|
+
```bash
|
|
573
|
+
node scripts/bench-mcp-startup.mjs --cwd . --cli dist/cli.js --repeats 3 --budget-ms 20000
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
The benchmark covers process launch, `initialize`, the initialized
|
|
577
|
+
notification, and `tools/list`. It requires an explicit project directory
|
|
578
|
+
because normal CLI startup maintenance may update that store. Pass `--debug`
|
|
579
|
+
before the `mcp` command to report project-resolution and orphan-cleanup
|
|
580
|
+
durations on stderr without corrupting JSON-RPC stdout.
|
|
581
|
+
|
|
567
582
|
But for capable agents, MCP is the first-class path for both reads and writes.
|