brainclaw 0.22.0 → 0.22.1
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,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import YAML from 'yaml';
|
|
3
4
|
import { MEMORY_DIR } from './io.js';
|
|
4
5
|
const MULTI_PROJECT_MARKERS = [
|
|
5
6
|
'pnpm-workspace.yaml',
|
|
@@ -11,6 +12,32 @@ const MULTI_PROJECT_MARKERS = [
|
|
|
11
12
|
const MULTI_PROJECT_DIRS = ['apps', 'packages', 'services'];
|
|
12
13
|
export function analyzeRepository(cwd) {
|
|
13
14
|
const reasons = [];
|
|
15
|
+
// ── Signal 1: Existing brainclaw config already declares multi-project ──
|
|
16
|
+
const configPath = path.join(cwd, MEMORY_DIR, 'config.yaml');
|
|
17
|
+
if (fs.existsSync(configPath)) {
|
|
18
|
+
try {
|
|
19
|
+
const raw = YAML.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
20
|
+
if (raw) {
|
|
21
|
+
const mode = raw.project_mode;
|
|
22
|
+
const projects = raw.projects;
|
|
23
|
+
const strategy = projects?.strategy ?? 'manual';
|
|
24
|
+
const knownCount = Array.isArray(projects?.known) ? projects.known.length : 0;
|
|
25
|
+
if (mode === 'multi-project' || strategy === 'folder' || knownCount > 0) {
|
|
26
|
+
reasons.push(`Existing brainclaw config: project_mode=${mode ?? 'auto'}, strategy=${strategy}` +
|
|
27
|
+
(knownCount > 0 ? `, ${knownCount} known project(s)` : ''));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Config unreadable — fall through to heuristic detection.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// ── Signal 2: Child brainclaw stores (subdirectories with .brainclaw/) ──
|
|
36
|
+
const scan = scanChildStoresShallow(cwd);
|
|
37
|
+
if (scan.length > 0) {
|
|
38
|
+
reasons.push(`Found ${scan.length} child brainclaw store(s): ${scan.join(', ')}`);
|
|
39
|
+
}
|
|
40
|
+
// ── Signal 3: Classic monorepo / workspace markers ──
|
|
14
41
|
for (const marker of MULTI_PROJECT_MARKERS) {
|
|
15
42
|
if (fs.existsSync(path.join(cwd, marker))) {
|
|
16
43
|
reasons.push(`Found workspace marker: ${marker}`);
|
|
@@ -46,6 +73,33 @@ export function analyzeRepository(cwd) {
|
|
|
46
73
|
reasons: ['No monorepo or multi-project markers detected'],
|
|
47
74
|
};
|
|
48
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Quick depth-1 scan for subdirectories that contain a .brainclaw/ store.
|
|
78
|
+
* Returns relative paths of child stores found.
|
|
79
|
+
*/
|
|
80
|
+
function scanChildStoresShallow(cwd) {
|
|
81
|
+
const childStores = [];
|
|
82
|
+
let entries;
|
|
83
|
+
try {
|
|
84
|
+
entries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return childStores;
|
|
88
|
+
}
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
if (!entry.isDirectory())
|
|
91
|
+
continue;
|
|
92
|
+
if (SKIP_DIRS.has(entry.name))
|
|
93
|
+
continue;
|
|
94
|
+
if (entry.name.startsWith('.'))
|
|
95
|
+
continue;
|
|
96
|
+
const childBrainclaw = path.join(cwd, entry.name, MEMORY_DIR);
|
|
97
|
+
if (fs.existsSync(childBrainclaw)) {
|
|
98
|
+
childStores.push(entry.name);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return childStores;
|
|
102
|
+
}
|
|
49
103
|
/** Markers whose presence indicates a service/project boundary worth initialising. */
|
|
50
104
|
const SERVICE_MARKERS = [
|
|
51
105
|
'package.json',
|
|
@@ -104,6 +104,13 @@ export function resolveContextStoreCwd(cwd = process.cwd(), target) {
|
|
|
104
104
|
if (!absoluteTarget) {
|
|
105
105
|
return cwd;
|
|
106
106
|
}
|
|
107
|
+
// ── Fast path: walk from target upward to cwd looking for a child store ──
|
|
108
|
+
// This works regardless of project_mode or strategy configuration.
|
|
109
|
+
const childStore = findClosestStoreBelow(absoluteTarget, primary.cwd);
|
|
110
|
+
if (childStore) {
|
|
111
|
+
return childStore;
|
|
112
|
+
}
|
|
113
|
+
// ── Fallback: use workspace project discovery (folder mode, registry, etc.) ──
|
|
107
114
|
let config;
|
|
108
115
|
try {
|
|
109
116
|
config = loadConfig(primary.cwd);
|
|
@@ -127,6 +134,37 @@ export function resolveContextStoreCwd(cwd = process.cwd(), target) {
|
|
|
127
134
|
}
|
|
128
135
|
return cwd;
|
|
129
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Walk from `target` upward toward `ceiling` (exclusive), returning the first
|
|
139
|
+
* directory that contains a `.brainclaw/config.yaml`. Returns undefined when
|
|
140
|
+
* no child store is found between target and ceiling.
|
|
141
|
+
*
|
|
142
|
+
* This deliberately bypasses workspace project discovery so that child stores
|
|
143
|
+
* are resolved even when the parent config is set to auto/manual mode.
|
|
144
|
+
*/
|
|
145
|
+
function findClosestStoreBelow(target, ceiling) {
|
|
146
|
+
const resolvedCeiling = path.resolve(ceiling);
|
|
147
|
+
// If target is a file, start from its parent directory
|
|
148
|
+
let current;
|
|
149
|
+
try {
|
|
150
|
+
current = fs.statSync(target).isDirectory() ? path.resolve(target) : path.resolve(path.dirname(target));
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Target doesn't exist on disk — try its parent as a directory
|
|
154
|
+
current = path.resolve(path.dirname(target));
|
|
155
|
+
}
|
|
156
|
+
while (current !== resolvedCeiling) {
|
|
157
|
+
const configPath = path.join(current, MEMORY_DIR, 'config.yaml');
|
|
158
|
+
if (fs.existsSync(configPath)) {
|
|
159
|
+
return current;
|
|
160
|
+
}
|
|
161
|
+
const parent = path.dirname(current);
|
|
162
|
+
if (parent === current)
|
|
163
|
+
break; // filesystem root
|
|
164
|
+
current = parent;
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
130
168
|
/**
|
|
131
169
|
* Return true if `dir` is at or below `ancestor` in the filesystem hierarchy.
|
|
132
170
|
*/
|