brainclaw 0.19.2
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/LICENSE +74 -0
- package/README.md +226 -0
- package/dist/cli.js +1037 -0
- package/dist/commands/accept.js +149 -0
- package/dist/commands/adapter-openclaw-import.js +75 -0
- package/dist/commands/add-step.js +35 -0
- package/dist/commands/agent-board.js +106 -0
- package/dist/commands/audit.js +35 -0
- package/dist/commands/bootstrap.js +34 -0
- package/dist/commands/capability.js +104 -0
- package/dist/commands/changes.js +112 -0
- package/dist/commands/check-constraints.js +63 -0
- package/dist/commands/claim-resource.js +54 -0
- package/dist/commands/claim.js +92 -0
- package/dist/commands/complete-step.js +34 -0
- package/dist/commands/constraint.js +44 -0
- package/dist/commands/context-diff.js +32 -0
- package/dist/commands/context.js +63 -0
- package/dist/commands/decision.js +45 -0
- package/dist/commands/delete-plan.js +20 -0
- package/dist/commands/diff.js +99 -0
- package/dist/commands/doctor.js +1275 -0
- package/dist/commands/enable-agent.js +63 -0
- package/dist/commands/env.js +46 -0
- package/dist/commands/estimation-report.js +167 -0
- package/dist/commands/explore.js +47 -0
- package/dist/commands/export.js +381 -0
- package/dist/commands/handoff.js +63 -0
- package/dist/commands/history.js +22 -0
- package/dist/commands/hooks.js +123 -0
- package/dist/commands/init.js +356 -0
- package/dist/commands/install-hooks.js +115 -0
- package/dist/commands/instruction.js +56 -0
- package/dist/commands/list-agents.js +44 -0
- package/dist/commands/list-claims.js +45 -0
- package/dist/commands/list-instructions.js +50 -0
- package/dist/commands/list-plans.js +48 -0
- package/dist/commands/mcp-worker.js +12 -0
- package/dist/commands/mcp.js +2272 -0
- package/dist/commands/memory.js +283 -0
- package/dist/commands/metrics.js +175 -0
- package/dist/commands/plan-resource.js +62 -0
- package/dist/commands/plan.js +76 -0
- package/dist/commands/prune-candidates.js +36 -0
- package/dist/commands/prune.js +48 -0
- package/dist/commands/pull.js +25 -0
- package/dist/commands/push.js +28 -0
- package/dist/commands/rebuild.js +14 -0
- package/dist/commands/reflect-runtime-note.js +74 -0
- package/dist/commands/reflect.js +286 -0
- package/dist/commands/register-agent.js +29 -0
- package/dist/commands/reject.js +52 -0
- package/dist/commands/release-claim.js +41 -0
- package/dist/commands/release-claims.js +67 -0
- package/dist/commands/review.js +242 -0
- package/dist/commands/rollback.js +156 -0
- package/dist/commands/runtime-note.js +144 -0
- package/dist/commands/runtime-status.js +49 -0
- package/dist/commands/search.js +36 -0
- package/dist/commands/session-end.js +187 -0
- package/dist/commands/session-start.js +147 -0
- package/dist/commands/set-trust.js +92 -0
- package/dist/commands/setup.js +446 -0
- package/dist/commands/show-candidate.js +31 -0
- package/dist/commands/star-candidate.js +28 -0
- package/dist/commands/status.js +133 -0
- package/dist/commands/sync.js +159 -0
- package/dist/commands/tool.js +126 -0
- package/dist/commands/trap.js +74 -0
- package/dist/commands/update-handoff.js +23 -0
- package/dist/commands/update-plan.js +37 -0
- package/dist/commands/upgrade.js +382 -0
- package/dist/commands/use-candidate.js +35 -0
- package/dist/commands/version.js +96 -0
- package/dist/commands/watch.js +215 -0
- package/dist/commands/whoami.js +104 -0
- package/dist/core/agent-context.js +340 -0
- package/dist/core/agent-files.js +874 -0
- package/dist/core/agent-integrations.js +135 -0
- package/dist/core/agent-inventory.js +401 -0
- package/dist/core/agent-registry.js +420 -0
- package/dist/core/ai-agent-detection.js +140 -0
- package/dist/core/audit.js +85 -0
- package/dist/core/bootstrap.js +658 -0
- package/dist/core/brainclaw-version.js +433 -0
- package/dist/core/candidates.js +137 -0
- package/dist/core/circuit-breaker.js +118 -0
- package/dist/core/claims.js +72 -0
- package/dist/core/config.js +86 -0
- package/dist/core/context-diff.js +122 -0
- package/dist/core/context.js +1212 -0
- package/dist/core/contradictions.js +270 -0
- package/dist/core/coordination.js +86 -0
- package/dist/core/cross-project.js +99 -0
- package/dist/core/duplicates.js +72 -0
- package/dist/core/event-log.js +152 -0
- package/dist/core/events.js +56 -0
- package/dist/core/execution-context.js +204 -0
- package/dist/core/freshness.js +87 -0
- package/dist/core/global-registry.js +182 -0
- package/dist/core/host.js +10 -0
- package/dist/core/identity.js +151 -0
- package/dist/core/ids.js +56 -0
- package/dist/core/input-validation.js +81 -0
- package/dist/core/instructions.js +117 -0
- package/dist/core/io.js +191 -0
- package/dist/core/json-store.js +63 -0
- package/dist/core/lifecycle.js +45 -0
- package/dist/core/lock.js +129 -0
- package/dist/core/logger.js +49 -0
- package/dist/core/machine-profile.js +332 -0
- package/dist/core/markdown.js +120 -0
- package/dist/core/memory-git.js +133 -0
- package/dist/core/migration.js +247 -0
- package/dist/core/project-registry.js +64 -0
- package/dist/core/reflection-safety.js +21 -0
- package/dist/core/repo-analysis.js +133 -0
- package/dist/core/reputation.js +409 -0
- package/dist/core/runtime.js +134 -0
- package/dist/core/schema.js +580 -0
- package/dist/core/search.js +115 -0
- package/dist/core/security.js +66 -0
- package/dist/core/setup-state.js +50 -0
- package/dist/core/state.js +83 -0
- package/dist/core/store-resolution.js +119 -0
- package/dist/core/sync-remote.js +83 -0
- package/dist/core/traps.js +86 -0
- package/package.json +60 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureMemoryDir, memoryDir, memoryExists } from '../core/io.js';
|
|
4
|
+
import { loadState, saveState } from '../core/state.js';
|
|
5
|
+
import { scanMigrationStatus } from '../core/migration.js';
|
|
6
|
+
import { commitMemoryChange, initMemoryRepo } from '../core/memory-git.js';
|
|
7
|
+
import { BRAINCLAW_SECTION_END, BRAINCLAW_SECTION_START, buildBrainclawSection, buildClaudeCodeCommandText, ensureClaudeCodeCommand, hasBrainclawSection, } from '../core/agent-files.js';
|
|
8
|
+
import { loadConfig } from '../core/config.js';
|
|
9
|
+
import { renderAgentExportForAgent, writeAgentExportForAgent } from './export.js';
|
|
10
|
+
import { generateCursorHook, writeHook } from './hooks.js';
|
|
11
|
+
/**
|
|
12
|
+
* Entity directory layout mapping: legacy flat name → entity-aligned path.
|
|
13
|
+
* Must match ENTITY_DIR_MAP in io.ts.
|
|
14
|
+
*/
|
|
15
|
+
const ENTITY_DIRS = [
|
|
16
|
+
{ legacy: 'constraints', entity: 'memory/constraints' },
|
|
17
|
+
{ legacy: 'decisions', entity: 'memory/decisions' },
|
|
18
|
+
{ legacy: 'traps', entity: 'memory/traps' },
|
|
19
|
+
{ legacy: 'instructions', entity: 'memory/instructions' },
|
|
20
|
+
{ legacy: 'plans', entity: 'coordination/plans' },
|
|
21
|
+
{ legacy: 'claims', entity: 'coordination/claims' },
|
|
22
|
+
{ legacy: 'handoffs', entity: 'coordination/handoffs' },
|
|
23
|
+
{ legacy: 'sessions', entity: 'coordination/sessions' },
|
|
24
|
+
{ legacy: 'inbox', entity: 'coordination/inbox' },
|
|
25
|
+
{ legacy: 'runtime', entity: 'coordination/runtime' },
|
|
26
|
+
];
|
|
27
|
+
const WORKSPACE_EXPORT_REFRESH_AGENTS = [
|
|
28
|
+
{ agentName: 'claude-code', relativePath: 'CLAUDE.md' },
|
|
29
|
+
{ agentName: 'cursor', relativePath: '.cursor/rules/brainclaw.md' },
|
|
30
|
+
{ agentName: 'windsurf', relativePath: '.windsurfrules' },
|
|
31
|
+
{ agentName: 'cline', relativePath: '.clinerules/brainclaw.md' },
|
|
32
|
+
{ agentName: 'roo', relativePath: '.roo/rules/brainclaw.md' },
|
|
33
|
+
{ agentName: 'continue', relativePath: '.continue/rules/brainclaw.md' },
|
|
34
|
+
{ agentName: 'antigravity', relativePath: 'GEMINI.md' },
|
|
35
|
+
];
|
|
36
|
+
export function runUpgrade(options = {}) {
|
|
37
|
+
const cwd = options.cwd ?? process.cwd();
|
|
38
|
+
if (!memoryExists(cwd)) {
|
|
39
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const base = memoryDir(cwd);
|
|
43
|
+
const actions = [];
|
|
44
|
+
let movedFiles = 0;
|
|
45
|
+
// Phase 1: Ensure entity-aligned directories exist
|
|
46
|
+
ensureMemoryDir(cwd);
|
|
47
|
+
// Phase 2: Detect and plan file migrations (legacy → entity)
|
|
48
|
+
for (const { legacy, entity } of ENTITY_DIRS) {
|
|
49
|
+
const legacyDir = path.join(base, legacy);
|
|
50
|
+
const entityDir = path.join(base, entity);
|
|
51
|
+
if (!fs.existsSync(legacyDir))
|
|
52
|
+
continue;
|
|
53
|
+
// Recursively collect all JSON files (handles subdirs like runtime/agent/, inbox/accepted/)
|
|
54
|
+
const files = listJsonFilesRecursive(legacyDir);
|
|
55
|
+
if (files.length === 0)
|
|
56
|
+
continue;
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
// Preserve subdirectory structure: runtime/jberdah/rtn_xxx.json → coordination/runtime/jberdah/rtn_xxx.json
|
|
59
|
+
const relativeToLegacy = path.relative(legacyDir, file);
|
|
60
|
+
const target = path.join(entityDir, relativeToLegacy);
|
|
61
|
+
if (fs.existsSync(target)) {
|
|
62
|
+
// Entity dir already has this file — skip (entity takes precedence)
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
actions.push({
|
|
66
|
+
type: 'move_file',
|
|
67
|
+
from: path.relative(base, file),
|
|
68
|
+
to: path.relative(base, target),
|
|
69
|
+
description: `Move ${relativeToLegacy} from ${legacy}/ to ${entity}/`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Phase 3: Check schema migration status
|
|
74
|
+
const migrationStatus = scanMigrationStatus(cwd);
|
|
75
|
+
const outdated = migrationStatus.filter(e => e.status === 'outdated');
|
|
76
|
+
for (const entry of outdated) {
|
|
77
|
+
actions.push({
|
|
78
|
+
type: 'migrate_schema',
|
|
79
|
+
from: entry.path,
|
|
80
|
+
description: `Migrate ${entry.documentType} from v${entry.detectedVersion} to v${entry.currentVersion}`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const agentRefreshActions = scanManagedWorkspaceAgentFileRefreshes(cwd);
|
|
84
|
+
actions.push(...agentRefreshActions);
|
|
85
|
+
// Report
|
|
86
|
+
if (options.json) {
|
|
87
|
+
outputJson(actions, options.dryRun ?? false);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (actions.length === 0) {
|
|
91
|
+
console.log('✔ Project memory is up to date. No upgrade needed.');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
console.log(`Found ${actions.length} upgrade action(s):\n`);
|
|
95
|
+
for (const action of actions) {
|
|
96
|
+
const prefix = action.type === 'move_file' ? '→' : '↑';
|
|
97
|
+
console.log(` ${prefix} ${action.description}`);
|
|
98
|
+
}
|
|
99
|
+
if (options.dryRun) {
|
|
100
|
+
console.log('\n(dry run — no changes made)');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// Execute file moves
|
|
104
|
+
console.log('');
|
|
105
|
+
for (const action of actions) {
|
|
106
|
+
if (action.type === 'move_file' && action.from && action.to) {
|
|
107
|
+
const src = path.join(base, action.from);
|
|
108
|
+
const dst = path.join(base, action.to);
|
|
109
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
110
|
+
fs.renameSync(src, dst);
|
|
111
|
+
movedFiles++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Execute schema migrations by re-saving state (loadState auto-migrates via Zod parse)
|
|
115
|
+
if (outdated.length > 0) {
|
|
116
|
+
const state = loadState(cwd);
|
|
117
|
+
saveState(state, cwd);
|
|
118
|
+
}
|
|
119
|
+
const refreshedAgentFiles = refreshManagedWorkspaceAgentFiles(cwd);
|
|
120
|
+
// Clean up empty legacy directories (recursively removes empty subdirs first)
|
|
121
|
+
let removedDirs = 0;
|
|
122
|
+
for (const { legacy } of ENTITY_DIRS) {
|
|
123
|
+
const legacyDir = path.join(base, legacy);
|
|
124
|
+
if (fs.existsSync(legacyDir)) {
|
|
125
|
+
removedDirs += removeEmptyDirsRecursive(legacyDir);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Ensure memory git repo exists and commit the upgrade
|
|
129
|
+
initMemoryRepo(cwd);
|
|
130
|
+
commitMemoryChange(`upgrade: ${movedFiles} files moved, ${outdated.length} schemas migrated`, cwd);
|
|
131
|
+
const parts = [
|
|
132
|
+
`${movedFiles} file(s) moved`,
|
|
133
|
+
`${outdated.length} schema(s) migrated`,
|
|
134
|
+
`${refreshedAgentFiles.length} managed agent file(s) refreshed`,
|
|
135
|
+
];
|
|
136
|
+
if (removedDirs > 0)
|
|
137
|
+
parts.push(`${removedDirs} empty legacy dir(s) removed`);
|
|
138
|
+
console.log(`✔ Upgrade complete: ${parts.join(', ')}.`);
|
|
139
|
+
}
|
|
140
|
+
function scanManagedWorkspaceAgentFileRefreshes(cwd) {
|
|
141
|
+
const config = loadConfig(cwd);
|
|
142
|
+
const storageDir = config.storage_dir ?? '.brainclaw';
|
|
143
|
+
const actions = [];
|
|
144
|
+
const agentsPath = path.join(cwd, 'AGENTS.md');
|
|
145
|
+
const agentsMode = getManagedInstructionMode(agentsPath);
|
|
146
|
+
if (agentsMode === 'bootstrap' && needsBootstrapSectionRefresh(agentsPath, buildBrainclawSection(storageDir))) {
|
|
147
|
+
actions.push({
|
|
148
|
+
type: 'refresh_agent_file',
|
|
149
|
+
to: 'AGENTS.md',
|
|
150
|
+
description: 'Refresh managed Brainclaw section in AGENTS.md',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else if (agentsMode === 'export') {
|
|
154
|
+
const rendered = renderAgentExportForAgent('codex', cwd);
|
|
155
|
+
if (rendered && needsExportSectionRefresh(agentsPath, rendered.content)) {
|
|
156
|
+
actions.push({
|
|
157
|
+
type: 'refresh_agent_file',
|
|
158
|
+
to: 'AGENTS.md',
|
|
159
|
+
description: 'Refresh generated Brainclaw instructions in AGENTS.md',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const copilotPath = path.join(cwd, '.github', 'copilot-instructions.md');
|
|
164
|
+
const copilotMode = getManagedInstructionMode(copilotPath);
|
|
165
|
+
if (copilotMode === 'bootstrap' && needsBootstrapSectionRefresh(copilotPath, buildBrainclawSection(storageDir))) {
|
|
166
|
+
actions.push({
|
|
167
|
+
type: 'refresh_agent_file',
|
|
168
|
+
to: '.github/copilot-instructions.md',
|
|
169
|
+
description: 'Refresh managed Brainclaw section in .github/copilot-instructions.md',
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
else if (copilotMode === 'export') {
|
|
173
|
+
const rendered = renderAgentExportForAgent('github-copilot', cwd);
|
|
174
|
+
if (rendered && needsExportSectionRefresh(copilotPath, rendered.content)) {
|
|
175
|
+
actions.push({
|
|
176
|
+
type: 'refresh_agent_file',
|
|
177
|
+
to: '.github/copilot-instructions.md',
|
|
178
|
+
description: 'Refresh generated Brainclaw instructions in .github/copilot-instructions.md',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const target of WORKSPACE_EXPORT_REFRESH_AGENTS) {
|
|
183
|
+
const filePath = path.join(cwd, target.relativePath);
|
|
184
|
+
const rendered = renderAgentExportForAgent(target.agentName, cwd);
|
|
185
|
+
if (rendered && needsExportSectionRefresh(filePath, rendered.content)) {
|
|
186
|
+
actions.push({
|
|
187
|
+
type: 'refresh_agent_file',
|
|
188
|
+
to: target.relativePath,
|
|
189
|
+
description: `Refresh generated Brainclaw instructions in ${target.relativePath}`,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const claudeCommandPath = path.join(cwd, '.claude', 'commands', 'brainclaw.md');
|
|
194
|
+
if (fs.existsSync(claudeCommandPath) && fs.readFileSync(claudeCommandPath, 'utf-8') !== buildClaudeCodeCommandText()) {
|
|
195
|
+
actions.push({
|
|
196
|
+
type: 'refresh_agent_file',
|
|
197
|
+
to: '.claude/commands/brainclaw.md',
|
|
198
|
+
description: 'Refresh Claude Code Brainclaw command instructions',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const cursorHookPath = path.join(cwd, '.cursor', 'rules', 'brainclaw-session.mdc');
|
|
202
|
+
const expectedCursorHook = generateCursorHook(config.project_name);
|
|
203
|
+
if (fs.existsSync(cursorHookPath) && fs.readFileSync(cursorHookPath, 'utf-8') !== expectedCursorHook) {
|
|
204
|
+
actions.push({
|
|
205
|
+
type: 'refresh_agent_file',
|
|
206
|
+
to: '.cursor/rules/brainclaw-session.mdc',
|
|
207
|
+
description: 'Refresh Cursor Brainclaw session hook',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return actions;
|
|
211
|
+
}
|
|
212
|
+
function refreshManagedWorkspaceAgentFiles(cwd) {
|
|
213
|
+
const config = loadConfig(cwd);
|
|
214
|
+
const storageDir = config.storage_dir ?? '.brainclaw';
|
|
215
|
+
const refreshed = new Set();
|
|
216
|
+
const agentsPath = path.join(cwd, 'AGENTS.md');
|
|
217
|
+
const agentsMode = getManagedInstructionMode(agentsPath);
|
|
218
|
+
if (agentsMode === 'bootstrap') {
|
|
219
|
+
if (writeBootstrapSectionFile(agentsPath, buildBrainclawSection(storageDir))) {
|
|
220
|
+
refreshed.add('AGENTS.md');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else if (agentsMode === 'export') {
|
|
224
|
+
const result = writeAgentExportForAgent('codex', cwd);
|
|
225
|
+
if (result && (result.created || result.updated)) {
|
|
226
|
+
refreshed.add(result.relativePath);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const copilotPath = path.join(cwd, '.github', 'copilot-instructions.md');
|
|
230
|
+
const copilotMode = getManagedInstructionMode(copilotPath);
|
|
231
|
+
if (copilotMode === 'bootstrap') {
|
|
232
|
+
if (writeBootstrapSectionFile(copilotPath, buildBrainclawSection(storageDir))) {
|
|
233
|
+
refreshed.add('.github/copilot-instructions.md');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (copilotMode === 'export') {
|
|
237
|
+
const result = writeAgentExportForAgent('github-copilot', cwd);
|
|
238
|
+
if (result && (result.created || result.updated)) {
|
|
239
|
+
refreshed.add(result.relativePath);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
for (const target of WORKSPACE_EXPORT_REFRESH_AGENTS) {
|
|
243
|
+
const filePath = path.join(cwd, target.relativePath);
|
|
244
|
+
if (!fs.existsSync(filePath) || !hasBrainclawSection(fs.readFileSync(filePath, 'utf-8'))) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const result = writeAgentExportForAgent(target.agentName, cwd);
|
|
248
|
+
if (result && (result.created || result.updated)) {
|
|
249
|
+
refreshed.add(result.relativePath);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (fs.existsSync(path.join(cwd, '.claude', 'commands', 'brainclaw.md'))) {
|
|
253
|
+
const result = ensureClaudeCodeCommand(cwd);
|
|
254
|
+
if (result.created || result.updated) {
|
|
255
|
+
refreshed.add(result.relativePath ?? '.claude/commands/brainclaw.md');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (fs.existsSync(path.join(cwd, '.cursor', 'rules', 'brainclaw-session.mdc'))) {
|
|
259
|
+
const expected = generateCursorHook(config.project_name);
|
|
260
|
+
const filePath = path.join(cwd, '.cursor', 'rules', 'brainclaw-session.mdc');
|
|
261
|
+
if (fs.readFileSync(filePath, 'utf-8') !== expected) {
|
|
262
|
+
const result = writeHook(expected, '.cursor/rules/brainclaw-session.mdc', cwd);
|
|
263
|
+
refreshed.add(result.relativePath);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return [...refreshed];
|
|
267
|
+
}
|
|
268
|
+
function getManagedInstructionMode(filePath) {
|
|
269
|
+
if (!fs.existsSync(filePath)) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
const existing = fs.readFileSync(filePath, 'utf-8');
|
|
273
|
+
if (!hasBrainclawSection(existing)) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
if (existing.includes('## Brainclaw — shared project memory')) {
|
|
277
|
+
return 'bootstrap';
|
|
278
|
+
}
|
|
279
|
+
return 'export';
|
|
280
|
+
}
|
|
281
|
+
function writeBootstrapSectionFile(filePath, section) {
|
|
282
|
+
if (!fs.existsSync(filePath)) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
const existing = fs.readFileSync(filePath, 'utf-8');
|
|
286
|
+
if (!hasBrainclawSection(existing)) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
const next = upsertSection(existing, section);
|
|
290
|
+
if (next === existing) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
fs.writeFileSync(filePath, next, 'utf-8');
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
function needsBootstrapSectionRefresh(filePath, section) {
|
|
297
|
+
if (!fs.existsSync(filePath)) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
const existing = fs.readFileSync(filePath, 'utf-8');
|
|
301
|
+
if (!hasBrainclawSection(existing)) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
return existing !== upsertSection(existing, section);
|
|
305
|
+
}
|
|
306
|
+
function needsExportSectionRefresh(filePath, content) {
|
|
307
|
+
if (!fs.existsSync(filePath)) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
const existing = fs.readFileSync(filePath, 'utf-8');
|
|
311
|
+
if (!hasBrainclawSection(existing)) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
const section = `${BRAINCLAW_SECTION_START}\n${content}\n${BRAINCLAW_SECTION_END}`;
|
|
315
|
+
return existing !== upsertSection(existing, section);
|
|
316
|
+
}
|
|
317
|
+
function upsertSection(existingContent, section) {
|
|
318
|
+
const start = existingContent.indexOf(BRAINCLAW_SECTION_START);
|
|
319
|
+
const end = existingContent.indexOf(BRAINCLAW_SECTION_END);
|
|
320
|
+
if (start !== -1 && end !== -1) {
|
|
321
|
+
const before = existingContent.slice(0, start);
|
|
322
|
+
const after = existingContent.slice(end + BRAINCLAW_SECTION_END.length);
|
|
323
|
+
return before + section + after;
|
|
324
|
+
}
|
|
325
|
+
const trimmed = existingContent.trimEnd();
|
|
326
|
+
return trimmed.length > 0 ? `${trimmed}\n\n${section}\n` : `${section}\n`;
|
|
327
|
+
}
|
|
328
|
+
function listJsonFiles(dir) {
|
|
329
|
+
if (!fs.existsSync(dir))
|
|
330
|
+
return [];
|
|
331
|
+
return fs.readdirSync(dir)
|
|
332
|
+
.filter(f => f.endsWith('.json'))
|
|
333
|
+
.map(f => path.join(dir, f));
|
|
334
|
+
}
|
|
335
|
+
function listJsonFilesRecursive(dir) {
|
|
336
|
+
if (!fs.existsSync(dir))
|
|
337
|
+
return [];
|
|
338
|
+
const results = [];
|
|
339
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
340
|
+
const full = path.join(dir, entry.name);
|
|
341
|
+
if (entry.isDirectory()) {
|
|
342
|
+
results.push(...listJsonFilesRecursive(full));
|
|
343
|
+
}
|
|
344
|
+
else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
345
|
+
results.push(full);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return results;
|
|
349
|
+
}
|
|
350
|
+
function isEmptyDir(dir) {
|
|
351
|
+
try {
|
|
352
|
+
return fs.readdirSync(dir).length === 0;
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/** Recursively remove empty directories bottom-up. Returns count of dirs removed. */
|
|
359
|
+
function removeEmptyDirsRecursive(dir) {
|
|
360
|
+
if (!fs.existsSync(dir))
|
|
361
|
+
return 0;
|
|
362
|
+
let removed = 0;
|
|
363
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
364
|
+
if (entry.isDirectory()) {
|
|
365
|
+
removed += removeEmptyDirsRecursive(path.join(dir, entry.name));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (isEmptyDir(dir)) {
|
|
369
|
+
fs.rmdirSync(dir);
|
|
370
|
+
removed++;
|
|
371
|
+
}
|
|
372
|
+
return removed;
|
|
373
|
+
}
|
|
374
|
+
function outputJson(actions, dryRun) {
|
|
375
|
+
console.log(JSON.stringify({
|
|
376
|
+
upgrade_needed: actions.length > 0,
|
|
377
|
+
dry_run: dryRun,
|
|
378
|
+
actions_count: actions.length,
|
|
379
|
+
actions,
|
|
380
|
+
}, null, 2));
|
|
381
|
+
}
|
|
382
|
+
//# sourceMappingURL=upgrade.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { memoryExists } from '../core/io.js';
|
|
2
|
+
import { addCandidateUse } from '../core/candidates.js';
|
|
3
|
+
import { loadConfig } from '../core/config.js';
|
|
4
|
+
export function runUseCandidate(id, options = {}) {
|
|
5
|
+
if (!memoryExists()) {
|
|
6
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
const actor = options.by ?? process.env.USER ?? process.env.USERNAME ?? 'unknown';
|
|
10
|
+
const context = options.context;
|
|
11
|
+
if (!context) {
|
|
12
|
+
console.error('Error: --context is required.');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const { candidate, added } = addCandidateUse(id, actor, context);
|
|
17
|
+
const config = loadConfig().reflective_memory;
|
|
18
|
+
const starThreshold = config?.promotion_stars_threshold ?? 3;
|
|
19
|
+
const usesThreshold = config?.promotion_uses_threshold ?? 2;
|
|
20
|
+
const recommendation = (candidate.star_count >= starThreshold || candidate.usage_count >= usesThreshold)
|
|
21
|
+
? ' Promotion is now recommended.'
|
|
22
|
+
: '';
|
|
23
|
+
if (!added) {
|
|
24
|
+
console.log(`✔ Candidate [${id}] was already marked used by ${actor} in '${context}' (${candidate.usage_count} use(s))`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
console.log(`✔ Candidate [${id}] used by ${actor} in '${context}' (${candidate.usage_count}/${usesThreshold} uses)${recommendation}`);
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
31
|
+
console.error(`Error: ${msg}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=use-candidate.js.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { loadConfig, saveConfig } from '../core/config.js';
|
|
4
|
+
import { memoryExists } from '../core/io.js';
|
|
5
|
+
import { assessBrainclawVersion, checkBrainclawInstallableUpdate, DEFAULT_LOCAL_RELEASE_MANIFEST_PATH, publishLocalBrainclawRelease, } from '../core/brainclaw-version.js';
|
|
6
|
+
export function runVersion(options = {}) {
|
|
7
|
+
const cwd = options.cwd ?? process.cwd();
|
|
8
|
+
const initialized = memoryExists(cwd);
|
|
9
|
+
let config = initialized ? loadConfig(cwd) : undefined;
|
|
10
|
+
let publishedLocalRelease;
|
|
11
|
+
if (options.publishLocal) {
|
|
12
|
+
if (!initialized || !config) {
|
|
13
|
+
console.error('Error: local release publishing requires an initialized Brainclaw project.');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
publishedLocalRelease = publishLocalBrainclawRelease(cwd, {
|
|
18
|
+
releaseNotes: options.releaseNotes,
|
|
19
|
+
});
|
|
20
|
+
config.brainclaw_update_source = {
|
|
21
|
+
type: 'local-pack',
|
|
22
|
+
manifest_path: DEFAULT_LOCAL_RELEASE_MANIFEST_PATH,
|
|
23
|
+
};
|
|
24
|
+
saveConfig(config, cwd);
|
|
25
|
+
ensureLocalReleasesGitignore(cwd);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
console.error(`Error: failed to publish local release channel: ${message}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const assessment = assessBrainclawVersion(config);
|
|
34
|
+
const updateCheck = options.check ? checkBrainclawInstallableUpdate(config, cwd) : undefined;
|
|
35
|
+
const result = {
|
|
36
|
+
initialized,
|
|
37
|
+
...assessment,
|
|
38
|
+
...(publishedLocalRelease ? { published_local_release: publishedLocalRelease } : {}),
|
|
39
|
+
...(updateCheck ? { installable_update: updateCheck } : {}),
|
|
40
|
+
};
|
|
41
|
+
if (options.json) {
|
|
42
|
+
console.log(JSON.stringify(result, null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log(`brainclaw ${assessment.cli_version}`);
|
|
46
|
+
if (!initialized) {
|
|
47
|
+
console.log('Project memory: not initialized');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (publishedLocalRelease) {
|
|
51
|
+
console.log(`Local release published: ${publishedLocalRelease.workspace_version}`);
|
|
52
|
+
console.log(`Manifest: ${publishedLocalRelease.manifest_path}`);
|
|
53
|
+
console.log(`Artifact: ${publishedLocalRelease.artifact_path}`);
|
|
54
|
+
console.log(`Install command: ${publishedLocalRelease.install_command}`);
|
|
55
|
+
}
|
|
56
|
+
if (assessment.minimum_brainclaw_version) {
|
|
57
|
+
console.log(`Minimum required: ${assessment.minimum_brainclaw_version}`);
|
|
58
|
+
}
|
|
59
|
+
if (assessment.recommended_brainclaw_version) {
|
|
60
|
+
console.log(`Recommended: ${assessment.recommended_brainclaw_version}`);
|
|
61
|
+
}
|
|
62
|
+
console.log(assessment.message);
|
|
63
|
+
if (assessment.upgrade_message) {
|
|
64
|
+
console.log(`Upgrade benefits: ${assessment.upgrade_message}`);
|
|
65
|
+
}
|
|
66
|
+
if (assessment.upgrade_command) {
|
|
67
|
+
console.log(`Upgrade command: ${assessment.upgrade_command}`);
|
|
68
|
+
}
|
|
69
|
+
if (!updateCheck) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log(updateCheck.message);
|
|
73
|
+
if (updateCheck.latest_installable_version) {
|
|
74
|
+
console.log(`Latest installable: ${updateCheck.latest_installable_version}`);
|
|
75
|
+
}
|
|
76
|
+
if (updateCheck.release_notes) {
|
|
77
|
+
console.log(`Release notes: ${updateCheck.release_notes}`);
|
|
78
|
+
}
|
|
79
|
+
if (updateCheck.install_command) {
|
|
80
|
+
console.log(`Install command: ${updateCheck.install_command}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function ensureLocalReleasesGitignore(cwd) {
|
|
84
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
85
|
+
const current = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
|
|
86
|
+
const ignoreLine = '.releases/';
|
|
87
|
+
const lines = new Set(current.split(/\r?\n/).map((line) => line.trim()));
|
|
88
|
+
if (lines.has(ignoreLine)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const trimmed = current.trimEnd();
|
|
92
|
+
const separator = trimmed.length > 0 ? '\n' : '';
|
|
93
|
+
const next = `${trimmed}${separator}\n# Local installable Brainclaw releases\n${ignoreLine}\n`;
|
|
94
|
+
fs.writeFileSync(gitignorePath, next, 'utf-8');
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=version.js.map
|