brainclaw 0.23.1 → 0.25.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cli.js +1 -0
  2. package/dist/commands/accept.js +5 -2
  3. package/dist/commands/claim.js +7 -5
  4. package/dist/commands/context-diff.js +62 -4
  5. package/dist/commands/export.js +40 -2
  6. package/dist/commands/init.js +41 -0
  7. package/dist/commands/instruction.js +5 -4
  8. package/dist/commands/mcp.js +64 -5
  9. package/dist/commands/prune.js +5 -4
  10. package/dist/commands/reflect.js +2 -0
  11. package/dist/commands/release-claim.js +4 -4
  12. package/dist/commands/release-claims.js +5 -4
  13. package/dist/commands/runtime-note.js +1 -0
  14. package/dist/core/ai-surface-tasks.js +3 -2
  15. package/dist/core/audit.js +3 -2
  16. package/dist/core/bootstrap.js +7 -6
  17. package/dist/core/candidates.js +4 -3
  18. package/dist/core/claims.js +3 -2
  19. package/dist/core/instructions.js +3 -2
  20. package/dist/core/io.js +1 -0
  21. package/dist/core/lock.js +2 -2
  22. package/dist/core/markdown.js +18 -0
  23. package/dist/core/mutation-pipeline.js +39 -0
  24. package/dist/core/runtime.js +4 -3
  25. package/dist/core/schema.js +8 -0
  26. package/dist/core/state.js +5 -4
  27. package/docs/cli.md +7 -5
  28. package/docs/concepts/memory.md +4 -3
  29. package/docs/concepts/plans-and-claims.md +10 -0
  30. package/docs/integrations/agents.md +2 -1
  31. package/docs/integrations/claude-code.md +1 -1
  32. package/docs/integrations/codex.md +1 -1
  33. package/docs/integrations/copilot.md +1 -1
  34. package/docs/integrations/cursor.md +1 -1
  35. package/docs/integrations/mcp.md +16 -6
  36. package/docs/integrations/overview.md +2 -2
  37. package/docs/quickstart.md +3 -1
  38. package/docs/storage.md +51 -24
  39. package/package.json +1 -1
@@ -4,7 +4,8 @@ import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import { JsonStore } from './json-store.js';
6
6
  import { generateId, generateIdWithLabel, nowISO } from './ids.js';
7
- import { memoryPath, resolveEntityDir, withStoreLock, writeFileAtomic } from './io.js';
7
+ import { resolveEntityDir } from './io.js';
8
+ import { mutate } from './mutation-pipeline.js';
8
9
  import { BootstrapApplicationReceiptSchema, BootstrapInterviewAnswerSchema, BootstrapInterviewPlanSchema, BootstrapInterviewQuestionSchema, BootstrapImportPlanDocumentSchema, BootstrapProfileDocumentSchema, BootstrapSuggestionDocumentSchema, MemorySeedDocumentSchema, } from './schema.js';
9
10
  import { loadVersionedJsonFile, saveVersionedJsonFile } from './migration.js';
10
11
  import { analyzeRepository } from './repo-analysis.js';
@@ -13,7 +14,7 @@ import { buildAgentToolingContext } from './agent-context.js';
13
14
  import { createInstruction, loadInstructions, saveInstruction } from './instructions.js';
14
15
  import { resolveCurrentAgentName } from './agent-registry.js';
15
16
  import { loadState, persistState } from './state.js';
16
- import { generateMarkdown } from './markdown.js';
17
+ import { rebuildProjectMd } from './markdown.js';
17
18
  const README_CANDIDATES = ['README.md', 'README', 'README.txt', 'README.mdx'];
18
19
  const DOC_HINTS = ['docs', 'doc'];
19
20
  const MAKEFILE_NAME = 'Makefile';
@@ -1241,7 +1242,7 @@ export function applyBootstrapImport(options = {}) {
1241
1242
  const managedArtifacts = [];
1242
1243
  let createdCount = 0;
1243
1244
  let skippedCount = 0;
1244
- withStoreLock(cwd, () => {
1245
+ mutate({ cwd }, () => {
1245
1246
  const state = loadState(cwd);
1246
1247
  const activeInstructionKeys = new Set(loadInstructions(cwd)
1247
1248
  .filter((entry) => entry.active)
@@ -1380,7 +1381,7 @@ export function applyBootstrapImport(options = {}) {
1380
1381
  persistState(state, cwd, { writeProjectMarkdown: false });
1381
1382
  }
1382
1383
  if (createdCount > 0) {
1383
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(loadState(cwd), cwd));
1384
+ rebuildProjectMd(loadState(cwd), cwd);
1384
1385
  }
1385
1386
  });
1386
1387
  const receipt = BootstrapApplicationReceiptSchema.parse({
@@ -1414,7 +1415,7 @@ export function uninstallBootstrapImport(cwd) {
1414
1415
  let deactivatedCount = 0;
1415
1416
  let deletedCount = 0;
1416
1417
  let skippedCount = 0;
1417
- withStoreLock(resolvedCwd, () => {
1418
+ mutate({ cwd: resolvedCwd }, () => {
1418
1419
  const state = loadState(resolvedCwd);
1419
1420
  const instructions = loadInstructions(resolvedCwd);
1420
1421
  let stateChanged = false;
@@ -1459,7 +1460,7 @@ export function uninstallBootstrapImport(cwd) {
1459
1460
  persistState(state, resolvedCwd, { writeProjectMarkdown: false });
1460
1461
  }
1461
1462
  if (deactivatedCount > 0 || deletedCount > 0) {
1462
- writeFileAtomic(memoryPath('project.md', resolvedCwd), generateMarkdown(loadState(resolvedCwd), resolvedCwd));
1463
+ rebuildProjectMd(loadState(resolvedCwd), resolvedCwd);
1463
1464
  }
1464
1465
  });
1465
1466
  const nextReceipt = BootstrapApplicationReceiptSchema.parse({
@@ -2,7 +2,8 @@ import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { CandidateSchema } from './schema.js';
5
- import { resolveEntityDir, withStoreLock } from './io.js';
5
+ import { resolveEntityDir } from './io.js';
6
+ import { mutate } from './mutation-pipeline.js';
6
7
  import { nowISO, getNextShortLabel } from './ids.js';
7
8
  import { JsonStore } from './json-store.js';
8
9
  function inboxDir(cwd, mode = 'read') {
@@ -35,7 +36,7 @@ function candidateStore(dest = 'pending', cwd) {
35
36
  });
36
37
  }
37
38
  export function saveCandidate(candidate, cwd) {
38
- withStoreLock(cwd, () => {
39
+ mutate({ cwd }, () => {
39
40
  ensureInboxDirs(cwd);
40
41
  candidateStore('pending', cwd).save(CandidateSchema.parse(candidate));
41
42
  });
@@ -51,7 +52,7 @@ export function listCandidates(status, cwd) {
51
52
  return status ? candidates.filter((candidate) => candidate.status === status) : candidates;
52
53
  }
53
54
  export function archiveCandidate(candidate, dest, cwd) {
54
- withStoreLock(cwd, () => {
55
+ mutate({ cwd }, () => {
55
56
  ensureInboxDirs(cwd);
56
57
  candidateStore(dest, cwd).save(CandidateSchema.parse(candidate));
57
58
  candidateStore('pending', cwd).delete(candidate.id);
@@ -1,7 +1,8 @@
1
1
  import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import { ClaimSchema } from './schema.js';
4
- import { resolveEntityDir, withStoreLock } from './io.js';
4
+ import { resolveEntityDir } from './io.js';
5
+ import { mutate } from './mutation-pipeline.js';
5
6
  import { nowISO } from './ids.js';
6
7
  import { JsonStore } from './json-store.js';
7
8
  function claimsDir(cwd, mode = 'read') {
@@ -22,7 +23,7 @@ function claimStore(cwd) {
22
23
  });
23
24
  }
24
25
  export function saveClaim(claim, cwd) {
25
- withStoreLock(cwd, () => {
26
+ mutate({ cwd }, () => {
26
27
  ensureClaimsDir(cwd);
27
28
  const writeStore = new JsonStore({
28
29
  dirPath: claimsDir(cwd, 'write'),
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
- import { resolveEntityDir, withStoreLock } from './io.js';
2
+ import { resolveEntityDir } from './io.js';
3
+ import { mutate } from './mutation-pipeline.js';
3
4
  import { generateId } from './ids.js';
4
5
  import { InstructionEntrySchema } from './schema.js';
5
6
  import { JsonStore } from './json-store.js';
@@ -19,7 +20,7 @@ export function loadInstructions(cwd) {
19
20
  return instructionStore(cwd).list();
20
21
  }
21
22
  export function saveInstruction(entry, cwd) {
22
- withStoreLock(cwd, () => {
23
+ mutate({ cwd }, () => {
23
24
  instructionStore(cwd, 'write').save(InstructionEntrySchema.parse(entry));
24
25
  });
25
26
  }
package/dist/core/io.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { withLock, cleanStaleLocks } from './lock.js';
4
+ export { mutate } from './mutation-pipeline.js';
4
5
  export const MEMORY_DIR = '.brainclaw';
5
6
  const STORE_LOCK_BASENAME = '.store-mutation';
6
7
  const RETRYABLE_RENAME_ERROR_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
package/dist/core/lock.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- const DEFAULT_TIMEOUT_MS = 2000;
3
+ const DEFAULT_TIMEOUT_MS = 5000;
4
4
  const LOCK_RETRY_INTERVAL_MS = 50;
5
- const LOCK_EXPIRY_MS = 5000;
5
+ const LOCK_EXPIRY_MS = 10000;
6
6
  const heldLocks = new Map();
7
7
  function lockFilePath(targetPath) {
8
8
  return targetPath + '.lock';
@@ -1,6 +1,8 @@
1
1
  import { listClaims } from './claims.js';
2
2
  import { loadInstructions } from './instructions.js';
3
+ import { memoryPath, writeFileAtomic } from './io.js';
3
4
  import { isTrapActive } from './traps.js';
5
+ import { logger } from './logger.js';
4
6
  export function generateMarkdown(state, cwd) {
5
7
  const lines = ['# Project Memory', ''];
6
8
  const instructions = loadInstructions(cwd).filter((entry) => entry.active);
@@ -117,4 +119,20 @@ export function generateMarkdown(state, cwd) {
117
119
  lines.push('');
118
120
  return lines.join('\n');
119
121
  }
122
+ /**
123
+ * Rebuild `.brainclaw/project.md` from canonical state.
124
+ *
125
+ * This is a **derived view** — it can always be regenerated from the
126
+ * canonical JSON files. Call this once at the end of a top-level mutation,
127
+ * not inside every nested helper. Best-effort: failures are logged but
128
+ * never propagate to the caller.
129
+ */
130
+ export function rebuildProjectMd(state, cwd) {
131
+ try {
132
+ writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
133
+ }
134
+ catch (err) {
135
+ logger.debug('Failed to rebuild project.md:', err);
136
+ }
137
+ }
120
138
  //# sourceMappingURL=markdown.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Mutation pipeline — single entry point for all .brainclaw/ store mutations.
3
+ *
4
+ * Every write operation against the store MUST go through `mutate()`.
5
+ * This ensures:
6
+ * 1. Store-wide serialization via the advisory file lock
7
+ * 2. Consistent error handling and timeout behavior
8
+ * 3. Observable mutation metadata for debugging and auditing
9
+ *
10
+ * @module
11
+ */
12
+ import { ensureMemoryDir, storeLockPath } from './io.js';
13
+ import { withLock } from './lock.js';
14
+ import { logger } from './logger.js';
15
+ /** Default timeout for store-wide lock acquisition (ms). */
16
+ export const STORE_LOCK_TIMEOUT_MS = 5_000;
17
+ export function mutate(optionsOrFn, maybeFn) {
18
+ const options = typeof optionsOrFn === 'function' ? {} : optionsOrFn;
19
+ const fn = typeof optionsOrFn === 'function' ? optionsOrFn : maybeFn;
20
+ const cwd = options.cwd ?? process.cwd();
21
+ const timeoutMs = options.timeoutMs ?? STORE_LOCK_TIMEOUT_MS;
22
+ ensureMemoryDir(cwd, options.preferredDirName);
23
+ const lockTarget = storeLockPath(cwd, options.preferredDirName);
24
+ const start = performance.now();
25
+ try {
26
+ const value = withLock(lockTarget, () => fn(cwd), timeoutMs);
27
+ const durationMs = performance.now() - start;
28
+ if (durationMs > 1_000) {
29
+ logger.debug(`Slow mutation: ${durationMs.toFixed(0)}ms (cwd=${cwd})`);
30
+ }
31
+ return value;
32
+ }
33
+ catch (err) {
34
+ const durationMs = performance.now() - start;
35
+ logger.debug(`Mutation failed after ${durationMs.toFixed(0)}ms: ${err instanceof Error ? err.message : String(err)}`);
36
+ throw err;
37
+ }
38
+ }
39
+ //# sourceMappingURL=mutation-pipeline.js.map
@@ -2,7 +2,8 @@ import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { resolveCurrentHostId, sanitizeHostId } from './host.js';
5
- import { resolveEntityDir, withStoreLock } from './io.js';
5
+ import { resolveEntityDir } from './io.js';
6
+ import { mutate } from './mutation-pipeline.js';
6
7
  import { loadVersionedJsonFile, saveVersionedJsonFile } from './migration.js';
7
8
  import { RuntimeNoteSchema } from './schema.js';
8
9
  import { commitMemoryChange } from './memory-git.js';
@@ -40,7 +41,7 @@ export function saveRuntimeNote(note, cwd) {
40
41
  const persistedNote = visibility === 'shared'
41
42
  ? { ...note, visibility, host_id: hostId }
42
43
  : { ...note, visibility, host_id: hostId };
43
- withStoreLock(cwd, () => {
44
+ mutate({ cwd }, () => {
44
45
  ensureRuntimeDir(note.agent, cwd, visibility, hostId);
45
46
  const filepath = visibility === 'shared'
46
47
  ? path.join(sharedAgentDir(note.agent, cwd, 'write'), `${note.id}.json`)
@@ -58,7 +59,7 @@ export function runtimeNotePath(note, cwd) {
58
59
  : path.join(hostAgentDir(visibility, hostId, note.agent, cwd), `${note.id}.json`);
59
60
  }
60
61
  export function deleteRuntimeNote(note, cwd) {
61
- return withStoreLock(cwd, () => {
62
+ return mutate({ cwd }, () => {
62
63
  const filepath = runtimeNotePath(note, cwd);
63
64
  if (!fs.existsSync(filepath)) {
64
65
  return false;
@@ -164,6 +164,7 @@ export const InstructionEntrySchema = z.object({
164
164
  created_at: z.string(),
165
165
  updated_at: z.string(),
166
166
  author: z.string(),
167
+ model: z.string().optional(),
167
168
  tags: z.array(z.string()).default([]),
168
169
  active: z.boolean().default(true),
169
170
  supersedes: z.string().optional(),
@@ -184,6 +185,7 @@ export const ProjectCapabilitySchema = z.object({
184
185
  created_at: z.string(),
185
186
  author: z.string(),
186
187
  author_id: z.string().optional(),
188
+ model: z.string().optional(),
187
189
  });
188
190
  export const ToolTypeSchema = z.enum(['workflow', 'validator', 'generator', 'utility', 'explorer']);
189
191
  export const ProjectToolSchema = z.object({
@@ -204,6 +206,7 @@ export const ProjectToolSchema = z.object({
204
206
  created_at: z.string(),
205
207
  author: z.string(),
206
208
  author_id: z.string().optional(),
209
+ model: z.string().optional(),
207
210
  });
208
211
  // --- State schema ---
209
212
  export const StateSchema = z.object({
@@ -258,6 +261,7 @@ export const CandidateSchema = z.object({
258
261
  created_at: z.string(),
259
262
  author: z.string(),
260
263
  author_id: z.string().optional(),
264
+ model: z.string().optional(),
261
265
  project_id: z.string().optional(),
262
266
  host_id: z.string().optional(),
263
267
  session_id: z.string().optional(),
@@ -326,6 +330,7 @@ export const ClaimSchema = z.object({
326
330
  status: ClaimStatusSchema,
327
331
  released_at: z.string().optional(),
328
332
  expires_at: z.string().optional(),
333
+ model: z.string().optional(),
329
334
  });
330
335
  // --- Runtime notes schemas ---
331
336
  export const RuntimeNoteSchema = z.object({
@@ -344,6 +349,7 @@ export const RuntimeNoteSchema = z.object({
344
349
  host_id: z.string().optional(),
345
350
  expires_at: z.string().optional(),
346
351
  note_type: z.enum(['observation', 'session_start', 'session_end']).default('observation'),
352
+ model: z.string().optional(),
347
353
  });
348
354
  // --- AI surface task request schemas ---
349
355
  export const AiSurfaceTaskStatusSchema = z.enum(['queued', 'in_progress', 'completed', 'cancelled', 'failed']);
@@ -369,6 +375,7 @@ export const AiSurfaceTaskRequestSchema = z.object({
369
375
  claimed_at: z.string().optional(),
370
376
  completed_at: z.string().optional(),
371
377
  result_note: z.string().optional(),
378
+ model: z.string().optional(),
372
379
  });
373
380
  // --- Runtime event schemas ---
374
381
  export const RuntimeEventTypeSchema = z.enum([
@@ -398,6 +405,7 @@ export const RuntimeEventSchema = z.object({
398
405
  to: z.string().optional(),
399
406
  related_paths: z.array(z.string()).optional(),
400
407
  metadata: z.record(z.unknown()).optional(),
408
+ model: z.string().optional(),
401
409
  });
402
410
  // --- Profile schema ---
403
411
  export const ProfileSchema = z.enum(['dev', 'openclaw', 'ops', 'research']);
@@ -1,11 +1,12 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { ConstraintSchema, DecisionSchema, TrapSchema, HandoffSchema, PlanItemSchema } from './schema.js';
4
- import { ensureMemoryDir, memoryPath, resolveEntityDir, withStoreLock, writeFileAtomic } from './io.js';
4
+ import { ensureMemoryDir, resolveEntityDir } from './io.js';
5
+ import { mutate } from './mutation-pipeline.js';
5
6
  import { commitMemoryChange } from './memory-git.js';
6
7
  import { appendEvent } from './event-log.js';
7
8
  import { loadVersionedJsonFile, saveVersionedJsonFile } from './migration.js';
8
- import { generateMarkdown } from './markdown.js';
9
+ import { rebuildProjectMd } from './markdown.js';
9
10
  export function emptyState() {
10
11
  return {
11
12
  version: 1,
@@ -83,10 +84,10 @@ function writeStateDirectories(state, cwd) {
83
84
  }
84
85
  export function persistState(state, cwd, options = {}) {
85
86
  const effectiveCwd = cwd ?? process.cwd();
86
- withStoreLock(effectiveCwd, () => {
87
+ mutate({ cwd: effectiveCwd }, () => {
87
88
  writeStateDirectories(state, effectiveCwd);
88
89
  if (options.writeProjectMarkdown ?? true) {
89
- writeFileAtomic(memoryPath('project.md', effectiveCwd), generateMarkdown(state, effectiveCwd));
90
+ rebuildProjectMd(state, effectiveCwd);
90
91
  }
91
92
  appendEvent({
92
93
  action: options.eventAction ?? 'update',
package/docs/cli.md CHANGED
@@ -86,7 +86,7 @@ brainclaw setup --roots ~/Projects,~/work --agents all # all agents, multiple r
86
86
 
87
87
  ### `brainclaw init`
88
88
 
89
- Initialize workspace state for the current project root. Detects the AI agent environment and writes to its native instruction file. `brainclaw setup` must have been run first on this machine. Do not run `init` from inside `.brainclaw/`; that directory is Brainclaw's own memory store, not a project root.
89
+ Initialize workspace state for the current project root. Detects the AI agent environment, writes to its native instruction file, and installs a git post-merge hook for automatic claim release. `brainclaw setup` must have been run first on this machine. Do not run `init` from inside `.brainclaw/`; that directory is Brainclaw's own memory store, not a project root.
90
90
 
91
91
  | Option | Description |
92
92
  |---|---|
@@ -993,13 +993,13 @@ brainclaw context --since-session --max-items 20
993
993
 
994
994
  ### `brainclaw context-diff`
995
995
 
996
- Show what has changed in shared memory since a reference point.
996
+ Hybrid context view for subsequent prompts: always includes **critical anchors** (active claims, resolved instructions, top 5 traps) plus the memory delta since a reference point. Used by the Claude Code UserPromptSubmit hook after the first prompt.
997
997
 
998
998
  | Option | Description |
999
999
  |---|---|
1000
1000
  | `--since <date>` | Start date for the diff |
1001
1001
  | `--session <id>` | Compare against a specific session start |
1002
- | `--json` | Output as JSON |
1002
+ | `--json` | Output as JSON (includes anchors + changed items) |
1003
1003
 
1004
1004
  ```bash
1005
1005
  brainclaw context-diff --session sess_42
@@ -1127,6 +1127,7 @@ Export memory as a native agent instruction file.
1127
1127
  |---|---|
1128
1128
  | `--format <format>` | Target format: `copilot-instructions`, `cursor-rules`, `agents-md`, `claude-md`, `gemini-md`, `windsurf`, `cline`, `roo`, or `continue` |
1129
1129
  | `--detect` | Auto-detect the running agent and write to its native file |
1130
+ | `--all` | Write all known agent instruction files at once (deduplicates by format) |
1130
1131
  | `--write` | Write output to the native file path (instead of stdout); generated workspace files are treated as local and added to `.gitignore` |
1131
1132
  | `--shared` | Keep the main exported instruction file versionable when used with `--write`; companion MCP/settings files stay local |
1132
1133
  | `--output <path>` | Write to a custom output path |
@@ -1146,6 +1147,7 @@ brainclaw export --format roo --write # .roo/rules/brainclaw.
1146
1147
  brainclaw export --format continue --write # .continue/rules/brainclaw.md
1147
1148
  brainclaw export --format claude-md --write --shared # publish CLAUDE.md intentionally
1148
1149
  brainclaw export --format claude-md # stdout
1150
+ brainclaw export --all # all 9 agent files at once
1149
1151
  ```
1150
1152
 
1151
1153
  `brainclaw export --write` is local-first by default: the generated workspace file and any companion MCP/settings files are added to `.gitignore`. Use `--shared` only when you intentionally want the main instruction file to be committed. `brainclaw export --detect` also writes companion MCP config where relevant, including `opencode.json` for OpenCode and `.gemini/antigravity/mcp_config.json` for Antigravity/Gemini when the local environment is available.
@@ -1174,7 +1176,7 @@ See [adapters/openclaw.md](adapters/openclaw.md).
1174
1176
 
1175
1177
  ### `brainclaw install-hooks`
1176
1178
 
1177
- Install Git hooks for constraint checking.
1179
+ Install Git hooks: pre-commit (constraint checking, sensitive content detection) and post-merge (auto-release of claims whose scope overlaps merged files).
1178
1180
 
1179
1181
  | Option | Description |
1180
1182
  |---|---|
@@ -1391,7 +1393,7 @@ brainclaw mcp
1391
1393
  | `bclaw_get_agent_board` | Live plan + claim board with active sessions |
1392
1394
  | `bclaw_search` | Full-text BM25 search across all memory items |
1393
1395
  | `bclaw_estimation_report` | Estimation accuracy report for completed plans |
1394
- | `bclaw_list_plans` | List plan items with the same filters as `brainclaw plan list` |
1396
+ | `bclaw_list_plans` | List plan items with filters, pagination (`limit`/`offset`), `compact` mode, and direct `id` lookup |
1395
1397
  | `bclaw_list_claims` | List claims with the same filters as `brainclaw claim list` |
1396
1398
  | `bclaw_list_agents` | List registered agents, optionally with bounded reputation summaries |
1397
1399
  | `bclaw_list_instructions` | List raw or resolved shared instructions |
@@ -32,7 +32,7 @@ Examples: constraints, decisions, traps, completed plans, handoffs.
32
32
 
33
33
  Shared traps now have a lifecycle too: `active`, `resolved`, or `expired`. Active views such as generated context, status, and `project.md` prioritize only active traps so old machine-setup issues stop polluting the current working set, while the canonical memory still keeps resolved traps for audit and search.
34
34
 
35
- These live in `.brainclaw/store.json` and are shared via Git (or by reading the same file).
35
+ These live as individual JSON files under `.brainclaw/memory/` (constraints, decisions, traps, instructions) and `.brainclaw/coordination/` (plans, claims, handoffs). They are versioned in `.brainclaw/.git` and shared via Git.
36
36
 
37
37
  ### Runtime memory
38
38
  Operational observations that may be short-lived, host-specific, or private.
@@ -59,8 +59,9 @@ brainclaw makes this context visible and versionable.
59
59
 
60
60
  brainclaw keeps:
61
61
 
62
- - canonical structured JSON as the source of truth (`.brainclaw/store.json`)
63
- - a generated readable view in each agent's native format (`CLAUDE.md`, `.cursor/rules/brainclaw.md`, etc.)
62
+ - canonical structured JSON as the source of truth (individual files under `.brainclaw/memory/` and `.brainclaw/coordination/`)
63
+ - a derived readable view (`project.md`) regenerated best-effort from canonical state
64
+ - native agent instruction files (`CLAUDE.md`, `.cursor/rules/brainclaw.md`, etc.) generated via `brainclaw export`
64
65
 
65
66
  This balances machine reliability with human readability.
66
67
 
@@ -106,6 +106,16 @@ brainclaw claim release <id>
106
106
 
107
107
  `claim list` shows who holds each claim and whether it is still active. If a claim has a `session_id`, the last 8 characters are shown so you can correlate with the agent session that created it.
108
108
 
109
+ ### Automatic claim release
110
+
111
+ Claims are automatically released after a `git merge` if the post-merge hook is installed (default since `brainclaw init` v0.25.3+). The hook matches merged file paths against active claim scopes and releases overlapping claims.
112
+
113
+ You can also install or reinstall the hook manually:
114
+
115
+ ```bash
116
+ brainclaw install-hooks
117
+ ```
118
+
109
119
  ## Why claims matter
110
120
 
111
121
  Without claims, multiple agents can easily touch the same area at once and generate conflicting changes.
@@ -83,7 +83,8 @@ brainclaw export --detect --write # auto-detect and write all formats
83
83
  When brainclaw memory changes (new constraints, resolved traps, updated plans), regenerate instruction files:
84
84
 
85
85
  ```bash
86
- brainclaw export --detect --write
86
+ brainclaw export --all # all 9 agent formats at once
87
+ brainclaw export --detect --write # only the detected agent
87
88
  ```
88
89
 
89
90
  For agents without MCP (Copilot), this is especially important — the instruction file is their only source of project context.
@@ -14,7 +14,7 @@ brainclaw export --format claude-md --write
14
14
 
15
15
  - use MCP as the default runtime path for dynamic retrieval and writes
16
16
  - keep `CLAUDE.md` lightweight and behavioral: it should tell Claude Code when to call Brainclaw, not carry all mutable workspace state
17
- - use `.brainclaw/project.md` as a readable fallback baseline, not as the primary live source of truth
17
+ - use `.brainclaw/project.md` as a readable fallback (it is a derived view, regenerated best-effort run `brainclaw rebuild` if stale)
18
18
  - use hooks or workflow checks when a stronger reminder is needed
19
19
 
20
20
  ## Key idea
@@ -14,7 +14,7 @@ brainclaw export --format agents-md --write
14
14
 
15
15
  - use MCP as the default runtime path for fresh context, plans, claims, and runtime notes
16
16
  - keep `AGENTS.md` lightweight and behavioral: it should remind Codex how to use Brainclaw, not duplicate live state
17
- - use `.brainclaw/project.md` only as a readable fallback when MCP is unavailable or when a human wants a simple snapshot
17
+ - use `.brainclaw/project.md` only as a readable fallback (derived view, regenerated best-effort run `brainclaw rebuild` if stale)
18
18
  - encourage plans, claims, and handoffs during multi-step work
19
19
 
20
20
  ## Good role for brainclaw here
@@ -14,7 +14,7 @@ brainclaw export --format copilot-instructions --write
14
14
 
15
15
  - use MCP whenever the Copilot surface supports it for fresh context and coordination views
16
16
  - keep `.github/copilot-instructions.md` lightweight and behavioral
17
- - use `.brainclaw/project.md` as readable fallback, not as the only live context source
17
+ - use `.brainclaw/project.md` as readable fallback (derived view, regenerated best-effort may need `brainclaw rebuild` if stale)
18
18
  - use plans, claims, and handoffs to reduce ambiguity across sessions
19
19
 
20
20
  ## Why this matters
@@ -14,7 +14,7 @@ brainclaw export --format cursor-rules --write
14
14
 
15
15
  - use MCP as the default dynamic path for context, board state, plans, and claims
16
16
  - let the generated `.cursor/rules/brainclaw.md` tell Cursor when to consult Brainclaw and how to stay inside the workflow
17
- - use `.brainclaw/project.md` only as readable fallback shared state
17
+ - use `.brainclaw/project.md` only as readable fallback (derived view, may be stale — run `brainclaw rebuild` to refresh)
18
18
  - rely on claims and plans when multiple agents or humans are active in the same repo
19
19
 
20
20
  ## Key point
@@ -42,7 +42,7 @@ This keeps session continuity inside Brainclaw instead of pushing the agent back
42
42
  | `bclaw_write_note` | Record a runtime note, supports `autoReflect: true` |
43
43
  | `bclaw_read_handoff` | Read active handoffs |
44
44
  | `bclaw_get_agent_board` | Coordination snapshot |
45
- | `bclaw_list_plans` | Structured plan listing with CLI-equivalent filters |
45
+ | `bclaw_list_plans` | Structured plan listing with filters, pagination (`limit`/`offset`), `compact` mode, and `id` lookup |
46
46
  | `bclaw_list_claims` | Structured claim listing with CLI-equivalent filters |
47
47
  | `bclaw_list_agents` | Registered agent inventory, optionally with bounded reputation |
48
48
  | `bclaw_list_instructions` | Raw or resolved instruction listing |
@@ -58,7 +58,7 @@ This keeps session continuity inside Brainclaw instead of pushing the agent back
58
58
  | Runtime writes with session continuity | MCP |
59
59
  | Local behavioral reminders inside the agent UI | native agent files |
60
60
  | Human inspection or scripting | CLI |
61
- | Simple readable fallback | `.brainclaw/project.md` |
61
+ | Simple readable fallback | `.brainclaw/project.md` (derived view, may be stale) |
62
62
 
63
63
  ## Starting The Server
64
64
 
@@ -87,16 +87,26 @@ Interview answers are keyed by question ID and may contain:
87
87
  - `response_boolean`
88
88
  - optional explicit `suggestions` when the agent wants to confirm exact canonical memory items
89
89
 
90
+ ## Mutation Safety
91
+
92
+ The MCP server serializes all mutations through a single-writer queue (`McpTaskRunner`). When an agent calls a write tool (e.g. `bclaw_claim`, `bclaw_write_note`, `bclaw_create_plan`), the request is enqueued and executed one at a time. This guarantees:
93
+
94
+ - no concurrent writes from the same MCP connection
95
+ - no partial state from interleaved mutations
96
+ - deterministic ordering of operations
97
+
98
+ A secondary file-based lock (`mutate()`) provides cross-process safety in case CLI commands run alongside MCP. But for agents, MCP is the safe path by design — no extra precautions needed.
99
+
90
100
  ## Important Rule
91
101
 
92
- If the agent has MCP available, do not treat the CLI as the primary runtime interface.
102
+ If the agent has MCP available, do not treat the CLI as the primary runtime interface. All agent mutations MUST go through MCP tools.
93
103
 
94
104
  The CLI remains valuable for:
95
105
 
96
- - setup
106
+ - setup and initialization
97
107
  - bootstrap by a human operator
98
- - scripting
108
+ - scripting and automation
99
109
  - release and packaging
100
110
  - debugging and fallback access
101
111
 
102
- But for capable agents, MCP should be the first-class path for dynamic state.
112
+ But for capable agents, MCP is the first-class path for both reads and writes.
@@ -87,9 +87,9 @@ The developer can dial back individual surfaces if needed, but the default is fu
87
87
 
88
88
  ## Sequential collaboration, not parallel editing
89
89
 
90
- For now, brainclaw works best when one agent works at a time in a given checkout. The next agent can pick up where the previous one stopped, using shared plans, claims, handoffs, and memory.
90
+ brainclaw's store mutations are serialized (MCP single-writer queue + file-based lock), so memory writes are safe even under contention. However, running multiple agents in parallel on the same checkout can still cause Git conflicts and confusing file-level state.
91
91
 
92
- Running multiple agents in parallel on the same checkout will create conflicts. Git worktree isolation per agent is planned but not yet available.
92
+ For now, brainclaw works best when one agent works at a time in a given checkout. The next agent can pick up where the previous one stopped, using shared plans, claims, handoffs, and memory. Git worktree isolation per agent is planned but not yet available.
93
93
 
94
94
  ## Next reads
95
95
 
@@ -74,7 +74,9 @@ This keeps non-code work visible to the project without overloading the active c
74
74
 
75
75
  ## Important: one agent at a time
76
76
 
77
- For now, use brainclaw for sequential collaboration. One agent works, finishes, and the next one picks up from shared context. Running multiple agents in parallel on the same checkout will cause conflicts.
77
+ brainclaw serializes all store mutations (file lock + MCP single-writer queue), so writes are safe. But running multiple agents in parallel on the same checkout can still cause Git conflicts and confusing state transitions.
78
+
79
+ Use brainclaw for sequential collaboration: one agent works, finishes, and the next one picks up from shared context. Use `bclaw_session_end` to hand off cleanly.
78
80
 
79
81
  ## Next reads
80
82