brainclaw 0.19.5 → 0.19.7

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 (151) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +22 -20
  3. package/dist/cli.js +0 -0
  4. package/dist/commands/accept.js +102 -104
  5. package/dist/commands/adapter-openclaw-import.js +0 -0
  6. package/dist/commands/add-step.js +3 -5
  7. package/dist/commands/agent-board.js +0 -0
  8. package/dist/commands/audit.js +0 -0
  9. package/dist/commands/bootstrap.js +0 -0
  10. package/dist/commands/capability.js +3 -5
  11. package/dist/commands/changes.js +0 -0
  12. package/dist/commands/check-constraints.js +0 -0
  13. package/dist/commands/claim-resource.js +0 -0
  14. package/dist/commands/claim.js +14 -12
  15. package/dist/commands/complete-step.js +3 -5
  16. package/dist/commands/constraint.js +3 -5
  17. package/dist/commands/context-diff.js +0 -0
  18. package/dist/commands/context.js +0 -0
  19. package/dist/commands/decision.js +3 -6
  20. package/dist/commands/delete-plan.js +3 -5
  21. package/dist/commands/diff.js +0 -0
  22. package/dist/commands/doctor.js +0 -0
  23. package/dist/commands/enable-agent.js +0 -0
  24. package/dist/commands/env.js +0 -0
  25. package/dist/commands/estimation-report.js +0 -0
  26. package/dist/commands/explore.js +0 -0
  27. package/dist/commands/export.js +0 -0
  28. package/dist/commands/handoff.js +3 -5
  29. package/dist/commands/history.js +0 -0
  30. package/dist/commands/hooks.js +0 -0
  31. package/dist/commands/init.js +0 -0
  32. package/dist/commands/install-hooks.js +0 -0
  33. package/dist/commands/instruction.js +16 -9
  34. package/dist/commands/list-agents.js +0 -0
  35. package/dist/commands/list-claims.js +0 -0
  36. package/dist/commands/list-instructions.js +0 -0
  37. package/dist/commands/list-plans.js +0 -0
  38. package/dist/commands/mcp-worker.js +0 -0
  39. package/dist/commands/mcp.js +11 -21
  40. package/dist/commands/memory.js +4 -7
  41. package/dist/commands/metrics.js +0 -0
  42. package/dist/commands/plan-resource.js +0 -0
  43. package/dist/commands/plan.js +3 -5
  44. package/dist/commands/prune-candidates.js +0 -0
  45. package/dist/commands/prune.js +27 -25
  46. package/dist/commands/pull.js +0 -0
  47. package/dist/commands/push.js +0 -0
  48. package/dist/commands/rebuild.js +0 -0
  49. package/dist/commands/reflect-runtime-note.js +0 -0
  50. package/dist/commands/reflect.js +3 -5
  51. package/dist/commands/register-agent.js +0 -0
  52. package/dist/commands/reject.js +0 -0
  53. package/dist/commands/release-claim.js +23 -20
  54. package/dist/commands/release-claims.js +22 -21
  55. package/dist/commands/review.js +0 -0
  56. package/dist/commands/rollback.js +2 -2
  57. package/dist/commands/runtime-note.js +0 -0
  58. package/dist/commands/runtime-status.js +0 -0
  59. package/dist/commands/search.js +0 -0
  60. package/dist/commands/session-end.js +0 -0
  61. package/dist/commands/session-start.js +0 -0
  62. package/dist/commands/set-trust.js +0 -0
  63. package/dist/commands/setup.js +0 -0
  64. package/dist/commands/show-candidate.js +0 -0
  65. package/dist/commands/star-candidate.js +0 -0
  66. package/dist/commands/status.js +0 -0
  67. package/dist/commands/sync.js +0 -0
  68. package/dist/commands/tool.js +3 -5
  69. package/dist/commands/trap.js +3 -5
  70. package/dist/commands/update-handoff.js +3 -5
  71. package/dist/commands/update-plan.js +3 -5
  72. package/dist/commands/upgrade.js +2 -2
  73. package/dist/commands/use-candidate.js +0 -0
  74. package/dist/commands/version.js +0 -0
  75. package/dist/commands/watch.js +0 -0
  76. package/dist/commands/whoami.js +0 -0
  77. package/dist/core/agent-context.js +0 -0
  78. package/dist/core/agent-files.js +0 -0
  79. package/dist/core/agent-integrations.js +0 -0
  80. package/dist/core/agent-inventory.js +0 -0
  81. package/dist/core/agent-registry.js +0 -0
  82. package/dist/core/ai-agent-detection.js +0 -0
  83. package/dist/core/audit.js +25 -25
  84. package/dist/core/bootstrap.js +0 -0
  85. package/dist/core/brainclaw-version.js +0 -0
  86. package/dist/core/candidates.js +10 -6
  87. package/dist/core/circuit-breaker.js +0 -0
  88. package/dist/core/claims.js +10 -8
  89. package/dist/core/config.js +0 -0
  90. package/dist/core/context-diff.js +0 -0
  91. package/dist/core/context.js +0 -0
  92. package/dist/core/contradictions.js +0 -0
  93. package/dist/core/coordination.js +2 -2
  94. package/dist/core/cross-project.js +0 -0
  95. package/dist/core/duplicates.js +0 -0
  96. package/dist/core/event-log.js +0 -0
  97. package/dist/core/events.js +0 -0
  98. package/dist/core/execution-context.js +0 -0
  99. package/dist/core/freshness.js +0 -0
  100. package/dist/core/global-registry.js +0 -0
  101. package/dist/core/host.js +0 -0
  102. package/dist/core/identity.js +0 -0
  103. package/dist/core/ids.js +0 -0
  104. package/dist/core/input-validation.js +0 -0
  105. package/dist/core/instructions.js +4 -2
  106. package/dist/core/io.js +8 -0
  107. package/dist/core/json-store.js +0 -0
  108. package/dist/core/lifecycle.js +0 -0
  109. package/dist/core/lock.js +18 -2
  110. package/dist/core/logger.js +0 -0
  111. package/dist/core/machine-profile.js +0 -0
  112. package/dist/core/markdown.js +0 -0
  113. package/dist/core/memory-git.js +0 -0
  114. package/dist/core/migration.js +0 -0
  115. package/dist/core/project-registry.js +0 -0
  116. package/dist/core/reflection-safety.js +0 -0
  117. package/dist/core/repo-analysis.js +0 -0
  118. package/dist/core/reputation.js +0 -0
  119. package/dist/core/runtime.js +18 -14
  120. package/dist/core/schema.js +0 -0
  121. package/dist/core/search.js +0 -0
  122. package/dist/core/security.js +0 -0
  123. package/dist/core/setup-state.js +0 -0
  124. package/dist/core/state.js +21 -4
  125. package/dist/core/store-resolution.js +0 -0
  126. package/dist/core/sync-remote.js +0 -0
  127. package/dist/core/traps.js +0 -0
  128. package/docs/adapters/openclaw.md +43 -0
  129. package/docs/cli.md +1230 -0
  130. package/docs/concepts/coordination.md +52 -0
  131. package/docs/concepts/memory.md +87 -0
  132. package/docs/concepts/plans-and-claims.md +139 -0
  133. package/docs/concepts/runtime-notes.md +38 -0
  134. package/docs/concepts/workspace-bootstrapping.md +40 -0
  135. package/docs/context-format-changelog.md +35 -0
  136. package/docs/context-format.md +48 -0
  137. package/docs/integrations/agents.md +169 -0
  138. package/docs/integrations/claude-code.md +23 -0
  139. package/docs/integrations/codex.md +23 -0
  140. package/docs/integrations/copilot.md +21 -0
  141. package/docs/integrations/cursor.md +23 -0
  142. package/docs/integrations/mcp.md +51 -0
  143. package/docs/integrations/overview.md +61 -0
  144. package/docs/mcp-schema-changelog.md +108 -0
  145. package/docs/product/positioning.md +97 -0
  146. package/docs/quickstart.md +99 -0
  147. package/docs/reputation.md +52 -0
  148. package/docs/review.md +43 -0
  149. package/docs/security.md +53 -0
  150. package/docs/storage.md +81 -0
  151. package/package.json +7 -6
@@ -8,9 +8,8 @@ import { buildCoordinationSnapshot } from '../core/coordination.js';
8
8
  import { buildContext, renderContextMarkdown, renderContextPromptTemplate } from '../core/context.js';
9
9
  import { buildExecutionContext, renderExecutionContextSummary } from '../core/execution-context.js';
10
10
  import { loadConfig } from '../core/config.js';
11
- import { loadState, saveState } from '../core/state.js';
12
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
13
- import { generateMarkdown } from '../core/markdown.js';
11
+ import { loadState, persistState } from '../core/state.js';
12
+ import { memoryExists } from '../core/io.js';
14
13
  import { generateCandidateIdWithLabel, listArchivedCandidates, listCandidates, saveCandidate } from '../core/candidates.js';
15
14
  import { generateClaimId, listClaims, loadClaim, saveClaim } from '../core/claims.js';
16
15
  import { createRuntimeNote } from './runtime-note.js';
@@ -1808,8 +1807,7 @@ export async function executeMcpToolCall(payload) {
1808
1807
  if (releasePlanStatus === 'done' && !releasePlan.completed_at)
1809
1808
  releasePlan.completed_at = ts;
1810
1809
  releasePlan.updated_at = ts;
1811
- saveState(releaseState, cwd);
1812
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(releaseState, cwd));
1810
+ persistState(releaseState, cwd);
1813
1811
  releasePlanUpdated = true;
1814
1812
  }
1815
1813
  }
@@ -1957,8 +1955,7 @@ export async function executeMcpToolCall(payload) {
1957
1955
  estimated_effort: estimatedEffort,
1958
1956
  };
1959
1957
  state.plan_items.push(entry);
1960
- saveState(state, cwd);
1961
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
1958
+ persistState(state, cwd);
1962
1959
  appendAuditEntry({ actor: resolved.identity.agent_name, actor_id: resolved.identity.agent_id, action: 'create', item_id: id, item_type: 'plan' }, cwd);
1963
1960
  return {
1964
1961
  response: toolResponse({
@@ -1996,8 +1993,7 @@ export async function executeMcpToolCall(payload) {
1996
1993
  if (args.actualEffort)
1997
1994
  plan.actual_effort = args.actualEffort;
1998
1995
  plan.updated_at = timestamp;
1999
- saveState(state, cwd);
2000
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
1996
+ persistState(state, cwd);
2001
1997
  appendAuditEntry({ actor: resolved.identity.agent_name, actor_id: resolved.identity.agent_id, action: 'update', item_id: plan.id, item_type: 'plan' }, cwd);
2002
1998
  return {
2003
1999
  response: toolResponse({
@@ -2032,8 +2028,7 @@ export async function executeMcpToolCall(payload) {
2032
2028
  };
2033
2029
  plan.steps = [...(plan.steps ?? []), step];
2034
2030
  plan.updated_at = nowISO();
2035
- saveState(state, cwd);
2036
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
2031
+ persistState(state, cwd);
2037
2032
  const total = plan.steps.length;
2038
2033
  const done = plan.steps.filter((s) => s.status === 'done').length;
2039
2034
  return {
@@ -2066,8 +2061,7 @@ export async function executeMcpToolCall(payload) {
2066
2061
  step.status = 'done';
2067
2062
  step.updated_at = nowISO();
2068
2063
  plan.updated_at = nowISO();
2069
- saveState(state, cwd);
2070
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
2064
+ persistState(state, cwd);
2071
2065
  const total = plan.steps.length;
2072
2066
  const done = plan.steps.filter((s) => s.status === 'done').length;
2073
2067
  return {
@@ -2124,8 +2118,7 @@ export async function executeMcpToolCall(payload) {
2124
2118
  else if (itemType === 'trap') {
2125
2119
  state.known_traps = state.known_traps.filter((t) => t.id !== itemId && t.short_label !== itemId);
2126
2120
  }
2127
- saveState(state, foundStore.cwd);
2128
- writeFileAtomic(memoryPath('project.md', foundStore.cwd), generateMarkdown(state, foundStore.cwd));
2121
+ persistState(state, foundStore.cwd);
2129
2122
  appendAuditEntry({ actor: resolved.identity.agent_name, actor_id: resolved.identity.agent_id, action: 'delete', item_id: itemId, item_type: itemType }, foundStore.cwd);
2130
2123
  return {
2131
2124
  response: toolResponse({
@@ -2211,8 +2204,7 @@ export async function executeMcpToolCall(payload) {
2211
2204
  else if (itemType === 'trap') {
2212
2205
  sourceState.known_traps = sourceState.known_traps.filter((t) => t.id !== itemId);
2213
2206
  }
2214
- saveState(sourceState, sourceStore.cwd);
2215
- writeFileAtomic(memoryPath('project.md', sourceStore.cwd), generateMarkdown(sourceState, sourceStore.cwd));
2207
+ persistState(sourceState, sourceStore.cwd);
2216
2208
  // Add to target store
2217
2209
  const targetState = loadState(targetCwd);
2218
2210
  if (itemType === 'constraint') {
@@ -2224,8 +2216,7 @@ export async function executeMcpToolCall(payload) {
2224
2216
  else if (itemType === 'trap') {
2225
2217
  targetState.known_traps.push(item);
2226
2218
  }
2227
- saveState(targetState, targetCwd);
2228
- writeFileAtomic(memoryPath('project.md', targetCwd), generateMarkdown(targetState, targetCwd));
2219
+ persistState(targetState, targetCwd);
2229
2220
  }
2230
2221
  else {
2231
2222
  // Just update in place
@@ -2245,8 +2236,7 @@ export async function executeMcpToolCall(payload) {
2245
2236
  if (idx >= 0)
2246
2237
  state.known_traps[idx] = item;
2247
2238
  }
2248
- saveState(state, sourceStore.cwd);
2249
- writeFileAtomic(memoryPath('project.md', sourceStore.cwd), generateMarkdown(state, sourceStore.cwd));
2239
+ persistState(state, sourceStore.cwd);
2250
2240
  }
2251
2241
  appendAuditEntry({ actor: resolved.identity.agent_name, actor_id: resolved.identity.agent_id, action: 'update', item_id: itemId, item_type: itemType }, sourceStore.cwd);
2252
2242
  return {
@@ -1,7 +1,6 @@
1
1
  import { loadConfig } from '../core/config.js';
2
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
3
- import { generateMarkdown } from '../core/markdown.js';
4
- import { loadState, saveState } from '../core/state.js';
2
+ import { memoryExists } from '../core/io.js';
3
+ import { loadState, persistState } from '../core/state.js';
5
4
  import { scanText } from '../core/security.js';
6
5
  import { validateCliInput } from '../core/input-validation.js';
7
6
  import { resolveTargetStore } from '../core/store-resolution.js';
@@ -222,8 +221,7 @@ function runMemoryUpdate(id, options) {
222
221
  break;
223
222
  }
224
223
  }
225
- saveState(state, options.cwd);
226
- writeFileAtomic(memoryPath('project.md', options.cwd), generateMarkdown(state, options.cwd));
224
+ persistState(state, options.cwd);
227
225
  console.log(`✔ Memory item updated: [${resolved.item.id}] ${resolved.item.text}`);
228
226
  }
229
227
  function runMemoryDelete(id, cwd) {
@@ -250,8 +248,7 @@ function runMemoryDelete(id, cwd) {
250
248
  console.error(`Error: Memory item '${id}' not found.`);
251
249
  process.exit(1);
252
250
  }
253
- saveState(state, cwd);
254
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
251
+ persistState(state, cwd);
255
252
  console.log(`✔ Memory item deleted: [${deleted.id}] ${deleted.text}`);
256
253
  }
257
254
  function collectMemoryItems(state) {
File without changes
File without changes
@@ -1,10 +1,9 @@
1
- import { loadState, saveState } from '../core/state.js';
1
+ import { loadState, persistState } from '../core/state.js';
2
2
  import { resolveCurrentAgentName } from '../core/agent-registry.js';
3
3
  import { loadConfig } from '../core/config.js';
4
- import { generateMarkdown } from '../core/markdown.js';
5
4
  import { generateIdWithLabel, nowISO } from '../core/ids.js';
6
5
  import { scanText } from '../core/security.js';
7
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
6
+ import { memoryExists } from '../core/io.js';
8
7
  import { validateCliInput } from '../core/input-validation.js';
9
8
  import { runListPlans } from './list-plans.js';
10
9
  import { resolveTargetStore } from '../core/store-resolution.js';
@@ -68,8 +67,7 @@ export function runPlan(text, options = {}) {
68
67
  estimated_effort: estimatedEffort,
69
68
  };
70
69
  state.plan_items.push(entry);
71
- saveState(state, cwd);
72
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
70
+ persistState(state, cwd);
73
71
  const storeLabel = options.store && options.store !== 'local' ? ` [store:${options.store}]` : '';
74
72
  console.log(`✔ Plan item added: [${id}] ${text}${storeLabel}`);
75
73
  }
File without changes
@@ -1,5 +1,6 @@
1
1
  import { loadState, saveState } from '../core/state.js';
2
- import { memoryExists } from '../core/io.js';
2
+ import { memoryExists, memoryPath, withStoreLock, writeFileAtomic } from '../core/io.js';
3
+ import { generateMarkdown } from '../core/markdown.js';
3
4
  import { deleteRuntimeNote, listRuntimeNotes } from '../core/runtime.js';
4
5
  import { expireStaleActiveClaims } from '../core/claims.js';
5
6
  export function runPrune(options = {}) {
@@ -8,36 +9,37 @@ export function runPrune(options = {}) {
8
9
  console.error('Project memory not initialized. Run `brainclaw init` first.');
9
10
  process.exit(1);
10
11
  }
11
- const state = loadState(cwd);
12
12
  const now = new Date().toISOString();
13
- const originalLength = state.active_constraints.length;
14
- // Transition expired constraints to "expired"
15
- for (const c of state.active_constraints) {
16
- if (c.status === 'active' && c.expires_at && c.expires_at < now) {
17
- c.status = 'expired';
18
- }
19
- }
20
- // Filter out constraints that have been expired for a while to keep memory clean
21
- // For the MVP, we just remove them if they are expired
22
- state.active_constraints = state.active_constraints.filter(c => c.status !== 'expired');
23
- const prunedCount = originalLength - state.active_constraints.length;
24
- saveState(state, cwd);
25
- const expiredClaimsCount = expireStaleActiveClaims(cwd);
13
+ let prunedCount = 0;
14
+ let expiredClaimsCount = 0;
26
15
  let expiredNotesCount = 0;
27
- if (options.expired) {
28
- // Prune expired runtime notes
29
- const notes = listRuntimeNotes(undefined, cwd);
30
- for (const note of notes) {
31
- if (note.expires_at && note.expires_at < now) {
32
- try {
33
- if (deleteRuntimeNote(note, cwd)) {
34
- expiredNotesCount++;
16
+ withStoreLock(cwd, () => {
17
+ const state = loadState(cwd);
18
+ const originalLength = state.active_constraints.length;
19
+ for (const c of state.active_constraints) {
20
+ if (c.status === 'active' && c.expires_at && c.expires_at < now) {
21
+ c.status = 'expired';
22
+ }
23
+ }
24
+ state.active_constraints = state.active_constraints.filter(c => c.status !== 'expired');
25
+ prunedCount = originalLength - state.active_constraints.length;
26
+ saveState(state, cwd);
27
+ expiredClaimsCount = expireStaleActiveClaims(cwd);
28
+ if (options.expired) {
29
+ const notes = listRuntimeNotes(undefined, cwd);
30
+ for (const note of notes) {
31
+ if (note.expires_at && note.expires_at < now) {
32
+ try {
33
+ if (deleteRuntimeNote(note, cwd)) {
34
+ expiredNotesCount++;
35
+ }
35
36
  }
37
+ catch { /* ignore */ }
36
38
  }
37
- catch { /* ignore */ }
38
39
  }
39
40
  }
40
- }
41
+ writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(loadState(cwd), cwd));
42
+ });
41
43
  if (options.expired) {
42
44
  console.log(`✔ Pruned ${prunedCount} expired constraints, ${expiredNotesCount} expired runtime notes, ${expiredClaimsCount} expired claims.`);
43
45
  }
File without changes
File without changes
File without changes
File without changes
@@ -1,9 +1,8 @@
1
1
  import fs from 'node:fs';
2
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
2
+ import { memoryExists } from '../core/io.js';
3
3
  import { loadConfig } from '../core/config.js';
4
4
  import { buildOperationalIdentity } from '../core/identity.js';
5
- import { loadState, saveState } from '../core/state.js';
6
- import { generateMarkdown } from '../core/markdown.js';
5
+ import { loadState, persistState } from '../core/state.js';
7
6
  import { scanText } from '../core/security.js';
8
7
  import { nowISO, generateIdWithLabel } from '../core/ids.js';
9
8
  import { saveCandidate, generateCandidateIdWithLabel, listCandidates } from '../core/candidates.js';
@@ -265,8 +264,7 @@ function promoteCandidateToState(candidate, cwd) {
265
264
  break;
266
265
  }
267
266
  }
268
- saveState(state, cwd);
269
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state));
267
+ persistState(state, cwd);
270
268
  return promotedItemId;
271
269
  }
272
270
  export function mapEventTypeToCandidateType(eventType) {
File without changes
File without changes
@@ -1,5 +1,5 @@
1
1
  import { memoryExists } from '../core/io.js';
2
- import { memoryPath, writeFileAtomic } from '../core/io.js';
2
+ import { memoryPath, withStoreLock, writeFileAtomic } from '../core/io.js';
3
3
  import { loadClaim, listClaims, releaseClaim } from '../core/claims.js';
4
4
  import { generateMarkdown } from '../core/markdown.js';
5
5
  import { loadState, saveState } from '../core/state.js';
@@ -9,27 +9,30 @@ export function runReleaseClaim(id, options = {}) {
9
9
  process.exit(1);
10
10
  }
11
11
  try {
12
- const existing = loadClaim(id, options.cwd);
13
- const claim = releaseClaim(id, options.cwd);
14
- let state = loadState(options.cwd);
15
- if (existing.plan_id) {
16
- const plan = state.plan_items.find((item) => item.id === existing.plan_id);
17
- if (plan) {
18
- const otherActiveClaims = listClaims(options.cwd).filter((item) => item.status === 'active' && item.plan_id === existing.plan_id);
19
- if (options.planStatus) {
20
- plan.status = options.planStatus;
12
+ let claim = loadClaim(id, options.cwd);
13
+ withStoreLock(options.cwd, () => {
14
+ const existing = loadClaim(id, options.cwd);
15
+ claim = releaseClaim(id, options.cwd);
16
+ let state = loadState(options.cwd);
17
+ if (existing.plan_id) {
18
+ const plan = state.plan_items.find((item) => item.id === existing.plan_id);
19
+ if (plan) {
20
+ const otherActiveClaims = listClaims(options.cwd).filter((item) => item.status === 'active' && item.plan_id === existing.plan_id);
21
+ if (options.planStatus) {
22
+ plan.status = options.planStatus;
23
+ }
24
+ else if (otherActiveClaims.length === 0 && plan.status === 'in_progress') {
25
+ plan.status = 'todo';
26
+ }
27
+ if (otherActiveClaims.length === 0 && plan.assignee === existing.agent) {
28
+ plan.assignee = undefined;
29
+ }
30
+ plan.updated_at = new Date().toISOString();
31
+ saveState(state, options.cwd);
21
32
  }
22
- else if (otherActiveClaims.length === 0 && plan.status === 'in_progress') {
23
- plan.status = 'todo';
24
- }
25
- if (otherActiveClaims.length === 0 && plan.assignee === existing.agent) {
26
- plan.assignee = undefined;
27
- }
28
- plan.updated_at = new Date().toISOString();
29
- saveState(state, options.cwd);
30
33
  }
31
- }
32
- writeFileAtomic(memoryPath('project.md', options.cwd), generateMarkdown(state, options.cwd));
34
+ writeFileAtomic(memoryPath('project.md', options.cwd), generateMarkdown(state, options.cwd));
35
+ });
33
36
  console.log(`✔ Claim [${id}] released (was: ${claim.agent} → ${claim.scope})`);
34
37
  }
35
38
  catch (e) {
@@ -1,5 +1,5 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
2
+ import { memoryExists, memoryPath, withStoreLock, writeFileAtomic } from '../core/io.js';
3
3
  import { listClaims, releaseClaim } from '../core/claims.js';
4
4
  import { generateMarkdown } from '../core/markdown.js';
5
5
  import { loadState, saveState } from '../core/state.js';
@@ -35,31 +35,32 @@ export function runReleaseClaims(options = {}) {
35
35
  const toRelease = claims.filter((c) => changedFiles.some((f) => scopeMatchesFile(c.scope, f)));
36
36
  if (toRelease.length === 0)
37
37
  process.exit(0);
38
- let state = loadState(options.cwd);
39
38
  let released = 0;
40
- for (const claim of toRelease) {
41
- try {
42
- releaseClaim(claim.id, options.cwd);
43
- released++;
44
- // Revert in_progress plan to todo if no more active claims
45
- if (claim.plan_id) {
46
- state = loadState(options.cwd);
47
- const plan = state.plan_items.find((p) => p.id === claim.plan_id);
48
- if (plan) {
49
- const remaining = listClaims(options.cwd).filter((c) => c.status === 'active' && c.plan_id === claim.plan_id);
50
- if (remaining.length === 0 && plan.status === 'in_progress') {
51
- plan.status = 'todo';
52
- plan.updated_at = new Date().toISOString();
53
- saveState(state, options.cwd);
39
+ withStoreLock(options.cwd, () => {
40
+ let state = loadState(options.cwd);
41
+ for (const claim of toRelease) {
42
+ try {
43
+ releaseClaim(claim.id, options.cwd);
44
+ released++;
45
+ if (claim.plan_id) {
46
+ state = loadState(options.cwd);
47
+ const plan = state.plan_items.find((p) => p.id === claim.plan_id);
48
+ if (plan) {
49
+ const remaining = listClaims(options.cwd).filter((c) => c.status === 'active' && c.plan_id === claim.plan_id);
50
+ if (remaining.length === 0 && plan.status === 'in_progress') {
51
+ plan.status = 'todo';
52
+ plan.updated_at = new Date().toISOString();
53
+ saveState(state, options.cwd);
54
+ }
54
55
  }
55
56
  }
57
+ console.log(`✔ Auto-released claim [${claim.id}]: ${claim.scope}`);
56
58
  }
57
- console.log(`✔ Auto-released claim [${claim.id}]: ${claim.scope}`);
59
+ catch { /* skip individual failures */ }
58
60
  }
59
- catch { /* skip individual failures */ }
60
- }
61
- state = loadState(options.cwd);
62
- writeFileAtomic(memoryPath('project.md', options.cwd), generateMarkdown(state, options.cwd));
61
+ state = loadState(options.cwd);
62
+ writeFileAtomic(memoryPath('project.md', options.cwd), generateMarkdown(state, options.cwd));
63
+ });
63
64
  if (released > 0) {
64
65
  console.log(`brainclaw: ${released} claim(s) auto-released after merge.`);
65
66
  }
File without changes
@@ -1,6 +1,6 @@
1
1
  import { memoryExists } from '../core/io.js';
2
2
  import { readAuditLog } from '../core/audit.js';
3
- import { loadState, saveState } from '../core/state.js';
3
+ import { loadState, persistState } from '../core/state.js';
4
4
  import { loadCandidate, saveCandidate } from '../core/candidates.js';
5
5
  import { appendAuditEntry } from '../core/audit.js';
6
6
  import { buildOperationalIdentity } from '../core/identity.js';
@@ -128,7 +128,7 @@ function applyRollback(entry, actorName, options) {
128
128
  state.known_traps.push(before);
129
129
  }
130
130
  }
131
- saveState(state);
131
+ persistState(state);
132
132
  }
133
133
  else {
134
134
  return { ok: false, message: `Rollback not supported for item_type '${item_type}'.` };
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,7 +1,6 @@
1
- import { loadState, saveState } from '../core/state.js';
1
+ import { loadState, persistState } from '../core/state.js';
2
2
  import { resolveCurrentAgentName } from '../core/agent-registry.js';
3
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
4
- import { generateMarkdown } from '../core/markdown.js';
3
+ import { memoryExists } from '../core/io.js';
5
4
  import { generateIdWithLabel, nowISO } from '../core/ids.js';
6
5
  import { loadConfig } from '../core/config.js';
7
6
  import { scanText } from '../core/security.js';
@@ -89,8 +88,7 @@ function runToolAdd(name, description, options, cwd) {
89
88
  // For now, store as decision to avoid schema migration
90
89
  // Will migrate to separate tool storage in v0.16
91
90
  state.recent_decisions.push(entry);
92
- saveState(state, cwd);
93
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state));
91
+ persistState(state, cwd);
94
92
  console.log(`✔ Tool added: [${id}] ${name}`);
95
93
  console.log(' (Stored in decisions for now; will move to dedicated registry in v0.16)');
96
94
  }
@@ -1,11 +1,10 @@
1
- import { loadState, saveState } from '../core/state.js';
1
+ import { loadState, persistState } from '../core/state.js';
2
2
  import { resolveCurrentAgentName } from '../core/agent-registry.js';
3
3
  import { resolveCurrentHostId } from '../core/host.js';
4
4
  import { loadConfig } from '../core/config.js';
5
- import { generateMarkdown } from '../core/markdown.js';
6
5
  import { nowISO } from '../core/ids.js';
7
6
  import { scanText } from '../core/security.js';
8
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
7
+ import { memoryExists } from '../core/io.js';
9
8
  import { generateTrapIdWithLabel, saveOperationalTrap } from '../core/traps.js';
10
9
  import { validateCliInput, validateCliTtl } from '../core/input-validation.js';
11
10
  import { resolveTargetStore } from '../core/store-resolution.js';
@@ -49,8 +48,7 @@ export function runTrap(text, options = {}) {
49
48
  };
50
49
  if (visibility === 'shared') {
51
50
  state.known_traps.push(entry);
52
- saveState(state, cwd);
53
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
51
+ persistState(state, cwd);
54
52
  }
55
53
  else {
56
54
  saveOperationalTrap(entry, cwd);
@@ -1,6 +1,5 @@
1
- import { loadState, saveState } from '../core/state.js';
2
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
3
- import { generateMarkdown } from '../core/markdown.js';
1
+ import { loadState, persistState } from '../core/state.js';
2
+ import { memoryExists } from '../core/io.js';
4
3
  export function runUpdateHandoff(id, options = {}) {
5
4
  if (!memoryExists()) {
6
5
  console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
@@ -16,8 +15,7 @@ export function runUpdateHandoff(id, options = {}) {
16
15
  handoff.status = options.status;
17
16
  if (options.to !== undefined)
18
17
  handoff.to = options.to;
19
- saveState(state);
20
- writeFileAtomic(memoryPath('project.md'), generateMarkdown(state));
18
+ persistState(state);
21
19
  console.log(`✔ Handoff updated: [${handoff.id}] ${handoff.from} → ${handoff.to} (${handoff.status})`);
22
20
  }
23
21
  //# sourceMappingURL=update-handoff.js.map
@@ -1,6 +1,5 @@
1
- import { loadState, saveState } from '../core/state.js';
2
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
3
- import { generateMarkdown } from '../core/markdown.js';
1
+ import { loadState, persistState } from '../core/state.js';
2
+ import { memoryExists } from '../core/io.js';
4
3
  import { nowISO } from '../core/ids.js';
5
4
  export function runUpdatePlan(id, options = {}) {
6
5
  if (!memoryExists(options.cwd)) {
@@ -30,8 +29,7 @@ export function runUpdatePlan(id, options = {}) {
30
29
  if (options.actualEffort)
31
30
  plan.actual_effort = options.actualEffort;
32
31
  plan.updated_at = timestamp;
33
- saveState(state, options.cwd);
34
- writeFileAtomic(memoryPath('project.md', options.cwd), generateMarkdown(state, options.cwd));
32
+ persistState(state, options.cwd);
35
33
  console.log(`✔ Plan item updated: [${plan.id}] ${plan.text}`);
36
34
  }
37
35
  //# sourceMappingURL=update-plan.js.map
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { ensureMemoryDir, memoryDir, memoryExists } from '../core/io.js';
4
- import { loadState, saveState } from '../core/state.js';
4
+ import { loadState, persistState } from '../core/state.js';
5
5
  import { scanMigrationStatus } from '../core/migration.js';
6
6
  import { commitMemoryChange, initMemoryRepo } from '../core/memory-git.js';
7
7
  import { BRAINCLAW_SECTION_END, BRAINCLAW_SECTION_START, buildBrainclawSection, buildClaudeCodeCommandText, ensureClaudeCodeCommand, hasBrainclawSection, } from '../core/agent-files.js';
@@ -114,7 +114,7 @@ export function runUpgrade(options = {}) {
114
114
  // Execute schema migrations by re-saving state (loadState auto-migrates via Zod parse)
115
115
  if (outdated.length > 0) {
116
116
  const state = loadState(cwd);
117
- saveState(state, cwd);
117
+ persistState(state, cwd);
118
118
  }
119
119
  const refreshedAgentFiles = refreshManagedWorkspaceAgentFiles(cwd);
120
120
  // Clean up empty legacy directories (recursively removes empty subdirs first)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { memoryDir } from './io.js';
3
+ import { memoryDir, withStoreLock } from './io.js';
4
4
  import { nowISO } from './ids.js';
5
5
  import { logger } from './logger.js';
6
6
  import { appendEvent } from './event-log.js';
@@ -23,31 +23,31 @@ function auditLogPath(cwd) {
23
23
  }
24
24
  export function appendAuditEntry(entry, cwd) {
25
25
  try {
26
- const full = {
27
- timestamp: nowISO(),
28
- actor: entry.actor,
29
- action: entry.action,
30
- item_id: entry.item_id,
31
- item_type: entry.item_type,
32
- before: entry.before,
33
- after: entry.after,
34
- actor_id: entry.actor_id,
35
- reason: entry.reason,
36
- };
37
- // Remove undefined fields for compactness
38
- const line = JSON.stringify(Object.fromEntries(Object.entries(full).filter(([, v]) => v !== undefined)));
39
- fs.appendFileSync(auditLogPath(cwd), line + '\n', 'utf-8');
40
- // Mirror to structured event log
41
- const eventAction = AUDIT_TO_EVENT_ACTION[entry.action];
42
- if (eventAction) {
43
- appendEvent({
44
- action: eventAction,
45
- item_type: entry.item_type ?? 'state',
26
+ withStoreLock(cwd, () => {
27
+ const full = {
28
+ timestamp: nowISO(),
29
+ actor: entry.actor,
30
+ action: entry.action,
46
31
  item_id: entry.item_id,
47
- agent: entry.actor,
48
- agent_id: entry.actor_id,
49
- }, cwd);
50
- }
32
+ item_type: entry.item_type,
33
+ before: entry.before,
34
+ after: entry.after,
35
+ actor_id: entry.actor_id,
36
+ reason: entry.reason,
37
+ };
38
+ const line = JSON.stringify(Object.fromEntries(Object.entries(full).filter(([, v]) => v !== undefined)));
39
+ fs.appendFileSync(auditLogPath(cwd), line + '\n', 'utf-8');
40
+ const eventAction = AUDIT_TO_EVENT_ACTION[entry.action];
41
+ if (eventAction) {
42
+ appendEvent({
43
+ action: eventAction,
44
+ item_type: entry.item_type ?? 'state',
45
+ item_id: entry.item_id,
46
+ agent: entry.actor,
47
+ agent_id: entry.actor_id,
48
+ }, cwd);
49
+ }
50
+ });
51
51
  }
52
52
  catch (err) {
53
53
  logger.debug('Failed to write audit log entry:', err);
File without changes