brainclaw 0.19.6 → 0.19.10

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 (51) hide show
  1. package/README.md +225 -126
  2. package/dist/cli.js +8 -3
  3. package/dist/commands/accept.js +102 -104
  4. package/dist/commands/add-step.js +3 -5
  5. package/dist/commands/bootstrap.js +72 -3
  6. package/dist/commands/capability.js +3 -5
  7. package/dist/commands/claim.js +14 -12
  8. package/dist/commands/complete-step.js +3 -5
  9. package/dist/commands/constraint.js +3 -5
  10. package/dist/commands/decision.js +3 -6
  11. package/dist/commands/delete-plan.js +3 -5
  12. package/dist/commands/handoff.js +3 -5
  13. package/dist/commands/init.js +20 -0
  14. package/dist/commands/instruction.js +16 -9
  15. package/dist/commands/mcp.js +18 -22
  16. package/dist/commands/memory.js +4 -7
  17. package/dist/commands/plan.js +3 -5
  18. package/dist/commands/prune.js +27 -25
  19. package/dist/commands/reflect.js +3 -5
  20. package/dist/commands/release-claim.js +23 -20
  21. package/dist/commands/release-claims.js +22 -21
  22. package/dist/commands/rollback.js +2 -2
  23. package/dist/commands/tool.js +3 -5
  24. package/dist/commands/trap.js +3 -5
  25. package/dist/commands/update-handoff.js +3 -5
  26. package/dist/commands/update-plan.js +3 -5
  27. package/dist/commands/upgrade.js +2 -2
  28. package/dist/core/audit.js +25 -25
  29. package/dist/core/bootstrap.js +587 -4
  30. package/dist/core/candidates.js +10 -6
  31. package/dist/core/claims.js +10 -8
  32. package/dist/core/coordination.js +2 -2
  33. package/dist/core/instructions.js +4 -2
  34. package/dist/core/io.js +8 -0
  35. package/dist/core/lock.js +18 -2
  36. package/dist/core/migration.js +6 -2
  37. package/dist/core/runtime.js +18 -14
  38. package/dist/core/schema.js +69 -0
  39. package/dist/core/state.js +21 -4
  40. package/docs/cli.md +21 -1
  41. package/docs/integrations/agents.md +42 -29
  42. package/docs/integrations/claude-code.md +4 -4
  43. package/docs/integrations/codex.md +5 -5
  44. package/docs/integrations/copilot.md +3 -2
  45. package/docs/integrations/cursor.md +4 -4
  46. package/docs/integrations/mcp.md +53 -24
  47. package/docs/integrations/overview.md +53 -40
  48. package/docs/mcp-schema-changelog.md +3 -0
  49. package/docs/product/positioning.md +17 -18
  50. package/docs/quickstart.md +87 -55
  51. package/package.json +1 -2
@@ -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,12 +1,42 @@
1
+ import readline from 'node:readline/promises';
2
+ import { stdin as input, stdout as output } from 'node:process';
1
3
  import { memoryExists } from '../core/io.js';
2
- import { renderBootstrapSummary, runBootstrapProfile } from '../core/bootstrap.js';
3
- export function runBootstrap(options = {}) {
4
+ import { applyBootstrapImport, renderBootstrapInterview, renderBootstrapSummary, runBootstrapProfile, uninstallBootstrapImport, } from '../core/bootstrap.js';
5
+ export async function runBootstrap(options = {}) {
4
6
  const cwd = options.cwd ?? process.cwd();
5
7
  if (!memoryExists(cwd)) {
6
8
  console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
7
9
  process.exit(1);
8
10
  }
9
11
  try {
12
+ if (options.apply && options.uninstall) {
13
+ console.error('Error: --apply and --uninstall are mutually exclusive.');
14
+ process.exit(1);
15
+ }
16
+ const audience = resolveBootstrapInterviewAudience(options.audience);
17
+ if (options.uninstall) {
18
+ await confirmBootstrapAction('Remove the last bootstrap import?', options.yes);
19
+ const result = uninstallBootstrapImport(cwd);
20
+ if (!result.receipt) {
21
+ console.log('No bootstrap import receipt found.');
22
+ return;
23
+ }
24
+ console.log(`✔ Bootstrap uninstall completed: ${result.deactivatedCount} instruction(s) deactivated, ${result.skippedCount} artifact(s) skipped.`);
25
+ return;
26
+ }
27
+ if (options.apply) {
28
+ await confirmBootstrapAction('Apply the current bootstrap import proposal to canonical memory?', options.yes);
29
+ const result = applyBootstrapImport({
30
+ target: options.for,
31
+ refresh: options.refresh,
32
+ cwd,
33
+ });
34
+ console.log(`✔ Bootstrap import applied: ${result.createdCount} item(s) created, ${result.skippedCount} suggestion(s) skipped.`);
35
+ if (result.receipt) {
36
+ console.log(`✔ Receipt saved: ${result.receipt.managed_artifacts.length} managed artifact(s) can be reverted with \`brainclaw bootstrap --uninstall\`.`);
37
+ }
38
+ return;
39
+ }
10
40
  const result = runBootstrapProfile({
11
41
  target: options.for,
12
42
  refresh: options.refresh,
@@ -18,17 +48,56 @@ export function runBootstrap(options = {}) {
18
48
  target: result.profile.target,
19
49
  repo_fingerprint: result.profile.repo_fingerprint,
20
50
  sources_scanned: result.profile.sources_scanned,
51
+ workspace_kind: result.profile.workspace_kind,
52
+ confidence: result.profile.confidence,
53
+ native_instruction_files: result.profile.native_instruction_files,
54
+ gaps: result.profile.gaps,
21
55
  seed_count: result.profile.seed_count,
22
56
  seeds: result.seeds,
57
+ import_plan: result.importPlan,
58
+ last_application: result.lastApplication,
23
59
  reused_profile: result.reusedProfile,
24
60
  }, null, 2));
25
61
  return;
26
62
  }
27
- console.log(renderBootstrapSummary(result));
63
+ console.log(options.interview ? renderBootstrapInterview(result, audience) : renderBootstrapSummary(result));
64
+ }
65
+ catch (error) {
66
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
67
+ process.exit(1);
68
+ }
69
+ }
70
+ function resolveBootstrapInterviewAudience(value) {
71
+ if (!value) {
72
+ return 'any';
73
+ }
74
+ if (value === 'cli' || value === 'ide_chat' || value === 'any') {
75
+ return value;
76
+ }
77
+ throw new Error(`Unsupported bootstrap interview audience '${value}'. Use cli, ide_chat, or any.`);
78
+ }
79
+ async function confirmBootstrapAction(question, yes) {
80
+ if (yes) {
81
+ return;
82
+ }
83
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
84
+ console.error(`Error: ${question} Re-run with --yes in non-interactive mode.`);
85
+ process.exit(1);
86
+ }
87
+ const rl = readline.createInterface({ input, output });
88
+ try {
89
+ const answer = await rl.question(`${question} [y/N] `);
90
+ if (answer.trim().toLowerCase() !== 'y') {
91
+ console.error('Cancelled.');
92
+ process.exit(1);
93
+ }
28
94
  }
29
95
  catch (error) {
30
96
  console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
31
97
  process.exit(1);
32
98
  }
99
+ finally {
100
+ rl.close();
101
+ }
33
102
  }
34
103
  //# sourceMappingURL=bootstrap.js.map
@@ -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
@@ -10,6 +10,7 @@ import { initMemoryRepo } from '../core/memory-git.js';
10
10
  import { buildProjectIdentity, resolveExistingProjectIdentity, saveProjectIdentity } from '../core/project-registry.js';
11
11
  import { scanProject, upsertProject } from '../core/global-registry.js';
12
12
  import { analyzeRepository, scanWorkspaceBoundaries } from '../core/repo-analysis.js';
13
+ import { renderBootstrapSummary, runBootstrapProfile } from '../core/bootstrap.js';
13
14
  import { isAgentIntegrationName, upsertAgentIntegrationDeclaration } from '../core/agent-integrations.js';
14
15
  import { describeAutoConfigWrite, ensureAgentFiles, ensureGitignoreEntries, writeDetectedAgentAutoConfig } from '../core/agent-files.js';
15
16
  import { detectAiAgent, detectWslEnvironment } from '../core/ai-agent-detection.js';
@@ -228,6 +229,25 @@ export async function runInit(options = {}) {
228
229
  if (initMemoryRepo(cwd)) {
229
230
  console.log('✔ Initialized memory git repo for versioning');
230
231
  }
232
+ const onboardingPreflight = runBootstrapProfile({ cwd, refresh: true });
233
+ console.log('');
234
+ console.log('Onboarding preflight:');
235
+ for (const line of renderBootstrapSummary(onboardingPreflight).split('\n')) {
236
+ console.log(` ${line}`);
237
+ }
238
+ if (onboardingPreflight.importPlan.suggestion_count > 0) {
239
+ console.log('');
240
+ console.log(`Next step: run 'brainclaw bootstrap --apply' to import ${onboardingPreflight.importPlan.suggestion_count} suggested item(s) into canonical memory.`);
241
+ console.log(`Rollback: run 'brainclaw bootstrap --uninstall' to deactivate the last bootstrap-managed import.`);
242
+ }
243
+ if ((onboardingPreflight.importPlan.interview?.question_count ?? 0) > 0) {
244
+ console.log('');
245
+ console.log(`Interview: run 'brainclaw bootstrap --interview --audience cli' for terminal agents or '--audience ide_chat' for IDE chat agents.`);
246
+ }
247
+ else if ((onboardingPreflight.profile.gaps?.length ?? 0) > 0) {
248
+ console.log('');
249
+ console.log(`Next step: review the onboarding gaps, then use 'brainclaw bootstrap --json' as the basis for an interview/import flow.`);
250
+ }
231
251
  console.log('');
232
252
  console.log(`Tip: run 'brainclaw context --json' to load the shared memory into your agent session.`);
233
253
  }
@@ -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) {