brainclaw 1.10.0 → 1.10.1
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 +32 -25
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/commands/dispatch-watch.js +1 -1
- package/dist/commands/doctor.js +3 -5
- package/dist/commands/loops-handlers.js +4 -1
- package/dist/commands/mcp-read-handlers.js +8 -0
- package/dist/commands/mcp.js +1 -2
- package/dist/commands/metrics.js +0 -1
- package/dist/commands/release-claims.js +1 -1
- package/dist/commands/sequence.js +1 -1
- package/dist/commands/sync.js +1 -1
- package/dist/commands/upgrade.js +0 -7
- package/dist/core/agent-context.js +1 -1
- package/dist/core/agent-files.js +1 -2
- package/dist/core/agent-integrations.js +3 -3
- package/dist/core/agent-registry.js +2 -2
- package/dist/core/assignments.js +12 -0
- package/dist/core/brainclaw-version.js +2 -2
- package/dist/core/code-map/backend.js +59 -6
- package/dist/core/code-map/freshness.js +36 -0
- package/dist/core/code-map/query.js +37 -6
- package/dist/core/code-map/refresh.js +0 -0
- package/dist/core/code-map/types.js +5 -0
- package/dist/core/code-map/wasm-loader.js +1 -1
- package/dist/core/context.js +1 -1
- package/dist/core/cross-project.js +1 -1
- package/dist/core/dispatcher.js +0 -2
- package/dist/core/entity-operations.js +0 -3
- package/dist/core/ids.js +1 -1
- package/dist/core/instruction-templates.js +1 -1
- package/dist/core/instructions.js +0 -1
- package/dist/core/loops/lock.js +0 -3
- package/dist/core/protocol-skills.js +5 -3
- package/dist/core/security-detectors.js +2 -2
- package/dist/core/security-extract.js +2 -2
- package/dist/facts.js +3 -3
- package/dist/facts.json +2 -2
- package/docs/code-map.md +14 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -369,34 +369,34 @@ The same surface is available through the canonical grammar for agents: `bclaw_c
|
|
|
369
369
|
|
|
370
370
|
## Documentation
|
|
371
371
|
|
|
372
|
-
The
|
|
372
|
+
The Markdown docs below ship in the npm package under `docs/` and are versioned in the repo. The links resolve on GitHub and are rewritten to the package repo on the npm page.
|
|
373
373
|
|
|
374
374
|
If you are integrating Brainclaw into an agent workflow, start with the agent-facing docs first:
|
|
375
375
|
|
|
376
|
-
|
|
|
377
|
-
|
|
|
378
|
-
|
|
|
379
|
-
|
|
|
380
|
-
|
|
|
381
|
-
|
|
|
382
|
-
|
|
|
383
|
-
|
|
|
384
|
-
|
|
|
385
|
-
|
|
|
386
|
-
|
|
|
387
|
-
|
|
|
388
|
-
|
|
|
389
|
-
|
|
|
390
|
-
|
|
|
391
|
-
|
|
|
392
|
-
|
|
|
393
|
-
|
|
|
394
|
-
|
|
|
395
|
-
|
|
|
396
|
-
|
|
|
397
|
-
|
|
|
398
|
-
|
|
|
399
|
-
|
|
|
376
|
+
| Doc | What it covers |
|
|
377
|
+
| --- | --- |
|
|
378
|
+
| [docs/index.md](docs/index.md) | Documentation index grouped by getting started, guides, reference, and design |
|
|
379
|
+
| [docs/integrations/overview.md](docs/integrations/overview.md) | Start here for agent integrations |
|
|
380
|
+
| [docs/integrations/mcp.md](docs/integrations/mcp.md) | MCP runtime path for capable agents |
|
|
381
|
+
| [docs/quickstart.md](docs/quickstart.md) | First-time setup on a new project (greenfield) |
|
|
382
|
+
| [docs/quickstart-existing-project.md](docs/quickstart-existing-project.md) | Joining a project that already has `.brainclaw/` |
|
|
383
|
+
| [docs/server-operations.md](docs/server-operations.md) | Operator and remote-server workflow guide |
|
|
384
|
+
| [docs/cli.md](docs/cli.md) | CLI reference for operators, scripts, and fallback use |
|
|
385
|
+
| [docs/code-map.md](docs/code-map.md) | Code Map — symbol/import index, freshness model, monorepo behavior |
|
|
386
|
+
| [docs/concepts/memory.md](docs/concepts/memory.md) | What "memory" means in brainclaw |
|
|
387
|
+
| [docs/concepts/plans-and-claims.md](docs/concepts/plans-and-claims.md) | Coordination layer |
|
|
388
|
+
| [docs/concepts/runtime-notes.md](docs/concepts/runtime-notes.md) | Ephemeral observations |
|
|
389
|
+
| [docs/concepts/multi-agent-workflows.md](docs/concepts/multi-agent-workflows.md) | The four common scenarios — orchestration, agent switching, project recovery, team async |
|
|
390
|
+
| [docs/concepts/troubleshooting.md](docs/concepts/troubleshooting.md) | Runbook for degraded coordination state — stale claims, missing dist, octopus failures, etc. |
|
|
391
|
+
| [docs/integrations/cursor.md](docs/integrations/cursor.md) | Cursor |
|
|
392
|
+
| [docs/integrations/claude-code.md](docs/integrations/claude-code.md) | Claude Code |
|
|
393
|
+
| [docs/integrations/copilot.md](docs/integrations/copilot.md) | GitHub Copilot |
|
|
394
|
+
| [docs/integrations/codex.md](docs/integrations/codex.md) | Codex |
|
|
395
|
+
| [docs/storage.md](docs/storage.md) | Storage model |
|
|
396
|
+
| [docs/security.md](docs/security.md) | Security model |
|
|
397
|
+
| [docs/review.md](docs/review.md) | Reflective review |
|
|
398
|
+
| [docs/reputation.md](docs/reputation.md) | Reputation signals |
|
|
399
|
+
| [docs/playbooks/](docs/playbooks/) | Audience design constraints for brainclaw development |
|
|
400
400
|
|
|
401
401
|
---
|
|
402
402
|
|
|
@@ -417,6 +417,13 @@ npm run test:coverage # with coverage report
|
|
|
417
417
|
|
|
418
418
|
For older releases (v0.x and the early v1.0 launch series), `git log` on `master` is the source of truth — every release commit follows the `chore(release): bump version to <semver>` convention, and the matching feature/fix commits reference their plan id (e.g. `feat(mcp): self-heal ... (pln#478)`).
|
|
419
419
|
|
|
420
|
+
### v1.10.1
|
|
421
|
+
|
|
422
|
+
Code Map fast-follows from the 1.10.0 dogfood, plus a lint cleanup:
|
|
423
|
+
|
|
424
|
+
- A git branch switch now flags the index stale (new `stale_git_head` freshness reason); `refresh` honors `.gitignore` (gitignored output dirs no longer indexed); `brief("<path>")` resolves the exact file instead of token-flooding; and the freshness badge distinguishes index freshness from a call's spot-check (`index_status`).
|
|
425
|
+
- The `brainclaw-session` skill + agent instruction surface now point agents at `bclaw_code_find` / `bclaw_code_brief` before grep. Lint baseline cleared to zero (stylistic rules ratcheted to `error`); README doc links clickable; `docs/code-map.md` lists JS / TS / JSX / TSX · Python · PHP · Java.
|
|
426
|
+
|
|
420
427
|
### v1.10.0
|
|
421
428
|
|
|
422
429
|
- **Code Map** — a new per-project [Tree-sitter](docs/code-map.md) symbol + import
|
|
Binary file
|
package/dist/commands/doctor.js
CHANGED
|
@@ -747,7 +747,6 @@ export function runDoctor(options = {}) {
|
|
|
747
747
|
const msg = e instanceof Error ? e.message : String(e);
|
|
748
748
|
checks.push({ name: 'config', status: 'error', message: `config.yaml is invalid: ${msg}` });
|
|
749
749
|
console.error(`✗ config.yaml is invalid: ${msg}`);
|
|
750
|
-
hasIssues = true;
|
|
751
750
|
if (options.json) {
|
|
752
751
|
console.log(JSON.stringify({ ok: false, checks, metrics: {} }, null, 2));
|
|
753
752
|
}
|
|
@@ -807,7 +806,6 @@ export function runDoctor(options = {}) {
|
|
|
807
806
|
const msg = e instanceof Error ? e.message : String(e);
|
|
808
807
|
checks.push({ name: 'state', status: 'error', message: `state is invalid: ${msg}` });
|
|
809
808
|
console.error(`✗ state is invalid: ${msg}`);
|
|
810
|
-
hasIssues = true;
|
|
811
809
|
if (options.json) {
|
|
812
810
|
console.log(JSON.stringify({ ok: false, checks, metrics: {} }, null, 2));
|
|
813
811
|
}
|
|
@@ -2202,7 +2200,7 @@ export function runDoctor(options = {}) {
|
|
|
2202
2200
|
}
|
|
2203
2201
|
// Find duplicates (same text at different levels)
|
|
2204
2202
|
const duplicates = [];
|
|
2205
|
-
allConstraints.forEach((items,
|
|
2203
|
+
allConstraints.forEach((items, _key) => {
|
|
2206
2204
|
if (items.length > 1 && new Set(items.map((i) => i.store)).size > 1) {
|
|
2207
2205
|
duplicates.push({
|
|
2208
2206
|
type: 'constraint',
|
|
@@ -2211,7 +2209,7 @@ export function runDoctor(options = {}) {
|
|
|
2211
2209
|
});
|
|
2212
2210
|
}
|
|
2213
2211
|
});
|
|
2214
|
-
allDecisions.forEach((items,
|
|
2212
|
+
allDecisions.forEach((items, _key) => {
|
|
2215
2213
|
if (items.length > 1 && new Set(items.map((i) => i.store)).size > 1) {
|
|
2216
2214
|
duplicates.push({
|
|
2217
2215
|
type: 'decision',
|
|
@@ -2220,7 +2218,7 @@ export function runDoctor(options = {}) {
|
|
|
2220
2218
|
});
|
|
2221
2219
|
}
|
|
2222
2220
|
});
|
|
2223
|
-
allTraps.forEach((items,
|
|
2221
|
+
allTraps.forEach((items, _key) => {
|
|
2224
2222
|
if (items.length > 1 && new Set(items.map((i) => i.store)).size > 1) {
|
|
2225
2223
|
duplicates.push({
|
|
2226
2224
|
type: 'trap',
|
|
@@ -84,7 +84,10 @@ function validateSemanticRequest(req) {
|
|
|
84
84
|
return null;
|
|
85
85
|
}
|
|
86
86
|
function requestPayload(req) {
|
|
87
|
-
const
|
|
87
|
+
const rest = { ...req };
|
|
88
|
+
delete rest.agent;
|
|
89
|
+
delete rest.agentId;
|
|
90
|
+
delete rest.client_request_id;
|
|
88
91
|
return rest;
|
|
89
92
|
}
|
|
90
93
|
function currentLoopVersion(loopId, cwd) {
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP read-only tool handlers.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from mcp.ts to reduce file size. These handlers do not mutate
|
|
5
|
+
* state — they build context, list items, search, and inspect.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
1
9
|
import { applyBootstrapImport, renderBootstrapInterview, renderBootstrapSummary, runBootstrapProfile, uninstallBootstrapImport } from '../core/bootstrap.js';
|
|
2
10
|
import { buildAgentToolingContext, renderAgentToolingSummary } from '../core/agent-context.js';
|
|
3
11
|
import { buildCoordinationSnapshot, buildCrossProjectSnapshot } from '../core/coordination.js';
|
package/dist/commands/mcp.js
CHANGED
|
@@ -452,7 +452,7 @@ export const MCP_READ_TOOLS = [
|
|
|
452
452
|
},
|
|
453
453
|
{
|
|
454
454
|
name: 'bclaw_code_status',
|
|
455
|
-
description: 'Code Map status for this project: store presence, freshness badge (fresh / stale_changed_files / stale_extractor / stale_grammar / partial / missing_index), and index stats (files, nodes, edges). Read-only; never refreshes. Pair with bclaw_code_refresh when freshness is missing_index or stale.',
|
|
455
|
+
description: 'Code Map status for this project: store presence, freshness badge (fresh / stale_changed_files / stale_extractor / stale_grammar / stale_git_head / partial / missing_index), and index stats (files, nodes, edges). Read-only; never refreshes. Pair with bclaw_code_refresh when freshness is missing_index or stale.',
|
|
456
456
|
annotations: { tier: 'standard', category: 'discovery', headlessApproval: 'auto' },
|
|
457
457
|
inputSchema: {
|
|
458
458
|
type: 'object',
|
|
@@ -5360,7 +5360,6 @@ async function _executeMcpToolCallInner(payload) {
|
|
|
5360
5360
|
}));
|
|
5361
5361
|
continue;
|
|
5362
5362
|
}
|
|
5363
|
-
const profile = check.profile;
|
|
5364
5363
|
// Ensure target agent is registered before creating claims/messages
|
|
5365
5364
|
ensureAgentRegisteredForDispatch(agentName, dispatchCwd);
|
|
5366
5365
|
const assignScope = req.scope ?? req.task;
|
package/dist/commands/metrics.js
CHANGED
|
@@ -12,7 +12,6 @@ export function runMetrics(options = {}) {
|
|
|
12
12
|
}
|
|
13
13
|
const config = loadConfig();
|
|
14
14
|
const slaHours = config.governance?.review_sla_hours ?? 24;
|
|
15
|
-
const now = new Date();
|
|
16
15
|
const report = buildMetricsReport(slaHours, options.since);
|
|
17
16
|
if (options.json) {
|
|
18
17
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -39,7 +39,7 @@ export function runReleaseClaims(options = {}) {
|
|
|
39
39
|
process.exit(0);
|
|
40
40
|
let released = 0;
|
|
41
41
|
mutate({ cwd: options.cwd }, () => {
|
|
42
|
-
let state
|
|
42
|
+
let state;
|
|
43
43
|
for (const claim of toRelease) {
|
|
44
44
|
try {
|
|
45
45
|
releaseClaim(claim.id, options.cwd);
|
|
@@ -11,7 +11,7 @@ function parseItems(raw) {
|
|
|
11
11
|
parsed = JSON.parse(raw);
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
14
|
-
throw new Error(`Invalid --items JSON: ${error instanceof Error ? error.message : String(error)}
|
|
14
|
+
throw new Error(`Invalid --items JSON: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
15
15
|
}
|
|
16
16
|
if (!Array.isArray(parsed)) {
|
|
17
17
|
throw new Error('Invalid --items JSON: expected an array');
|
package/dist/commands/sync.js
CHANGED
|
@@ -61,7 +61,7 @@ export function runSync(options = {}) {
|
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
// Check git status of .brainclaw/
|
|
64
|
-
let gitStatus
|
|
64
|
+
let gitStatus;
|
|
65
65
|
try {
|
|
66
66
|
// Security: execFileSync (no shell) + scopePaths spread as separate args so
|
|
67
67
|
// path specs cannot inject (Socket 2026-06-08 class).
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -463,13 +463,6 @@ function upsertSection(existingContent, section) {
|
|
|
463
463
|
const trimmed = existingContent.trimEnd();
|
|
464
464
|
return trimmed.length > 0 ? `${trimmed}\n\n${section}\n` : `${section}\n`;
|
|
465
465
|
}
|
|
466
|
-
function listJsonFiles(dir) {
|
|
467
|
-
if (!fs.existsSync(dir))
|
|
468
|
-
return [];
|
|
469
|
-
return fs.readdirSync(dir)
|
|
470
|
-
.filter(f => f.endsWith('.json'))
|
|
471
|
-
.map(f => path.join(dir, f));
|
|
472
|
-
}
|
|
473
466
|
function listJsonFilesRecursive(dir) {
|
|
474
467
|
if (!fs.existsSync(dir))
|
|
475
468
|
return [];
|
|
@@ -63,7 +63,7 @@ function readAgentsMarkdown(cwd) {
|
|
|
63
63
|
// Only extract rules from actionable sections, not from descriptive sections
|
|
64
64
|
// like "why this matters" which contain explanatory bullets, not instructions.
|
|
65
65
|
const SKIP_SECTIONS = /why this matters|what it provides|what brainclaw/i;
|
|
66
|
-
let currentSection
|
|
66
|
+
let currentSection;
|
|
67
67
|
let skipSection = false;
|
|
68
68
|
const rules = [];
|
|
69
69
|
for (const line of lines) {
|
package/dist/core/agent-files.js
CHANGED
|
@@ -1158,7 +1158,7 @@ export function ensureClaudeCodeCommand(cwd) {
|
|
|
1158
1158
|
relativePath: CLAUDE_CODE_COMMAND_RELATIVE_PATH,
|
|
1159
1159
|
};
|
|
1160
1160
|
}
|
|
1161
|
-
export function ensureClaudeCodeUserSettings(homeDir,
|
|
1161
|
+
export function ensureClaudeCodeUserSettings(homeDir, _env = process.env) {
|
|
1162
1162
|
if (!homeDir)
|
|
1163
1163
|
return undefined;
|
|
1164
1164
|
const filePath = path.join(homeDir, '.claude', 'settings.json');
|
|
@@ -1630,7 +1630,6 @@ export function ensureCodexMcpConfig(homeDir, env = process.env) {
|
|
|
1630
1630
|
const autoApprovedSet = new Set(getHeadlessAutoApprovedToolNames());
|
|
1631
1631
|
const toolSectionRe = /^\[mcp_servers\.brainclaw\.tools\.([^\]]+)\]/gm;
|
|
1632
1632
|
const approvalModeRe = /^\s*approval_mode\s*=\s*"([^"]+)"/m;
|
|
1633
|
-
let m;
|
|
1634
1633
|
const warnings = [];
|
|
1635
1634
|
// Split into sections to check each tool block
|
|
1636
1635
|
const lines = existing.split('\n');
|
|
@@ -160,8 +160,8 @@ export function extractMcpCommandVal(agentName, expectedPath) {
|
|
|
160
160
|
return { is_valid: false };
|
|
161
161
|
}
|
|
162
162
|
if (expectedPath.endsWith('.toml')) {
|
|
163
|
-
const cmdMatch = content.match(/\[mcp_servers\.brainclaw\](?:[
|
|
164
|
-
const argsMatch = content.match(/\[mcp_servers\.brainclaw\](?:[
|
|
163
|
+
const cmdMatch = content.match(/\[mcp_servers\.brainclaw\](?:[^[]*)command\s*=\s*(["'])(.+?)\1/is);
|
|
164
|
+
const argsMatch = content.match(/\[mcp_servers\.brainclaw\](?:[^[]*)args\s*=\s*\[(.+?)\]/is);
|
|
165
165
|
let args;
|
|
166
166
|
if (argsMatch) {
|
|
167
167
|
args = argsMatch[1]
|
|
@@ -274,7 +274,7 @@ export function assessAgentIntegrationReadiness(config, cwd, env = process.env)
|
|
|
274
274
|
const surfaces = declaration.surfaces.map((surface) => surfaceExists(surface, cwd, env, declaration.agent_name));
|
|
275
275
|
const missingSurfaces = surfaces.filter((surface) => !surface.exists);
|
|
276
276
|
const driftingSurfaces = surfaces.filter((surface) => surface.drift_message != null);
|
|
277
|
-
let effectiveTier
|
|
277
|
+
let effectiveTier;
|
|
278
278
|
const selfHealingGuidance = [];
|
|
279
279
|
const hasMissingMcpOrHook = missingSurfaces.some((s) => s.kind === 'mcp' || s.kind === 'hook');
|
|
280
280
|
const hasDriftingMcp = driftingSurfaces.some((s) => s.kind === 'mcp');
|
|
@@ -275,7 +275,7 @@ export function registerAgentIdentity(input) {
|
|
|
275
275
|
saveAgentIdentity(created, input.cwd, input.preferredDirName);
|
|
276
276
|
return created;
|
|
277
277
|
}
|
|
278
|
-
export function resolveCurrentAgentIdentity(cwd, preferredDirName,
|
|
278
|
+
export function resolveCurrentAgentIdentity(cwd, preferredDirName, _homeDir) {
|
|
279
279
|
// env var takes priority over config — allows AI agent to self-identify
|
|
280
280
|
const envAgentId = (process.env.BRAINCLAW_AGENT_ID ?? '').trim();
|
|
281
281
|
const envAgentName = (process.env.BRAINCLAW_AGENT_NAME ?? process.env.BRAINCLAW_AGENT ?? '').trim();
|
|
@@ -537,7 +537,7 @@ export function resolveCurrentModel(cwd) {
|
|
|
537
537
|
* Note: config.current_agent is intentionally NOT used here — it's a singleton
|
|
538
538
|
* global that causes cross-agent confusion in multi-agent setups.
|
|
539
539
|
*/
|
|
540
|
-
export function resolveCurrentAgentName(
|
|
540
|
+
export function resolveCurrentAgentName(_cwd, _homeDir) {
|
|
541
541
|
const fromEnv = (process.env.BRAINCLAW_AGENT_NAME ?? process.env.BRAINCLAW_AGENT)?.trim();
|
|
542
542
|
if (fromEnv)
|
|
543
543
|
return fromEnv;
|
package/dist/core/assignments.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assignment lifecycle — Agent SDK runtime protocol.
|
|
3
|
+
*
|
|
4
|
+
* An Assignment is the canonical coordination entity that tracks the full
|
|
5
|
+
* lifecycle of dispatched work: from creation through offer, acceptance,
|
|
6
|
+
* execution, and completion (or failure/timeout/reroute).
|
|
7
|
+
*
|
|
8
|
+
* Assignments reference Claims (scope lock) and InboxMessages (brief delivery)
|
|
9
|
+
* but don't replace them. They own the status FSM and heartbeat tracking.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
1
13
|
import fs from 'node:fs';
|
|
2
14
|
import { AssignmentSchema } from './schema.js';
|
|
3
15
|
import { resolveEntityDir } from './io.js';
|
|
@@ -555,7 +555,7 @@ function readWorkspaceBrainclawPackage(cwd) {
|
|
|
555
555
|
}
|
|
556
556
|
catch (error) {
|
|
557
557
|
const message = error instanceof Error ? error.message : String(error);
|
|
558
|
-
throw new Error(`Failed to read package.json: ${message}
|
|
558
|
+
throw new Error(`Failed to read package.json: ${message}`, { cause: error });
|
|
559
559
|
}
|
|
560
560
|
const name = typeof parsed.name === 'string' ? parsed.name.trim() : '';
|
|
561
561
|
const version = typeof parsed.version === 'string' ? parsed.version.trim() : '';
|
|
@@ -617,7 +617,7 @@ function parseNpmDistTags(stdout) {
|
|
|
617
617
|
}
|
|
618
618
|
catch (error) {
|
|
619
619
|
const message = error instanceof Error ? error.message : String(error);
|
|
620
|
-
throw new Error(`npm view returned invalid JSON: ${message}
|
|
620
|
+
throw new Error(`npm view returned invalid JSON: ${message}`, { cause: error });
|
|
621
621
|
}
|
|
622
622
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
623
623
|
throw new Error('npm view did not return a dist-tag object.');
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
* return not-yet-implemented placeholders that still carry a real
|
|
9
9
|
* `freshness_badge`, locking the response shape for later sprints.
|
|
10
10
|
*/
|
|
11
|
+
import { execFileSync } from 'node:child_process';
|
|
11
12
|
import path from 'node:path';
|
|
12
13
|
import { readManifest, storeExists } from './store.js';
|
|
13
14
|
import { refresh as runRefresh } from './refresh.js';
|
|
15
|
+
import { applyGitHeadDrift } from './freshness.js';
|
|
14
16
|
import { brief as runBrief, find as runFind } from './query.js';
|
|
15
17
|
import { defaultMemoryReader } from './memory-reader.js';
|
|
16
18
|
/** spec §9 caps the brief reading list at 12 files. */
|
|
@@ -18,6 +20,31 @@ export const BRIEF_FILE_CAP = 12;
|
|
|
18
20
|
function badge(status, details = {}) {
|
|
19
21
|
return { status, details };
|
|
20
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Read the working tree's current commit at `root` (read-path git-HEAD drift,
|
|
25
|
+
* trp_42688015). Returns null on any failure or a non-git project (also detached
|
|
26
|
+
* HEAD resolves to the commit sha, which is the correct comparison key) — a null
|
|
27
|
+
* makes the comparison a no-op, preserving existing behaviour.
|
|
28
|
+
*
|
|
29
|
+
* COST (review finding, LOW): one synchronous `git rev-parse HEAD` per status/
|
|
30
|
+
* find/brief call. These are interactive, human-/agent-paced reads (not a tight
|
|
31
|
+
* loop), so a single ~5–15ms spawn is acceptable and keeps branch-switch detection
|
|
32
|
+
* immediate. If this ever shows up on a profile, memoize per `root` behind a short
|
|
33
|
+
* TTL (a few seconds) — short enough that a checkout is still caught promptly.
|
|
34
|
+
*/
|
|
35
|
+
function readCurrentGitHead(root) {
|
|
36
|
+
try {
|
|
37
|
+
const out = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
38
|
+
cwd: root,
|
|
39
|
+
encoding: 'utf-8',
|
|
40
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
41
|
+
}).trim();
|
|
42
|
+
return out || null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
21
48
|
/**
|
|
22
49
|
* P0 JSONL-backed query backend. Reads the durable file store (manifest +
|
|
23
50
|
* shards + indexes); no graph DB. find()/brief() are stubbed for Sprint 1.
|
|
@@ -28,8 +55,11 @@ export class JsonlBackend {
|
|
|
28
55
|
* path; tests inject an in-memory reader to assert attachment without a store.
|
|
29
56
|
*/
|
|
30
57
|
memoryReader;
|
|
58
|
+
/** Read-path git-HEAD reader (injectable for tests). trp_42688015. */
|
|
59
|
+
gitHeadReader;
|
|
31
60
|
constructor(opts = {}) {
|
|
32
61
|
this.memoryReader = opts.memoryReader ?? defaultMemoryReader;
|
|
62
|
+
this.gitHeadReader = opts.gitHeadReader ?? readCurrentGitHead;
|
|
33
63
|
}
|
|
34
64
|
async status(input) {
|
|
35
65
|
const manifest = readManifest(input.cwd, input.preferredDirName);
|
|
@@ -40,12 +70,13 @@ export class JsonlBackend {
|
|
|
40
70
|
stats: null,
|
|
41
71
|
};
|
|
42
72
|
}
|
|
73
|
+
const base = badge(manifest.freshness.status, {
|
|
74
|
+
stale_file_count: manifest.freshness.stale_file_count,
|
|
75
|
+
partial_reason: manifest.freshness.partial_reason,
|
|
76
|
+
});
|
|
43
77
|
return {
|
|
44
78
|
store_exists: true,
|
|
45
|
-
freshness_badge:
|
|
46
|
-
stale_file_count: manifest.freshness.stale_file_count,
|
|
47
|
-
partial_reason: manifest.freshness.partial_reason,
|
|
48
|
-
}),
|
|
79
|
+
freshness_badge: this.withHeadDrift(base, manifest, input.cwd),
|
|
49
80
|
stats: {
|
|
50
81
|
files_indexed: manifest.stats.files_indexed,
|
|
51
82
|
nodes: manifest.stats.nodes,
|
|
@@ -95,10 +126,15 @@ export class JsonlBackend {
|
|
|
95
126
|
async find(input) {
|
|
96
127
|
const ctx = this.queryContext(input);
|
|
97
128
|
const out = runFind(input.query, input.limit, ctx);
|
|
129
|
+
const manifest = readManifest(input.cwd, input.preferredDirName);
|
|
130
|
+
const base = {
|
|
131
|
+
status: out.freshness_badge.status,
|
|
132
|
+
details: out.freshness_badge.details,
|
|
133
|
+
};
|
|
98
134
|
return {
|
|
99
135
|
query: out.query,
|
|
100
136
|
matches: out.matches,
|
|
101
|
-
freshness_badge:
|
|
137
|
+
freshness_badge: this.withHeadDrift(base, manifest, input.cwd),
|
|
102
138
|
};
|
|
103
139
|
}
|
|
104
140
|
/**
|
|
@@ -109,13 +145,30 @@ export class JsonlBackend {
|
|
|
109
145
|
async brief(input) {
|
|
110
146
|
const ctx = this.queryContext(input);
|
|
111
147
|
const out = runBrief(input.target, input.limit, ctx, this.memoryReader);
|
|
148
|
+
const manifest = readManifest(input.cwd, input.preferredDirName);
|
|
149
|
+
const base = {
|
|
150
|
+
status: out.freshness_badge.status,
|
|
151
|
+
details: out.freshness_badge.details,
|
|
152
|
+
};
|
|
112
153
|
return {
|
|
113
154
|
target: out.target,
|
|
114
155
|
suggested_files_to_read: out.suggested_files_to_read,
|
|
115
156
|
related_memory: out.related_memory,
|
|
116
|
-
freshness_badge:
|
|
157
|
+
freshness_badge: this.withHeadDrift(base, manifest, input.cwd),
|
|
117
158
|
};
|
|
118
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Annotate a read badge with git-HEAD drift vs the commit the index was built
|
|
162
|
+
* against (`manifest.git.head`). trp_42688015 — a branch switch (whole-tree move)
|
|
163
|
+
* is otherwise unflagged because `status` reads only write-side freshness and the
|
|
164
|
+
* per-file lazy check is query-scoped + budgeted. No-op for non-git projects.
|
|
165
|
+
*/
|
|
166
|
+
withHeadDrift(base, manifest, cwd) {
|
|
167
|
+
if (!manifest)
|
|
168
|
+
return base;
|
|
169
|
+
const root = manifest.project_root || cwd || process.cwd();
|
|
170
|
+
return applyGitHeadDrift(base, manifest.git.head, this.gitHeadReader(root));
|
|
171
|
+
}
|
|
119
172
|
queryContext(input) {
|
|
120
173
|
return { cwd: input.cwd, preferredDirName: input.preferredDirName };
|
|
121
174
|
}
|
|
@@ -105,4 +105,40 @@ export function summarizeFreshness(shards) {
|
|
|
105
105
|
status = 'stale_grammar';
|
|
106
106
|
return { status, stale_file_count: staleTotal, partial_reason: null };
|
|
107
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Read-path git-HEAD drift (trp_42688015).
|
|
110
|
+
*
|
|
111
|
+
* The index records the commit it was built against (`manifest.git.head`). The
|
|
112
|
+
* per-file lazy read check (query.ts §6.1) only samples a query's candidate files
|
|
113
|
+
* within a bounded budget, and `status` reports ONLY the write-side manifest
|
|
114
|
+
* freshness (extractor/grammar hashes) — neither keys on git HEAD. So a whole-tree
|
|
115
|
+
* move such as `git checkout <other-branch>` left the index reported `fresh`, and
|
|
116
|
+
* find/brief could serve OLD-branch paths/symbols. This compares the index head to
|
|
117
|
+
* the working tree's current head and, when they differ, sets a clean `fresh` badge
|
|
118
|
+
* to the dedicated `stale_git_head` reason, recording the precise cause in
|
|
119
|
+
* `details.git_head_changed`.
|
|
120
|
+
*
|
|
121
|
+
* `stale_git_head` is kept DISTINCT from `stale_changed_files` (review finding):
|
|
122
|
+
* the latter means CONFIRMED per-file content/path drift (and carries a real
|
|
123
|
+
* `stale_file_count`); a HEAD move is a weaker signal — "the index was built at
|
|
124
|
+
* another commit, refresh recommended" — and must not masquerade as confirmed file
|
|
125
|
+
* changes with a contradictory `stale_file_count: 0`.
|
|
126
|
+
*
|
|
127
|
+
* No-op when either head is unknown (non-git project, older manifest) or the heads
|
|
128
|
+
* match — so existing fresh/non-git behaviour is unchanged. A badge that is already
|
|
129
|
+
* non-`fresh` (stale_*, partial, missing_index) keeps its more-specific/equally-
|
|
130
|
+
* actionable status; only the cause detail is added.
|
|
131
|
+
*/
|
|
132
|
+
export function applyGitHeadDrift(badge, indexHead, currentHead) {
|
|
133
|
+
if (!indexHead || !currentHead || indexHead === currentHead)
|
|
134
|
+
return badge;
|
|
135
|
+
const status = badge.status === 'fresh' ? 'stale_git_head' : badge.status;
|
|
136
|
+
return {
|
|
137
|
+
status,
|
|
138
|
+
details: {
|
|
139
|
+
...badge.details,
|
|
140
|
+
git_head_changed: { index_head: indexHead, current_head: currentHead },
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
108
144
|
//# sourceMappingURL=freshness.js.map
|
|
@@ -66,7 +66,7 @@ function validateEntry(entry, checker, acc, projectRoot, maxParseFileBytes, cwd,
|
|
|
66
66
|
if (cached !== undefined)
|
|
67
67
|
return cached;
|
|
68
68
|
const abs = path.join(projectRoot, entry.path);
|
|
69
|
-
let stat
|
|
69
|
+
let stat;
|
|
70
70
|
try {
|
|
71
71
|
stat = fs.statSync(abs);
|
|
72
72
|
}
|
|
@@ -158,6 +158,14 @@ function deriveBadge(base, acc, budgetExhausted, hadConfidentMatch, emptyIndex)
|
|
|
158
158
|
details.partial_reason = 'lazy_check_budget_exhausted';
|
|
159
159
|
details.budget = { ...LAZY_BUDGET };
|
|
160
160
|
}
|
|
161
|
+
// pln#593 #2 — distinguish INDEX freshness (manifest state) from THIS call's
|
|
162
|
+
// read-path spot-check. When the call-level status diverges from the index
|
|
163
|
+
// status (a budget-limited `partial`, or a per-file `stale_changed_files` over a
|
|
164
|
+
// `fresh` index), surface the index status so an agent does not read
|
|
165
|
+
// status()=fresh vs find()/brief()=partial as a contradiction: it's "index
|
|
166
|
+
// <index_status>, this call's spot-check <status>".
|
|
167
|
+
if (status !== base)
|
|
168
|
+
details.index_status = base;
|
|
161
169
|
void hadConfidentMatch;
|
|
162
170
|
return { status, details };
|
|
163
171
|
}
|
|
@@ -460,6 +468,17 @@ function reverseDeps(resolutionIndex, definingPaths, definingByNodeId) {
|
|
|
460
468
|
}
|
|
461
469
|
return [...byPath.values()];
|
|
462
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* Heuristic: does the brief target denote a file PATH rather than a bare symbol
|
|
473
|
+
* name? A path separator or a supported source extension marks a path target —
|
|
474
|
+
* for which we resolve the exact file directly instead of fuzzy-tokenizing the
|
|
475
|
+
* name. Fuzzy-tokenizing a path floods the brief with unrelated same-token symbols
|
|
476
|
+
* (e.g. brief('src/commands/switch.ts') pulling in every `switch`-named symbol and
|
|
477
|
+
* code-map test). pln#593 1b.
|
|
478
|
+
*/
|
|
479
|
+
function looksLikePathTarget(target) {
|
|
480
|
+
return /[\\/]/.test(target) || /\.(?:ts|tsx|js|jsx|mjs|cjs|py|php|java)$/i.test(target);
|
|
481
|
+
}
|
|
463
482
|
/** Find files whose path matches the target directly (path-target briefs). */
|
|
464
483
|
function filesMatchingPath(symbolsIndex, target) {
|
|
465
484
|
const norm = target.replace(/\\/g, '/');
|
|
@@ -494,12 +513,24 @@ export function brief(target, limit, ctx, memoryReader) {
|
|
|
494
513
|
// result with unrelated same-token symbols (e.g. `resolveProjectImports` would pull
|
|
495
514
|
// in every `resolve*`), burying the real defining file + its graph signals. Fall
|
|
496
515
|
// back to the fuzzy token set, then to a path match. (find() stays fuzzy by design.)
|
|
497
|
-
let defining
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
516
|
+
let defining;
|
|
517
|
+
if (looksLikePathTarget(target)) {
|
|
518
|
+
// PATH target (pln#593 1b): resolve the exact file; the graph signals (its
|
|
519
|
+
// imports / dependents / direct tests) then rank below it via rankFiles. Skip
|
|
520
|
+
// the fuzzy token gather entirely — it floods a path brief with same-token
|
|
521
|
+
// noise. Degrade to the fuzzy set only if the path resolves to nothing indexed.
|
|
502
522
|
defining = filesMatchingPath(symbolsIndex, target);
|
|
523
|
+
if (defining.length === 0)
|
|
524
|
+
defining = gatherSymbolEntries(symbolsIndex, target);
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
defining = gatherSymbolEntries(symbolsIndex, target);
|
|
528
|
+
const exact = defining.filter((e) => e.name.toLowerCase() === target.toLowerCase());
|
|
529
|
+
if (exact.length > 0)
|
|
530
|
+
defining = exact;
|
|
531
|
+
else if (defining.length === 0)
|
|
532
|
+
defining = filesMatchingPath(symbolsIndex, target);
|
|
533
|
+
}
|
|
503
534
|
const root = resolveRoot(ctx);
|
|
504
535
|
const maxBytes = maxParseBytes(ctx);
|
|
505
536
|
const checker = makeLazyChecker();
|
|
Binary file
|
|
@@ -44,6 +44,11 @@ export const FreshnessStatusSchema = z.enum([
|
|
|
44
44
|
'stale_changed_files',
|
|
45
45
|
'stale_extractor',
|
|
46
46
|
'stale_grammar',
|
|
47
|
+
// Read-path only (trp_42688015): the index was built against a different git
|
|
48
|
+
// commit than the working tree's current HEAD (e.g. after `git checkout`). Kept
|
|
49
|
+
// DISTINCT from `stale_changed_files` (confirmed per-file content drift) so a
|
|
50
|
+
// HEAD move is not misreported as confirmed changes — see applyGitHeadDrift.
|
|
51
|
+
'stale_git_head',
|
|
47
52
|
'partial',
|
|
48
53
|
'missing_index',
|
|
49
54
|
]);
|
|
@@ -158,7 +158,7 @@ async function loadEngineGlue() {
|
|
|
158
158
|
// '../../vendor/...' === dist/vendor/...
|
|
159
159
|
let mod;
|
|
160
160
|
const vendored = new URL('../../vendor/web-tree-sitter/tree-sitter.js', import.meta.url);
|
|
161
|
-
let vendoredExists
|
|
161
|
+
let vendoredExists;
|
|
162
162
|
try {
|
|
163
163
|
vendoredExists = fs.existsSync(fileURLToPath(vendored));
|
|
164
164
|
}
|
package/dist/core/context.js
CHANGED
|
@@ -65,7 +65,7 @@ export function detectCrossProjectCycles(cwd) {
|
|
|
65
65
|
const baseCwd = path.resolve(cwd ?? process.cwd());
|
|
66
66
|
const cycles = [];
|
|
67
67
|
function walk(currentCwd, visited) {
|
|
68
|
-
let links
|
|
68
|
+
let links;
|
|
69
69
|
try {
|
|
70
70
|
links = resolveCrossProjectLinks(currentCwd);
|
|
71
71
|
}
|
package/dist/core/dispatcher.js
CHANGED
|
@@ -53,7 +53,6 @@ import { sweepAssignments } from './assignment-sweeper.js';
|
|
|
53
53
|
import { InboxMessageSchema } from './schema.js';
|
|
54
54
|
import { generateId, nowISO } from './ids.js';
|
|
55
55
|
import { applyHandoffUpdates } from '../commands/update-handoff.js';
|
|
56
|
-
const MAX_INLINE_BRIEF_LENGTH = 4000;
|
|
57
56
|
/**
|
|
58
57
|
* Build a cross-platform env prefix for BRAINCLAW_CLAIM_ID. Delegates to
|
|
59
58
|
* the centralised buildClaimEnvPrefix in src/core/execution-profile.ts
|
|
@@ -555,7 +554,6 @@ export function scoreAgents(agentPool, plan, activeClaims, cycleAssignments) {
|
|
|
555
554
|
for (const claim of activeClaims) {
|
|
556
555
|
claimCounts.set(claim.agent, (claimCounts.get(claim.agent) ?? 0) + 1);
|
|
557
556
|
}
|
|
558
|
-
const maxClaims = Math.max(1, ...claimCounts.values());
|
|
559
557
|
return agentPool.map(agent => {
|
|
560
558
|
// Factor 1: Preference — is this the plan's assignee?
|
|
561
559
|
const preference = (plan.assignee === agent) ? 1.0 : 0.0;
|
|
@@ -212,9 +212,6 @@ function loadAll(name, cwd) {
|
|
|
212
212
|
throw new EntityOperationUnsupportedError(name, 'find');
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
|
-
function applyFilter(items, filter) {
|
|
216
|
-
return applyFieldFilter(items, filter).filter((item) => passesProvenanceFilter(item, filter));
|
|
217
|
-
}
|
|
218
215
|
function applyFieldFilter(items, filter) {
|
|
219
216
|
let result = items;
|
|
220
217
|
if (filter.status) {
|
package/dist/core/ids.js
CHANGED
|
@@ -48,7 +48,7 @@ export function getNextShortLabel(prefix, cwd, preferredDirName) {
|
|
|
48
48
|
}
|
|
49
49
|
catch (error) {
|
|
50
50
|
if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT') {
|
|
51
|
-
throw new Error(`Could not read short-label counter ${fp}: ${error instanceof Error ? error.message : String(error)}
|
|
51
|
+
throw new Error(`Could not read short-label counter ${fp}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
const next = (counter[prefix] ?? 0) + 1;
|
|
@@ -323,7 +323,7 @@ function renderAvailableTools() {
|
|
|
323
323
|
'**Session/claims:** `bclaw_session_start`, `bclaw_session_end`, `bclaw_claim`, `bclaw_release_claim` · **steps:** `bclaw_add_step`, `bclaw_complete_step`, `bclaw_update_step`, `bclaw_delete_step` · **sequences:** `bclaw_list_sequences`, `bclaw_create_sequence`, `bclaw_update_sequence`, `bclaw_delete_sequence`',
|
|
324
324
|
'**Inbox:** `bclaw_read_inbox`, `bclaw_ack_message`, `bclaw_send_message`, `bclaw_correct_handoff` · **capture:** `bclaw_write_note`, `bclaw_quick_capture(text, type?)` · **search:** `bclaw_search` · **setup:** `bclaw_setup`, `bclaw_bootstrap`, `bclaw_switch`, `bclaw_release_notes`',
|
|
325
325
|
'**Escalation (orchestrators):** `bclaw_coordinate(intent=review|consult|assign|ideate)` · `bclaw_dispatch(intent=execute)` on an active sequence · `bclaw_loop(intent=turn|complete_turn|advance|close)` to drive turns · `bclaw_dispatch_status(target_id)` to verify',
|
|
326
|
-
'**Code discovery (Code Map):** `bclaw_code_find(query)` locate a symbol/class/component · `bclaw_code_brief(target)` ranked reading list + related memory before editing · `bclaw_code_status` / `bclaw_code_refresh` check freshness / rebuild the symbol+import index.
|
|
326
|
+
'**Code discovery (Code Map):** `bclaw_code_find(query)` locate a symbol/class/component · `bclaw_code_brief(target)` ranked reading list + related memory before editing · `bclaw_code_status` / `bclaw_code_refresh` check freshness / rebuild the symbol+import index. **Reach for these before `rg`/grep on unfamiliar code** — and if your harness defers MCP tools, `tool_search` for `bclaw_code_*` first rather than falling back to a blind grep. See `docs/code-map.md`.',
|
|
327
327
|
'',
|
|
328
328
|
'Responses are self-teaching — follow their `next_actions`. Full catalog + stability contract: `docs/integrations/mcp.md`, `docs/concepts/mcp-governance.md`.',
|
|
329
329
|
].join('\n');
|
package/dist/core/loops/lock.js
CHANGED
|
@@ -167,9 +167,6 @@ function loadIdempotencyRecord(kind, key, cwd) {
|
|
|
167
167
|
function readLockBlob(lockPath) {
|
|
168
168
|
return readJsonIfExists(lockPath) ?? null;
|
|
169
169
|
}
|
|
170
|
-
function writeLockAtomic(lockPath, blob) {
|
|
171
|
-
writeJsonAtomic(lockPath, blob);
|
|
172
|
-
}
|
|
173
170
|
function processIsAlive(pid) {
|
|
174
171
|
try {
|
|
175
172
|
// Signal 0 just checks for the process's existence without actually sending a signal.
|
|
@@ -34,16 +34,18 @@ reachable (cold start, or a worktree without \`.brainclaw/\`).
|
|
|
34
34
|
|
|
35
35
|
1. \`bclaw_work(intent='consult')\` — loads project memory and reports active claims. Read \`bootstrap_recommended\`: if true the project has no usable PROJECT.md (see \`brainclaw-multi-agent\` → bootstrap loop).
|
|
36
36
|
2. If you will edit a scope, claim it: \`bclaw_work(intent='execute', scope='<path-or-feature>')\`. The response's \`claim_id\` is yours; \`claim_status='created'\` = new, \`'existing'\` = resumed.
|
|
37
|
-
3.
|
|
38
|
-
4.
|
|
37
|
+
3. Before editing unfamiliar code, orient with the **Code Map** instead of grepping blind: \`bclaw_code_brief(target='<symbol|path>')\` for a ranked reading list — the defining file, its importers, and related decisions/traps — or \`bclaw_code_find(query='<name>')\` to locate a symbol/class/component. A \`missing_index\`/stale badge means run \`bclaw_code_refresh\` first.
|
|
38
|
+
4. Do the work. Honor the \`warnings\` array (claim conflicts, sensitive paths, high-severity traps on your scope).
|
|
39
|
+
5. When done (committed, tested), \`bclaw_session_end(autoRelease: true)\` — closes the session record and releases your remaining claims. \`autoRelease\` defaults to false; pass it explicitly or claims survive the session.
|
|
39
40
|
|
|
40
|
-
CLI fallback: \`brainclaw context --json\` · \`brainclaw claim create "<desc>" --scope <path>\` · \`brainclaw session-end --auto-release\`.
|
|
41
|
+
CLI fallback: \`brainclaw context --json\` · \`brainclaw code-map brief <symbol>\` / \`brainclaw code-map find <name>\` · \`brainclaw claim create "<desc>" --scope <path>\` · \`brainclaw session-end --auto-release\`.
|
|
41
42
|
|
|
42
43
|
## Anti-rationalizations
|
|
43
44
|
|
|
44
45
|
- **"I'm just exploring, I'll skip session-end."** → A live claim outlives a crash. The next agent sees your stale claim and is blocked. Calling \`bclaw_session_end(autoRelease: true)\` is the zero-cost guarantee — without the flag the claim survives.
|
|
45
46
|
- **"I know the project, I don't need to consult."** → State changes between sessions (commits, new traps, new constraints). Consult is cheap and surfaces what you'd miss.
|
|
46
47
|
- **"I'll claim later once I know the exact scope."** → Claim-before-edit IS the contract; it is exactly what prevents races with parallel agents.
|
|
48
|
+
- **"I'll just grep for it."** → On an unfamiliar or large repo, grep floods you with noise and misses the blast radius. \`bclaw_code_brief\` returns the defining file + its importers + related memory in one ranked call; fall back to grep only when the Code Map index is missing.
|
|
47
49
|
|
|
48
50
|
## Red flags
|
|
49
51
|
|
|
@@ -26,7 +26,7 @@ export const BUILTIN_DETECTORS = [
|
|
|
26
26
|
{ id: 'aws_access_key', label: 'AWS access key ID', pattern: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
27
27
|
{ id: 'aws_temp_access_key', label: 'AWS temporary access key ID', pattern: /\bASIA[0-9A-Z]{16}\b/ },
|
|
28
28
|
// Google
|
|
29
|
-
{ id: 'google_api_key', label: 'Google API key', pattern: /\bAIza[0-9A-Za-z_
|
|
29
|
+
{ id: 'google_api_key', label: 'Google API key', pattern: /\bAIza[0-9A-Za-z_-]{35}\b/ },
|
|
30
30
|
// Slack
|
|
31
31
|
{ id: 'slack_token', label: 'Slack token', pattern: /\bxox[abprs]-[0-9A-Za-z-]{10,}\b/ },
|
|
32
32
|
{ id: 'slack_webhook', label: 'Slack webhook', pattern: /https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]{8,}\/B[A-Z0-9]{8,}\/[A-Za-z0-9]{24,}/ },
|
|
@@ -80,7 +80,7 @@ export function shannonEntropy(s) {
|
|
|
80
80
|
return h;
|
|
81
81
|
}
|
|
82
82
|
const SECRET_KEYWORD_CONTEXT = /(?:api[_-]?key|secret|token|password|passwd|auth|bearer|access[_-]?key|private[_-]?key)/i;
|
|
83
|
-
const HIGH_ENTROPY_CANDIDATE = /[A-Za-z0-9_
|
|
83
|
+
const HIGH_ENTROPY_CANDIDATE = /[A-Za-z0-9_+/=-]{16,}/g;
|
|
84
84
|
/**
|
|
85
85
|
* Entropy-based detection. Scans `text` for token-shaped substrings near
|
|
86
86
|
* a sensitive keyword. Two-stage gating keeps false positives low:
|
|
@@ -126,7 +126,7 @@ export function parseLockfile(filePath) {
|
|
|
126
126
|
parsed = JSON.parse(raw);
|
|
127
127
|
}
|
|
128
128
|
catch (err) {
|
|
129
|
-
throw new Error(`Failed to parse lockfile ${filePath}: ${err.message}
|
|
129
|
+
throw new Error(`Failed to parse lockfile ${filePath}: ${err.message}`, { cause: err });
|
|
130
130
|
}
|
|
131
131
|
if (!parsed || typeof parsed !== 'object')
|
|
132
132
|
return [];
|
|
@@ -175,7 +175,7 @@ function readFileOrThrow(p, label) {
|
|
|
175
175
|
return fs.readFileSync(p, 'utf-8');
|
|
176
176
|
}
|
|
177
177
|
catch (err) {
|
|
178
|
-
throw new Error(`Could not read ${label} at ${p}: ${err.message}
|
|
178
|
+
throw new Error(`Could not read ${label} at ${p}: ${err.message}`, { cause: err });
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
function resolveSibling(parent, nested) {
|
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.10.
|
|
2
|
+
// Source: brainclaw v1.10.1 on 2026-06-21T19:26:16.599Z
|
|
3
3
|
export const FACTS = {
|
|
4
|
-
"version": "1.10.
|
|
5
|
-
"generated_at": "2026-06-
|
|
4
|
+
"version": "1.10.1",
|
|
5
|
+
"generated_at": "2026-06-21T19:26:16.599Z",
|
|
6
6
|
"tools": {
|
|
7
7
|
"count": 66,
|
|
8
8
|
"published_count": 65,
|
package/dist/facts.json
CHANGED
package/docs/code-map.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Code Map
|
|
2
2
|
|
|
3
|
-
Code Map is a per-project structural index of your JavaScript / TypeScript /
|
|
4
|
-
TSX codebase. It parses each supported file with Tree-sitter and records the
|
|
3
|
+
Code Map is a per-project structural index of your JavaScript / TypeScript / JSX /
|
|
4
|
+
TSX, Python, PHP, and Java codebase. It parses each supported file with Tree-sitter and records the
|
|
5
5
|
symbols it defines (functions, classes, types, interfaces, React components and
|
|
6
6
|
hooks), what it imports and exports, and how files relate — then answers fast
|
|
7
7
|
"what should I read before I edit this?" questions for both human operators and
|
|
@@ -130,6 +130,17 @@ which is independent from a parser-binary change (`stale_grammar`). The badge
|
|
|
130
130
|
surfaces the dominant reason; `--json` output and the manifest carry the per-file
|
|
131
131
|
counts.
|
|
132
132
|
|
|
133
|
+
**Index freshness vs this call's spot-check.** `bclaw_code_status` reports the
|
|
134
|
+
*index* freshness (the manifest state). `bclaw_code_find` / `bclaw_code_brief`
|
|
135
|
+
additionally run a bounded, per-query *spot-check* of the files they actually
|
|
136
|
+
touch — so a single call can read `stale_changed_files` (a file it looked at
|
|
137
|
+
changed on disk) or `partial` (the spot-check hit its budget) even while the index
|
|
138
|
+
itself is `fresh`. When the call-level status diverges from the index, the badge
|
|
139
|
+
carries an `index_status` detail so the two are not confused, e.g.
|
|
140
|
+
`{ status: "partial", details: { index_status: "fresh", partial_reason:
|
|
141
|
+
"lazy_check_budget_exhausted" } }` reads as *"index fresh, this call's spot-check
|
|
142
|
+
incomplete (budget)"* — not a contradiction with a `fresh` `status()`.
|
|
143
|
+
|
|
133
144
|
## Lifecycle — pull-based, no daemon
|
|
134
145
|
|
|
135
146
|
Code Map never runs in the background and never auto-reindexes. The model is lazy
|
|
@@ -180,7 +191,7 @@ like any other directory.
|
|
|
180
191
|
|
|
181
192
|
The parser is [Tree-sitter](https://tree-sitter.github.io/) compiled to
|
|
182
193
|
WebAssembly. The engine glue (`web-tree-sitter`) and the prebuilt grammar `.wasm`
|
|
183
|
-
files (TypeScript /
|
|
194
|
+
files (JavaScript / TypeScript / JSX / TSX, Python, PHP, Java) are **bundled into the package** during the
|
|
184
195
|
build (`scripts/copy-code-map-wasm.mjs` copies them into `dist/wasm/` and vendors
|
|
185
196
|
the engine glue into `dist/vendor/web-tree-sitter/`).
|
|
186
197
|
|