feat: whyrating - initial project from turbostarter boilerplate
This commit is contained in:
743
packages/cognitive-context/src/cli.ts
Normal file
743
packages/cognitive-context/src/cli.ts
Normal file
@@ -0,0 +1,743 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* CLI Interface for Cognitive Context
|
||||
*
|
||||
* Provides commands for managing cognitive context:
|
||||
* - init: Initialize cognitive.config.yaml
|
||||
* - extract: Extract entities from source code
|
||||
* - validate: Validate context completeness
|
||||
* - drift: Check for context drift
|
||||
* - sync: Sync context to enabled tools
|
||||
* - watch: Watch for changes and auto-sync
|
||||
* - status: Show current context status
|
||||
* - hook: Manage git pre-commit hook
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { resolve, relative, join } from 'node:path';
|
||||
import { readFile, writeFile, mkdir, access } from 'node:fs/promises';
|
||||
|
||||
// Core modules
|
||||
import { initConfig, loadConfig, loadConfigOrDefault, ConfigNotFoundError } from './config.js';
|
||||
import { extractEntitiesFromDir, type ExtractOptions } from './extractor.js';
|
||||
import { validateCapabilitiesFile, formatValidationResult, hasValidationIssues } from './validator.js';
|
||||
import { detectDrift, formatDriftSummary, hasChanges as hasDriftChanges } from './drift.js';
|
||||
import { syncAll, loadKnowledge, getSupportedTools } from './sync.js';
|
||||
import { createWatcher } from './watcher.js';
|
||||
import { countTokens, formatTokenCount } from './tokens.js';
|
||||
import { installHook, uninstallHook, runPreCommitCheck, formatPreCommitOutput } from './hooks/pre-commit.js';
|
||||
import type { KnowledgeGraph, ExtractedEntity } from './types.js';
|
||||
|
||||
// ============================================
|
||||
// Constants
|
||||
// ============================================
|
||||
|
||||
const VERSION = '0.1.0';
|
||||
const COGNITIVE_DIR = '.cognitive';
|
||||
const KNOWLEDGE_FILE = 'knowledge.json';
|
||||
const CAPABILITIES_FILE = 'capabilities.yaml';
|
||||
|
||||
// ANSI color codes (inline to avoid external dependency)
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
bold: '\x1b[1m',
|
||||
dim: '\x1b[2m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m',
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Output Helpers
|
||||
// ============================================
|
||||
|
||||
function c(color: keyof typeof colors, text: string): string {
|
||||
return `${colors[color]}${text}${colors.reset}`;
|
||||
}
|
||||
|
||||
function success(message: string): void {
|
||||
console.log(`${c('green', '✓')} ${message}`);
|
||||
}
|
||||
|
||||
function error(message: string): void {
|
||||
console.error(`${c('red', '✗')} ${message}`);
|
||||
}
|
||||
|
||||
function warn(message: string): void {
|
||||
console.warn(`${c('yellow', '!')} ${message}`);
|
||||
}
|
||||
|
||||
function info(message: string): void {
|
||||
console.log(`${c('blue', 'i')} ${message}`);
|
||||
}
|
||||
|
||||
function heading(title: string): void {
|
||||
console.log(`\n${c('bold', title)}`);
|
||||
console.log(c('dim', '─'.repeat(title.length)));
|
||||
}
|
||||
|
||||
function bullet(text: string, indent: number = 0): void {
|
||||
console.log(`${' '.repeat(indent)}${c('dim', '•')} ${text}`);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Helper Functions
|
||||
// ============================================
|
||||
|
||||
async function fileExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSavedGraph(projectRoot: string): Promise<KnowledgeGraph | null> {
|
||||
try {
|
||||
const graphPath = join(projectRoot, COGNITIVE_DIR, KNOWLEDGE_FILE);
|
||||
const content = await readFile(graphPath, 'utf-8');
|
||||
return JSON.parse(content) as KnowledgeGraph;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveGraph(projectRoot: string, graph: KnowledgeGraph): Promise<void> {
|
||||
const cognitiveDir = join(projectRoot, COGNITIVE_DIR);
|
||||
await mkdir(cognitiveDir, { recursive: true });
|
||||
const graphPath = join(cognitiveDir, KNOWLEDGE_FILE);
|
||||
await writeFile(graphPath, JSON.stringify(graph, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
function buildKnowledgeGraph(
|
||||
entities: ExtractedEntity[],
|
||||
projectRoot: string,
|
||||
commit: string = 'unknown'
|
||||
): KnowledgeGraph {
|
||||
const entitiesMap: Record<string, ExtractedEntity> = {};
|
||||
|
||||
for (const entity of entities) {
|
||||
// Use path as key for uniqueness
|
||||
entitiesMap[entity.path] = entity;
|
||||
}
|
||||
|
||||
return {
|
||||
meta: {
|
||||
generatedAt: new Date().toISOString(),
|
||||
fromCommit: commit,
|
||||
totalEntities: entities.length,
|
||||
projectRoot,
|
||||
version: VERSION,
|
||||
},
|
||||
entities: entitiesMap,
|
||||
relationships: [], // Build relationships from imports
|
||||
};
|
||||
}
|
||||
|
||||
async function getGitCommit(projectRoot: string): Promise<string> {
|
||||
try {
|
||||
const { execSync } = await import('node:child_process');
|
||||
const commit = execSync('git rev-parse --short HEAD', {
|
||||
cwd: projectRoot,
|
||||
encoding: 'utf-8',
|
||||
}).trim();
|
||||
return commit;
|
||||
} catch {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Command Implementations
|
||||
// ============================================
|
||||
|
||||
async function cmdInit(_options: { config?: string; verbose?: boolean }): Promise<void> {
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
try {
|
||||
// Check if config already exists
|
||||
try {
|
||||
await loadConfig(projectRoot);
|
||||
error('Configuration already exists. Remove existing config to reinitialize.');
|
||||
process.exit(1);
|
||||
} catch (e) {
|
||||
if (!(e instanceof ConfigNotFoundError)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const configPath = await initConfig(projectRoot);
|
||||
success(`Created ${c('cyan', relative(projectRoot, configPath))}`);
|
||||
|
||||
// Create .cognitive directory
|
||||
const cognitiveDir = join(projectRoot, COGNITIVE_DIR);
|
||||
await mkdir(cognitiveDir, { recursive: true });
|
||||
success(`Created ${c('cyan', COGNITIVE_DIR + '/')} directory`);
|
||||
|
||||
info('Next steps:');
|
||||
bullet('Run `cognitive extract` to scan your codebase');
|
||||
bullet('Run `cognitive sync` to generate tool-specific files');
|
||||
|
||||
} catch (err) {
|
||||
error(`Failed to initialize: ${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdExtract(options: { config?: string; verbose?: boolean }): Promise<void> {
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
try {
|
||||
const config = await loadConfigOrDefault(projectRoot);
|
||||
|
||||
heading('Extracting entities');
|
||||
|
||||
const extractOptions: ExtractOptions = {
|
||||
include: config.includePatterns,
|
||||
exclude: config.excludePatterns,
|
||||
};
|
||||
|
||||
let allEntities: ExtractedEntity[] = [];
|
||||
|
||||
for (const dir of config.sourceDirs) {
|
||||
const sourceDir = resolve(projectRoot, dir);
|
||||
if (!(await fileExists(sourceDir))) {
|
||||
if (options.verbose) {
|
||||
warn(`Skipping non-existent directory: ${dir}`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const entities = await extractEntitiesFromDir(sourceDir, extractOptions);
|
||||
if (options.verbose) {
|
||||
info(`${dir}: Found ${entities.length} entities`);
|
||||
}
|
||||
allEntities = allEntities.concat(entities);
|
||||
}
|
||||
|
||||
if (allEntities.length === 0) {
|
||||
warn('No entities found. Check your sourceDirs and patterns in config.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Build and save knowledge graph
|
||||
const commit = await getGitCommit(projectRoot);
|
||||
const graph = buildKnowledgeGraph(allEntities, projectRoot, commit);
|
||||
await saveGraph(projectRoot, graph);
|
||||
|
||||
// Summary by type
|
||||
const byType = new Map<string, number>();
|
||||
for (const entity of allEntities) {
|
||||
byType.set(entity.type, (byType.get(entity.type) || 0) + 1);
|
||||
}
|
||||
|
||||
success(`Extracted ${c('bold', String(allEntities.length))} entities`);
|
||||
|
||||
for (const [type, count] of byType) {
|
||||
bullet(`${type}: ${count}`, 1);
|
||||
}
|
||||
|
||||
info(`Knowledge graph saved to ${c('cyan', COGNITIVE_DIR + '/' + KNOWLEDGE_FILE)}`);
|
||||
|
||||
} catch (err) {
|
||||
error(`Extraction failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdValidate(_options: { config?: string; verbose?: boolean }): Promise<void> {
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
try {
|
||||
const savedGraph = await loadSavedGraph(projectRoot);
|
||||
|
||||
if (!savedGraph) {
|
||||
warn('No knowledge graph found. Run `cognitive extract` first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
heading('Validating context completeness');
|
||||
|
||||
const capabilitiesPath = join(projectRoot, COGNITIVE_DIR, CAPABILITIES_FILE);
|
||||
|
||||
if (!(await fileExists(capabilitiesPath))) {
|
||||
warn(`No ${CAPABILITIES_FILE} found. Create one to validate against.`);
|
||||
info('Tip: Define your project capabilities in .cognitive/capabilities.yaml');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await validateCapabilitiesFile(capabilitiesPath, savedGraph);
|
||||
|
||||
console.log('');
|
||||
console.log(formatValidationResult(result));
|
||||
|
||||
if (hasValidationIssues(result)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
success('Validation passed!');
|
||||
|
||||
} catch (err) {
|
||||
error(`Validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdDrift(_options: { config?: string; verbose?: boolean }): Promise<void> {
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
try {
|
||||
const config = await loadConfigOrDefault(projectRoot);
|
||||
const savedGraph = await loadSavedGraph(projectRoot);
|
||||
|
||||
if (!savedGraph) {
|
||||
info('No previous knowledge graph found. Run `cognitive extract` to create one.');
|
||||
return;
|
||||
}
|
||||
|
||||
heading('Checking for drift');
|
||||
|
||||
// Re-extract current state
|
||||
const extractOptions: ExtractOptions = {
|
||||
include: config.includePatterns,
|
||||
exclude: config.excludePatterns,
|
||||
};
|
||||
|
||||
let currentEntities: ExtractedEntity[] = [];
|
||||
|
||||
for (const dir of config.sourceDirs) {
|
||||
const sourceDir = resolve(projectRoot, dir);
|
||||
if (await fileExists(sourceDir)) {
|
||||
const entities = await extractEntitiesFromDir(sourceDir, extractOptions);
|
||||
currentEntities = currentEntities.concat(entities);
|
||||
}
|
||||
}
|
||||
|
||||
const commit = await getGitCommit(projectRoot);
|
||||
const currentGraph = buildKnowledgeGraph(currentEntities, projectRoot, commit);
|
||||
|
||||
const report = detectDrift(currentGraph, savedGraph);
|
||||
|
||||
console.log('');
|
||||
console.log(formatDriftSummary(report));
|
||||
|
||||
if (hasDriftChanges(report)) {
|
||||
warn('Drift detected! Consider running `cognitive extract && cognitive sync`');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
success('No significant drift detected.');
|
||||
|
||||
} catch (err) {
|
||||
error(`Drift check failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdSync(options: { config?: string; verbose?: boolean; dryRun?: boolean }): Promise<void> {
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
try {
|
||||
const config = await loadConfigOrDefault(projectRoot);
|
||||
|
||||
heading('Syncing to tools');
|
||||
|
||||
const enabledTools = config.tools.filter((t) => t.enabled);
|
||||
|
||||
if (enabledTools.length === 0) {
|
||||
warn('No tools enabled. Enable tools in your cognitive.config.yaml');
|
||||
info(`Supported tools: ${getSupportedTools().join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
info(`Enabled tools: ${enabledTools.map((t) => t.name).join(', ')}`);
|
||||
|
||||
if (options.dryRun) {
|
||||
info(c('yellow', 'Dry run mode - no files will be written'));
|
||||
}
|
||||
|
||||
const toolConfigs = config.tools.map((t) => ({
|
||||
name: t.name,
|
||||
enabled: t.enabled,
|
||||
outputPath: t.outputPath ?? '',
|
||||
}));
|
||||
|
||||
const report = await syncAll(projectRoot, toolConfigs, {
|
||||
dryRun: options.dryRun,
|
||||
verbose: options.verbose,
|
||||
});
|
||||
|
||||
console.log('');
|
||||
|
||||
for (const result of report.results) {
|
||||
if (result.success) {
|
||||
if (result.filesWritten.length > 0 || result.filesDeleted.length > 0) {
|
||||
success(`${result.tool}: ${result.filesWritten.length} written, ${result.filesDeleted.length} deleted`);
|
||||
if (options.verbose) {
|
||||
for (const file of result.filesWritten) {
|
||||
bullet(c('green', '+') + ' ' + relative(projectRoot, file), 1);
|
||||
}
|
||||
for (const file of result.filesDeleted) {
|
||||
bullet(c('red', '-') + ' ' + relative(projectRoot, file), 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info(`${result.tool}: No changes needed`);
|
||||
}
|
||||
} else {
|
||||
error(`${result.tool}: ${result.error}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
if (report.allSuccessful) {
|
||||
success(`Sync complete: ${report.totalFilesWritten} files written, ${report.totalFilesDeleted} deleted`);
|
||||
} else {
|
||||
warn('Sync completed with errors');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
error(`Sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdWatch(options: { config?: string; verbose?: boolean }): Promise<void> {
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
try {
|
||||
const config = await loadConfigOrDefault(projectRoot);
|
||||
|
||||
heading('Watch mode');
|
||||
|
||||
const sourceDirs = config.sourceDirs.map((d) => resolve(projectRoot, d));
|
||||
|
||||
info(`Watching: ${config.sourceDirs.join(', ')}`);
|
||||
info('Press Ctrl+C to stop');
|
||||
console.log('');
|
||||
|
||||
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let pendingChanges = new Set<string>();
|
||||
|
||||
const handleSync = async () => {
|
||||
if (pendingChanges.size === 0) return;
|
||||
|
||||
const changes = Array.from(pendingChanges);
|
||||
pendingChanges.clear();
|
||||
|
||||
info(`Changes detected: ${changes.length} file(s)`);
|
||||
|
||||
// Re-extract and sync
|
||||
const extractOptions: ExtractOptions = {
|
||||
include: config.includePatterns,
|
||||
exclude: config.excludePatterns,
|
||||
};
|
||||
|
||||
let allEntities: ExtractedEntity[] = [];
|
||||
for (const dir of sourceDirs) {
|
||||
if (await fileExists(dir)) {
|
||||
const entities = await extractEntitiesFromDir(dir, extractOptions);
|
||||
allEntities = allEntities.concat(entities);
|
||||
}
|
||||
}
|
||||
|
||||
const commit = await getGitCommit(projectRoot);
|
||||
const graph = buildKnowledgeGraph(allEntities, projectRoot, commit);
|
||||
await saveGraph(projectRoot, graph);
|
||||
|
||||
const toolConfigs = config.tools.map((t) => ({
|
||||
name: t.name,
|
||||
enabled: t.enabled,
|
||||
outputPath: t.outputPath ?? '',
|
||||
}));
|
||||
|
||||
const report = await syncAll(projectRoot, toolConfigs, { verbose: options.verbose });
|
||||
|
||||
if (report.allSuccessful && report.totalFilesWritten > 0) {
|
||||
success(`Synced ${report.totalFilesWritten} files`);
|
||||
}
|
||||
};
|
||||
|
||||
const watcher = createWatcher(
|
||||
sourceDirs,
|
||||
{
|
||||
enabled: true,
|
||||
debounceMs: config.watch.debounceMs,
|
||||
ignorePaths: config.watch.ignorePaths,
|
||||
},
|
||||
{
|
||||
onFileChange: (event) => {
|
||||
pendingChanges.add(event.path);
|
||||
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
|
||||
debounceTimer = setTimeout(() => {
|
||||
handleSync().catch((err) => {
|
||||
error(`Sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
});
|
||||
}, config.watch.debounceMs);
|
||||
},
|
||||
onError: (err) => {
|
||||
error(`Watch error: ${err.message}`);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
watcher.start();
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('');
|
||||
info('Stopping watch mode...');
|
||||
watcher.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Keep the process running
|
||||
await new Promise(() => {});
|
||||
|
||||
} catch (err) {
|
||||
error(`Watch failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdStatus(_options: { config?: string; verbose?: boolean }): Promise<void> {
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
try {
|
||||
heading('Cognitive Context Status');
|
||||
|
||||
// Check for config
|
||||
let hasConfig = false;
|
||||
try {
|
||||
await loadConfig(projectRoot);
|
||||
hasConfig = true;
|
||||
success('Configuration: Found');
|
||||
} catch (e) {
|
||||
if (e instanceof ConfigNotFoundError) {
|
||||
warn('Configuration: Not found (run `cognitive init`)');
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for knowledge graph
|
||||
const savedGraph = await loadSavedGraph(projectRoot);
|
||||
if (savedGraph) {
|
||||
success(`Knowledge graph: ${savedGraph.meta.totalEntities} entities`);
|
||||
bullet(`Generated: ${new Date(savedGraph.meta.generatedAt).toLocaleString()}`, 1);
|
||||
bullet(`Commit: ${savedGraph.meta.fromCommit}`, 1);
|
||||
} else {
|
||||
warn('Knowledge graph: Not found (run `cognitive extract`)');
|
||||
}
|
||||
|
||||
// Check for .cognitive directory
|
||||
const cognitiveDir = join(projectRoot, COGNITIVE_DIR);
|
||||
if (await fileExists(cognitiveDir)) {
|
||||
success(`.cognitive/ directory: Found`);
|
||||
} else {
|
||||
warn(`.cognitive/ directory: Not found`);
|
||||
}
|
||||
|
||||
// Check for knowledge files
|
||||
const knowledge = await loadKnowledge(projectRoot);
|
||||
|
||||
console.log('');
|
||||
info('Knowledge files:');
|
||||
|
||||
if (knowledge.summary) {
|
||||
const tokens = countTokens(knowledge.summary);
|
||||
bullet(`SUMMARY.md: ${formatTokenCount(tokens)} tokens`, 1);
|
||||
} else {
|
||||
bullet(c('dim', 'SUMMARY.md: Not found'), 1);
|
||||
}
|
||||
|
||||
if (Object.keys(knowledge.capabilities).length > 0) {
|
||||
bullet(`capabilities.yaml: ${Object.keys(knowledge.capabilities).length} top-level keys`, 1);
|
||||
} else {
|
||||
bullet(c('dim', 'capabilities.yaml: Not found'), 1);
|
||||
}
|
||||
|
||||
if (knowledge.wisdom.size > 0) {
|
||||
bullet(`Wisdom files: ${knowledge.wisdom.size}`, 1);
|
||||
}
|
||||
|
||||
if (knowledge.commands.size > 0) {
|
||||
bullet(`Commands: ${knowledge.commands.size}`, 1);
|
||||
}
|
||||
|
||||
// Show enabled tools
|
||||
if (hasConfig) {
|
||||
const config = await loadConfigOrDefault(projectRoot);
|
||||
const enabledTools = config.tools.filter((t) => t.enabled).map((t) => t.name);
|
||||
|
||||
console.log('');
|
||||
info(`Enabled tools: ${enabledTools.length > 0 ? enabledTools.join(', ') : 'None'}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
} catch (err) {
|
||||
error(`Status check failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdHook(action: string, _options: { config?: string; verbose?: boolean }): Promise<void> {
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'install':
|
||||
await installHook(projectRoot);
|
||||
success('Pre-commit hook installed');
|
||||
info('Hook will run `cognitive pre-commit` before each commit');
|
||||
break;
|
||||
|
||||
case 'uninstall':
|
||||
await uninstallHook(projectRoot);
|
||||
success('Pre-commit hook uninstalled');
|
||||
break;
|
||||
|
||||
default:
|
||||
error(`Unknown action: ${action}. Use 'install' or 'uninstall'`);
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
error(`Hook ${action} failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdPreCommit(options: { config?: string; verbose?: boolean; block?: boolean }): Promise<void> {
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
try {
|
||||
const result = await runPreCommitCheck(projectRoot, {
|
||||
blockOnStale: options.block,
|
||||
verbose: options.verbose,
|
||||
});
|
||||
|
||||
console.log(formatPreCommitOutput(result));
|
||||
|
||||
if (!result.passed) {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
error(`Pre-commit check failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Program Factory
|
||||
// ============================================
|
||||
|
||||
export function createProgram(): Command {
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('cognitive')
|
||||
.description('Cognitive Context System - AI coding assistant context management')
|
||||
.version(VERSION)
|
||||
.option('-c, --config <path>', 'Path to config file')
|
||||
.option('-v, --verbose', 'Enable verbose output');
|
||||
|
||||
program
|
||||
.command('init')
|
||||
.description('Initialize cognitive.config.yaml in project')
|
||||
.action(async () => {
|
||||
const opts = program.opts();
|
||||
await cmdInit(opts);
|
||||
});
|
||||
|
||||
program
|
||||
.command('extract')
|
||||
.description('Extract entities from source code')
|
||||
.action(async () => {
|
||||
const opts = program.opts();
|
||||
await cmdExtract(opts);
|
||||
});
|
||||
|
||||
program
|
||||
.command('validate')
|
||||
.description('Validate context completeness')
|
||||
.action(async () => {
|
||||
const opts = program.opts();
|
||||
await cmdValidate(opts);
|
||||
});
|
||||
|
||||
program
|
||||
.command('drift')
|
||||
.description('Check for context drift')
|
||||
.action(async () => {
|
||||
const opts = program.opts();
|
||||
await cmdDrift(opts);
|
||||
});
|
||||
|
||||
program
|
||||
.command('sync')
|
||||
.description('Sync context to enabled tools')
|
||||
.option('-n, --dry-run', 'Show what would be done without making changes')
|
||||
.action(async (cmdOpts) => {
|
||||
const opts = { ...program.opts(), ...cmdOpts };
|
||||
await cmdSync(opts);
|
||||
});
|
||||
|
||||
program
|
||||
.command('watch')
|
||||
.description('Watch for changes and auto-sync')
|
||||
.action(async () => {
|
||||
const opts = program.opts();
|
||||
await cmdWatch(opts);
|
||||
});
|
||||
|
||||
program
|
||||
.command('status')
|
||||
.description('Show current context status')
|
||||
.action(async () => {
|
||||
const opts = program.opts();
|
||||
await cmdStatus(opts);
|
||||
});
|
||||
|
||||
program
|
||||
.command('hook <action>')
|
||||
.description('Manage git pre-commit hook (install/uninstall)')
|
||||
.action(async (action) => {
|
||||
const opts = program.opts();
|
||||
await cmdHook(action, opts);
|
||||
});
|
||||
|
||||
// Hidden command for pre-commit hook
|
||||
program
|
||||
.command('pre-commit', { hidden: true })
|
||||
.description('Run pre-commit validation (used by git hook)')
|
||||
.option('-b, --block', 'Block commit if context is stale')
|
||||
.action(async (cmdOpts) => {
|
||||
const opts = { ...program.opts(), ...cmdOpts };
|
||||
await cmdPreCommit(opts);
|
||||
});
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Main Entry Point
|
||||
// ============================================
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
const program = createProgram();
|
||||
await program.parseAsync(process.argv);
|
||||
}
|
||||
|
||||
// Note: Direct execution is handled by bin/cognitive.js
|
||||
// This module exports run() and createProgram() for programmatic use
|
||||
Reference in New Issue
Block a user