brainclaw 0.19.6 → 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.
@@ -1,7 +1,6 @@
1
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
1
+ import { memoryExists, withStoreLock } from '../core/io.js';
2
2
  import { loadCandidate, archiveCandidate, resolveIdOrAlias } from '../core/candidates.js';
3
- import { loadState, saveState } from '../core/state.js';
4
- import { generateMarkdown } from '../core/markdown.js';
3
+ import { loadState, persistState } from '../core/state.js';
5
4
  import { generateIdWithLabel, nowISO } from '../core/ids.js';
6
5
  import { generateTrapIdWithLabel } from '../core/traps.js';
7
6
  import { requireMinimumTrustLevel, requireRegisteredAgentIdentity } from '../core/agent-registry.js';
@@ -36,109 +35,108 @@ export function acceptCandidate(id, by, cwd, byId) {
36
35
  });
37
36
  requireMinimumTrustLevel(actorIdentity, 'trusted');
38
37
  const actor = actorIdentity.agent_name;
39
- const state = loadState(cwd);
40
38
  let promotedItemId = '';
41
- // Promote candidate into canonical state based on type
42
- switch (candidate.type) {
43
- case 'constraint': {
44
- const { id: entryId, short_label } = generateIdWithLabel('active_constraints', cwd);
45
- const entry = {
46
- id: entryId,
47
- short_label,
48
- text: candidate.text,
49
- created_at: candidate.created_at,
50
- author: candidate.author,
51
- author_id: candidate.author_id,
52
- project_id: candidate.project_id,
53
- host_id: candidate.host_id,
54
- session_id: candidate.session_id,
55
- status: 'active',
56
- tags: candidate.tags,
57
- };
58
- state.active_constraints.push(entry);
59
- promotedItemId = entryId;
60
- break;
39
+ withStoreLock(cwd, () => {
40
+ const state = loadState(cwd);
41
+ switch (candidate.type) {
42
+ case 'constraint': {
43
+ const { id: entryId, short_label } = generateIdWithLabel('active_constraints', cwd);
44
+ const entry = {
45
+ id: entryId,
46
+ short_label,
47
+ text: candidate.text,
48
+ created_at: candidate.created_at,
49
+ author: candidate.author,
50
+ author_id: candidate.author_id,
51
+ project_id: candidate.project_id,
52
+ host_id: candidate.host_id,
53
+ session_id: candidate.session_id,
54
+ status: 'active',
55
+ tags: candidate.tags,
56
+ };
57
+ state.active_constraints.push(entry);
58
+ promotedItemId = entryId;
59
+ break;
60
+ }
61
+ case 'decision': {
62
+ const { id: entryId, short_label } = generateIdWithLabel('recent_decisions', cwd);
63
+ const entry = {
64
+ id: entryId,
65
+ short_label,
66
+ text: candidate.text,
67
+ created_at: candidate.created_at,
68
+ author: candidate.author,
69
+ author_id: candidate.author_id,
70
+ project_id: candidate.project_id,
71
+ host_id: candidate.host_id,
72
+ session_id: candidate.session_id,
73
+ related_paths: candidate.related_paths,
74
+ plan_id: candidate.plan_id,
75
+ tags: candidate.tags,
76
+ };
77
+ state.recent_decisions.push(entry);
78
+ promotedItemId = entryId;
79
+ break;
80
+ }
81
+ case 'trap': {
82
+ const { id: entryId, short_label } = generateTrapIdWithLabel(cwd);
83
+ const entry = {
84
+ id: entryId,
85
+ short_label,
86
+ text: candidate.text,
87
+ created_at: candidate.created_at,
88
+ author: candidate.author,
89
+ author_id: candidate.author_id,
90
+ project_id: candidate.project_id,
91
+ host_id: candidate.host_id,
92
+ session_id: candidate.session_id,
93
+ status: 'active',
94
+ severity: candidate.severity ?? 'medium',
95
+ tags: candidate.tags,
96
+ plan_id: candidate.plan_id,
97
+ visibility: 'shared',
98
+ };
99
+ state.known_traps.push(entry);
100
+ promotedItemId = entryId;
101
+ break;
102
+ }
103
+ case 'handoff': {
104
+ const { id: entryId, short_label } = generateIdWithLabel('open_handoffs', cwd);
105
+ const entry = {
106
+ id: entryId,
107
+ short_label,
108
+ from: candidate.from ?? 'unknown',
109
+ to: candidate.to ?? 'unknown',
110
+ text: candidate.text,
111
+ created_at: candidate.created_at,
112
+ author: candidate.author,
113
+ author_id: candidate.author_id,
114
+ project_id: candidate.project_id,
115
+ host_id: candidate.host_id,
116
+ session_id: candidate.session_id,
117
+ status: 'open',
118
+ tags: candidate.tags,
119
+ };
120
+ state.open_handoffs.push(entry);
121
+ promotedItemId = entryId;
122
+ break;
123
+ }
61
124
  }
62
- case 'decision': {
63
- const { id: entryId, short_label } = generateIdWithLabel('recent_decisions', cwd);
64
- const entry = {
65
- id: entryId,
66
- short_label,
67
- text: candidate.text,
68
- created_at: candidate.created_at,
69
- author: candidate.author,
70
- author_id: candidate.author_id,
71
- project_id: candidate.project_id,
72
- host_id: candidate.host_id,
73
- session_id: candidate.session_id,
74
- related_paths: candidate.related_paths,
75
- plan_id: candidate.plan_id,
76
- tags: candidate.tags,
77
- };
78
- state.recent_decisions.push(entry);
79
- promotedItemId = entryId;
80
- break;
81
- }
82
- case 'trap': {
83
- const { id: entryId, short_label } = generateTrapIdWithLabel(cwd);
84
- const entry = {
85
- id: entryId,
86
- short_label,
87
- text: candidate.text,
88
- created_at: candidate.created_at,
89
- author: candidate.author,
90
- author_id: candidate.author_id,
91
- project_id: candidate.project_id,
92
- host_id: candidate.host_id,
93
- session_id: candidate.session_id,
94
- status: 'active',
95
- severity: candidate.severity ?? 'medium',
96
- tags: candidate.tags,
97
- plan_id: candidate.plan_id,
98
- visibility: 'shared',
99
- };
100
- state.known_traps.push(entry);
101
- promotedItemId = entryId;
102
- break;
103
- }
104
- case 'handoff': {
105
- const { id: entryId, short_label } = generateIdWithLabel('open_handoffs', cwd);
106
- const entry = {
107
- id: entryId,
108
- short_label,
109
- from: candidate.from ?? 'unknown',
110
- to: candidate.to ?? 'unknown',
111
- text: candidate.text,
112
- created_at: candidate.created_at,
113
- author: candidate.author,
114
- author_id: candidate.author_id,
115
- project_id: candidate.project_id,
116
- host_id: candidate.host_id,
117
- session_id: candidate.session_id,
118
- status: 'open',
119
- tags: candidate.tags,
120
- };
121
- state.open_handoffs.push(entry);
122
- promotedItemId = entryId;
123
- break;
124
- }
125
- }
126
- saveState(state, cwd);
127
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
128
- // Archive candidate (use resolved hash ID)
129
- candidate.status = 'accepted';
130
- candidate.resolved_at = nowISO();
131
- candidate.resolved_by = actor;
132
- archiveCandidate(candidate, 'accepted', cwd);
133
- appendAuditEntry({
134
- actor,
135
- actor_id: actorIdentity.agent_id,
136
- action: 'accept',
137
- item_id: resolvedId,
138
- item_type: candidate.type,
139
- after: { type: candidate.type, text: candidate.text },
140
- reason: 'trusted-agent',
141
- }, cwd);
125
+ persistState(state, cwd);
126
+ candidate.status = 'accepted';
127
+ candidate.resolved_at = nowISO();
128
+ candidate.resolved_by = actor;
129
+ archiveCandidate(candidate, 'accepted', cwd);
130
+ appendAuditEntry({
131
+ actor,
132
+ actor_id: actorIdentity.agent_id,
133
+ action: 'accept',
134
+ item_id: resolvedId,
135
+ item_type: candidate.type,
136
+ after: { type: candidate.type, text: candidate.text },
137
+ reason: 'trusted-agent',
138
+ }, cwd);
139
+ });
142
140
  return {
143
141
  candidate_id: resolvedId,
144
142
  candidate_type: candidate.type,
@@ -1,7 +1,6 @@
1
- import { loadState, saveState } from '../core/state.js';
2
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
1
+ import { loadState, persistState } from '../core/state.js';
2
+ import { memoryExists } from '../core/io.js';
3
3
  import { generateId, nowISO } from '../core/ids.js';
4
- import { generateMarkdown } from '../core/markdown.js';
5
4
  import { validateCliInput } from '../core/input-validation.js';
6
5
  export function runAddStep(planId, text, options = {}) {
7
6
  if (!memoryExists()) {
@@ -25,8 +24,7 @@ export function runAddStep(planId, text, options = {}) {
25
24
  };
26
25
  plan.steps = [...(plan.steps ?? []), step];
27
26
  plan.updated_at = nowISO();
28
- saveState(state);
29
- writeFileAtomic(memoryPath('project.md'), generateMarkdown(state));
27
+ persistState(state);
30
28
  const total = plan.steps.length;
31
29
  const done = plan.steps.filter((s) => s.status === 'done').length;
32
30
  console.log(`✔ Step added: [${step.id}] ${text}`);
@@ -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';
@@ -80,8 +79,7 @@ function runCapabilityAdd(name, description, options, cwd) {
80
79
  // For now, store as decision to avoid schema migration
81
80
  // Will migrate to separate capability storage in v0.16
82
81
  state.recent_decisions.push(entry);
83
- saveState(state, cwd);
84
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state));
82
+ persistState(state, cwd);
85
83
  console.log(`✔ Capability added: [${id}] ${name}`);
86
84
  console.log(' (Stored in decisions for now; will move to dedicated registry in v0.16)');
87
85
  }
@@ -1,5 +1,5 @@
1
1
  import { buildOperationalIdentity } from '../core/identity.js';
2
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
2
+ import { memoryExists, memoryPath, withStoreLock, writeFileAtomic } from '../core/io.js';
3
3
  import { saveClaim, generateClaimId, listClaims } from '../core/claims.js';
4
4
  import { generateMarkdown } from '../core/markdown.js';
5
5
  import { loadState, saveState } from '../core/state.js';
@@ -72,18 +72,20 @@ export function runClaim(description, options) {
72
72
  status: 'active',
73
73
  expires_at: options.ttl ? parseTtl(options.ttl) : undefined,
74
74
  };
75
- if (plan) {
76
- if (!plan.assignee) {
77
- plan.assignee = actor.agent;
75
+ withStoreLock(options.cwd, () => {
76
+ if (plan) {
77
+ if (!plan.assignee) {
78
+ plan.assignee = actor.agent;
79
+ }
80
+ if (plan.status === 'todo') {
81
+ plan.status = 'in_progress';
82
+ }
83
+ plan.updated_at = nowISO();
84
+ saveState(state, options.cwd);
78
85
  }
79
- if (plan.status === 'todo') {
80
- plan.status = 'in_progress';
81
- }
82
- plan.updated_at = nowISO();
83
- saveState(state, options.cwd);
84
- }
85
- saveClaim(claim, options.cwd);
86
- writeFileAtomic(memoryPath('project.md', options.cwd), generateMarkdown(plan ? state : loadState(options.cwd), options.cwd));
86
+ saveClaim(claim, options.cwd);
87
+ writeFileAtomic(memoryPath('project.md', options.cwd), generateMarkdown(plan ? state : loadState(options.cwd), options.cwd));
88
+ });
87
89
  const planInfo = claim.plan_id ? ` [plan ${claim.plan_id}]` : '';
88
90
  const ttlInfo = claim.expires_at ? ` (expires ${claim.expires_at.slice(0, 16).replace('T', ' ')})` : '';
89
91
  const storeLabel = options.store && options.store !== 'local' ? ` [store:${options.store}]` : '';
@@ -1,7 +1,6 @@
1
- import { loadState, saveState } from '../core/state.js';
2
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
1
+ import { loadState, persistState } from '../core/state.js';
2
+ import { memoryExists } from '../core/io.js';
3
3
  import { nowISO } from '../core/ids.js';
4
- import { generateMarkdown } from '../core/markdown.js';
5
4
  export function runCompleteStep(planId, stepId) {
6
5
  if (!memoryExists()) {
7
6
  console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
@@ -21,8 +20,7 @@ export function runCompleteStep(planId, stepId) {
21
20
  step.status = 'done';
22
21
  step.updated_at = nowISO();
23
22
  plan.updated_at = nowISO();
24
- saveState(state);
25
- writeFileAtomic(memoryPath('project.md'), generateMarkdown(state));
23
+ persistState(state);
26
24
  const total = plan.steps.length;
27
25
  const done = plan.steps.filter((s) => s.status === 'done').length;
28
26
  console.log(`✔ Step completed: [${step.id}] ${step.text}`);
@@ -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 { resolveTargetStore } from '../core/store-resolution.js';
10
9
  export function runConstraint(text, options = {}) {
@@ -37,8 +36,7 @@ export function runConstraint(text, options = {}) {
37
36
  related_paths: options.path,
38
37
  };
39
38
  state.active_constraints.push(entry);
40
- saveState(state, cwd);
41
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state));
39
+ persistState(state, cwd);
42
40
  console.log(`✔ Constraint added: [${id}] ${text}`);
43
41
  }
44
42
  //# sourceMappingURL=constraint.js.map
@@ -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 { resolveTargetStore } from '../core/store-resolution.js';
10
9
  export function runDecision(text, options = {}) {
@@ -37,9 +36,7 @@ export function runDecision(text, options = {}) {
37
36
  plan_id: options.plan,
38
37
  };
39
38
  state.recent_decisions.push(entry);
40
- saveState(state, cwd);
41
- // Rebuild markdown
42
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state));
39
+ persistState(state, cwd);
43
40
  console.log(`✔ Decision added: [${id}] ${text}`);
44
41
  }
45
42
  //# sourceMappingURL=decision.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
  export function runDeletePlan(id, options = {}) {
5
4
  if (!memoryExists(options.cwd)) {
6
5
  console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
@@ -13,8 +12,7 @@ export function runDeletePlan(id, options = {}) {
13
12
  process.exit(1);
14
13
  }
15
14
  const [plan] = state.plan_items.splice(index, 1);
16
- saveState(state, options.cwd);
17
- writeFileAtomic(memoryPath('project.md', options.cwd), generateMarkdown(state, options.cwd));
15
+ persistState(state, options.cwd);
18
16
  console.log(`✔ Plan item deleted: [${plan.id}] ${plan.text}`);
19
17
  }
20
18
  //# sourceMappingURL=delete-plan.js.map
@@ -1,11 +1,10 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { loadState, saveState } from '../core/state.js';
2
+ import { loadState, persistState } from '../core/state.js';
3
3
  import { resolveCurrentAgentName } from '../core/agent-registry.js';
4
4
  import { loadConfig } from '../core/config.js';
5
- import { generateMarkdown } from '../core/markdown.js';
6
5
  import { generateIdWithLabel, 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 { validateCliInput } from '../core/input-validation.js';
10
9
  import { resolveTargetStore } from '../core/store-resolution.js';
11
10
  export function runHandoff(text, options) {
@@ -56,8 +55,7 @@ export function runHandoff(text, options) {
56
55
  snapshot: diff ? { diff } : undefined,
57
56
  };
58
57
  state.open_handoffs.push(entry);
59
- saveState(state, cwd);
60
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(state, cwd));
58
+ persistState(state, cwd);
61
59
  console.log(`✔ Handoff added: [${id}] ${options.from} → ${options.to}: ${text}`);
62
60
  }
63
61
  //# sourceMappingURL=handoff.js.map
@@ -1,7 +1,7 @@
1
1
  import { resolveAgentScope, resolveCurrentAgentName } from '../core/agent-registry.js';
2
2
  import { loadConfig } from '../core/config.js';
3
3
  import { createInstruction } from '../core/instructions.js';
4
- import { memoryExists, memoryPath, writeFileAtomic } from '../core/io.js';
4
+ import { memoryExists, memoryPath, withStoreLock, writeFileAtomic } from '../core/io.js';
5
5
  import { generateMarkdown } from '../core/markdown.js';
6
6
  import { loadState } from '../core/state.js';
7
7
  import { scanText } from '../core/security.js';
@@ -25,14 +25,21 @@ export function runInstruction(text, options = {}) {
25
25
  process.exit(1);
26
26
  }
27
27
  }
28
- const entry = createInstruction(text, {
29
- layer,
30
- scope,
31
- tags: options.tag,
32
- author: options.author ?? resolveCurrentAgentName(cwd),
33
- supersedes: options.supersedes,
34
- }, cwd);
35
- writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(loadState(cwd)));
28
+ let entry;
29
+ withStoreLock(cwd, () => {
30
+ entry = createInstruction(text, {
31
+ layer,
32
+ scope,
33
+ tags: options.tag,
34
+ author: options.author ?? resolveCurrentAgentName(cwd),
35
+ supersedes: options.supersedes,
36
+ }, cwd);
37
+ writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(loadState(cwd)));
38
+ });
39
+ if (!entry) {
40
+ console.error('Error: failed to persist instruction.');
41
+ process.exit(1);
42
+ }
36
43
  console.log(`✔ Instruction added: [${entry.id}] <${entry.layer}${entry.scope ? `:${entry.scope}` : ''}> ${entry.text}`);
37
44
  }
38
45
  function resolveScope(layer, options, cwd) {
@@ -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) {
@@ -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
  }