nexus-agents 2.77.12 → 2.78.0
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.
- package/dist/{child-mcp-config-MJMUF7TL.js → child-mcp-config-CTO2MBRM.js} +3 -4
- package/dist/{child-mcp-config-MJMUF7TL.js.map → child-mcp-config-CTO2MBRM.js.map} +1 -1
- package/dist/{chunk-YJ2IGAD2.js → chunk-2UYTFLMO.js} +2 -2
- package/dist/{chunk-6AY5DK4E.js → chunk-2YPG6PDG.js} +3 -3
- package/dist/{chunk-3VWMM6UF.js → chunk-3NIPH6UP.js} +2 -2
- package/dist/{chunk-L3TPDTP3.js → chunk-4N33QZLH.js} +13 -15
- package/dist/{chunk-L3TPDTP3.js.map → chunk-4N33QZLH.js.map} +1 -1
- package/dist/{chunk-JN6UWGHH.js → chunk-5O6XLBPP.js} +2 -2
- package/dist/{chunk-ERWXGXV2.js → chunk-6TFTVW77.js} +3 -3
- package/dist/{chunk-2IAWMNNB.js → chunk-6WBTNZAY.js} +183 -87
- package/dist/chunk-6WBTNZAY.js.map +1 -0
- package/dist/{chunk-GOT7OAL5.js → chunk-7BMOZJYS.js} +29 -5
- package/dist/chunk-7BMOZJYS.js.map +1 -0
- package/dist/{chunk-C2LLQ6TW.js → chunk-7XCUZI4G.js} +4 -4
- package/dist/chunk-7XCUZI4G.js.map +1 -0
- package/dist/{chunk-TDV5ALHY.js → chunk-D6TM2VHX.js} +3 -3
- package/dist/{chunk-PWTJGGKB.js → chunk-DLXT23AC.js} +2 -2
- package/dist/chunk-DNO2INX5.js +276 -0
- package/dist/chunk-DNO2INX5.js.map +1 -0
- package/dist/{chunk-G2CSKBY5.js → chunk-FJWWSVWB.js} +29 -6
- package/dist/chunk-FJWWSVWB.js.map +1 -0
- package/dist/{chunk-DSQ5XM4O.js → chunk-FVPYP5DD.js} +4 -4
- package/dist/{chunk-MGLWPN2I.js → chunk-GONMG4NM.js} +2 -2
- package/dist/{chunk-XYA3DPWJ.js → chunk-GTGDVBLW.js} +5 -5
- package/dist/{chunk-YQMQSJQK.js → chunk-HYU4GZY6.js} +2 -2
- package/dist/{chunk-3DH5SLFH.js → chunk-K2QILJG4.js} +6 -6
- package/dist/{chunk-5WHWKY32.js → chunk-KT5FIBWS.js} +2 -2
- package/dist/{chunk-DIB6V67T.js → chunk-L6SCKLGO.js} +3 -3
- package/dist/{chunk-IPWCD22D.js → chunk-PLX6FCFC.js} +2 -2
- package/dist/chunk-PQHVC4BD.js +639 -0
- package/dist/chunk-PQHVC4BD.js.map +1 -0
- package/dist/chunk-Q5CFPIJ5.js +5581 -0
- package/dist/chunk-Q5CFPIJ5.js.map +1 -0
- package/dist/{chunk-G6ZPVADX.js → chunk-SD76JZBG.js} +2 -2
- package/dist/{chunk-Y2CP4Z5B.js → chunk-SWFJU3W2.js} +220 -4580
- package/dist/chunk-SWFJU3W2.js.map +1 -0
- package/dist/{chunk-3MRM53T4.js → chunk-WDYCIJWN.js} +640 -470
- package/dist/chunk-WDYCIJWN.js.map +1 -0
- package/dist/{chunk-CM3TORGV.js → chunk-YXWGEIQR.js} +2 -2
- package/dist/{chunk-7NK7BTWP.js → chunk-ZVCED4Z4.js} +2 -2
- package/dist/cli-circuit-breaker-I74ZQ44Q.js +13 -0
- package/dist/cli.js +109 -58
- package/dist/cli.js.map +1 -1
- package/dist/{composite-router-S6E26BCI.js → composite-router-V3OC57IE.js} +3 -4
- package/dist/consensus-vote-ESFPGEJE.js +30 -0
- package/dist/context-retriever-MB3D7KS6.js +18 -0
- package/dist/dist-NIXVXYIH.js +42 -0
- package/dist/doctor-deep-KQ765XZA.js +12 -0
- package/dist/expert-bridge-JKLC57IC.js +10 -0
- package/dist/factory-BUUXNGIB.js +14 -0
- package/dist/{factory-X3VKIGKP.js → factory-LHHYDVZX.js} +5 -6
- package/dist/index.d.ts +72 -8
- package/dist/index.js +208 -316
- package/dist/index.js.map +1 -1
- package/dist/{init-opencode-CFE7M6XA.js → init-opencode-GXZN2W5S.js} +6 -7
- package/dist/{init-opencode-CFE7M6XA.js.map → init-opencode-GXZN2W5S.js.map} +1 -1
- package/dist/issue-triage-RMXPDZ2K.js +15 -0
- package/dist/{learning-persistence-N6ILD2HX.js → learning-persistence-Q3HTOGTU.js} +2 -3
- package/dist/outcome-store-adapter-QRFJJIKB.js +57 -0
- package/dist/outcome-store-adapter-QRFJJIKB.js.map +1 -0
- package/dist/{registry-command-RPPC7N2K.js → registry-command-6E4YKAMT.js} +3 -4
- package/dist/{registry-command-RPPC7N2K.js.map → registry-command-6E4YKAMT.js.map} +1 -1
- package/dist/{repo-security-plan-7ZCDVH5O.js → repo-security-plan-AGRU72DL.js} +4 -5
- package/dist/research-helpers-synthesize-K2UCJQQG.js +13 -0
- package/dist/{routing-memory-5VTX7LQX.js → routing-memory-3B6DDZ76.js} +3 -4
- package/dist/{session-memory-7XBV6BMY.js → session-memory-L7EQIY2O.js} +4 -5
- package/dist/{setup-command-3ZTEPKDA.js → setup-command-VYV4RFWW.js} +11 -12
- package/dist/setup-config-EQT24DD4.js +10 -0
- package/dist/{setup-custom-api-WM5W5AY5.js → setup-custom-api-IBDV654K.js} +5 -6
- package/dist/{setup-custom-api-WM5W5AY5.js.map → setup-custom-api-IBDV654K.js.map} +1 -1
- package/dist/tool-memory-6HCHQLAN.js +19 -0
- package/dist/{weather-report-YJMVKJGA.js → weather-report-ER3WUZ7S.js} +3 -4
- package/package.json +3 -2
- package/dist/adaptive-memory-EI564K4C.js +0 -16
- package/dist/chunk-2IAWMNNB.js.map +0 -1
- package/dist/chunk-3MRM53T4.js.map +0 -1
- package/dist/chunk-BJ2OMC7P.js +0 -944
- package/dist/chunk-BJ2OMC7P.js.map +0 -1
- package/dist/chunk-C2LLQ6TW.js.map +0 -1
- package/dist/chunk-G2CSKBY5.js.map +0 -1
- package/dist/chunk-GOT7OAL5.js.map +0 -1
- package/dist/chunk-I7ORMAO7.js +0 -32
- package/dist/chunk-I7ORMAO7.js.map +0 -1
- package/dist/chunk-Y2CP4Z5B.js.map +0 -1
- package/dist/cli-circuit-breaker-YX4BWZD5.js +0 -14
- package/dist/consensus-vote-MUQ4HPIF.js +0 -30
- package/dist/doctor-deep-BRU5ZUJI.js +0 -13
- package/dist/expert-bridge-ZPNVLJVN.js +0 -11
- package/dist/factory-A7DTCCUY.js +0 -15
- package/dist/issue-triage-6XD6CVPB.js +0 -16
- package/dist/mobimem-CG2MNS7V.js +0 -14
- package/dist/nexus-data-dir-77UO7N6J.js +0 -12
- package/dist/research-helpers-synthesize-36TUTUUA.js +0 -14
- package/dist/setup-config-EI5KROA3.js +0 -11
- /package/dist/{chunk-YJ2IGAD2.js.map → chunk-2UYTFLMO.js.map} +0 -0
- /package/dist/{chunk-6AY5DK4E.js.map → chunk-2YPG6PDG.js.map} +0 -0
- /package/dist/{chunk-3VWMM6UF.js.map → chunk-3NIPH6UP.js.map} +0 -0
- /package/dist/{chunk-JN6UWGHH.js.map → chunk-5O6XLBPP.js.map} +0 -0
- /package/dist/{chunk-ERWXGXV2.js.map → chunk-6TFTVW77.js.map} +0 -0
- /package/dist/{chunk-TDV5ALHY.js.map → chunk-D6TM2VHX.js.map} +0 -0
- /package/dist/{chunk-PWTJGGKB.js.map → chunk-DLXT23AC.js.map} +0 -0
- /package/dist/{chunk-DSQ5XM4O.js.map → chunk-FVPYP5DD.js.map} +0 -0
- /package/dist/{chunk-MGLWPN2I.js.map → chunk-GONMG4NM.js.map} +0 -0
- /package/dist/{chunk-XYA3DPWJ.js.map → chunk-GTGDVBLW.js.map} +0 -0
- /package/dist/{chunk-YQMQSJQK.js.map → chunk-HYU4GZY6.js.map} +0 -0
- /package/dist/{chunk-3DH5SLFH.js.map → chunk-K2QILJG4.js.map} +0 -0
- /package/dist/{chunk-5WHWKY32.js.map → chunk-KT5FIBWS.js.map} +0 -0
- /package/dist/{chunk-DIB6V67T.js.map → chunk-L6SCKLGO.js.map} +0 -0
- /package/dist/{chunk-IPWCD22D.js.map → chunk-PLX6FCFC.js.map} +0 -0
- /package/dist/{chunk-G6ZPVADX.js.map → chunk-SD76JZBG.js.map} +0 -0
- /package/dist/{chunk-CM3TORGV.js.map → chunk-YXWGEIQR.js.map} +0 -0
- /package/dist/{chunk-7NK7BTWP.js.map → chunk-ZVCED4Z4.js.map} +0 -0
- /package/dist/{adaptive-memory-EI564K4C.js.map → cli-circuit-breaker-I74ZQ44Q.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-YX4BWZD5.js.map → composite-router-V3OC57IE.js.map} +0 -0
- /package/dist/{composite-router-S6E26BCI.js.map → consensus-vote-ESFPGEJE.js.map} +0 -0
- /package/dist/{consensus-vote-MUQ4HPIF.js.map → context-retriever-MB3D7KS6.js.map} +0 -0
- /package/dist/{doctor-deep-BRU5ZUJI.js.map → dist-NIXVXYIH.js.map} +0 -0
- /package/dist/{expert-bridge-ZPNVLJVN.js.map → doctor-deep-KQ765XZA.js.map} +0 -0
- /package/dist/{factory-A7DTCCUY.js.map → expert-bridge-JKLC57IC.js.map} +0 -0
- /package/dist/{factory-X3VKIGKP.js.map → factory-BUUXNGIB.js.map} +0 -0
- /package/dist/{issue-triage-6XD6CVPB.js.map → factory-LHHYDVZX.js.map} +0 -0
- /package/dist/{learning-persistence-N6ILD2HX.js.map → issue-triage-RMXPDZ2K.js.map} +0 -0
- /package/dist/{mobimem-CG2MNS7V.js.map → learning-persistence-Q3HTOGTU.js.map} +0 -0
- /package/dist/{nexus-data-dir-77UO7N6J.js.map → repo-security-plan-AGRU72DL.js.map} +0 -0
- /package/dist/{repo-security-plan-7ZCDVH5O.js.map → research-helpers-synthesize-K2UCJQQG.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-36TUTUUA.js.map → routing-memory-3B6DDZ76.js.map} +0 -0
- /package/dist/{routing-memory-5VTX7LQX.js.map → session-memory-L7EQIY2O.js.map} +0 -0
- /package/dist/{session-memory-7XBV6BMY.js.map → setup-command-VYV4RFWW.js.map} +0 -0
- /package/dist/{setup-command-3ZTEPKDA.js.map → setup-config-EQT24DD4.js.map} +0 -0
- /package/dist/{setup-config-EI5KROA3.js.map → tool-memory-6HCHQLAN.js.map} +0 -0
- /package/dist/{weather-report-YJMVKJGA.js.map → weather-report-ER3WUZ7S.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context/memory-backend-types.ts","../src/context/memory-backend.ts","../src/context/memory-markdown.ts","../src/context/memory-operations.ts","../src/context/adaptive-memory-types.ts","../src/utils/similarity-utils.ts","../src/utils/memory-db-utils.ts","../src/context/adaptive-memory-helpers.ts","../src/context/adaptive-memory.ts"],"sourcesContent":["/**\n * nexus-agents/context - Hybrid Memory Backend Types\n *\n * Type definitions, interfaces, and schemas for the hybrid memory backend.\n *\n * @module context/memory-backend-types\n */\n\nimport { z } from 'zod';\nimport type { Result } from '../core/result.js';\nimport { NexusError, ErrorCode } from '../core/errors.js';\nimport type { ILogger } from '../core/logger.js';\nimport type { ISQLiteDatabase, ISQLiteStatement } from '../core/types/index.js';\n\n// Re-export for backward compatibility\nexport type { ISQLiteDatabase, ISQLiteStatement };\n\n// ============================================================================\n// Types and Interfaces\n// ============================================================================\n\n/**\n * Importance levels for memory entries.\n */\nexport const MemoryImportance = {\n LOW: 'low',\n MEDIUM: 'medium',\n HIGH: 'high',\n} as const;\n\nexport type MemoryImportance = (typeof MemoryImportance)[keyof typeof MemoryImportance];\n\n/**\n * Zod schema for MemoryImportance validation.\n */\nexport const MemoryImportanceSchema = z.enum(['low', 'medium', 'high']);\n\n/**\n * Metadata associated with a memory entry.\n */\nexport interface MemoryMetadata {\n /** Importance level determining storage strategy */\n importance: MemoryImportance;\n /** Optional tags for categorization */\n tags?: string[];\n /** Time-to-live in milliseconds (optional) */\n ttl?: number;\n}\n\n/**\n * Zod schema for MemoryMetadata validation.\n */\nexport const MemoryMetadataSchema = z.object({\n importance: MemoryImportanceSchema,\n tags: z.array(z.string()).optional(),\n ttl: z.number().positive().optional(),\n});\n\n/**\n * A complete memory entry with all fields.\n */\nexport interface MemoryEntry {\n /** Unique key for the memory */\n key: string;\n /** The stored value (JSON-serializable) */\n value: unknown;\n /** Associated metadata */\n metadata: MemoryMetadata;\n /** When the entry was created */\n createdAt: Date;\n /** When the entry was last accessed */\n accessedAt: Date;\n}\n\n/**\n * Zod schema for MemoryEntry validation.\n */\nexport const MemoryEntrySchema = z.object({\n key: z.string().min(1),\n value: z.unknown(),\n metadata: MemoryMetadataSchema,\n createdAt: z.date(),\n accessedAt: z.date(),\n});\n\n/**\n * Error class for memory operations.\n */\nexport class MemoryError extends NexusError {\n constructor(\n message: string,\n options?: Partial<\n Omit<{ code: ErrorCode; cause?: Error; context?: Record<string, unknown> }, 'code'>\n >\n ) {\n super(message, { code: ErrorCode.INTERNAL_ERROR, ...options });\n this.name = 'MemoryError';\n }\n}\n\n/**\n * Interface for memory backend implementations.\n */\nexport interface IMemoryBackend {\n /**\n * Store a value with associated metadata.\n * @param key - Unique key for the memory\n * @param value - The value to store (must be JSON-serializable)\n * @param metadata - Associated metadata\n */\n store(key: string, value: unknown, metadata: MemoryMetadata): Promise<Result<void, MemoryError>>;\n\n /**\n * Retrieve a value by key.\n * @param key - The key to look up\n * @returns The value or null if not found\n */\n retrieve(key: string): Promise<Result<unknown, MemoryError>>;\n\n /**\n * Search memories using full-text search.\n * @param query - Search query string\n * @param limit - Maximum number of results\n */\n search(query: string, limit: number): Promise<Result<MemoryEntry[], MemoryError>>;\n\n /**\n * Remove memories older than the specified date.\n * @param olderThan - Cutoff date for pruning\n * @returns Number of entries pruned\n */\n prune(olderThan: Date): Promise<Result<number, MemoryError>>;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Configuration for HybridMemoryBackend.\n */\nexport interface HybridMemoryConfig {\n /** Path to SQLite database file */\n dbPath: string;\n /** Directory for Markdown exports */\n markdownDir: string;\n /** Optional logger instance */\n logger?: ILogger;\n /** Whether to auto-expire TTL entries on access (default: true) */\n autoExpire?: boolean;\n}\n\n/**\n * Zod schema for HybridMemoryConfig validation.\n */\nexport const HybridMemoryConfigSchema = z.object({\n dbPath: z.string().min(1),\n markdownDir: z.string().min(1),\n autoExpire: z.boolean().optional(),\n});\n\n// ============================================================================\n// SQLite Types (for better-sqlite3)\n// ============================================================================\n\n/**\n * Row structure in the memories table.\n */\nexport interface MemoryRow {\n key: string;\n value: string;\n metadata: string;\n created_at: number;\n accessed_at: number;\n expires_at: number | null;\n}\n\n// ISQLiteDatabase and ISQLiteStatement imported from core/types/database-types.ts\n// and re-exported above for backward compatibility\n","/**\n * nexus-agents/context - Hybrid Memory Backend\n *\n * Implements hybrid memory storage using SQLite for fast retrieval\n * and full-text search, with Markdown export for human-readable\n * high-importance memories.\n *\n * @module context/memory-backend\n */\n\nimport { z } from 'zod';\nimport type { Result } from '../core/result.js';\nimport { ok, err } from '../core/result.js';\nimport { getTimeProvider, formatZodError } from '../core/index.js';\nimport { ValidationError } from '../core/errors.js';\nimport type { ILogger } from '../core/logger.js';\nimport { createLogger } from '../core/logger.js';\nimport {\n type HybridMemoryConfig,\n type IMemoryBackend,\n type ISQLiteDatabase,\n type MemoryEntry,\n type MemoryMetadata,\n type MemoryRow,\n HybridMemoryConfigSchema,\n MemoryError,\n MemoryImportance,\n MemoryMetadataSchema,\n} from './memory-backend-types.js';\nimport { MemoryMarkdownHelper } from './memory-markdown.js';\nimport {\n sanitizeFtsQuery,\n cleanupExpiredEntries,\n countMemories,\n expireAllEntries,\n pruneOldEntries,\n} from './memory-operations.js';\n\n// Re-export types for convenience\nexport {\n type HybridMemoryConfig,\n type IMemoryBackend,\n type ISQLiteDatabase,\n type ISQLiteStatement,\n type MemoryEntry,\n type MemoryMetadata,\n type MemoryRow,\n HybridMemoryConfigSchema,\n MemoryEntrySchema,\n MemoryError,\n MemoryImportance,\n MemoryImportanceSchema,\n MemoryMetadataSchema,\n} from './memory-backend-types.js';\n\n/**\n * Hybrid memory backend using SQLite for storage and Markdown for export.\n */\nexport class HybridMemoryBackend implements IMemoryBackend {\n private readonly dbPath: string;\n private readonly logger: ILogger;\n private readonly autoExpire: boolean;\n private readonly markdown: MemoryMarkdownHelper;\n private db: ISQLiteDatabase | null = null;\n private initialized = false;\n private initPromise: Promise<Result<void, MemoryError>> | undefined;\n\n constructor(config: HybridMemoryConfig) {\n const validation = HybridMemoryConfigSchema.safeParse(config);\n if (!validation.success) {\n throw new ValidationError(\n `Invalid HybridMemoryBackend config: ${formatZodError(validation.error)}`,\n {\n context: { config, validationErrors: validation.error.issues },\n }\n );\n }\n\n this.dbPath = config.dbPath;\n this.logger = config.logger ?? createLogger({ component: 'HybridMemoryBackend' });\n this.autoExpire = config.autoExpire ?? true;\n this.markdown = new MemoryMarkdownHelper(config.markdownDir, this.logger);\n }\n\n initializeWithDatabase(database: ISQLiteDatabase): void {\n this.db = database;\n this.createTables();\n this.markdown.ensureDir();\n this.initialized = true;\n this.logger.info('HybridMemoryBackend initialized', { dbPath: this.dbPath });\n }\n\n async initialize(): Promise<Result<void, MemoryError>> {\n if (this.initialized) return ok(undefined);\n this.initPromise ??= this.doInitialize().finally(() => {\n this.initPromise = undefined;\n });\n return this.initPromise;\n }\n\n private async doInitialize(): Promise<Result<void, MemoryError>> {\n try {\n const betterSqlite3Module = await import('better-sqlite3').catch((cause: unknown) => {\n this.logger.debug('better-sqlite3 import failed', { error: String(cause) });\n return null;\n });\n if (betterSqlite3Module === null) {\n return err(\n new MemoryError('better-sqlite3 not installed. Install: npm install better-sqlite3', {\n context: { dbPath: this.dbPath },\n })\n );\n }\n\n const Database = betterSqlite3Module.default;\n this.db = new Database(this.dbPath);\n this.createTables();\n this.markdown.ensureDir();\n this.initialized = true;\n this.logger.info('HybridMemoryBackend initialized', { dbPath: this.dbPath });\n return ok(undefined);\n } catch (error) {\n const causeError = error instanceof Error ? error : new Error(String(error));\n this.logger.error('Failed to initialize HybridMemoryBackend', causeError);\n return err(\n new MemoryError('Failed to initialize memory backend', {\n cause: causeError,\n context: { dbPath: this.dbPath },\n })\n );\n }\n }\n\n private createTables(): void {\n const database = this.getDatabase();\n database.exec(`\n CREATE TABLE IF NOT EXISTS memories (\n key TEXT PRIMARY KEY, value TEXT NOT NULL, metadata TEXT NOT NULL,\n created_at INTEGER NOT NULL, accessed_at INTEGER NOT NULL, expires_at INTEGER\n )\n `);\n database.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(\n key, value, tags, content='memories', content_rowid='rowid'\n )\n `);\n database.exec(`\n CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN\n INSERT INTO memories_fts(rowid, key, value, tags)\n SELECT rowid, NEW.key, NEW.value, json_extract(NEW.metadata, '$.tags') FROM memories WHERE key = NEW.key;\n END\n `);\n database.exec(`\n CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN\n INSERT INTO memories_fts(memories_fts, rowid, key, value, tags)\n VALUES('delete', OLD.rowid, OLD.key, OLD.value, json_extract(OLD.metadata, '$.tags'));\n END\n `);\n database.exec(`\n CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN\n INSERT INTO memories_fts(memories_fts, rowid, key, value, tags)\n VALUES('delete', OLD.rowid, OLD.key, OLD.value, json_extract(OLD.metadata, '$.tags'));\n INSERT INTO memories_fts(rowid, key, value, tags)\n SELECT rowid, NEW.key, NEW.value, json_extract(NEW.metadata, '$.tags') FROM memories WHERE key = NEW.key;\n END\n `);\n database.exec(\n `CREATE INDEX IF NOT EXISTS idx_memories_expires_at ON memories(expires_at) WHERE expires_at IS NOT NULL`\n );\n database.exec(`CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at)`);\n this.logger.debug('Database tables created');\n }\n\n private getDatabase(): ISQLiteDatabase {\n if (this.db === null) throw new MemoryError('Database not initialized');\n return this.db;\n }\n\n private ensureInitialized(): void {\n if (!this.initialized || this.db === null) {\n throw new MemoryError('HybridMemoryBackend not initialized. Call initialize() first.');\n }\n }\n\n async store(\n key: string,\n value: unknown,\n metadata: MemoryMetadata\n ): Promise<Result<void, MemoryError>> {\n try {\n this.ensureInitialized();\n const keyValidation = z.string().min(1).safeParse(key);\n if (!keyValidation.success)\n return err(new MemoryError('Invalid key: must be non-empty string', { context: { key } }));\n\n const metadataValidation = MemoryMetadataSchema.safeParse(metadata);\n if (!metadataValidation.success)\n return err(\n new MemoryError('Invalid metadata', {\n context: { metadata, errors: metadataValidation.error.issues },\n })\n );\n\n const now = getTimeProvider().now();\n const expiresAt = metadata.ttl !== undefined ? now + metadata.ttl : null;\n const database = this.getDatabase();\n\n const stmt = database.prepare<MemoryRow>(\n `INSERT OR REPLACE INTO memories (key, value, metadata, created_at, accessed_at, expires_at) VALUES (?, ?, ?, ?, ?, ?)`\n );\n stmt.run(key, JSON.stringify(value), JSON.stringify(metadata), now, now, expiresAt);\n\n this.logger.debug('Stored memory', { key, importance: metadata.importance });\n if (metadata.importance === MemoryImportance.HIGH)\n await this.markdown.write(key, value, metadata, new Date(now));\n\n return ok(undefined);\n } catch (error) {\n const causeError = error instanceof Error ? error : new Error(String(error));\n this.logger.error('Failed to store memory', causeError, { key });\n return err(\n new MemoryError('Failed to store memory', { cause: causeError, context: { key } })\n );\n }\n }\n\n retrieve(key: string): Promise<Result<unknown, MemoryError>> {\n try {\n this.ensureInitialized();\n const database = this.getDatabase();\n const stmt = database.prepare<MemoryRow>(\n `SELECT key, value, metadata, created_at, accessed_at, expires_at FROM memories WHERE key = ?`\n );\n const row = stmt.get(key);\n\n if (row === undefined) return Promise.resolve(ok(null));\n if (this.autoExpire && row.expires_at !== null && row.expires_at < getTimeProvider().now()) {\n database.prepare('DELETE FROM memories WHERE key = ?').run(key);\n this.logger.debug('Auto-expired memory', { key });\n return Promise.resolve(ok(null));\n }\n database\n .prepare('UPDATE memories SET accessed_at = ? WHERE key = ?')\n .run(getTimeProvider().now(), key);\n return Promise.resolve(ok(JSON.parse(row.value) as unknown));\n } catch (error) {\n const causeError = error instanceof Error ? error : new Error(String(error));\n this.logger.error('Failed to retrieve memory', causeError, { key });\n return Promise.resolve(\n err(new MemoryError('Failed to retrieve memory', { cause: causeError, context: { key } }))\n );\n }\n }\n\n search(query: string, limit: number): Promise<Result<MemoryEntry[], MemoryError>> {\n try {\n this.ensureInitialized();\n if (limit <= 0 || limit > 1000)\n return Promise.resolve(\n err(new MemoryError('Invalid limit: must be between 1 and 1000', { context: { limit } }))\n );\n\n const sanitizedQuery = sanitizeFtsQuery(query);\n if (sanitizedQuery.length === 0) return Promise.resolve(ok([]));\n\n const database = this.getDatabase();\n const stmt = database.prepare<MemoryRow>(`\n SELECT m.key, m.value, m.metadata, m.created_at, m.accessed_at, m.expires_at\n FROM memories m INNER JOIN memories_fts fts ON m.rowid = fts.rowid\n WHERE memories_fts MATCH ? ORDER BY rank LIMIT ?\n `);\n const rows = stmt.all(sanitizedQuery, limit);\n const { entries } = cleanupExpiredEntries(rows, database, this.autoExpire, this.logger);\n return Promise.resolve(ok(entries));\n } catch (error) {\n const causeError = error instanceof Error ? error : new Error(String(error));\n this.logger.error('Failed to search memories', causeError, { query });\n return Promise.resolve(\n err(\n new MemoryError('Failed to search memories', {\n cause: causeError,\n context: { query, limit },\n })\n )\n );\n }\n }\n\n prune(olderThan: Date): Promise<Result<number, MemoryError>> {\n this.ensureInitialized();\n return Promise.resolve(pruneOldEntries(this.getDatabase(), olderThan, this.logger));\n }\n\n expireAll(): Promise<Result<number, MemoryError>> {\n this.ensureInitialized();\n return Promise.resolve(expireAllEntries(this.getDatabase(), this.logger));\n }\n\n delete(key: string): Promise<Result<boolean, MemoryError>> {\n try {\n this.ensureInitialized();\n const result = this.getDatabase().prepare('DELETE FROM memories WHERE key = ?').run(key);\n if (result.changes > 0) {\n this.markdown.delete(key);\n this.logger.debug('Deleted memory', { key });\n return Promise.resolve(ok(true));\n }\n return Promise.resolve(ok(false));\n } catch (error) {\n const causeError = error instanceof Error ? error : new Error(String(error));\n this.logger.error('Failed to delete memory', causeError, { key });\n return Promise.resolve(\n err(new MemoryError('Failed to delete memory', { cause: causeError, context: { key } }))\n );\n }\n }\n\n getAll(limit = 100): Promise<Result<MemoryEntry[], MemoryError>> {\n try {\n this.ensureInitialized();\n const database = this.getDatabase();\n const rows = database\n .prepare<MemoryRow>(\n `SELECT key, value, metadata, created_at, accessed_at, expires_at FROM memories ORDER BY accessed_at DESC LIMIT ?`\n )\n .all(limit);\n const { entries } = cleanupExpiredEntries(rows, database, this.autoExpire, this.logger);\n return Promise.resolve(ok(entries));\n } catch (error) {\n const causeError = error instanceof Error ? error : new Error(String(error));\n this.logger.error('Failed to get all memories', causeError);\n return Promise.resolve(\n err(new MemoryError('Failed to get all memories', { cause: causeError }))\n );\n }\n }\n\n count(): Promise<Result<number, MemoryError>> {\n this.ensureInitialized();\n return Promise.resolve(countMemories(this.getDatabase()));\n }\n\n close(): void {\n if (this.db !== null) {\n this.db.close();\n this.db = null;\n this.initialized = false;\n this.logger.info('HybridMemoryBackend closed');\n }\n }\n}\n","/**\n * nexus-agents/context - Memory Markdown Helper\n *\n * Handles Markdown file export for high-importance memories.\n *\n * @module context/memory-markdown\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { ILogger } from '../core/logger.js';\nimport type { MemoryMetadata } from './memory-backend-types.js';\n\n/**\n * Helper class for Markdown memory file operations.\n */\nexport class MemoryMarkdownHelper {\n constructor(\n private readonly markdownDir: string,\n private readonly logger: ILogger\n ) {}\n\n /**\n * Ensure the Markdown export directory exists.\n */\n ensureDir(): void {\n if (!fs.existsSync(this.markdownDir)) {\n fs.mkdirSync(this.markdownDir, { recursive: true });\n this.logger.debug('Created Markdown directory', { path: this.markdownDir });\n }\n }\n\n /**\n * Write a memory entry to Markdown file.\n */\n async write(\n key: string,\n value: unknown,\n metadata: MemoryMetadata,\n createdAt: Date\n ): Promise<void> {\n const filename = this.keyToFilename(key);\n const filepath = path.join(this.markdownDir, filename);\n\n const content = this.format(key, value, metadata, createdAt);\n\n try {\n await fs.promises.writeFile(filepath, content, 'utf-8');\n this.logger.debug('Wrote Markdown file', { key, filepath });\n } catch (error) {\n this.logger.warn('Failed to write Markdown file', { key, filepath, error });\n // Don't throw - Markdown export is secondary to SQLite storage\n }\n }\n\n /**\n * Delete a Markdown file for a memory.\n */\n delete(key: string): void {\n const filename = this.keyToFilename(key);\n const filepath = path.join(this.markdownDir, filename);\n\n try {\n if (fs.existsSync(filepath)) {\n fs.unlinkSync(filepath);\n this.logger.debug('Deleted Markdown file', { key, filepath });\n }\n } catch (error) {\n this.logger.warn('Failed to delete Markdown file', { key, filepath, error });\n }\n }\n\n /**\n * Convert a memory key to a safe filename.\n */\n private keyToFilename(key: string): string {\n // Replace unsafe characters with underscores\n const safeKey = key\n .replace(/[^a-zA-Z0-9-_]/g, '_')\n .replace(/_+/g, '_')\n .substring(0, 200); // Limit filename length\n\n return `${safeKey}.md`;\n }\n\n /**\n * Format a memory entry as Markdown.\n */\n private format(key: string, value: unknown, metadata: MemoryMetadata, createdAt: Date): string {\n const lines: string[] = [\n `# Memory: ${key}`,\n '',\n '## Metadata',\n '',\n `- **Importance:** ${metadata.importance}`,\n `- **Created:** ${createdAt.toISOString()}`,\n ];\n\n if (metadata.tags !== undefined && metadata.tags.length > 0) {\n lines.push(`- **Tags:** ${metadata.tags.join(', ')}`);\n }\n\n if (metadata.ttl !== undefined) {\n const expiresAt = new Date(createdAt.getTime() + metadata.ttl);\n lines.push(`- **Expires:** ${expiresAt.toISOString()}`);\n }\n\n lines.push('', '## Value', '');\n\n // Format value based on type\n if (typeof value === 'string') {\n lines.push(value);\n } else if (value === null) {\n lines.push('`null`');\n } else if (typeof value === 'object') {\n lines.push('```json', JSON.stringify(value, null, 2), '```');\n } else {\n // For primitives (number, boolean, etc.), convert to string representation\n const stringValue =\n typeof value === 'number' || typeof value === 'boolean'\n ? String(value)\n : JSON.stringify(value);\n lines.push(`\\`${stringValue}\\``);\n }\n\n lines.push('');\n\n return lines.join('\\n');\n }\n}\n","/**\n * nexus-agents/context - Memory Operations\n *\n * Query and mutation operations for the hybrid memory backend.\n *\n * @module context/memory-operations\n */\n\nimport type { Result } from '../core/result.js';\nimport { ok, err } from '../core/result.js';\nimport { getTimeProvider } from '../core/index.js';\nimport type { ILogger } from '../core/logger.js';\nimport type {\n ISQLiteDatabase,\n MemoryEntry,\n MemoryMetadata,\n MemoryRow,\n} from './memory-backend-types.js';\nimport { MemoryError } from './memory-backend-types.js';\n\n/** Safely parse JSON from a DB column, returning null on corrupt data. */\nfunction safeParseJson(raw: string): unknown {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return null;\n }\n}\n\n/** Parse metadata JSON or return safe defaults for corrupt rows. */\nfunction parseMetadataOrDefault(raw: string): MemoryMetadata {\n const parsed = safeParseJson(raw);\n if (parsed !== null && typeof parsed === 'object') return parsed as MemoryMetadata;\n return { importance: 'medium' };\n}\n\n/**\n * Converts a database row to a MemoryEntry.\n * Gracefully handles corrupt JSON in DB rows (#1680 quality scan).\n */\nexport function rowToEntry(row: MemoryRow): MemoryEntry {\n return {\n key: row.key,\n value: safeParseJson(row.value),\n metadata: parseMetadataOrDefault(row.metadata),\n createdAt: new Date(row.created_at),\n accessedAt: new Date(row.accessed_at),\n };\n}\n\n/**\n * Sanitizes a query string for FTS5.\n * Removes special FTS5 operators to prevent injection.\n */\nexport function sanitizeFtsQuery(query: string): string {\n return query\n .replace(/[*:^\"(){}[\\]]/g, ' ')\n .replace(/\\bAND\\b/gi, ' ')\n .replace(/\\bOR\\b/gi, ' ')\n .replace(/\\bNOT\\b/gi, ' ')\n .replace(/\\bNEAR\\b/gi, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/**\n * Cleans up expired entries from the results.\n */\nexport function cleanupExpiredEntries(\n rows: MemoryRow[],\n database: ISQLiteDatabase,\n autoExpire: boolean,\n logger: ILogger\n): { entries: MemoryEntry[]; expiredCount: number } {\n const now = getTimeProvider().now();\n const entries: MemoryEntry[] = [];\n const expiredKeys: string[] = [];\n\n for (const row of rows) {\n if (autoExpire && row.expires_at !== null && row.expires_at < now) {\n expiredKeys.push(row.key);\n continue;\n }\n entries.push(rowToEntry(row));\n }\n\n if (expiredKeys.length > 0) {\n const deleteStmt = database.prepare(\n `DELETE FROM memories WHERE key IN (${expiredKeys.map(() => '?').join(',')})`\n );\n deleteStmt.run(...expiredKeys);\n logger.debug('Auto-expired memories', { count: expiredKeys.length });\n }\n\n return { entries, expiredCount: expiredKeys.length };\n}\n\n/**\n * Executes a search query against FTS5.\n */\nexport function executeSearch(\n database: ISQLiteDatabase,\n sanitizedQuery: string,\n limit: number,\n autoExpire: boolean,\n logger: ILogger\n): Result<MemoryEntry[], MemoryError> {\n try {\n const stmt = database.prepare<MemoryRow>(`\n SELECT m.key, m.value, m.metadata, m.created_at, m.accessed_at, m.expires_at\n FROM memories m\n INNER JOIN memories_fts fts ON m.rowid = fts.rowid\n WHERE memories_fts MATCH ?\n ORDER BY rank\n LIMIT ?\n `);\n\n const rows = stmt.all(sanitizedQuery, limit);\n const { entries } = cleanupExpiredEntries(rows, database, autoExpire, logger);\n\n return ok(entries);\n } catch (error) {\n const causeError = error instanceof Error ? error : new Error(String(error));\n return err(new MemoryError('Failed to execute search', { cause: causeError }));\n }\n}\n\n/**\n * Retrieves all memories with pagination.\n */\nexport function getAllMemories(\n database: ISQLiteDatabase,\n limit: number,\n autoExpire: boolean,\n logger: ILogger\n): Result<MemoryEntry[], MemoryError> {\n try {\n const stmt = database.prepare<MemoryRow>(`\n SELECT key, value, metadata, created_at, accessed_at, expires_at\n FROM memories ORDER BY accessed_at DESC LIMIT ?\n `);\n\n const rows = stmt.all(limit);\n const { entries } = cleanupExpiredEntries(rows, database, autoExpire, logger);\n\n return ok(entries);\n } catch (error) {\n const causeError = error instanceof Error ? error : new Error(String(error));\n return err(new MemoryError('Failed to get all memories', { cause: causeError }));\n }\n}\n\n/**\n * Counts total memories in the database.\n */\nexport function countMemories(database: ISQLiteDatabase): Result<number, MemoryError> {\n try {\n const stmt = database.prepare<{ count: number }>('SELECT COUNT(*) as count FROM memories');\n const row = stmt.get();\n return ok(row?.count ?? 0);\n } catch (error) {\n const causeError = error instanceof Error ? error : new Error(String(error));\n return err(new MemoryError('Failed to count memories', { cause: causeError }));\n }\n}\n\n/**\n * Expires all entries that have passed their TTL.\n */\nexport function expireAllEntries(\n database: ISQLiteDatabase,\n logger: ILogger\n): Result<number, MemoryError> {\n try {\n const stmt = database.prepare(\n 'DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?'\n );\n const result = stmt.run(getTimeProvider().now());\n logger.info('Expired memories', { count: result.changes });\n return ok(result.changes);\n } catch (error) {\n const causeError = error instanceof Error ? error : new Error(String(error));\n return err(new MemoryError('Failed to expire memories', { cause: causeError }));\n }\n}\n\n/**\n * Prunes entries older than the specified date.\n */\nexport function pruneOldEntries(\n database: ISQLiteDatabase,\n olderThan: Date,\n logger: ILogger\n): Result<number, MemoryError> {\n try {\n const stmt = database.prepare('DELETE FROM memories WHERE created_at < ?');\n const result = stmt.run(olderThan.getTime());\n logger.info('Pruned old memories', {\n olderThan: olderThan.toISOString(),\n count: result.changes,\n });\n return ok(result.changes);\n } catch (error) {\n const causeError = error instanceof Error ? error : new Error(String(error));\n return err(\n new MemoryError('Failed to prune memories', {\n cause: causeError,\n context: { olderThan: olderThan.toISOString() },\n })\n );\n }\n}\n","/**\n * Adaptive Memory Types\n *\n * Type definitions for adaptive memory with priority-based retrieval\n * combining recency decay, importance weighting, and context relevance.\n *\n * @module context/adaptive-memory-types\n * (Source: Issue #143, arXiv:2310.08560)\n */\n\nimport { z } from 'zod';\nimport type { Result } from '../core/result.js';\nimport type { MemoryEntry, MemoryError, IMemoryBackend } from './memory-backend-types.js';\n\n// ============================================================================\n// Scoring Configuration\n// ============================================================================\n\n/**\n * Weights for combining priority score components.\n * All weights should sum to 1.0 for normalized scoring.\n */\nexport interface ScoringWeights {\n /** Weight for recency score (0-1) */\n readonly recency: number;\n /** Weight for importance score (0-1) */\n readonly importance: number;\n /** Weight for relevance score (0-1) */\n readonly relevance: number;\n}\n\n/** Base Zod schema for ScoringWeights (without sum validation). */\nconst ScoringWeightsBaseSchema = z.object({\n recency: z.number().min(0).max(1),\n importance: z.number().min(0).max(1),\n relevance: z.number().min(0).max(1),\n});\n\n/** Zod schema for ScoringWeights validation (with sum check). */\nexport const ScoringWeightsSchema = ScoringWeightsBaseSchema.refine(\n (w) => Math.abs(w.recency + w.importance + w.relevance - 1.0) < 0.001,\n { message: 'Weights must sum to 1.0' }\n);\n\n/** Partial ScoringWeights schema for overrides. */\nexport const PartialScoringWeightsSchema = ScoringWeightsBaseSchema.partial();\n\n/**\n * Configuration for importance level weights.\n */\nexport interface ImportanceWeights {\n /** Score for LOW importance (0-1) */\n readonly low: number;\n /** Score for MEDIUM importance (0-1) */\n readonly medium: number;\n /** Score for HIGH importance (0-1) */\n readonly high: number;\n}\n\n/** Zod schema for ImportanceWeights validation. */\nexport const ImportanceWeightsSchema = z.object({\n low: z.number().min(0).max(1),\n medium: z.number().min(0).max(1),\n high: z.number().min(0).max(1),\n});\n\n/**\n * Configuration for recency decay.\n */\nexport interface DecayConfig {\n /** Half-life in milliseconds (time for score to decay by 50%) */\n readonly halfLifeMs: number;\n /** Minimum recency score (floor, prevents zero scores) */\n readonly minScore: number;\n}\n\n/** Zod schema for DecayConfig validation. */\nexport const DecayConfigSchema = z.object({\n halfLifeMs: z.number().positive(),\n minScore: z.number().min(0).max(1),\n});\n\n// ============================================================================\n// Priority Score\n// ============================================================================\n\n/**\n * Components of a priority score.\n */\nexport interface PriorityScoreComponents {\n /** Recency score (0-1) based on time since last access */\n readonly recency: number;\n /** Importance score (0-1) based on importance level */\n readonly importance: number;\n /** Relevance score (0-1) based on query similarity */\n readonly relevance: number;\n}\n\n/**\n * Complete priority score with components.\n */\nexport interface PriorityScore {\n /** Final combined score */\n readonly score: number;\n /** Individual score components */\n readonly components: PriorityScoreComponents;\n}\n\n/** Zod schema for PriorityScore validation. */\nexport const PriorityScoreSchema = z.object({\n score: z.number().min(0),\n components: z.object({\n recency: z.number().min(0).max(1),\n importance: z.number().min(0).max(1),\n relevance: z.number().min(0).max(1),\n }),\n});\n\n// ============================================================================\n// Scored Memory Entry\n// ============================================================================\n\n/**\n * A memory entry with its priority score.\n */\nexport interface ScoredMemoryEntry {\n /** The memory entry */\n readonly entry: MemoryEntry;\n /** The priority score */\n readonly priority: PriorityScore;\n}\n\n/** Zod schema for ScoredMemoryEntry validation. */\nexport const ScoredMemoryEntrySchema = z.object({\n entry: z.object({\n key: z.string(),\n value: z.unknown(),\n metadata: z.object({\n importance: z.enum(['low', 'medium', 'high']),\n tags: z.array(z.string()).optional(),\n ttl: z.number().optional(),\n }),\n createdAt: z.date(),\n accessedAt: z.date(),\n }),\n priority: PriorityScoreSchema,\n});\n\n// ============================================================================\n// Retrieval Options\n// ============================================================================\n\n/**\n * Options for priority-based retrieval.\n */\nexport interface PriorityRetrievalOptions {\n /** Query string for relevance scoring (optional) */\n readonly query?: string;\n /** Maximum number of results */\n readonly limit?: number;\n /** Minimum priority score threshold */\n readonly minScore?: number;\n /** Override scoring weights for this query */\n readonly weights?: Partial<ScoringWeights>;\n /** Filter by importance levels */\n readonly importanceFilter?: readonly ('low' | 'medium' | 'high')[];\n /** Filter by tags (entries must have at least one matching tag) */\n readonly tagFilter?: readonly string[];\n}\n\n/** Zod schema for PriorityRetrievalOptions validation. */\nexport const PriorityRetrievalOptionsSchema = z.object({\n query: z.string().optional(),\n limit: z.number().int().positive().optional(),\n minScore: z.number().min(0).optional(),\n weights: PartialScoringWeightsSchema.optional(),\n importanceFilter: z.array(z.enum(['low', 'medium', 'high'])).optional(),\n tagFilter: z.array(z.string()).optional(),\n});\n\n// ============================================================================\n// Adaptive Memory Interface\n// ============================================================================\n\n/**\n * Extended memory backend with adaptive priority-based retrieval.\n */\nexport interface IAdaptiveMemory extends IMemoryBackend {\n /**\n * Retrieve memories sorted by priority score.\n * @param opts - Retrieval options\n */\n retrieveByPriority(\n opts?: PriorityRetrievalOptions\n ): Promise<Result<ScoredMemoryEntry[], MemoryError>>;\n\n /**\n * Get the priority score for a specific memory entry.\n * @param key - Memory key\n * @param query - Optional query for relevance scoring\n */\n getPriorityScore(key: string, query?: string): Promise<Result<PriorityScore, MemoryError>>;\n\n /**\n * Boost the priority of a memory entry by updating its access time.\n * @param key - Memory key\n */\n touch(key: string): Promise<Result<void, MemoryError>>;\n\n /**\n * Get the current scoring configuration.\n */\n getScoringConfig(): ScoringConfig;\n\n /**\n * Update the scoring configuration.\n * @param config - Partial configuration to update\n */\n updateScoringConfig(config: Partial<ScoringConfig>): void;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Complete scoring configuration.\n */\nexport interface ScoringConfig {\n /** Weights for combining score components */\n readonly weights: ScoringWeights;\n /** Importance level score mappings */\n readonly importanceWeights: ImportanceWeights;\n /** Recency decay configuration */\n readonly decay: DecayConfig;\n}\n\n/** Zod schema for ScoringConfig validation. */\nexport const ScoringConfigSchema = z.object({\n weights: ScoringWeightsSchema,\n importanceWeights: ImportanceWeightsSchema,\n decay: DecayConfigSchema,\n});\n\n/**\n * Configuration for AdaptiveMemoryBackend.\n */\nexport interface AdaptiveMemoryConfig {\n /** Path to SQLite database file */\n readonly dbPath: string;\n /** Directory for Markdown exports */\n readonly markdownDir: string;\n /** Scoring configuration (optional, uses defaults if not provided) */\n readonly scoring?: Partial<ScoringConfig>;\n /** Whether to auto-expire TTL entries (default: true) */\n readonly autoExpire?: boolean;\n}\n\n/** Zod schema for AdaptiveMemoryConfig validation. */\nexport const AdaptiveMemoryConfigSchema = z.object({\n dbPath: z.string().min(1),\n markdownDir: z.string().min(1),\n scoring: ScoringConfigSchema.partial().optional(),\n autoExpire: z.boolean().optional(),\n});\n\n// ============================================================================\n// Default Configuration\n// ============================================================================\n\n/** Default scoring weights (balanced). */\nexport const DEFAULT_SCORING_WEIGHTS: ScoringWeights = {\n recency: 0.4,\n importance: 0.3,\n relevance: 0.3,\n};\n\n/** Default importance weights. */\nexport const DEFAULT_IMPORTANCE_WEIGHTS: ImportanceWeights = {\n low: 0.25,\n medium: 0.5,\n high: 1.0,\n};\n\n/** Default decay configuration (24-hour half-life). */\nexport const DEFAULT_DECAY_CONFIG: DecayConfig = {\n halfLifeMs: 24 * 60 * 60 * 1000, // 24 hours\n minScore: 0.1,\n};\n\n/** Default scoring configuration. */\nexport const DEFAULT_SCORING_CONFIG: ScoringConfig = {\n weights: DEFAULT_SCORING_WEIGHTS,\n importanceWeights: DEFAULT_IMPORTANCE_WEIGHTS,\n decay: DEFAULT_DECAY_CONFIG,\n};\n","/**\n * nexus-agents/utils - Similarity Utilities\n *\n * Shared utility functions for text similarity calculations.\n * Consolidates duplicate similarity code from multiple modules per ADR-0013.\n *\n * Used by:\n * - context/adaptive-memory-helpers.ts (relevance scoring)\n * - agents/orchestration/policy-feature-extraction.ts (stuck detection)\n *\n * @module utils/similarity-utils\n * @see docs/adr/0013-memory-helpers-consolidation.md\n */\n\n// ============================================================================\n// Token-Based Similarity\n// ============================================================================\n\n/**\n * Calculate token overlap score (query coverage).\n *\n * Measures what fraction of query tokens appear in the target.\n * Score = |query ∩ target| / |query|\n *\n * @param queryTokens - Tokens to find matches for\n * @param targetTokens - Tokens to search in\n * @returns Score between 0 (no overlap) and 1 (full coverage)\n *\n * @example\n * ```ts\n * calculateTokenOverlap(['foo', 'bar'], ['bar', 'baz'])\n * // Returns 0.5 (1 match / 2 query tokens)\n * ```\n */\nexport function calculateTokenOverlap(queryTokens: string[], targetTokens: string[]): number {\n if (queryTokens.length === 0) return 0;\n if (targetTokens.length === 0) return 0;\n\n const targetSet = new Set(targetTokens);\n let matches = 0;\n\n for (const token of queryTokens) {\n if (targetSet.has(token)) matches++;\n }\n\n return matches / queryTokens.length;\n}\n\n/**\n * Calculate set overlap count.\n *\n * Returns the number of elements that appear in both sets.\n * |A ∩ B|\n *\n * @param sourceSet - First set\n * @param targetSet - Second set\n * @returns Number of overlapping elements\n */\nexport function calculateSetOverlapCount<T>(sourceSet: Set<T>, targetSet: Set<T>): number {\n let count = 0;\n for (const item of sourceSet) {\n if (targetSet.has(item)) count++;\n }\n return count;\n}\n\n// ============================================================================\n// Jaccard Similarity\n// ============================================================================\n\n/**\n * Calculate Jaccard similarity between two sets.\n *\n * Jaccard = |A ∩ B| / |A ∪ B|\n *\n * @param set1 - First set\n * @param set2 - Second set\n * @returns Score between 0 (no overlap) and 1 (identical sets)\n *\n * @example\n * ```ts\n * calculateJaccardSimilarity(new Set(['a', 'b']), new Set(['b', 'c']))\n * // Returns 0.333 (1 intersection / 3 union)\n * ```\n */\nexport function calculateJaccardSimilarity<T>(set1: Set<T>, set2: Set<T>): number {\n if (set1.size === 0 && set2.size === 0) return 1;\n if (set1.size === 0 || set2.size === 0) return 0;\n\n const intersection = calculateSetOverlapCount(set1, set2);\n const union = set1.size + set2.size - intersection;\n\n return union > 0 ? intersection / union : 0;\n}\n\n/**\n * Calculate Jaccard similarity between two strings using word tokenization.\n *\n * @param text1 - First text\n * @param text2 - Second text\n * @returns Score between 0 (no overlap) and 1 (identical word sets)\n */\nexport function calculateTextJaccardSimilarity(text1: string, text2: string): number {\n const words1 = new Set(\n text1\n .toLowerCase()\n .split(/\\s+/)\n .filter((w) => w.length > 0)\n );\n const words2 = new Set(\n text2\n .toLowerCase()\n .split(/\\s+/)\n .filter((w) => w.length > 0)\n );\n\n return calculateJaccardSimilarity(words1, words2);\n}\n\n// ============================================================================\n// Similarity Comparison Helpers\n// ============================================================================\n\n/**\n * Check if two texts are highly similar (above threshold).\n *\n * Useful for stuck/loop detection in orchestration.\n *\n * @param text1 - First text\n * @param text2 - Second text\n * @param threshold - Similarity threshold (default: 0.8)\n * @returns True if Jaccard similarity >= threshold\n */\nexport function areTextsSimilar(text1: string, text2: string, threshold = 0.8): boolean {\n return calculateTextJaccardSimilarity(text1, text2) >= threshold;\n}\n\n/**\n * Find the maximum pairwise similarity among a list of texts.\n *\n * @param texts - List of texts to compare\n * @returns Maximum similarity score between any two adjacent texts\n */\nexport function calculateMaxPairwiseSimilarity(texts: string[]): number {\n if (texts.length < 2) return 0;\n\n let maxSimilarity = 0;\n\n for (let i = 1; i < texts.length; i++) {\n const prev = texts[i - 1];\n const curr = texts[i];\n if (prev !== undefined && curr !== undefined) {\n const similarity = calculateTextJaccardSimilarity(prev, curr);\n if (similarity > maxSimilarity) {\n maxSimilarity = similarity;\n }\n }\n }\n\n return maxSimilarity;\n}\n","/**\n * nexus-agents/utils - Memory Database Utilities\n *\n * Shared utility functions for memory database operations.\n * Consolidates duplicate code from multiple memory systems per ADR-0013.\n *\n * Used by:\n * - context/adaptive-memory-helpers.ts\n * - context/graph-memory-helpers.ts\n *\n * @module utils/memory-db-utils\n * @see docs/adr/0013-memory-helpers-consolidation.md\n */\n\nimport {\n type MemoryEntry,\n type MemoryRow,\n type ISQLiteDatabase,\n MemoryImportance,\n} from '../context/memory-backend-types.js';\nimport { createLogger } from '../core/index.js';\n\nconst logger = createLogger({ component: 'MemoryDbUtils' });\n\n// ============================================================================\n// Row Conversion\n// ============================================================================\n\n/**\n * Safely parse JSON, returning fallback on corrupt data instead of throwing.\n */\nfunction safeJsonParse<T>(json: string, fallback: T, context: string): T {\n try {\n return JSON.parse(json) as T;\n } catch {\n logger.warn('Corrupt JSON in memory database row', { context });\n return fallback;\n }\n}\n\n/**\n * Convert a database MemoryRow to a MemoryEntry.\n *\n * Parses JSON fields (value, metadata) and converts timestamps to Date objects.\n * Handles corrupt JSON gracefully by returning safe defaults.\n *\n * @param row - Database row from memories table\n * @returns Parsed MemoryEntry object\n */\nexport function memoryRowToEntry(row: MemoryRow): MemoryEntry {\n return {\n key: row.key,\n value: safeJsonParse<unknown>(row.value, row.value, `value for key=\"${row.key}\"`),\n metadata: safeJsonParse<MemoryEntry['metadata']>(\n row.metadata,\n { importance: MemoryImportance.MEDIUM },\n `metadata for key=\"${row.key}\"`\n ),\n createdAt: new Date(row.created_at),\n accessedAt: new Date(row.accessed_at),\n };\n}\n\n// ============================================================================\n// Existence Check\n// ============================================================================\n\n/**\n * Check if a memory key exists in the database.\n *\n * @param db - SQLite database instance\n * @param key - Memory key to check\n * @returns true if key exists, false otherwise\n */\nexport function memoryExists(db: ISQLiteDatabase, key: string): boolean {\n const stmt = db.prepare<{ count: number }>(\n 'SELECT COUNT(*) as count FROM memories WHERE key = ?'\n );\n const result = stmt.get(key);\n return result !== undefined && result.count > 0;\n}\n\n// ============================================================================\n// Memory Retrieval\n// ============================================================================\n\n/**\n * Get a memory entry by key.\n *\n * @param db - SQLite database instance\n * @param key - Memory key to retrieve\n * @returns MemoryEntry if found, undefined otherwise\n */\nexport function getMemoryEntry(db: ISQLiteDatabase, key: string): MemoryEntry | undefined {\n const stmt = db.prepare<MemoryRow>('SELECT * FROM memories WHERE key = ?');\n const row = stmt.get(key);\n return row !== undefined ? memoryRowToEntry(row) : undefined;\n}\n\n/**\n * Get a single memory row by key.\n *\n * @param db - SQLite database instance\n * @param key - Memory key to retrieve\n * @returns MemoryRow if found, undefined otherwise\n */\nexport function getMemoryRow(db: ISQLiteDatabase, key: string): MemoryRow | undefined {\n const stmt = db.prepare<MemoryRow>('SELECT * FROM memories WHERE key = ?');\n return stmt.get(key);\n}\n\n/**\n * Get all memory rows from the database with limit.\n *\n * @param db - SQLite database instance\n * @param limit - Maximum number of rows to return\n * @returns Array of MemoryRow objects\n */\nexport function getAllMemoryRows(db: ISQLiteDatabase, limit: number): MemoryRow[] {\n const stmt = db.prepare<MemoryRow>('SELECT * FROM memories ORDER BY accessed_at DESC LIMIT ?');\n return stmt.all(limit);\n}\n","/**\n * Adaptive Memory Helpers\n *\n * Helper functions for adaptive memory scoring including recency decay,\n * importance weighting, and relevance calculation.\n *\n * @module context/adaptive-memory-helpers\n * (Source: Issue #143, arXiv:2310.08560)\n */\n\nimport { getTimeProvider } from '../core/index.js';\nimport type { MemoryEntry, MemoryRow, ISQLiteDatabase } from './memory-backend-types.js';\nimport { MemoryImportance } from './memory-backend-types.js';\nimport type {\n ScoringConfig,\n PriorityScore,\n PriorityScoreComponents,\n ScoredMemoryEntry,\n PriorityRetrievalOptions,\n ScoringWeights,\n} from './adaptive-memory-types.js';\nimport { DEFAULT_SCORING_CONFIG } from './adaptive-memory-types.js';\n// Shared utilities per ADR-0013\nimport {\n tokenize as sharedTokenize,\n stringifyValue as sharedStringifyValue,\n} from '../utils/text-utils.js';\nimport { calculateTokenOverlap } from '../utils/similarity-utils.js';\nimport { memoryRowToEntry as sharedMemoryRowToEntry } from '../utils/memory-db-utils.js';\n\n// ============================================================================\n// Recency Scoring\n// ============================================================================\n\n/**\n * Calculate recency score using exponential decay.\n * Score = max(minScore, e^(-λt)) where λ = ln(2) / halfLife\n *\n * @param accessedAt - Last access time\n * @param now - Current time\n * @param halfLifeMs - Half-life in milliseconds\n * @param minScore - Minimum score floor\n */\nexport function calculateRecencyScore(\n accessedAt: Date,\n now: Date,\n halfLifeMs: number,\n minScore: number\n): number {\n const elapsedMs = now.getTime() - accessedAt.getTime();\n if (elapsedMs <= 0) return 1.0;\n\n // λ = ln(2) / halfLife\n const lambda = Math.LN2 / halfLifeMs;\n const decayedScore = Math.exp(-lambda * elapsedMs);\n\n return Math.max(minScore, decayedScore);\n}\n\n// ============================================================================\n// Importance Scoring\n// ============================================================================\n\n/**\n * Calculate importance score based on importance level.\n *\n * @param importance - Memory importance level\n * @param config - Scoring configuration\n */\nexport function calculateImportanceScore(importance: string, config: ScoringConfig): number {\n switch (importance) {\n case MemoryImportance.HIGH:\n return config.importanceWeights.high;\n case MemoryImportance.MEDIUM:\n return config.importanceWeights.medium;\n case MemoryImportance.LOW:\n return config.importanceWeights.low;\n default:\n return config.importanceWeights.medium;\n }\n}\n\n// ============================================================================\n// Relevance Scoring\n// ============================================================================\n\n/**\n * Calculate relevance score between query and memory value.\n * Uses token overlap scoring via shared similarity-utils (ADR-0013).\n *\n * @param query - Search query\n * @param value - Memory value (stringified)\n */\nexport function calculateRelevanceScore(query: string | undefined, value: string): number {\n if (query === undefined || query.trim() === '') return 1.0;\n\n const queryTokens = tokenize(query);\n const valueTokens = tokenize(value);\n\n if (queryTokens.length === 0 || valueTokens.length === 0) return 0.5;\n\n // Use shared utility for overlap calculation (ADR-0013)\n return calculateTokenOverlap(queryTokens, valueTokens);\n}\n\n/**\n * Tokenize a string into lowercase words.\n * Uses shared utility from utils/text-utils.ts per ADR-0013.\n */\nfunction tokenize(text: string): string[] {\n return sharedTokenize(text, 1); // Use minLength=1 to match original filter(t.length > 0)\n}\n\n// ============================================================================\n// Combined Priority Scoring\n// ============================================================================\n\n/**\n * Configuration for priority calculation.\n */\nexport interface PriorityCalculationConfig {\n readonly entry: MemoryEntry;\n readonly query?: string;\n readonly now: Date;\n readonly config: ScoringConfig;\n readonly weightOverrides?: Partial<ScoringWeights>;\n}\n\n/**\n * Calculate combined priority score for a memory entry.\n */\nexport function calculatePriorityScore(input: PriorityCalculationConfig): PriorityScore {\n const { entry, query, now, config, weightOverrides } = input;\n\n // Calculate individual components\n const recency = calculateRecencyScore(\n entry.accessedAt,\n now,\n config.decay.halfLifeMs,\n config.decay.minScore\n );\n const importance = calculateImportanceScore(entry.metadata.importance, config);\n const relevance = calculateRelevanceScore(query, stringifyValue(entry.value));\n\n const components: PriorityScoreComponents = { recency, importance, relevance };\n\n // Apply weights\n const weights = resolveWeights(config.weights, weightOverrides);\n const score =\n recency * weights.recency + importance * weights.importance + relevance * weights.relevance;\n\n return { score, components };\n}\n\n/**\n * Stringify a value for relevance scoring.\n * Uses shared utility from utils/text-utils.ts per ADR-0013.\n */\nfunction stringifyValue(value: unknown): string {\n return sharedStringifyValue(value);\n}\n\n/**\n * Resolve weights with optional overrides.\n */\nfunction resolveWeights(base: ScoringWeights, overrides?: Partial<ScoringWeights>): ScoringWeights {\n if (overrides === undefined) return base;\n\n const merged = {\n recency: overrides.recency ?? base.recency,\n importance: overrides.importance ?? base.importance,\n relevance: overrides.relevance ?? base.relevance,\n };\n\n // Normalize if overrides don't sum to 1\n const sum = merged.recency + merged.importance + merged.relevance;\n if (sum === 0) return base;\n if (Math.abs(sum - 1.0) > 0.001) {\n return {\n recency: merged.recency / sum,\n importance: merged.importance / sum,\n relevance: merged.relevance / sum,\n };\n }\n\n return merged;\n}\n\n// ============================================================================\n// Filtering\n// ============================================================================\n\n/**\n * Configuration for filtering scored entries.\n */\nexport interface FilterConfig {\n readonly minScore?: number;\n readonly importanceFilter?: readonly string[];\n readonly tagFilter?: readonly string[];\n}\n\n/**\n * Filter scored entries based on options.\n */\nexport function filterScoredEntries(\n entries: ScoredMemoryEntry[],\n config: FilterConfig\n): ScoredMemoryEntry[] {\n return entries.filter((e) => {\n // Score threshold\n if (config.minScore !== undefined && e.priority.score < config.minScore) {\n return false;\n }\n\n // Importance filter\n if (config.importanceFilter !== undefined && config.importanceFilter.length > 0) {\n if (!config.importanceFilter.includes(e.entry.metadata.importance)) {\n return false;\n }\n }\n\n // Tag filter (at least one matching tag)\n if (config.tagFilter !== undefined && config.tagFilter.length > 0) {\n const entryTags = e.entry.metadata.tags ?? [];\n const hasMatch = config.tagFilter.some((t) => entryTags.includes(t));\n if (!hasMatch) return false;\n }\n\n return true;\n });\n}\n\n// ============================================================================\n// Database Queries\n// ============================================================================\n\n/**\n * Update the accessed_at timestamp for a memory.\n */\nexport function touchMemory(db: ISQLiteDatabase, key: string): boolean {\n const stmt = db.prepare('UPDATE memories SET accessed_at = ? WHERE key = ?');\n const result = stmt.run(getTimeProvider().now(), key);\n return result.changes > 0;\n}\n\n// ============================================================================\n// Scoring Pipeline\n// ============================================================================\n\n/**\n * Score all entries and return sorted by priority.\n */\nexport function scoreAndSortEntries(\n rows: MemoryRow[],\n opts: PriorityRetrievalOptions | undefined,\n config: ScoringConfig\n): ScoredMemoryEntry[] {\n const now = new Date(getTimeProvider().now());\n\n // Convert and score\n const scored: ScoredMemoryEntry[] = rows.map((row) => {\n const entry = sharedMemoryRowToEntry(row);\n const priority = calculatePriorityScore({\n entry,\n now,\n config,\n ...(opts?.query !== undefined && { query: opts.query }),\n ...(opts?.weights !== undefined && { weightOverrides: opts.weights }),\n });\n return { entry, priority };\n });\n\n // Filter - build config with only defined properties\n const filterConfig: FilterConfig = {\n ...(opts?.minScore !== undefined && { minScore: opts.minScore }),\n ...(opts?.importanceFilter !== undefined && { importanceFilter: opts.importanceFilter }),\n ...(opts?.tagFilter !== undefined && { tagFilter: opts.tagFilter }),\n };\n const filtered = filterScoredEntries(scored, filterConfig);\n\n // Sort by score descending\n filtered.sort((a, b) => b.priority.score - a.priority.score);\n\n // Apply limit\n const limit = opts?.limit ?? 100;\n return filtered.slice(0, limit);\n}\n\n// ============================================================================\n// Configuration Merging\n// ============================================================================\n\n/**\n * Merge partial scoring config with defaults.\n */\nexport function mergeScoringConfig(partial?: Partial<ScoringConfig>): ScoringConfig {\n if (partial === undefined) return DEFAULT_SCORING_CONFIG;\n\n return {\n weights: partial.weights ?? DEFAULT_SCORING_CONFIG.weights,\n importanceWeights: partial.importanceWeights ?? DEFAULT_SCORING_CONFIG.importanceWeights,\n decay: partial.decay ?? DEFAULT_SCORING_CONFIG.decay,\n };\n}\n","/**\n * Adaptive Memory Backend\n *\n * Implements adaptive memory with priority-based retrieval combining\n * recency decay, importance weighting, and context relevance.\n *\n * @module context/adaptive-memory\n * (Source: Issue #143, arXiv:2310.08560)\n */\n\nimport type { Result } from '../core/result.js';\nimport { ok, err } from '../core/result.js';\nimport { getTimeProvider } from '../core/index.js';\nimport type { ILogger } from '../core/logger.js';\nimport { createLogger } from '../core/logger.js';\nimport type { MemoryEntry, MemoryMetadata, ISQLiteDatabase } from './memory-backend-types.js';\nimport { MemoryError } from './memory-backend-types.js';\nimport { HybridMemoryBackend } from './memory-backend.js';\nimport type {\n IAdaptiveMemory,\n AdaptiveMemoryConfig,\n ScoringConfig,\n PriorityScore,\n ScoredMemoryEntry,\n PriorityRetrievalOptions,\n} from './adaptive-memory-types.js';\nimport { AdaptiveMemoryConfigSchema } from './adaptive-memory-types.js';\nimport {\n mergeScoringConfig,\n scoreAndSortEntries,\n touchMemory,\n calculatePriorityScore,\n} from './adaptive-memory-helpers.js';\n// Shared utilities per ADR-0013\nimport {\n getAllMemoryRows,\n getMemoryRow,\n memoryExists,\n memoryRowToEntry,\n} from '../utils/memory-db-utils.js';\n\n// Re-export types\nexport type {\n IAdaptiveMemory,\n AdaptiveMemoryConfig,\n ScoringConfig,\n PriorityScore,\n ScoredMemoryEntry,\n PriorityRetrievalOptions,\n ScoringWeights,\n ImportanceWeights,\n DecayConfig,\n PriorityScoreComponents,\n} from './adaptive-memory-types.js';\nexport { DEFAULT_SCORING_CONFIG } from './adaptive-memory-types.js';\n\nconst logger = createLogger({ component: 'AdaptiveMemoryBackend' });\n\n/**\n * Adaptive memory backend with priority-based retrieval.\n */\nexport class AdaptiveMemoryBackend implements IAdaptiveMemory {\n private readonly config: AdaptiveMemoryConfig;\n private readonly log: ILogger;\n private readonly base: HybridMemoryBackend;\n private scoringConfig: ScoringConfig;\n private db: ISQLiteDatabase | null = null;\n private initialized = false;\n private initPromise: Promise<Result<void, MemoryError>> | undefined;\n\n constructor(config: AdaptiveMemoryConfig) {\n const validation = AdaptiveMemoryConfigSchema.safeParse(config);\n if (!validation.success) {\n const msg = validation.error.issues\n .map((i) => `${i.path.join('.')}: ${i.message}`)\n .join('; ');\n throw new MemoryError(`Invalid AdaptiveMemoryBackend config: ${msg}`);\n }\n this.config = config;\n this.log = logger;\n this.scoringConfig = mergeScoringConfig(config.scoring);\n this.base = new HybridMemoryBackend({ dbPath: config.dbPath, markdownDir: config.markdownDir });\n }\n\n async initialize(): Promise<Result<void, MemoryError>> {\n if (this.initialized) return ok(undefined);\n this.initPromise ??= this.doInitialize().finally(() => {\n this.initPromise = undefined;\n });\n return this.initPromise;\n }\n\n private async doInitialize(): Promise<Result<void, MemoryError>> {\n const baseInit = await this.base.initialize();\n if (!baseInit.ok) return baseInit;\n\n try {\n const mod = await import('better-sqlite3').catch((cause: unknown) => {\n this.log.debug('better-sqlite3 import failed', { error: String(cause) });\n return null;\n });\n if (mod === null)\n return err(\n new MemoryError('better-sqlite3 not installed. Install: npm install better-sqlite3')\n );\n const Database = mod.default;\n this.db = new Database(this.config.dbPath);\n this.initialized = true;\n this.log.info('AdaptiveMemoryBackend initialized');\n return ok(undefined);\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n return err(new MemoryError('Failed to initialize adaptive backend', { cause }));\n }\n }\n\n initializeWithDatabase(database: ISQLiteDatabase): void {\n this.base.initializeWithDatabase(database);\n this.db = database;\n this.initialized = true;\n this.log.info('AdaptiveMemoryBackend initialized with database');\n }\n\n private getDb(): ISQLiteDatabase {\n if (this.db === null) throw new MemoryError('Database not initialized');\n return this.db;\n }\n\n private ensureInit(): void {\n if (!this.initialized) throw new MemoryError('AdaptiveMemoryBackend not initialized');\n }\n\n // =========================================================================\n // IMemoryBackend Methods (delegated to base)\n // =========================================================================\n\n store(key: string, value: unknown, metadata: MemoryMetadata): Promise<Result<void, MemoryError>> {\n return this.base.store(key, value, metadata);\n }\n\n retrieve(key: string): Promise<Result<unknown, MemoryError>> {\n return this.base.retrieve(key);\n }\n\n search(query: string, limit: number): Promise<Result<MemoryEntry[], MemoryError>> {\n return this.base.search(query, limit);\n }\n\n prune(olderThan: Date): Promise<Result<number, MemoryError>> {\n return this.base.prune(olderThan);\n }\n\n // =========================================================================\n // IAdaptiveMemory Methods\n // =========================================================================\n\n retrieveByPriority(\n opts?: PriorityRetrievalOptions\n ): Promise<Result<ScoredMemoryEntry[], MemoryError>> {\n try {\n this.ensureInit();\n const db = this.getDb();\n\n // Get all rows (with a reasonable limit for initial fetch)\n const maxFetch = (opts?.limit ?? 100) * 2;\n const rows = getAllMemoryRows(db, maxFetch);\n\n // Score, filter, and sort\n const scored = scoreAndSortEntries(rows, opts, this.scoringConfig);\n\n this.log.debug('Retrieved by priority', { count: scored.length, query: opts?.query });\n return Promise.resolve(ok(scored));\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n return Promise.resolve(err(new MemoryError('Failed to retrieve by priority', { cause })));\n }\n }\n\n getPriorityScore(key: string, query?: string): Promise<Result<PriorityScore, MemoryError>> {\n try {\n this.ensureInit();\n const db = this.getDb();\n\n if (!memoryExists(db, key)) {\n return Promise.resolve(err(new MemoryError(`Key not found: ${key}`)));\n }\n\n const row = getMemoryRow(db, key);\n if (row === undefined) {\n return Promise.resolve(err(new MemoryError(`Key not found: ${key}`)));\n }\n\n const entry = memoryRowToEntry(row);\n const priority = calculatePriorityScore({\n entry,\n now: new Date(getTimeProvider().now()),\n config: this.scoringConfig,\n ...(query !== undefined && { query }),\n });\n\n return Promise.resolve(ok(priority));\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n return Promise.resolve(err(new MemoryError('Failed to get priority score', { cause })));\n }\n }\n\n touch(key: string): Promise<Result<void, MemoryError>> {\n try {\n this.ensureInit();\n const db = this.getDb();\n\n if (!memoryExists(db, key)) {\n return Promise.resolve(err(new MemoryError(`Key not found: ${key}`)));\n }\n\n const updated = touchMemory(db, key);\n if (!updated) {\n return Promise.resolve(err(new MemoryError(`Failed to touch: ${key}`)));\n }\n\n this.log.debug('Touched memory', { key });\n return Promise.resolve(ok(undefined));\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n return Promise.resolve(err(new MemoryError('Failed to touch memory', { cause })));\n }\n }\n\n getScoringConfig(): ScoringConfig {\n return this.scoringConfig;\n }\n\n updateScoringConfig(config: Partial<ScoringConfig>): void {\n this.scoringConfig = mergeScoringConfig({ ...this.scoringConfig, ...config });\n this.log.info('Updated scoring config', { config: this.scoringConfig });\n }\n\n // =========================================================================\n // Lifecycle\n // =========================================================================\n\n close(): void {\n this.base.close();\n if (this.db !== null) {\n this.db.close();\n this.db = null;\n }\n this.initialized = false;\n this.log.info('AdaptiveMemoryBackend closed');\n }\n}\n\n/** Create an AdaptiveMemoryBackend instance. */\nexport function createAdaptiveMemory(config: AdaptiveMemoryConfig): AdaptiveMemoryBackend {\n return new AdaptiveMemoryBackend(config);\n}\n"],"mappings":"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","names":["z","logger","logger","logger","z","z","tokenize","stringifyValue","logger"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/opencode-bridge.ts","../src/adapters/openai-compat-adapter.ts","../src/learning/usage-log.ts"],"sourcesContent":["/**\n * opencode.json gateway-config bridge (#2503, child 3 of epic #2500).\n *\n * When nexus-agents runs as an MCP loaded by OpenCode (typically inside a\n * Docker sandbox), OpenCode's `opencode.json` already declares the\n * OpenAI-compatible gateway the harness uses. Reading it from there saves\n * the operator from re-declaring the same `baseURL` + `apiKey` as\n * NEXUS_OPENAI_COMPAT_* env vars.\n *\n * Scope (per direction discussion 2026-05-09):\n * - **Only** reads `providers.openai-compat.options.{baseURL, apiKey}`.\n * Does NOT read OpenCode's MCP config, model list, logging, or other\n * settings. nexus-agents' own behaviour stays driven by\n * `nexus-agents.yaml` + `NEXUS_*` env vars.\n * - Resolves OpenCode's `{env:VAR}` interpolation in the apiKey field.\n * - Returns `null` on any failure path (missing file, parse error,\n * missing fields, unset interpolated env var) — caller falls back to\n * env-var precedence; we don't throw.\n *\n * Precedence (applied in `openai-compat-adapter.ts`):\n * 1. `NEXUS_OPENAI_COMPAT_URL` + `NEXUS_OPENAI_COMPAT_KEY` env vars (if both set)\n * 2. `NEXUS_OPENCODE_CONFIG` → `opencode.json` → `providers.openai-compat`\n * 3. Unconfigured → `null`, no adapter registered\n *\n * Sanitised logging: never log the resolved apiKey. Only the baseURL is\n * logged on success.\n *\n * @module config/opencode-bridge\n */\n\nimport { readFileSync } from 'node:fs';\n\nimport { createLogger } from '../core/index.js';\n\nconst logger = createLogger({ component: 'opencode-bridge' });\n\nexport interface OpencodeGatewayConfig {\n readonly baseURL: string;\n readonly apiKey: string;\n}\n\n/**\n * Read `providers.openai-compat.options.{baseURL, apiKey}` from the given\n * opencode.json path. Returns `null` on any failure — the caller falls\n * back to env-var precedence.\n */\nexport function readOpencodeGateway(path: string): OpencodeGatewayConfig | null {\n const raw = readFileOrNull(path);\n if (raw === null) return null;\n\n const parsed = parseJsonOrNull(raw, path);\n if (parsed === null) return null;\n\n const options = extractOpenAICompatOptions(parsed);\n if (options === null) {\n logger.debug('opencode.json has no providers.openai-compat.options block', { path });\n return null;\n }\n\n const baseURL = typeof options.baseURL === 'string' ? options.baseURL.trim() : '';\n const apiKeyRaw = typeof options.apiKey === 'string' ? options.apiKey.trim() : '';\n if (baseURL === '' || apiKeyRaw === '') {\n logger.warn('opencode.json providers.openai-compat missing baseURL or apiKey', { path });\n return null;\n }\n\n const apiKey = resolveEnvInterpolation(apiKeyRaw);\n if (apiKey === null) {\n logger.warn(\n 'opencode.json apiKey references an env var that is not set; gateway not configured',\n { path }\n );\n return null;\n }\n\n logger.info('Gateway config sourced from opencode.json', { baseURL, path });\n return { baseURL, apiKey };\n}\n\nfunction readFileOrNull(path: string): string | null {\n try {\n return readFileSync(path, 'utf8');\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug('Could not read opencode.json', { path, error: msg });\n return null;\n }\n}\n\nfunction parseJsonOrNull(raw: string, path: string): unknown {\n try {\n return JSON.parse(raw);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.warn('opencode.json is not valid JSON; ignoring', { path, error: msg });\n return null;\n }\n}\n\n/**\n * Pull `providers.openai-compat.options` out of the parsed JSON without\n * coupling to a Zod schema. OpenCode's config has many keys we don't\n * care about; we only navigate to the one we need.\n */\nfunction extractOpenAICompatOptions(\n parsed: unknown\n): { baseURL?: unknown; apiKey?: unknown } | null {\n if (typeof parsed !== 'object' || parsed === null) return null;\n const root = parsed as { providers?: unknown };\n const providers = root.providers;\n if (typeof providers !== 'object' || providers === null) return null;\n const provider = (providers as Record<string, unknown>)['openai-compat'];\n if (typeof provider !== 'object' || provider === null) return null;\n const options = (provider as { options?: unknown }).options;\n if (typeof options !== 'object' || options === null) return null;\n return options;\n}\n\n/**\n * Resolve OpenCode's `{env:VAR}` interpolation. Only the literal pattern\n * is supported (no shell expansion, no defaults). Returns the string as-is\n * when no interpolation is present; returns `null` when the referenced\n * env var is not set.\n */\nfunction resolveEnvInterpolation(value: string): string | null {\n const match = /^\\{env:([A-Z0-9_]+)\\}$/.exec(value);\n if (match === null) return value;\n const envName = match[1];\n if (envName === undefined) return null;\n const resolved = process.env[envName];\n if (resolved === undefined || resolved === '') return null;\n return resolved;\n}\n","/**\n * OpenAI-compatible gateway adapter — talk to any HTTP gateway that exposes\n * the OpenAI Chat Completions API. The gateway may itself be a multi-model\n * router (Bedrock/Vertex/Azure proxy, OpenRouter, vLLM, etc.). nexus-agents\n * sees one adapter, the gateway exposes N models, and the existing routing\n * pipeline picks among them.\n *\n * Source: Issue #2468 (epic #2467 child).\n *\n * Configuration precedence (#2503, child 3 of epic #2500):\n * 1. NEXUS_OPENAI_COMPAT_URL + NEXUS_OPENAI_COMPAT_KEY env vars (both required)\n * 2. NEXUS_OPENCODE_CONFIG path → opencode.json → providers.openai-compat\n * 3. Unconfigured → adapter not built\n *\n * Models are discovered via GET {base}/v1/models at first use. Each model\n * the gateway exposes can be selected by ID; the adapter wraps the existing\n * `OpenAIAdapter` for the actual chat-completions request, so streaming +\n * tool use + the full IModelAdapter contract come for free.\n */\n\nimport OpenAI from 'openai';\n\nimport type {\n Result,\n CompletionRequest,\n CompletionResponse,\n ModelError,\n ModelMetadata,\n IModelAdapter,\n} from '../core/index.js';\nimport { ok, err, ConfigError, getErrorMessage, getTimeProvider } from '../core/index.js';\nimport { OpenAIAdapter } from './openai-adapter.js';\nimport { recordUsageEvent, computeCostUSD } from '../learning/usage-log.js';\nimport { readOpencodeGateway } from '../config/opencode-bridge.js';\n\nexport interface OpenAICompatConfig {\n /** Gateway base URL — must reach `/v1/models` and `/v1/chat/completions`. */\n readonly baseUrl: string;\n /** API key the gateway expects. */\n readonly apiKey: string;\n}\n\nexport interface DiscoveredModel {\n readonly id: string;\n /** Unix epoch seconds when the model was created (per OpenAI API). */\n readonly created?: number;\n /** Owning organization or upstream provider name. */\n readonly ownedBy?: string;\n}\n\n/**\n * Read the gateway config with the precedence chain documented in the\n * module docstring: env vars > opencode.json > unconfigured.\n *\n * The env-var path (#2468) wins when both `NEXUS_OPENAI_COMPAT_URL` and\n * `NEXUS_OPENAI_COMPAT_KEY` are set. Otherwise, when `NEXUS_OPENCODE_CONFIG`\n * names a path, the opencode.json bridge tries to source the gateway from\n * `providers.openai-compat.options.{baseURL, apiKey}` (#2503). Returns\n * `null` when neither path yields a config — caller treats unset gateway\n * as \"no adapter from this source.\"\n */\nexport function readOpenAICompatEnv(): OpenAICompatConfig | null {\n const fromEnv = readGatewayFromEnv();\n if (fromEnv !== null) return fromEnv;\n return readGatewayFromOpencode();\n}\n\nfunction readGatewayFromEnv(): OpenAICompatConfig | null {\n const envUrl = process.env['NEXUS_OPENAI_COMPAT_URL']?.trim();\n const envKey = process.env['NEXUS_OPENAI_COMPAT_KEY']?.trim();\n if (envUrl === undefined || envUrl === '') return null;\n if (envKey === undefined || envKey === '') return null;\n return { baseUrl: envUrl, apiKey: envKey };\n}\n\nfunction readGatewayFromOpencode(): OpenAICompatConfig | null {\n const opencodePath = process.env['NEXUS_OPENCODE_CONFIG']?.trim();\n if (opencodePath === undefined || opencodePath === '') return null;\n const fromFile = readOpencodeGateway(opencodePath);\n if (fromFile === null) return null;\n return { baseUrl: fromFile.baseURL, apiKey: fromFile.apiKey };\n}\n\n/**\n * Discover available models by calling `GET {baseUrl}/v1/models`. Uses the\n * official `openai` SDK's `client.models.list()` so we benefit from its\n * pagination + retry handling. The list is the strongly authoritative\n * source: nexus-agents won't try to dispatch to a model the gateway doesn't\n * expose.\n */\nexport async function discoverModels(\n config: OpenAICompatConfig\n): Promise<Result<readonly DiscoveredModel[], ConfigError>> {\n try {\n const client = new OpenAI({ baseURL: config.baseUrl, apiKey: config.apiKey });\n const list = await client.models.list();\n const models: readonly DiscoveredModel[] = list.data.map((m) => ({\n id: m.id,\n created: m.created,\n ownedBy: m.owned_by,\n }));\n return ok(models);\n } catch (e: unknown) {\n return err(\n new ConfigError(\n `Failed to discover models from ${config.baseUrl}: ${getErrorMessage(e)}. ` +\n `Verify NEXUS_OPENAI_COMPAT_URL and NEXUS_OPENAI_COMPAT_KEY, then retry.`\n )\n );\n }\n}\n\n/**\n * Create an OpenAIAdapter pointed at the gateway for a specific model ID,\n * wrapped with usage recording so every completion appends a UsageEvent\n * to the JSONL log consumed by `nexus-agents usage`.\n *\n * The wrapper is transparent — same IModelAdapter contract, same fields,\n * same error handling. Recording is best-effort (telemetry never fails\n * the user's call).\n *\n * When invoked via MCP, the host harness's model identifier is passed\n * through verbatim — nexus-agents doesn't second-guess what the host is\n * already routing.\n */\nexport function createOpenAICompatAdapter(\n modelId: string,\n config: OpenAICompatConfig\n): IModelAdapter {\n const inner = new OpenAIAdapter({ modelId, apiKey: config.apiKey, baseUrl: config.baseUrl });\n return withUsageRecording(inner);\n}\n\n/**\n * Wrap any IModelAdapter so that successful + failed `complete()` calls\n * append a UsageEvent to the on-disk usage log. Stream calls aren't yet\n * instrumented (a future PR can add streaming-aware recording).\n *\n * The returned object preserves the IModelAdapter contract identically;\n * downstream code can't tell the difference except that one extra JSONL\n * line gets written per call.\n */\nfunction withUsageRecording(inner: IModelAdapter): IModelAdapter {\n const wrapped: IModelAdapter = {\n providerId: inner.providerId,\n modelId: inner.modelId,\n capabilities: inner.capabilities,\n countTokens: (text) => inner.countTokens(text),\n validateConfig: () => inner.validateConfig(),\n stream: (request) => inner.stream(request),\n async complete(request: CompletionRequest): Promise<Result<CompletionResponse, ModelError>> {\n const start = getTimeProvider().now();\n const result = await inner.complete(request);\n const latencyMs = getTimeProvider().now() - start;\n try {\n if (result.ok) {\n const u = result.value.usage;\n recordUsageEvent({\n timestamp: new Date().toISOString(),\n modelId: inner.modelId,\n providerId: inner.providerId,\n inputTokens: u.inputTokens,\n outputTokens: u.outputTokens,\n usdCost: computeCostUSD(inner.modelId, u.inputTokens, u.outputTokens),\n latencyMs,\n success: true,\n });\n } else {\n recordUsageEvent({\n timestamp: new Date().toISOString(),\n modelId: inner.modelId,\n providerId: inner.providerId,\n inputTokens: 0,\n outputTokens: 0,\n usdCost: 0,\n latencyMs,\n success: false,\n errorCode: result.error.code,\n });\n }\n } catch {\n // Telemetry must not break user calls.\n }\n return result;\n },\n };\n attachListModels(wrapped, inner);\n return wrapped;\n}\n\n/**\n * (#2540) Forward `listModels` through the wrapper when the inner adapter\n * exposes one. Only attach when defined so the wrapper's `listModels?:`\n * hint stays accurate for the resolver. The inner reference is captured\n * by closure so the forwarded call binds `this` to the inner adapter.\n */\nfunction attachListModels(wrapped: IModelAdapter, inner: IModelAdapter): void {\n const list = inner.listModels?.bind(inner);\n if (list === undefined) return;\n wrapped.listModels = (): Promise<readonly ModelMetadata[]> => list();\n}\n\n/**\n * Convenience: read env, discover, return adapter instances for every\n * discovered model. Returns `null` (not an error) when env vars aren't set\n * — the caller treats unset gateway as \"no adapter from this source.\"\n *\n * Use case: the unified registry / factory calls this at startup; if the\n * operator has configured a gateway, every discovered model becomes a\n * dispatch target alongside the existing claude/codex/gemini/opencode\n * adapter slots.\n */\nexport async function buildOpenAICompatAdapters(): Promise<Result<\n readonly IModelAdapter[],\n ConfigError\n> | null> {\n const config = readOpenAICompatEnv();\n if (config === null) return null;\n const discovered = await discoverModels(config);\n if (!discovered.ok) return discovered;\n return ok(discovered.value.map((m) => createOpenAICompatAdapter(m.id, config)));\n}\n","/**\n * usage-log — append-only per-call usage events with cost.\n *\n * Source: Issue #2469 (epic #2467 child).\n *\n * For operators running against metered API gateways, per-call cost +\n * tokens + latency is the data they need to manage spend. This module\n * provides three things:\n *\n * 1. `recordUsageEvent(event)` — append a per-call record to a JSONL\n * log under <NEXUS_DATA_DIR>/usage/usage-YYYY-MM.jsonl.\n * 2. `loadUsageEvents({...})` — read events for a window, filtered\n * by model / category.\n * 3. `computeCostUSD(modelId, inputTokens, outputTokens)` — compute\n * cost from `config/model-capabilities.ts` pricing.\n *\n * The `usage` CLI command (cli/usage-command.ts) consumes this for the\n * operator dashboard. Existing OutcomeStore is intentionally untouched\n * — its schema is for routing/learning signals, not billing.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nimport { getNexusDataDir } from '../config/nexus-data-dir.js';\nimport { lookupInTreeCapability } from '../config/model-config-helpers.js';\n\nexport interface UsageEvent {\n /** ISO 8601 timestamp of the call. */\n readonly timestamp: string;\n /** Model identifier (e.g., 'claude-sonnet-4', 'gpt-4o'). */\n readonly modelId: string;\n /** Provider/adapter (e.g., 'anthropic', 'openai', 'openai-compat'). */\n readonly providerId: string;\n /** Token counts. */\n readonly inputTokens: number;\n readonly outputTokens: number;\n /** Cost in USD. Computed at write time from pricing in model-capabilities. */\n readonly usdCost: number;\n /** Wall-clock latency in milliseconds. */\n readonly latencyMs: number;\n /** Whether the call succeeded. */\n readonly success: boolean;\n /**\n * Optional task category — populated when the call was made on behalf of\n * a routed task (so aggregation can roll up by category).\n */\n readonly category?: string;\n /** Optional failure code when success === false. */\n readonly errorCode?: string;\n}\n\n/**\n * Compute cost in USD given a model and token counts. Returns 0 when the\n * model has no pricing data (e.g., free local model, gateway-routed model\n * we don't have rates for). Operators with custom gateways can extend\n * `config/model-capabilities.ts` to add their pricing.\n */\nexport function computeCostUSD(modelId: string, inputTokens: number, outputTokens: number): number {\n const cap = lookupInTreeCapability(modelId);\n if (cap === undefined) return 0;\n const inputPer1M = cap.pricing?.inputPer1M ?? 0;\n const outputPer1M = cap.pricing?.outputPer1M ?? 0;\n // Multiply token counts by per-million rate then divide. Use Math.round\n // at micro-USD precision so JSONL files don't drift to floating-point\n // noise on small calls.\n const microUsd = Math.round(\n inputTokens * inputPer1M + outputTokens * outputPer1M // micro-USD per million scaled\n );\n return microUsd / 1_000_000;\n}\n\n/** Resolve the active usage log path for the current month. */\nexport function getUsageLogPath(date: Date = new Date()): string {\n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n return join(getNexusDataDir(), 'usage', `usage-${String(year)}-${month}.jsonl`);\n}\n\n/**\n * Append a usage event to the current month's log. Best-effort — failures\n * are silent (we don't want to fail a successful model call because we\n * couldn't write a log line).\n */\nexport function recordUsageEvent(event: UsageEvent): void {\n try {\n const path = getUsageLogPath(new Date(event.timestamp));\n mkdirSync(dirname(path), { recursive: true });\n appendFileSync(path, `${JSON.stringify(event)}\\n`, 'utf-8');\n } catch {\n // Intentionally silent — telemetry must not break user calls.\n }\n}\n\nexport interface LoadUsageOptions {\n /** Restrict to events at or after this ISO timestamp. */\n readonly sinceIso?: string;\n /** Restrict to events before this ISO timestamp. */\n readonly untilIso?: string;\n /** Only events for this model. */\n readonly modelId?: string;\n /** Only events for this category. */\n readonly category?: string;\n}\n\nfunction listUsageFiles(dir: string): readonly string[] {\n if (!existsSync(dir)) return [];\n try {\n return readdirSync(dir).filter((f) => f.startsWith('usage-') && f.endsWith('.jsonl'));\n } catch {\n return [];\n }\n}\n\ninterface LoadFilter {\n readonly sinceMs: number;\n readonly untilMs: number;\n readonly modelId: string | undefined;\n readonly category: string | undefined;\n}\n\nfunction eventMatches(parsed: UsageEvent, f: LoadFilter): boolean {\n const ts = Date.parse(parsed.timestamp);\n if (ts < f.sinceMs || ts >= f.untilMs) return false;\n if (f.modelId !== undefined && parsed.modelId !== f.modelId) return false;\n if (f.category !== undefined && parsed.category !== f.category) return false;\n return true;\n}\n\nfunction parseFileLines(filePath: string, filter: LoadFilter): readonly UsageEvent[] {\n let content: string;\n try {\n content = readFileSync(filePath, 'utf-8');\n } catch {\n return [];\n }\n const out: UsageEvent[] = [];\n for (const line of content.split('\\n')) {\n if (line.trim() === '') continue;\n try {\n const parsed = JSON.parse(line) as UsageEvent;\n if (eventMatches(parsed, filter)) out.push(parsed);\n } catch {\n // Skip malformed line; keep reading.\n continue;\n }\n }\n return out;\n}\n\n/**\n * Load all usage events from disk that match the filter. Reads every\n * monthly log file under the data dir; for sub-second filtering at scale\n * a future PR can index by month, but linear scan is fine at the\n * \"operator dashboard\" scale this command targets.\n */\nexport function loadUsageEvents(opts: LoadUsageOptions = {}): readonly UsageEvent[] {\n const dir = join(getNexusDataDir(), 'usage');\n const files = listUsageFiles(dir);\n if (files.length === 0) return [];\n const filter: LoadFilter = {\n sinceMs: opts.sinceIso !== undefined ? Date.parse(opts.sinceIso) : Number.NEGATIVE_INFINITY,\n untilMs: opts.untilIso !== undefined ? Date.parse(opts.untilIso) : Number.POSITIVE_INFINITY,\n modelId: opts.modelId,\n category: opts.category,\n };\n const events: UsageEvent[] = [];\n for (const f of files) {\n events.push(...parseFileLines(join(dir, f), filter));\n }\n return events;\n}\n\nexport interface ModelRollup {\n readonly modelId: string;\n readonly providerId: string;\n readonly callCount: number;\n readonly successCount: number;\n readonly successRate: number;\n readonly totalInputTokens: number;\n readonly totalOutputTokens: number;\n readonly totalUsdCost: number;\n readonly avgLatencyMs: number;\n readonly costPerSuccessUsd: number;\n}\n\n/**\n * Aggregate events into per-model rollups. Sorted by total cost descending\n * — the model burning the most money at top. Useful for \"where is my spend\n * going?\" investigations.\n */\nexport function rollupByModel(events: readonly UsageEvent[]): readonly ModelRollup[] {\n const groups = new Map<string, UsageEvent[]>();\n for (const e of events) {\n const arr = groups.get(e.modelId);\n if (arr === undefined) groups.set(e.modelId, [e]);\n else arr.push(e);\n }\n const rollups: ModelRollup[] = [];\n for (const [modelId, group] of groups) {\n const callCount = group.length;\n const successCount = group.filter((e) => e.success).length;\n const totalInputTokens = group.reduce((s, e) => s + e.inputTokens, 0);\n const totalOutputTokens = group.reduce((s, e) => s + e.outputTokens, 0);\n const totalUsdCost = group.reduce((s, e) => s + e.usdCost, 0);\n const totalLatency = group.reduce((s, e) => s + e.latencyMs, 0);\n const successRate = callCount === 0 ? 0 : successCount / callCount;\n const avgLatencyMs = callCount === 0 ? 0 : totalLatency / callCount;\n const costPerSuccessUsd = successCount === 0 ? totalUsdCost : totalUsdCost / successCount;\n rollups.push({\n modelId,\n providerId: group[0]?.providerId ?? 'unknown',\n callCount,\n successCount,\n successRate,\n totalInputTokens,\n totalOutputTokens,\n totalUsdCost,\n avgLatencyMs,\n costPerSuccessUsd,\n });\n }\n return rollups.sort((a, b) => b.totalUsdCost - a.totalUsdCost);\n}\n"],"mappings":"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","names":["err","readFileSync","readFileSync"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcp/tools/scanner-registry-fetcher.ts","../src/mcp/tools/repo-security-plan-fallback.ts","../src/mcp/tools/repo-security-plan.ts"],"sourcesContent":["/**\n * nexus-agents/mcp - Scanner Registry Fetcher\n *\n * Fetches the scanner-registry.json manifest from the\n * vulnerability-scanner-registry GitHub Releases at runtime.\n * Uses a TTL cache and falls back to embedded data on failure.\n *\n * @module mcp/tools/scanner-registry-fetcher\n * (Source: Consensus vote — externalize scanner registry, 6-0 unanimous)\n */\n\nimport { z } from 'zod';\nimport { createLogger, getTimeProvider } from '../../core/index.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** A scanner entry from the registry manifest. */\nexport interface RegistryScanner {\n readonly name: string;\n readonly displayName: string;\n readonly categories: readonly string[];\n readonly license: string;\n readonly pricingModel: string;\n readonly relationships?: readonly RegistryRelationship[] | undefined;\n}\n\n/** A relationship edge between scanners. */\nexport interface RegistryRelationship {\n readonly target: string;\n readonly type: 'uses' | 'supersedes' | 'bundles' | 'competes-with';\n}\n\n/** Language matrix: category → scanner names. */\nexport interface LanguageMatrixEntry {\n readonly sast?: readonly string[] | undefined;\n readonly sca?: readonly string[] | undefined;\n readonly secrets?: readonly string[] | undefined;\n readonly container?: readonly string[] | undefined;\n readonly iac?: readonly string[] | undefined;\n readonly dast?: readonly string[] | undefined;\n}\n\n/** The full registry manifest shape. */\nexport interface ScannerRegistryManifest {\n readonly version: string;\n readonly generatedAt: string;\n readonly scanners: readonly RegistryScanner[];\n readonly languageMatrix: Readonly<Record<string, LanguageMatrixEntry>>;\n}\n\n// ============================================================================\n// Zod Schema for Validation\n// ============================================================================\n\nconst RelationshipSchema = z.object({\n target: z.string().min(1),\n type: z.enum(['uses', 'supersedes', 'bundles', 'competes-with']),\n});\n\nconst ScannerSchema = z.object({\n name: z.string().min(1),\n displayName: z.string().min(1),\n categories: z.array(z.string().min(1)),\n license: z.string().min(1),\n pricingModel: z.string().min(1),\n relationships: z.array(RelationshipSchema).optional(),\n});\n\nconst LanguageMatrixEntrySchema = z\n .object({\n sast: z.array(z.string()).optional(),\n sca: z.array(z.string()).optional(),\n secrets: z.array(z.string()).optional(),\n container: z.array(z.string()).optional(),\n iac: z.array(z.string()).optional(),\n dast: z.array(z.string()).optional(),\n })\n .loose();\n\nconst ManifestSchema = z.object({\n version: z.string().min(1),\n generatedAt: z.string().min(1),\n scanners: z.array(ScannerSchema),\n languageMatrix: z.record(z.string().max(50), LanguageMatrixEntrySchema),\n});\n\n// ============================================================================\n// Cache\n// ============================================================================\n\ninterface CacheEntry {\n manifest: ScannerRegistryManifest;\n fetchedAt: number;\n releaseTag: string;\n}\n\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\nlet cachedEntry: CacheEntry | null = null;\n\n/** Inflight fetch promise for probe coalescing (#1448). */\nlet inflightFetch: Promise<ScannerRegistryManifest | null> | undefined;\n\n/** Clear the cache and inflight state (for testing). */\nexport function clearRegistryCache(): void {\n cachedEntry = null;\n inflightFetch = undefined;\n}\n\n// ============================================================================\n// Fetcher\n// ============================================================================\n\nconst REGISTRY_REPO = 'williamzujkowski/vulnerability-scanner-registry';\nconst FETCH_TIMEOUT_MS = 10_000;\n\n/** Promisified execFile signature used by fetcher helpers. */\ntype ExecFileAsync = (\n file: string,\n args: readonly string[],\n options: { timeout?: number; maxBuffer?: number }\n) => Promise<{ stdout: string; stderr: string }>;\n\nconst logger = createLogger({ component: 'scanner-registry-fetcher' });\n\n/** Get the latest release tag name (lightweight check, no download). */\nasync function getLatestReleaseTag(execFileAsync: ExecFileAsync): Promise<string | null> {\n const { stdout } = await execFileAsync(\n 'gh',\n ['release', 'view', '--repo', REGISTRY_REPO, '--json', 'tagName', '--jq', '.tagName'],\n { timeout: FETCH_TIMEOUT_MS }\n );\n return stdout.trim() || null;\n}\n\n/** Download and parse the full manifest. */\nasync function downloadManifest(\n execFileAsync: ExecFileAsync\n): Promise<ScannerRegistryManifest | null> {\n const { stdout } = await execFileAsync(\n 'gh',\n [\n 'release',\n 'download',\n '--repo',\n REGISTRY_REPO,\n '--pattern',\n 'scanner-registry.json',\n '--output',\n '-',\n ],\n { timeout: FETCH_TIMEOUT_MS, maxBuffer: 1024 * 1024 }\n );\n\n let jsonData: unknown;\n try {\n jsonData = JSON.parse(stdout);\n } catch {\n logger.warn('Registry manifest is not valid JSON', {\n stdoutLength: stdout.length,\n preview: stdout.slice(0, 100),\n });\n return null;\n }\n\n const parsed = ManifestSchema.safeParse(jsonData);\n if (!parsed.success) {\n logger.warn('Registry manifest failed schema validation', {\n errors: parsed.error.issues.slice(0, 3),\n });\n return null;\n }\n\n logger.info('Fetched scanner registry manifest', {\n version: parsed.data.version,\n scanners: parsed.data.scanners.length,\n languages: Object.keys(parsed.data.languageMatrix).length,\n });\n return parsed.data;\n}\n\n/**\n * Fetch the scanner registry manifest from GitHub Releases.\n * If we have a cached version and the release tag hasn't changed,\n * just refreshes the cache timer (no download).\n */\nasync function fetchManifestFromGitHub(): Promise<ScannerRegistryManifest | null> {\n try {\n const { execFile } = await import('node:child_process');\n const { promisify } = await import('node:util');\n const execFileAsync = promisify(execFile);\n\n const tag = await getLatestReleaseTag(execFileAsync);\n if (tag === null) {\n logger.warn('No releases found in scanner registry');\n return null;\n }\n\n // If cached version matches the latest tag, refresh timer only\n if (cachedEntry !== null && cachedEntry.releaseTag === tag) {\n logger.debug('Scanner registry unchanged, refreshing cache timer', { tag });\n cachedEntry = { ...cachedEntry, fetchedAt: getTimeProvider().now() };\n return cachedEntry.manifest;\n }\n\n // New release — download full manifest\n const manifest = await downloadManifest(execFileAsync);\n if (manifest !== null) {\n cachedEntry = { manifest, fetchedAt: getTimeProvider().now(), releaseTag: tag };\n }\n return manifest;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug('Failed to fetch scanner registry', { error: msg });\n return null;\n }\n}\n\n/**\n * Get the scanner registry, fetching from GitHub if cache is stale.\n * Returns null if no cached data and fetch fails.\n */\nexport async function getRegistryManifest(): Promise<ScannerRegistryManifest | null> {\n // Check cache\n if (cachedEntry !== null) {\n const age = getTimeProvider().now() - cachedEntry.fetchedAt;\n if (age < CACHE_TTL_MS) {\n return cachedEntry.manifest;\n }\n }\n\n // Coalesce concurrent fetches — only one inflight request at a time (#1448)\n inflightFetch ??= fetchManifestFromGitHub().finally(() => {\n inflightFetch = undefined;\n });\n const manifest = await inflightFetch;\n if (manifest !== null) {\n return manifest;\n }\n\n // Return stale cache if available\n if (cachedEntry !== null) {\n logger.warn('Using stale cached registry manifest');\n return cachedEntry.manifest;\n }\n\n return null;\n}\n\n/**\n * Extract scanners from manifest into the format expected by plan builder.\n */\nexport function extractScannerEntries(\n manifest: ScannerRegistryManifest\n): readonly RegistryScanner[] {\n return manifest.scanners;\n}\n\n/**\n * Extract language matrix, normalizing to consistent category keys.\n */\nexport function extractLanguageMatrix(\n manifest: ScannerRegistryManifest\n): Readonly<Record<string, LanguageMatrixEntry>> {\n return manifest.languageMatrix;\n}\n","/**\n * nexus-agents/mcp - Fallback Scanner Data\n *\n * Embedded snapshot of the vulnerability-scanner-registry manifest.\n * Used when the live registry fetch fails (network issues, gh CLI\n * unavailable, etc.). Updated periodically from the canonical\n * registry at github.com/williamzujkowski/vulnerability-scanner-registry.\n *\n * @module mcp/tools/repo-security-plan-fallback\n * (Source: Consensus vote — externalize scanner registry, 6-0 unanimous)\n */\n\nimport type { ScannerData } from './repo-security-plan.js';\n\n// ============================================================================\n// Fallback Scanner Entries (27 scanners)\n// ============================================================================\n\nconst FALLBACK_SCANNERS: ScannerData['scanners'] = [\n {\n name: 'semgrep',\n displayName: 'Semgrep',\n categories: ['sast', 'secrets'],\n license: 'LGPL-2.1',\n pricingModel: 'freemium',\n },\n {\n name: 'codeql',\n displayName: 'CodeQL',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'freemium',\n },\n {\n name: 'bandit',\n displayName: 'Bandit',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'gosec',\n displayName: 'Gosec',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'brakeman',\n displayName: 'Brakeman',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'phpstan',\n displayName: 'PHPStan',\n categories: ['sast'],\n license: 'MIT',\n pricingModel: 'freemium',\n },\n {\n name: 'shellcheck',\n displayName: 'ShellCheck',\n categories: ['sast'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'cppcheck',\n displayName: 'Cppcheck',\n categories: ['sast'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'detekt',\n displayName: 'detekt',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'spotbugs',\n displayName: 'SpotBugs',\n categories: ['sast'],\n license: 'LGPL-2.1',\n pricingModel: 'free',\n },\n {\n name: 'eslint-security',\n displayName: 'eslint-plugin-security',\n categories: ['sast'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'sonarqube',\n displayName: 'SonarQube',\n categories: ['sast', 'sca'],\n license: 'LGPL-3.0',\n pricingModel: 'freemium',\n },\n {\n name: 'osv-scanner',\n displayName: 'OSV-Scanner',\n categories: ['sca', 'container', 'iac', 'sbom'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'grype',\n displayName: 'Grype',\n categories: ['sca', 'container'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'snyk',\n displayName: 'Snyk',\n categories: ['sca', 'sast', 'container'],\n license: 'Proprietary',\n pricingModel: 'freemium',\n },\n {\n name: 'npm-audit',\n displayName: 'npm audit',\n categories: ['sca'],\n license: 'Artistic-2.0',\n pricingModel: 'free',\n },\n {\n name: 'pip-audit',\n displayName: 'pip-audit',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'cargo-audit',\n displayName: 'cargo-audit',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'bundler-audit',\n displayName: 'bundler-audit',\n categories: ['sca'],\n license: 'GPL-3.0',\n pricingModel: 'free',\n },\n {\n name: 'govulncheck',\n displayName: 'govulncheck',\n categories: ['sca'],\n license: 'BSD-3-Clause',\n pricingModel: 'free',\n },\n {\n name: 'owasp-dependency-check',\n displayName: 'OWASP Dependency-Check',\n categories: ['sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'gitleaks',\n displayName: 'Gitleaks',\n categories: ['secrets'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'trufflehog',\n displayName: 'TruffleHog',\n categories: ['secrets'],\n license: 'AGPL-3.0',\n pricingModel: 'freemium',\n },\n {\n name: 'checkov',\n displayName: 'Checkov',\n categories: ['iac', 'sca'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'tfsec',\n displayName: 'tfsec',\n categories: ['iac'],\n license: 'MIT',\n pricingModel: 'free',\n },\n {\n name: 'owasp-zap',\n displayName: 'OWASP ZAP',\n categories: ['dast', 'api'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'syft',\n displayName: 'Syft',\n categories: ['sbom'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n {\n name: 'grype-image',\n displayName: 'Grype (image scan)',\n categories: ['image-currency', 'container'],\n license: 'Apache-2.0',\n pricingModel: 'free',\n },\n];\n\n// ============================================================================\n// Fallback Language Map (16 languages)\n// ============================================================================\n\nconst FALLBACK_LANGUAGE_MAP: ScannerData['languageMap'] = {\n TypeScript: {\n sast: ['semgrep', 'eslint-security', 'codeql'],\n sca: ['npm-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n JavaScript: {\n sast: ['semgrep', 'eslint-security', 'codeql'],\n sca: ['npm-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Python: {\n sast: ['bandit', 'semgrep', 'codeql'],\n sca: ['pip-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Java: {\n sast: ['codeql', 'semgrep', 'spotbugs'],\n sca: ['owasp-dependency-check', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Go: {\n sast: ['gosec', 'semgrep', 'codeql'],\n sca: ['govulncheck', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Ruby: {\n sast: ['brakeman', 'semgrep', 'codeql'],\n sca: ['bundler-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n PHP: {\n sast: ['phpstan', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n 'C#': {\n sast: ['codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n C: {\n sast: ['cppcheck', 'codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n 'C++': {\n sast: ['cppcheck', 'codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Rust: {\n sast: ['semgrep'],\n sca: ['cargo-audit', 'osv-scanner'],\n secrets: ['gitleaks'],\n },\n Kotlin: {\n sast: ['detekt', 'semgrep', 'codeql'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Swift: {\n sast: ['codeql', 'semgrep'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Scala: {\n sast: ['semgrep', 'spotbugs'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n Shell: {\n sast: ['shellcheck', 'semgrep'],\n sca: [],\n secrets: ['gitleaks'],\n },\n HCL: {\n sast: ['checkov', 'tfsec'],\n sca: ['osv-scanner'],\n secrets: ['gitleaks'],\n },\n};\n\n// ============================================================================\n// Exported Fallback\n// ============================================================================\n\n/** Embedded scanner data snapshot used when live registry is unavailable. */\nexport const FALLBACK_SCANNER_DATA: ScannerData = {\n scanners: FALLBACK_SCANNERS,\n languageMap: FALLBACK_LANGUAGE_MAP,\n source: 'fallback',\n};\n","/**\n * nexus-agents/mcp - Repository Security Plan Logic\n *\n * Generates a language-aware security scanning pipeline recommendation\n * by composing repo_analyze output with scanner registry data.\n * Fetches fresh data from vulnerability-scanner-registry GitHub Releases;\n * falls back to embedded snapshot if fetch fails.\n *\n * @module mcp/tools/repo-security-plan\n * (Source: Issue #1079, externalization vote 6-0 unanimous)\n */\n\nimport type { RepoAnalysis } from './repo-analyze-types.js';\nimport type {\n RepoSecurityPlanInput,\n RepoSecurityPlan,\n ScannerRecommendation,\n ConflictWarning,\n CoverageAnalysis,\n} from './repo-security-plan-types.js';\nimport { analyzeGitHubRepo } from './repo-analyze.js';\nimport { getRegistryManifest } from './scanner-registry-fetcher.js';\nimport type { RegistryScanner, LanguageMatrixEntry } from './scanner-registry-fetcher.js';\nimport { FALLBACK_SCANNER_DATA } from './repo-security-plan-fallback.js';\nimport { createLogger } from '../../core/index.js';\n\nconst logger = createLogger({ component: 'repo-security-plan' });\n\n// ============================================================================\n// Scanner Data Interface (common shape for fetched + fallback)\n// ============================================================================\n\n/** Internal scanner entry used by plan builder. */\nexport interface ScannerEntry {\n readonly name: string;\n readonly displayName: string;\n readonly categories: readonly string[];\n readonly license: string;\n readonly pricingModel: string;\n readonly supersedes?: readonly string[];\n}\n\n/** Language mapping: category → scanner names. */\ninterface LanguageMapping {\n readonly sast: readonly string[];\n readonly sca: readonly string[];\n readonly secrets: readonly string[];\n}\n\n/** Resolved scanner data for plan building. */\nexport interface ScannerData {\n readonly scanners: readonly ScannerEntry[];\n readonly languageMap: Readonly<Record<string, LanguageMapping>>;\n readonly source: 'registry' | 'fallback';\n}\n\n// Re-export for consumers\nexport { FALLBACK_SCANNER_DATA } from './repo-security-plan-fallback.js';\n\n// ============================================================================\n// Registry → ScannerData Conversion\n// ============================================================================\n\nfunction convertRegistryScanner(s: RegistryScanner): ScannerEntry {\n const supersedes = s.relationships?.filter((r) => r.type === 'supersedes').map((r) => r.target);\n return {\n name: s.name,\n displayName: s.displayName,\n categories: s.categories,\n license: s.license,\n pricingModel: s.pricingModel,\n ...(supersedes !== undefined && supersedes.length > 0 ? { supersedes } : {}),\n };\n}\n\n/** Known PascalCase language names from GitHub API. Handles registry keys like \"typescript\" → \"TypeScript\". */\nconst LANGUAGE_PASCAL_MAP: Readonly<Record<string, string>> = {\n typescript: 'TypeScript',\n javascript: 'JavaScript',\n python: 'Python',\n java: 'Java',\n csharp: 'C#',\n 'c#': 'C#',\n cpp: 'C++',\n 'c++': 'C++',\n go: 'Go',\n rust: 'Rust',\n ruby: 'Ruby',\n php: 'PHP',\n swift: 'Swift',\n kotlin: 'Kotlin',\n scala: 'Scala',\n hcl: 'HCL',\n shell: 'Shell',\n dockerfile: 'Dockerfile',\n};\n\nfunction normalizeLangName(lang: string): string {\n const lower = lang.toLowerCase();\n return LANGUAGE_PASCAL_MAP[lower] ?? lang.charAt(0).toUpperCase() + lang.slice(1);\n}\n\nfunction convertLanguageMatrix(\n matrix: Readonly<Record<string, LanguageMatrixEntry>>\n): Record<string, LanguageMapping> {\n const result: Record<string, LanguageMapping> = {};\n for (const [lang, entry] of Object.entries(matrix)) {\n // Normalize language name to PascalCase (GitHub API returns PascalCase like \"TypeScript\")\n const normalized = normalizeLangName(lang);\n result[normalized] = {\n sast: entry.sast ?? [],\n sca: entry.sca ?? [],\n secrets: entry.secrets ?? [],\n };\n }\n return result;\n}\n\n/** Resolve scanner data: fetch from registry, fall back to embedded. */\nexport async function resolveScannerData(): Promise<ScannerData> {\n const manifest = await getRegistryManifest();\n if (manifest !== null) {\n logger.info('Using live scanner registry', {\n version: manifest.version,\n scanners: manifest.scanners.length,\n });\n return {\n scanners: manifest.scanners.map(convertRegistryScanner),\n languageMap: convertLanguageMatrix(manifest.languageMatrix),\n source: 'registry',\n };\n }\n\n logger.info('Using fallback scanner data');\n return FALLBACK_SCANNER_DATA;\n}\n\n// ============================================================================\n// CI Snippet Generation (GitHub Actions only for v1)\n// ============================================================================\n\nconst CI_SNIPPETS: Readonly<Record<string, string>> = {\n semgrep: '- uses: semgrep/semgrep-action@v1\\n with:\\n config: auto',\n codeql: '- uses: github/codeql-action/analyze@v3',\n grype: '- uses: anchore/scan-action@v4\\n with:\\n path: .',\n 'grype-image':\n '- uses: anchore/scan-action@v4\\n with:\\n image: ${{ env.IMAGE_TAG }}\\n severity: CRITICAL,HIGH\\n exit-code: 1',\n gitleaks: '- uses: gitleaks/gitleaks-action@v2',\n bandit: '- run: pip install bandit && bandit -r . -f json',\n gosec: '- uses: securego/gosec@master\\n with:\\n args: ./...',\n checkov: '- uses: bridgecrewio/checkov-action@master',\n 'osv-scanner': '- uses: google/osv-scanner-action@v1',\n snyk: '- uses: snyk/actions/node@master # adjust for language',\n shellcheck: '- uses: ludeeus/action-shellcheck@master',\n};\n\nfunction generateCiSnippet(name: string, ci: string | null): string | null {\n if (ci !== 'github-actions') return null;\n return CI_SNIPPETS[name] ?? null;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction findScanner(name: string, scanners: readonly ScannerEntry[]): ScannerEntry | undefined {\n return scanners.find((s) => s.name === name);\n}\n\nfunction isAlreadyUsed(name: string, existing: readonly string[]): boolean {\n return existing.some((t) => t.toLowerCase().includes(name.toLowerCase()));\n}\n\n/** Context passed to recommendation collectors. */\ninterface RecContext {\n readonly existing: readonly string[];\n readonly ciProvider: string | null;\n readonly language: string | null;\n readonly categoryFilter: ReadonlySet<string> | null;\n readonly maxScanners: number;\n readonly scanners: readonly ScannerEntry[];\n}\n\n/** Options for collecting recommendations in a single category. */\ninterface CategoryRecOptions {\n readonly names: readonly string[];\n readonly category: string;\n readonly rationale: (entry: ScannerEntry) => string;\n readonly priority: 'critical' | 'recommended';\n readonly ctx: RecContext;\n}\n\n/** Collect recommendations for a single category. */\nfunction collectCategoryRecs(recs: ScannerRecommendation[], opts: CategoryRecOptions): void {\n for (const name of opts.names) {\n if (recs.length >= opts.ctx.maxScanners) break;\n if (isAlreadyUsed(name, opts.ctx.existing)) continue;\n const entry = findScanner(name, opts.ctx.scanners);\n if (!entry) continue;\n if (opts.ctx.categoryFilter && !opts.ctx.categoryFilter.has(opts.category)) continue;\n const isFirst = opts.category === 'sast' && recs.length === 0;\n recs.push({\n name,\n displayName: entry.displayName,\n category: opts.category,\n license: entry.license,\n pricingModel: entry.pricingModel,\n rationale: opts.rationale(entry),\n priority: isFirst ? 'critical' : opts.priority,\n ciSnippet: generateCiSnippet(name, opts.ctx.ciProvider),\n });\n }\n}\n\n/** Collect language-specific recommendations (SAST + SCA + secrets). */\nfunction collectLanguageRecs(\n langMap: LanguageMapping,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n const lang = ctx.language ?? 'unknown';\n collectCategoryRecs(recs, {\n names: langMap.sast,\n category: 'sast',\n rationale: (e) => `${e.displayName} provides SAST for ${lang}`,\n priority: 'recommended',\n ctx,\n });\n collectCategoryRecs(recs, {\n names: langMap.sca,\n category: 'sca',\n rationale: (e) => `${e.displayName} provides SCA for ${lang} dependencies`,\n priority: 'critical',\n ctx,\n });\n collectCategoryRecs(recs, {\n names: langMap.secrets,\n category: 'secrets',\n rationale: () => 'Detects leaked credentials and API keys in source code',\n priority: 'critical',\n ctx,\n });\n}\n\n/** Try to add a single scanner if not already present. */\nfunction tryAddScanner(\n scannerName: string,\n category: string,\n rationale: string,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n if (recs.length >= ctx.maxScanners) return;\n if (ctx.categoryFilter && !ctx.categoryFilter.has(category)) return;\n if (isAlreadyUsed(scannerName, ctx.existing)) return;\n if (recs.some((r) => r.name === scannerName)) return;\n const entry = findScanner(scannerName, ctx.scanners);\n if (!entry) return;\n recs.push({\n name: scannerName,\n displayName: entry.displayName,\n category,\n license: entry.license,\n pricingModel: entry.pricingModel,\n rationale,\n priority: 'recommended',\n ciSnippet: generateCiSnippet(scannerName, ctx.ciProvider),\n });\n}\n\n// ============================================================================\n// Conflict Detection\n// ============================================================================\n\nfunction detectConflicts(\n recs: readonly ScannerRecommendation[],\n scanners: readonly ScannerEntry[]\n): readonly ConflictWarning[] {\n const warnings: ConflictWarning[] = [];\n const names = new Set(recs.map((r) => r.name));\n detectSuperseded(names, scanners, warnings);\n detectRedundant(recs, warnings);\n return warnings;\n}\n\nfunction detectSuperseded(\n names: ReadonlySet<string>,\n scanners: readonly ScannerEntry[],\n warnings: ConflictWarning[]\n): void {\n for (const scanner of scanners) {\n if (!names.has(scanner.name)) continue;\n if (!scanner.supersedes) continue;\n for (const old of scanner.supersedes) {\n if (names.has(old)) {\n warnings.push({\n scanners: [old, scanner.name],\n type: 'superseded',\n recommendation: `${scanner.displayName} supersedes ${old}. Remove ${old}.`,\n });\n }\n }\n }\n}\n\nfunction detectRedundant(\n recs: readonly ScannerRecommendation[],\n warnings: ConflictWarning[]\n): void {\n const catMap = new Map<string, string[]>();\n for (const rec of recs) {\n const arr = catMap.get(rec.category) ?? [];\n arr.push(rec.name);\n catMap.set(rec.category, arr);\n }\n for (const [cat, scanners] of catMap) {\n if (scanners.length > 2) {\n const count = String(scanners.length);\n warnings.push({\n scanners,\n type: 'redundant',\n recommendation: `${count} scanners for ${cat}. Consider keeping top 2.`,\n });\n }\n }\n}\n\n// ============================================================================\n// Coverage Analysis\n// ============================================================================\n\nconst ALL_CATEGORIES = ['sast', 'dast', 'sca', 'secrets', 'container', 'iac', 'image-currency'];\n\nfunction buildCoverage(\n recs: readonly ScannerRecommendation[],\n existing: readonly string[],\n scanners: readonly ScannerEntry[]\n): readonly CoverageAnalysis[] {\n return ALL_CATEGORIES.map((cat) => {\n const found = recs.filter((r) => r.category === cat).map((r) => r.name);\n const existingMatch = existing.some((t) =>\n scanners.some((s) => s.categories.includes(cat) && t.toLowerCase().includes(s.name))\n );\n return { category: cat, covered: found.length > 0 || existingMatch, scanners: found };\n });\n}\n\n// ============================================================================\n// Plan Assembly\n// ============================================================================\n\n/** Options for buildPlanFromAnalysis (allows optional fields for testability). */\ninterface BuildPlanOptions {\n readonly repo: string;\n readonly categories?: readonly string[] | undefined;\n readonly maxScanners?: number | undefined;\n}\n\n/** Generate a security scanning plan for a repository (fetches live data). */\nexport async function generateSecurityPlan(\n input: RepoSecurityPlanInput\n): Promise<RepoSecurityPlan> {\n const [analysis, data] = await Promise.all([\n analyzeGitHubRepo({ repo: input.repo, depth: 'deep' }),\n resolveScannerData(),\n ]);\n return buildPlanFromAnalysis(analysis, input, data);\n}\n\n/** Collect infrastructure-specific scanner recommendations. */\nfunction collectInfraRecs(\n analysis: RepoAnalysis,\n recs: ScannerRecommendation[],\n ctx: RecContext\n): void {\n if (analysis.hasDockerfile) {\n tryAddScanner(\n 'grype',\n 'container',\n 'Dockerfile detected — scan container images for vulnerabilities',\n recs,\n ctx\n );\n tryAddScanner('grype-image', 'image-currency', buildImageCurrencyRationale(), recs, ctx);\n }\n if (analysis.hasHelmCharts) {\n tryAddScanner(\n 'checkov',\n 'iac',\n 'Helm charts detected — scan IaC for misconfigurations',\n recs,\n ctx\n );\n }\n}\n\n/**\n * Build the rationale string for periodic container base image CVE scanning.\n * Extracted to keep collectInfraRecs within the 50-line function limit.\n */\nfunction buildImageCurrencyRationale(): string {\n return (\n 'Dockerfile detected — periodically scan built images with ' +\n '`grype image --severity CRITICAL,HIGH` to detect CVEs introduced by stale base images. ' +\n 'Pin base images to specific version tags (e.g., node:22.4.0-alpine3.20) rather than ' +\n ':latest to get reproducible scans and predictable CVE surface area. ' +\n 'Alpine-based images typically have a smaller CVE surface than Debian/Ubuntu equivalents ' +\n 'due to musl libc and a minimal package set, but verify with grype before assuming.'\n );\n}\n\n/** Pure function: build plan from analysis + scanner data (testable). */\nexport function buildPlanFromAnalysis(\n analysis: RepoAnalysis,\n input: BuildPlanOptions,\n data?: ScannerData\n): RepoSecurityPlan {\n const resolved = data ?? FALLBACK_SCANNER_DATA;\n const ctx: RecContext = {\n existing: analysis.securityTooling,\n ciProvider: analysis.ciProvider,\n language: analysis.language,\n categoryFilter: input.categories ? new Set(input.categories) : null,\n maxScanners: input.maxScanners ?? 10,\n scanners: resolved.scanners,\n };\n\n const recs: ScannerRecommendation[] = [];\n const normalizedLang = analysis.language !== null ? normalizeLangName(analysis.language) : null;\n const langMap = normalizedLang !== null ? resolved.languageMap[normalizedLang] : undefined;\n if (langMap) collectLanguageRecs(langMap, recs, ctx);\n collectInfraRecs(analysis, recs, ctx);\n\n const conflicts = detectConflicts(recs, resolved.scanners);\n const coverage = buildCoverage(recs, analysis.securityTooling, resolved.scanners);\n const uncovered = coverage.filter((c) => !c.covered).map((c) => c.category);\n\n return {\n repo: analysis.name,\n language: analysis.language,\n framework: analysis.framework,\n ciProvider: analysis.ciProvider,\n existingTooling: analysis.securityTooling,\n recommendations: recs,\n conflicts,\n coverage,\n gapsSummary: [\n ...analysis.gaps,\n ...(uncovered.length > 0 ? [`Uncovered categories: ${uncovered.join(', ')}`] : []),\n ],\n };\n}\n"],"mappings":"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","names":["logger"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/nexus-data-dir.ts","../src/config/sandbox-detection.ts"],"sourcesContent":["/**\n * Nexus runtime data directory resolver (#2302, child of #2301).\n *\n * Returns the absolute path under which nexus-agents stores all runtime\n * state — memory, learning, audit, voting, sessions, checkpoints, traces,\n * model registry. Single source of truth so portable / sandbox / CI\n * deployments can redirect state to a workspace-local folder via the\n * `NEXUS_DATA_DIR` environment variable.\n *\n * Resolution order (first match wins):\n * 1. `NEXUS_DATA_DIR` env var if set + non-empty (resolved against `cwd`).\n * 2. Sandbox-mode default (#2501): when `NEXUS_SANDBOX` is set, use\n * `${NEXUS_SANDBOX_ROOT ?? '/'}/.nexus-agents`. Sandboxed deployments\n * typically mount a multi-repo root; state goes there, shared across\n * repo subfolders rather than buried inside one.\n * 3. `<homedir>/.nexus-agents` (zero-breakage fallback for laptop use).\n *\n * No caching, no filesystem walks, no discovery. The contrarian-narrowed\n * scope (#2301 vote) explicitly defers ancestor-walking to a separate\n * child with a security design pass per CVE-2022-24765. Recomputing the\n * trivial env-or-homedir lookup on each call is ~100ns and avoids cache\n * coordination issues with tests that mock `homedir()`.\n *\n * @module config/nexus-data-dir\n */\n\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\n\nimport { detectSandbox } from './sandbox-detection.js';\n\n/** Returns the absolute path to the nexus-agents data directory. */\nexport function getNexusDataDir(): string {\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') {\n return resolve(fromEnv);\n }\n const sandbox = detectSandbox();\n if (sandbox.active) {\n return resolve(sandbox.root ?? '/', '.nexus-agents');\n }\n return join(homedir(), '.nexus-agents');\n}\n\n/**\n * No-op kept for source-compatibility with consumers that called this\n * earlier in development. The resolver is no longer cached, so resetting\n * is unnecessary. Kept exported (rather than removed) to avoid breaking\n * imports in tests that may have already adopted it.\n */\nexport function resetNexusDataDirCache(): void {\n // intentionally empty — see module docstring\n}\n\n/** Returns a path joined under the resolved data directory. */\nexport function nexusDataPath(...segments: string[]): string {\n return join(getNexusDataDir(), ...segments);\n}\n","/**\n * Sandbox detection (#2501, child 1 of epic #2500).\n *\n * nexus-agents needs to know whether it's running inside a host-provided\n * sandbox (Docker Desktop Sandbox + OpenCode, Codex sandbox, locked-down\n * CI runner) so it can adjust behaviour:\n *\n * - default `NEXUS_DATA_DIR` to the multi-repo root rather than `~/...`\n * - skip CLI subprocess detection (binaries aren't there)\n * - fail-fast when the gateway is unreachable instead of degrading\n * - suppress diagnostics that don't apply\n *\n * The signal is **explicit**: the image author (Dockerfile, devcontainer,\n * harness wrapper) sets `NEXUS_SANDBOX=<flavor>`. The flavor string is\n * for diagnostics + per-flavor branching (`docker-opencode`, `codex`,\n * `claude-code`, `ci-restricted`, …); presence is the on/off signal.\n *\n * A heuristic check runs alongside for verification — if `NEXUS_SANDBOX`\n * claims `docker-opencode` but `/.dockerenv` is missing, that's a\n * misconfiguration worth surfacing to the operator.\n *\n * @module config/sandbox-detection\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\n\n/**\n * Heuristic detection result. Independent from the explicit\n * `NEXUS_SANDBOX` env var; produced by inspecting filesystem markers.\n */\nexport type SandboxHeuristic = 'docker' | 'podman' | 'unknown' | null;\n\nexport interface SandboxInfo {\n /**\n * True iff `NEXUS_SANDBOX` is set + non-empty. The explicit signal\n * the rest of the codebase keys off — never `true` by heuristic alone.\n */\n readonly active: boolean;\n /**\n * Operator-supplied flavor string, e.g. `docker-opencode`. Undefined\n * when `active === false` or the env var is empty.\n */\n readonly flavor: string | undefined;\n /**\n * Multi-repo root the user mounted. From `NEXUS_SANDBOX_ROOT`. Undefined\n * when unset; consumers that need a default substitute `/`.\n */\n readonly root: string | undefined;\n /**\n * Independent heuristic match — `null` when we couldn't run the check,\n * `'unknown'` when ran but no marker matched. Used by `doctor` to flag\n * mismatches between the explicit signal and the runtime environment.\n */\n readonly heuristicMatch: SandboxHeuristic;\n}\n\n/**\n * Detect whether nexus-agents is running inside a host-provided sandbox.\n * Pure read of env + filesystem; no caching (cheap enough to recompute,\n * and tests routinely mutate process.env).\n */\nexport function detectSandbox(): SandboxInfo {\n const flavorRaw = process.env['NEXUS_SANDBOX']?.trim();\n const flavor = flavorRaw !== undefined && flavorRaw !== '' ? flavorRaw : undefined;\n const active = flavor !== undefined;\n\n const rootRaw = process.env['NEXUS_SANDBOX_ROOT']?.trim();\n const root = rootRaw !== undefined && rootRaw !== '' ? rootRaw : undefined;\n\n return {\n active,\n flavor,\n root,\n heuristicMatch: detectContainerHeuristic(),\n };\n}\n\n/**\n * Look for filesystem markers indicating a container runtime. Used as a\n * cross-check against the explicit `NEXUS_SANDBOX` signal.\n *\n * Order of checks:\n * 1. `/.dockerenv` (Docker)\n * 2. `/run/.containerenv` (Podman)\n * 3. `/proc/1/cgroup` containing `docker` or `containerd` strings\n *\n * Returns `null` when none of the checks could run (non-Linux host with\n * no `/proc`, sandbox blocks `existsSync`, etc.) — distinct from\n * `'unknown'` (checks ran, no marker matched) so the doctor message can\n * differentiate \"we couldn't tell\" from \"we checked, no container\".\n */\nfunction detectContainerHeuristic(): SandboxHeuristic {\n try {\n if (existsSync('/.dockerenv')) return 'docker';\n if (existsSync('/run/.containerenv')) return 'podman';\n if (existsSync('/proc/1/cgroup')) {\n const cgroup = readFileSync('/proc/1/cgroup', 'utf8');\n if (/\\bdocker\\b/.test(cgroup)) return 'docker';\n if (/\\bcontainerd\\b/.test(cgroup)) return 'docker';\n }\n return 'unknown';\n } catch {\n return null;\n }\n}\n"],"mappings":";AA0BA,SAAS,eAAe;AACxB,SAAS,MAAM,eAAe;;;ACH9B,SAAS,YAAY,oBAAoB;AAqClC,SAAS,gBAA6B;AAC3C,QAAM,YAAY,QAAQ,IAAI,eAAe,GAAG,KAAK;AACrD,QAAM,SAAS,cAAc,UAAa,cAAc,KAAK,YAAY;AACzE,QAAM,SAAS,WAAW;AAE1B,QAAM,UAAU,QAAQ,IAAI,oBAAoB,GAAG,KAAK;AACxD,QAAM,OAAO,YAAY,UAAa,YAAY,KAAK,UAAU;AAEjE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB,yBAAyB;AAAA,EAC3C;AACF;AAgBA,SAAS,2BAA6C;AACpD,MAAI;AACF,QAAI,WAAW,aAAa,EAAG,QAAO;AACtC,QAAI,WAAW,oBAAoB,EAAG,QAAO;AAC7C,QAAI,WAAW,gBAAgB,GAAG;AAChC,YAAM,SAAS,aAAa,kBAAkB,MAAM;AACpD,UAAI,aAAa,KAAK,MAAM,EAAG,QAAO;AACtC,UAAI,iBAAiB,KAAK,MAAM,EAAG,QAAO;AAAA,IAC5C;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ADxEO,SAAS,kBAA0B;AACxC,QAAM,UAAU,QAAQ,IAAI,gBAAgB,GAAG,KAAK;AACpD,MAAI,YAAY,UAAa,YAAY,IAAI;AAC3C,WAAO,QAAQ,OAAO;AAAA,EACxB;AACA,QAAM,UAAU,cAAc;AAC9B,MAAI,QAAQ,QAAQ;AAClB,WAAO,QAAQ,QAAQ,QAAQ,KAAK,eAAe;AAAA,EACrD;AACA,SAAO,KAAK,QAAQ,GAAG,eAAe;AACxC;AAQO,SAAS,yBAA+B;AAE/C;AAGO,SAAS,iBAAiB,UAA4B;AAC3D,SAAO,KAAK,gBAAgB,GAAG,GAAG,QAAQ;AAC5C;","names":[]}
|
package/dist/chunk-I7ORMAO7.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getNexusDataDir
|
|
3
|
-
} from "./chunk-GOT7OAL5.js";
|
|
4
|
-
|
|
5
|
-
// src/config/learning-persistence.ts
|
|
6
|
-
import { mkdirSync } from "fs";
|
|
7
|
-
import { join } from "path";
|
|
8
|
-
function getLearningDir() {
|
|
9
|
-
return join(getNexusDataDir(), "learning");
|
|
10
|
-
}
|
|
11
|
-
function getOutcomesFile() {
|
|
12
|
-
return join(getLearningDir(), "outcomes.jsonl");
|
|
13
|
-
}
|
|
14
|
-
function getRulesFile() {
|
|
15
|
-
return join(getLearningDir(), "rules.json");
|
|
16
|
-
}
|
|
17
|
-
var DIR_MODE = 448;
|
|
18
|
-
function isPersistenceEnabled() {
|
|
19
|
-
return process.env["NEXUS_PERSIST_LEARNING"] !== "false";
|
|
20
|
-
}
|
|
21
|
-
function ensureLearningDir(dir) {
|
|
22
|
-
mkdirSync(dir ?? getLearningDir(), { recursive: true, mode: DIR_MODE });
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export {
|
|
26
|
-
getLearningDir,
|
|
27
|
-
getOutcomesFile,
|
|
28
|
-
getRulesFile,
|
|
29
|
-
isPersistenceEnabled,
|
|
30
|
-
ensureLearningDir
|
|
31
|
-
};
|
|
32
|
-
//# sourceMappingURL=chunk-I7ORMAO7.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/learning-persistence.ts"],"sourcesContent":["/**\n * Shared configuration for cross-session learning persistence.\n *\n * Controls where learning data (outcomes, distilled rules) is stored\n * on disk and whether persistence is enabled via feature flag.\n *\n * @module config/learning-persistence\n * (Source: Issue #1009 — Cross-session persistence)\n */\n\nimport { mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getNexusDataDir } from './nexus-data-dir.js';\n\n// ============================================================================\n// Path resolution (#2316: must read NEXUS_DATA_DIR at call time, not import)\n// ============================================================================\n\n/**\n * Base directory for learning persistence data. Resolved at call time so\n * `NEXUS_DATA_DIR` overrides take effect — the const-at-import-time\n * pattern this replaces was the bug discovered while dogfooding v2.63.0\n * (#2316). Outcome counts on a fresh portable workspace were leaking\n * the host home directory's outcome history.\n */\nexport function getLearningDir(): string {\n return join(getNexusDataDir(), 'learning');\n}\n\n/** JSONL file for persisted task outcomes. */\nexport function getOutcomesFile(): string {\n return join(getLearningDir(), 'outcomes.jsonl');\n}\n\n/** JSON file for persisted distilled rules. */\nexport function getRulesFile(): string {\n return join(getLearningDir(), 'rules.json');\n}\n\n// Note: previous LEARNING_DIR / OUTCOMES_FILE / RULES_FILE exports were\n// removed in #2316 — they were evaluated at module import time and ignored\n// `NEXUS_DATA_DIR`. All callers must use the getter functions above.\n\n/** Directory mode: owner-only (rwx------). */\nconst DIR_MODE = 0o700;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Check whether learning persistence is enabled via feature flag.\n *\n * Defaults to true — cross-session LinUCB routing data persists to\n * `~/.nexus-agents/learning/` unless explicitly disabled.\n * Only routing metadata is stored (model, success, duration, category).\n * No user prompts, API keys, or model outputs are persisted.\n *\n * Set NEXUS_PERSIST_LEARNING=false to disable.\n */\nexport function isPersistenceEnabled(): boolean {\n return process.env['NEXUS_PERSIST_LEARNING'] !== 'false';\n}\n\n/** Ensure the learning data directory exists. */\nexport function ensureLearningDir(dir?: string): void {\n mkdirSync(dir ?? getLearningDir(), { recursive: true, mode: DIR_MODE });\n}\n"],"mappings":";;;;;AAUA,SAAS,iBAAiB;AAC1B,SAAS,YAAY;AAcd,SAAS,iBAAyB;AACvC,SAAO,KAAK,gBAAgB,GAAG,UAAU;AAC3C;AAGO,SAAS,kBAA0B;AACxC,SAAO,KAAK,eAAe,GAAG,gBAAgB;AAChD;AAGO,SAAS,eAAuB;AACrC,SAAO,KAAK,eAAe,GAAG,YAAY;AAC5C;AAOA,IAAM,WAAW;AAgBV,SAAS,uBAAgC;AAC9C,SAAO,QAAQ,IAAI,wBAAwB,MAAM;AACnD;AAGO,SAAS,kBAAkB,KAAoB;AACpD,YAAU,OAAO,eAAe,GAAG,EAAE,WAAW,MAAM,MAAM,SAAS,CAAC;AACxE;","names":[]}
|