kirograph 0.23.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -5
- package/dist/bin/commands/configure-mcp.js +104 -0
- package/dist/bin/commands/configure-mcp.js.map +7 -0
- package/dist/bin/commands/memory.js +334 -2
- package/dist/bin/commands/memory.js.map +2 -2
- package/dist/bin/commands/wiki.js +307 -0
- package/dist/bin/commands/wiki.js.map +7 -0
- package/dist/bin/installer/common.js +9 -0
- package/dist/bin/installer/common.js.map +2 -2
- package/dist/bin/installer/config-prompt.js +96 -5
- package/dist/bin/installer/config-prompt.js.map +2 -2
- package/dist/bin/installer/hooks.js +40 -0
- package/dist/bin/installer/hooks.js.map +2 -2
- package/dist/bin/installer/index.js +22 -11
- package/dist/bin/installer/index.js.map +2 -2
- package/dist/bin/installer/instructions.js +27 -1
- package/dist/bin/installer/instructions.js.map +2 -2
- package/dist/bin/installer/mcp.js +37 -4
- package/dist/bin/installer/mcp.js.map +2 -2
- package/dist/bin/installer/prompts.js +3 -3
- package/dist/bin/installer/prompts.js.map +2 -2
- package/dist/bin/installer/steering.js +633 -291
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/installer/targets/index.js.map +1 -1
- package/dist/bin/installer/targets/kiro.js +5 -4
- package/dist/bin/installer/targets/kiro.js.map +2 -2
- package/dist/bin/kirograph.js +3 -1
- package/dist/bin/kirograph.js.map +3 -3
- package/dist/config.js +52 -5
- package/dist/config.js.map +2 -2
- package/dist/db/database.js +11 -0
- package/dist/db/database.js.map +2 -2
- package/dist/db/memory-schema.sql +34 -1
- package/dist/db/wiki-schema.sql +40 -0
- package/dist/mcp/server.js +35 -11
- package/dist/mcp/server.js.map +2 -2
- package/dist/mcp/tool-names.js +112 -1
- package/dist/mcp/tool-names.js.map +2 -2
- package/dist/mcp/tools.js +458 -3
- package/dist/mcp/tools.js.map +2 -2
- package/dist/memory/database.js +131 -7
- package/dist/memory/database.js.map +2 -2
- package/dist/memory/index.js +130 -4
- package/dist/memory/index.js.map +3 -3
- package/dist/memory/types.js.map +1 -1
- package/dist/watchmen/synthesize.js +2 -0
- package/dist/watchmen/synthesize.js.map +2 -2
- package/dist/wiki/database.js +143 -0
- package/dist/wiki/database.js.map +7 -0
- package/dist/wiki/index.js +149 -0
- package/dist/wiki/index.js.map +7 -0
- package/dist/wiki/ingest.js +259 -0
- package/dist/wiki/ingest.js.map +7 -0
- package/dist/wiki/lint.js +93 -0
- package/dist/wiki/lint.js.map +7 -0
- package/dist/wiki/schema.js +148 -0
- package/dist/wiki/schema.js.map +7 -0
- package/dist/wiki/synthesize.js +104 -0
- package/dist/wiki/synthesize.js.map +7 -0
- package/dist/wiki/types.js +17 -0
- package/dist/wiki/types.js.map +7 -0
- package/package.json +5 -3
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} 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 });\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) return ftsResults.slice(0, limit);\n if (ftsResults.length === 0) return vectorResults.slice(0, limit);\n\n // Hybrid merge: normalize scores and blend\n const merged = this.mergeResults(ftsResults, vectorResults, alpha);\n return merged.slice(0, limit);\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 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;
|
|
6
|
-
"names": ["import_database", "import_compress", "import_symbols", "import_vectors"]
|
|
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": "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",
|
|
6
|
+
"names": ["import_database", "import_compress", "import_symbols", "import_vectors", "sliced", "ids", "relMap"]
|
|
7
7
|
}
|
package/dist/memory/types.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/memory/types.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * KiroGraph Memory \u2014 Type definitions\n */\n\n// \u2500\u2500 Session \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 MemSession {\n id: string;\n ide?: string;\n cwd?: string;\n startedAt: number;\n endedAt?: number;\n}\n\n// \u2500\u2500 Observation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 type ObservationKind = 'decision' | 'error' | 'pattern' | 'architecture' | 'summary' | 'note';\nexport type ObservationSource = 'hook' | 'manual' | 'agent';\n\nexport interface MemObservation {\n id: string;\n sessionId?: string;\n content: string;\n contentRaw?: string;\n contentHash: string;\n kind: ObservationKind;\n source: ObservationSource;\n tags?: string[];\n createdAt: number;\n /** Temporal validity: when this fact became valid (epoch ms). */\n validFrom?: number;\n /** Temporal validity: when this fact expires (epoch ms). Null = no expiry. */\n validUntil?: number;\n /** ID of the observation that supersedes this one. */\n supersededBy?: string;\n /** Fact type for temporal classification. */\n factType?: 'observation' | 'decision' | 'procedure' | 'constraint';\n}\n\nexport interface MemObservationInput {\n content: string;\n kind?: ObservationKind;\n source?: ObservationSource;\n tags?: string[];\n}\n\n// \u2500\u2500 Links \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 MemLink {\n observationId: string;\n qualifiedName: string;\n relevance: number;\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\u2500\u2500\n\nexport interface ScoredObservation {\n observation: MemObservation;\n score: number;\n /** Source of the score: 'fts', 'vector', or 'hybrid' */\n scoreSource: 'fts' | 'vector' | 'hybrid';\n}\n\nexport interface MemSearchOptions {\n limit?: number;\n kind?: ObservationKind;\n sessionId?: string;\n /** FTS/vector blend: 0 = FTS only, 1 = vector only, 0.5 = equal blend */\n alpha?: number;\n /** Query facts valid at this timestamp (epoch ms). Filters out expired/superseded observations. */\n asOf?: number;\n}\n\nexport interface MemTimelineOptions {\n limit?: number;\n sessionId?: string;\n}\n\n// \u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 MemStats {\n sessions: number;\n activeSessions: number;\n observations: number;\n links: number;\n vectors: number;\n embeddableCount: number;\n modelMismatch: boolean;\n currentModel?: string;\n}\n\n// \u2500\u2500 Watchmen \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 WatchmenReadyResult {\n id: string;\n watchmenReady: true;\n pendingCount: number;\n /** Synthesis instructions for the active agent. */\n message: string;\n /** Project-relative paths to write the workspace brief to. */\n targetFiles: string[];\n /**\n * Present when Kiro is detected (.kiro/ exists). Path to the steering\n * directory where individual skill files should be written as separate\n * `inclusion: manual` files (e.g. `.kiro/steering/watchmen-<slug>.md`).\n * Absent for all other tools \u2014 embed procedures in the brief instead.\n */\n skillTargetDir?: string;\n}\n\n// \u2500\u2500 Compress \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 CompressResult {\n compressed: string;\n originalLength: number;\n compressedLength: number;\n detectedSymbols: string[];\n}\n"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Memory \u2014 Type definitions\n */\n\n// \u2500\u2500 Session \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 MemSession {\n id: string;\n ide?: string;\n cwd?: string;\n startedAt: number;\n endedAt?: number;\n}\n\n// \u2500\u2500 Observation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 type ObservationKind = 'decision' | 'error' | 'pattern' | 'architecture' | 'summary' | 'note';\nexport type ObservationSource = 'hook' | 'manual' | 'agent';\n\nexport interface MemObservation {\n id: string;\n sessionId?: string;\n content: string;\n contentRaw?: string;\n contentHash: string;\n kind: ObservationKind;\n source: ObservationSource;\n tags?: string[];\n createdAt: number;\n /** Temporal validity: when this fact became valid (epoch ms). */\n validFrom?: number;\n /** Temporal validity: when this fact expires (epoch ms). Null = no expiry. */\n validUntil?: number;\n /** ID of the observation that supersedes this one. */\n supersededBy?: string;\n /** Fact type for temporal classification. */\n factType?: 'observation' | 'decision' | 'procedure' | 'constraint';\n topicKey?: string;\n reviewAfter?: number;\n}\n\nexport interface MemObservationInput {\n content: string;\n kind?: ObservationKind;\n source?: ObservationSource;\n tags?: string[];\n topicKey?: string;\n reviewAfter?: number;\n}\n\n// \u2500\u2500 Links \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 MemLink {\n observationId: string;\n qualifiedName: string;\n relevance: number;\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\u2500\u2500\n\nexport interface ScoredObservation {\n observation: MemObservation;\n score: number;\n /** Source of the score: 'fts', 'vector', or 'hybrid' */\n scoreSource: 'fts' | 'vector' | 'hybrid';\n /** Active relations for this observation (populated by search) */\n relations?: MemRelation[];\n}\n\nexport interface MemSearchOptions {\n limit?: number;\n kind?: ObservationKind;\n sessionId?: string;\n /** FTS/vector blend: 0 = FTS only, 1 = vector only, 0.5 = equal blend */\n alpha?: number;\n /** Query facts valid at this timestamp (epoch ms). Filters out expired/superseded observations. */\n asOf?: number;\n}\n\nexport interface MemTimelineOptions {\n limit?: number;\n sessionId?: string;\n}\n\n// \u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 MemStats {\n sessions: number;\n activeSessions: number;\n observations: number;\n links: number;\n vectors: number;\n embeddableCount: number;\n modelMismatch: boolean;\n currentModel?: string;\n relations: number;\n pendingConflicts: number;\n}\n\n// \u2500\u2500 Watchmen \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 WatchmenReadyResult {\n id: string;\n watchmenReady: true;\n pendingCount: number;\n /** Synthesis instructions for the active agent. */\n message: string;\n /** Project-relative paths to write the workspace brief to. */\n targetFiles: string[];\n /**\n * Present when Kiro is detected (.kiro/ exists). Path to the steering\n * directory where individual skill files should be written as separate\n * `inclusion: manual` files (e.g. `.kiro/steering/watchmen-<slug>.md`).\n * Absent for all other tools \u2014 embed procedures in the brief instead.\n */\n skillTargetDir?: string;\n}\n\n// \u2500\u2500 Compress \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 CompressResult {\n compressed: string;\n originalLength: number;\n compressedLength: number;\n detectedSymbols: string[];\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\u2500\u2500\u2500\u2500\n\nexport type RelationType =\n | 'supersedes'\n | 'conflicts_with'\n | 'compatible'\n | 'scoped'\n | 'related'\n | 'not_conflict';\n\nexport type JudgmentStatus = 'pending' | 'judged' | 'ignored';\n\nexport interface MemRelation {\n id: string;\n observationA: string;\n observationB: string;\n relation: RelationType;\n confidence: number;\n reason?: string;\n evidence?: string;\n judgmentStatus: JudgmentStatus;\n judgedAt?: number;\n createdAt: number;\n}\n\nexport interface MemRelationInput {\n observationA: string;\n observationB: string;\n relation: RelationType;\n confidence?: number;\n reason?: string;\n evidence?: string;\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\u2500\u2500\u2500\n\nexport interface MemPrompt {\n id: string;\n sessionId?: string;\n content: string;\n createdAt: number;\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -34,6 +34,7 @@ __export(synthesize_exports, {
|
|
|
34
34
|
extractGeneratedText: () => extractGeneratedText,
|
|
35
35
|
extractKirographTools: () => extractKirographTools,
|
|
36
36
|
fixKirographToolNames: () => fixKirographToolNames,
|
|
37
|
+
getGenPipeline: () => getGenPipeline,
|
|
37
38
|
parseOutput: () => parseOutput,
|
|
38
39
|
pruneStaleSkills: () => pruneStaleSkills,
|
|
39
40
|
removeHallucinatedContent: () => removeHallucinatedContent,
|
|
@@ -462,6 +463,7 @@ async function runLocalSynthesis(observations, readyResult, modelName, projectRo
|
|
|
462
463
|
extractGeneratedText,
|
|
463
464
|
extractKirographTools,
|
|
464
465
|
fixKirographToolNames,
|
|
466
|
+
getGenPipeline,
|
|
465
467
|
parseOutput,
|
|
466
468
|
pruneStaleSkills,
|
|
467
469
|
removeHallucinatedContent,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/watchmen/synthesize.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * KiroGraph Watchmen \u2014 local model synthesis\n *\n * Runs a local HuggingFace text-generation model (via @huggingface/transformers)\n * to synthesize memory observations into a workspace brief + skill files.\n * Same lazy-singleton pattern as src/memory/vectors.ts.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport type { MemObservation } from '../memory/types';\nimport type { WatchmenReadyResult } from '../memory/types';\n\n// \u2500\u2500 Model detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction isGemma4(modelName: string): boolean {\n return /gemma-?4/i.test(modelName);\n}\n\n// \u2500\u2500 Model singleton (same pattern as vectors.ts) \u2500\u2500\u2500\u2500\u2500\u2500\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\nlet genPipeline: any = null;\nlet genPipelineModel: string | null = null;\nlet genProcessor: any = null;\n\nasync function getGenPipeline(modelName: string, quiet = false) {\n if (genPipeline && genPipelineModel === modelName) return genPipeline;\n const cacheDir = path.join(os.homedir(), '.kirograph', 'models');\n\n const { pipeline, env } = await import('@huggingface/transformers') as any;\n env.cacheDir = cacheDir;\n\n // Same pattern as vectors/index.ts: if model is cached, disable remote access entirely\n // so transformers.js never makes a network request on subsequent runs.\n const cached = require('fs').existsSync(path.join(cacheDir, modelName));\n if (cached) env.allowRemoteModels = false;\n\n // Download progress bar \u2014 same pattern as src/vectors/index.ts\n const fileBytes = new Map<string, { loaded: number; total: number }>();\n let lastFile = '';\n\n function renderProgress(file: string, loaded: number, total: number, done: boolean) {\n const entry = fileBytes.get(file) ?? { loaded: 0, total: 0 };\n if (total > 0) entry.total = total;\n entry.loaded = done ? entry.total : loaded;\n fileBytes.set(file, entry);\n\n const knownFiles = Array.from(fileBytes.values()).filter(f => f.total > 0);\n const totalLoaded = knownFiles.reduce((s, f) => s + f.loaded, 0);\n const totalBytes = knownFiles.reduce((s, f) => s + f.total, 0);\n const pct = totalBytes > 0 ? Math.min((totalLoaded / totalBytes) * 100, 100) : 0;\n const filled = Math.round(pct / 5);\n const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(20 - filled);\n const mb = (totalLoaded / 1024 / 1024).toFixed(1);\n const totalMb = (totalBytes / 1024 / 1024).toFixed(1);\n process.stdout.write(`\\r [${bar}] ${pct.toFixed(0).padStart(3)}% ${mb} / ${totalMb} MB `);\n }\n\n const dtype = isGemma4(modelName) ? 'q4f16' : 'q4';\n genPipeline = await pipeline('text-generation', modelName, {\n cache_dir: cacheDir,\n dtype,\n ...(!cached && !quiet ? {\n progress_callback: (p: { status: string; file?: string; loaded?: number; total?: number }) => {\n if (p.status === 'progress' && p.file) {\n lastFile = p.file;\n renderProgress(p.file, p.loaded ?? 0, p.total ?? 0, false);\n } else if (p.status === 'done' && lastFile) {\n renderProgress(lastFile, 1, 1, true);\n lastFile = '';\n }\n },\n } : {}),\n } as any);\n\n if (!cached && !quiet && fileBytes.size > 0) {\n process.stdout.write('\\n'); // newline after progress bar\n }\n\n genProcessor = null;\n genPipelineModel = modelName;\n return genPipeline;\n}\n\n// \u2500\u2500 Prompt building \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nconst SYSTEM_PROMPT = `You are a software project assistant. You receive coding session observations and produce structured workspace documentation.\n\nRules:\n- Output ONLY raw markdown. Do NOT wrap in code fences (\\`\\`\\`markdown or \\`\\`\\`).\n- Do NOT add preamble, explanation, or commentary outside the markdown sections.\n- Use exactly the section headings requested. Do not invent new heading names.\n- CRITICAL: copy tool names, function names, CLI commands, and symbol names EXACTLY as they appear in the observations. Do not paraphrase, rename, or abbreviate them. For example: if an observation says \"kirograph_impact\", write \"kirograph_impact\" \u2014 never \"kilo-graph\", \"kirograph impact\", \"kiloGraph\", or any other variation.`;\n\n/** Extract kirograph_* tool names from observations for post-processing correction. */\nexport function extractKirographTools(observations: MemObservation[]): string[] {\n const tools = new Set<string>();\n for (const obs of observations) {\n for (const m of obs.content.matchAll(/\\bkirograph_\\w+/g)) tools.add(m[0]);\n }\n return [...tools];\n}\n\n/**\n * Replace common model hallucinations of kirograph tool names with the correct form.\n * The model often splits \"kirograph_impact\" into \"kilo-graph\", \"kyrograph_\", etc.\n */\n/**\n * Replace model hallucinations of kirograph tool names with the correct form.\n * Matches broad variants: kilo-graph, kyrograph, kirographtest, kirograph_X, etc.\n * Uses the known tool list from observations to pick the right replacement.\n */\nexport function fixKirographToolNames(text: string, knownTools: string[]): string {\n if (knownTools.length === 0) return text;\n\n // Map tool suffix \u2192 correct full name (e.g. 'impact' \u2192 'kirograph_impact')\n const toolMap = new Map<string, string>();\n for (const tool of knownTools) {\n const parts = tool.split('_');\n if (parts.length >= 2) toolMap.set(parts.slice(1).join('_'), tool);\n }\n\n return text.replace(\n /\\b(?:kilo|kiro|kyro|kero)[a-z]*[_\\- ]?graph[a-z_\\- ]*\\b/gi,\n (match) => {\n const lower = match.toLowerCase().replace(/[^a-z]/g, '');\n // Find the known tool whose suffix appears in the match\n for (const [suffix, tool] of toolMap) {\n if (lower.includes(suffix.replace(/_/g, ''))) return tool;\n }\n // Fallback: return the first known tool (likely the only one)\n return knownTools[0];\n }\n );\n}\n\n/** Prompt for the workspace brief only \u2014 no skills. */\nexport function buildBriefPrompt(observations: MemObservation[]): string {\n const byKind: Record<string, string[]> = {};\n for (const obs of observations) {\n (byKind[obs.kind] ??= []).push(obs.content);\n }\n\n const lines: string[] = ['Observations from recent coding sessions:\\n'];\n for (const [kind, contents] of Object.entries(byKind)) {\n lines.push(`[${kind}]`);\n for (const c of contents) lines.push(`- ${c}`);\n lines.push('');\n }\n\n lines.push(\n 'Write a workspace brief using ONLY these sections (skip any with no content):\\n',\n '## Decisions',\n '(one bullet per architectural or implementation decision)\\n',\n '## Known Errors & Fixes',\n '(one bullet per error \u2014 what broke and the fix)\\n',\n '## Recurring Patterns',\n '(one bullet per recurring pattern)\\n',\n '## Architecture Notes',\n '(one bullet per architectural constraint)\\n',\n 'Be concise. One fact per bullet. Do not repeat observations verbatim. Do NOT use code fences.',\n );\n\n return lines.join('\\n');\n}\n\n/** Prompt for a single skill file given a group of related observations. */\nexport function buildSkillPrompt(procedureObservations: string[]): string {\n const lines = [\n 'The following observations all describe the same recurring development procedure:\\n',\n ...procedureObservations.map(o => `- ${o}`),\n '',\n 'Write a skill file in EXACTLY this format (fill in the placeholders, keep the structure):\\n',\n '---',\n 'name: <short-kebab-case-slug>',\n 'description: <one sentence describing the procedure>',\n 'trigger_phrases:',\n ' - <phrase that means \"I should use this skill\">',\n ' - <another trigger phrase>',\n '---',\n '',\n '## When to use',\n '<One sentence: when should a developer run this procedure?>',\n '',\n '## Steps',\n '1. <First step \u2014 include the exact command/tool if relevant>',\n '2. <Second step>',\n '3. <Third step>',\n '',\n 'Use the EXACT tool names from the observations (e.g. kirograph_impact, not \"kirograph impact\").',\n 'Write ONLY the skill file content. No explanation, no preamble, no code fences.',\n ];\n return lines.join('\\n');\n}\n\n/** Group pattern/decision observations that share a common procedure theme. */\nexport function detectRecurringProcedures(observations: MemObservation[]): string[][] {\n // Only look at pattern observations \u2014 they most likely describe recurring steps\n const patterns = observations\n .filter(o => o.kind === 'pattern' || o.kind === 'decision')\n .map(o => o.content);\n\n if (patterns.length < 3) return [];\n\n // Simple heuristic: if 3+ pattern observations exist, treat them as one procedure group.\n // A more sophisticated implementation could cluster by keyword overlap.\n return [patterns];\n}\n\n// \u2500\u2500 Output parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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/** Extract the assistant reply from a transformers.js text-generation output. */\nexport function extractGeneratedText(output: any): string {\n const generated = output?.[0]?.generated_text;\n let text: string;\n if (Array.isArray(generated)) {\n // Chat model: returns array of messages \u2014 last one is assistant\n text = generated.at(-1)?.content ?? '';\n } else {\n // Plain text model\n text = typeof generated === 'string' ? generated : '';\n }\n return stripCodeFences(text);\n}\n\n/** Strip outer markdown code fences that small models tend to add. */\nexport function stripCodeFences(text: string): string {\n return text\n .replace(/^```(?:markdown|md)?\\n?/m, '')\n .replace(/\\n?```\\s*$/m, '')\n .trim();\n}\n\n/**\n * Detect and truncate repetition loops that small models fall into.\n * Splits into sentences and cuts at the first one that closely repeats a prior sentence.\n */\nexport function truncateAtRepetitionLoop(text: string): string {\n const sentences = text.split(/(?<=[.!?])\\s+/);\n const seen: string[] = [];\n const result: string[] = [];\n\n for (const sentence of sentences) {\n const normalized = sentence.toLowerCase().replace(/[^a-z0-9]/g, '').slice(0, 60);\n if (normalized.length < 10) { result.push(sentence); continue; }\n // If this sentence is very similar to a recent one, stop\n const isDuplicate = seen.slice(-5).some(s => {\n const overlap = [...normalized].filter((c, i) => s[i] === c).length;\n return overlap / normalized.length > 0.8;\n });\n if (isDuplicate) break;\n seen.push(normalized);\n result.push(sentence);\n }\n\n return result.join(' ').trim();\n}\n\n/** Remove hallucinated image markdown and leaked run-IDs from model output. */\nexport function removeHallucinatedContent(text: string): string {\n return text\n // Remove image markdown:  \u2014 models hallucinate URLs\n .replace(/!\\[.*?\\]\\(.*?\\)/g, '')\n // Remove run-id timestamps [1780913041] wherever they appear (inline or line-start)\n .replace(/\\*?\\*?\\[\\d{9,}\\]\\*?\\*?\\s*/g, '')\n // Collapse 3+ consecutive blank lines into 2\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\n/**\n * Split generated text into brief body and skill sections.\n *\n * Primary: looks for `## Skill: <slug>` (explicit format requested in prompt).\n * Fallback: looks for `### <Title>` subsections inside a `## Recurring Procedures`\n * block \u2014 small models often use this structure instead.\n */\nexport function parseOutput(text: string): { brief: string; skills: Array<{ slug: string; content: string }> } {\n // \u2500\u2500 Primary: flexible skill heading detection \u2500\u2500\u2500\u2500\u2500\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 // Small models use many variations:\n // ## Skill: slug (ideal)\n // # Skills: Title (common)\n // Skill: Title (no heading marker)\n // Skill:\\nTitle (name on next line)\n const skillRegex = /^(?:#{1,3}\\s+)?Skills?:\\s*(.*)$/gim;\n const skillMatches: Array<{ index: number; slug: string }> = [];\n let match: RegExpExecArray | null;\n while ((match = skillRegex.exec(text)) !== null) {\n let title = match[1].trim();\n // If no title on same line, grab the next non-empty line as the title\n if (!title) {\n const rest = text.slice(match.index + match[0].length);\n const nextLine = rest.split('\\n').find(l => l.trim());\n title = nextLine?.trim() ?? '';\n }\n if (!title) continue;\n const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n skillMatches.push({ index: match.index, slug });\n }\n\n if (skillMatches.length > 0) {\n const briefEnd = skillMatches[0].index;\n const skills = skillMatches.map((m, i) => {\n const start = m.index;\n const end = i + 1 < skillMatches.length ? skillMatches[i + 1].index : text.length;\n // Normalise heading to ## Skill: <slug> for the file writer\n const body = text.slice(start, end).replace(/^#{1,3} Skills?: .+$/im, '').trim();\n return { slug: m.slug, content: `## Skill: ${m.slug}\\n\\n${body}` };\n });\n return { brief: text.slice(0, briefEnd).trim(), skills };\n }\n\n // \u2500\u2500 Fallback: ### subsections inside ## Recurring Procedures \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const recurringMatch = /^## Recurring Procedures\\b.*$/m.exec(text);\n if (recurringMatch) {\n const sectionStart = recurringMatch.index;\n // Find the end of this section (next ## heading or end of text)\n const nextH2 = /^## /m.exec(text.slice(sectionStart + recurringMatch[0].length));\n const sectionEnd = nextH2\n ? sectionStart + recurringMatch[0].length + nextH2.index\n : text.length;\n\n const sectionBody = text.slice(sectionStart, sectionEnd);\n const subRegex = /^### (.+)$/gm;\n const subMatches: Array<{ index: number; title: string }> = [];\n let sub: RegExpExecArray | null;\n while ((sub = subRegex.exec(sectionBody)) !== null) {\n subMatches.push({ index: sectionStart + sub.index, title: sub[1].trim() });\n }\n\n if (subMatches.length > 0) {\n const brief = (text.slice(0, sectionStart) + text.slice(sectionEnd)).trim();\n const skills = subMatches.map((m, i) => {\n const start = m.index;\n const end = i + 1 < subMatches.length ? subMatches[i + 1].index : sectionEnd;\n const slug = m.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n // Reformat as ## Skill: heading for the file writer\n const content = `## Skill: ${slug}\\n\\n` + text.slice(start + m.title.length + 4, end).trim();\n return { slug, content };\n });\n return { brief, skills };\n }\n }\n\n return { brief: text.trim(), skills: [] };\n}\n\n// \u2500\u2500 File writing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nconst WATCHMEN_BLOCK_START = '<!-- kirograph-watchmen:start -->';\nconst WATCHMEN_BLOCK_END = '<!-- kirograph-watchmen:end -->';\n\n/** Upsert the KiroGraph Watchmen block in a plain text/markdown file. */\nexport function upsertBriefBlock(filePath: string, brief: string): void {\n const header = `## KiroGraph Watchmen\\n\\n_Auto-generated by KiroGraph Watchmen. Last updated: ${new Date().toISOString().slice(0, 10)}._\\n\\n`;\n const block = `${WATCHMEN_BLOCK_START}\\n${header}${brief}\\n${WATCHMEN_BLOCK_END}`;\n\n let existing = '';\n try { existing = fs.readFileSync(filePath, 'utf8'); } catch { /* new file */ }\n\n const startIdx = existing.indexOf(WATCHMEN_BLOCK_START);\n const endIdx = existing.indexOf(WATCHMEN_BLOCK_END);\n\n let updated: string;\n if (startIdx !== -1 && endIdx !== -1) {\n updated = existing.slice(0, startIdx) + block + existing.slice(endIdx + WATCHMEN_BLOCK_END.length);\n } else {\n updated = existing ? `${existing}\\n\\n${block}\\n` : `${block}\\n`;\n }\n\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, updated, 'utf8');\n}\n\n/** Write the Kiro steering workspace brief (inclusion: always). */\nexport function writeKiroBrief(steeringDir: string, brief: string): void {\n const content =\n `---\\ninclusion: always\\n---\\n\\n` +\n `# Workspace Knowledge (KiroGraph Watchmen)\\n\\n` +\n `_Auto-generated. Last updated: ${new Date().toISOString().slice(0, 10)}._\\n\\n` +\n `${brief}\\n`;\n const filePath = path.join(steeringDir, 'kirograph-watchmen.md');\n fs.mkdirSync(steeringDir, { recursive: true });\n fs.writeFileSync(filePath, content, 'utf8');\n}\n\n/**\n * Write a Kiro skill file (inclusion: manual) with SKILL.md format.\n * Merges the `inclusion: manual` Kiro frontmatter with whatever name/description/\n * trigger_phrases frontmatter the model generated inside the content.\n */\nexport function writeKiroSkill(steeringDir: string, slug: string, content: string): void {\n // The model may have produced its own frontmatter block \u2014 extract it\n const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);\n let modelFrontmatter = '';\n let body = content;\n\n if (frontmatterMatch) {\n modelFrontmatter = frontmatterMatch[1].trim();\n body = frontmatterMatch[2].trim();\n }\n\n // Build the final frontmatter: Kiro inclusion first, then model metadata\n const frontmatter = ['---', 'inclusion: manual'];\n if (modelFrontmatter) {\n for (const line of modelFrontmatter.split('\\n')) {\n // Skip if it would duplicate inclusion\n if (!line.startsWith('inclusion:')) frontmatter.push(line);\n }\n }\n frontmatter.push('---');\n\n const filePath = path.join(steeringDir, `watchmen-${slug}.md`);\n fs.mkdirSync(steeringDir, { recursive: true });\n fs.writeFileSync(filePath, frontmatter.join('\\n') + '\\n\\n' + body + '\\n', 'utf8');\n}\n\n/** Remove stale watchmen-*.md skill files that are not in the current slug set. */\nexport function pruneStaleSkills(steeringDir: string, currentSlugs: Set<string>): string[] {\n const pruned: string[] = [];\n if (!fs.existsSync(steeringDir)) return pruned;\n for (const file of fs.readdirSync(steeringDir)) {\n if (!file.startsWith('watchmen-') || !file.endsWith('.md')) continue;\n const slug = file.slice('watchmen-'.length, -'.md'.length);\n if (!currentSlugs.has(slug)) {\n fs.unlinkSync(path.join(steeringDir, file));\n pruned.push(file);\n }\n }\n return pruned;\n}\n\n// \u2500\u2500 Main synthesis entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 SynthesisResult {\n skipped: boolean;\n reason?: string;\n filesWritten: string[];\n skillsWritten: string[];\n skillsPruned: string[];\n summaryObservation: string;\n}\n\n/** Run one model call with a token progress bar. Returns cleaned text. */\nasync function generate(\n pipe: any,\n systemPrompt: string,\n userPrompt: string,\n modelName: string,\n label: string,\n quiet: boolean,\n maxNewTokens = 500,\n): Promise<string> {\n const knownTools: string[] = []; // filled per-call by caller if needed\n let tokenCount = 0;\n\n let streamer: any = undefined;\n if (!quiet && pipe.tokenizer) {\n const { TextStreamer } = await import('@huggingface/transformers') as any;\n streamer = new TextStreamer(pipe.tokenizer, {\n skip_prompt: true,\n skip_special_tokens: true,\n callback_function: () => {\n tokenCount++;\n const pct = Math.min(Math.round((tokenCount / maxNewTokens) * 100), 99);\n const filled = Math.round(pct / 5);\n const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(20 - filled);\n process.stdout.write(`\\r ${label.padEnd(14)} [${bar}] ${String(tokenCount).padStart(3)} tokens`);\n },\n });\n }\n\n const genOpts: any = {\n ...(isGemma4(modelName)\n ? { temperature: 1.0, top_p: 0.95, top_k: 64, do_sample: true }\n : { do_sample: false, repetition_penalty: 1.3 }),\n max_new_tokens: maxNewTokens,\n ...(streamer ? { streamer } : {}),\n };\n\n const messages = [\n { role: 'system' as const, content: systemPrompt },\n { role: 'user' as const, content: userPrompt },\n ];\n\n const output = await pipe(messages, genOpts);\n\n if (!quiet && tokenCount > 0) {\n process.stdout.write(`\\r ${' '.repeat(64)}\\r`);\n }\n\n return truncateAtRepetitionLoop(\n removeHallucinatedContent(stripCodeFences(extractGeneratedText(output)))\n );\n}\n\nexport async function runLocalSynthesis(\n observations: MemObservation[],\n readyResult: WatchmenReadyResult,\n modelName: string,\n projectRoot: string,\n quiet = false,\n): Promise<SynthesisResult> {\n const pipe = await getGenPipeline(modelName, quiet);\n const knownTools = extractKirographTools(observations);\n\n const filesWritten: string[] = [];\n const skillsWritten: string[] = [];\n let skillsPruned: string[] = [];\n\n // \u2500\u2500 Pass 1: workspace brief \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 if (!quiet) process.stdout.write(' Generating brief...\\n');\n const briefRaw = await generate(pipe, SYSTEM_PROMPT, buildBriefPrompt(observations), modelName, 'Brief', quiet);\n const brief = fixKirographToolNames(briefRaw, knownTools);\n\n for (const target of readyResult.targetFiles) {\n const absPath = path.isAbsolute(target) ? target : path.join(projectRoot, target);\n if (target === '.kiro/steering/kirograph-watchmen.md') {\n const steeringDir = path.join(projectRoot, readyResult.skillTargetDir ?? '.kiro/steering');\n writeKiroBrief(steeringDir, brief);\n } else {\n upsertBriefBlock(absPath, brief);\n }\n filesWritten.push(target);\n }\n\n // \u2500\u2500 Pass 2: skill files (Kiro only, one call per procedure group) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (readyResult.skillTargetDir) {\n const steeringDir = path.join(projectRoot, readyResult.skillTargetDir);\n const currentSlugs = new Set<string>();\n const procedureGroups = detectRecurringProcedures(observations);\n\n for (let i = 0; i < procedureGroups.length; i++) {\n const group = procedureGroups[i];\n const label = `Skill ${i + 1}/${procedureGroups.length}`;\n if (!quiet) process.stdout.write(` Generating ${label}...\\n`);\n\n const skillRaw = await generate(\n pipe, SYSTEM_PROMPT, buildSkillPrompt(group), modelName, label, quiet, 350,\n );\n const skillText = fixKirographToolNames(skillRaw, knownTools);\n\n // Extract slug from name: field in model frontmatter, or derive from content\n const nameMatch = skillText.match(/^name:\\s*(.+)$/m);\n const slug = nameMatch\n ? nameMatch[1].trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')\n : `procedure-${i + 1}`;\n\n if (!currentSlugs.has(slug)) {\n writeKiroSkill(steeringDir, slug, skillText);\n skillsWritten.push(`watchmen-${slug}.md`);\n currentSlugs.add(slug);\n }\n }\n\n skillsPruned = pruneStaleSkills(steeringDir, currentSlugs);\n }\n\n const summaryParts = [`Synthesized ${observations.length} observations`];\n if (filesWritten.length) summaryParts.push(`into ${filesWritten.join(', ')}`);\n if (skillsWritten.length) summaryParts.push(`wrote skills: ${skillsWritten.join(', ')}`);\n if (skillsPruned.length) summaryParts.push(`pruned: ${skillsPruned.join(', ')}`);\n\n return {\n skipped: false,\n filesWritten,\n skillsWritten,\n skillsPruned,\n summaryObservation: summaryParts.join('; ') + '.',\n };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Watchmen \u2014 local model synthesis\n *\n * Runs a local HuggingFace text-generation model (via @huggingface/transformers)\n * to synthesize memory observations into a workspace brief + skill files.\n * Same lazy-singleton pattern as src/memory/vectors.ts.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport type { MemObservation } from '../memory/types';\nimport type { WatchmenReadyResult } from '../memory/types';\n\n// \u2500\u2500 Model detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nfunction isGemma4(modelName: string): boolean {\n return /gemma-?4/i.test(modelName);\n}\n\n// \u2500\u2500 Model singleton (same pattern as vectors.ts) \u2500\u2500\u2500\u2500\u2500\u2500\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\nlet genPipeline: any = null;\nlet genPipelineModel: string | null = null;\nlet genProcessor: any = null;\n\nexport async function getGenPipeline(modelName: string, quiet = false) {\n if (genPipeline && genPipelineModel === modelName) return genPipeline;\n const cacheDir = path.join(os.homedir(), '.kirograph', 'models');\n\n const { pipeline, env } = await import('@huggingface/transformers') as any;\n env.cacheDir = cacheDir;\n\n // Same pattern as vectors/index.ts: if model is cached, disable remote access entirely\n // so transformers.js never makes a network request on subsequent runs.\n const cached = require('fs').existsSync(path.join(cacheDir, modelName));\n if (cached) env.allowRemoteModels = false;\n\n // Download progress bar \u2014 same pattern as src/vectors/index.ts\n const fileBytes = new Map<string, { loaded: number; total: number }>();\n let lastFile = '';\n\n function renderProgress(file: string, loaded: number, total: number, done: boolean) {\n const entry = fileBytes.get(file) ?? { loaded: 0, total: 0 };\n if (total > 0) entry.total = total;\n entry.loaded = done ? entry.total : loaded;\n fileBytes.set(file, entry);\n\n const knownFiles = Array.from(fileBytes.values()).filter(f => f.total > 0);\n const totalLoaded = knownFiles.reduce((s, f) => s + f.loaded, 0);\n const totalBytes = knownFiles.reduce((s, f) => s + f.total, 0);\n const pct = totalBytes > 0 ? Math.min((totalLoaded / totalBytes) * 100, 100) : 0;\n const filled = Math.round(pct / 5);\n const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(20 - filled);\n const mb = (totalLoaded / 1024 / 1024).toFixed(1);\n const totalMb = (totalBytes / 1024 / 1024).toFixed(1);\n process.stdout.write(`\\r [${bar}] ${pct.toFixed(0).padStart(3)}% ${mb} / ${totalMb} MB `);\n }\n\n const dtype = isGemma4(modelName) ? 'q4f16' : 'q4';\n genPipeline = await pipeline('text-generation', modelName, {\n cache_dir: cacheDir,\n dtype,\n ...(!cached && !quiet ? {\n progress_callback: (p: { status: string; file?: string; loaded?: number; total?: number }) => {\n if (p.status === 'progress' && p.file) {\n lastFile = p.file;\n renderProgress(p.file, p.loaded ?? 0, p.total ?? 0, false);\n } else if (p.status === 'done' && lastFile) {\n renderProgress(lastFile, 1, 1, true);\n lastFile = '';\n }\n },\n } : {}),\n } as any);\n\n if (!cached && !quiet && fileBytes.size > 0) {\n process.stdout.write('\\n'); // newline after progress bar\n }\n\n genProcessor = null;\n genPipelineModel = modelName;\n return genPipeline;\n}\n\n// \u2500\u2500 Prompt building \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nconst SYSTEM_PROMPT = `You are a software project assistant. You receive coding session observations and produce structured workspace documentation.\n\nRules:\n- Output ONLY raw markdown. Do NOT wrap in code fences (\\`\\`\\`markdown or \\`\\`\\`).\n- Do NOT add preamble, explanation, or commentary outside the markdown sections.\n- Use exactly the section headings requested. Do not invent new heading names.\n- CRITICAL: copy tool names, function names, CLI commands, and symbol names EXACTLY as they appear in the observations. Do not paraphrase, rename, or abbreviate them. For example: if an observation says \"kirograph_impact\", write \"kirograph_impact\" \u2014 never \"kilo-graph\", \"kirograph impact\", \"kiloGraph\", or any other variation.`;\n\n/** Extract kirograph_* tool names from observations for post-processing correction. */\nexport function extractKirographTools(observations: MemObservation[]): string[] {\n const tools = new Set<string>();\n for (const obs of observations) {\n for (const m of obs.content.matchAll(/\\bkirograph_\\w+/g)) tools.add(m[0]);\n }\n return [...tools];\n}\n\n/**\n * Replace common model hallucinations of kirograph tool names with the correct form.\n * The model often splits \"kirograph_impact\" into \"kilo-graph\", \"kyrograph_\", etc.\n */\n/**\n * Replace model hallucinations of kirograph tool names with the correct form.\n * Matches broad variants: kilo-graph, kyrograph, kirographtest, kirograph_X, etc.\n * Uses the known tool list from observations to pick the right replacement.\n */\nexport function fixKirographToolNames(text: string, knownTools: string[]): string {\n if (knownTools.length === 0) return text;\n\n // Map tool suffix \u2192 correct full name (e.g. 'impact' \u2192 'kirograph_impact')\n const toolMap = new Map<string, string>();\n for (const tool of knownTools) {\n const parts = tool.split('_');\n if (parts.length >= 2) toolMap.set(parts.slice(1).join('_'), tool);\n }\n\n return text.replace(\n /\\b(?:kilo|kiro|kyro|kero)[a-z]*[_\\- ]?graph[a-z_\\- ]*\\b/gi,\n (match) => {\n const lower = match.toLowerCase().replace(/[^a-z]/g, '');\n // Find the known tool whose suffix appears in the match\n for (const [suffix, tool] of toolMap) {\n if (lower.includes(suffix.replace(/_/g, ''))) return tool;\n }\n // Fallback: return the first known tool (likely the only one)\n return knownTools[0];\n }\n );\n}\n\n/** Prompt for the workspace brief only \u2014 no skills. */\nexport function buildBriefPrompt(observations: MemObservation[]): string {\n const byKind: Record<string, string[]> = {};\n for (const obs of observations) {\n (byKind[obs.kind] ??= []).push(obs.content);\n }\n\n const lines: string[] = ['Observations from recent coding sessions:\\n'];\n for (const [kind, contents] of Object.entries(byKind)) {\n lines.push(`[${kind}]`);\n for (const c of contents) lines.push(`- ${c}`);\n lines.push('');\n }\n\n lines.push(\n 'Write a workspace brief using ONLY these sections (skip any with no content):\\n',\n '## Decisions',\n '(one bullet per architectural or implementation decision)\\n',\n '## Known Errors & Fixes',\n '(one bullet per error \u2014 what broke and the fix)\\n',\n '## Recurring Patterns',\n '(one bullet per recurring pattern)\\n',\n '## Architecture Notes',\n '(one bullet per architectural constraint)\\n',\n 'Be concise. One fact per bullet. Do not repeat observations verbatim. Do NOT use code fences.',\n );\n\n return lines.join('\\n');\n}\n\n/** Prompt for a single skill file given a group of related observations. */\nexport function buildSkillPrompt(procedureObservations: string[]): string {\n const lines = [\n 'The following observations all describe the same recurring development procedure:\\n',\n ...procedureObservations.map(o => `- ${o}`),\n '',\n 'Write a skill file in EXACTLY this format (fill in the placeholders, keep the structure):\\n',\n '---',\n 'name: <short-kebab-case-slug>',\n 'description: <one sentence describing the procedure>',\n 'trigger_phrases:',\n ' - <phrase that means \"I should use this skill\">',\n ' - <another trigger phrase>',\n '---',\n '',\n '## When to use',\n '<One sentence: when should a developer run this procedure?>',\n '',\n '## Steps',\n '1. <First step \u2014 include the exact command/tool if relevant>',\n '2. <Second step>',\n '3. <Third step>',\n '',\n 'Use the EXACT tool names from the observations (e.g. kirograph_impact, not \"kirograph impact\").',\n 'Write ONLY the skill file content. No explanation, no preamble, no code fences.',\n ];\n return lines.join('\\n');\n}\n\n/** Group pattern/decision observations that share a common procedure theme. */\nexport function detectRecurringProcedures(observations: MemObservation[]): string[][] {\n // Only look at pattern observations \u2014 they most likely describe recurring steps\n const patterns = observations\n .filter(o => o.kind === 'pattern' || o.kind === 'decision')\n .map(o => o.content);\n\n if (patterns.length < 3) return [];\n\n // Simple heuristic: if 3+ pattern observations exist, treat them as one procedure group.\n // A more sophisticated implementation could cluster by keyword overlap.\n return [patterns];\n}\n\n// \u2500\u2500 Output parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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/** Extract the assistant reply from a transformers.js text-generation output. */\nexport function extractGeneratedText(output: any): string {\n const generated = output?.[0]?.generated_text;\n let text: string;\n if (Array.isArray(generated)) {\n // Chat model: returns array of messages \u2014 last one is assistant\n text = generated.at(-1)?.content ?? '';\n } else {\n // Plain text model\n text = typeof generated === 'string' ? generated : '';\n }\n return stripCodeFences(text);\n}\n\n/** Strip outer markdown code fences that small models tend to add. */\nexport function stripCodeFences(text: string): string {\n return text\n .replace(/^```(?:markdown|md)?\\n?/m, '')\n .replace(/\\n?```\\s*$/m, '')\n .trim();\n}\n\n/**\n * Detect and truncate repetition loops that small models fall into.\n * Splits into sentences and cuts at the first one that closely repeats a prior sentence.\n */\nexport function truncateAtRepetitionLoop(text: string): string {\n const sentences = text.split(/(?<=[.!?])\\s+/);\n const seen: string[] = [];\n const result: string[] = [];\n\n for (const sentence of sentences) {\n const normalized = sentence.toLowerCase().replace(/[^a-z0-9]/g, '').slice(0, 60);\n if (normalized.length < 10) { result.push(sentence); continue; }\n // If this sentence is very similar to a recent one, stop\n const isDuplicate = seen.slice(-5).some(s => {\n const overlap = [...normalized].filter((c, i) => s[i] === c).length;\n return overlap / normalized.length > 0.8;\n });\n if (isDuplicate) break;\n seen.push(normalized);\n result.push(sentence);\n }\n\n return result.join(' ').trim();\n}\n\n/** Remove hallucinated image markdown and leaked run-IDs from model output. */\nexport function removeHallucinatedContent(text: string): string {\n return text\n // Remove image markdown:  \u2014 models hallucinate URLs\n .replace(/!\\[.*?\\]\\(.*?\\)/g, '')\n // Remove run-id timestamps [1780913041] wherever they appear (inline or line-start)\n .replace(/\\*?\\*?\\[\\d{9,}\\]\\*?\\*?\\s*/g, '')\n // Collapse 3+ consecutive blank lines into 2\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\n/**\n * Split generated text into brief body and skill sections.\n *\n * Primary: looks for `## Skill: <slug>` (explicit format requested in prompt).\n * Fallback: looks for `### <Title>` subsections inside a `## Recurring Procedures`\n * block \u2014 small models often use this structure instead.\n */\nexport function parseOutput(text: string): { brief: string; skills: Array<{ slug: string; content: string }> } {\n // \u2500\u2500 Primary: flexible skill heading detection \u2500\u2500\u2500\u2500\u2500\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 // Small models use many variations:\n // ## Skill: slug (ideal)\n // # Skills: Title (common)\n // Skill: Title (no heading marker)\n // Skill:\\nTitle (name on next line)\n const skillRegex = /^(?:#{1,3}\\s+)?Skills?:\\s*(.*)$/gim;\n const skillMatches: Array<{ index: number; slug: string }> = [];\n let match: RegExpExecArray | null;\n while ((match = skillRegex.exec(text)) !== null) {\n let title = match[1].trim();\n // If no title on same line, grab the next non-empty line as the title\n if (!title) {\n const rest = text.slice(match.index + match[0].length);\n const nextLine = rest.split('\\n').find(l => l.trim());\n title = nextLine?.trim() ?? '';\n }\n if (!title) continue;\n const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n skillMatches.push({ index: match.index, slug });\n }\n\n if (skillMatches.length > 0) {\n const briefEnd = skillMatches[0].index;\n const skills = skillMatches.map((m, i) => {\n const start = m.index;\n const end = i + 1 < skillMatches.length ? skillMatches[i + 1].index : text.length;\n // Normalise heading to ## Skill: <slug> for the file writer\n const body = text.slice(start, end).replace(/^#{1,3} Skills?: .+$/im, '').trim();\n return { slug: m.slug, content: `## Skill: ${m.slug}\\n\\n${body}` };\n });\n return { brief: text.slice(0, briefEnd).trim(), skills };\n }\n\n // \u2500\u2500 Fallback: ### subsections inside ## Recurring Procedures \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const recurringMatch = /^## Recurring Procedures\\b.*$/m.exec(text);\n if (recurringMatch) {\n const sectionStart = recurringMatch.index;\n // Find the end of this section (next ## heading or end of text)\n const nextH2 = /^## /m.exec(text.slice(sectionStart + recurringMatch[0].length));\n const sectionEnd = nextH2\n ? sectionStart + recurringMatch[0].length + nextH2.index\n : text.length;\n\n const sectionBody = text.slice(sectionStart, sectionEnd);\n const subRegex = /^### (.+)$/gm;\n const subMatches: Array<{ index: number; title: string }> = [];\n let sub: RegExpExecArray | null;\n while ((sub = subRegex.exec(sectionBody)) !== null) {\n subMatches.push({ index: sectionStart + sub.index, title: sub[1].trim() });\n }\n\n if (subMatches.length > 0) {\n const brief = (text.slice(0, sectionStart) + text.slice(sectionEnd)).trim();\n const skills = subMatches.map((m, i) => {\n const start = m.index;\n const end = i + 1 < subMatches.length ? subMatches[i + 1].index : sectionEnd;\n const slug = m.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n // Reformat as ## Skill: heading for the file writer\n const content = `## Skill: ${slug}\\n\\n` + text.slice(start + m.title.length + 4, end).trim();\n return { slug, content };\n });\n return { brief, skills };\n }\n }\n\n return { brief: text.trim(), skills: [] };\n}\n\n// \u2500\u2500 File writing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nconst WATCHMEN_BLOCK_START = '<!-- kirograph-watchmen:start -->';\nconst WATCHMEN_BLOCK_END = '<!-- kirograph-watchmen:end -->';\n\n/** Upsert the KiroGraph Watchmen block in a plain text/markdown file. */\nexport function upsertBriefBlock(filePath: string, brief: string): void {\n const header = `## KiroGraph Watchmen\\n\\n_Auto-generated by KiroGraph Watchmen. Last updated: ${new Date().toISOString().slice(0, 10)}._\\n\\n`;\n const block = `${WATCHMEN_BLOCK_START}\\n${header}${brief}\\n${WATCHMEN_BLOCK_END}`;\n\n let existing = '';\n try { existing = fs.readFileSync(filePath, 'utf8'); } catch { /* new file */ }\n\n const startIdx = existing.indexOf(WATCHMEN_BLOCK_START);\n const endIdx = existing.indexOf(WATCHMEN_BLOCK_END);\n\n let updated: string;\n if (startIdx !== -1 && endIdx !== -1) {\n updated = existing.slice(0, startIdx) + block + existing.slice(endIdx + WATCHMEN_BLOCK_END.length);\n } else {\n updated = existing ? `${existing}\\n\\n${block}\\n` : `${block}\\n`;\n }\n\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, updated, 'utf8');\n}\n\n/** Write the Kiro steering workspace brief (inclusion: always). */\nexport function writeKiroBrief(steeringDir: string, brief: string): void {\n const content =\n `---\\ninclusion: always\\n---\\n\\n` +\n `# Workspace Knowledge (KiroGraph Watchmen)\\n\\n` +\n `_Auto-generated. Last updated: ${new Date().toISOString().slice(0, 10)}._\\n\\n` +\n `${brief}\\n`;\n const filePath = path.join(steeringDir, 'kirograph-watchmen.md');\n fs.mkdirSync(steeringDir, { recursive: true });\n fs.writeFileSync(filePath, content, 'utf8');\n}\n\n/**\n * Write a Kiro skill file (inclusion: manual) with SKILL.md format.\n * Merges the `inclusion: manual` Kiro frontmatter with whatever name/description/\n * trigger_phrases frontmatter the model generated inside the content.\n */\nexport function writeKiroSkill(steeringDir: string, slug: string, content: string): void {\n // The model may have produced its own frontmatter block \u2014 extract it\n const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);\n let modelFrontmatter = '';\n let body = content;\n\n if (frontmatterMatch) {\n modelFrontmatter = frontmatterMatch[1].trim();\n body = frontmatterMatch[2].trim();\n }\n\n // Build the final frontmatter: Kiro inclusion first, then model metadata\n const frontmatter = ['---', 'inclusion: manual'];\n if (modelFrontmatter) {\n for (const line of modelFrontmatter.split('\\n')) {\n // Skip if it would duplicate inclusion\n if (!line.startsWith('inclusion:')) frontmatter.push(line);\n }\n }\n frontmatter.push('---');\n\n const filePath = path.join(steeringDir, `watchmen-${slug}.md`);\n fs.mkdirSync(steeringDir, { recursive: true });\n fs.writeFileSync(filePath, frontmatter.join('\\n') + '\\n\\n' + body + '\\n', 'utf8');\n}\n\n/** Remove stale watchmen-*.md skill files that are not in the current slug set. */\nexport function pruneStaleSkills(steeringDir: string, currentSlugs: Set<string>): string[] {\n const pruned: string[] = [];\n if (!fs.existsSync(steeringDir)) return pruned;\n for (const file of fs.readdirSync(steeringDir)) {\n if (!file.startsWith('watchmen-') || !file.endsWith('.md')) continue;\n const slug = file.slice('watchmen-'.length, -'.md'.length);\n if (!currentSlugs.has(slug)) {\n fs.unlinkSync(path.join(steeringDir, file));\n pruned.push(file);\n }\n }\n return pruned;\n}\n\n// \u2500\u2500 Main synthesis entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 SynthesisResult {\n skipped: boolean;\n reason?: string;\n filesWritten: string[];\n skillsWritten: string[];\n skillsPruned: string[];\n summaryObservation: string;\n}\n\n/** Run one model call with a token progress bar. Returns cleaned text. */\nasync function generate(\n pipe: any,\n systemPrompt: string,\n userPrompt: string,\n modelName: string,\n label: string,\n quiet: boolean,\n maxNewTokens = 500,\n): Promise<string> {\n const knownTools: string[] = []; // filled per-call by caller if needed\n let tokenCount = 0;\n\n let streamer: any = undefined;\n if (!quiet && pipe.tokenizer) {\n const { TextStreamer } = await import('@huggingface/transformers') as any;\n streamer = new TextStreamer(pipe.tokenizer, {\n skip_prompt: true,\n skip_special_tokens: true,\n callback_function: () => {\n tokenCount++;\n const pct = Math.min(Math.round((tokenCount / maxNewTokens) * 100), 99);\n const filled = Math.round(pct / 5);\n const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(20 - filled);\n process.stdout.write(`\\r ${label.padEnd(14)} [${bar}] ${String(tokenCount).padStart(3)} tokens`);\n },\n });\n }\n\n const genOpts: any = {\n ...(isGemma4(modelName)\n ? { temperature: 1.0, top_p: 0.95, top_k: 64, do_sample: true }\n : { do_sample: false, repetition_penalty: 1.3 }),\n max_new_tokens: maxNewTokens,\n ...(streamer ? { streamer } : {}),\n };\n\n const messages = [\n { role: 'system' as const, content: systemPrompt },\n { role: 'user' as const, content: userPrompt },\n ];\n\n const output = await pipe(messages, genOpts);\n\n if (!quiet && tokenCount > 0) {\n process.stdout.write(`\\r ${' '.repeat(64)}\\r`);\n }\n\n return truncateAtRepetitionLoop(\n removeHallucinatedContent(stripCodeFences(extractGeneratedText(output)))\n );\n}\n\nexport async function runLocalSynthesis(\n observations: MemObservation[],\n readyResult: WatchmenReadyResult,\n modelName: string,\n projectRoot: string,\n quiet = false,\n): Promise<SynthesisResult> {\n const pipe = await getGenPipeline(modelName, quiet);\n const knownTools = extractKirographTools(observations);\n\n const filesWritten: string[] = [];\n const skillsWritten: string[] = [];\n let skillsPruned: string[] = [];\n\n // \u2500\u2500 Pass 1: workspace brief \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 if (!quiet) process.stdout.write(' Generating brief...\\n');\n const briefRaw = await generate(pipe, SYSTEM_PROMPT, buildBriefPrompt(observations), modelName, 'Brief', quiet);\n const brief = fixKirographToolNames(briefRaw, knownTools);\n\n for (const target of readyResult.targetFiles) {\n const absPath = path.isAbsolute(target) ? target : path.join(projectRoot, target);\n if (target === '.kiro/steering/kirograph-watchmen.md') {\n const steeringDir = path.join(projectRoot, readyResult.skillTargetDir ?? '.kiro/steering');\n writeKiroBrief(steeringDir, brief);\n } else {\n upsertBriefBlock(absPath, brief);\n }\n filesWritten.push(target);\n }\n\n // \u2500\u2500 Pass 2: skill files (Kiro only, one call per procedure group) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (readyResult.skillTargetDir) {\n const steeringDir = path.join(projectRoot, readyResult.skillTargetDir);\n const currentSlugs = new Set<string>();\n const procedureGroups = detectRecurringProcedures(observations);\n\n for (let i = 0; i < procedureGroups.length; i++) {\n const group = procedureGroups[i];\n const label = `Skill ${i + 1}/${procedureGroups.length}`;\n if (!quiet) process.stdout.write(` Generating ${label}...\\n`);\n\n const skillRaw = await generate(\n pipe, SYSTEM_PROMPT, buildSkillPrompt(group), modelName, label, quiet, 350,\n );\n const skillText = fixKirographToolNames(skillRaw, knownTools);\n\n // Extract slug from name: field in model frontmatter, or derive from content\n const nameMatch = skillText.match(/^name:\\s*(.+)$/m);\n const slug = nameMatch\n ? nameMatch[1].trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')\n : `procedure-${i + 1}`;\n\n if (!currentSlugs.has(slug)) {\n writeKiroSkill(steeringDir, slug, skillText);\n skillsWritten.push(`watchmen-${slug}.md`);\n currentSlugs.add(slug);\n }\n }\n\n skillsPruned = pruneStaleSkills(steeringDir, currentSlugs);\n }\n\n const summaryParts = [`Synthesized ${observations.length} observations`];\n if (filesWritten.length) summaryParts.push(`into ${filesWritten.join(', ')}`);\n if (skillsWritten.length) summaryParts.push(`wrote skills: ${skillsWritten.join(', ')}`);\n if (skillsPruned.length) summaryParts.push(`pruned: ${skillsPruned.join(', ')}`);\n\n return {\n skipped: false,\n filesWritten,\n skillsWritten,\n skillsPruned,\n summaryObservation: summaryParts.join('; ') + '.',\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var database_exports = {};
|
|
30
|
+
__export(database_exports, {
|
|
31
|
+
WikiDatabase: () => WikiDatabase
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(database_exports);
|
|
34
|
+
var fs = __toESM(require("fs"));
|
|
35
|
+
var path = __toESM(require("path"));
|
|
36
|
+
class WikiDatabase {
|
|
37
|
+
constructor(db) {
|
|
38
|
+
this.initialized = false;
|
|
39
|
+
this.db = db;
|
|
40
|
+
}
|
|
41
|
+
initialize() {
|
|
42
|
+
if (this.initialized) return;
|
|
43
|
+
const schemaPath = path.join(__dirname, "../db/wiki-schema.sql");
|
|
44
|
+
const sql = fs.readFileSync(schemaPath, "utf8");
|
|
45
|
+
this.db.exec(sql);
|
|
46
|
+
this.initialized = true;
|
|
47
|
+
}
|
|
48
|
+
// ── Writes ────────────────────────────────────────────────────────────────
|
|
49
|
+
upsertPage(page) {
|
|
50
|
+
const now = page.updatedAt ?? Date.now();
|
|
51
|
+
this.db.run(
|
|
52
|
+
`INSERT INTO wiki_pages (slug, title, content, file_path, updated_at, source_count)
|
|
53
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
54
|
+
ON CONFLICT(slug) DO UPDATE SET
|
|
55
|
+
title = excluded.title,
|
|
56
|
+
content = excluded.content,
|
|
57
|
+
file_path = excluded.file_path,
|
|
58
|
+
updated_at = excluded.updated_at,
|
|
59
|
+
source_count = wiki_pages.source_count + excluded.source_count`,
|
|
60
|
+
[page.slug, page.title, page.content, page.filePath, now, page.sourceCount ?? 1]
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
deletePage(slug) {
|
|
64
|
+
this.db.run("DELETE FROM wiki_pages WHERE slug = ?", [slug]);
|
|
65
|
+
}
|
|
66
|
+
clearAll() {
|
|
67
|
+
this.db.exec("DELETE FROM wiki_pages");
|
|
68
|
+
}
|
|
69
|
+
// ── Queue (local synthesis) ───────────────────────────────────────────────
|
|
70
|
+
queueSource(sourceName, sourceText) {
|
|
71
|
+
this.db.run(
|
|
72
|
+
"INSERT INTO wiki_queue (source_name, source_text, queued_at) VALUES (?, ?, ?)",
|
|
73
|
+
[sourceName, sourceText, Date.now()]
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
getPendingQueue() {
|
|
77
|
+
const rows = this.db.all("SELECT id, source_name, source_text FROM wiki_queue ORDER BY queued_at ASC");
|
|
78
|
+
return rows.map((r) => ({ id: r.id, sourceName: r.source_name, sourceText: r.source_text }));
|
|
79
|
+
}
|
|
80
|
+
clearQueue(ids) {
|
|
81
|
+
if (ids && ids.length > 0) {
|
|
82
|
+
this.db.exec(`DELETE FROM wiki_queue WHERE id IN (${ids.join(",")})`);
|
|
83
|
+
} else {
|
|
84
|
+
this.db.exec("DELETE FROM wiki_queue");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
getQueueCount() {
|
|
88
|
+
const row = this.db.get("SELECT COUNT(*) as count FROM wiki_queue");
|
|
89
|
+
return row?.count ?? 0;
|
|
90
|
+
}
|
|
91
|
+
// ── Reads ─────────────────────────────────────────────────────────────────
|
|
92
|
+
getPage(slug) {
|
|
93
|
+
const row = this.db.get("SELECT * FROM wiki_pages WHERE slug = ?", [slug]);
|
|
94
|
+
return row ? this.rowToPage(row) : null;
|
|
95
|
+
}
|
|
96
|
+
listPages() {
|
|
97
|
+
return this.db.all("SELECT * FROM wiki_pages ORDER BY slug ASC").map((r) => this.rowToPage(r));
|
|
98
|
+
}
|
|
99
|
+
search(query, limit = 10) {
|
|
100
|
+
const safe = query.replace(/\b(AND|OR|NOT)\b/g, " ").replace(/['"*()?\-+^~:{}\\\.\/,\[\]]/g, " ").replace(/\s+/g, " ").trim();
|
|
101
|
+
if (!safe) return [];
|
|
102
|
+
const ftsQuery = safe + "*";
|
|
103
|
+
const safeLimit = Math.max(1, Math.floor(Number(limit)));
|
|
104
|
+
const rows = this.db.all(
|
|
105
|
+
`SELECT p.*, f.rank FROM wiki_fts f
|
|
106
|
+
JOIN wiki_pages p ON p.rowid = f.rowid
|
|
107
|
+
WHERE wiki_fts MATCH '${ftsQuery}'
|
|
108
|
+
ORDER BY f.rank
|
|
109
|
+
LIMIT ${safeLimit}`,
|
|
110
|
+
[]
|
|
111
|
+
);
|
|
112
|
+
return rows.map((row) => ({
|
|
113
|
+
page: this.rowToPage(row),
|
|
114
|
+
score: Math.abs(row.rank ?? 0)
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
getStats() {
|
|
118
|
+
const counts = this.db.get(
|
|
119
|
+
"SELECT COUNT(*) as pageCount, SUM(source_count) as totalSources, MIN(updated_at) as oldest, MAX(updated_at) as newest FROM wiki_pages"
|
|
120
|
+
);
|
|
121
|
+
return {
|
|
122
|
+
pageCount: counts?.pageCount ?? 0,
|
|
123
|
+
totalSources: counts?.totalSources ?? 0,
|
|
124
|
+
oldestPage: counts?.oldest ?? null,
|
|
125
|
+
newestPage: counts?.newest ?? null
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
rowToPage(row) {
|
|
129
|
+
return {
|
|
130
|
+
slug: row.slug,
|
|
131
|
+
title: row.title,
|
|
132
|
+
content: row.content,
|
|
133
|
+
filePath: row.file_path,
|
|
134
|
+
updatedAt: row.updated_at,
|
|
135
|
+
sourceCount: row.source_count ?? 0
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
140
|
+
0 && (module.exports = {
|
|
141
|
+
WikiDatabase
|
|
142
|
+
});
|
|
143
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/wiki/database.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph Wiki \u2014 Database layer\n *\n * SQLite index for fast FTS over wiki pages.\n * Source of truth is .kirograph/wiki/*.md \u2014 this is a regeneratable index.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { WikiPage, ScoredWikiPage, WikiStats } from './types';\n\nexport class WikiDatabase {\n private db: any;\n private initialized = false;\n\n constructor(db: any) {\n this.db = db;\n }\n\n initialize(): void {\n if (this.initialized) return;\n const schemaPath = path.join(__dirname, '../db/wiki-schema.sql');\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n this.initialized = true;\n }\n\n // \u2500\u2500 Writes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 upsertPage(page: Omit<WikiPage, 'updatedAt'> & { updatedAt?: number }): void {\n const now = page.updatedAt ?? Date.now();\n this.db.run(\n `INSERT INTO wiki_pages (slug, title, content, file_path, updated_at, source_count)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(slug) DO UPDATE SET\n title = excluded.title,\n content = excluded.content,\n file_path = excluded.file_path,\n updated_at = excluded.updated_at,\n source_count = wiki_pages.source_count + excluded.source_count`,\n [page.slug, page.title, page.content, page.filePath, now, page.sourceCount ?? 1]\n );\n }\n\n deletePage(slug: string): void {\n this.db.run('DELETE FROM wiki_pages WHERE slug = ?', [slug]);\n }\n\n clearAll(): void {\n this.db.exec('DELETE FROM wiki_pages');\n }\n\n // \u2500\u2500 Queue (local synthesis) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 queueSource(sourceName: string, sourceText: string): void {\n this.db.run(\n 'INSERT INTO wiki_queue (source_name, source_text, queued_at) VALUES (?, ?, ?)',\n [sourceName, sourceText, Date.now()]\n );\n }\n\n getPendingQueue(): Array<{ id: number; sourceName: string; sourceText: string }> {\n const rows = this.db.all('SELECT id, source_name, source_text FROM wiki_queue ORDER BY queued_at ASC') as any[];\n return rows.map(r => ({ id: r.id, sourceName: r.source_name, sourceText: r.source_text }));\n }\n\n clearQueue(ids?: number[]): void {\n if (ids && ids.length > 0) {\n this.db.exec(`DELETE FROM wiki_queue WHERE id IN (${ids.join(',')})`);\n } else {\n this.db.exec('DELETE FROM wiki_queue');\n }\n }\n\n getQueueCount(): number {\n const row = this.db.get('SELECT COUNT(*) as count FROM wiki_queue') as any;\n return row?.count ?? 0;\n }\n\n // \u2500\u2500 Reads \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 getPage(slug: string): WikiPage | null {\n const row = this.db.get('SELECT * FROM wiki_pages WHERE slug = ?', [slug]);\n return row ? this.rowToPage(row) : null;\n }\n\n listPages(): WikiPage[] {\n return (this.db.all('SELECT * FROM wiki_pages ORDER BY slug ASC') as any[]).map(r => this.rowToPage(r));\n }\n\n search(query: string, limit = 10): ScoredWikiPage[] {\n const safe = query\n .replace(/\\b(AND|OR|NOT)\\b/g, ' ')\n .replace(/['\"*()?\\-+^~:{}\\\\\\.\\/,\\[\\]]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n if (!safe) return [];\n\n const ftsQuery = safe + '*';\n const safeLimit = Math.max(1, Math.floor(Number(limit)));\n\n const rows = this.db.all(\n `SELECT p.*, f.rank FROM wiki_fts f\n JOIN wiki_pages p ON p.rowid = f.rowid\n WHERE wiki_fts MATCH '${ftsQuery}'\n ORDER BY f.rank\n LIMIT ${safeLimit}`,\n []\n ) as any[];\n\n return rows.map(row => ({\n page: this.rowToPage(row),\n score: Math.abs(row.rank ?? 0),\n }));\n }\n\n getStats(): WikiStats {\n const counts = this.db.get(\n 'SELECT COUNT(*) as pageCount, SUM(source_count) as totalSources, MIN(updated_at) as oldest, MAX(updated_at) as newest FROM wiki_pages'\n ) as any;\n return {\n pageCount: counts?.pageCount ?? 0,\n totalSources: counts?.totalSources ?? 0,\n oldestPage: counts?.oldest ?? null,\n newestPage: counts?.newest ?? null,\n };\n }\n\n private rowToPage(row: any): WikiPage {\n return {\n slug: row.slug,\n title: row.title,\n content: row.content,\n filePath: row.file_path,\n updatedAt: row.updated_at,\n sourceCount: row.source_count ?? 0,\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|