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.
Files changed (39) hide show
  1. package/README.md +32 -25
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/commands/dispatch-watch.js +1 -1
  4. package/dist/commands/doctor.js +3 -5
  5. package/dist/commands/loops-handlers.js +4 -1
  6. package/dist/commands/mcp-read-handlers.js +8 -0
  7. package/dist/commands/mcp.js +1 -2
  8. package/dist/commands/metrics.js +0 -1
  9. package/dist/commands/release-claims.js +1 -1
  10. package/dist/commands/sequence.js +1 -1
  11. package/dist/commands/sync.js +1 -1
  12. package/dist/commands/upgrade.js +0 -7
  13. package/dist/core/agent-context.js +1 -1
  14. package/dist/core/agent-files.js +1 -2
  15. package/dist/core/agent-integrations.js +3 -3
  16. package/dist/core/agent-registry.js +2 -2
  17. package/dist/core/assignments.js +12 -0
  18. package/dist/core/brainclaw-version.js +2 -2
  19. package/dist/core/code-map/backend.js +59 -6
  20. package/dist/core/code-map/freshness.js +36 -0
  21. package/dist/core/code-map/query.js +37 -6
  22. package/dist/core/code-map/refresh.js +0 -0
  23. package/dist/core/code-map/types.js +5 -0
  24. package/dist/core/code-map/wasm-loader.js +1 -1
  25. package/dist/core/context.js +1 -1
  26. package/dist/core/cross-project.js +1 -1
  27. package/dist/core/dispatcher.js +0 -2
  28. package/dist/core/entity-operations.js +0 -3
  29. package/dist/core/ids.js +1 -1
  30. package/dist/core/instruction-templates.js +1 -1
  31. package/dist/core/instructions.js +0 -1
  32. package/dist/core/loops/lock.js +0 -3
  33. package/dist/core/protocol-skills.js +5 -3
  34. package/dist/core/security-detectors.js +2 -2
  35. package/dist/core/security-extract.js +2 -2
  36. package/dist/facts.js +3 -3
  37. package/dist/facts.json +2 -2
  38. package/docs/code-map.md +14 -3
  39. 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 npm package includes the Markdown docs below under `docs/`. Public web docs on `brainclaw.dev` are still being rolled out, so the npm README does not depend on private GitHub links.
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
- | `docs/index.md` | Documentation index grouped by getting started, guides, reference, and design |
379
- | `docs/integrations/overview.md` | Start here for agent integrations |
380
- | `docs/integrations/mcp.md` | MCP runtime path for capable agents |
381
- | `docs/quickstart.md` | First-time setup on a new project (greenfield) |
382
- | `docs/quickstart-existing-project.md` | Joining a project that already has `.brainclaw/` |
383
- | `docs/server-operations.md` | Operator and remote-server workflow guide |
384
- | `docs/cli.md` | CLI reference for operators, scripts, and fallback use |
385
- | `docs/code-map.md` | Code Map — symbol/import index, freshness model, monorepo behavior |
386
- | `docs/concepts/memory.md` | What "memory" means in brainclaw |
387
- | `docs/concepts/plans-and-claims.md` | Coordination layer |
388
- | `docs/concepts/runtime-notes.md` | Ephemeral observations |
389
- | `docs/concepts/multi-agent-workflows.md` | The four common scenarios — orchestration, agent switching, project recovery, team async |
390
- | `docs/concepts/troubleshooting.md` | Runbook for degraded coordination state — stale claims, missing dist, octopus failures, etc. |
391
- | `docs/integrations/cursor.md` | Cursor |
392
- | `docs/integrations/claude-code.md` | Claude Code |
393
- | `docs/integrations/copilot.md` | GitHub Copilot |
394
- | `docs/integrations/codex.md` | Codex |
395
- | `docs/storage.md` | Storage model |
396
- | `docs/security.md` | Security model |
397
- | `docs/review.md` | Reflective review |
398
- | `docs/reputation.md` | Reputation signals |
399
- | `docs/playbooks/` | Audience design constraints for brainclaw development |
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
@@ -94,7 +94,7 @@ export async function runDispatchWatch(targetId, options = {}) {
94
94
  const baseRef = options.base ?? 'master';
95
95
  const startedAt = Date.now();
96
96
  let poll = 0;
97
- let lastState = 'running';
97
+ let lastState;
98
98
  let lastStatus;
99
99
  for (;;) {
100
100
  poll += 1;
@@ -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, key) => {
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, key) => {
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, key) => {
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 { agent, agentId, client_request_id, ...rest } = req;
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';
@@ -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;
@@ -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 = loadState(options.cwd);
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');
@@ -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).
@@ -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) {
@@ -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, env = process.env) {
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\](?:[^\[]*)command\s*=\s*(["'])(.+?)\1/is);
164
- const argsMatch = content.match(/\[mcp_servers\.brainclaw\](?:[^\[]*)args\s*=\s*\[(.+?)\]/is);
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 = 'tier-b';
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, homeDir) {
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(cwd, _homeDir) {
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;
@@ -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: badge(manifest.freshness.status, {
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: { status: out.freshness_badge.status, details: out.freshness_badge.details },
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: { status: out.freshness_badge.status, details: out.freshness_badge.details },
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 = null;
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 = gatherSymbolEntries(symbolsIndex, target);
498
- const exact = defining.filter((e) => e.name.toLowerCase() === target.toLowerCase());
499
- if (exact.length > 0)
500
- defining = exact;
501
- else if (defining.length === 0)
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 = false;
161
+ let vendoredExists;
162
162
  try {
163
163
  vendoredExists = fs.existsSync(fileURLToPath(vendored));
164
164
  }
@@ -1574,7 +1574,7 @@ function isExecutionSensitiveTarget(target) {
1574
1574
  function tokenise(input) {
1575
1575
  return input
1576
1576
  .toLowerCase()
1577
- .split(/[^a-z0-9_\/-]+/)
1577
+ .split(/[^a-z0-9_/-]+/)
1578
1578
  .map((x) => x.trim())
1579
1579
  .filter(Boolean);
1580
1580
  }
@@ -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
  }
@@ -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. Use it before grepping unfamiliar code — see `docs/code-map.md`.',
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');
@@ -25,7 +25,6 @@ export function saveInstruction(entry, cwd) {
25
25
  });
26
26
  }
27
27
  export function createInstruction(text, options, cwd) {
28
- const entries = loadInstructions(cwd);
29
28
  const timestamp = new Date().toISOString();
30
29
  const entry = {
31
30
  schema_version: 2,
@@ -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. Do the work. Honor the \`warnings\` array (claim conflicts, sensitive paths, high-severity traps on your scope).
38
- 4. 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.
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_\-]{35}\b/ },
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_+/=\-]{16,}/g;
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.0 on 2026-06-20T21:43:38.629Z
2
+ // Source: brainclaw v1.10.1 on 2026-06-21T19:26:16.599Z
3
3
  export const FACTS = {
4
- "version": "1.10.0",
5
- "generated_at": "2026-06-20T21:43:38.629Z",
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
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.10.0",
3
- "generated_at": "2026-06-20T21:43:38.629Z",
2
+ "version": "1.10.1",
3
+ "generated_at": "2026-06-21T19:26:16.599Z",
4
4
  "tools": {
5
5
  "count": 66,
6
6
  "published_count": 65,
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 / TSX / JavaScript) are **bundled into the package** during the
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainclaw",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "description": "Shared project memory for humans and coding agents.",
5
5
  "type": "module",
6
6
  "repository": {