chore(8.0): final pre-RC1 sweep — API consistency, named errors, orphans, zero-cast codebase

This commit is contained in:
David Snelling 2026-06-11 14:51:00 -07:00
parent 970e08c466
commit 1f7e365a4e
237 changed files with 1951 additions and 49413 deletions

View file

@ -7,10 +7,11 @@
*/
import chalk from 'chalk'
import ora from 'ora'
import ora, { type Ora } from 'ora'
import inquirer from 'inquirer'
import { Brainy } from '../../brainy.js'
import { BrainyTypes, NounType, VerbType } from '../../index.js'
import type { Entity, Result } from '../../types/brainy.types.js'
interface CoreOptions {
verbose?: boolean
@ -87,7 +88,7 @@ export const coreCommands = {
* Add data to the neural database
*/
async add(text: string | undefined, options: AddOptions) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if no text provided
if (!text) {
@ -222,7 +223,7 @@ export const coreCommands = {
* Search the neural database with Triple Intelligence
*/
async search(query: string | undefined, options: SearchOptions) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if no query provided
if (!query) {
@ -407,8 +408,13 @@ export const coreCommands = {
if (result.score !== undefined) {
console.log(chalk.green(` Score: ${(result.score * 100).toFixed(1)}%`))
if (options.explain && (result as any).scores) {
const scores = (result as any).scores
// Per-intelligence sub-scores aren't part of the public Result
// contract (the typed breakdown lives on `explanation`) — read
// defensively so --explain degrades to no breakdown when absent.
const scores = (result as Result & {
scores?: { vector?: number; graph?: number; field?: number }
}).scores
if (options.explain && scores) {
if (scores.vector !== undefined) {
console.log(chalk.dim(` Vector: ${(scores.vector * 100).toFixed(1)}%`))
}
@ -422,24 +428,28 @@ export const coreCommands = {
}
// Show type
if ((entity as any).type) {
console.log(chalk.dim(` Type: ${(entity as any).type}`))
if (entity.type) {
console.log(chalk.dim(` Type: ${entity.type}`))
}
// Show content preview
if ((entity as any).content) {
const preview = (entity as any).content.substring(0, 80)
console.log(chalk.dim(` Content: ${preview}${(entity as any).content.length > 80 ? '...' : ''}`))
// Show content preview. `content` is not a standard Entity field —
// surfaced defensively for records that carry flattened text content.
const content = (entity as Entity & { content?: string }).content
if (content) {
const preview = content.substring(0, 80)
console.log(chalk.dim(` Content: ${preview}${content.length > 80 ? '...' : ''}`))
}
// Show metadata
if ((entity as any).metadata && Object.keys((entity as any).metadata).length > 0) {
console.log(chalk.dim(` Metadata: ${JSON.stringify((entity as any).metadata)}`))
if (entity.metadata && Object.keys(entity.metadata).length > 0) {
console.log(chalk.dim(` Metadata: ${JSON.stringify(entity.metadata)}`))
}
// Show relationships
if (options.includeRelations && (result as any).relations) {
const relations = (result as any).relations
// Show relationships. `relations` isn't part of the public Result
// contract — read defensively so --include-relations degrades to
// no output when find() doesn't attach it.
const relations = (result as Result & { relations?: unknown[] }).relations
if (options.includeRelations && relations) {
if (relations.length > 0) {
console.log(chalk.dim(` Relations: ${relations.length} connections`))
}
@ -487,7 +497,7 @@ export const coreCommands = {
* Get item by ID
*/
async get(id: string | undefined, options: GetOptions) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if no ID provided
if (!id) {
@ -527,18 +537,25 @@ export const coreCommands = {
if (!options.json) {
console.log(chalk.cyan('\nItem Details:'))
console.log(` ID: ${item.id}`)
console.log(` Content: ${(item as any).content || 'N/A'}`)
// `content` is not a standard Entity field — see the search() note.
console.log(` Content: ${(item as Entity & { content?: string }).content || 'N/A'}`)
if (item.metadata) {
console.log(` Metadata: ${JSON.stringify(item.metadata, null, 2)}`)
}
if (options.withConnections) {
// Get verbs/relationships
// Get connections if method exists
const connections = (brain as any).getConnections ? await (brain as any).getConnections(id) : []
// Brainy's public API has no getConnections() — this optional probe
// predates related() and resolves to an empty list on current
// builds. Typed to match the legacy edge shape it rendered.
const legacyBrain = brain as Brainy & {
getConnections?: (id: string | undefined) => Promise<
Array<{ source: string; type: string; target: string }>
>
}
const connections = legacyBrain.getConnections ? await legacyBrain.getConnections(id) : []
if (connections && connections.length > 0) {
console.log(chalk.cyan('\nConnections:'))
connections.forEach((conn: any) => {
connections.forEach((conn) => {
console.log(` ${conn.source} --[${conn.type}]--> ${conn.target}`)
})
}
@ -561,7 +578,7 @@ export const coreCommands = {
* Create relationship between items
*/
async relate(source: string | undefined, verb: string | undefined, target: string | undefined, options: RelateOptions) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if parameters missing
if (!source || !verb || !target) {
@ -630,10 +647,12 @@ export const coreCommands = {
// `--subtype <value>` → Brainy default `'cli-relate'`. Same rationale
// as `brainy add` — guarantees CLI works under strict-mode brains
// (added 7.30.1).
// The verb arrives as a raw CLI string; relate() validates it against
// the VerbType vocabulary at runtime.
const result = await brain.relate({
from: source,
to: target,
type: verb as any,
type: verb as VerbType,
subtype: options.subtype ?? 'cli-relate',
metadata
})
@ -664,7 +683,7 @@ export const coreCommands = {
* Update an existing entity
*/
async update(id: string | undefined, options: AddOptions & { content?: string }) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if no ID provided
if (!id) {
@ -770,10 +789,10 @@ export const coreCommands = {
},
/**
* Delete an entity
* Remove an entity
*/
async deleteEntity(id: string | undefined, options: CoreOptions & { force?: boolean }) {
let spinner: any = null
async removeEntity(id: string | undefined, options: CoreOptions & { force?: boolean }) {
let spinner: Ora | null = null
try {
// Interactive mode if no ID provided
if (!id) {
@ -781,7 +800,7 @@ export const coreCommands = {
{
type: 'input',
name: 'id',
message: 'Entity ID to delete:',
message: 'Entity ID to remove:',
validate: (input: string) => input.trim().length > 0 || 'ID cannot be empty'
},
{
@ -793,7 +812,7 @@ export const coreCommands = {
])
if (!answers.confirm) {
console.log(chalk.yellow('Delete cancelled'))
console.log(chalk.yellow('Operation cancelled'))
return
}
@ -803,36 +822,36 @@ export const coreCommands = {
const answer = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: `Delete entity ${id}? This cannot be undone.`,
message: `Remove entity ${id}? This cannot be undone.`,
default: false
}])
if (!answer.confirm) {
console.log(chalk.yellow('Delete cancelled'))
console.log(chalk.yellow('Operation cancelled'))
return
}
}
spinner = ora('Deleting entity...').start()
spinner = ora('Removing entity...').start()
const brain = getBrainy()
await brain.init()
await brain.delete(id)
await brain.remove(id)
spinner.succeed('Entity deleted successfully')
spinner.succeed('Entity removed successfully')
if (!options.json) {
console.log(chalk.green(`Deleted entity: ${id}`))
console.log(chalk.green(`Removed entity: ${id}`))
} else {
formatOutput({ id, deleted: true }, options)
formatOutput({ id, removed: true }, options)
}
// One-shot command — see add() for why the explicit close + exit.
await brain.close()
process.exit(0)
} catch (error: any) {
if (spinner) spinner.fail('Failed to delete entity')
console.error(chalk.red('Delete failed:', error.message))
if (spinner) spinner.fail('Failed to remove entity')
console.error(chalk.red('Remove failed:', error.message))
process.exit(1)
}
},
@ -841,7 +860,7 @@ export const coreCommands = {
* Remove a relationship
*/
async unrelate(id: string | undefined, options: CoreOptions & { force?: boolean }) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if no ID provided
if (!id) {

View file

@ -10,7 +10,7 @@
* The legacy backup/import/export facade that used to live behind this
* command was superseded in 8.0: snapshots are `brainy snapshot` / `brainy
* restore` (the Db API's `persist()`/`restore()`), and data ingestion is
* `brainy import` (UniversalImportAPI).
* `brainy import` (`brain.import()`).
*/
import chalk from 'chalk'

View file

@ -3,7 +3,7 @@
* @description Import commands neural import and data import.
*
* Complete import system exposing ALL Brainy import capabilities:
* - UniversalImportAPI: Neural import with AI type matching
* - `brain.import()`: universal ingestion with neural type matching
* - DirectoryImporter: VFS directory imports
*
* Supports: files, directories, URLs, all formats. Backup/restore is the
@ -11,19 +11,23 @@
*/
import chalk from 'chalk'
import ora from 'ora'
import ora, { type Ora } from 'ora'
import inquirer from 'inquirer'
import { readFileSync, statSync, existsSync } from 'node:fs'
import { Brainy } from '../../brainy.js'
import { NounType } from '../../types/graphTypes.js'
import type { SupportedFormat } from '../../import/FormatDetector.js'
interface ImportOptions {
verbose?: boolean
json?: boolean
pretty?: boolean
quiet?: boolean
// Format options
format?: 'json' | 'csv' | 'jsonl' | 'yaml' | 'markdown' | 'html' | 'xml' | 'text'
// Format options. Populated by commander straight from `--format` with no
// validation, so the runtime value is whatever the user typed — kept as a
// plain string and narrowed to brain.import()'s SupportedFormat at the call
// boundary (the import extractor rejects unsupported values at runtime).
format?: string
// Import behavior
recursive?: boolean
batchSize?: string
@ -60,11 +64,11 @@ const formatOutput = (data: any, options: ImportOptions): void => {
export const importCommands = {
/**
* Enhanced import using UniversalImportAPI
* Supports files, directories, URLs, all formats
* Enhanced import via `brain.import()`.
* Supports files, directories, URLs, all formats.
*/
async import(source: string | undefined, options: ImportOptions) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if no source provided
if (!source) {
@ -260,7 +264,8 @@ export const importCommands = {
} else {
// File import with progress
result = await brain.import(source, {
format: options.format as any,
// Raw CLI string → SupportedFormat (see ImportOptions.format note).
format: options.format as SupportedFormat | undefined,
enableNeuralExtraction: options.extractEntities !== false,
enableRelationshipInference: options.detectRelationships !== false,
enableConceptExtraction: options.extractConcepts || false,
@ -422,7 +427,7 @@ export const importCommands = {
* VFS-specific import (files/directories into VFS)
*/
async vfsImport(source: string | undefined, options: ImportOptions) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if no source provided
if (!source) {

View file

@ -5,7 +5,7 @@
*/
import chalk from 'chalk'
import ora from 'ora'
import ora, { type Ora } from 'ora'
import inquirer from 'inquirer'
import Table from 'cli-table3'
import { Brainy } from '../../brainy.js'
@ -192,7 +192,7 @@ export const insightsCommands = {
* Get field values for a specific field
*/
async fieldValues(field: string | undefined, options: InsightsOptions & { limit?: string }) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if no field provided
if (!field) {
@ -274,7 +274,7 @@ export const insightsCommands = {
* Get optimal query plan for filters
*/
async queryPlan(options: InsightsOptions & { filters?: string }) {
let spinner: any = null
let spinner: Ora | null = null
try {
let filters: Record<string, any> = {}

View file

@ -15,6 +15,8 @@
import chalk from 'chalk'
import ora from 'ora'
import { Brainy } from '../../brainy.js'
import type { RelatedParams } from '../../types/brainy.types.js'
import type { NounType, VerbType } from '../../types/graphTypes.js'
interface InspectOptions {
fresh?: boolean
@ -109,8 +111,10 @@ export const inspectCommands = {
const where = options.where ? JSON.parse(options.where) : undefined
const limit = options.limit ? parseInt(options.limit, 10) : 20
const offset = options.offset ? parseInt(options.offset, 10) : 0
// --type arrives as a raw CLI string; find() resolves it against the
// NounType vocabulary at runtime.
const results = await brain.find({
type: options.type as any,
type: options.type as NounType | undefined,
where,
limit,
offset
@ -158,12 +162,16 @@ export const inspectCommands = {
if (spinner) spinner.text = 'Walking graph…'
const direction = options.direction ?? 'both'
const limit = options.limit ? parseInt(options.limit, 10) : 50
const rels = await brain.getRelations({
// `direction` is accepted by the CLI surface but RelatedParams has
// no such filter — related() reads from/to/type/limit and ignores
// extras, so the assertion only admits the extra key, it doesn't change
// what the query does. --type arrives as a raw CLI string.
const rels = await brain.related({
from: id,
direction,
type: options.type as any,
type: options.type as VerbType | undefined,
limit
} as any)
} as RelatedParams)
spinner?.succeed(`Found ${rels.length} relationship(s)`)
emit(rels, options)
await brain.close()
@ -185,7 +193,7 @@ export const inspectCommands = {
const brain = await openReader(path, options)
if (spinner) spinner.text = 'Planning query…'
const where = options.where ? JSON.parse(options.where) : {}
const plan = await brain.explain({ type: options.type as any, where })
const plan = await brain.explain({ type: options.type as NounType | undefined, where })
spinner?.succeed('Plan ready')
emit(plan, options)
await brain.close()
@ -228,7 +236,7 @@ export const inspectCommands = {
// dedicated random-sample API in Brainy core for this v1.
const window = Math.min(Math.max(n * 10, 50), 1000)
const pool = await brain.find({
type: options.type as any,
type: options.type as NounType | undefined,
limit: window
})
const sample = pool
@ -275,7 +283,7 @@ export const inspectCommands = {
let offset = 0
while (true) {
const page = await brain.find({
type: options.type as any,
type: options.type as NounType | undefined,
limit: batch,
offset
})
@ -310,7 +318,7 @@ export const inspectCommands = {
try {
const brain = await openReader(path, { ...options, quiet: true })
const page = await brain.find({
type: options.type as any,
type: options.type as NounType | undefined,
limit: 200
})
for (const e of page) {

View file

@ -10,10 +10,11 @@
import inquirer from 'inquirer'
import chalk from 'chalk'
import ora from 'ora'
import ora, { type Ora } from 'ora'
import fs from 'node:fs'
import path from 'node:path'
import { Brainy } from '../../brainy.js'
import type { NeuralClusterParams } from '../../types/brainy.types.js'
interface CommandArguments {
action?: string;
@ -113,10 +114,12 @@ async function handleSimilarCommand(neural: any, argv: CommandArguments): Promis
}
async function handleClustersCommand(neural: any, argv: CommandArguments): Promise<void> {
let spinner: any = null
let spinner: Ora | null = null
try {
let options: any = {
algorithm: argv.algorithm as any,
// --algorithm arrives as a raw CLI string; the interactive prompt below
// and neural.clusters() constrain it to the supported algorithms.
algorithm: argv.algorithm as NeuralClusterParams['algorithm'],
threshold: argv.threshold,
maxClusters: argv.limit
}

View file

@ -5,7 +5,7 @@
*/
import chalk from 'chalk'
import ora from 'ora'
import ora, { type Ora } from 'ora'
import inquirer from 'inquirer'
import Table from 'cli-table3'
import { Brainy } from '../../brainy.js'
@ -37,7 +37,7 @@ export const nlpCommands = {
* Extract entities from text
*/
async extract(text: string | undefined, options: NLPOptions) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if no text provided
if (!text) {
@ -126,7 +126,7 @@ export const nlpCommands = {
* Extract concepts from text
*/
async extractConcepts(text: string | undefined, options: NLPOptions & { threshold?: string }) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if no text provided
if (!text) {
@ -197,7 +197,7 @@ export const nlpCommands = {
* Analyze text with full NLP pipeline
*/
async analyze(text: string | undefined, options: NLPOptions) {
let spinner: any = null
let spinner: Ora | null = null
try {
// Interactive mode if no text provided
if (!text) {

View file

@ -6,7 +6,7 @@
* - `storage status` backend, root directory, entity/relationship counts,
* writer-lock holder, and operational mode (rendered from `brain.stats()`).
* - `storage batch-delete <file>` delete a newline-separated list of entity
* IDs through the public `brain.delete()` path (vectors, metadata, indexes,
* IDs through the public `brain.remove()` path (vectors, metadata, indexes,
* and statistics all stay consistent), with per-ID retry and an optional
* continue-on-error mode.
*
@ -144,7 +144,7 @@ export const storageCommands = {
/**
* @description Delete a list of entity IDs (one per line in a file) through
* the public `brain.delete()` path so vectors, metadata, graph edges,
* the public `brain.remove()` path so vectors, metadata, graph edges,
* indexes, and statistics all stay consistent. Asks for confirmation before
* deleting; failed IDs are retried up to `--max-retries` times, and
* `--continue-on-error` keeps going past IDs that still fail.
@ -204,7 +204,7 @@ export const storageCommands = {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
await brain.delete(id)
await brain.remove(id)
succeeded = true
break
} catch (error) {

View file

@ -30,6 +30,15 @@ interface BenchmarkOptions extends UtilityOptions {
iterations?: string
}
/** Per-operation timing summary produced by `brainy benchmark`. */
interface BenchmarkOperationStats {
avg: string
min: number
max: number
median: number
ops: string
}
let brainyInstance: Brainy | null = null
const getBrainy = (): Brainy => {
@ -188,7 +197,10 @@ export const utilityCommands = {
console.log(chalk.cyan(`\n🚀 Running Benchmarks (${iterations} iterations)\n`))
const results: any = {
const results: {
operations: Record<string, BenchmarkOperationStats>
summary: { totalOperations?: number; averageOpsPerSec?: string }
} = {
operations: {},
summary: {}
}
@ -253,7 +265,7 @@ export const utilityCommands = {
}
// Calculate summary
const totalOps: number = (Object.values(results.operations) as any[]).reduce((sum: number, op: any) =>
const totalOps: number = Object.values(results.operations).reduce((sum: number, op) =>
sum + parseFloat(op.ops), 0)
results.summary = {
@ -277,7 +289,7 @@ export const utilityCommands = {
style: { head: [], border: [] }
})
Object.entries(results.operations).forEach(([op, stats]: [string, any]) => {
Object.entries(results.operations).forEach(([op, stats]) => {
table.push([
op,
stats.avg,

View file

@ -9,6 +9,7 @@ import ora from 'ora'
import Table from 'cli-table3'
import { readFileSync, writeFileSync } from 'node:fs'
import { Brainy } from '../../brainy.js'
import type { SearchResult, VFSDirent } from '../../vfs/types.js'
interface VFSOptions {
verbose?: boolean
@ -54,8 +55,9 @@ export const vfsCommands = {
try {
const brain = await getBrainy() // Await async getBrainy
// VFS auto-initialized, no need for vfs.init()
// --encoding arrives as a raw CLI string (Node validates it at use time).
const buffer = await brain.vfs.readFile(path, {
encoding: options.encoding as any
encoding: options.encoding as BufferEncoding | undefined
})
spinner.succeed('File read successfully')
@ -103,8 +105,9 @@ export const vfsCommands = {
process.exit(1)
}
// --encoding arrives as a raw CLI string (Node validates it at use time).
await brain.vfs.writeFile(path, data, {
encoding: options.encoding as any
encoding: options.encoding as BufferEncoding | undefined
})
spinner.succeed('File written successfully')
@ -135,7 +138,9 @@ export const vfsCommands = {
try {
const brain = await getBrainy()
const entries = await brain.vfs.readdir(path, { withFileTypes: true })
// withFileTypes: true selects the VFSDirent[] branch of readdir's
// `string[] | VFSDirent[]` union return type.
const entries = await brain.vfs.readdir(path, { withFileTypes: true }) as VFSDirent[]
spinner.succeed(`Found ${Array.isArray(entries) ? entries.length : 0} items`)
@ -154,13 +159,14 @@ export const vfsCommands = {
style: { head: [], border: [] }
})
for (const entry of entries as any[]) {
for (const entry of entries) {
if (!options.all && entry.name.startsWith('.')) continue
const isDirectory = entry.type === 'directory'
const stat = await brain.vfs.stat(`${path}/${entry.name}`)
table.push([
entry.isDirectory() ? chalk.blue('DIR') : 'FILE',
entry.isDirectory() ? '-' : formatBytes(stat.size),
isDirectory ? chalk.blue('DIR') : 'FILE',
isDirectory ? '-' : formatBytes(stat.size),
formatDate(stat.mtime),
entry.name
])
@ -170,10 +176,10 @@ export const vfsCommands = {
} else {
// Simple format
console.log()
for (const entry of entries as any[]) {
for (const entry of entries) {
if (!options.all && entry.name.startsWith('.')) continue
if (entry.isDirectory()) {
if (entry.type === 'directory') {
console.log(chalk.blue(entry.name + '/'))
} else {
console.log(entry.name)
@ -322,8 +328,11 @@ export const vfsCommands = {
if (result.score) {
console.log(chalk.dim(` Score: ${(result.score * 100).toFixed(1)}%`))
}
if ((result as any).excerpt) {
console.log(chalk.dim(` ${(result as any).excerpt}`))
// `excerpt` isn't part of the VFS SearchResult contract — search()
// doesn't attach it today; read defensively.
const excerpt = (result as SearchResult & { excerpt?: string }).excerpt
if (excerpt) {
console.log(chalk.dim(` ${excerpt}`))
}
console.log()
})

View file

@ -46,7 +46,7 @@ ${chalk.cyan('Examples:')}
$ brainy add "React is a JavaScript library"
$ brainy find "JavaScript frameworks"
$ brainy update <id> --content "Updated content"
$ brainy delete <id> ${chalk.dim('# Requires confirmation')}
$ brainy remove <id> ${chalk.dim('# Requires confirmation')}
$ brainy search "react" --type Component --where '{"tested":true}'
${chalk.dim('# Neural API')}
@ -143,10 +143,10 @@ program
.action(coreCommands.update)
program
.command('delete [id]')
.description('Delete an entity (interactive if no ID, requires confirmation)')
.command('remove [id]')
.description('Remove an entity (interactive if no ID, requires confirmation)')
.option('-f, --force', 'Skip confirmation prompt')
.action(coreCommands.deleteEntity)
.action(coreCommands.removeEntity)
program
.command('unrelate [id]')
@ -472,7 +472,7 @@ program
.addCommand(
new Command('batch-delete')
.argument('<file>', 'File containing entity IDs (one per line)')
.description('Delete a list of entities through the public delete path (with retry)')
.description('Delete a list of entities through the public remove() path (with retry)')
.option('--max-retries <n>', 'Maximum retry attempts per ID', '3')
.option('--continue-on-error', 'Continue past IDs that still fail after retries')
.action((file, options) => {