kirograph 1.0.0 → 1.1.1
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/README.md +3 -2
- package/dist/architecture/manifest/python.js +2 -2
- package/dist/architecture/manifest/python.js.map +2 -2
- package/dist/bin/commands/help.js +3 -1
- package/dist/bin/commands/help.js.map +2 -2
- package/dist/bin/commands/memory.js +18 -8
- package/dist/bin/commands/memory.js.map +2 -2
- package/dist/bin/commands/reachability.js +7 -2
- package/dist/bin/commands/reachability.js.map +2 -2
- package/dist/bin/commands/vulns.js +16 -1
- package/dist/bin/commands/vulns.js.map +2 -2
- package/dist/bin/commands/wiki.js +56 -0
- package/dist/bin/commands/wiki.js.map +2 -2
- package/dist/bin/installer/steering.js +9 -0
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/kirograph.js +1 -1
- package/dist/config.js +4 -0
- package/dist/config.js.map +2 -2
- package/dist/extraction/extractor.js +46 -41
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/extraction/notebook.js +15 -7
- package/dist/extraction/notebook.js.map +2 -2
- package/dist/jev/client.js +99 -0
- package/dist/jev/client.js.map +7 -0
- package/dist/jev/types.js +17 -0
- package/dist/jev/types.js.map +7 -0
- package/dist/mcp/handlers/memory.js +14 -8
- package/dist/mcp/handlers/memory.js.map +2 -2
- package/dist/mcp/handlers/security.js +5 -3
- package/dist/mcp/handlers/security.js.map +2 -2
- package/dist/mcp/tools.js +1 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/memory/index.js +13 -1
- package/dist/memory/index.js.map +3 -3
- package/dist/memory/relation-jev.js +59 -0
- package/dist/memory/relation-jev.js.map +7 -0
- package/dist/memory/schema.js +57 -0
- package/dist/memory/schema.js.map +7 -0
- package/dist/security/auth-detection-jev.js +47 -0
- package/dist/security/auth-detection-jev.js.map +7 -0
- package/dist/security/manifest/adapter.js +17 -3
- package/dist/security/manifest/adapter.js.map +3 -3
- package/dist/security/manifest/plugins/cargo.js +23 -0
- package/dist/security/manifest/plugins/cargo.js.map +2 -2
- package/dist/security/manifest/plugins/composer.js +12 -0
- package/dist/security/manifest/plugins/composer.js.map +2 -2
- package/dist/security/manifest/plugins/gradle.js +12 -0
- package/dist/security/manifest/plugins/gradle.js.map +2 -2
- package/dist/security/manifest/plugins/hex.js +12 -0
- package/dist/security/manifest/plugins/hex.js.map +2 -2
- package/dist/security/manifest/plugins/maven.js +68 -1
- package/dist/security/manifest/plugins/maven.js.map +2 -2
- package/dist/security/manifest/plugins/npm.js +86 -0
- package/dist/security/manifest/plugins/npm.js.map +2 -2
- package/dist/security/manifest/plugins/nuget.js +13 -0
- package/dist/security/manifest/plugins/nuget.js.map +2 -2
- package/dist/security/manifest/plugins/pubspec.js +12 -0
- package/dist/security/manifest/plugins/pubspec.js.map +2 -2
- package/dist/security/manifest/plugins/pyproject.js +12 -0
- package/dist/security/manifest/plugins/pyproject.js.map +2 -2
- package/dist/security/manifest/plugins/rubygems.js +12 -0
- package/dist/security/manifest/plugins/rubygems.js.map +2 -2
- package/dist/security/pipeline.js +1 -1
- package/dist/security/pipeline.js.map +2 -2
- package/dist/security/reachability.js +54 -5
- package/dist/security/reachability.js.map +3 -3
- package/dist/security/types.js.map +1 -1
- package/dist/security/vuln/client.js +45 -1
- package/dist/security/vuln/client.js.map +2 -2
- package/dist/security/vuln/osv-adapter.js +45 -1
- package/dist/security/vuln/osv-adapter.js.map +2 -2
- package/dist/wiki/contradiction-jev.js +49 -0
- package/dist/wiki/contradiction-jev.js.map +7 -0
- package/dist/wiki/index.js +13 -0
- package/dist/wiki/index.js.map +2 -2
- package/dist/wiki/ingest.js +2 -0
- package/dist/wiki/ingest.js.map +2 -2
- package/dist/wiki/links.js +53 -0
- package/dist/wiki/links.js.map +7 -0
- package/dist/wiki/lint.js +3 -6
- package/dist/wiki/lint.js.map +2 -2
- package/dist/wiki/rename.js +103 -0
- package/dist/wiki/rename.js.map +7 -0
- package/package.json +1 -1
package/dist/memory/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/memory/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * KiroGraph Memory \u2014 Public API\n *\n * MemoryManager is the single entry point for all memory operations.\n * Coordinates compression, storage, symbol detection, and embedding.\n */\n\nimport type { KiroGraphConfig } from '../config';\nimport type {\n MemObservation,\n MemObservationInput,\n MemSession,\n ScoredObservation,\n MemSearchOptions,\n MemTimelineOptions,\n MemStats,\n CompressResult,\n WatchmenReadyResult,\n MemRelation,\n MemRelationInput,\n RelationType,\n MemPrompt,\n} from './types';\nimport { MemoryDatabase } from './database';\nimport { compressObservation, type CavemanMode } from './compress';\nimport { detectSymbols } from './symbols';\nimport { MemoryVectorManager } from './vectors';\nimport { WatchmenChecker } from '../watchmen';\nimport { logDebug } from '../errors';\n\nexport { MemoryDatabase } from './database';\nexport { compressObservation, extractIdentifiers, type CavemanMode } from './compress';\nexport { detectSymbols } from './symbols';\nexport { MemoryVectorManager } from './vectors';\nexport * from './types';\n\n// \u2500\u2500 MemoryManager \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class MemoryManager {\n private memDb: MemoryDatabase;\n private vectorMgr: MemoryVectorManager;\n private config: KiroGraphConfig;\n private db: any; // raw sqlite handle for symbol detection\n private cavemanMode: CavemanMode;\n private excludePatterns: string[];\n private watchmenChecker: WatchmenChecker | null;\n private projectRoot: string;\n\n constructor(config: KiroGraphConfig, db: any, projectRoot = process.cwd()) {\n this.config = config;\n this.db = db;\n this.projectRoot = projectRoot;\n this.memDb = new MemoryDatabase(db);\n const kirographDir = require('path').join(projectRoot, '.kirograph');\n this.vectorMgr = new MemoryVectorManager(config, this.memDb, kirographDir);\n this.cavemanMode = (config as any).cavemanMode ?? 'off';\n this.excludePatterns = (config as any).memoryExcludePatterns ?? [];\n this.watchmenChecker = config.enableWatchmen\n ? new WatchmenChecker(config.watchmenThreshold)\n : null;\n }\n\n /**\n * Initialize memory tables. Must be called before any operations.\n */\n initialize(): void {\n this.memDb.initialize();\n }\n\n /**\n * Check if memory is enabled in config.\n */\n static isEnabled(config: KiroGraphConfig): boolean {\n return !!(config as any).enableMemory;\n }\n\n // \u2500\u2500 Store \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Store an observation. Handles:\n * 1. Privacy stripping (<private> blocks)\n * 2. Exclude pattern checking\n * 3. Caveman compression (if enabled)\n * 4. Deduplication (content hash)\n * 5. Symbol detection and linking\n * 6. Embedding (if enabled)\n * 7. Watchmen threshold check (if enabled) \u2014 returns WatchmenReadyResult when synthesis should run\n *\n * Returns the observation ID, a WatchmenReadyResult, or null if skipped (duplicate or excluded).\n */\n async store(input: MemObservationInput, ide = 'kiro'): Promise<string | WatchmenReadyResult | null> {\n let text = input.content;\n\n // 1. Strip <private> blocks\n text = MemoryDatabase.stripPrivate(text);\n if (!text.trim()) return null;\n\n // 2. Check exclude patterns\n if (this.shouldExclude(text)) return null;\n\n // 3. Compress (if caveman is on)\n let contentRaw: string | undefined;\n if (this.cavemanMode !== 'off') {\n const result = compressObservation(text, this.cavemanMode);\n contentRaw = text;\n text = result.compressed;\n }\n\n // 4. Get or create session\n const cwd = process.cwd();\n const sessionTimeout = (this.config as any).memorySessionTimeout ?? 7200;\n const sessionId = this.memDb.getOrCreateSession(ide, cwd, sessionTimeout);\n\n // 5. Insert observation (deduplicates via content_hash)\n const id = this.memDb.insertObservation(text, {\n contentRaw: this.cavemanMode !== 'off' ? contentRaw : undefined,\n kind: input.kind ?? 'note',\n source: input.source ?? 'manual',\n tags: input.tags,\n sessionId,\n topicKey: input.topicKey,\n reviewAfter: input.reviewAfter,\n });\n\n if (!id) {\n logDebug('Memory: duplicate observation skipped');\n return null;\n }\n\n // 6. Detect and link symbols\n const symbols = detectSymbols(text, this.db);\n if (symbols.length > 0) {\n this.memDb.linkToSymbols(id, symbols.map(s => s.qualifiedName));\n }\n\n // Also detect in raw text if different\n if (contentRaw) {\n const rawSymbols = detectSymbols(contentRaw, this.db);\n const extraSymbols = rawSymbols.filter(\n s => !symbols.some(existing => existing.qualifiedName === s.qualifiedName)\n );\n if (extraSymbols.length > 0) {\n this.memDb.linkToSymbols(id, extraSymbols.map(s => s.qualifiedName));\n }\n }\n\n // 7. Embed (async, non-blocking for the store operation)\n const obs = this.memDb.getObservation(id);\n if (obs) {\n await this.vectorMgr.embedObservation(obs);\n }\n\n // 8. Watchmen threshold check\n if (this.watchmenChecker && input.kind !== 'summary') {\n const check = this.watchmenChecker.shouldSynthesize(this.memDb);\n if (check.ready) {\n return this.watchmenChecker.buildReadyResponse(id, check.pendingCount, this.projectRoot);\n }\n }\n\n return id;\n }\n\n // \u2500\u2500 Search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Hybrid search: combines FTS5 and vector search with configurable alpha.\n */\n async search(query: string, opts: MemSearchOptions = {}): Promise<ScoredObservation[]> {\n const limit = opts.limit ?? 10;\n const alpha = opts.alpha ?? (this.config as any).memorySearchAlpha ?? 0.5;\n\n // FTS search\n const ftsResults = this.memDb.searchFTS(query, { ...opts, limit: limit * 2 });\n\n // Vector search (if enabled and no model mismatch)\n let vectorResults: ScoredObservation[] = [];\n if (this.vectorMgr.isEnabled() && !this.vectorMgr.hasModelMismatch()) {\n vectorResults = await this.vectorMgr.search(query, limit * 2);\n }\n\n // If only one source has results, return it directly\n if (vectorResults.length === 0) {\n const sliced = ftsResults.slice(0, limit);\n const ids = sliced.map(r => r.observation.id);\n const relMap = this.memDb.getRelationsForObservations(ids);\n for (const r of sliced) {\n const rels = relMap.get(r.observation.id);\n if (rels && rels.length > 0) r.relations = rels;\n }\n return sliced;\n }\n if (ftsResults.length === 0) {\n const sliced = vectorResults.slice(0, limit);\n const ids = sliced.map(r => r.observation.id);\n const relMap = this.memDb.getRelationsForObservations(ids);\n for (const r of sliced) {\n const rels = relMap.get(r.observation.id);\n if (rels && rels.length > 0) r.relations = rels;\n }\n return sliced;\n }\n\n // Hybrid merge: normalize scores and blend\n const merged = this.mergeResults(ftsResults, vectorResults, alpha);\n const sliced = merged.slice(0, limit);\n\n // Enrich with relations\n const ids = sliced.map(r => r.observation.id);\n const relMap = this.memDb.getRelationsForObservations(ids);\n for (const r of sliced) {\n const rels = relMap.get(r.observation.id);\n if (rels && rels.length > 0) r.relations = rels;\n }\n\n return sliced;\n }\n\n /**\n * Get observations linked to specific symbols.\n * Used by kirograph_context and kirograph_impact.\n */\n getLinkedObservations(qualifiedNames: string[], limit = 3, threshold = 0.3): ScoredObservation[] {\n const results = this.memDb.getLinkedObservationsForSymbols(qualifiedNames, limit * 2);\n return results.filter(r => r.score >= threshold).slice(0, limit);\n }\n\n // \u2500\u2500 Timeline \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * List sessions with their observation counts.\n */\n timeline(opts: MemTimelineOptions = {}): { sessions: MemSession[]; observations: Map<string, MemObservation[]> } {\n const limit = opts.limit ?? 5;\n\n if (opts.sessionId) {\n const session = this.memDb.getSession(opts.sessionId);\n if (!session) return { sessions: [], observations: new Map() };\n const obs = this.memDb.getObservationsBySession(opts.sessionId);\n const map = new Map<string, MemObservation[]>();\n map.set(opts.sessionId, obs);\n return { sessions: [session], observations: map };\n }\n\n const sessions = this.memDb.listSessions(limit);\n const observations = new Map<string, MemObservation[]>();\n for (const session of sessions) {\n const obs = this.memDb.getObservationsBySession(session.id, 10);\n observations.set(session.id, obs);\n }\n return { sessions, observations };\n }\n\n // \u2500\u2500 Status \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getStats(): MemStats {\n const modelName = (this.config as any).embeddingModel ?? 'nomic-ai/nomic-embed-text-v1.5';\n return this.memDb.getStats(modelName);\n }\n\n // \u2500\u2500 Session management \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n endCurrentSession(ide = 'kiro'): void {\n const cwd = process.cwd();\n const sessionTimeout = (this.config as any).memorySessionTimeout ?? 7200;\n // Find active session and close it\n const sessionId = this.memDb.getOrCreateSession(ide, cwd, sessionTimeout);\n this.memDb.endSession(sessionId);\n }\n\n // \u2500\u2500 Maintenance \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n prune(olderThanMs: number): number {\n const cutoff = Date.now() - olderThanMs;\n return this.memDb.prune(cutoff);\n }\n\n async reembed(batchSize = 32): Promise<number> {\n return this.vectorMgr.reembed(batchSize);\n }\n\n lint(): { staleLinks: number; modelMismatch: boolean; staleSessions: number } {\n const staleLinks = this.memDb.findStaleLinks().length;\n const modelMismatch = this.vectorMgr.hasModelMismatch();\n const sessionTimeout = ((this.config as any).memorySessionTimeout ?? 7200) * 1000;\n const staleSessions = this.memDb.closeStaleSessionsOlderThan(sessionTimeout);\n return { staleLinks, modelMismatch, staleSessions };\n }\n\n removeStaleLinks(): number {\n return this.memDb.removeStaleLinks();\n }\n\n // \u2500\u2500 Relations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n compareObservations(input: MemRelationInput): string {\n // Resolve by topic_key if not found by ID\n let obsA = input.observationA;\n let obsB = input.observationB;\n if (!this.memDb.getObservation(obsA)) {\n const byKey = this.memDb.resolveObservationByTopicKey(obsA);\n if (byKey) obsA = byKey.id;\n }\n if (!this.memDb.getObservation(obsB)) {\n const byKey = this.memDb.resolveObservationByTopicKey(obsB);\n if (byKey) obsB = byKey.id;\n }\n return this.memDb.insertRelation({ ...input, observationA: obsA, observationB: obsB });\n }\n\n judgeRelation(relationId: string, relation: RelationType, confidence: number, reason?: string, evidence?: string): void {\n this.memDb.judgeRelation(relationId, relation, confidence, reason, evidence);\n }\n\n ignoreRelation(relationId: string): void {\n this.memDb.ignoreRelation(relationId);\n }\n\n getPendingRelations(limit = 20): MemRelation[] {\n return this.memDb.getPendingRelations(limit);\n }\n\n // \u2500\u2500 Review \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getObservationsForReview(limit = 20): MemObservation[] {\n return this.memDb.getObservationsForReview(limit);\n }\n\n markReviewed(id: string): void {\n this.memDb.markReviewed(id);\n }\n\n // \u2500\u2500 Prompts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async savePrompt(content: string, ide = 'kiro'): Promise<string> {\n const cwd = process.cwd();\n const sessionTimeout = (this.config as any).memorySessionTimeout ?? 7200;\n const sessionId = this.memDb.getOrCreateSession(ide, cwd, sessionTimeout);\n return this.memDb.insertPrompt(sessionId, content);\n }\n\n // \u2500\u2500 Passive capture \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n capturePassive(content: string, sessionId?: string): Array<{ id: string | null; kind: string; content: string }> {\n const kindMap: Record<string, string> = {\n 'key learnings': 'pattern',\n 'learnings': 'pattern',\n 'observations': 'note',\n 'decisions': 'decision',\n 'key changes': 'pattern',\n 'errors': 'error',\n 'patterns': 'pattern',\n };\n\n const results: Array<{ id: string | null; kind: string; content: string }> = [];\n const lines = content.split('\\n');\n let currentKind: string | null = null;\n\n for (const line of lines) {\n const headingMatch = line.match(/^#{1,3}\\s+(.+)/);\n if (headingMatch) {\n const heading = headingMatch[1].toLowerCase().trim();\n currentKind = kindMap[heading] ?? null;\n continue;\n }\n if (!currentKind) continue;\n const bulletMatch = line.match(/^[\\s]*[-*\u2022]\\s+(.+)|^[\\s]*\\d+[.)]\\s+(.+)/);\n if (bulletMatch) {\n const text = (bulletMatch[1] || bulletMatch[2]).trim();\n if (text.length < 5) continue;\n const id = this.memDb.insertObservation(text, { kind: currentKind, source: 'passive', sessionId });\n const symbols = detectSymbols(text, this.db);\n if (id && symbols.length > 0) this.memDb.linkToSymbols(id, symbols.map(s => s.qualifiedName));\n results.push({ id, kind: currentKind, content: text });\n }\n }\n\n return results;\n }\n\n // \u2500\u2500 Topic key \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n suggestTopicKey(kind: string, title: string): string {\n const slug = title\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .trim()\n .split(/\\s+/)\n .slice(0, 6)\n .join('-');\n return `${kind}/${slug}`;\n }\n\n // \u2500\u2500 Conflict scan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n scanConflicts(limit = 50): Array<{ observationA: MemObservation; observationB: MemObservation; similarity: number }> {\n const recent = this.memDb.getObservationsByTimeRange(0, Date.now(), limit);\n const candidates: Array<{ observationA: MemObservation; observationB: MemObservation; similarity: number }> = [];\n const seen = new Set<string>();\n\n for (const obs of recent) {\n if (!obs.content || obs.content.length < 20) continue;\n const words = obs.content.split(/\\s+/).slice(0, 8).join(' ');\n const similar = this.memDb.searchFTS(words, { limit: 5 });\n for (const match of similar) {\n if (match.observation.id === obs.id) continue;\n const pairKey = [obs.id, match.observation.id].sort().join('|');\n if (seen.has(pairKey)) continue;\n seen.add(pairKey);\n const existing = this.memDb.getRelationsForObservations([obs.id]);\n const alreadyLinked = existing.get(obs.id)?.some(r => r.observationA === match.observation.id || r.observationB === match.observation.id);\n if (!alreadyLinked && match.score > 0.3) {\n candidates.push({ observationA: obs, observationB: match.observation, similarity: match.score });\n }\n }\n }\n\n return candidates.slice(0, 20);\n }\n\n // \u2500\u2500 Private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private shouldExclude(text: string): boolean {\n if (this.excludePatterns.length === 0) return false;\n\n // Simple check: if any exclude pattern appears as a substring in the text\n // This is a basic implementation; could use picomatch for glob matching\n for (const pattern of this.excludePatterns) {\n const normalized = pattern.replace(/\\*/g, '');\n if (normalized && text.includes(normalized)) return true;\n }\n return false;\n }\n\n private mergeResults(\n ftsResults: ScoredObservation[],\n vectorResults: ScoredObservation[],\n alpha: number\n ): ScoredObservation[] {\n // Normalize FTS scores (rank is negative in FTS5, lower = better)\n const maxFts = Math.max(...ftsResults.map(r => r.score), 1);\n const normalizedFts = new Map<string, number>();\n for (const r of ftsResults) {\n normalizedFts.set(r.observation.id, r.score / maxFts);\n }\n\n // Vector scores are already 0-1 (cosine similarity)\n const normalizedVec = new Map<string, number>();\n for (const r of vectorResults) {\n normalizedVec.set(r.observation.id, r.score);\n }\n\n // Merge: collect all unique observation IDs\n const allIds = new Set([...normalizedFts.keys(), ...normalizedVec.keys()]);\n const merged: ScoredObservation[] = [];\n\n // Build a lookup for observations\n const obsMap = new Map<string, MemObservation>();\n for (const r of ftsResults) obsMap.set(r.observation.id, r.observation);\n for (const r of vectorResults) obsMap.set(r.observation.id, r.observation);\n\n for (const id of allIds) {\n const ftsScore = normalizedFts.get(id) ?? 0;\n const vecScore = normalizedVec.get(id) ?? 0;\n const hybridScore = (1 - alpha) * ftsScore + alpha * vecScore;\n const obs = obsMap.get(id)!;\n merged.push({ observation: obs, score: hybridScore, scoreSource: 'hybrid' });\n }\n\n merged.sort((a, b) => b.score - a.score);\n return merged;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBA,sBAA+B;AAC/B,sBAAsD;AACtD,qBAA8B;AAC9B,qBAAoC;AACpC,sBAAgC;AAChC,oBAAyB;
|
|
6
|
-
"names": ["import_database", "import_compress", "import_symbols", "import_vectors", "sliced", "ids", "relMap"]
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Memory \u2014 Public API\n *\n * MemoryManager is the single entry point for all memory operations.\n * Coordinates compression, storage, symbol detection, and embedding.\n */\n\nimport type { KiroGraphConfig } from '../config';\nimport type {\n MemObservation,\n MemObservationInput,\n MemSession,\n ScoredObservation,\n MemSearchOptions,\n MemTimelineOptions,\n MemStats,\n CompressResult,\n WatchmenReadyResult,\n MemRelation,\n MemRelationInput,\n RelationType,\n MemPrompt,\n} from './types';\nimport { MemoryDatabase } from './database';\nimport { compressObservation, type CavemanMode } from './compress';\nimport { detectSymbols } from './symbols';\nimport { MemoryVectorManager } from './vectors';\nimport { WatchmenChecker } from '../watchmen';\nimport { logDebug } from '../errors';\nimport { validateObservationInput } from './schema';\n\nexport { MemoryDatabase } from './database';\nexport { compressObservation, extractIdentifiers, type CavemanMode } from './compress';\nexport { detectSymbols } from './symbols';\nexport { MemoryVectorManager } from './vectors';\nexport { MemorySchemaError, OBSERVATION_KINDS, validateObservationInput } from './schema';\nexport * from './types';\n\n// \u2500\u2500 MemoryManager \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class MemoryManager {\n private memDb: MemoryDatabase;\n private vectorMgr: MemoryVectorManager;\n private config: KiroGraphConfig;\n private db: any; // raw sqlite handle for symbol detection\n private cavemanMode: CavemanMode;\n private excludePatterns: string[];\n private watchmenChecker: WatchmenChecker | null;\n private projectRoot: string;\n\n constructor(config: KiroGraphConfig, db: any, projectRoot = process.cwd()) {\n this.config = config;\n this.db = db;\n this.projectRoot = projectRoot;\n this.memDb = new MemoryDatabase(db);\n const kirographDir = require('path').join(projectRoot, '.kirograph');\n this.vectorMgr = new MemoryVectorManager(config, this.memDb, kirographDir);\n this.cavemanMode = (config as any).cavemanMode ?? 'off';\n this.excludePatterns = (config as any).memoryExcludePatterns ?? [];\n this.watchmenChecker = config.enableWatchmen\n ? new WatchmenChecker(config.watchmenThreshold)\n : null;\n }\n\n /**\n * Initialize memory tables. Must be called before any operations.\n */\n initialize(): void {\n this.memDb.initialize();\n }\n\n /**\n * Check if memory is enabled in config.\n */\n static isEnabled(config: KiroGraphConfig): boolean {\n return !!(config as any).enableMemory;\n }\n\n // \u2500\u2500 Store \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Store an observation. Handles:\n * 1. Privacy stripping (<private> blocks)\n * 2. Exclude pattern checking\n * 3. Caveman compression (if enabled)\n * 4. Deduplication (content hash)\n * 5. Symbol detection and linking\n * 6. Embedding (if enabled)\n * 7. Watchmen threshold check (if enabled) \u2014 returns WatchmenReadyResult when synthesis should run\n *\n * Returns the observation ID, a WatchmenReadyResult, or null if skipped (duplicate or excluded).\n * @throws MemorySchemaError if `memoryStrictWrites` is enabled and the input doesn't conform.\n */\n async store(input: MemObservationInput, ide = 'kiro'): Promise<string | WatchmenReadyResult | null> {\n // 0. Opt-in schema validation \u2014 rejects instead of silently corrupting the store.\n if ((this.config as any).memoryStrictWrites) {\n validateObservationInput(input);\n }\n\n let text = input.content;\n\n // 1. Strip <private> blocks\n text = MemoryDatabase.stripPrivate(text);\n if (!text.trim()) return null;\n\n // 2. Check exclude patterns\n if (this.shouldExclude(text)) return null;\n\n // 3. Compress (if caveman is on)\n let contentRaw: string | undefined;\n if (this.cavemanMode !== 'off') {\n const result = compressObservation(text, this.cavemanMode);\n contentRaw = text;\n text = result.compressed;\n }\n\n // 4. Get or create session\n const cwd = process.cwd();\n const sessionTimeout = (this.config as any).memorySessionTimeout ?? 7200;\n const sessionId = this.memDb.getOrCreateSession(ide, cwd, sessionTimeout);\n\n // 5. Insert observation (deduplicates via content_hash)\n const id = this.memDb.insertObservation(text, {\n contentRaw: this.cavemanMode !== 'off' ? contentRaw : undefined,\n kind: input.kind ?? 'note',\n source: input.source ?? 'manual',\n tags: input.tags,\n sessionId,\n topicKey: input.topicKey,\n reviewAfter: input.reviewAfter,\n });\n\n if (!id) {\n logDebug('Memory: duplicate observation skipped');\n return null;\n }\n\n // 6. Detect and link symbols\n const symbols = detectSymbols(text, this.db);\n if (symbols.length > 0) {\n this.memDb.linkToSymbols(id, symbols.map(s => s.qualifiedName));\n }\n\n // Also detect in raw text if different\n if (contentRaw) {\n const rawSymbols = detectSymbols(contentRaw, this.db);\n const extraSymbols = rawSymbols.filter(\n s => !symbols.some(existing => existing.qualifiedName === s.qualifiedName)\n );\n if (extraSymbols.length > 0) {\n this.memDb.linkToSymbols(id, extraSymbols.map(s => s.qualifiedName));\n }\n }\n\n // 7. Embed (async, non-blocking for the store operation)\n const obs = this.memDb.getObservation(id);\n if (obs) {\n await this.vectorMgr.embedObservation(obs);\n }\n\n // 8. Watchmen threshold check\n if (this.watchmenChecker && input.kind !== 'summary') {\n const check = this.watchmenChecker.shouldSynthesize(this.memDb);\n if (check.ready) {\n return this.watchmenChecker.buildReadyResponse(id, check.pendingCount, this.projectRoot);\n }\n }\n\n return id;\n }\n\n // \u2500\u2500 Search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Hybrid search: combines FTS5 and vector search with configurable alpha.\n */\n async search(query: string, opts: MemSearchOptions = {}): Promise<ScoredObservation[]> {\n const limit = opts.limit ?? 10;\n const alpha = opts.alpha ?? (this.config as any).memorySearchAlpha ?? 0.5;\n\n // FTS search\n const ftsResults = this.memDb.searchFTS(query, { ...opts, limit: limit * 2 });\n\n // Vector search (if enabled and no model mismatch)\n let vectorResults: ScoredObservation[] = [];\n if (this.vectorMgr.isEnabled() && !this.vectorMgr.hasModelMismatch()) {\n vectorResults = await this.vectorMgr.search(query, limit * 2);\n }\n\n // If only one source has results, return it directly\n if (vectorResults.length === 0) {\n const sliced = ftsResults.slice(0, limit);\n const ids = sliced.map(r => r.observation.id);\n const relMap = this.memDb.getRelationsForObservations(ids);\n for (const r of sliced) {\n const rels = relMap.get(r.observation.id);\n if (rels && rels.length > 0) r.relations = rels;\n }\n return sliced;\n }\n if (ftsResults.length === 0) {\n const sliced = vectorResults.slice(0, limit);\n const ids = sliced.map(r => r.observation.id);\n const relMap = this.memDb.getRelationsForObservations(ids);\n for (const r of sliced) {\n const rels = relMap.get(r.observation.id);\n if (rels && rels.length > 0) r.relations = rels;\n }\n return sliced;\n }\n\n // Hybrid merge: normalize scores and blend\n const merged = this.mergeResults(ftsResults, vectorResults, alpha);\n const sliced = merged.slice(0, limit);\n\n // Enrich with relations\n const ids = sliced.map(r => r.observation.id);\n const relMap = this.memDb.getRelationsForObservations(ids);\n for (const r of sliced) {\n const rels = relMap.get(r.observation.id);\n if (rels && rels.length > 0) r.relations = rels;\n }\n\n return sliced;\n }\n\n /**\n * Get observations linked to specific symbols.\n * Used by kirograph_context and kirograph_impact.\n */\n getLinkedObservations(qualifiedNames: string[], limit = 3, threshold = 0.3): ScoredObservation[] {\n const results = this.memDb.getLinkedObservationsForSymbols(qualifiedNames, limit * 2);\n return results.filter(r => r.score >= threshold).slice(0, limit);\n }\n\n // \u2500\u2500 Timeline \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * List sessions with their observation counts.\n */\n timeline(opts: MemTimelineOptions = {}): { sessions: MemSession[]; observations: Map<string, MemObservation[]> } {\n const limit = opts.limit ?? 5;\n\n if (opts.sessionId) {\n const session = this.memDb.getSession(opts.sessionId);\n if (!session) return { sessions: [], observations: new Map() };\n const obs = this.memDb.getObservationsBySession(opts.sessionId);\n const map = new Map<string, MemObservation[]>();\n map.set(opts.sessionId, obs);\n return { sessions: [session], observations: map };\n }\n\n const sessions = this.memDb.listSessions(limit);\n const observations = new Map<string, MemObservation[]>();\n for (const session of sessions) {\n const obs = this.memDb.getObservationsBySession(session.id, 10);\n observations.set(session.id, obs);\n }\n return { sessions, observations };\n }\n\n // \u2500\u2500 Status \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getStats(): MemStats {\n const modelName = (this.config as any).embeddingModel ?? 'nomic-ai/nomic-embed-text-v1.5';\n return this.memDb.getStats(modelName);\n }\n\n // \u2500\u2500 Session management \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n endCurrentSession(ide = 'kiro'): void {\n const cwd = process.cwd();\n const sessionTimeout = (this.config as any).memorySessionTimeout ?? 7200;\n // Find active session and close it\n const sessionId = this.memDb.getOrCreateSession(ide, cwd, sessionTimeout);\n this.memDb.endSession(sessionId);\n }\n\n // \u2500\u2500 Maintenance \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n prune(olderThanMs: number): number {\n const cutoff = Date.now() - olderThanMs;\n return this.memDb.prune(cutoff);\n }\n\n async reembed(batchSize = 32): Promise<number> {\n return this.vectorMgr.reembed(batchSize);\n }\n\n lint(): { staleLinks: number; modelMismatch: boolean; staleSessions: number } {\n const staleLinks = this.memDb.findStaleLinks().length;\n const modelMismatch = this.vectorMgr.hasModelMismatch();\n const sessionTimeout = ((this.config as any).memorySessionTimeout ?? 7200) * 1000;\n const staleSessions = this.memDb.closeStaleSessionsOlderThan(sessionTimeout);\n return { staleLinks, modelMismatch, staleSessions };\n }\n\n removeStaleLinks(): number {\n return this.memDb.removeStaleLinks();\n }\n\n // \u2500\u2500 Relations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n compareObservations(input: MemRelationInput): string {\n // Resolve by topic_key if not found by ID\n let obsA = input.observationA;\n let obsB = input.observationB;\n if (!this.memDb.getObservation(obsA)) {\n const byKey = this.memDb.resolveObservationByTopicKey(obsA);\n if (byKey) obsA = byKey.id;\n }\n if (!this.memDb.getObservation(obsB)) {\n const byKey = this.memDb.resolveObservationByTopicKey(obsB);\n if (byKey) obsB = byKey.id;\n }\n return this.memDb.insertRelation({ ...input, observationA: obsA, observationB: obsB });\n }\n\n judgeRelation(relationId: string, relation: RelationType, confidence: number, reason?: string, evidence?: string): void {\n this.memDb.judgeRelation(relationId, relation, confidence, reason, evidence);\n }\n\n ignoreRelation(relationId: string): void {\n this.memDb.ignoreRelation(relationId);\n }\n\n getPendingRelations(limit = 20): MemRelation[] {\n return this.memDb.getPendingRelations(limit);\n }\n\n // \u2500\u2500 Review \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getObservationsForReview(limit = 20): MemObservation[] {\n return this.memDb.getObservationsForReview(limit);\n }\n\n markReviewed(id: string): void {\n this.memDb.markReviewed(id);\n }\n\n // \u2500\u2500 Prompts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async savePrompt(content: string, ide = 'kiro'): Promise<string> {\n const cwd = process.cwd();\n const sessionTimeout = (this.config as any).memorySessionTimeout ?? 7200;\n const sessionId = this.memDb.getOrCreateSession(ide, cwd, sessionTimeout);\n return this.memDb.insertPrompt(sessionId, content);\n }\n\n // \u2500\u2500 Passive capture \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n capturePassive(content: string, sessionId?: string): Array<{ id: string | null; kind: string; content: string }> {\n const kindMap: Record<string, string> = {\n 'key learnings': 'pattern',\n 'learnings': 'pattern',\n 'observations': 'note',\n 'decisions': 'decision',\n 'key changes': 'pattern',\n 'errors': 'error',\n 'patterns': 'pattern',\n };\n\n const results: Array<{ id: string | null; kind: string; content: string }> = [];\n const lines = content.split('\\n');\n let currentKind: string | null = null;\n\n for (const line of lines) {\n const headingMatch = line.match(/^#{1,3}\\s+(.+)/);\n if (headingMatch) {\n const heading = headingMatch[1].toLowerCase().trim();\n currentKind = kindMap[heading] ?? null;\n continue;\n }\n if (!currentKind) continue;\n const bulletMatch = line.match(/^[\\s]*[-*\u2022]\\s+(.+)|^[\\s]*\\d+[.)]\\s+(.+)/);\n if (bulletMatch) {\n const text = (bulletMatch[1] || bulletMatch[2]).trim();\n if (text.length < 5) continue;\n const id = this.memDb.insertObservation(text, { kind: currentKind, source: 'passive', sessionId });\n const symbols = detectSymbols(text, this.db);\n if (id && symbols.length > 0) this.memDb.linkToSymbols(id, symbols.map(s => s.qualifiedName));\n results.push({ id, kind: currentKind, content: text });\n }\n }\n\n return results;\n }\n\n // \u2500\u2500 Topic key \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n suggestTopicKey(kind: string, title: string): string {\n const slug = title\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .trim()\n .split(/\\s+/)\n .slice(0, 6)\n .join('-');\n return `${kind}/${slug}`;\n }\n\n // \u2500\u2500 Conflict scan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n scanConflicts(limit = 50): Array<{ observationA: MemObservation; observationB: MemObservation; similarity: number }> {\n const recent = this.memDb.getObservationsByTimeRange(0, Date.now(), limit);\n const candidates: Array<{ observationA: MemObservation; observationB: MemObservation; similarity: number }> = [];\n const seen = new Set<string>();\n\n for (const obs of recent) {\n if (!obs.content || obs.content.length < 20) continue;\n const words = obs.content.split(/\\s+/).slice(0, 8).join(' ');\n const similar = this.memDb.searchFTS(words, { limit: 5 });\n for (const match of similar) {\n if (match.observation.id === obs.id) continue;\n const pairKey = [obs.id, match.observation.id].sort().join('|');\n if (seen.has(pairKey)) continue;\n seen.add(pairKey);\n const existing = this.memDb.getRelationsForObservations([obs.id]);\n const alreadyLinked = existing.get(obs.id)?.some(r => r.observationA === match.observation.id || r.observationB === match.observation.id);\n if (!alreadyLinked && match.score > 0.3) {\n candidates.push({ observationA: obs, observationB: match.observation, similarity: match.score });\n }\n }\n }\n\n return candidates.slice(0, 20);\n }\n\n // \u2500\u2500 Private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private shouldExclude(text: string): boolean {\n if (this.excludePatterns.length === 0) return false;\n\n // Simple check: if any exclude pattern appears as a substring in the text\n // This is a basic implementation; could use picomatch for glob matching\n for (const pattern of this.excludePatterns) {\n const normalized = pattern.replace(/\\*/g, '');\n if (normalized && text.includes(normalized)) return true;\n }\n return false;\n }\n\n private mergeResults(\n ftsResults: ScoredObservation[],\n vectorResults: ScoredObservation[],\n alpha: number\n ): ScoredObservation[] {\n // Normalize FTS scores (rank is negative in FTS5, lower = better)\n const maxFts = Math.max(...ftsResults.map(r => r.score), 1);\n const normalizedFts = new Map<string, number>();\n for (const r of ftsResults) {\n normalizedFts.set(r.observation.id, r.score / maxFts);\n }\n\n // Vector scores are already 0-1 (cosine similarity)\n const normalizedVec = new Map<string, number>();\n for (const r of vectorResults) {\n normalizedVec.set(r.observation.id, r.score);\n }\n\n // Merge: collect all unique observation IDs\n const allIds = new Set([...normalizedFts.keys(), ...normalizedVec.keys()]);\n const merged: ScoredObservation[] = [];\n\n // Build a lookup for observations\n const obsMap = new Map<string, MemObservation>();\n for (const r of ftsResults) obsMap.set(r.observation.id, r.observation);\n for (const r of vectorResults) obsMap.set(r.observation.id, r.observation);\n\n for (const id of allIds) {\n const ftsScore = normalizedFts.get(id) ?? 0;\n const vecScore = normalizedVec.get(id) ?? 0;\n const hybridScore = (1 - alpha) * ftsScore + alpha * vecScore;\n const obs = obsMap.get(id)!;\n merged.push({ observation: obs, score: hybridScore, scoreSource: 'hybrid' });\n }\n\n merged.sort((a, b) => b.score - a.score);\n return merged;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_database", "import_compress", "import_symbols", "import_vectors", "import_schema", "sliced", "ids", "relMap"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var relation_jev_exports = {};
|
|
20
|
+
__export(relation_jev_exports, {
|
|
21
|
+
classifyRelationWithJev: () => classifyRelationWithJev
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(relation_jev_exports);
|
|
24
|
+
const RELATION_CRITERIA = {
|
|
25
|
+
supersedes: "Observation B replaces or invalidates Observation A as the current decision or fact \u2014 A is now outdated.",
|
|
26
|
+
conflicts_with: "A and B make contradictory claims about the same thing that cannot both be true at the same time.",
|
|
27
|
+
compatible: "A and B are both true and can coexist without contradiction, even if they discuss related things.",
|
|
28
|
+
scoped: "A and B both apply, but in different, non-overlapping contexts or scopes (e.g. different environments, services, or time periods).",
|
|
29
|
+
related: "A and B are on the same topic and worth cross-referencing, but neither conflicts with nor supersedes the other.",
|
|
30
|
+
not_conflict: "A and B only appear related on the surface (shared keywords) but are not meaningfully connected."
|
|
31
|
+
};
|
|
32
|
+
async function classifyRelationWithJev(client, contentA, contentB) {
|
|
33
|
+
const state = `Observation A: ${contentA}
|
|
34
|
+
|
|
35
|
+
Observation B: ${contentB}`;
|
|
36
|
+
const response = await client.ask(state, {
|
|
37
|
+
relation: {
|
|
38
|
+
type: "choice",
|
|
39
|
+
instructions: "How does Observation B relate to Observation A?",
|
|
40
|
+
criteria: RELATION_CRITERIA
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
const answer = response.answers.relation;
|
|
44
|
+
if (!answer || answer.type !== "choice") {
|
|
45
|
+
throw new Error('jev did not return a choice answer for "relation"');
|
|
46
|
+
}
|
|
47
|
+
if (!(answer.choice in RELATION_CRITERIA)) {
|
|
48
|
+
throw new Error(`jev returned an unrecognized relation "${answer.choice}"`);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
relation: answer.choice,
|
|
52
|
+
confidence: answer.confidence ?? 0
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
56
|
+
0 && (module.exports = {
|
|
57
|
+
classifyRelationWithJev
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=relation-jev.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/memory/relation-jev.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Memory \u2014 jev-based relation classification (opt-in, memoryRelationMode: 'jev')\n *\n * Classifies the relation between two observations via a single jev Choice\n * question instead of requiring the calling agent to reason it out manually\n * and supply `relation`/`confidence` itself.\n */\nimport type { JevClient } from '../jev/client';\nimport type { RelationType } from './types';\n\nconst RELATION_CRITERIA: Record<RelationType, string> = {\n supersedes: 'Observation B replaces or invalidates Observation A as the current decision or fact \u2014 A is now outdated.',\n conflicts_with: 'A and B make contradictory claims about the same thing that cannot both be true at the same time.',\n compatible: 'A and B are both true and can coexist without contradiction, even if they discuss related things.',\n scoped: 'A and B both apply, but in different, non-overlapping contexts or scopes (e.g. different environments, services, or time periods).',\n related: 'A and B are on the same topic and worth cross-referencing, but neither conflicts with nor supersedes the other.',\n not_conflict: 'A and B only appear related on the surface (shared keywords) but are not meaningfully connected.',\n};\n\nexport interface JevRelationClassification {\n relation: RelationType;\n confidence: number;\n}\n\n/** Classify how observation B relates to observation A. */\nexport async function classifyRelationWithJev(\n client: JevClient,\n contentA: string,\n contentB: string,\n): Promise<JevRelationClassification> {\n const state = `Observation A: ${contentA}\\n\\nObservation B: ${contentB}`;\n const response = await client.ask(state, {\n relation: {\n type: 'choice',\n instructions: 'How does Observation B relate to Observation A?',\n criteria: RELATION_CRITERIA,\n },\n });\n\n const answer = response.answers.relation;\n if (!answer || answer.type !== 'choice') {\n throw new Error('jev did not return a choice answer for \"relation\"');\n }\n if (!(answer.choice in RELATION_CRITERIA)) {\n throw new Error(`jev returned an unrecognized relation \"${answer.choice}\"`);\n }\n\n return {\n relation: answer.choice as RelationType,\n confidence: answer.confidence ?? 0,\n };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,MAAM,oBAAkD;AAAA,EACtD,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,cAAc;AAChB;AAQA,eAAsB,wBACpB,QACA,UACA,UACoC;AACpC,QAAM,QAAQ,kBAAkB,QAAQ;AAAA;AAAA,iBAAsB,QAAQ;AACtE,QAAM,WAAW,MAAM,OAAO,IAAI,OAAO;AAAA,IACvC,UAAU;AAAA,MACR,MAAM;AAAA,MACN,cAAc;AAAA,MACd,UAAU;AAAA,IACZ;AAAA,EACF,CAAC;AAED,QAAM,SAAS,SAAS,QAAQ;AAChC,MAAI,CAAC,UAAU,OAAO,SAAS,UAAU;AACvC,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AACA,MAAI,EAAE,OAAO,UAAU,oBAAoB;AACzC,UAAM,IAAI,MAAM,0CAA0C,OAAO,MAAM,GAAG;AAAA,EAC5E;AAEA,SAAO;AAAA,IACL,UAAU,OAAO;AAAA,IACjB,YAAY,OAAO,cAAc;AAAA,EACnC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var schema_exports = {};
|
|
20
|
+
__export(schema_exports, {
|
|
21
|
+
MemorySchemaError: () => MemorySchemaError,
|
|
22
|
+
OBSERVATION_KINDS: () => OBSERVATION_KINDS,
|
|
23
|
+
validateObservationInput: () => validateObservationInput
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(schema_exports);
|
|
26
|
+
const OBSERVATION_KINDS = [
|
|
27
|
+
"decision",
|
|
28
|
+
"error",
|
|
29
|
+
"pattern",
|
|
30
|
+
"architecture",
|
|
31
|
+
"summary",
|
|
32
|
+
"note"
|
|
33
|
+
];
|
|
34
|
+
class MemorySchemaError extends Error {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "MemorySchemaError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function validateObservationInput(input) {
|
|
41
|
+
const kind = input.kind ?? "note";
|
|
42
|
+
if (!OBSERVATION_KINDS.includes(kind)) {
|
|
43
|
+
throw new MemorySchemaError(
|
|
44
|
+
`Invalid observation kind "${kind}" \u2014 must be one of: ${OBSERVATION_KINDS.join(", ")}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (input.tags?.some((t) => typeof t !== "string" || t.trim() === "")) {
|
|
48
|
+
throw new MemorySchemaError("Observation tags must be non-empty strings");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
52
|
+
0 && (module.exports = {
|
|
53
|
+
MemorySchemaError,
|
|
54
|
+
OBSERVATION_KINDS,
|
|
55
|
+
validateObservationInput
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/memory/schema.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Memory \u2014 Opt-in write validation\n *\n * Off by default (config `memoryStrictWrites`): MemoryManager.store() has\n * never validated its input \u2014 `kind` is typed as a fixed union in\n * MemObservationInput, but every call site (CLI `--kind`, the\n * `kirograph_mem_store` MCP tool, direct API use) casts through `any` with\n * no runtime check, so an arbitrary string silently lands in the `kind`\n * column and breaks every downstream consumer that pattern-matches against\n * the known set (search filters, watchmen's passive-capture kind map,\n * timeline rendering). When `memoryStrictWrites` is enabled, writes that\n * don't conform are rejected with the violation named instead of silently\n * corrupting the store.\n */\nimport type { MemObservationInput, ObservationKind } from './types';\n\nexport const OBSERVATION_KINDS: readonly ObservationKind[] = [\n 'decision', 'error', 'pattern', 'architecture', 'summary', 'note',\n];\n\nexport class MemorySchemaError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'MemorySchemaError';\n }\n}\n\n/**\n * Validate an observation write against KiroGraph's memory model.\n * Throws MemorySchemaError naming the violation; callers only invoke this\n * when `memoryStrictWrites` is on.\n */\nexport function validateObservationInput(input: MemObservationInput): void {\n const kind = input.kind ?? 'note';\n if (!OBSERVATION_KINDS.includes(kind)) {\n throw new MemorySchemaError(\n `Invalid observation kind \"${kind}\" \u2014 must be one of: ${OBSERVATION_KINDS.join(', ')}`,\n );\n }\n\n if (input.tags?.some(t => typeof t !== 'string' || t.trim() === '')) {\n throw new MemorySchemaError('Observation tags must be non-empty strings');\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBO,MAAM,oBAAgD;AAAA,EAC3D;AAAA,EAAY;AAAA,EAAS;AAAA,EAAW;AAAA,EAAgB;AAAA,EAAW;AAC7D;AAEO,MAAM,0BAA0B,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAOO,SAAS,yBAAyB,OAAkC;AACzE,QAAM,OAAO,MAAM,QAAQ;AAC3B,MAAI,CAAC,kBAAkB,SAAS,IAAI,GAAG;AACrC,UAAM,IAAI;AAAA,MACR,6BAA6B,IAAI,4BAAuB,kBAAkB,KAAK,IAAI,CAAC;AAAA,IACtF;AAAA,EACF;AAEA,MAAI,MAAM,MAAM,KAAK,OAAK,OAAO,MAAM,YAAY,EAAE,KAAK,MAAM,EAAE,GAAG;AACnE,UAAM,IAAI,kBAAkB,4CAA4C;AAAA,EAC1E;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var auth_detection_jev_exports = {};
|
|
20
|
+
__export(auth_detection_jev_exports, {
|
|
21
|
+
checkAuthWithJev: () => checkAuthWithJev
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(auth_detection_jev_exports);
|
|
24
|
+
async function checkAuthWithJev(client, routeName, callPathNames) {
|
|
25
|
+
const state = `Route: ${routeName}
|
|
26
|
+
|
|
27
|
+
Functions/middleware on its call path: ${callPathNames.length > 0 ? callPathNames.join(", ") : "(none found)"}`;
|
|
28
|
+
const response = await client.ask(state, {
|
|
29
|
+
authenticated: {
|
|
30
|
+
type: "noul",
|
|
31
|
+
instructions: "This route is protected by authentication or authorization before its handler logic runs (a session check, token verification, login guard, permission check, etc. somewhere on its call path)."
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
const answer = response.answers.authenticated;
|
|
35
|
+
if (!answer || answer.type !== "noul") {
|
|
36
|
+
throw new Error('jev did not return a noul answer for "authenticated"');
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
authenticated: answer.noul >= 0.5,
|
|
40
|
+
confidence: answer.confidence ?? answer.noul
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
44
|
+
0 && (module.exports = {
|
|
45
|
+
checkAuthWithJev
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=auth-detection-jev.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/security/auth-detection-jev.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph-Sec \u2014 jev-based auth detection (opt-in, securityAuthDetectionMode: 'jev')\n *\n * Backstop for AttackSurfaceAnalyzer's isAuthenticated heuristic: only called\n * when the substring-match heuristic finds no auth pattern on the call path,\n * to catch custom-named auth wrappers the heuristic would otherwise silently\n * misclassify as unauthenticated. The heuristic's positive matches are\n * trusted as-is and never re-checked, keeping this cheap.\n */\nimport type { JevClient } from '../jev/client';\n\nexport interface JevAuthResult {\n authenticated: boolean;\n confidence: number;\n}\n\nexport async function checkAuthWithJev(\n client: JevClient,\n routeName: string,\n callPathNames: string[],\n): Promise<JevAuthResult> {\n const state = `Route: ${routeName}\\n\\nFunctions/middleware on its call path: ${callPathNames.length > 0 ? callPathNames.join(', ') : '(none found)'}`;\n const response = await client.ask(state, {\n authenticated: {\n type: 'noul',\n instructions: 'This route is protected by authentication or authorization before its handler logic runs (a session check, token verification, login guard, permission check, etc. somewhere on its call path).',\n },\n });\n\n const answer = response.answers.authenticated;\n if (!answer || answer.type !== 'noul') {\n throw new Error('jev did not return a noul answer for \"authenticated\"');\n }\n\n return {\n authenticated: answer.noul >= 0.5,\n confidence: answer.confidence ?? answer.noul,\n };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBA,eAAsB,iBACpB,QACA,WACA,eACwB;AACxB,QAAM,QAAQ,UAAU,SAAS;AAAA;AAAA,yCAA8C,cAAc,SAAS,IAAI,cAAc,KAAK,IAAI,IAAI,cAAc;AACnJ,QAAM,WAAW,MAAM,OAAO,IAAI,OAAO;AAAA,IACvC,eAAe;AAAA,MACb,MAAM;AAAA,MACN,cAAc;AAAA,IAChB;AAAA,EACF,CAAC;AAED,QAAM,SAAS,SAAS,QAAQ;AAChC,MAAI,CAAC,UAAU,OAAO,SAAS,QAAQ;AACrC,UAAM,IAAI,MAAM,sDAAsD;AAAA,EACxE;AAEA,SAAO;AAAA,IACL,eAAe,OAAO,QAAQ;AAAA,IAC9B,YAAY,OAAO,cAAc,OAAO;AAAA,EAC1C;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -133,6 +133,7 @@ class SecurityManifestAdapter {
|
|
|
133
133
|
if (!archParser) continue;
|
|
134
134
|
const relManifest = path.relative(this.projectRoot, manifestPath).replace(/\\/g, "/");
|
|
135
135
|
const plugin = this.plugins.get(archParser.name);
|
|
136
|
+
const standalonePlugin = !plugin || !plugin.canExtract(manifestPath) ? [...this.standalonePlugins.values()].find((p) => p.canExtract(manifestPath)) : void 0;
|
|
136
137
|
if (plugin && plugin.canExtract(manifestPath)) {
|
|
137
138
|
try {
|
|
138
139
|
const deps = await plugin.extract(manifestPath, this.projectRoot);
|
|
@@ -142,6 +143,15 @@ class SecurityManifestAdapter {
|
|
|
142
143
|
const message = err instanceof Error ? err.message : String(err);
|
|
143
144
|
errors.push({ file: relManifest, message });
|
|
144
145
|
}
|
|
146
|
+
} else if (standalonePlugin) {
|
|
147
|
+
try {
|
|
148
|
+
const deps = await standalonePlugin.extract(manifestPath, this.projectRoot);
|
|
149
|
+
allDeps.push(...deps);
|
|
150
|
+
manifestsParsed++;
|
|
151
|
+
} catch (err) {
|
|
152
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
153
|
+
errors.push({ file: relManifest, message });
|
|
154
|
+
}
|
|
145
155
|
} else {
|
|
146
156
|
try {
|
|
147
157
|
const packages = await archParser.parse(manifestPath, this.projectRoot);
|
|
@@ -175,9 +185,9 @@ class SecurityManifestAdapter {
|
|
|
175
185
|
async extractFromManifest(manifestPath) {
|
|
176
186
|
const archParser = this.archParsers.find((p) => p.canParse(manifestPath));
|
|
177
187
|
if (!archParser) {
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
180
|
-
return
|
|
188
|
+
const standalonePlugin2 = [...this.standalonePlugins.values()].find((p) => p.canExtract(manifestPath));
|
|
189
|
+
if (standalonePlugin2) {
|
|
190
|
+
return standalonePlugin2.extract(manifestPath, this.projectRoot);
|
|
181
191
|
}
|
|
182
192
|
return [];
|
|
183
193
|
}
|
|
@@ -185,6 +195,10 @@ class SecurityManifestAdapter {
|
|
|
185
195
|
if (plugin && plugin.canExtract(manifestPath)) {
|
|
186
196
|
return plugin.extract(manifestPath, this.projectRoot);
|
|
187
197
|
}
|
|
198
|
+
const standalonePlugin = [...this.standalonePlugins.values()].find((p) => p.canExtract(manifestPath));
|
|
199
|
+
if (standalonePlugin) {
|
|
200
|
+
return standalonePlugin.extract(manifestPath, this.projectRoot);
|
|
201
|
+
}
|
|
188
202
|
const packages = await archParser.parse(manifestPath, this.projectRoot);
|
|
189
203
|
const relManifest = path.relative(this.projectRoot, manifestPath).replace(/\\/g, "/");
|
|
190
204
|
return this._fallbackExtract(packages, archParser.name, relManifest);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/security/manifest/adapter.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * SecurityManifestAdapter\n *\n * Wraps the existing architecture manifest parsers (11 parsers: npm, cargo, go,\n * gradle, maven, csproj, python, scala, elm, ocaml) to additionally extract\n * version constraints and dependency scopes that the architecture module does\n * not capture.\n *\n * The architecture parsers discover manifest files and extract package names into\n * `ArchPackage.externalDeps`. This adapter reuses that discovery infrastructure\n * and layers version/scope extraction on top via ecosystem-specific plugins.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getAllManifestParsers } from '../../architecture/manifest/index';\nimport type { ManifestParser as ArchManifestParser, ArchPackage } from '../../architecture/types';\nimport type { ParsedDependency, ManifestParseResult } from '../types';\n\n// \u2500\u2500 Include/Exclude filter support \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ManifestScanOptions {\n /** Glob patterns for paths to include (relative to projectRoot). Empty = all. */\n include?: string[];\n /** Glob patterns for paths to exclude (relative to projectRoot). */\n exclude?: string[];\n}\n\n// \u2500\u2500 Plugin Interface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * A version extraction plugin provides ecosystem-specific logic to extract\n * version constraints and scopes from manifest files. Each plugin corresponds\n * to one or more architecture parsers and enhances their output with security-\n * relevant metadata.\n */\nexport interface VersionExtractionPlugin {\n /** Ecosystem identifier matching the architecture parser name (e.g. 'npm', 'maven', 'go') */\n ecosystem: string;\n /** Manifest filenames this plugin handles (should match the architecture parser's manifestFiles) */\n manifestFiles: string[];\n /** Returns true if this plugin can handle the given manifest file path */\n canExtract(manifestPath: string): boolean;\n /**\n * Extract version constraints and scopes from a manifest file.\n * This is called AFTER the architecture parser has already discovered the manifest.\n * The plugin receives the manifest path and project root, and returns detailed\n * dependency information including versions and scopes.\n */\n extract(manifestPath: string, projectRoot: string): Promise<ParsedDependency[]>;\n}\n\n// \u2500\u2500 Directories to skip (mirrors architecture module's SKIP_DIRS) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst SKIP_DIRS = new Set([\n 'node_modules', '.git', 'dist', 'build', 'target', '.gradle', '__pycache__',\n '.kirograph', 'vendor', '.cache', 'coverage', '.nyc_output', '_build', '_opam',\n 'elm-stuff', 'zig-cache', 'zig-out',\n]);\n\n// \u2500\u2500 SecurityManifestAdapter \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class SecurityManifestAdapter {\n private readonly archParsers: ArchManifestParser[];\n private readonly plugins: Map<string, VersionExtractionPlugin> = new Map();\n /** Standalone plugins have no corresponding architecture parser \u2014 they own their own discovery. */\n private readonly standalonePlugins: Map<string, VersionExtractionPlugin> = new Map();\n private readonly projectRoot: string;\n private readonly scanOptions: ManifestScanOptions;\n\n constructor(projectRoot: string, scanOptions?: ManifestScanOptions) {\n this.projectRoot = projectRoot;\n this.scanOptions = scanOptions ?? {};\n this.archParsers = getAllManifestParsers();\n }\n\n /**\n * Register a version extraction plugin for an ecosystem that has a\n * corresponding architecture parser. The plugin's `ecosystem` field\n * must equal the architecture parser's `name` for the lookup to work.\n */\n registerPlugin(plugin: VersionExtractionPlugin): void {\n this.plugins.set(plugin.ecosystem, plugin);\n }\n\n /**\n * Register a standalone version extraction plugin for an ecosystem that\n * has NO corresponding architecture parser. The plugin's `manifestFiles`\n * list drives discovery \u2014 the adapter will scan for those filenames and\n * invoke the plugin directly when found.\n */\n registerStandalonePlugin(plugin: VersionExtractionPlugin): void {\n this.standalonePlugins.set(plugin.ecosystem, plugin);\n }\n\n /**\n * Get all registered version extraction plugins (wrapped + standalone).\n */\n getPlugins(): VersionExtractionPlugin[] {\n return [...this.plugins.values(), ...this.standalonePlugins.values()];\n }\n\n /**\n * Get the architecture parsers being wrapped.\n */\n getArchParsers(): ArchManifestParser[] {\n return this.archParsers;\n }\n\n /**\n * Discover all manifest files in the project tree.\n * Includes files known to architecture parsers AND files from standalone plugins.\n * Respects config include/exclude patterns when provided.\n * Returns absolute paths.\n */\n discoverManifests(): string[] {\n const allManifests = this._findManifests(this.projectRoot);\n\n // If no include patterns, return all discovered manifests\n if (!this.scanOptions.include || this.scanOptions.include.length === 0) {\n return allManifests;\n }\n\n // Filter manifests to only those within included paths\n const picomatch = require('picomatch');\n const includeMatchers = this.scanOptions.include.map((p: string) => picomatch(p));\n const excludeMatchers = (this.scanOptions.exclude ?? []).map((p: string) => picomatch(p));\n\n return allManifests.filter(absPath => {\n const rel = path.relative(this.projectRoot, absPath).replace(/\\\\/g, '/');\n const included = includeMatchers.some((m: (s: string) => boolean) => m(rel));\n if (!included) return false;\n const excluded = excludeMatchers.some((m: (s: string) => boolean) => m(rel));\n return !excluded;\n });\n }\n\n /**\n * Run full discovery and extraction:\n * 1. Discover manifest files (reusing architecture's SKIP_DIRS logic)\n * 2. For each manifest, find the matching architecture parser\n * 3. If a version extraction plugin is registered for that ecosystem,\n * use it to extract detailed version/scope information\n * 4. Otherwise, fall back to basic extraction from the architecture parser's\n * ArchPackage.externalDeps (names only, no version info)\n */\n async extractAll(): Promise<ManifestParseResult> {\n const manifestPaths = this.discoverManifests();\n const allDeps: ParsedDependency[] = [];\n const warnings: Array<{ file: string; line?: number; message: string }> = [];\n const errors: Array<{ file: string; message: string }> = [];\n let manifestsParsed = 0;\n\n for (const manifestPath of manifestPaths) {\n const archParser = this.archParsers.find(p => p.canParse(manifestPath));\n if (!archParser) continue;\n\n const relManifest = path.relative(this.projectRoot, manifestPath).replace(/\\\\/g, '/');\n const plugin = this.plugins.get(archParser.name);\n\n if (plugin && plugin.canExtract(manifestPath)) {\n // Use the version extraction plugin for detailed parsing\n try {\n const deps = await plugin.extract(manifestPath, this.projectRoot);\n allDeps.push(...deps);\n manifestsParsed++;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n errors.push({ file: relManifest, message });\n }\n } else {\n // Fallback: use architecture parser output (names only, no versions)\n try {\n const packages = await archParser.parse(manifestPath, this.projectRoot);\n const deps = this._fallbackExtract(packages, archParser.name, relManifest);\n allDeps.push(...deps);\n manifestsParsed++;\n\n if (!plugin) {\n warnings.push({\n file: relManifest,\n message: `No version extraction plugin registered for ecosystem '${archParser.name}'; dependency names extracted without version constraints`,\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n errors.push({ file: relManifest, message });\n }\n }\n }\n\n return {\n dependenciesCreated: allDeps.length,\n manifestsParsed,\n warnings,\n errors,\n };\n }\n\n /**\n * Extract dependencies for a single manifest file.\n * Tries standalone plugins first (when there is no arch parser), then\n * wrapped plugins, then falls back to arch parser output.\n */\n async extractFromManifest(manifestPath: string): Promise<ParsedDependency[]> {\n const archParser = this.archParsers.find(p => p.canParse(manifestPath));\n\n if (!archParser) {\n // No arch parser \u2014 try standalone plugins\n const standalonePlugin = [...this.standalonePlugins.values()]\n .find(p => p.canExtract(manifestPath));\n if (standalonePlugin) {\n return standalonePlugin.extract(manifestPath, this.projectRoot);\n }\n return [];\n }\n\n const plugin = this.plugins.get(archParser.name);\n\n if (plugin && plugin.canExtract(manifestPath)) {\n return plugin.extract(manifestPath, this.projectRoot);\n }\n\n // Fallback: architecture parser output (names only)\n const packages = await archParser.parse(manifestPath, this.projectRoot);\n const relManifest = path.relative(this.projectRoot, manifestPath).replace(/\\\\/g, '/');\n return this._fallbackExtract(packages, archParser.name, relManifest);\n }\n\n /**\n * Get the ecosystem name for a given manifest file path.\n * Returns undefined if no parser handles this file.\n */\n getEcosystem(manifestPath: string): string | undefined {\n const parser = this.archParsers.find(p => p.canParse(manifestPath));\n return parser?.name;\n }\n\n // \u2500\u2500 Private helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Fallback extraction: converts ArchPackage.externalDeps (names only) into\n * ParsedDependency records with empty version constraints and default scope.\n */\n private _fallbackExtract(\n packages: ArchPackage[],\n ecosystem: string,\n sourceManifest: string,\n ): ParsedDependency[] {\n const deps: ParsedDependency[] = [];\n for (const pkg of packages) {\n if (!pkg.externalDeps) continue;\n for (const depName of pkg.externalDeps) {\n deps.push({\n name: depName,\n declaredConstraint: '*',\n scope: 'production',\n ecosystem,\n sourceManifest,\n });\n }\n }\n return deps;\n }\n\n /**\n * Walk the project directory tree looking for manifest files.\n * Mirrors the architecture module's _findManifests() logic, respecting SKIP_DIRS.\n * Includes manifest filenames from standalone plugins in addition to arch parsers.\n */\n private _findManifests(dir: string, results: string[] = []): string[] {\n const manifestFilenames = new Set([\n ...this.archParsers.flatMap(p => p.manifestFiles),\n ...Array.from(this.standalonePlugins.values()).flatMap(p => p.manifestFiles),\n ]);\n\n return this._walkDir(dir, manifestFilenames, results);\n }\n\n private _walkDir(dir: string, manifestFilenames: Set<string>, results: string[]): string[] {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!SKIP_DIRS.has(entry.name)) {\n this._walkDir(path.join(dir, entry.name), manifestFilenames, results);\n }\n } else if (entry.isFile()) {\n if (manifestFilenames.has(entry.name) || entry.name.endsWith('.csproj')) {\n results.push(path.join(dir, entry.name));\n }\n }\n }\n\n return results;\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
6
|
-
"names": []
|
|
4
|
+
"sourcesContent": ["/**\n * SecurityManifestAdapter\n *\n * Wraps the existing architecture manifest parsers (11 parsers: npm, cargo, go,\n * gradle, maven, csproj, python, scala, elm, ocaml) to additionally extract\n * version constraints and dependency scopes that the architecture module does\n * not capture.\n *\n * The architecture parsers discover manifest files and extract package names into\n * `ArchPackage.externalDeps`. This adapter reuses that discovery infrastructure\n * and layers version/scope extraction on top via ecosystem-specific plugins.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getAllManifestParsers } from '../../architecture/manifest/index';\nimport type { ManifestParser as ArchManifestParser, ArchPackage } from '../../architecture/types';\nimport type { ParsedDependency, ManifestParseResult } from '../types';\n\n// \u2500\u2500 Include/Exclude filter support \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ManifestScanOptions {\n /** Glob patterns for paths to include (relative to projectRoot). Empty = all. */\n include?: string[];\n /** Glob patterns for paths to exclude (relative to projectRoot). */\n exclude?: string[];\n}\n\n// \u2500\u2500 Plugin Interface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * A version extraction plugin provides ecosystem-specific logic to extract\n * version constraints and scopes from manifest files. Each plugin corresponds\n * to one or more architecture parsers and enhances their output with security-\n * relevant metadata.\n */\nexport interface VersionExtractionPlugin {\n /** Ecosystem identifier matching the architecture parser name (e.g. 'npm', 'maven', 'go') */\n ecosystem: string;\n /** Manifest filenames this plugin handles (should match the architecture parser's manifestFiles) */\n manifestFiles: string[];\n /** Returns true if this plugin can handle the given manifest file path */\n canExtract(manifestPath: string): boolean;\n /**\n * Extract version constraints and scopes from a manifest file.\n * This is called AFTER the architecture parser has already discovered the manifest.\n * The plugin receives the manifest path and project root, and returns detailed\n * dependency information including versions and scopes.\n */\n extract(manifestPath: string, projectRoot: string): Promise<ParsedDependency[]>;\n}\n\n// \u2500\u2500 Directories to skip (mirrors architecture module's SKIP_DIRS) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst SKIP_DIRS = new Set([\n 'node_modules', '.git', 'dist', 'build', 'target', '.gradle', '__pycache__',\n '.kirograph', 'vendor', '.cache', 'coverage', '.nyc_output', '_build', '_opam',\n 'elm-stuff', 'zig-cache', 'zig-out',\n]);\n\n// \u2500\u2500 SecurityManifestAdapter \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class SecurityManifestAdapter {\n private readonly archParsers: ArchManifestParser[];\n private readonly plugins: Map<string, VersionExtractionPlugin> = new Map();\n /** Standalone plugins have no corresponding architecture parser \u2014 they own their own discovery. */\n private readonly standalonePlugins: Map<string, VersionExtractionPlugin> = new Map();\n private readonly projectRoot: string;\n private readonly scanOptions: ManifestScanOptions;\n\n constructor(projectRoot: string, scanOptions?: ManifestScanOptions) {\n this.projectRoot = projectRoot;\n this.scanOptions = scanOptions ?? {};\n this.archParsers = getAllManifestParsers();\n }\n\n /**\n * Register a version extraction plugin for an ecosystem that has a\n * corresponding architecture parser. The plugin's `ecosystem` field\n * must equal the architecture parser's `name` for the lookup to work.\n */\n registerPlugin(plugin: VersionExtractionPlugin): void {\n this.plugins.set(plugin.ecosystem, plugin);\n }\n\n /**\n * Register a standalone version extraction plugin for an ecosystem that\n * has NO corresponding architecture parser. The plugin's `manifestFiles`\n * list drives discovery \u2014 the adapter will scan for those filenames and\n * invoke the plugin directly when found.\n */\n registerStandalonePlugin(plugin: VersionExtractionPlugin): void {\n this.standalonePlugins.set(plugin.ecosystem, plugin);\n }\n\n /**\n * Get all registered version extraction plugins (wrapped + standalone).\n */\n getPlugins(): VersionExtractionPlugin[] {\n return [...this.plugins.values(), ...this.standalonePlugins.values()];\n }\n\n /**\n * Get the architecture parsers being wrapped.\n */\n getArchParsers(): ArchManifestParser[] {\n return this.archParsers;\n }\n\n /**\n * Discover all manifest files in the project tree.\n * Includes files known to architecture parsers AND files from standalone plugins.\n * Respects config include/exclude patterns when provided.\n * Returns absolute paths.\n */\n discoverManifests(): string[] {\n const allManifests = this._findManifests(this.projectRoot);\n\n // If no include patterns, return all discovered manifests\n if (!this.scanOptions.include || this.scanOptions.include.length === 0) {\n return allManifests;\n }\n\n // Filter manifests to only those within included paths\n const picomatch = require('picomatch');\n const includeMatchers = this.scanOptions.include.map((p: string) => picomatch(p));\n const excludeMatchers = (this.scanOptions.exclude ?? []).map((p: string) => picomatch(p));\n\n return allManifests.filter(absPath => {\n const rel = path.relative(this.projectRoot, absPath).replace(/\\\\/g, '/');\n const included = includeMatchers.some((m: (s: string) => boolean) => m(rel));\n if (!included) return false;\n const excluded = excludeMatchers.some((m: (s: string) => boolean) => m(rel));\n return !excluded;\n });\n }\n\n /**\n * Run full discovery and extraction:\n * 1. Discover manifest files (reusing architecture's SKIP_DIRS logic)\n * 2. For each manifest, find the matching architecture parser\n * 3. If a version extraction plugin is registered for that ecosystem,\n * use it to extract detailed version/scope information\n * 4. Otherwise, fall back to basic extraction from the architecture parser's\n * ArchPackage.externalDeps (names only, no version info)\n */\n async extractAll(): Promise<ManifestParseResult> {\n const manifestPaths = this.discoverManifests();\n const allDeps: ParsedDependency[] = [];\n const warnings: Array<{ file: string; line?: number; message: string }> = [];\n const errors: Array<{ file: string; message: string }> = [];\n let manifestsParsed = 0;\n\n for (const manifestPath of manifestPaths) {\n const archParser = this.archParsers.find(p => p.canParse(manifestPath));\n if (!archParser) continue;\n\n const relManifest = path.relative(this.projectRoot, manifestPath).replace(/\\\\/g, '/');\n const plugin = this.plugins.get(archParser.name);\n\n const standalonePlugin = (!plugin || !plugin.canExtract(manifestPath))\n ? [...this.standalonePlugins.values()].find(p => p.canExtract(manifestPath))\n : undefined;\n\n if (plugin && plugin.canExtract(manifestPath)) {\n // Use the version extraction plugin for detailed parsing\n try {\n const deps = await plugin.extract(manifestPath, this.projectRoot);\n allDeps.push(...deps);\n manifestsParsed++;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n errors.push({ file: relManifest, message });\n }\n } else if (standalonePlugin) {\n // A wrapped plugin exists for this arch parser's ecosystem but\n // doesn't claim this specific file \u2014 e.g. pip's plugin only claims\n // requirements.txt, not pyproject.toml, which the Python arch parser\n // also matches. Use the standalone plugin registered for it instead.\n try {\n const deps = await standalonePlugin.extract(manifestPath, this.projectRoot);\n allDeps.push(...deps);\n manifestsParsed++;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n errors.push({ file: relManifest, message });\n }\n } else {\n // Fallback: use architecture parser output (names only, no versions)\n try {\n const packages = await archParser.parse(manifestPath, this.projectRoot);\n const deps = this._fallbackExtract(packages, archParser.name, relManifest);\n allDeps.push(...deps);\n manifestsParsed++;\n\n if (!plugin) {\n warnings.push({\n file: relManifest,\n message: `No version extraction plugin registered for ecosystem '${archParser.name}'; dependency names extracted without version constraints`,\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n errors.push({ file: relManifest, message });\n }\n }\n }\n\n return {\n dependenciesCreated: allDeps.length,\n manifestsParsed,\n warnings,\n errors,\n };\n }\n\n /**\n * Extract dependencies for a single manifest file.\n * Tries standalone plugins first (when there is no arch parser), then\n * wrapped plugins, then falls back to arch parser output.\n */\n async extractFromManifest(manifestPath: string): Promise<ParsedDependency[]> {\n const archParser = this.archParsers.find(p => p.canParse(manifestPath));\n\n if (!archParser) {\n // No arch parser \u2014 try standalone plugins\n const standalonePlugin = [...this.standalonePlugins.values()]\n .find(p => p.canExtract(manifestPath));\n if (standalonePlugin) {\n return standalonePlugin.extract(manifestPath, this.projectRoot);\n }\n return [];\n }\n\n const plugin = this.plugins.get(archParser.name);\n\n if (plugin && plugin.canExtract(manifestPath)) {\n return plugin.extract(manifestPath, this.projectRoot);\n }\n\n // A wrapped plugin can be registered under the same key as the arch\n // parser without claiming this specific file \u2014 e.g. pip's plugin only\n // claims requirements.txt, but the Python arch parser also matches\n // pyproject.toml. Check standalone plugins before giving up on version\n // info entirely.\n const standalonePlugin = [...this.standalonePlugins.values()]\n .find(p => p.canExtract(manifestPath));\n if (standalonePlugin) {\n return standalonePlugin.extract(manifestPath, this.projectRoot);\n }\n\n // Fallback: architecture parser output (names only)\n const packages = await archParser.parse(manifestPath, this.projectRoot);\n const relManifest = path.relative(this.projectRoot, manifestPath).replace(/\\\\/g, '/');\n return this._fallbackExtract(packages, archParser.name, relManifest);\n }\n\n /**\n * Get the ecosystem name for a given manifest file path.\n * Returns undefined if no parser handles this file.\n */\n getEcosystem(manifestPath: string): string | undefined {\n const parser = this.archParsers.find(p => p.canParse(manifestPath));\n return parser?.name;\n }\n\n // \u2500\u2500 Private helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Fallback extraction: converts ArchPackage.externalDeps (names only) into\n * ParsedDependency records with empty version constraints and default scope.\n */\n private _fallbackExtract(\n packages: ArchPackage[],\n ecosystem: string,\n sourceManifest: string,\n ): ParsedDependency[] {\n const deps: ParsedDependency[] = [];\n for (const pkg of packages) {\n if (!pkg.externalDeps) continue;\n for (const depName of pkg.externalDeps) {\n deps.push({\n name: depName,\n declaredConstraint: '*',\n scope: 'production',\n ecosystem,\n sourceManifest,\n });\n }\n }\n return deps;\n }\n\n /**\n * Walk the project directory tree looking for manifest files.\n * Mirrors the architecture module's _findManifests() logic, respecting SKIP_DIRS.\n * Includes manifest filenames from standalone plugins in addition to arch parsers.\n */\n private _findManifests(dir: string, results: string[] = []): string[] {\n const manifestFilenames = new Set([\n ...this.archParsers.flatMap(p => p.manifestFiles),\n ...Array.from(this.standalonePlugins.values()).flatMap(p => p.manifestFiles),\n ]);\n\n return this._walkDir(dir, manifestFilenames, results);\n }\n\n private _walkDir(dir: string, manifestFilenames: Set<string>, results: string[]): string[] {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!SKIP_DIRS.has(entry.name)) {\n this._walkDir(path.join(dir, entry.name), manifestFilenames, results);\n }\n } else if (entry.isFile()) {\n if (manifestFilenames.has(entry.name) || entry.name.endsWith('.csproj')) {\n results.push(path.join(dir, entry.name));\n }\n }\n }\n\n return results;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["standalonePlugin"]
|
|
7
7
|
}
|
|
@@ -67,8 +67,31 @@ async function parseCargoManifest(manifestPath, projectRoot) {
|
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
const rootCrateName = extractCargoPackageName(content);
|
|
71
|
+
const directNames = new Set(dependencies.map((d) => d.name));
|
|
72
|
+
for (const [name, version] of resolvedVersions) {
|
|
73
|
+
if (directNames.has(name) || name === rootCrateName) continue;
|
|
74
|
+
dependencies.push({
|
|
75
|
+
name,
|
|
76
|
+
declaredConstraint: version,
|
|
77
|
+
resolvedVersion: version,
|
|
78
|
+
scope: "production",
|
|
79
|
+
ecosystem: "cargo",
|
|
80
|
+
sourceManifest: relativeManifest
|
|
81
|
+
});
|
|
82
|
+
}
|
|
70
83
|
return dependencies;
|
|
71
84
|
}
|
|
85
|
+
function extractCargoPackageName(content) {
|
|
86
|
+
const packageSectionMatch = /^\[package\]\s*$/m.exec(content);
|
|
87
|
+
if (!packageSectionMatch) return void 0;
|
|
88
|
+
const startIdx = packageSectionMatch.index + packageSectionMatch[0].length;
|
|
89
|
+
const remaining = content.slice(startIdx);
|
|
90
|
+
const nextSection = remaining.match(/^\[/m);
|
|
91
|
+
const sectionContent = nextSection ? remaining.slice(0, nextSection.index) : remaining;
|
|
92
|
+
const nameMatch = sectionContent.match(/^name\s*=\s*"([^"]+)"/m);
|
|
93
|
+
return nameMatch ? nameMatch[1].trim() : void 0;
|
|
94
|
+
}
|
|
72
95
|
function extractSectionDependencies(content, section, relativeManifest) {
|
|
73
96
|
const deps = [];
|
|
74
97
|
const sectionRegex = new RegExp(`^\\[${escapeRegex(section)}\\]\\s*$`, "m");
|