feat(8.0): brain.fillSubtypes migration helper + pre-RC1 gap closure
- brain.fillSubtypes(rules): idempotent subtype back-fill for pre-8.0 data.
One rule per NounType/VerbType (literal default or per-entry function);
fills only entries still missing a subtype through the public update()/
updateRelation() paths; returns { scanned, filled, skipped, errors, byType }.
Full unit suite in tests/unit/brainy/fill-subtypes.test.ts.
- Fix getNouns/getVerbs pagination hasMore (peek one past the window) —
was permanently false, silently truncating every multi-page walk.
- find({ near }) without near.id now throws a teaching error instead of an
opaque storage sharding failure; CLI --threshold without --near applies a
plain score floor.
- CLI init/close audit: every one-shot command init()s, close()s, and exits
explicitly; delete the unmaintained interactive REPL; replace the cloud-era
storage subcommands with status/batch-delete; new types/validate commands.
- requireSubtype JSDoc now documents the 8.0 default-on contract; audit()
recommendation points at fillSubtypes.
- Docs: data-storage-architecture rewritten to the real 8.0 on-disk layout;
README storage section reflects filesystem+memory and snapshots; eli5 and
SEMANTIC_VFS /as-of/ semantics corrected; internal tracker IDs and
.strategy references scrubbed from published files.
This commit is contained in:
parent
9b0f4acd5b
commit
c44678390e
30 changed files with 1517 additions and 3226 deletions
|
|
@ -1,7 +1,11 @@
|
|||
/**
|
||||
* 🧠 Neural Similarity API Commands
|
||||
*
|
||||
* CLI interface for semantic similarity, clustering, and neural operations
|
||||
* @module cli/commands/neural
|
||||
* @description Neural CLI commands: semantic similarity, clustering,
|
||||
* hierarchy, neighbors, outlier detection, and visualization data export.
|
||||
* Registered in `src/cli/index.ts` as `similar` / `cluster` / `related` /
|
||||
* `hierarchy` / `outliers` / `visualize`. Each command is one-shot: it
|
||||
* initializes the shared Brainy instance, runs the neural operation, then
|
||||
* closes the store and exits explicitly.
|
||||
*/
|
||||
|
||||
import inquirer from 'inquirer'
|
||||
|
|
@ -25,136 +29,13 @@ interface CommandArguments {
|
|||
_: string[];
|
||||
}
|
||||
|
||||
export const neuralCommand = {
|
||||
command: 'neural [action]',
|
||||
describe: '🧠 Neural similarity and clustering operations',
|
||||
|
||||
builder: (yargs: any) => {
|
||||
return yargs
|
||||
.positional('action', {
|
||||
describe: 'Neural operation to perform',
|
||||
type: 'string',
|
||||
choices: ['similar', 'clusters', 'hierarchy', 'neighbors', 'path', 'outliers', 'visualize']
|
||||
})
|
||||
.option('id', {
|
||||
describe: 'Item ID for similarity operations',
|
||||
type: 'string',
|
||||
alias: 'i'
|
||||
})
|
||||
.option('query', {
|
||||
describe: 'Query text for similarity search',
|
||||
type: 'string',
|
||||
alias: 'q'
|
||||
})
|
||||
.option('threshold', {
|
||||
describe: 'Similarity threshold (0-1)',
|
||||
type: 'number',
|
||||
default: 0.7,
|
||||
alias: 't'
|
||||
})
|
||||
.option('format', {
|
||||
describe: 'Output format',
|
||||
type: 'string',
|
||||
choices: ['json', 'table', 'tree', 'graph'],
|
||||
default: 'table',
|
||||
alias: 'f'
|
||||
})
|
||||
.option('output', {
|
||||
describe: 'Output file path',
|
||||
type: 'string',
|
||||
alias: 'o'
|
||||
})
|
||||
.option('limit', {
|
||||
describe: 'Maximum number of results',
|
||||
type: 'number',
|
||||
default: 10,
|
||||
alias: 'l'
|
||||
})
|
||||
.option('algorithm', {
|
||||
describe: 'Clustering algorithm',
|
||||
type: 'string',
|
||||
choices: ['hierarchical', 'kmeans', 'dbscan', 'auto'],
|
||||
default: 'auto',
|
||||
alias: 'a'
|
||||
})
|
||||
.option('dimensions', {
|
||||
describe: 'Visualization dimensions (2 or 3)',
|
||||
type: 'number',
|
||||
choices: [2, 3],
|
||||
default: 2,
|
||||
alias: 'd'
|
||||
})
|
||||
.option('explain', {
|
||||
describe: 'Include detailed explanations',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
alias: 'e'
|
||||
})
|
||||
},
|
||||
|
||||
handler: async (argv: CommandArguments) => {
|
||||
console.log(chalk.cyan('\n🧠 NEURAL SIMILARITY API'))
|
||||
console.log(chalk.gray('━'.repeat(50)))
|
||||
let brainyInstance: Brainy | null = null
|
||||
|
||||
// Initialize Brainy and Neural API
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
try {
|
||||
const action = argv.action || await promptForAction()
|
||||
|
||||
switch (action) {
|
||||
case 'similar':
|
||||
await handleSimilarCommand(neural, argv)
|
||||
break
|
||||
case 'clusters':
|
||||
await handleClustersCommand(neural, argv)
|
||||
break
|
||||
case 'hierarchy':
|
||||
await handleHierarchyCommand(neural, argv)
|
||||
break
|
||||
case 'neighbors':
|
||||
await handleNeighborsCommand(neural, argv)
|
||||
break
|
||||
case 'path':
|
||||
console.log(chalk.yellow('\n⚠️ Semantic path finding coming soon'))
|
||||
console.log(chalk.dim('This feature requires implementing graph traversal algorithms'))
|
||||
console.log(chalk.dim('Use "neighbors" and "hierarchy" commands to explore connections'))
|
||||
break
|
||||
case 'outliers':
|
||||
await handleOutliersCommand(neural, argv)
|
||||
break
|
||||
case 'visualize':
|
||||
await handleVisualizeCommand(neural, argv)
|
||||
break
|
||||
default:
|
||||
console.log(chalk.red(`❌ Unknown action: ${action}`))
|
||||
showHelp()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(chalk.red('💥 Error:'), error instanceof Error ? error.message : error)
|
||||
process.exit(1)
|
||||
}
|
||||
const getBrainy = (): Brainy => {
|
||||
if (!brainyInstance) {
|
||||
brainyInstance = new Brainy()
|
||||
}
|
||||
}
|
||||
|
||||
async function promptForAction(): Promise<string> {
|
||||
const answer = await inquirer.prompt([{
|
||||
type: 'list',
|
||||
name: 'action',
|
||||
message: 'Choose a neural operation:',
|
||||
choices: [
|
||||
{ name: '🔗 Calculate similarity between items', value: 'similar' },
|
||||
{ name: '🎯 Find semantic clusters', value: 'clusters' },
|
||||
{ name: '🌳 Show item hierarchy', value: 'hierarchy' },
|
||||
{ name: '🕸️ Find semantic neighbors', value: 'neighbors' },
|
||||
{ name: '🛣️ Find semantic path between items (coming soon)', value: 'path', disabled: true },
|
||||
{ name: '🚨 Detect outliers', value: 'outliers' },
|
||||
{ name: '📊 Generate visualization data', value: 'visualize' }
|
||||
]
|
||||
}])
|
||||
|
||||
return answer.action
|
||||
return brainyInstance
|
||||
}
|
||||
|
||||
async function handleSimilarCommand(neural: any, argv: CommandArguments): Promise<void> {
|
||||
|
|
@ -436,63 +317,6 @@ async function handleNeighborsCommand(neural: any, argv: CommandArguments): Prom
|
|||
}
|
||||
}
|
||||
|
||||
async function handlePathCommand(neural: any, argv: CommandArguments): Promise<void> {
|
||||
const spinner = ora('🛣️ Finding semantic path...').start()
|
||||
|
||||
try {
|
||||
let fromId: string, toId: string
|
||||
|
||||
if (argv._ && argv._.length >= 3) {
|
||||
fromId = argv._[1]
|
||||
toId = argv._[2]
|
||||
} else {
|
||||
spinner.stop()
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'from',
|
||||
message: 'From item ID:',
|
||||
validate: (input: string) => input.length > 0
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'to',
|
||||
message: 'To item ID:',
|
||||
validate: (input: string) => input.length > 0
|
||||
}
|
||||
])
|
||||
fromId = answers.from
|
||||
toId = answers.to
|
||||
spinner.start()
|
||||
}
|
||||
|
||||
const path = await neural.semanticPath(fromId, toId)
|
||||
|
||||
if (path.length === 0) {
|
||||
spinner.warn('🚫 No semantic path found')
|
||||
console.log(`No path found between ${chalk.cyan(fromId)} and ${chalk.cyan(toId)}`)
|
||||
} else {
|
||||
spinner.succeed(`✅ Found path with ${path.length} hops`)
|
||||
|
||||
console.log(`\n🛣️ Semantic Path from ${chalk.cyan(fromId)} to ${chalk.cyan(toId)}:`)
|
||||
console.log(`${chalk.cyan(fromId)} (start)`)
|
||||
|
||||
path.forEach((hop, index) => {
|
||||
console.log(`${' '.repeat(index + 1)}↓ ${(hop.similarity * 100).toFixed(1)}%`)
|
||||
console.log(`${' '.repeat(index + 1)}${hop.id} (hop ${hop.hop})`)
|
||||
})
|
||||
}
|
||||
|
||||
if (argv.output) {
|
||||
await saveToFile(argv.output, path, argv.format!)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
spinner.fail('💥 Failed to find path')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOutliersCommand(neural: any, argv: CommandArguments): Promise<void> {
|
||||
const spinner = ora('🚨 Detecting semantic outliers...').start()
|
||||
|
||||
|
|
@ -592,32 +416,32 @@ function formatAsTable(data: any): string {
|
|||
return JSON.stringify(data, null, 2)
|
||||
}
|
||||
|
||||
function showHelp(): void {
|
||||
console.log('\n🧠 Neural Similarity API Commands:')
|
||||
console.log('')
|
||||
console.log(' brainy neural similar <item1> <item2> Calculate similarity')
|
||||
console.log(' brainy neural clusters Find semantic clusters')
|
||||
console.log(' brainy neural hierarchy <id> Show item hierarchy')
|
||||
console.log(' brainy neural neighbors <id> Find semantic neighbors')
|
||||
console.log(' brainy neural path <from> <to> Find semantic path')
|
||||
console.log(' brainy neural outliers Detect outliers')
|
||||
console.log(' brainy neural visualize Generate visualization data')
|
||||
console.log('')
|
||||
console.log('Options:')
|
||||
console.log(' --threshold, -t Similarity threshold (0-1)')
|
||||
console.log(' --format, -f Output format (json|table|tree|graph)')
|
||||
console.log(' --output, -o Save to file')
|
||||
console.log(' --limit, -l Maximum results')
|
||||
console.log(' --explain, -e Include explanations')
|
||||
console.log('')
|
||||
/**
|
||||
* @description Run a neural CLI handler with the one-shot lifecycle every
|
||||
* Brainy command follows: init → work → `close()` → explicit `process.exit`.
|
||||
* close() releases the writer lock and indexes, but global timers
|
||||
* (UnifiedCache bookkeeping, PathResolver stats) keep the event loop alive,
|
||||
* so one-shot commands must exit explicitly.
|
||||
* @param work - The handler body, given the initialized neural API.
|
||||
*/
|
||||
async function runNeuralCommand(work: (neural: ReturnType<Brainy['neural']>) => Promise<void>): Promise<void> {
|
||||
try {
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
await work(brain.neural())
|
||||
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error(chalk.red('💥 Error:'), error instanceof Error ? error.message : error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Commander-compatible wrappers
|
||||
export const neuralCommands = {
|
||||
async similar(a?: string, b?: string, options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
// Build argv-style object for handler
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'similar', a || '', b || ''].filter(x => x),
|
||||
|
|
@ -627,13 +451,10 @@ export const neuralCommands = {
|
|||
...options
|
||||
}
|
||||
|
||||
await handleSimilarCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleSimilarCommand(neural, argv))
|
||||
},
|
||||
|
||||
async cluster(options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'cluster'],
|
||||
algorithm: options?.algorithm || 'hierarchical',
|
||||
|
|
@ -643,26 +464,20 @@ export const neuralCommands = {
|
|||
...options
|
||||
}
|
||||
|
||||
await handleClustersCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleClustersCommand(neural, argv))
|
||||
},
|
||||
|
||||
async hierarchy(id?: string, options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'hierarchy', id || ''].filter(x => x),
|
||||
id,
|
||||
...options
|
||||
}
|
||||
|
||||
await handleHierarchyCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleHierarchyCommand(neural, argv))
|
||||
},
|
||||
|
||||
async related(id?: string, options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'related', id || ''].filter(x => x),
|
||||
id,
|
||||
|
|
@ -671,13 +486,10 @@ export const neuralCommands = {
|
|||
...options
|
||||
}
|
||||
|
||||
await handleNeighborsCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleNeighborsCommand(neural, argv))
|
||||
},
|
||||
|
||||
async outliers(options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'outliers'],
|
||||
threshold: options?.threshold ? parseFloat(options.threshold) : 0.3,
|
||||
|
|
@ -685,13 +497,10 @@ export const neuralCommands = {
|
|||
...options
|
||||
}
|
||||
|
||||
await handleOutliersCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleOutliersCommand(neural, argv))
|
||||
},
|
||||
|
||||
async visualize(options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'visualize'],
|
||||
format: options?.format || 'json',
|
||||
|
|
@ -701,8 +510,6 @@ export const neuralCommands = {
|
|||
...options
|
||||
}
|
||||
|
||||
await handleVisualizeCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleVisualizeCommand(neural, argv))
|
||||
}
|
||||
}
|
||||
|
||||
export default neuralCommand
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue