feat: implement comprehensive type safety system with BrainyTypes API

Major enhancements for type safety and developer experience:

- Add BrainyTypes static API for type management and AI-powered suggestions
- Implement strict type validation for all 31 NounType categories
- Remove dangerous generic add() method that bypassed type safety
- Add intelligent type inference with confidence scoring
- Provide helpful error messages with typo suggestions using Levenshtein distance
- Update all internal code, examples, and documentation to use typed methods
- Enhance CLI with new type management commands (types, suggest, validate)

Breaking changes:
- Remove deprecated add() method - use addNoun() with explicit type parameter
- All addNoun() calls now require explicit type as second parameter

This release significantly improves type safety across the entire system while
maintaining backward compatibility for properly typed method calls.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
David Snelling 2025-09-01 09:37:36 -07:00
parent d7d2d749b6
commit 6c62bc4e9d
40 changed files with 1704 additions and 497 deletions

View file

@ -8,6 +8,7 @@ import chalk from 'chalk'
import ora from 'ora'
import { readFileSync, writeFileSync } from 'fs'
import { BrainyData } from '../../brainyData.js'
import { BrainyTypes, NounType, VerbType } from '../../index.js'
interface CoreOptions {
verbose?: boolean
@ -85,12 +86,36 @@ export const coreCommands = {
metadata.id = options.id
}
// Determine noun type
let nounType: NounType
if (options.type) {
metadata.type = options.type
// Validate provided type
if (!BrainyTypes.isValidNoun(options.type)) {
spinner.fail(`Invalid noun type: ${options.type}`)
console.log(chalk.dim('Run "brainy types --noun" to see valid types'))
process.exit(1)
}
nounType = options.type as NounType
} else {
// Use AI to suggest type
spinner.text = 'Detecting type with AI...'
const suggestion = await BrainyTypes.suggestNoun(
typeof text === 'string' ? { content: text, ...metadata } : text
)
if (suggestion.confidence < 0.6) {
spinner.fail('Could not determine type with confidence')
console.log(chalk.yellow(`Suggestion: ${suggestion.type} (${(suggestion.confidence * 100).toFixed(1)}%)`)))
console.log(chalk.dim('Use --type flag to specify explicitly'))
process.exit(1)
}
nounType = suggestion.type as NounType
spinner.text = `Using detected type: ${nounType}`
}
// Smart detection by default
const result = await brain.add(text, metadata)
// Add with explicit type
const result = await brain.addNoun(text, nounType, metadata)
spinner.succeed('Added successfully')
@ -312,13 +337,28 @@ export const coreCommands = {
const batch = items.slice(i, i + batchSize)
for (const item of batch) {
let content: string
let metadata: any = {}
if (typeof item === 'string') {
await brain.add(item)
content = item
} else if (item.content || item.text) {
await brain.add(item.content || item.text, item.metadata || item)
content = item.content || item.text
metadata = item.metadata || item
} else {
await brain.add(JSON.stringify(item), { originalData: item })
content = JSON.stringify(item)
metadata = { originalData: item }
}
// Use AI to detect type for each item
const suggestion = await BrainyTypes.suggestNoun(
typeof content === 'string' ? { content, ...metadata } : content
)
// Use suggested type or default to Content if low confidence
const nounType = suggestion.confidence >= 0.5 ? suggestion.type : NounType.Content
await brain.addNoun(content, nounType as NounType, metadata)
imported++
}

226
src/cli/commands/types.ts Normal file
View file

@ -0,0 +1,226 @@
/**
* CLI Commands for Type Management
* Consistent with BrainyTypes public API
*/
import chalk from 'chalk'
import ora from 'ora'
import inquirer from 'inquirer'
import Table from 'cli-table3'
import { BrainyTypes, NounType, VerbType } from '../../index.js'
/**
* List types - matches BrainyTypes.nouns and BrainyTypes.verbs
* Usage: brainy types
*/
export async function types(options: { json?: boolean, noun?: boolean, verb?: boolean }) {
try {
// Default to showing both if neither flag specified
const showNouns = options.noun || (!options.noun && !options.verb)
const showVerbs = options.verb || (!options.noun && !options.verb)
const result: any = {}
if (showNouns) result.nouns = BrainyTypes.nouns
if (showVerbs) result.verbs = BrainyTypes.verbs
if (options.json) {
console.log(JSON.stringify(result, null, 2))
return
}
// Display nouns
if (showNouns) {
console.log(chalk.bold.cyan('\n📚 Noun Types (31):\n'))
const nounChunks = []
for (let i = 0; i < BrainyTypes.nouns.length; i += 3) {
nounChunks.push(BrainyTypes.nouns.slice(i, i + 3))
}
for (const chunk of nounChunks) {
console.log(' ' + chunk.map(n => chalk.green(n.padEnd(20))).join(''))
}
}
// Display verbs
if (showVerbs) {
console.log(chalk.bold.cyan('\n🔗 Verb Types (40):\n'))
const verbChunks = []
for (let i = 0; i < BrainyTypes.verbs.length; i += 3) {
verbChunks.push(BrainyTypes.verbs.slice(i, i + 3))
}
for (const chunk of verbChunks) {
console.log(' ' + chunk.map(v => chalk.blue(v.padEnd(20))).join(''))
}
}
console.log(chalk.dim('\n💡 Use "brainy suggest <data>" to get AI-powered type suggestions'))
} catch (error: any) {
console.error(chalk.red('Error:', error.message))
process.exit(1)
}
}
/**
* Suggest type - matches BrainyTypes.suggestNoun() and suggestVerb()
* Usage: brainy suggest <data>
* Interactive if data not provided
*/
export async function suggest(
data?: string,
options: {
verb?: boolean,
json?: boolean
} = {}
) {
try {
// Interactive mode if no data provided
if (!data) {
const answers = await inquirer.prompt([
{
type: 'list',
name: 'kind',
message: 'What type do you want to suggest?',
choices: ['Noun', 'Verb'],
default: 'Noun'
},
{
type: 'input',
name: 'data',
message: 'Enter data (JSON or text):',
validate: (input) => input.length > 0 || 'Data is required'
},
{
type: 'input',
name: 'hint',
message: 'Relationship hint (optional):',
when: (answers) => answers.kind === 'Verb'
}
])
data = answers.data
options.verb = answers.kind === 'Verb'
// For verbs, parse source/target if provided as JSON
if (options.verb && answers.hint) {
data = JSON.stringify({ hint: answers.hint })
}
}
const spinner = ora('Analyzing with AI...').start()
let parsedData: any
try {
parsedData = JSON.parse(data)
} catch {
parsedData = { content: data }
}
let suggestion
if (options.verb) {
// For verb suggestions, need source and target
const source = parsedData.source || { type: 'unknown' }
const target = parsedData.target || { type: 'unknown' }
const hint = parsedData.hint || parsedData.relationship || parsedData.verb
suggestion = await BrainyTypes.suggestVerb(source, target, hint)
spinner.succeed('Verb type analyzed')
} else {
suggestion = await BrainyTypes.suggestNoun(parsedData)
spinner.succeed('Noun type analyzed')
}
if (options.json) {
console.log(JSON.stringify(suggestion, null, 2))
return
}
// Display results
console.log(chalk.bold.green(`\n✨ Suggested: ${suggestion.type}`))
console.log(chalk.cyan(`Confidence: ${(suggestion.confidence * 100).toFixed(1)}%`))
if (suggestion.alternatives && suggestion.alternatives.length > 0) {
console.log(chalk.yellow('\nAlternatives:'))
for (const alt of suggestion.alternatives.slice(0, 3)) {
console.log(` ${alt.type} (${(alt.confidence * 100).toFixed(1)}%)`)
}
}
} catch (error: any) {
console.error(chalk.red('Error:', error.message))
process.exit(1)
}
}
/**
* Validate type - matches BrainyTypes.isValidNoun() and isValidVerb()
* Usage: brainy validate <type>
*/
export async function validate(
type?: string,
options: { verb?: boolean, json?: boolean } = {}
) {
try {
// Interactive mode if no type provided
if (!type) {
const answers = await inquirer.prompt([
{
type: 'list',
name: 'kind',
message: 'Validate as:',
choices: ['Noun Type', 'Verb Type'],
default: 'Noun Type'
},
{
type: 'input',
name: 'type',
message: 'Enter type to validate:',
validate: (input) => input.length > 0 || 'Type is required'
}
])
type = answers.type
options.verb = answers.kind === 'Verb Type'
}
const isValid = options.verb
? BrainyTypes.isValidVerb(type)
: BrainyTypes.isValidNoun(type)
if (options.json) {
console.log(JSON.stringify({
type,
kind: options.verb ? 'verb' : 'noun',
valid: isValid
}, null, 2))
return
}
if (isValid) {
console.log(chalk.green(`✅ "${type}" is valid`))
} else {
console.log(chalk.red(`❌ "${type}" is invalid`))
// Show valid options
const validTypes = options.verb ? BrainyTypes.verbs : BrainyTypes.nouns
const similar = validTypes.filter(t =>
t.toLowerCase().includes(type.toLowerCase()) ||
type.toLowerCase().includes(t.toLowerCase())
).slice(0, 5)
if (similar.length > 0) {
console.log(chalk.yellow('\nDid you mean:'))
similar.forEach(s => console.log(` ${s}`))
} else {
console.log(chalk.dim(`\nRun "brainy types" to see all valid types`))
}
}
process.exit(isValid ? 0 : 1)
} catch (error: any) {
console.error(chalk.red('Error:', error.message))
process.exit(1)
}
}