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,247 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import YAML from 'yaml';
|
|
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';
|
|
6
|
+
export class MigrationError extends Error {
|
|
7
|
+
kind;
|
|
8
|
+
documentType;
|
|
9
|
+
constructor(kind, message, documentType) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.kind = kind;
|
|
12
|
+
this.documentType = documentType;
|
|
13
|
+
this.name = 'MigrationError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const CURRENT_SCHEMA_VERSION = 2;
|
|
17
|
+
const registry = {
|
|
18
|
+
agent_identity: createRegistryEntry(AgentIdentityDocumentSchema),
|
|
19
|
+
bootstrap_profile: createRegistryEntry(BootstrapProfileDocumentSchema),
|
|
20
|
+
candidate: createRegistryEntry(CandidateSchema),
|
|
21
|
+
claim: createRegistryEntry(ClaimSchema),
|
|
22
|
+
config: createRegistryEntry(ConfigSchema),
|
|
23
|
+
constraint: createRegistryEntry(ConstraintSchema),
|
|
24
|
+
current_session: createRegistryEntry(CurrentSessionStateSchema),
|
|
25
|
+
decision: createRegistryEntry(DecisionSchema),
|
|
26
|
+
handoff: createRegistryEntry(HandoffSchema),
|
|
27
|
+
instruction: createRegistryEntry(InstructionEntrySchema),
|
|
28
|
+
memory_seed: createRegistryEntry(MemorySeedDocumentSchema),
|
|
29
|
+
plan: createRegistryEntry(PlanItemSchema),
|
|
30
|
+
project_identity: createRegistryEntry(ProjectIdentityDocumentSchema),
|
|
31
|
+
runtime_note: createRegistryEntry(RuntimeNoteSchema),
|
|
32
|
+
session_snapshot: createRegistryEntry(SessionSnapshotSchema),
|
|
33
|
+
trap: createRegistryEntry(TrapSchema),
|
|
34
|
+
};
|
|
35
|
+
function createRegistryEntry(schema) {
|
|
36
|
+
return {
|
|
37
|
+
currentVersion: CURRENT_SCHEMA_VERSION,
|
|
38
|
+
schema,
|
|
39
|
+
detectVersion: detectDocumentVersion,
|
|
40
|
+
migrate(raw, fromVersion, toVersion) {
|
|
41
|
+
let current = raw;
|
|
42
|
+
for (let version = fromVersion; version < toVersion; version += 1) {
|
|
43
|
+
if (version === 1) {
|
|
44
|
+
current = addSchemaVersion(current, version + 1);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`No migration path from v${version} to v${version + 1}`);
|
|
48
|
+
}
|
|
49
|
+
return current;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function currentDocumentVersion(documentType) {
|
|
54
|
+
return registry[documentType].currentVersion;
|
|
55
|
+
}
|
|
56
|
+
export function detectDocumentVersion(raw) {
|
|
57
|
+
if (!isRecord(raw)) {
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
if (typeof raw.schema_version === 'number' && Number.isFinite(raw.schema_version)) {
|
|
61
|
+
return raw.schema_version;
|
|
62
|
+
}
|
|
63
|
+
if (typeof raw.version === 'number' && Number.isFinite(raw.version)) {
|
|
64
|
+
return raw.version;
|
|
65
|
+
}
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
export function migrateVersionedDocument(documentType, raw) {
|
|
69
|
+
const entry = registry[documentType];
|
|
70
|
+
const detectedVersion = entry.detectVersion(raw);
|
|
71
|
+
if (detectedVersion > entry.currentVersion) {
|
|
72
|
+
throw new MigrationError('unknown_version', `${documentType} uses unsupported schema version ${detectedVersion} (current ${entry.currentVersion})`, documentType);
|
|
73
|
+
}
|
|
74
|
+
let migratedRaw = raw;
|
|
75
|
+
if (detectedVersion < entry.currentVersion) {
|
|
76
|
+
try {
|
|
77
|
+
migratedRaw = entry.migrate(raw, detectedVersion, entry.currentVersion);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
throw new MigrationError('migration_failed', error instanceof Error ? error.message : String(error), documentType);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
return {
|
|
85
|
+
document: entry.schema.parse(migratedRaw),
|
|
86
|
+
metadata: {
|
|
87
|
+
detectedVersion,
|
|
88
|
+
currentVersion: entry.currentVersion,
|
|
89
|
+
migrated: detectedVersion < entry.currentVersion,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
throw new MigrationError('validation_failed', error instanceof Error ? error.message : String(error), documentType);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function parseVersionedJsonDocumentFromString(documentType, rawText) {
|
|
98
|
+
try {
|
|
99
|
+
return migrateVersionedDocument(documentType, JSON.parse(rawText));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof MigrationError) {
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
throw new MigrationError('parse', error instanceof Error ? error.message : String(error), documentType);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function parseVersionedYamlDocumentFromString(documentType, rawText) {
|
|
109
|
+
try {
|
|
110
|
+
return migrateVersionedDocument(documentType, YAML.parse(rawText));
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error instanceof MigrationError) {
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
throw new MigrationError('parse', error instanceof Error ? error.message : String(error), documentType);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function loadVersionedJsonFile(documentType, filepath) {
|
|
120
|
+
return parseVersionedJsonDocumentFromString(documentType, readFileSync(filepath));
|
|
121
|
+
}
|
|
122
|
+
export function loadVersionedYamlFile(documentType, filepath) {
|
|
123
|
+
return parseVersionedYamlDocumentFromString(documentType, readFileSync(filepath));
|
|
124
|
+
}
|
|
125
|
+
export function preparePersistedDocument(documentType, document) {
|
|
126
|
+
return {
|
|
127
|
+
...(isRecord(document) ? document : {}),
|
|
128
|
+
schema_version: currentDocumentVersion(documentType),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function saveVersionedJsonFile(documentType, filepath, document) {
|
|
132
|
+
writeFileAtomic(filepath, `${JSON.stringify(preparePersistedDocument(documentType, document), null, 2)}\n`);
|
|
133
|
+
}
|
|
134
|
+
export function saveVersionedYamlFile(documentType, filepath, document) {
|
|
135
|
+
const yaml = YAML.stringify(preparePersistedDocument(documentType, document), { lineWidth: 0 });
|
|
136
|
+
writeFileAtomic(filepath, yaml);
|
|
137
|
+
}
|
|
138
|
+
export function scanMigrationStatus(cwd) {
|
|
139
|
+
const baseDir = memoryDir(cwd);
|
|
140
|
+
if (!fs.existsSync(baseDir)) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
const entries = [];
|
|
144
|
+
collectSingle(entries, memoryPath('config.yaml', cwd), 'config', 'yaml');
|
|
145
|
+
collectSingle(entries, memoryPath('project.identity.json', cwd), 'project_identity');
|
|
146
|
+
collectSingle(entries, memoryPath('.current-session', cwd), 'current_session');
|
|
147
|
+
collectSingle(entries, memoryPath(path.join('bootstrap', 'profile.json'), cwd), 'bootstrap_profile');
|
|
148
|
+
const effectiveCwd = cwd ?? process.cwd();
|
|
149
|
+
collectDirectory(entries, resolveEntityDir('constraints', effectiveCwd, 'read'), 'constraint');
|
|
150
|
+
collectDirectory(entries, resolveEntityDir('decisions', effectiveCwd, 'read'), 'decision');
|
|
151
|
+
collectDirectory(entries, resolveEntityDir('traps', effectiveCwd, 'read'), 'trap');
|
|
152
|
+
collectDirectory(entries, resolveEntityDir('traps-hosts', effectiveCwd, 'read'), 'trap', true);
|
|
153
|
+
collectDirectory(entries, resolveEntityDir('traps-private', effectiveCwd, 'read'), 'trap', true);
|
|
154
|
+
collectDirectory(entries, resolveEntityDir('handoffs', effectiveCwd, 'read'), 'handoff');
|
|
155
|
+
collectDirectory(entries, resolveEntityDir('plans', effectiveCwd, 'read'), 'plan');
|
|
156
|
+
collectDirectory(entries, resolveEntityDir('inbox', effectiveCwd, 'read'), 'candidate');
|
|
157
|
+
collectDirectory(entries, resolveEntityDir('inbox/accepted', effectiveCwd, 'read'), 'candidate');
|
|
158
|
+
collectDirectory(entries, resolveEntityDir('inbox/rejected', effectiveCwd, 'read'), 'candidate');
|
|
159
|
+
collectDirectory(entries, resolveEntityDir('claims', effectiveCwd, 'read'), 'claim');
|
|
160
|
+
collectDirectory(entries, resolveEntityDir('runtime', effectiveCwd, 'read'), 'runtime_note', true);
|
|
161
|
+
collectDirectory(entries, resolveEntityDir('runtime-hosts', effectiveCwd, 'read'), 'runtime_note', true);
|
|
162
|
+
collectDirectory(entries, resolveEntityDir('runtime-private', effectiveCwd, 'read'), 'runtime_note', true);
|
|
163
|
+
collectDirectory(entries, resolveEntityDir('instructions', effectiveCwd, 'read'), 'instruction');
|
|
164
|
+
collectDirectory(entries, resolveEntityDir('bootstrap', effectiveCwd, 'read'), 'memory_seed');
|
|
165
|
+
collectDirectory(entries, resolveEntityDir('agents', effectiveCwd, 'read'), 'agent_identity');
|
|
166
|
+
collectDirectory(entries, resolveEntityDir('sessions', effectiveCwd, 'read'), 'session_snapshot');
|
|
167
|
+
return entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
168
|
+
}
|
|
169
|
+
function collectSingle(entries, filepath, documentType, format = 'json') {
|
|
170
|
+
if (!fs.existsSync(filepath)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
entries.push(buildCheckEntry(filepath, documentType, format));
|
|
174
|
+
}
|
|
175
|
+
function collectDirectory(entries, dirPath, documentType, recursive = false) {
|
|
176
|
+
if (!fs.existsSync(dirPath)) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
for (const filepath of listJsonFiles(dirPath, recursive)) {
|
|
180
|
+
entries.push(buildCheckEntry(filepath, documentType));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function listJsonFiles(dirPath, recursive) {
|
|
184
|
+
if (!fs.existsSync(dirPath)) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
const files = [];
|
|
188
|
+
for (const entry of fs.readdirSync(dirPath).sort()) {
|
|
189
|
+
const fullPath = path.join(dirPath, entry);
|
|
190
|
+
const stat = fs.statSync(fullPath);
|
|
191
|
+
if (stat.isDirectory()) {
|
|
192
|
+
if (recursive) {
|
|
193
|
+
files.push(...listJsonFiles(fullPath, true));
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (entry.endsWith('.json')) {
|
|
198
|
+
files.push(fullPath);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return files;
|
|
202
|
+
}
|
|
203
|
+
function buildCheckEntry(filepath, documentType, format = 'json') {
|
|
204
|
+
const relativePath = path.relative(process.cwd(), filepath).replace(/\\/g, '/');
|
|
205
|
+
const currentVersion = currentDocumentVersion(documentType);
|
|
206
|
+
try {
|
|
207
|
+
const rawText = readFileSync(filepath);
|
|
208
|
+
const rawValue = format === 'yaml' ? YAML.parse(rawText) : JSON.parse(rawText);
|
|
209
|
+
const detectedVersion = detectDocumentVersion(rawValue);
|
|
210
|
+
if (detectedVersion > currentVersion) {
|
|
211
|
+
return {
|
|
212
|
+
documentType,
|
|
213
|
+
path: relativePath,
|
|
214
|
+
status: 'invalid',
|
|
215
|
+
detectedVersion,
|
|
216
|
+
currentVersion,
|
|
217
|
+
error: `unsupported schema version ${detectedVersion}`,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
documentType,
|
|
222
|
+
path: relativePath,
|
|
223
|
+
status: detectedVersion < currentVersion ? 'outdated' : 'ok',
|
|
224
|
+
detectedVersion,
|
|
225
|
+
currentVersion,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
return {
|
|
230
|
+
documentType,
|
|
231
|
+
path: relativePath,
|
|
232
|
+
status: 'invalid',
|
|
233
|
+
currentVersion,
|
|
234
|
+
error: error instanceof Error ? error.message : String(error),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function addSchemaVersion(raw, schemaVersion) {
|
|
239
|
+
if (!isRecord(raw)) {
|
|
240
|
+
throw new Error('document is not an object');
|
|
241
|
+
}
|
|
242
|
+
return { ...raw, schema_version: schemaVersion };
|
|
243
|
+
}
|
|
244
|
+
function isRecord(value) {
|
|
245
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { loadConfig } from './config.js';
|
|
4
|
+
import { loadVersionedJsonFile, saveVersionedJsonFile } from './migration.js';
|
|
5
|
+
import { nowISO } from './ids.js';
|
|
6
|
+
import { MEMORY_DIR, memoryPath } from './io.js';
|
|
7
|
+
import { ProjectIdentityDocumentSchema } from './schema.js';
|
|
8
|
+
const PROJECT_IDENTITY_FILE = 'project.identity.json';
|
|
9
|
+
export function generateProjectId() {
|
|
10
|
+
return `prj_${crypto.randomUUID().replace(/-/g, '')}`;
|
|
11
|
+
}
|
|
12
|
+
export function loadProjectIdentity(cwd, preferredDirName) {
|
|
13
|
+
const filepath = memoryPath(PROJECT_IDENTITY_FILE, cwd, preferredDirName);
|
|
14
|
+
return loadVersionedJsonFile('project_identity', filepath).document;
|
|
15
|
+
}
|
|
16
|
+
export function saveProjectIdentity(identity, cwd, preferredDirName) {
|
|
17
|
+
const filepath = memoryPath(PROJECT_IDENTITY_FILE, cwd, preferredDirName);
|
|
18
|
+
saveVersionedJsonFile('project_identity', filepath, ProjectIdentityDocumentSchema.parse(identity));
|
|
19
|
+
}
|
|
20
|
+
export function projectIdentityExists(cwd, preferredDirName) {
|
|
21
|
+
return fs.existsSync(memoryPath(PROJECT_IDENTITY_FILE, cwd, preferredDirName));
|
|
22
|
+
}
|
|
23
|
+
export function resolveExistingProjectIdentity(cwd) {
|
|
24
|
+
for (const dirName of [MEMORY_DIR]) {
|
|
25
|
+
try {
|
|
26
|
+
return loadProjectIdentity(cwd, dirName);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Ignore missing or malformed identity docs and keep searching.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const dirName of [MEMORY_DIR]) {
|
|
33
|
+
try {
|
|
34
|
+
const config = loadConfig(cwd, dirName);
|
|
35
|
+
if (config.project_id) {
|
|
36
|
+
return {
|
|
37
|
+
schema_version: 2,
|
|
38
|
+
version: 1,
|
|
39
|
+
project_id: config.project_id,
|
|
40
|
+
project_name: config.project_name,
|
|
41
|
+
created_at: nowISO(),
|
|
42
|
+
storage_dir: config.storage_dir,
|
|
43
|
+
topology: config.topology,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Ignore missing or malformed config and keep searching.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
export function buildProjectIdentity(input) {
|
|
54
|
+
return {
|
|
55
|
+
schema_version: 2,
|
|
56
|
+
version: 1,
|
|
57
|
+
project_id: input.existing?.project_id ?? generateProjectId(),
|
|
58
|
+
project_name: input.projectName,
|
|
59
|
+
created_at: input.existing?.created_at ?? nowISO(),
|
|
60
|
+
storage_dir: input.storageDir,
|
|
61
|
+
topology: input.topology,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=project-registry.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { loadState } from './state.js';
|
|
2
|
+
import { detectNewItemContradictions, hasBlockingContradictions, summarizeContradictions } from './contradictions.js';
|
|
3
|
+
export function evaluateReflectionSafety(input) {
|
|
4
|
+
if (input.type === 'handoff') {
|
|
5
|
+
return {};
|
|
6
|
+
}
|
|
7
|
+
const contradictions = detectNewItemContradictions(input.text, input.tags, input.relatedPaths, loadState(input.cwd), input.projectId);
|
|
8
|
+
if (contradictions.length === 0) {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
const contradictionSummary = summarizeContradictions(contradictions);
|
|
12
|
+
const promotionBlockedReason = input.automation && hasBlockingContradictions(contradictions)
|
|
13
|
+
? 'contradiction_detected'
|
|
14
|
+
: undefined;
|
|
15
|
+
return {
|
|
16
|
+
contradictions_detected: contradictions,
|
|
17
|
+
contradiction_summary: contradictionSummary,
|
|
18
|
+
promotion_blocked_reason: promotionBlockedReason,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=reflection-safety.js.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { MEMORY_DIR } from './io.js';
|
|
4
|
+
const MULTI_PROJECT_MARKERS = [
|
|
5
|
+
'pnpm-workspace.yaml',
|
|
6
|
+
'turbo.json',
|
|
7
|
+
'nx.json',
|
|
8
|
+
'lerna.json',
|
|
9
|
+
'rush.json',
|
|
10
|
+
];
|
|
11
|
+
const MULTI_PROJECT_DIRS = ['apps', 'packages', 'services'];
|
|
12
|
+
export function analyzeRepository(cwd) {
|
|
13
|
+
const reasons = [];
|
|
14
|
+
for (const marker of MULTI_PROJECT_MARKERS) {
|
|
15
|
+
if (fs.existsSync(path.join(cwd, marker))) {
|
|
16
|
+
reasons.push(`Found workspace marker: ${marker}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const matchedDirs = MULTI_PROJECT_DIRS.filter((dirName) => {
|
|
20
|
+
const dirPath = path.join(cwd, dirName);
|
|
21
|
+
return fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory();
|
|
22
|
+
});
|
|
23
|
+
if (matchedDirs.length > 0) {
|
|
24
|
+
reasons.push(`Found top-level project folders: ${matchedDirs.join(', ')}`);
|
|
25
|
+
}
|
|
26
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
27
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
28
|
+
try {
|
|
29
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
30
|
+
if (packageJson.workspaces) {
|
|
31
|
+
reasons.push('Found workspace configuration in package.json');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Ignore package.json parse errors during advisory analysis.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (reasons.length > 0) {
|
|
39
|
+
return {
|
|
40
|
+
recommendedMode: 'multi-project',
|
|
41
|
+
reasons,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
recommendedMode: 'single-project',
|
|
46
|
+
reasons: ['No monorepo or multi-project markers detected'],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Markers whose presence indicates a service/project boundary worth initialising. */
|
|
50
|
+
const SERVICE_MARKERS = [
|
|
51
|
+
'package.json',
|
|
52
|
+
'Dockerfile',
|
|
53
|
+
'docker-compose.yml',
|
|
54
|
+
'docker-compose.yaml',
|
|
55
|
+
'go.mod',
|
|
56
|
+
'requirements.txt',
|
|
57
|
+
'pyproject.toml',
|
|
58
|
+
'Cargo.toml',
|
|
59
|
+
'pom.xml',
|
|
60
|
+
'build.gradle',
|
|
61
|
+
'build.gradle.kts',
|
|
62
|
+
];
|
|
63
|
+
/** Directories to skip when scanning for service boundaries. */
|
|
64
|
+
const SKIP_DIRS = new Set([
|
|
65
|
+
'node_modules',
|
|
66
|
+
'.git',
|
|
67
|
+
'dist',
|
|
68
|
+
'dist-test',
|
|
69
|
+
'build',
|
|
70
|
+
'.brainclaw',
|
|
71
|
+
'__pycache__',
|
|
72
|
+
'.venv',
|
|
73
|
+
'venv',
|
|
74
|
+
'target',
|
|
75
|
+
'vendor',
|
|
76
|
+
'.next',
|
|
77
|
+
'.nuxt',
|
|
78
|
+
'coverage',
|
|
79
|
+
]);
|
|
80
|
+
/**
|
|
81
|
+
* Walk up to `maxDepth` levels below `rootDir`, find subdirectories that
|
|
82
|
+
* contain service markers but no `.brainclaw/` yet.
|
|
83
|
+
*
|
|
84
|
+
* `rootDir` itself is excluded (the caller is presumably about to `init` it).
|
|
85
|
+
*/
|
|
86
|
+
export function scanWorkspaceBoundaries(rootDir, maxDepth = 3) {
|
|
87
|
+
const suggestions = [];
|
|
88
|
+
const alreadyInitialised = [];
|
|
89
|
+
function walk(dir, depth) {
|
|
90
|
+
if (depth > maxDepth)
|
|
91
|
+
return;
|
|
92
|
+
let entries;
|
|
93
|
+
try {
|
|
94
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
if (!entry.isDirectory())
|
|
101
|
+
continue;
|
|
102
|
+
if (SKIP_DIRS.has(entry.name))
|
|
103
|
+
continue;
|
|
104
|
+
if (entry.name.startsWith('.') && entry.name !== '.brainclaw')
|
|
105
|
+
continue;
|
|
106
|
+
const childDir = path.join(dir, entry.name);
|
|
107
|
+
const childEntries = (() => {
|
|
108
|
+
try {
|
|
109
|
+
return fs.readdirSync(childDir).map((n) => n);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
const hasMemory = childEntries.includes(MEMORY_DIR);
|
|
116
|
+
const foundMarkers = SERVICE_MARKERS.filter((m) => childEntries.includes(m));
|
|
117
|
+
const relPath = path.relative(rootDir, childDir);
|
|
118
|
+
if (hasMemory) {
|
|
119
|
+
alreadyInitialised.push({ dir: childDir, relativePath: relPath });
|
|
120
|
+
}
|
|
121
|
+
else if (foundMarkers.length > 0) {
|
|
122
|
+
suggestions.push({ dir: childDir, relativePath: relPath, markers: foundMarkers });
|
|
123
|
+
}
|
|
124
|
+
// Only recurse into dirs that didn't already match a boundary
|
|
125
|
+
if (!hasMemory && foundMarkers.length === 0) {
|
|
126
|
+
walk(childDir, depth + 1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
walk(rootDir, 1);
|
|
131
|
+
return { suggestions, alreadyInitialised };
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=repo-analysis.js.map
|