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.
Files changed (128) hide show
  1. package/LICENSE +74 -0
  2. package/README.md +226 -0
  3. package/dist/cli.js +1037 -0
  4. package/dist/commands/accept.js +149 -0
  5. package/dist/commands/adapter-openclaw-import.js +75 -0
  6. package/dist/commands/add-step.js +35 -0
  7. package/dist/commands/agent-board.js +106 -0
  8. package/dist/commands/audit.js +35 -0
  9. package/dist/commands/bootstrap.js +34 -0
  10. package/dist/commands/capability.js +104 -0
  11. package/dist/commands/changes.js +112 -0
  12. package/dist/commands/check-constraints.js +63 -0
  13. package/dist/commands/claim-resource.js +54 -0
  14. package/dist/commands/claim.js +92 -0
  15. package/dist/commands/complete-step.js +34 -0
  16. package/dist/commands/constraint.js +44 -0
  17. package/dist/commands/context-diff.js +32 -0
  18. package/dist/commands/context.js +63 -0
  19. package/dist/commands/decision.js +45 -0
  20. package/dist/commands/delete-plan.js +20 -0
  21. package/dist/commands/diff.js +99 -0
  22. package/dist/commands/doctor.js +1275 -0
  23. package/dist/commands/enable-agent.js +63 -0
  24. package/dist/commands/env.js +46 -0
  25. package/dist/commands/estimation-report.js +167 -0
  26. package/dist/commands/explore.js +47 -0
  27. package/dist/commands/export.js +381 -0
  28. package/dist/commands/handoff.js +63 -0
  29. package/dist/commands/history.js +22 -0
  30. package/dist/commands/hooks.js +123 -0
  31. package/dist/commands/init.js +356 -0
  32. package/dist/commands/install-hooks.js +115 -0
  33. package/dist/commands/instruction.js +56 -0
  34. package/dist/commands/list-agents.js +44 -0
  35. package/dist/commands/list-claims.js +45 -0
  36. package/dist/commands/list-instructions.js +50 -0
  37. package/dist/commands/list-plans.js +48 -0
  38. package/dist/commands/mcp-worker.js +12 -0
  39. package/dist/commands/mcp.js +2272 -0
  40. package/dist/commands/memory.js +283 -0
  41. package/dist/commands/metrics.js +175 -0
  42. package/dist/commands/plan-resource.js +62 -0
  43. package/dist/commands/plan.js +76 -0
  44. package/dist/commands/prune-candidates.js +36 -0
  45. package/dist/commands/prune.js +48 -0
  46. package/dist/commands/pull.js +25 -0
  47. package/dist/commands/push.js +28 -0
  48. package/dist/commands/rebuild.js +14 -0
  49. package/dist/commands/reflect-runtime-note.js +74 -0
  50. package/dist/commands/reflect.js +286 -0
  51. package/dist/commands/register-agent.js +29 -0
  52. package/dist/commands/reject.js +52 -0
  53. package/dist/commands/release-claim.js +41 -0
  54. package/dist/commands/release-claims.js +67 -0
  55. package/dist/commands/review.js +242 -0
  56. package/dist/commands/rollback.js +156 -0
  57. package/dist/commands/runtime-note.js +144 -0
  58. package/dist/commands/runtime-status.js +49 -0
  59. package/dist/commands/search.js +36 -0
  60. package/dist/commands/session-end.js +187 -0
  61. package/dist/commands/session-start.js +147 -0
  62. package/dist/commands/set-trust.js +92 -0
  63. package/dist/commands/setup.js +446 -0
  64. package/dist/commands/show-candidate.js +31 -0
  65. package/dist/commands/star-candidate.js +28 -0
  66. package/dist/commands/status.js +133 -0
  67. package/dist/commands/sync.js +159 -0
  68. package/dist/commands/tool.js +126 -0
  69. package/dist/commands/trap.js +74 -0
  70. package/dist/commands/update-handoff.js +23 -0
  71. package/dist/commands/update-plan.js +37 -0
  72. package/dist/commands/upgrade.js +382 -0
  73. package/dist/commands/use-candidate.js +35 -0
  74. package/dist/commands/version.js +96 -0
  75. package/dist/commands/watch.js +215 -0
  76. package/dist/commands/whoami.js +104 -0
  77. package/dist/core/agent-context.js +340 -0
  78. package/dist/core/agent-files.js +874 -0
  79. package/dist/core/agent-integrations.js +135 -0
  80. package/dist/core/agent-inventory.js +401 -0
  81. package/dist/core/agent-registry.js +420 -0
  82. package/dist/core/ai-agent-detection.js +140 -0
  83. package/dist/core/audit.js +85 -0
  84. package/dist/core/bootstrap.js +658 -0
  85. package/dist/core/brainclaw-version.js +433 -0
  86. package/dist/core/candidates.js +137 -0
  87. package/dist/core/circuit-breaker.js +118 -0
  88. package/dist/core/claims.js +72 -0
  89. package/dist/core/config.js +86 -0
  90. package/dist/core/context-diff.js +122 -0
  91. package/dist/core/context.js +1212 -0
  92. package/dist/core/contradictions.js +270 -0
  93. package/dist/core/coordination.js +86 -0
  94. package/dist/core/cross-project.js +99 -0
  95. package/dist/core/duplicates.js +72 -0
  96. package/dist/core/event-log.js +152 -0
  97. package/dist/core/events.js +56 -0
  98. package/dist/core/execution-context.js +204 -0
  99. package/dist/core/freshness.js +87 -0
  100. package/dist/core/global-registry.js +182 -0
  101. package/dist/core/host.js +10 -0
  102. package/dist/core/identity.js +151 -0
  103. package/dist/core/ids.js +56 -0
  104. package/dist/core/input-validation.js +81 -0
  105. package/dist/core/instructions.js +117 -0
  106. package/dist/core/io.js +191 -0
  107. package/dist/core/json-store.js +63 -0
  108. package/dist/core/lifecycle.js +45 -0
  109. package/dist/core/lock.js +129 -0
  110. package/dist/core/logger.js +49 -0
  111. package/dist/core/machine-profile.js +332 -0
  112. package/dist/core/markdown.js +120 -0
  113. package/dist/core/memory-git.js +133 -0
  114. package/dist/core/migration.js +247 -0
  115. package/dist/core/project-registry.js +64 -0
  116. package/dist/core/reflection-safety.js +21 -0
  117. package/dist/core/repo-analysis.js +133 -0
  118. package/dist/core/reputation.js +409 -0
  119. package/dist/core/runtime.js +134 -0
  120. package/dist/core/schema.js +580 -0
  121. package/dist/core/search.js +115 -0
  122. package/dist/core/security.js +66 -0
  123. package/dist/core/setup-state.js +50 -0
  124. package/dist/core/state.js +83 -0
  125. package/dist/core/store-resolution.js +119 -0
  126. package/dist/core/sync-remote.js +83 -0
  127. package/dist/core/traps.js +86 -0
  128. package/package.json +60 -0
@@ -0,0 +1,433 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { BrainclawLocalReleaseManifestSchema, } from './schema.js';
6
+ export const DEFAULT_LOCAL_RELEASES_DIR = '.releases';
7
+ export const DEFAULT_LOCAL_RELEASE_MANIFEST_PATH = `${DEFAULT_LOCAL_RELEASES_DIR}/brainclaw-local.json`;
8
+ const SEMVER_RE = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
9
+ let cachedCliVersion;
10
+ export function getInstalledBrainclawVersion() {
11
+ if (cachedCliVersion) {
12
+ return cachedCliVersion;
13
+ }
14
+ const packageJsonPath = findOwnPackageJson();
15
+ if (!packageJsonPath) {
16
+ cachedCliVersion = '0.0.0';
17
+ return cachedCliVersion;
18
+ }
19
+ try {
20
+ const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
21
+ cachedCliVersion = typeof parsed.version === 'string' && parsed.version.trim().length > 0
22
+ ? parsed.version.trim()
23
+ : '0.0.0';
24
+ }
25
+ catch {
26
+ cachedCliVersion = '0.0.0';
27
+ }
28
+ return cachedCliVersion;
29
+ }
30
+ export function assessBrainclawVersion(config) {
31
+ const cliVersion = getInstalledBrainclawVersion();
32
+ const minimumVersion = normalizeConfiguredVersion(config?.minimum_brainclaw_version);
33
+ const recommendedVersion = normalizeConfiguredVersion(config?.recommended_brainclaw_version);
34
+ const upgradeMessage = config?.brainclaw_upgrade_message?.trim() || null;
35
+ const upgradeCommand = config?.brainclaw_upgrade_command?.trim() || null;
36
+ const invalidFields = [
37
+ minimumVersion ? undefined : invalidField('minimum_brainclaw_version', config?.minimum_brainclaw_version),
38
+ recommendedVersion ? undefined : invalidField('recommended_brainclaw_version', config?.recommended_brainclaw_version),
39
+ ].filter((value) => Boolean(value));
40
+ if (invalidFields.length > 0) {
41
+ return {
42
+ cli_version: cliVersion,
43
+ minimum_brainclaw_version: minimumVersion,
44
+ recommended_brainclaw_version: recommendedVersion,
45
+ upgrade_message: upgradeMessage,
46
+ upgrade_command: upgradeCommand,
47
+ target_version: recommendedVersion ?? minimumVersion,
48
+ status: 'invalid_config',
49
+ message: `Invalid Brainclaw version policy in config.yaml: ${invalidFields.join(', ')}`,
50
+ };
51
+ }
52
+ if (minimumVersion && compareVersions(cliVersion, minimumVersion) < 0) {
53
+ return {
54
+ cli_version: cliVersion,
55
+ minimum_brainclaw_version: minimumVersion,
56
+ recommended_brainclaw_version: recommendedVersion,
57
+ upgrade_message: upgradeMessage,
58
+ upgrade_command: upgradeCommand,
59
+ target_version: recommendedVersion ?? minimumVersion,
60
+ status: 'upgrade_required',
61
+ message: `Installed brainclaw ${cliVersion} is older than the required minimum ${minimumVersion}.`,
62
+ };
63
+ }
64
+ if (recommendedVersion && compareVersions(cliVersion, recommendedVersion) < 0) {
65
+ return {
66
+ cli_version: cliVersion,
67
+ minimum_brainclaw_version: minimumVersion,
68
+ recommended_brainclaw_version: recommendedVersion,
69
+ upgrade_message: upgradeMessage,
70
+ upgrade_command: upgradeCommand,
71
+ target_version: recommendedVersion,
72
+ status: 'update_available',
73
+ message: `Installed brainclaw ${cliVersion} is older than the project recommendation ${recommendedVersion}.`,
74
+ };
75
+ }
76
+ const message = minimumVersion || recommendedVersion
77
+ ? `Installed brainclaw ${cliVersion} satisfies the project version policy.`
78
+ : `Installed brainclaw ${cliVersion}; no project-specific version policy is configured.`;
79
+ return {
80
+ cli_version: cliVersion,
81
+ minimum_brainclaw_version: minimumVersion,
82
+ recommended_brainclaw_version: recommendedVersion,
83
+ upgrade_message: upgradeMessage,
84
+ upgrade_command: upgradeCommand,
85
+ target_version: recommendedVersion ?? minimumVersion,
86
+ status: 'ok',
87
+ message,
88
+ };
89
+ }
90
+ export function checkBrainclawInstallableUpdate(config, cwd) {
91
+ const source = config?.brainclaw_update_source;
92
+ if (!source) {
93
+ return {
94
+ checked: false,
95
+ source_type: null,
96
+ source_description: null,
97
+ latest_installable_version: null,
98
+ artifact_path: null,
99
+ install_command: null,
100
+ release_notes: null,
101
+ status: 'not_configured',
102
+ message: 'No installable update source is configured for this project.',
103
+ };
104
+ }
105
+ if (source.type === 'npm') {
106
+ const packageName = source.package_name?.trim() || 'brainclaw';
107
+ const distTag = source.dist_tag?.trim() || 'latest';
108
+ return {
109
+ checked: false,
110
+ source_type: 'npm',
111
+ source_description: `${packageName}@${distTag}`,
112
+ latest_installable_version: null,
113
+ artifact_path: null,
114
+ install_command: null,
115
+ release_notes: null,
116
+ status: 'unsupported_source',
117
+ message: 'The npm update source is modeled in config but is not implemented yet in this build.',
118
+ };
119
+ }
120
+ const manifestPath = source.manifest_path.trim();
121
+ if (manifestPath.length === 0) {
122
+ return {
123
+ checked: false,
124
+ source_type: 'local-pack',
125
+ source_description: null,
126
+ latest_installable_version: null,
127
+ artifact_path: null,
128
+ install_command: null,
129
+ release_notes: null,
130
+ status: 'invalid_config',
131
+ message: 'brainclaw_update_source.manifest_path must not be empty.',
132
+ };
133
+ }
134
+ const resolvedManifestPath = path.isAbsolute(manifestPath)
135
+ ? manifestPath
136
+ : path.resolve(cwd, manifestPath);
137
+ if (!fs.existsSync(resolvedManifestPath)) {
138
+ return {
139
+ checked: true,
140
+ source_type: 'local-pack',
141
+ source_description: resolvedManifestPath,
142
+ latest_installable_version: null,
143
+ artifact_path: null,
144
+ install_command: null,
145
+ release_notes: null,
146
+ status: 'check_failed',
147
+ message: `The configured local-pack manifest was not found: ${resolvedManifestPath}`,
148
+ };
149
+ }
150
+ try {
151
+ const parsed = JSON.parse(fs.readFileSync(resolvedManifestPath, 'utf-8'));
152
+ const manifest = BrainclawLocalReleaseManifestSchema.parse(parsed);
153
+ const latestVersion = normalizeConfiguredVersion(manifest.latest_installable_version);
154
+ if (!latestVersion) {
155
+ return {
156
+ checked: true,
157
+ source_type: 'local-pack',
158
+ source_description: resolvedManifestPath,
159
+ latest_installable_version: null,
160
+ artifact_path: null,
161
+ install_command: null,
162
+ release_notes: manifest.release_notes?.trim() || config?.brainclaw_upgrade_message?.trim() || null,
163
+ status: 'check_failed',
164
+ message: `The local-pack manifest has an invalid latest_installable_version: ${manifest.latest_installable_version}`,
165
+ };
166
+ }
167
+ const artifactPath = manifest.artifact_path
168
+ ? resolveManifestArtifactPath(manifest.artifact_path, resolvedManifestPath)
169
+ : null;
170
+ const installCommand = manifest.install_command?.trim()
171
+ || (artifactPath ? `npm install -g "${artifactPath}"` : config?.brainclaw_upgrade_command?.trim() || null);
172
+ const releaseNotes = manifest.release_notes?.trim() || config?.brainclaw_upgrade_message?.trim() || null;
173
+ const installedVersion = getInstalledBrainclawVersion();
174
+ if (compareVersions(installedVersion, latestVersion) < 0) {
175
+ return {
176
+ checked: true,
177
+ source_type: 'local-pack',
178
+ source_description: resolvedManifestPath,
179
+ latest_installable_version: latestVersion,
180
+ artifact_path: artifactPath,
181
+ install_command: installCommand,
182
+ release_notes: releaseNotes,
183
+ status: 'update_available',
184
+ message: `A newer installable brainclaw build is available: ${latestVersion} (installed ${installedVersion}).`,
185
+ };
186
+ }
187
+ return {
188
+ checked: true,
189
+ source_type: 'local-pack',
190
+ source_description: resolvedManifestPath,
191
+ latest_installable_version: latestVersion,
192
+ artifact_path: artifactPath,
193
+ install_command: installCommand,
194
+ release_notes: releaseNotes,
195
+ status: 'up_to_date',
196
+ message: `Installed brainclaw ${installedVersion} is up to date for the configured local-pack channel.`,
197
+ };
198
+ }
199
+ catch (error) {
200
+ const message = error instanceof Error ? error.message : String(error);
201
+ return {
202
+ checked: true,
203
+ source_type: 'local-pack',
204
+ source_description: resolvedManifestPath,
205
+ latest_installable_version: null,
206
+ artifact_path: null,
207
+ install_command: null,
208
+ release_notes: null,
209
+ status: 'check_failed',
210
+ message: `Failed to read the configured local-pack manifest: ${message}`,
211
+ };
212
+ }
213
+ }
214
+ export function publishLocalBrainclawRelease(cwd, options = {}) {
215
+ const workspacePackage = readWorkspaceBrainclawPackage(cwd);
216
+ const outputDir = path.resolve(cwd, options.outputDir ?? DEFAULT_LOCAL_RELEASES_DIR);
217
+ const manifestPath = path.resolve(cwd, options.manifestPath ?? DEFAULT_LOCAL_RELEASE_MANIFEST_PATH);
218
+ fs.mkdirSync(outputDir, { recursive: true });
219
+ fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
220
+ const packResult = spawnSync(resolveNpmCommand(), resolveNpmPackArgs(outputDir), {
221
+ cwd,
222
+ encoding: 'utf-8',
223
+ timeout: 120000,
224
+ });
225
+ if (packResult.error) {
226
+ throw new Error(`Failed to run npm pack: ${packResult.error.message}`);
227
+ }
228
+ if (packResult.status !== 0) {
229
+ throw new Error(firstNonEmptyLine(packResult.stderr) ?? firstNonEmptyLine(packResult.stdout) ?? 'npm pack failed');
230
+ }
231
+ const artifactFilename = parsePackedFilename(packResult.stdout);
232
+ if (!artifactFilename) {
233
+ throw new Error('npm pack did not report the generated tarball filename.');
234
+ }
235
+ const artifactAbsolutePath = path.join(outputDir, artifactFilename);
236
+ if (!fs.existsSync(artifactAbsolutePath)) {
237
+ throw new Error(`npm pack reported ${artifactFilename}, but the tarball was not found in ${outputDir}.`);
238
+ }
239
+ const manifestArtifactPath = toManifestRelativePath(path.relative(path.dirname(manifestPath), artifactAbsolutePath));
240
+ const projectArtifactPath = toManifestRelativePath(path.relative(cwd, artifactAbsolutePath));
241
+ const projectManifestPath = toPortablePath(path.relative(cwd, manifestPath));
242
+ const installCommand = `npm install -g "${projectArtifactPath}"`;
243
+ const releaseNotes = options.releaseNotes?.trim() || null;
244
+ const manifest = BrainclawLocalReleaseManifestSchema.parse({
245
+ version: 1,
246
+ channel: 'local-pack',
247
+ package_name: workspacePackage.name,
248
+ latest_installable_version: workspacePackage.version,
249
+ published_at: new Date().toISOString(),
250
+ artifact_path: manifestArtifactPath,
251
+ install_command: installCommand,
252
+ release_notes: releaseNotes ?? undefined,
253
+ });
254
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
255
+ return {
256
+ package_name: workspacePackage.name,
257
+ workspace_version: workspacePackage.version,
258
+ manifest_path: projectManifestPath,
259
+ artifact_path: projectArtifactPath,
260
+ install_command: installCommand,
261
+ release_notes: releaseNotes,
262
+ };
263
+ }
264
+ function resolveNpmCommand() {
265
+ return process.platform === 'win32' ? (process.env.ComSpec?.trim() || 'cmd.exe') : 'npm';
266
+ }
267
+ function resolveNpmPackArgs(outputDir) {
268
+ if (process.platform === 'win32') {
269
+ return ['/d', '/s', '/c', 'npm', 'pack', '--json', '--pack-destination', outputDir];
270
+ }
271
+ return ['pack', '--json', '--pack-destination', outputDir];
272
+ }
273
+ function findOwnPackageJson() {
274
+ let currentDir = path.dirname(fileURLToPath(import.meta.url));
275
+ while (true) {
276
+ const candidate = path.join(currentDir, 'package.json');
277
+ if (fs.existsSync(candidate)) {
278
+ try {
279
+ const parsed = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
280
+ if (parsed.name === 'brainclaw') {
281
+ return candidate;
282
+ }
283
+ }
284
+ catch {
285
+ // Ignore malformed package.json while walking upward.
286
+ }
287
+ }
288
+ const parent = path.dirname(currentDir);
289
+ if (parent === currentDir) {
290
+ return undefined;
291
+ }
292
+ currentDir = parent;
293
+ }
294
+ }
295
+ function resolveManifestArtifactPath(artifactPath, manifestPath) {
296
+ if (path.isAbsolute(artifactPath)) {
297
+ return artifactPath;
298
+ }
299
+ return path.resolve(path.dirname(manifestPath), artifactPath);
300
+ }
301
+ function readWorkspaceBrainclawPackage(cwd) {
302
+ const packageJsonPath = path.join(cwd, 'package.json');
303
+ if (!fs.existsSync(packageJsonPath)) {
304
+ throw new Error('Local Brainclaw release publishing requires a package.json in the current workspace.');
305
+ }
306
+ let parsed;
307
+ try {
308
+ parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
309
+ }
310
+ catch (error) {
311
+ const message = error instanceof Error ? error.message : String(error);
312
+ throw new Error(`Failed to read package.json: ${message}`);
313
+ }
314
+ const name = typeof parsed.name === 'string' ? parsed.name.trim() : '';
315
+ const version = typeof parsed.version === 'string' ? parsed.version.trim() : '';
316
+ if (name !== 'brainclaw') {
317
+ throw new Error(`Local release publishing is only supported from the brainclaw workspace; found package name "${name || '<missing>'}".`);
318
+ }
319
+ if (!parseVersion(version)) {
320
+ throw new Error(`package.json version must be a valid semver string; found "${version || '<missing>'}".`);
321
+ }
322
+ return { name, version };
323
+ }
324
+ function parsePackedFilename(stdout) {
325
+ try {
326
+ const parsed = JSON.parse(stdout);
327
+ const filename = parsed[0]?.filename;
328
+ return typeof filename === 'string' && filename.trim().length > 0 ? filename.trim() : undefined;
329
+ }
330
+ catch {
331
+ return firstNonEmptyLine(stdout);
332
+ }
333
+ }
334
+ function firstNonEmptyLine(value) {
335
+ return value
336
+ .split(/\r?\n/)
337
+ .map((line) => line.trim())
338
+ .find(Boolean);
339
+ }
340
+ function toManifestRelativePath(value) {
341
+ const portable = toPortablePath(value);
342
+ if (portable.startsWith('./') || portable.startsWith('../')) {
343
+ return portable;
344
+ }
345
+ return `./${portable}`;
346
+ }
347
+ function toPortablePath(value) {
348
+ return value.replace(/\\/g, '/');
349
+ }
350
+ function normalizeConfiguredVersion(value) {
351
+ if (!value) {
352
+ return null;
353
+ }
354
+ const trimmed = value.trim();
355
+ if (trimmed.length === 0) {
356
+ return null;
357
+ }
358
+ return parseVersion(trimmed) ? trimmed : null;
359
+ }
360
+ function invalidField(fieldName, rawValue) {
361
+ if (!rawValue || rawValue.trim().length === 0) {
362
+ return undefined;
363
+ }
364
+ return parseVersion(rawValue.trim()) ? undefined : `${fieldName}=${rawValue.trim()}`;
365
+ }
366
+ function parseVersion(value) {
367
+ const match = value.match(SEMVER_RE);
368
+ if (!match) {
369
+ return undefined;
370
+ }
371
+ return {
372
+ major: Number.parseInt(match[1], 10),
373
+ minor: Number.parseInt(match[2], 10),
374
+ patch: Number.parseInt(match[3], 10),
375
+ prerelease: match[4] ? match[4].split('.') : [],
376
+ };
377
+ }
378
+ function compareVersions(left, right) {
379
+ const parsedLeft = parseVersion(left);
380
+ const parsedRight = parseVersion(right);
381
+ if (!parsedLeft || !parsedRight) {
382
+ return 0;
383
+ }
384
+ if (parsedLeft.major !== parsedRight.major) {
385
+ return parsedLeft.major - parsedRight.major;
386
+ }
387
+ if (parsedLeft.minor !== parsedRight.minor) {
388
+ return parsedLeft.minor - parsedRight.minor;
389
+ }
390
+ if (parsedLeft.patch !== parsedRight.patch) {
391
+ return parsedLeft.patch - parsedRight.patch;
392
+ }
393
+ return comparePrerelease(parsedLeft.prerelease, parsedRight.prerelease);
394
+ }
395
+ function comparePrerelease(left, right) {
396
+ if (left.length === 0 && right.length === 0) {
397
+ return 0;
398
+ }
399
+ if (left.length === 0) {
400
+ return 1;
401
+ }
402
+ if (right.length === 0) {
403
+ return -1;
404
+ }
405
+ const length = Math.max(left.length, right.length);
406
+ for (let index = 0; index < length; index += 1) {
407
+ const leftPart = left[index];
408
+ const rightPart = right[index];
409
+ if (leftPart === undefined) {
410
+ return -1;
411
+ }
412
+ if (rightPart === undefined) {
413
+ return 1;
414
+ }
415
+ if (leftPart === rightPart) {
416
+ continue;
417
+ }
418
+ const leftNumber = /^\d+$/.test(leftPart) ? Number.parseInt(leftPart, 10) : undefined;
419
+ const rightNumber = /^\d+$/.test(rightPart) ? Number.parseInt(rightPart, 10) : undefined;
420
+ if (leftNumber !== undefined && rightNumber !== undefined) {
421
+ return leftNumber - rightNumber;
422
+ }
423
+ if (leftNumber !== undefined) {
424
+ return -1;
425
+ }
426
+ if (rightNumber !== undefined) {
427
+ return 1;
428
+ }
429
+ return leftPart.localeCompare(rightPart);
430
+ }
431
+ return 0;
432
+ }
433
+ //# sourceMappingURL=brainclaw-version.js.map
@@ -0,0 +1,137 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { CandidateSchema } from './schema.js';
5
+ import { resolveEntityDir } from './io.js';
6
+ import { nowISO, getNextShortLabel } from './ids.js';
7
+ import { JsonStore } from './json-store.js';
8
+ function inboxDir(cwd, mode = 'read') {
9
+ return resolveEntityDir('inbox', cwd ?? process.cwd(), mode);
10
+ }
11
+ function acceptedDir(cwd, mode = 'read') {
12
+ return resolveEntityDir('inbox/accepted', cwd ?? process.cwd(), mode);
13
+ }
14
+ function rejectedDir(cwd, mode = 'read') {
15
+ return resolveEntityDir('inbox/rejected', cwd ?? process.cwd(), mode);
16
+ }
17
+ export function ensureInboxDirs(cwd) {
18
+ for (const dir of [inboxDir(cwd, 'write'), acceptedDir(cwd, 'write'), rejectedDir(cwd, 'write')]) {
19
+ if (!fs.existsSync(dir)) {
20
+ fs.mkdirSync(dir, { recursive: true });
21
+ }
22
+ }
23
+ }
24
+ function candidateStore(dest = 'pending', cwd) {
25
+ const dir = dest === 'accepted'
26
+ ? acceptedDir(cwd)
27
+ : dest === 'rejected'
28
+ ? rejectedDir(cwd)
29
+ : inboxDir(cwd);
30
+ return new JsonStore({
31
+ dirPath: dir,
32
+ documentType: 'candidate',
33
+ getId: (candidate) => candidate.id,
34
+ sort: (a, b) => a.created_at.localeCompare(b.created_at),
35
+ });
36
+ }
37
+ export function saveCandidate(candidate, cwd) {
38
+ ensureInboxDirs(cwd);
39
+ candidateStore('pending', cwd).save(CandidateSchema.parse(candidate));
40
+ }
41
+ export function loadCandidate(id, cwd) {
42
+ return candidateStore('pending', cwd).load(id);
43
+ }
44
+ export function updateCandidate(candidate, cwd) {
45
+ saveCandidate(candidate, cwd);
46
+ }
47
+ export function listCandidates(status, cwd) {
48
+ const candidates = candidateStore('pending', cwd).list();
49
+ return status ? candidates.filter((candidate) => candidate.status === status) : candidates;
50
+ }
51
+ export function archiveCandidate(candidate, dest, cwd) {
52
+ ensureInboxDirs(cwd);
53
+ candidateStore(dest, cwd).save(CandidateSchema.parse(candidate));
54
+ candidateStore('pending', cwd).delete(candidate.id);
55
+ }
56
+ export function listArchivedCandidates(dest, cwd) {
57
+ return candidateStore(dest, cwd).list();
58
+ }
59
+ export function deleteArchivedCandidate(id, dest, cwd) {
60
+ const dir = dest === 'accepted' ? acceptedDir(cwd) : rejectedDir(cwd);
61
+ const filepath = path.join(dir, `${id}.json`);
62
+ if (fs.existsSync(filepath)) {
63
+ fs.unlinkSync(filepath);
64
+ return true;
65
+ }
66
+ return false;
67
+ }
68
+ export function addCandidateStar(id, by, cwd) {
69
+ const candidate = loadCandidate(id, cwd);
70
+ if (candidate.status !== 'pending') {
71
+ throw new Error(`Candidate '${id}' is already ${candidate.status}.`);
72
+ }
73
+ const actor = by.trim();
74
+ if (!actor) {
75
+ throw new Error('Star actor must not be empty.');
76
+ }
77
+ if (candidate.starred_by.includes(actor)) {
78
+ return { candidate, added: false };
79
+ }
80
+ candidate.starred_by = [...candidate.starred_by, actor].sort((a, b) => a.localeCompare(b));
81
+ candidate.star_count = candidate.starred_by.length;
82
+ candidate.last_starred_at = nowISO();
83
+ updateCandidate(candidate, cwd);
84
+ return { candidate, added: true };
85
+ }
86
+ export function addCandidateUse(id, by, context, cwd) {
87
+ const candidate = loadCandidate(id, cwd);
88
+ if (candidate.status !== 'pending') {
89
+ throw new Error(`Candidate '${id}' is already ${candidate.status}.`);
90
+ }
91
+ const actor = by.trim();
92
+ const usageContext = context.trim();
93
+ if (!actor) {
94
+ throw new Error('Usage actor must not be empty.');
95
+ }
96
+ if (!usageContext) {
97
+ throw new Error('Usage context must not be empty.');
98
+ }
99
+ const exists = candidate.usage_events.some((event) => event.by === actor && event.context === usageContext);
100
+ if (exists) {
101
+ return { candidate, added: false };
102
+ }
103
+ candidate.usage_events = [
104
+ ...candidate.usage_events,
105
+ { by: actor, context: usageContext, created_at: nowISO() },
106
+ ].sort((a, b) => a.created_at.localeCompare(b.created_at));
107
+ candidate.usage_count = candidate.usage_events.length;
108
+ candidate.last_used_at = nowISO();
109
+ updateCandidate(candidate, cwd);
110
+ return { candidate, added: true };
111
+ }
112
+ export function generateCandidateId() {
113
+ const rand = crypto.randomBytes(4).toString('hex');
114
+ return `cnd_${rand}`;
115
+ }
116
+ /** Generate both a hash candidate ID and a short label (e.g. `cnd#47`). */
117
+ export function generateCandidateIdWithLabel(cwd) {
118
+ const rand = crypto.randomBytes(4).toString('hex');
119
+ const id = `cnd_${rand}`;
120
+ const short_label = getNextShortLabel('cnd', cwd);
121
+ return { id, short_label };
122
+ }
123
+ /**
124
+ * Resolve a candidate alias (`cnd#47`) or hash ID to the canonical hash ID.
125
+ * Searches pending inbox only — use `resolveArchivedIdOrAlias` for historical items.
126
+ */
127
+ export function resolveIdOrAlias(input, cwd) {
128
+ if (!/^[a-z]+#\d+$/.test(input))
129
+ return input;
130
+ const candidates = listCandidates(undefined, cwd);
131
+ const found = candidates.find(c => c.short_label === input);
132
+ if (!found) {
133
+ throw new Error(`No pending candidate found with alias '${input}'.`);
134
+ }
135
+ return found.id;
136
+ }
137
+ //# sourceMappingURL=candidates.js.map
@@ -0,0 +1,118 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { listArchivedCandidates } from './candidates.js';
4
+ import { loadConfig } from './config.js';
5
+ import { memoryDir } from './io.js';
6
+ import { withLock } from './lock.js';
7
+ const OVERRIDES_FILE = '.circuit-breaker-overrides.json';
8
+ function overridesPath(cwd) {
9
+ return path.join(memoryDir(cwd), OVERRIDES_FILE);
10
+ }
11
+ function readOverrides(cwd) {
12
+ const fp = overridesPath(cwd);
13
+ try {
14
+ return JSON.parse(fs.readFileSync(fp, 'utf-8'));
15
+ }
16
+ catch {
17
+ return {};
18
+ }
19
+ }
20
+ /** Record a manual reset for a given agent key (resets the rolling window for that agent). */
21
+ export function resetCircuitBreaker(agentKey, cwd) {
22
+ const fp = overridesPath(cwd);
23
+ withLock(fp, () => {
24
+ const overrides = readOverrides(cwd);
25
+ overrides[agentKey.trim().toLowerCase()] = new Date().toISOString();
26
+ fs.writeFileSync(fp, JSON.stringify(overrides, null, 2), 'utf-8');
27
+ });
28
+ }
29
+ /**
30
+ * Check circuit-breaker state for a single agent.
31
+ * Counts accepted rejections in the rolling window and compares to threshold.
32
+ */
33
+ export function checkCircuitBreaker(agentNameOrId, cwd) {
34
+ const config = loadConfig(cwd);
35
+ const threshold = config.reflective_memory?.circuit_breaker_threshold ?? 5;
36
+ const windowDays = config.reflective_memory?.circuit_breaker_window_days ?? 7;
37
+ const windowMs = windowDays * 24 * 60 * 60 * 1000;
38
+ const key = agentNameOrId.trim().toLowerCase();
39
+ // Honour manual reset: if a reset was recorded more recently than the window start, use it
40
+ const overrides = readOverrides(cwd);
41
+ const resetAt = overrides[key] ? new Date(overrides[key]) : null;
42
+ const windowStart = resetAt
43
+ ? new Date(Math.max(Date.now() - windowMs, resetAt.getTime()))
44
+ : new Date(Date.now() - windowMs);
45
+ const rejected = listArchivedCandidates('rejected', cwd);
46
+ const recentRejections = rejected.filter((c) => {
47
+ const matchesAgent = (c.author_id?.trim().toLowerCase() === key) ||
48
+ (c.author.trim().toLowerCase() === key);
49
+ if (!matchesAgent)
50
+ return false;
51
+ const resolvedAt = c.resolved_at ?? c.created_at;
52
+ return new Date(resolvedAt) >= windowStart;
53
+ });
54
+ return {
55
+ tripped: recentRejections.length >= threshold,
56
+ agent_key: key,
57
+ rejection_count: recentRejections.length,
58
+ threshold,
59
+ window_days: windowDays,
60
+ window_start: windowStart.toISOString(),
61
+ };
62
+ }
63
+ /**
64
+ * Build a snapshot of circuit-breaker state for all agents that have
65
+ * any recent rejection activity. Agents with no activity are omitted.
66
+ */
67
+ export function buildCircuitBreakerSnapshot(cwd) {
68
+ const config = loadConfig(cwd);
69
+ const threshold = config.reflective_memory?.circuit_breaker_threshold ?? 5;
70
+ const windowDays = config.reflective_memory?.circuit_breaker_window_days ?? 7;
71
+ const windowMs = windowDays * 24 * 60 * 60 * 1000;
72
+ const overrides = readOverrides(cwd);
73
+ const rejected = listArchivedCandidates('rejected', cwd);
74
+ const agentCounts = new Map();
75
+ for (const c of rejected) {
76
+ const key = (c.author_id?.trim().toLowerCase() ?? c.author.trim().toLowerCase());
77
+ const resetAt = overrides[key] ? new Date(overrides[key]) : null;
78
+ const windowStart = resetAt
79
+ ? new Date(Math.max(Date.now() - windowMs, resetAt.getTime()))
80
+ : new Date(Date.now() - windowMs);
81
+ const resolvedAt = c.resolved_at ?? c.created_at;
82
+ if (new Date(resolvedAt) < windowStart)
83
+ continue;
84
+ const existing = agentCounts.get(key);
85
+ if (existing) {
86
+ existing.count++;
87
+ }
88
+ else {
89
+ agentCounts.set(key, { name: c.author, id: c.author_id, count: 1, windowStart });
90
+ }
91
+ }
92
+ const tripped = [];
93
+ const clear = [];
94
+ for (const [key, data] of agentCounts.entries()) {
95
+ const status = {
96
+ tripped: data.count >= threshold,
97
+ agent_key: key,
98
+ rejection_count: data.count,
99
+ threshold,
100
+ window_days: windowDays,
101
+ window_start: data.windowStart.toISOString(),
102
+ };
103
+ if (status.tripped) {
104
+ tripped.push(status);
105
+ }
106
+ else {
107
+ clear.push(status);
108
+ }
109
+ }
110
+ return {
111
+ checked_at: new Date().toISOString(),
112
+ window_days: windowDays,
113
+ threshold,
114
+ tripped_agents: tripped,
115
+ clear_agents: clear,
116
+ };
117
+ }
118
+ //# sourceMappingURL=circuit-breaker.js.map