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.
- package/README.md +225 -126
- package/dist/cli.js +8 -3
- package/dist/commands/accept.js +102 -104
- package/dist/commands/add-step.js +3 -5
- package/dist/commands/bootstrap.js +72 -3
- package/dist/commands/capability.js +3 -5
- package/dist/commands/claim.js +14 -12
- package/dist/commands/complete-step.js +3 -5
- package/dist/commands/constraint.js +3 -5
- package/dist/commands/decision.js +3 -6
- package/dist/commands/delete-plan.js +3 -5
- package/dist/commands/handoff.js +3 -5
- package/dist/commands/init.js +20 -0
- package/dist/commands/instruction.js +16 -9
- package/dist/commands/mcp.js +18 -22
- package/dist/commands/memory.js +4 -7
- package/dist/commands/plan.js +3 -5
- package/dist/commands/prune.js +27 -25
- package/dist/commands/reflect.js +3 -5
- package/dist/commands/release-claim.js +23 -20
- package/dist/commands/release-claims.js +22 -21
- package/dist/commands/rollback.js +2 -2
- package/dist/commands/tool.js +3 -5
- package/dist/commands/trap.js +3 -5
- package/dist/commands/update-handoff.js +3 -5
- package/dist/commands/update-plan.js +3 -5
- package/dist/commands/upgrade.js +2 -2
- package/dist/core/audit.js +25 -25
- package/dist/core/bootstrap.js +587 -4
- package/dist/core/candidates.js +10 -6
- package/dist/core/claims.js +10 -8
- package/dist/core/coordination.js +2 -2
- package/dist/core/instructions.js +4 -2
- package/dist/core/io.js +8 -0
- package/dist/core/lock.js +18 -2
- package/dist/core/migration.js +6 -2
- package/dist/core/runtime.js +18 -14
- package/dist/core/schema.js +69 -0
- package/dist/core/state.js +21 -4
- package/docs/cli.md +21 -1
- package/docs/integrations/agents.md +42 -29
- package/docs/integrations/claude-code.md +4 -4
- package/docs/integrations/codex.md +5 -5
- package/docs/integrations/copilot.md +3 -2
- package/docs/integrations/cursor.md +4 -4
- package/docs/integrations/mcp.md +53 -24
- package/docs/integrations/overview.md +53 -40
- package/docs/mcp-schema-changelog.md +3 -0
- package/docs/product/positioning.md +17 -18
- package/docs/quickstart.md +87 -55
- package/package.json +1 -2
package/dist/core/candidates.js
CHANGED
|
@@ -2,7 +2,7 @@ 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 } from './io.js';
|
|
5
|
+
import { resolveEntityDir, withStoreLock } from './io.js';
|
|
6
6
|
import { nowISO, getNextShortLabel } from './ids.js';
|
|
7
7
|
import { JsonStore } from './json-store.js';
|
|
8
8
|
function inboxDir(cwd, mode = 'read') {
|
|
@@ -35,8 +35,10 @@ function candidateStore(dest = 'pending', cwd) {
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
export function saveCandidate(candidate, cwd) {
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
withStoreLock(cwd, () => {
|
|
39
|
+
ensureInboxDirs(cwd);
|
|
40
|
+
candidateStore('pending', cwd).save(CandidateSchema.parse(candidate));
|
|
41
|
+
});
|
|
40
42
|
}
|
|
41
43
|
export function loadCandidate(id, cwd) {
|
|
42
44
|
return candidateStore('pending', cwd).load(id);
|
|
@@ -49,9 +51,11 @@ export function listCandidates(status, cwd) {
|
|
|
49
51
|
return status ? candidates.filter((candidate) => candidate.status === status) : candidates;
|
|
50
52
|
}
|
|
51
53
|
export function archiveCandidate(candidate, dest, cwd) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
withStoreLock(cwd, () => {
|
|
55
|
+
ensureInboxDirs(cwd);
|
|
56
|
+
candidateStore(dest, cwd).save(CandidateSchema.parse(candidate));
|
|
57
|
+
candidateStore('pending', cwd).delete(candidate.id);
|
|
58
|
+
});
|
|
55
59
|
}
|
|
56
60
|
export function listArchivedCandidates(dest, cwd) {
|
|
57
61
|
return candidateStore(dest, cwd).list();
|
package/dist/core/claims.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { ClaimSchema } from './schema.js';
|
|
4
|
-
import { resolveEntityDir } from './io.js';
|
|
4
|
+
import { resolveEntityDir, withStoreLock } from './io.js';
|
|
5
5
|
import { nowISO } from './ids.js';
|
|
6
6
|
import { JsonStore } from './json-store.js';
|
|
7
7
|
function claimsDir(cwd, mode = 'read') {
|
|
@@ -22,14 +22,16 @@ function claimStore(cwd) {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
export function saveClaim(claim, cwd) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
withStoreLock(cwd, () => {
|
|
26
|
+
ensureClaimsDir(cwd);
|
|
27
|
+
const writeStore = new JsonStore({
|
|
28
|
+
dirPath: claimsDir(cwd, 'write'),
|
|
29
|
+
documentType: 'claim',
|
|
30
|
+
getId: (c) => c.id,
|
|
31
|
+
sort: (a, b) => a.created_at.localeCompare(b.created_at),
|
|
32
|
+
});
|
|
33
|
+
writeStore.save(ClaimSchema.parse(claim));
|
|
31
34
|
});
|
|
32
|
-
writeStore.save(ClaimSchema.parse(claim));
|
|
33
35
|
}
|
|
34
36
|
export function loadClaim(id, cwd) {
|
|
35
37
|
return claimStore(cwd).load(id);
|
|
@@ -5,7 +5,7 @@ import { listClaims } from './claims.js';
|
|
|
5
5
|
import { inferProjectFromTarget, loadInstructions, resolveInstructions } from './instructions.js';
|
|
6
6
|
import { buildReputationSummary, findAgentReputationSummary } from './reputation.js';
|
|
7
7
|
import { listRuntimeNotes } from './runtime.js';
|
|
8
|
-
import { loadState,
|
|
8
|
+
import { loadState, persistState } from './state.js';
|
|
9
9
|
export function buildCoordinationSnapshot(options = {}) {
|
|
10
10
|
const config = loadConfig(options.cwd);
|
|
11
11
|
const state = loadState(options.cwd);
|
|
@@ -58,7 +58,7 @@ export function buildCoordinationSnapshot(options = {}) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
if (changed)
|
|
61
|
-
|
|
61
|
+
persistState(state, options.cwd);
|
|
62
62
|
}
|
|
63
63
|
return {
|
|
64
64
|
project_id: config.project_id,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { resolveEntityDir } from './io.js';
|
|
2
|
+
import { resolveEntityDir, withStoreLock } from './io.js';
|
|
3
3
|
import { generateId } from './ids.js';
|
|
4
4
|
import { InstructionEntrySchema } from './schema.js';
|
|
5
5
|
import { JsonStore } from './json-store.js';
|
|
@@ -19,7 +19,9 @@ export function loadInstructions(cwd) {
|
|
|
19
19
|
return instructionStore(cwd).list();
|
|
20
20
|
}
|
|
21
21
|
export function saveInstruction(entry, cwd) {
|
|
22
|
-
|
|
22
|
+
withStoreLock(cwd, () => {
|
|
23
|
+
instructionStore(cwd, 'write').save(InstructionEntrySchema.parse(entry));
|
|
24
|
+
});
|
|
23
25
|
}
|
|
24
26
|
export function createInstruction(text, options, cwd) {
|
|
25
27
|
const entries = loadInstructions(cwd);
|
package/dist/core/io.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { withLock, cleanStaleLocks } from './lock.js';
|
|
4
4
|
export const MEMORY_DIR = '.brainclaw';
|
|
5
|
+
const STORE_LOCK_BASENAME = '.store-mutation';
|
|
5
6
|
const RETRYABLE_RENAME_ERROR_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
|
|
6
7
|
const DEFAULT_RENAME_RETRY_ATTEMPTS = 6;
|
|
7
8
|
const DEFAULT_RENAME_RETRY_DELAY_MS = 25;
|
|
@@ -72,6 +73,9 @@ export function memoryDir(cwd = process.cwd(), preferredDirName) {
|
|
|
72
73
|
export function memoryPath(filename, cwd, preferredDirName) {
|
|
73
74
|
return path.join(memoryDir(cwd, preferredDirName), filename);
|
|
74
75
|
}
|
|
76
|
+
export function storeLockPath(cwd, preferredDirName) {
|
|
77
|
+
return memoryPath(STORE_LOCK_BASENAME, cwd, preferredDirName);
|
|
78
|
+
}
|
|
75
79
|
export function memoryExists(cwd, preferredDirName) {
|
|
76
80
|
return fs.existsSync(memoryDir(cwd, preferredDirName));
|
|
77
81
|
}
|
|
@@ -95,6 +99,10 @@ export function ensureMemoryDir(cwd, preferredDirName) {
|
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
}
|
|
102
|
+
export function withStoreLock(cwd = process.cwd(), fn, preferredDirName) {
|
|
103
|
+
ensureMemoryDir(cwd, preferredDirName);
|
|
104
|
+
return withLock(storeLockPath(cwd, preferredDirName), fn);
|
|
105
|
+
}
|
|
98
106
|
/** Check if a path is a file, or a directory with at least one entry. */
|
|
99
107
|
function hasContent(p) {
|
|
100
108
|
try {
|
package/dist/core/lock.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
const DEFAULT_TIMEOUT_MS = 2000;
|
|
4
4
|
const LOCK_RETRY_INTERVAL_MS = 50;
|
|
5
5
|
const LOCK_EXPIRY_MS = 5000;
|
|
6
|
+
const heldLocks = new Map();
|
|
6
7
|
function lockFilePath(targetPath) {
|
|
7
8
|
return targetPath + '.lock';
|
|
8
9
|
}
|
|
@@ -59,22 +60,37 @@ function tryBreakLock(lockPath) {
|
|
|
59
60
|
}
|
|
60
61
|
export function acquireLock(targetPath, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
61
62
|
const lockPath = lockFilePath(targetPath);
|
|
63
|
+
const heldCount = heldLocks.get(lockPath);
|
|
64
|
+
if (heldCount) {
|
|
65
|
+
heldLocks.set(lockPath, heldCount + 1);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
62
68
|
const deadline = Date.now() + timeoutMs;
|
|
63
69
|
const dir = path.dirname(lockPath);
|
|
64
70
|
if (!fs.existsSync(dir)) {
|
|
65
71
|
fs.mkdirSync(dir, { recursive: true });
|
|
66
72
|
}
|
|
67
73
|
while (Date.now() < deadline) {
|
|
68
|
-
if (tryCreateLock(lockPath))
|
|
74
|
+
if (tryCreateLock(lockPath)) {
|
|
75
|
+
heldLocks.set(lockPath, 1);
|
|
69
76
|
return true;
|
|
70
|
-
|
|
77
|
+
}
|
|
78
|
+
if (tryBreakLock(lockPath)) {
|
|
79
|
+
heldLocks.set(lockPath, 1);
|
|
71
80
|
return true;
|
|
81
|
+
}
|
|
72
82
|
syncSleep(Math.min(LOCK_RETRY_INTERVAL_MS, deadline - Date.now()));
|
|
73
83
|
}
|
|
74
84
|
return false;
|
|
75
85
|
}
|
|
76
86
|
export function releaseLock(targetPath) {
|
|
77
87
|
const lockPath = lockFilePath(targetPath);
|
|
88
|
+
const heldCount = heldLocks.get(lockPath);
|
|
89
|
+
if (heldCount && heldCount > 1) {
|
|
90
|
+
heldLocks.set(lockPath, heldCount - 1);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
heldLocks.delete(lockPath);
|
|
78
94
|
try {
|
|
79
95
|
if (fs.existsSync(lockPath)) {
|
|
80
96
|
fs.unlinkSync(lockPath);
|
package/dist/core/migration.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import YAML from 'yaml';
|
|
4
4
|
import { memoryDir, memoryPath, readFileSync, writeFileAtomic, resolveEntityDir } from './io.js';
|
|
5
|
-
import { AgentIdentityDocumentSchema, BootstrapProfileDocumentSchema, CandidateSchema, ClaimSchema, ConfigSchema, MemorySeedDocumentSchema, ConstraintSchema, CurrentSessionStateSchema, DecisionSchema, HandoffSchema, InstructionEntrySchema, PlanItemSchema, ProjectIdentityDocumentSchema, RuntimeNoteSchema, SessionSnapshotSchema, TrapSchema, } from './schema.js';
|
|
5
|
+
import { BootstrapApplicationReceiptSchema, BootstrapImportPlanDocumentSchema, AgentIdentityDocumentSchema, BootstrapProfileDocumentSchema, CandidateSchema, ClaimSchema, ConfigSchema, MemorySeedDocumentSchema, ConstraintSchema, CurrentSessionStateSchema, DecisionSchema, HandoffSchema, InstructionEntrySchema, PlanItemSchema, ProjectIdentityDocumentSchema, RuntimeNoteSchema, SessionSnapshotSchema, TrapSchema, } from './schema.js';
|
|
6
6
|
export class MigrationError extends Error {
|
|
7
7
|
kind;
|
|
8
8
|
documentType;
|
|
@@ -16,6 +16,8 @@ export class MigrationError extends Error {
|
|
|
16
16
|
const CURRENT_SCHEMA_VERSION = 2;
|
|
17
17
|
const registry = {
|
|
18
18
|
agent_identity: createRegistryEntry(AgentIdentityDocumentSchema),
|
|
19
|
+
bootstrap_application: createRegistryEntry(BootstrapApplicationReceiptSchema),
|
|
20
|
+
bootstrap_import_plan: createRegistryEntry(BootstrapImportPlanDocumentSchema),
|
|
19
21
|
bootstrap_profile: createRegistryEntry(BootstrapProfileDocumentSchema),
|
|
20
22
|
candidate: createRegistryEntry(CandidateSchema),
|
|
21
23
|
claim: createRegistryEntry(ClaimSchema),
|
|
@@ -145,6 +147,8 @@ export function scanMigrationStatus(cwd) {
|
|
|
145
147
|
collectSingle(entries, memoryPath('project.identity.json', cwd), 'project_identity');
|
|
146
148
|
collectSingle(entries, memoryPath('.current-session', cwd), 'current_session');
|
|
147
149
|
collectSingle(entries, memoryPath(path.join('bootstrap', 'profile.json'), cwd), 'bootstrap_profile');
|
|
150
|
+
collectSingle(entries, memoryPath(path.join('bootstrap', 'import-plan.json'), cwd), 'bootstrap_import_plan');
|
|
151
|
+
collectSingle(entries, memoryPath(path.join('bootstrap', 'last-application.json'), cwd), 'bootstrap_application');
|
|
148
152
|
const effectiveCwd = cwd ?? process.cwd();
|
|
149
153
|
collectDirectory(entries, resolveEntityDir('constraints', effectiveCwd, 'read'), 'constraint');
|
|
150
154
|
collectDirectory(entries, resolveEntityDir('decisions', effectiveCwd, 'read'), 'decision');
|
|
@@ -161,7 +165,7 @@ export function scanMigrationStatus(cwd) {
|
|
|
161
165
|
collectDirectory(entries, resolveEntityDir('runtime-hosts', effectiveCwd, 'read'), 'runtime_note', true);
|
|
162
166
|
collectDirectory(entries, resolveEntityDir('runtime-private', effectiveCwd, 'read'), 'runtime_note', true);
|
|
163
167
|
collectDirectory(entries, resolveEntityDir('instructions', effectiveCwd, 'read'), 'instruction');
|
|
164
|
-
collectDirectory(entries, resolveEntityDir('bootstrap', effectiveCwd, 'read'), 'memory_seed');
|
|
168
|
+
collectDirectory(entries, path.join(resolveEntityDir('bootstrap', effectiveCwd, 'read'), 'seeds'), 'memory_seed');
|
|
165
169
|
collectDirectory(entries, resolveEntityDir('agents', effectiveCwd, 'read'), 'agent_identity');
|
|
166
170
|
collectDirectory(entries, resolveEntityDir('sessions', effectiveCwd, 'read'), 'session_snapshot');
|
|
167
171
|
return entries.sort((a, b) => a.path.localeCompare(b.path));
|
package/dist/core/runtime.js
CHANGED
|
@@ -2,7 +2,7 @@ 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 } from './io.js';
|
|
5
|
+
import { resolveEntityDir, withStoreLock } from './io.js';
|
|
6
6
|
import { loadVersionedJsonFile, saveVersionedJsonFile } from './migration.js';
|
|
7
7
|
import { RuntimeNoteSchema } from './schema.js';
|
|
8
8
|
import { commitMemoryChange } from './memory-git.js';
|
|
@@ -40,13 +40,15 @@ export function saveRuntimeNote(note, cwd) {
|
|
|
40
40
|
const persistedNote = visibility === 'shared'
|
|
41
41
|
? { ...note, visibility, host_id: hostId }
|
|
42
42
|
: { ...note, visibility, host_id: hostId };
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
withStoreLock(cwd, () => {
|
|
44
|
+
ensureRuntimeDir(note.agent, cwd, visibility, hostId);
|
|
45
|
+
const filepath = visibility === 'shared'
|
|
46
|
+
? path.join(sharedAgentDir(note.agent, cwd, 'write'), `${note.id}.json`)
|
|
47
|
+
: path.join(hostAgentDir(visibility, hostId, note.agent, cwd, 'write'), `${note.id}.json`);
|
|
48
|
+
saveVersionedJsonFile('runtime_note', filepath, RuntimeNoteSchema.parse(persistedNote));
|
|
49
|
+
appendEvent({ action: 'create', item_type: 'runtime_note', item_id: note.id, agent: note.agent, agent_id: note.agent_id }, cwd);
|
|
50
|
+
commitMemoryChange(`runtime note: ${note.note_type ?? 'note'} (${note.agent})`, cwd);
|
|
51
|
+
});
|
|
50
52
|
}
|
|
51
53
|
export function runtimeNotePath(note, cwd) {
|
|
52
54
|
const visibility = note.visibility ?? 'shared';
|
|
@@ -56,12 +58,14 @@ export function runtimeNotePath(note, cwd) {
|
|
|
56
58
|
: path.join(hostAgentDir(visibility, hostId, note.agent, cwd), `${note.id}.json`);
|
|
57
59
|
}
|
|
58
60
|
export function deleteRuntimeNote(note, cwd) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
return withStoreLock(cwd, () => {
|
|
62
|
+
const filepath = runtimeNotePath(note, cwd);
|
|
63
|
+
if (!fs.existsSync(filepath)) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
fs.unlinkSync(filepath);
|
|
67
|
+
return true;
|
|
68
|
+
});
|
|
65
69
|
}
|
|
66
70
|
function readAgentNotes(dir, agent) {
|
|
67
71
|
if (!fs.existsSync(dir))
|
package/dist/core/schema.js
CHANGED
|
@@ -451,6 +451,7 @@ export const MemorySeedKindSchema = z.enum([
|
|
|
451
451
|
export const MemorySeedSourceKindSchema = z.enum([
|
|
452
452
|
'readme',
|
|
453
453
|
'agents_md',
|
|
454
|
+
'native_instruction',
|
|
454
455
|
'manifest',
|
|
455
456
|
'repo_analysis',
|
|
456
457
|
'git',
|
|
@@ -483,6 +484,74 @@ export const BootstrapProfileDocumentSchema = z.object({
|
|
|
483
484
|
agents_md_present: z.boolean().default(false),
|
|
484
485
|
seed_count: z.number().int().nonnegative(),
|
|
485
486
|
target: z.string().optional(),
|
|
487
|
+
workspace_kind: z.enum(['empty', 'existing']).optional(),
|
|
488
|
+
confidence: MemorySeedConfidenceSchema.optional(),
|
|
489
|
+
native_instruction_files: z.array(z.string()).default([]),
|
|
490
|
+
gaps: z.array(z.string()).default([]),
|
|
491
|
+
});
|
|
492
|
+
export const BootstrapSuggestionTargetSchema = z.enum(['instruction', 'decision', 'constraint', 'trap']);
|
|
493
|
+
export const BootstrapSuggestionDocumentSchema = z.object({
|
|
494
|
+
schema_version: z.number().int().positive().optional(),
|
|
495
|
+
id: z.string(),
|
|
496
|
+
target: BootstrapSuggestionTargetSchema,
|
|
497
|
+
text: z.string(),
|
|
498
|
+
rationale: z.string(),
|
|
499
|
+
confidence: MemorySeedConfidenceSchema,
|
|
500
|
+
source_seed_ids: z.array(z.string()).default([]),
|
|
501
|
+
source_refs: z.array(z.string()).default([]),
|
|
502
|
+
layer: z.enum(['global', 'project', 'agent']).optional(),
|
|
503
|
+
scope: z.string().optional(),
|
|
504
|
+
tags: z.array(z.string()).default([]),
|
|
505
|
+
related_paths: z.array(z.string()).optional(),
|
|
506
|
+
reversible: z.boolean().default(true),
|
|
507
|
+
});
|
|
508
|
+
export const BootstrapInterviewAudienceSchema = z.enum(['cli', 'ide_chat', 'any']);
|
|
509
|
+
export const BootstrapInterviewQuestionSchema = z.object({
|
|
510
|
+
id: z.string(),
|
|
511
|
+
prompt: z.string(),
|
|
512
|
+
rationale: z.string(),
|
|
513
|
+
priority: z.enum(['high', 'medium', 'low']),
|
|
514
|
+
audience: BootstrapInterviewAudienceSchema.default('any'),
|
|
515
|
+
response_kind: z.enum(['short_text', 'long_text', 'boolean', 'list']).default('short_text'),
|
|
516
|
+
gap_keys: z.array(z.string()).default([]),
|
|
517
|
+
});
|
|
518
|
+
export const BootstrapInterviewPlanSchema = z.object({
|
|
519
|
+
schema_version: z.number().int().positive().optional(),
|
|
520
|
+
derived_at: z.string(),
|
|
521
|
+
workspace_kind: z.enum(['empty', 'existing']).optional(),
|
|
522
|
+
audience: BootstrapInterviewAudienceSchema.default('any'),
|
|
523
|
+
summary: z.string(),
|
|
524
|
+
question_count: z.number().int().nonnegative(),
|
|
525
|
+
questions: z.array(BootstrapInterviewQuestionSchema).default([]),
|
|
526
|
+
});
|
|
527
|
+
export const BootstrapImportPlanDocumentSchema = z.object({
|
|
528
|
+
schema_version: z.number().int().positive().optional(),
|
|
529
|
+
derived_at: z.string(),
|
|
530
|
+
target: z.string().optional(),
|
|
531
|
+
workspace_kind: z.enum(['empty', 'existing']).optional(),
|
|
532
|
+
confidence: MemorySeedConfidenceSchema.optional(),
|
|
533
|
+
summary: z.string(),
|
|
534
|
+
requires_confirmation: z.boolean().default(true),
|
|
535
|
+
gaps: z.array(z.string()).default([]),
|
|
536
|
+
suggestion_count: z.number().int().nonnegative(),
|
|
537
|
+
suggestions: z.array(BootstrapSuggestionDocumentSchema).default([]),
|
|
538
|
+
interview: BootstrapInterviewPlanSchema.optional(),
|
|
539
|
+
});
|
|
540
|
+
export const BootstrapManagedArtifactSchema = z.object({
|
|
541
|
+
kind: z.enum(['instruction', 'decision', 'constraint', 'trap']),
|
|
542
|
+
id: z.string(),
|
|
543
|
+
suggestion_id: z.string(),
|
|
544
|
+
rollback_action: z.enum(['deactivate', 'delete']).default('delete'),
|
|
545
|
+
});
|
|
546
|
+
export const BootstrapApplicationReceiptSchema = z.object({
|
|
547
|
+
schema_version: z.number().int().positive().optional(),
|
|
548
|
+
applied_at: z.string(),
|
|
549
|
+
proposal_derived_at: z.string(),
|
|
550
|
+
target: z.string().optional(),
|
|
551
|
+
workspace_kind: z.enum(['empty', 'existing']).optional(),
|
|
552
|
+
managed_artifacts: z.array(BootstrapManagedArtifactSchema).default([]),
|
|
553
|
+
suggestion_ids: z.array(z.string()).default([]),
|
|
554
|
+
uninstalled_at: z.string().optional(),
|
|
486
555
|
});
|
|
487
556
|
export const AgentIntegrationNameSchema = z.enum([
|
|
488
557
|
'github-copilot',
|
package/dist/core/state.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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, resolveEntityDir } from './io.js';
|
|
4
|
+
import { ensureMemoryDir, memoryPath, resolveEntityDir, withStoreLock, writeFileAtomic } from './io.js';
|
|
5
5
|
import { commitMemoryChange } from './memory-git.js';
|
|
6
6
|
import { appendEvent } from './event-log.js';
|
|
7
7
|
import { loadVersionedJsonFile, saveVersionedJsonFile } from './migration.js';
|
|
8
|
+
import { generateMarkdown } from './markdown.js';
|
|
8
9
|
export function emptyState() {
|
|
9
10
|
return {
|
|
10
11
|
version: 1,
|
|
@@ -69,15 +70,31 @@ function syncDirectory(dirPath, items, documentType) {
|
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
export function saveState(state, cwd) {
|
|
73
|
+
persistState(state, cwd, { writeProjectMarkdown: false });
|
|
74
|
+
}
|
|
75
|
+
function writeStateDirectories(state, cwd) {
|
|
72
76
|
ensureMemoryDir(cwd);
|
|
73
77
|
const effectiveCwd = cwd ?? process.cwd();
|
|
74
|
-
// Write to entity-aligned directories
|
|
75
78
|
syncDirectory(resolveEntityDir('constraints', effectiveCwd, 'write'), state.active_constraints, 'constraint');
|
|
76
79
|
syncDirectory(resolveEntityDir('decisions', effectiveCwd, 'write'), state.recent_decisions, 'decision');
|
|
77
80
|
syncDirectory(resolveEntityDir('traps', effectiveCwd, 'write'), state.known_traps, 'trap');
|
|
78
81
|
syncDirectory(resolveEntityDir('handoffs', effectiveCwd, 'write'), state.open_handoffs, 'handoff');
|
|
79
82
|
syncDirectory(resolveEntityDir('plans', effectiveCwd, 'write'), state.plan_items, 'plan');
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
}
|
|
84
|
+
export function persistState(state, cwd, options = {}) {
|
|
85
|
+
const effectiveCwd = cwd ?? process.cwd();
|
|
86
|
+
withStoreLock(effectiveCwd, () => {
|
|
87
|
+
writeStateDirectories(state, effectiveCwd);
|
|
88
|
+
if (options.writeProjectMarkdown ?? true) {
|
|
89
|
+
writeFileAtomic(memoryPath('project.md', effectiveCwd), generateMarkdown(state, effectiveCwd));
|
|
90
|
+
}
|
|
91
|
+
appendEvent({
|
|
92
|
+
action: options.eventAction ?? 'update',
|
|
93
|
+
item_type: 'state',
|
|
94
|
+
agent: 'system',
|
|
95
|
+
summary: options.eventSummary,
|
|
96
|
+
}, effectiveCwd);
|
|
97
|
+
commitMemoryChange(options.commitMessage ?? 'state update', effectiveCwd);
|
|
98
|
+
});
|
|
82
99
|
}
|
|
83
100
|
//# sourceMappingURL=state.js.map
|
package/docs/cli.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# CLI Reference
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The CLI is Brainclaw's explicit operator, scripting, release, and fallback surface. All commands are available as `brainclaw` or its alias `bclaw`.
|
|
4
|
+
|
|
5
|
+
For capable coding agents, prefer MCP for dynamic runtime state:
|
|
6
|
+
|
|
7
|
+
- `bclaw_bootstrap` instead of manual bootstrap polling
|
|
8
|
+
- `bclaw_get_context` instead of repeated raw CLI context calls
|
|
9
|
+
- `bclaw_list_plans` / `bclaw_list_claims` / `bclaw_get_agent_board` for live coordination views
|
|
10
|
+
- `bclaw_claim`, `bclaw_write_note`, and `bclaw_session_end` for session continuity
|
|
11
|
+
|
|
12
|
+
Use the CLI when a human operator is driving the workflow, when you are scripting setup or release operations, or when MCP is not the integration path.
|
|
4
13
|
|
|
5
14
|
---
|
|
6
15
|
|
|
@@ -97,15 +106,24 @@ brainclaw rebuild
|
|
|
97
106
|
|
|
98
107
|
Bootstrap shared memory for a new agent or project context.
|
|
99
108
|
|
|
109
|
+
For capable agents, the MCP equivalent is `bclaw_bootstrap`. Use the CLI when a human is driving onboarding, reviewing the import proposal, or operating in fallback mode.
|
|
110
|
+
|
|
100
111
|
| Option | Description |
|
|
101
112
|
|---|---|
|
|
102
113
|
| `--for <agent>` | Target agent name |
|
|
103
114
|
| `--json` | Output as JSON |
|
|
104
115
|
| `--refresh` | Force refresh even if bootstrap data is recent |
|
|
116
|
+
| `--interview` | Render adaptive interview prompts instead of the bootstrap summary |
|
|
117
|
+
| `--audience <audience>` | Filter interview prompts for `cli`, `ide_chat`, or `any` |
|
|
118
|
+
| `--apply` | Import the current bootstrap proposal into canonical memory |
|
|
119
|
+
| `--uninstall` | Deactivate the last bootstrap-managed import |
|
|
120
|
+
| `-y, --yes` | Skip confirmation prompts for apply/uninstall |
|
|
105
121
|
|
|
106
122
|
```bash
|
|
107
123
|
brainclaw bootstrap --for copilot
|
|
108
124
|
brainclaw bootstrap --for claude --refresh --json
|
|
125
|
+
brainclaw bootstrap --interview --audience cli
|
|
126
|
+
brainclaw bootstrap --apply -y
|
|
109
127
|
```
|
|
110
128
|
|
|
111
129
|
### `brainclaw env`
|
|
@@ -574,6 +592,8 @@ brainclaw search "rollout" --since 2026-01-01 --json
|
|
|
574
592
|
|
|
575
593
|
## Planning and Coordination
|
|
576
594
|
|
|
595
|
+
For capable agents, prefer the MCP plan and claim tools for live runtime interactions. The CLI remains the operator and scripting form of the same coordination model.
|
|
596
|
+
|
|
577
597
|
### `brainclaw plan create <text>`
|
|
578
598
|
|
|
579
599
|
Create a shared work item.
|
|
@@ -10,30 +10,43 @@ That means brainclaw integration should not rely on only one mechanism such as:
|
|
|
10
10
|
- a single skill
|
|
11
11
|
- a single startup command
|
|
12
12
|
|
|
13
|
-
## Better approach
|
|
14
|
-
|
|
15
|
-
Use multiple points of contact:
|
|
16
|
-
|
|
17
|
-
- lightweight system instructions
|
|
18
|
-
- project-specific context retrieval
|
|
19
|
-
- prompt-ready generated context
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
13
|
+
## Better approach
|
|
14
|
+
|
|
15
|
+
Use multiple points of contact:
|
|
16
|
+
|
|
17
|
+
- lightweight system instructions
|
|
18
|
+
- project-specific context retrieval through MCP when available
|
|
19
|
+
- prompt-ready generated context
|
|
20
|
+
- native agent files for local reminders
|
|
21
|
+
- MCP tools for dynamic state
|
|
22
|
+
- board and status views
|
|
23
|
+
- workflow reminders around plans, claims, and handoffs
|
|
24
|
+
|
|
25
|
+
## Surface hierarchy
|
|
26
|
+
|
|
27
|
+
The default order is:
|
|
28
|
+
|
|
29
|
+
1. MCP for live state
|
|
30
|
+
2. native agent files for local workflow guidance
|
|
31
|
+
3. readable files as fallback
|
|
32
|
+
4. CLI for setup, operator tasks, scripting, and fallback workflows
|
|
33
|
+
|
|
34
|
+
This keeps dynamic state dynamic instead of trying to encode it permanently into static instructions.
|
|
23
35
|
|
|
24
36
|
## System instructions vs project instructions
|
|
25
37
|
|
|
26
38
|
Keep these separate.
|
|
27
39
|
|
|
28
|
-
### System instructions
|
|
29
|
-
How the agent should use brainclaw.
|
|
40
|
+
### System instructions
|
|
41
|
+
How the agent should use brainclaw.
|
|
30
42
|
|
|
31
43
|
Examples:
|
|
32
44
|
|
|
33
|
-
- check workspace memory before significant changes
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
45
|
+
- check workspace memory before significant changes
|
|
46
|
+
- prefer MCP over manual CLI calls when MCP is available
|
|
47
|
+
- bootstrap if the workspace is not initialized and the workflow allows it
|
|
48
|
+
- respect file claims
|
|
49
|
+
- update shared plan status when appropriate
|
|
37
50
|
|
|
38
51
|
### Project instructions
|
|
39
52
|
What is true for the current workspace.
|
|
@@ -86,9 +99,9 @@ By default, `--write` treats generated workspace files as local setup and adds t
|
|
|
86
99
|
|
|
87
100
|
If a repo already contains tracked local agent files from an older setup, `brainclaw session-start` warns at the beginning of work and `brainclaw doctor --fix-agent-ignore` can repair the missing `.gitignore` entries. Tracked files still need to be untracked manually with Git after the ignore rules are in place.
|
|
88
101
|
|
|
89
|
-
## MCP server workflow
|
|
90
|
-
|
|
91
|
-
The brainclaw MCP server exposes
|
|
102
|
+
## MCP server workflow
|
|
103
|
+
|
|
104
|
+
The brainclaw MCP server exposes the dynamic Brainclaw workflow directly. For capable agents, this is the nominal runtime path.
|
|
92
105
|
|
|
93
106
|
### Starting the MCP server
|
|
94
107
|
|
|
@@ -116,19 +129,19 @@ Most agents pick this up via their MCP config file (`.mcp.json`, `~/.cursor/mcp.
|
|
|
116
129
|
| `bclaw_bootstrap` | Initialize the workspace if not already done |
|
|
117
130
|
| `bclaw_get_execution_context` | Get execution context (identity, claims, active plans) |
|
|
118
131
|
|
|
119
|
-
## Session lifecycle
|
|
120
|
-
|
|
121
|
-
### Starting a session
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
brainclaw session-start --agent my-agent --model claude-opus-4-5
|
|
132
|
+
## Session lifecycle
|
|
133
|
+
|
|
134
|
+
### Starting a session
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
brainclaw session-start --agent my-agent --model claude-opus-4-5
|
|
125
138
|
```
|
|
126
139
|
|
|
127
140
|
This registers the agent's identity and optionally records the model being used. Other agents can see active sessions in `list-claims` and `board`.
|
|
128
141
|
|
|
129
|
-
### During the session
|
|
130
|
-
|
|
131
|
-
|
|
142
|
+
### During the session
|
|
143
|
+
|
|
144
|
+
For MCP-capable agents, prefer the MCP equivalents of these actions. The CLI examples below are the operator and fallback form of the same lifecycle.
|
|
132
145
|
|
|
133
146
|
```bash
|
|
134
147
|
brainclaw context --json # load fresh project state
|
|
@@ -146,7 +159,7 @@ brainclaw session-end --auto-release
|
|
|
146
159
|
|
|
147
160
|
This releases all active claims held by the current agent and updates plan statuses.
|
|
148
161
|
|
|
149
|
-
## Generated files are local-only
|
|
162
|
+
## Generated files are local-only
|
|
150
163
|
|
|
151
164
|
Agent config files generated by brainclaw — `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `.cursor/rules/brainclaw.md`, `.windsurfrules`, `.github/copilot-instructions.md` — are **not committed to Git**.
|
|
152
165
|
|
|
@@ -12,12 +12,12 @@ brainclaw export --format claude-md --write
|
|
|
12
12
|
|
|
13
13
|
## Recommended approach
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
15
|
+
- use MCP as the default runtime path for dynamic retrieval and writes
|
|
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
|
|
18
18
|
- use hooks or workflow checks when a stronger reminder is needed
|
|
19
19
|
|
|
20
20
|
## Key idea
|
|
21
21
|
|
|
22
22
|
Claude Code should not carry all workspace state in static instructions.
|
|
23
|
-
brainclaw provides the living workspace layer.
|
|
23
|
+
brainclaw provides the living workspace layer through MCP plus local workflow guidance.
|
|
@@ -12,12 +12,12 @@ brainclaw export --format agents-md --write
|
|
|
12
12
|
|
|
13
13
|
## Recommended approach
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
- use
|
|
18
|
-
- encourage
|
|
15
|
+
- use MCP as the default runtime path for fresh context, plans, claims, and runtime notes
|
|
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
|
|
18
|
+
- encourage plans, claims, and handoffs during multi-step work
|
|
19
19
|
|
|
20
20
|
## Good role for brainclaw here
|
|
21
21
|
|
|
22
22
|
Codex stays the coding agent.
|
|
23
|
-
brainclaw provides the
|
|
23
|
+
brainclaw provides the live workspace context and coordination layer.
|
|
@@ -12,9 +12,10 @@ brainclaw export --format copilot-instructions --write
|
|
|
12
12
|
|
|
13
13
|
## Recommended approach
|
|
14
14
|
|
|
15
|
-
-
|
|
15
|
+
- use MCP whenever the Copilot surface supports it for fresh context and coordination views
|
|
16
|
+
- keep `.github/copilot-instructions.md` lightweight and behavioral
|
|
17
|
+
- use `.brainclaw/project.md` as readable fallback, not as the only live context source
|
|
16
18
|
- use plans, claims, and handoffs to reduce ambiguity across sessions
|
|
17
|
-
- use MCP where supported for dynamic collaboration views
|
|
18
19
|
|
|
19
20
|
## Why this matters
|
|
20
21
|
|
|
@@ -12,12 +12,12 @@ brainclaw export --format cursor-rules --write
|
|
|
12
12
|
|
|
13
13
|
## Recommended approach
|
|
14
14
|
|
|
15
|
-
- the
|
|
16
|
-
-
|
|
17
|
-
- use
|
|
15
|
+
- use MCP as the default dynamic path for context, board state, plans, and claims
|
|
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
|
|
18
18
|
- rely on claims and plans when multiple agents or humans are active in the same repo
|
|
19
19
|
|
|
20
20
|
## Key point
|
|
21
21
|
|
|
22
22
|
Cursor rules describe behavior.
|
|
23
|
-
brainclaw provides the living shared state those rules should point to.
|
|
23
|
+
brainclaw provides the living shared state those rules should point to through MCP.
|