sumulige-claude 1.1.2 → 1.2.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/.claude/hooks/code-formatter.cjs +7 -2
- package/.claude/hooks/multi-session.cjs +9 -3
- package/.claude/hooks/pre-commit.cjs +0 -0
- package/.claude/hooks/pre-push.cjs +0 -0
- package/.claude/hooks/project-kickoff.cjs +22 -11
- package/.claude/hooks/rag-skill-loader.cjs +7 -0
- package/.claude/hooks/thinking-silent.cjs +9 -3
- package/.claude/hooks/todo-manager.cjs +19 -13
- package/.claude/hooks/verify-work.cjs +10 -4
- package/.claude/quality-gate.json +9 -3
- package/.claude/settings.local.json +16 -1
- package/.claude/templates/hooks/README.md +302 -0
- package/.claude/templates/hooks/hook.sh.template +94 -0
- package/.claude/templates/hooks/user-prompt-submit.cjs.template +116 -0
- package/.claude/templates/hooks/user-response-submit.cjs.template +94 -0
- package/.claude/templates/hooks/validate.js +173 -0
- package/.claude/workflow/document-scanner.js +426 -0
- package/.claude/workflow/knowledge-engine.js +941 -0
- package/.claude/workflow/notebooklm/browser.js +1028 -0
- package/.claude/workflow/phases/phase1-research.js +578 -0
- package/.claude/workflow/phases/phase1-research.ts +465 -0
- package/.claude/workflow/phases/phase2-approve.js +722 -0
- package/.claude/workflow/phases/phase3-plan.js +1200 -0
- package/.claude/workflow/phases/phase4-develop.js +894 -0
- package/.claude/workflow/search-cache.js +230 -0
- package/.claude/workflow/templates/approval.md +315 -0
- package/.claude/workflow/templates/development.md +377 -0
- package/.claude/workflow/templates/planning.md +328 -0
- package/.claude/workflow/templates/research.md +250 -0
- package/.claude/workflow/types.js +37 -0
- package/.claude/workflow/web-search.js +278 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +176 -0
- package/CHANGELOG.md +7 -14
- package/cli.js +20 -0
- package/config/quality-gate.json +9 -3
- package/development/cache/web-search/search_1193d605f8eb364651fc2f2041b58a31.json +36 -0
- package/development/cache/web-search/search_3798bf06960edc125f744a1abb5b72c5.json +36 -0
- package/development/cache/web-search/search_37c7d4843a53f0d83f1122a6f908a2a3.json +36 -0
- package/development/cache/web-search/search_44166fa0153709ee168485a22aa0ab40.json +36 -0
- package/development/cache/web-search/search_4deaebb1f77e86a8ca066dc5a49c59fd.json +36 -0
- package/development/cache/web-search/search_94da91789466070a7f545612e73c7372.json +36 -0
- package/development/cache/web-search/search_dd5de8491b8b803a3cb01339cd210fb0.json +36 -0
- package/development/knowledge-base/.index.clean.json +0 -0
- package/development/knowledge-base/.index.json +486 -0
- package/development/knowledge-base/test-best-practices.md +29 -0
- package/development/projects/proj_mkh1pazz_ixmt1/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh4jvnb_z7rwf/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh4jxkd_ewz5a/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh4k84n_ni73k/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh4wfyd_u9w88/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh4wsbo_iahvf/development/projects/proj_mkh4xbpg_4na5w/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh4wsbo_iahvf/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh4xulg_1ka8x/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh4xwhj_gch8j/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase2/requirements.md +226 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase3/PRD.md +345 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase3/TASK_PLAN.md +284 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase3/prototype/README.md +14 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/DEVELOPMENT_LOG.md +35 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/TASKS.md +34 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/.env.example +5 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/README.md +60 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/package.json +25 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/index.js +70 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/routes/index.js +48 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/health.test.js +20 -0
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/jest.config.js +21 -0
- package/development/projects/proj_mkh7veqg_3lypc/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh7veqg_3lypc/phase2/requirements.md +226 -0
- package/development/projects/proj_mkh7veqg_3lypc/phase3/PRD.md +345 -0
- package/development/projects/proj_mkh7veqg_3lypc/phase3/TASK_PLAN.md +284 -0
- package/development/projects/proj_mkh7veqg_3lypc/phase3/prototype/README.md +14 -0
- package/development/projects/proj_mkh8k8fo_rmqn5/phase1/feasibility-report.md +160 -0
- package/development/projects/proj_mkh8xyhy_1vshq/phase1/feasibility-report.md +178 -0
- package/development/projects/proj_mkh8zddd_dhamf/phase1/feasibility-report.md +377 -0
- package/development/projects/proj_mkh8zddd_dhamf/phase2/requirements.md +442 -0
- package/development/projects/proj_mkh8zddd_dhamf/phase3/api-design.md +800 -0
- package/development/projects/proj_mkh8zddd_dhamf/phase3/architecture.md +625 -0
- package/development/projects/proj_mkh8zddd_dhamf/phase3/data-model.md +830 -0
- package/development/projects/proj_mkh8zddd_dhamf/phase3/risks.md +957 -0
- package/development/projects/proj_mkh8zddd_dhamf/phase3/wbs.md +381 -0
- package/development/todos/.state.json +14 -1
- package/development/todos/INDEX.md +31 -73
- package/development/todos/completed/develop/local-knowledge-index.md +85 -0
- package/development/todos/{active → completed/develop}/todo-system.md +13 -3
- package/development/todos/completed/develop/web-search-integration.md +83 -0
- package/development/todos/completed/test/phase1-e2e-test.md +103 -0
- package/lib/commands.js +388 -0
- package/package.json +3 -2
- package/tests/config-manager.test.js +677 -0
- package/tests/config-validator.test.js +436 -0
- package/tests/errors.test.js +477 -0
- package/tests/manual/phase1-e2e.sh +389 -0
- package/tests/manual/phase2-test-cases.md +311 -0
- package/tests/manual/phase3-test-cases.md +309 -0
- package/tests/manual/phase4-test-cases.md +414 -0
- package/tests/manual/test-cases.md +417 -0
- package/tests/quality-gate.test.js +679 -0
- package/tests/quality-rules.test.js +619 -0
- package/tests/version-check.test.js +75 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sources": [
|
|
3
|
+
{
|
|
4
|
+
"type": "local_directory",
|
|
5
|
+
"path": "/Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow",
|
|
6
|
+
"title": "workflow",
|
|
7
|
+
"description": "Directory with 7 indexed files",
|
|
8
|
+
"tags": [],
|
|
9
|
+
"fileCount": 7,
|
|
10
|
+
"id": "src_1768581173603_xzjmvhyku",
|
|
11
|
+
"addedAt": 1768581173603,
|
|
12
|
+
"lastAccessed": 1768581173603
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"type": "local_file",
|
|
16
|
+
"path": "/Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/document-scanner.js",
|
|
17
|
+
"title": "document-scanner.js",
|
|
18
|
+
"description": "File: /Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/document-scanner.js",
|
|
19
|
+
"tags": [],
|
|
20
|
+
"size": 12332,
|
|
21
|
+
"contentType": "text/javascript",
|
|
22
|
+
"scannable": true,
|
|
23
|
+
"wordCount": 1467,
|
|
24
|
+
"lineCount": 427,
|
|
25
|
+
"headings": [
|
|
26
|
+
{
|
|
27
|
+
"type": "class",
|
|
28
|
+
"name": "DocumentScanner",
|
|
29
|
+
"line": 38
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"links": [],
|
|
33
|
+
"codeBlocks": [],
|
|
34
|
+
"content": "/**\n * Document Scanner - Extract content and metadata from local files\n *\n * Supports:\n * - Text extraction from multiple file formats\n * - Metadata extraction (word count, headings, links)\n * - Content checksum for change detection\n */\n\nconst fs = require('fs');\nconst path = require('path');\nconst crypto = require('crypto');\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst MAX_CONTENT_SIZE = 500 * 1024; // 500KB - don't store content if larger\nconst MAX_SNIPPET_SIZE = 2000; // Store snippet for large files\n\n// Supported file types for content scanning\nconst SCANNABLE_TYPES = [\n '.md', '.markdown', // Markdown\n '.txt', // Plain text\n '.json', '.yaml', '.yml', // Config files\n '.js', '.ts', '.jsx', '.tsx', // JavaScript/TypeScript\n '.py', '.rs', '.go', '.java', '.c', '.cpp', '.h', '.hpp', // Code\n '.sh', '.bash', '.zsh', '.fish', // Shell scripts\n '.css', '.scss', '.less', // Stylesheets\n '.html', '.htm', '.xml', // Markup\n '.sql', '.graphql', '.gql' // Query languages\n];\n\n// ============================================================================\n// Document Scanner Class\n// ============================================================================\n\nclass DocumentScanner {\n /**\n * Check if a file type is scannable\n */\n static isScannable(filePath) {\n const ext = path.extname(filePath).toLowerCase();\n return SCANNABLE_TYPES.includes(ext);\n }\n\n /**\n * Scan a file and extract metadata\n */\n static scanFile(filePath, options = {}) {\n const {\n includeContent = true,\n maxContentSize = MAX_CONTENT_SIZE\n } = options;\n\n if (!fs.existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n\n const stats = fs.statSync(filePath);\n const ext = path.extname(filePath).toLowerCase();\n\n // Basic metadata\n const metadata = {\n path: filePath,\n size: stats.size,\n lastModified: stats.mtimeMs,\n contentType: this.getMimeType(ext),\n extension: ext\n };\n\n // If file is too large or not scannable, skip content\n if (!this.isScannable(filePath) || stats.size === 0) {\n return {\n ...metadata,\n scannable: false,\n wordCount: 0,\n headings: [],\n links: [],\n };\n }\n\n // Read file content\n let content;\n try {\n content = fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n return {\n ...metadata,\n scannable: false,\n error: error.message\n };\n }\n\n // Calculate checksum\n const checksum = crypto\n .createHash('md5')\n .update(content)\n .digest('hex');\n\n // Extract metadata based on file type\n const extracted = this.extractMetadata(content, ext);\n\n // Determine whether to store full content or just snippet\n const shouldStoreContent = includeContent && content.length <= maxContentSize;\n\n return {\n ...metadata,\n scannable: true,\n checksum,\n wordCount: extracted.wordCount,\n lineCount: extracted.lineCount,\n headings: extracted.headings,\n links: extracted.links,\n codeBlocks: extracted.codeBlocks,\n frontMatter: extracted.frontMatter,\n // Content or snippet\n content: shouldStoreContent ? content : null,\n snippet: shouldStoreContent ? null : content.substring(0, MAX_SNIPPET_SIZE)\n };\n }\n\n /**\n * Extract metadata from content based on file type\n */\n static extractMetadata(content, ext) {\n const lines = content.split('\\n');\n const wordCount = this.countWords(content);\n const lineCount = lines.length;\n\n let headings = [];\n let links = [];\n let codeBlocks = [];\n let frontMatter = null;\n\n // Markdown-specific extraction\n if (['.md', '.markdown'].includes(ext)) {\n const mdResult = this.extractMarkdownMetadata(content);\n headings = mdResult.headings;\n links = mdResult.links;\n codeBlocks = mdResult.codeBlocks;\n frontMatter = mdResult.frontMatter;\n }\n // Code-specific extraction\n else if (['.js', '.ts', '.jsx', '.tsx', '.py', '.rs', '.go'].includes(ext)) {\n const codeResult = this.extractCodeMetadata(content, ext);\n headings = codeResult.headings; // Functions/classes as headings\n links = codeResult.links; // Import/require statements\n }\n\n return {\n wordCount,\n lineCount,\n headings,\n links,\n codeBlocks,\n frontMatter\n };\n }\n\n /**\n * Extract Markdown-specific metadata\n */\n static extractMarkdownMetadata(content) {\n const headings = [];\n const links = [];\n const codeBlocks = [];\n let frontMatter = null;\n\n const lines = content.split('\\n');\n let inCodeBlock = false;\n let codeBlockLang = null;\n let currentCodeBlock = [];\n\n // Check for YAML front matter\n if (lines[0] === '---') {\n const endIdx = lines.slice(1).findIndex(line => line === '---');\n if (endIdx > 0) {\n const frontMatterContent = lines.slice(1, endIdx + 1).join('\\n');\n frontMatter = this.parseFrontMatter(frontMatterContent);\n }\n }\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n\n // Track code blocks\n if (line.startsWith('```')) {\n if (!inCodeBlock) {\n inCodeBlock = true;\n codeBlockLang = line.substring(3).trim() || 'text';\n currentCodeBlock = [];\n } else {\n codeBlocks.push({\n language: codeBlockLang,\n lineStart: i - currentCodeBlock.length,\n preview: currentCodeBlock.slice(0, 3).join('\\n')\n });\n inCodeBlock = false;\n currentCodeBlock = [];\n }\n continue;\n }\n\n if (inCodeBlock) {\n currentCodeBlock.push(line);\n continue;\n }\n\n // Extract headings\n const headingMatch = line.match(/^(#{1,6})\\s+(.+)$/);\n if (headingMatch) {\n const level = headingMatch[1].length;\n const text = headingMatch[2].trim();\n headings.push({ level, text, line: i + 1 });\n }\n\n // Extract links\n const linkMatch = line.match(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g);\n if (linkMatch) {\n linkMatch.forEach(link => {\n const match = link.match(/\\[([^\\]]+)\\]\\(([^)]+)\\)/);\n if (match) {\n links.push({ text: match[1], url: match[2] });\n }\n });\n }\n }\n\n return { headings, links, codeBlocks, frontMatter };\n }\n\n /**\n * Extract code-specific metadata (functions, classes, imports)\n */\n static extractCodeMetadata(content, ext) {\n const headings = [];\n const links = [];\n\n const lines = content.split('\\n');\n\n // Patterns for different languages\n const patterns = {\n '.js': { func: /^\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)/, class: /^\\s*class\\s+(\\w+)/, import: /^\\s*import\\s+.*from\\s+['\"]([^'\"]+)['\"]/ },\n '.ts': { func: /^\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)/, class: /^\\s*class\\s+(\\w+)/, import: /^\\s*import\\s+.*from\\s+['\"]([^'\"]+)['\"]/ },\n '.jsx': { func: /^\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)/, class: /^\\s*class\\s+(\\w+)/, import: /^\\s*import\\s+.*from\\s+['\"]([^'\"]+)['\"]/ },\n '.tsx': { func: /^\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)/, class: /^\\s*class\\s+(\\w+)/, import: /^\\s*import\\s+.*from\\s+['\"]([^'\"]+)['\"]/ },\n '.py': { func: /^\\s*def\\s+(\\w+)\\s*\\(/, class: /^\\s*class\\s+(\\w+)\\s*:/, import: /^\\s*(?:import|from)\\s+(\\w+)/ },\n '.rs': { func: /^\\s*(?:pub\\s+)?fn\\s+(\\w+)\\s*\\(/, class: /^\\s*(?:pub\\s+)?(struct|enum|trait)\\s+(\\w+)/, import: /^\\s*use\\s+([^;]+);/ },\n '.go': { func: /^\\s*func\\s+(?:\\(\\w+\\s+\\*?\\w+\\)\\s+)?(\\w+)\\s*\\(/, class: /^\\s*type\\s+(\\w+)\\s+struct/, import: /^\\s*import\\s+(?:\\(|\")([^\")]+)/ }\n };\n\n const lang = patterns[ext] || patterns['.js'];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n\n // Extract functions\n const funcMatch = line.match(lang.func);\n if (funcMatch) {\n headings.push({ type: 'function', name: funcMatch[1], line: i + 1 });\n }\n\n // Extract classes\n const classMatch = line.match(lang.class);\n if (classMatch) {\n const name = classMatch[2] || classMatch[1];\n headings.push({ type: 'class', name, line: i + 1 });\n }\n\n // Extract imports\n const importMatch = line.match(lang.import);\n if (importMatch) {\n links.push({ type: 'import', name: importMatch[1], line: i + 1 });\n }\n }\n\n return { headings, links };\n }\n\n /**\n * Parse YAML front matter\n */\n static parseFrontMatter(content) {\n const result = {};\n const lines = content.split('\\n');\n\n for (const line of lines) {\n const match = line.match(/^(\\w+):\\s*(.+)$/);\n if (match) {\n result[match[1]] = match[2].trim();\n }\n }\n\n return result;\n }\n\n /**\n * Count words in content (rough estimate for mixed content)\n */\n static countWords(content) {\n // For code files, count tokens more accurately\n // For text files, count words\n const tokens = content\n .replace(/\\s+/g, ' ')\n .replace(/[{}();,.<>[\\]]/g, ' ')\n .split(' ')\n .filter(t => t.length > 0);\n\n return tokens.length;\n }\n\n /**\n * Get MIME type for extension\n */\n static getMimeType(ext) {\n const mimeTypes = {\n '.md': 'text/markdown',\n '.markdown': 'text/markdown',\n '.txt': 'text/plain',\n '.json': 'application/json',\n '.yaml': 'text/yaml',\n '.yml': 'text/yaml',\n '.js': 'text/javascript',\n '.ts': 'text/typescript',\n '.jsx': 'text/jsx',\n '.tsx': 'text/tsx',\n '.py': 'text/x-python',\n '.rs': 'text/x-rust',\n '.go': 'text/x-go',\n '.java': 'text/x-java',\n '.c': 'text/x-c',\n '.cpp': 'text/x-c++',\n '.h': 'text/x-c',\n '.hpp': 'text/x-c++',\n '.sh': 'text/x-shellscript',\n '.bash': 'text/x-shellscript',\n '.css': 'text/css',\n '.scss': 'text/x-scss',\n '.less': 'text/x-less',\n '.html': 'text/html',\n '.htm': 'text/html',\n '.xml': 'text/xml',\n '.sql': 'text/x-sql',\n '.graphql': 'text/x-graphql',\n '.gql': 'text/x-graphql'\n };\n return mimeTypes[ext.toLowerCase()] || 'text/plain';\n }\n\n /**\n * Scan a directory recursively\n */\n static scanDirectory(dirPath, options = {}) {\n const {\n recursive = true,\n maxDepth = 10,\n includePatterns = [],\n excludePatterns = ['node_modules', '.git', 'dist', 'build', 'coverage']\n } = options;\n\n if (!fs.existsSync(dirPath)) {\n throw new Error(`Directory not found: ${dirPath}`);\n }\n\n const results = [];\n const scanQueue = [{ dir: dirPath, depth: 0 }];\n\n while (scanQueue.length > 0) {\n const { dir, depth } = scanQueue.shift();\n\n if (depth > maxDepth) continue;\n\n let entries;\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch (error) {\n continue; // Skip directories we can't read\n }\n\n for (const entry of entries) {\n // Skip excluded directories\n if (entry.isDirectory() && excludePatterns.includes(entry.name)) {\n continue;\n }\n\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory() && recursive) {\n scanQueue.push({ dir: fullPath, depth: depth + 1 });\n } else if (entry.isFile()) {\n // Check include patterns\n if (includePatterns.length > 0) {\n const matches = includePatterns.some(pattern => {\n if (pattern instanceof RegExp) {\n return pattern.test(fullPath);\n }\n return fullPath.includes(pattern);\n });\n if (!matches) continue;\n }\n\n try {\n const scanResult = this.scanFile(fullPath, options);\n results.push(scanResult);\n } catch (error) {\n // Skip files that can't be scanned\n }\n }\n }\n }\n\n return results;\n }\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nmodule.exports = {\n DocumentScanner,\n SCANNABLE_TYPES,\n MAX_CONTENT_SIZE,\n MAX_SNIPPET_SIZE\n};\n",
|
|
35
|
+
"snippet": null,
|
|
36
|
+
"checksum": "f4c462a24365411bdd55c23bb0e04df2",
|
|
37
|
+
"lastModified": 1768580956234.866,
|
|
38
|
+
"indexedAt": 1768581173601,
|
|
39
|
+
"id": "src_1768581173601_8ijslbgvy",
|
|
40
|
+
"addedAt": 1768581173601,
|
|
41
|
+
"lastAccessed": 1768581173601
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"type": "local_file",
|
|
45
|
+
"path": "/Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/knowledge-engine.js",
|
|
46
|
+
"title": "knowledge-engine.js",
|
|
47
|
+
"description": "File: /Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/knowledge-engine.js",
|
|
48
|
+
"tags": [],
|
|
49
|
+
"size": 26039,
|
|
50
|
+
"contentType": "text/javascript",
|
|
51
|
+
"scannable": true,
|
|
52
|
+
"wordCount": 3241,
|
|
53
|
+
"lineCount": 942,
|
|
54
|
+
"headings": [
|
|
55
|
+
{
|
|
56
|
+
"type": "class",
|
|
57
|
+
"name": "KnowledgeEngine",
|
|
58
|
+
"line": 41
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"type": "function",
|
|
62
|
+
"name": "getKnowledgeEngine",
|
|
63
|
+
"line": 722
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "function",
|
|
67
|
+
"name": "handleKnowledgeCommand",
|
|
68
|
+
"line": 733
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"links": [],
|
|
72
|
+
"codeBlocks": [],
|
|
73
|
+
"content": "/**\n * Knowledge Engine (JavaScript version)\n *\n * Integrates NotebookLM knowledge capabilities with local knowledge base\n * and web search for Phase 1 research.\n */\n\nconst fs = require('fs');\nconst path = require('path');\n\n// Import DocumentScanner\nconst { DocumentScanner } = require('./document-scanner.js');\n\n// Import WebSearch and SearchCache\nconst { WebSearch } = require('./web-search.js');\nconst { SearchCache } = require('./search-cache.js');\n\n// Try to import NotebookLM browser module\nlet NotebookLMClient = null;\nlet getClient = null;\ntry {\n const notebooklm = require('./notebooklm/browser.js');\n NotebookLMClient = notebooklm.NotebookLMClient;\n getClient = notebooklm.getClient;\n} catch (e) {\n // NotebookLM not available, will use fallback\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst KNOWLEDGE_BASE_DIR = path.join(process.cwd(), 'development/knowledge-base');\nconst PROJECTS_DIR = path.join(process.cwd(), 'development/projects');\nconst KNOWLEDGE_INDEX_FILE = path.join(KNOWLEDGE_BASE_DIR, '.index.json');\n\n// ============================================================================\n// Knowledge Engine Class\n// ============================================================================\n\nclass KnowledgeEngine {\n constructor() {\n this.index = { sources: [], lastUpdated: 0 };\n this.indexLoaded = false;\n this.ensureDirectories();\n }\n\n // --------------------------------------------------------------------------\n // Initialization\n // --------------------------------------------------------------------------\n\n ensureDirectories() {\n [KNOWLEDGE_BASE_DIR, PROJECTS_DIR].forEach(dir => {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n });\n }\n\n loadIndex() {\n if (this.indexLoaded) return;\n\n try {\n if (fs.existsSync(KNOWLEDGE_INDEX_FILE)) {\n const content = fs.readFileSync(KNOWLEDGE_INDEX_FILE, 'utf-8');\n this.index = JSON.parse(content);\n }\n this.indexLoaded = true;\n } catch (error) {\n console.warn('Failed to load knowledge index, starting fresh:', error);\n this.index = { sources: [], lastUpdated: Date.now() };\n this.indexLoaded = true;\n }\n }\n\n saveIndex() {\n try {\n fs.writeFileSync(\n KNOWLEDGE_INDEX_FILE,\n JSON.stringify(this.index, null, 2),\n 'utf-8'\n );\n this.index.lastUpdated = Date.now();\n } catch (error) {\n console.error('Failed to save knowledge index:', error);\n }\n }\n\n // --------------------------------------------------------------------------\n // Knowledge Source Management\n // --------------------------------------------------------------------------\n\n generateId() {\n return `src_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n }\n\n getMimeType(ext) {\n const mimeTypes = {\n '.md': 'text/markdown',\n '.txt': 'text/plain',\n '.json': 'application/json',\n '.pdf': 'application/pdf',\n '.html': 'text/html',\n '.js': 'text/javascript',\n '.ts': 'text/typescript',\n '.py': 'text/x-python',\n '.rs': 'text/x-rust'\n };\n return mimeTypes[ext.toLowerCase()] || 'application/octet-stream';\n }\n\n /**\n * Add a knowledge source to the index\n */\n addSource(source) {\n this.loadIndex();\n\n const id = this.generateId();\n const newSource = {\n ...source,\n id,\n addedAt: Date.now(),\n lastAccessed: Date.now()\n };\n\n this.index.sources.push(newSource);\n this.saveIndex();\n\n return id;\n }\n\n /**\n * Add a local file as knowledge source\n */\n addFile(filePath, tags = [], options = {}) {\n const resolvedPath = path.resolve(filePath);\n if (!fs.existsSync(resolvedPath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n\n const stats = fs.statSync(resolvedPath);\n const ext = path.extname(resolvedPath);\n\n // Scan file for content and metadata\n const scanResult = DocumentScanner.scanFile(resolvedPath, {\n includeContent: true,\n maxContentSize: options.maxContentSize || 500 * 1024\n });\n\n // Build title from front matter or filename\n let title = path.basename(resolvedPath);\n let description = `Local file: ${resolvedPath}`;\n\n if (scanResult.frontMatter && scanResult.frontMatter.title) {\n title = scanResult.frontMatter.title;\n }\n if (scanResult.frontMatter && scanResult.frontMatter.description) {\n description = scanResult.frontMatter.description;\n }\n\n return this.addSource({\n type: 'local_file',\n path: resolvedPath,\n title,\n description,\n tags: tags.concat(scanResult.frontMatter?.tags?.split(',') || []),\n size: stats.size,\n contentType: scanResult.contentType,\n\n // New fields from scanning\n scannable: scanResult.scannable,\n wordCount: scanResult.wordCount,\n lineCount: scanResult.lineCount,\n headings: scanResult.headings,\n links: scanResult.links,\n codeBlocks: scanResult.codeBlocks,\n content: scanResult.content,\n snippet: scanResult.snippet,\n checksum: scanResult.checksum,\n lastModified: scanResult.lastModified,\n indexedAt: Date.now()\n });\n }\n\n /**\n * Add a local directory as knowledge source\n * Recursively scans all supported files in the directory\n */\n addDirectory(dirPath, tags = [], options = {}) {\n const resolvedPath = path.resolve(dirPath);\n if (!fs.existsSync(resolvedPath)) {\n throw new Error(`Directory not found: ${dirPath}`);\n }\n\n const {\n recursive = true,\n maxDepth = 10,\n includePatterns = [],\n excludePatterns = ['node_modules', '.git', 'dist', 'build', 'coverage', 'sessions', '.claude']\n } = options;\n\n // Scan the directory for files\n const scanResults = DocumentScanner.scanDirectory(resolvedPath, {\n recursive,\n maxDepth,\n includePatterns,\n excludePatterns\n });\n\n // Add each file as a separate source\n const addedIds = [];\n for (const result of scanResults) {\n if (!result.scannable) continue;\n\n let title = path.basename(result.path);\n let description = `File: ${result.path}`;\n\n // Use front matter if available\n if (result.frontMatter && result.frontMatter.title) {\n title = result.frontMatter.title;\n }\n if (result.frontMatter && result.frontMatter.description) {\n description = result.frontMatter.description;\n }\n\n const id = this.addSource({\n type: 'local_file',\n path: result.path,\n title,\n description,\n tags: tags.concat(result.frontMatter?.tags?.split(',') || []),\n size: result.size,\n contentType: result.contentType,\n scannable: result.scannable,\n wordCount: result.wordCount,\n lineCount: result.lineCount,\n headings: result.headings,\n links: result.links,\n codeBlocks: result.codeBlocks,\n content: result.content,\n snippet: result.snippet,\n checksum: result.checksum,\n lastModified: result.lastModified,\n indexedAt: Date.now()\n });\n addedIds.push(id);\n }\n\n // Also add the directory itself as a container\n this.addSource({\n type: 'local_directory',\n path: resolvedPath,\n title: path.basename(resolvedPath),\n description: `Directory with ${addedIds.length} indexed files`,\n tags,\n fileCount: addedIds.length\n });\n\n return addedIds;\n }\n\n /**\n * Add a NotebookLM notebook as knowledge source\n */\n addNotebook(notebookUrl, title, tags = []) {\n return this.addSource({\n type: 'notebooklm',\n notebookUrl,\n title,\n description: `NotebookLM: ${title}`,\n tags\n });\n }\n\n /**\n * List all knowledge sources\n */\n listSources(filter = {}) {\n this.loadIndex();\n\n let sources = [...this.index.sources];\n\n if (filter.type) {\n sources = sources.filter(s => s.type === filter.type);\n }\n\n if (filter.tag) {\n sources = sources.filter(s => s.tags.includes(filter.tag));\n }\n\n return sources.sort((a, b) => b.lastAccessed - a.lastAccessed);\n }\n\n /**\n * Get a specific knowledge source by ID\n */\n getSource(id) {\n this.loadIndex();\n return this.index.sources.find(s => s.id === id) || null;\n }\n\n /**\n * Remove a knowledge source\n */\n removeSource(id) {\n this.loadIndex();\n const initialLength = this.index.sources.length;\n this.index.sources = this.index.sources.filter(s => s.id !== id);\n\n if (this.index.sources.length < initialLength) {\n this.saveIndex();\n return true;\n }\n return false;\n }\n\n /**\n * Check if a source needs reindexing due to file changes\n */\n needsReindex(source) {\n if (source.type !== 'local_file') return false;\n if (!fs.existsSync(source.path)) return false; // File deleted\n\n try {\n const stats = fs.statSync(source.path);\n // Check if file was modified since last index\n if (source.lastModified && stats.mtimeMs > source.lastModified) {\n return true;\n }\n // Also check checksum if available\n if (source.checksum) {\n const currentContent = fs.readFileSync(source.path, 'utf-8');\n const currentChecksum = require('crypto')\n .createHash('md5')\n .update(currentContent)\n .digest('hex');\n return currentChecksum !== source.checksum;\n }\n } catch (error) {\n return false;\n }\n\n return false;\n }\n\n /**\n * Reindex a single file source\n */\n reindexFile(source) {\n try {\n const scanResult = DocumentScanner.scanFile(source.path, {\n includeContent: true\n });\n\n // Update the source with new data\n Object.assign(source, {\n wordCount: scanResult.wordCount,\n lineCount: scanResult.lineCount,\n headings: scanResult.headings,\n links: scanResult.links,\n codeBlocks: scanResult.codeBlocks,\n content: scanResult.content,\n snippet: scanResult.snippet,\n checksum: scanResult.checksum,\n lastModified: scanResult.lastModified,\n indexedAt: Date.now()\n });\n\n this.saveIndex();\n return true;\n } catch (error) {\n console.warn(`Failed to reindex ${source.path}:`, error.message);\n return false;\n }\n }\n\n /**\n * Update index for all files that have changed\n */\n updateIndex(options = {}) {\n const { progressCallback } = options;\n this.loadIndex();\n\n const fileSources = this.index.sources.filter(s => s.type === 'local_file');\n let updatedCount = 0;\n\n for (const source of fileSources) {\n if (this.needsReindex(source)) {\n progressCallback?.(`Reindexing: ${source.title}`);\n if (this.reindexFile(source)) {\n updatedCount++;\n }\n }\n }\n\n // Remove sources for files that no longer exist\n const initialLength = this.index.sources.length;\n this.index.sources = this.index.sources.filter(s => {\n if (s.type === 'local_file') {\n return fs.existsSync(s.path);\n }\n return true;\n });\n\n if (this.index.sources.length < initialLength) {\n this.saveIndex();\n }\n\n return { updatedCount, removedCount: initialLength - this.index.sources.length };\n }\n\n // --------------------------------------------------------------------------\n // Knowledge Query\n // --------------------------------------------------------------------------\n\n calculateRelevance(question, source) {\n const questionLower = question.toLowerCase();\n const questionWords = questionLower.split(/\\s+/).filter(w => w.length > 2);\n\n let score = 0;\n const weights = {\n title: 0.35,\n tags: 0.25,\n content: 0.30,\n headings: 0.10\n };\n\n // 1. Title matching\n const titleLower = (source.title || '').toLowerCase();\n const titleWords = titleLower.split(/\\s+/);\n let titleMatches = 0;\n questionWords.forEach(word => {\n if (titleWords.some(t => t.includes(word) || word.includes(t))) {\n titleMatches++;\n }\n });\n score += (titleMatches / Math.max(questionWords.length, 1)) * weights.title;\n\n // 2. Tag matching\n if (source.tags && source.tags.length > 0) {\n let tagMatches = 0;\n questionWords.forEach(word => {\n if (source.tags.some(t => t.toLowerCase().includes(word))) {\n tagMatches++;\n }\n });\n score += (tagMatches / Math.max(questionWords.length, 1)) * weights.tags;\n }\n\n // 3. Content matching (full-text search)\n if (source.content || source.snippet) {\n const contentLower = (source.content || source.snippet || '').toLowerCase();\n let contentMatches = 0;\n questionWords.forEach(word => {\n if (contentLower.includes(word)) {\n contentMatches++;\n }\n });\n score += (contentMatches / Math.max(questionWords.length, 1)) * weights.content;\n\n // Boost for exact phrase match\n if (contentLower.includes(questionLower)) {\n score += 0.2;\n }\n }\n\n // 4. Headings matching\n if (source.headings && source.headings.length > 0) {\n let headingMatches = 0;\n questionWords.forEach(word => {\n if (source.headings.some(h => h.text && h.text.toLowerCase().includes(word))) {\n headingMatches++;\n }\n });\n score += (headingMatches / Math.max(questionWords.length, 1)) * weights.headings;\n }\n\n return Math.min(score, 1);\n }\n\n /**\n * Query the knowledge base with NotebookLM integration\n */\n async query(question, options = {}) {\n this.loadIndex();\n\n const {\n includeWeb = false,\n maxSources = 5,\n progressCallback,\n useNotebookLM = true\n } = options;\n\n const sources = [];\n const webResults = [];\n let notebooklmAnswer = null;\n\n // Step 1: Search local knowledge base\n await progressCallback?.('Searching local knowledge base...', 1, 5);\n\n const localSources = this.index.sources.filter(\n s => s.type === 'local_file' || s.type === 'local_directory'\n );\n\n for (const source of localSources.slice(0, maxSources)) {\n const relevance = this.calculateRelevance(question, source);\n if (relevance > 0.3) {\n sources.push({\n title: source.title,\n type: source.type,\n relevance,\n excerpt: `Excerpt from ${source.title}`\n });\n source.lastAccessed = Date.now();\n }\n }\n\n // Step 2: Query NotebookLM if available and enabled\n await progressCallback?.('Querying NotebookLM...', 2, 5);\n\n const notebooklmSources = this.index.sources.filter(s => s.type === 'notebooklm');\n\n if (useNotebookLM && notebooklmSources.length > 0 && getClient) {\n try {\n const client = getClient();\n\n // Check if authenticated\n const stats = client.getStats();\n if (!stats.authenticated) {\n sources.push({\n title: 'NotebookLM (requires auth)',\n type: 'notebooklm',\n relevance: 0.9,\n excerpt: 'NotebookLM requires authentication. Run: smc notebooklm auth'\n });\n } else {\n // Use the first NotebookLM source\n const notebookUrl = notebooklmSources[0].notebookUrl || null;\n notebooklmAnswer = await client.ask(notebookUrl, question, (msg) => {\n // Forward progress\n });\n\n sources.push({\n title: 'NotebookLM',\n type: 'notebooklm',\n relevance: 1.0,\n excerpt: notebooklmAnswer?.substring(0, 200) + '...'\n });\n }\n } catch (error) {\n sources.push({\n title: 'NotebookLM (error)',\n type: 'notebooklm',\n relevance: 0.5,\n excerpt: `NotebookLM query failed: ${error.message}`\n });\n }\n } else if (notebooklmSources.length === 0) {\n sources.push({\n title: 'NotebookLM',\n type: 'notebooklm',\n relevance: 0.5,\n excerpt: 'No NotebookLM sources added. Add one with: smc knowledge notebook <url> <title>'\n });\n }\n\n // Step 3: Web search\n if (includeWeb) {\n await progressCallback?.('Searching web for latest information...', 3, 5);\n\n try {\n // Check cache first\n const cached = SearchCache.get(question);\n if (cached && cached.length > 0) {\n webResults.push(...cached);\n await progressCallback?.('Using cached web results...', 4, 5);\n } else {\n // Perform fresh search\n const freshResults = await WebSearch.search(question, { maxResults: 5 });\n if (freshResults.length > 0) {\n webResults.push(...freshResults);\n // Cache the results\n SearchCache.set(question, freshResults);\n }\n }\n } catch (error) {\n // Graceful degradation - search failed but continue\n webResults.push({\n title: 'Web Search (unavailable)',\n url: '#',\n excerpt: `Web search is currently unavailable: ${error.message}. Try again later.`,\n source: 'error'\n });\n }\n }\n\n // Step 4: Synthesize answer\n await progressCallback?.('Synthesizing answer...', 4, 5);\n\n // Use NotebookLM answer if available, otherwise fall back to synthesis\n let finalAnswer;\n if (notebooklmAnswer) {\n finalAnswer = notebooklmAnswer;\n } else {\n finalAnswer = this.synthesizeAnswer(question, sources, webResults);\n }\n\n await progressCallback?.('Query complete!', 5, 5);\n\n return {\n answer: finalAnswer,\n sources: sources.slice(0, maxSources).sort((a, b) => b.relevance - a.relevance),\n webResults,\n confidence: this.calculateConfidence(sources, webResults),\n notebooklmUsed: !!notebooklmAnswer\n };\n }\n\n /**\n * Query NotebookLM directly (bypass local knowledge)\n */\n async queryNotebookLM(question, notebookUrl = null, progressCallback) {\n if (!getClient) {\n throw new Error('NotebookLM module not available. Install patchright: npm install patchright');\n }\n\n const client = getClient();\n const stats = client.getStats();\n\n if (!stats.authenticated) {\n throw new Error('Not authenticated. Run: smc notebooklm auth');\n }\n\n await progressCallback?.('Asking NotebookLM...');\n const answer = await client.ask(notebookUrl, question, (msg) => {\n // Forward progress silently or log\n });\n\n return {\n answer,\n notebooklmUsed: true,\n confidence: 0.95\n };\n }\n\n synthesizeAnswer(question, sources, webResults) {\n if (sources.length === 0 && (!webResults || webResults.length === 0)) {\n return `No relevant information found in the knowledge base for: \"${question}\"`;\n }\n\n let answer = `Based on the knowledge base, here's what I found regarding \"${question}\":\\n\\n`;\n\n if (sources.length > 0) {\n answer += `**Local Knowledge:**\\n`;\n sources.forEach(source => {\n answer += `- ${source.title} (relevance: ${(source.relevance * 100).toFixed(0)}%)\\n`;\n if (source.excerpt && source.excerpt !== 'Excerpt from ' + source.title) {\n answer += ` ${source.excerpt.substring(0, 150)}${source.excerpt.length > 150 ? '...' : ''}\\n`;\n }\n });\n answer += '\\n';\n }\n\n if (webResults && webResults.length > 0) {\n answer += `**Web Sources:**\\n`;\n webResults.forEach(result => {\n // Format URL for display (decode if needed)\n let displayUrl = result.url;\n if (displayUrl.startsWith('a1aHR0c')) {\n // URL appears to be still encoded\n try {\n displayUrl = Buffer.from(displayUrl, 'base64').toString('ascii');\n // Extract the actual URL from the encoded string\n const urlMatch = displayUrl.match(/https?:\\/\\/[^&\\s]+/);\n if (urlMatch) displayUrl = urlMatch[0];\n } catch (e) {}\n }\n\n answer += `- [${result.title}](${displayUrl})\\n`;\n if (result.excerpt && result.excerpt !== 'No description available.') {\n answer += ` ${result.excerpt.substring(0, 200)}${result.excerpt.length > 200 ? '...' : ''}\\n`;\n }\n });\n }\n\n return answer;\n }\n\n calculateConfidence(sources, webResults) {\n const sourceCount = sources.length + (webResults?.length || 0);\n const avgRelevance = sources.reduce((sum, s) => sum + s.relevance, 0) / Math.max(sources.length, 1);\n\n return Math.min((sourceCount / 5) * 0.5 + avgRelevance * 0.5, 1);\n }\n\n // --------------------------------------------------------------------------\n // Statistics\n // --------------------------------------------------------------------------\n\n getStats() {\n this.loadIndex();\n\n const sourcesByType = {};\n this.index.sources.forEach(s => {\n sourcesByType[s.type] = (sourcesByType[s.type] || 0) + 1;\n });\n\n return {\n totalSources: this.index.sources.length,\n sourcesByType,\n lastUpdated: this.index.lastUpdated\n };\n }\n}\n\n// ============================================================================\n// Singleton Instance\n// ============================================================================\n\nlet knowledgeEngineInstance = null;\n\nfunction getKnowledgeEngine() {\n if (!knowledgeEngineInstance) {\n knowledgeEngineInstance = new KnowledgeEngine();\n }\n return knowledgeEngineInstance;\n}\n\n// ============================================================================\n// CLI Helpers\n// ============================================================================\n\nasync function handleKnowledgeCommand(args) {\n const engine = getKnowledgeEngine();\n const [action, ...rest] = args;\n\n switch (action) {\n case 'add': {\n const [filePath, ...restArgs] = rest;\n if (!filePath) {\n console.error('Usage: smc knowledge add <file|directory> [tags...] [--recursive] [--max-depth=N]');\n process.exit(1);\n }\n\n try {\n // Parse options\n const tags = restArgs.filter(arg => !arg.startsWith('--'));\n const isRecursive = restArgs.includes('--recursive');\n const maxDepthMatch = restArgs.find(arg => arg.startsWith('--max-depth='));\n const maxDepth = maxDepthMatch ? parseInt(maxDepthMatch.split('=')[1]) : 10;\n\n const resolvedPath = path.resolve(filePath);\n const stats = fs.existsSync(resolvedPath) ? fs.statSync(resolvedPath) : null;\n\n if (stats && stats.isDirectory()) {\n // Add directory\n console.log(`📁 Scanning directory: ${filePath}`);\n const addedIds = engine.addDirectory(filePath, tags, {\n recursive: isRecursive !== false, // default true\n maxDepth\n });\n console.log(`✅ Added ${addedIds.length} files from directory`);\n } else if (stats && stats.isFile()) {\n // Add single file\n const id = engine.addFile(filePath, tags);\n const source = engine.getSource(id);\n const scanInfo = source.scannable\n ? ` (${source.wordCount} words, ${source.headings?.length || 0} headings)`\n : ' (not scannable)';\n console.log(`✅ Added: ${source.title}${scanInfo}`);\n } else {\n console.error(`❌ Error: File not found: ${filePath}`);\n process.exit(1);\n }\n } catch (error) {\n console.error(`❌ Error: ${error.message}`);\n process.exit(1);\n }\n break;\n }\n\n case 'list': {\n const sources = engine.listSources();\n console.log(`\\n📚 Knowledge Base (${sources.length} sources):\\n`);\n\n if (sources.length === 0) {\n console.log(' No knowledge sources yet.');\n console.log(' Add sources with: smc knowledge add <file|directory> [tags...]');\n } else {\n const icons = {\n local_file: '📄',\n local_directory: '📁',\n notebooklm: '📓',\n web_search: '🔍',\n web_url: '🌐'\n };\n\n sources.forEach(source => {\n const icon = icons[source.type] || '📄';\n const tags = source.tags && source.tags.length > 0 ? ` [${source.tags.join(', ')}]` : '';\n console.log(` ${icon} ${source.title}${tags}`);\n\n // Show file info\n if (source.scannable) {\n const wordInfo = source.wordCount ? `${source.wordCount} words` : '';\n const headingInfo = source.headings && source.headings.length > 0 ? `${source.headings.length} headings` : '';\n const details = [wordInfo, headingInfo].filter(Boolean).join(', ');\n if (details) {\n console.log(` 📊 ${details}`);\n }\n }\n\n console.log(` Type: ${source.type} | Added: ${new Date(source.addedAt).toLocaleDateString()}`);\n });\n }\n console.log('');\n break;\n }\n\n case 'query': {\n const question = rest.join(' ').replace('--web', '').trim();\n if (!question) {\n console.error('Usage: smc knowledge query \"<question>\" [--web]');\n process.exit(1);\n }\n\n const includeWeb = rest.includes('--web');\n const result = await engine.query(question, { includeWeb });\n\n console.log(`\\n${result.answer}\\n`);\n console.log(`Confidence: ${(result.confidence * 100).toFixed(0)}%`);\n break;\n }\n\n case 'stats': {\n const stats = engine.getStats();\n console.log('\\n📊 Knowledge Base Statistics:\\n');\n console.log(` Total Sources: ${stats.totalSources}`);\n console.log(' By Type:');\n Object.entries(stats.sourcesByType).forEach(([type, count]) => {\n console.log(` - ${type}: ${count}`);\n });\n console.log(` Last Updated: ${new Date(stats.lastUpdated).toLocaleString()}\\n`);\n break;\n }\n\n case 'remove': {\n const [id] = rest;\n if (!id) {\n console.error('Usage: smc knowledge remove <source-id>');\n process.exit(1);\n }\n\n if (engine.removeSource(id)) {\n console.log(`✅ Removed knowledge source: ${id}`);\n } else {\n console.error(`❌ Source not found: ${id}`);\n process.exit(1);\n }\n break;\n }\n\n case 'update': {\n console.log('🔄 Checking for file changes...\\n');\n const result = engine.updateIndex({\n progressCallback: (msg) => console.log(` ${msg}`)\n });\n\n if (result.updatedCount === 0 && result.removedCount === 0) {\n console.log('✅ Index is up to date!');\n } else {\n console.log(`\\n✅ Updated: ${result.updatedCount} files`);\n if (result.removedCount > 0) {\n console.log(` Removed: ${result.removedCount} deleted files`);\n }\n }\n break;\n }\n\n case 'cache': {\n const [action] = rest;\n if (action === 'clear') {\n const cleared = SearchCache.clear();\n console.log(`🗑️ Cleared ${cleared} cached search results`);\n } else if (action === 'clean') {\n const cleaned = SearchCache.clean();\n console.log(`🧹 Cleaned ${cleaned} expired cache entries`);\n } else if (action === 'stats') {\n const stats = SearchCache.getStats();\n console.log('\\n📊 Search Cache Statistics:\\n');\n console.log(` Total Entries: ${stats.totalEntries}`);\n console.log(` Valid Entries: ${stats.validEntries}`);\n console.log(` Cache Size: ${(stats.totalSize / 1024).toFixed(2)} KB`);\n console.log(` Cache Dir: ${stats.cacheDir}\\n`);\n } else {\n console.log(`\nSearch Cache Commands:\n\n smc knowledge cache clear Clear all cached results\n smc knowledge cache clean Remove expired entries\n smc knowledge cache stats Show cache statistics\n `);\n }\n break;\n }\n\n case 'sync': {\n console.log('🔄 NotebookLM sync pending - requires notebooklm-mcp integration');\n break;\n }\n\n default:\n console.log(`\nKnowledge Base Commands:\n\n smc knowledge add <file|directory> [tags...] Add a knowledge source\n smc knowledge list List all sources\n smc knowledge query \"<question>\" [--web] Query the knowledge base\n smc knowledge remove <source-id> Remove a source\n smc knowledge update Update index for changed files\n smc knowledge cache <clear|clean|stats> Manage search cache\n smc knowledge stats Show statistics\n smc knowledge sync Sync with NotebookLM\n\nExamples:\n smc knowledge add ./docs/best-practices.md architecture\n smc knowledge add ./docs --recursive\n smc knowledge list\n smc knowledge query \"What are the best practices for API design?\"\n smc knowledge query --web \"React 19 new features\"\n smc knowledge update\n smc knowledge cache stats\n `);\n }\n}\n\nmodule.exports = {\n KnowledgeEngine,\n getKnowledgeEngine,\n handleKnowledgeCommand\n};\n",
|
|
74
|
+
"snippet": null,
|
|
75
|
+
"checksum": "b2af696f19e4514ee63584b3308cd9c0",
|
|
76
|
+
"lastModified": 1768582103048.8494,
|
|
77
|
+
"indexedAt": 1768588114499,
|
|
78
|
+
"id": "src_1768581173601_d50zxluba",
|
|
79
|
+
"addedAt": 1768581173601,
|
|
80
|
+
"lastAccessed": 1768581173601
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"type": "local_file",
|
|
84
|
+
"path": "/Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/notebooklm/browser.js",
|
|
85
|
+
"title": "browser.js",
|
|
86
|
+
"description": "File: /Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/notebooklm/browser.js",
|
|
87
|
+
"tags": [],
|
|
88
|
+
"size": 29372,
|
|
89
|
+
"contentType": "text/javascript",
|
|
90
|
+
"scannable": true,
|
|
91
|
+
"wordCount": 3343,
|
|
92
|
+
"lineCount": 1029,
|
|
93
|
+
"headings": [
|
|
94
|
+
{
|
|
95
|
+
"type": "class",
|
|
96
|
+
"name": "PathManager",
|
|
97
|
+
"line": 49
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"type": "class",
|
|
101
|
+
"name": "StealthUtils",
|
|
102
|
+
"line": 98
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"type": "class",
|
|
106
|
+
"name": "PageUtils",
|
|
107
|
+
"line": 163
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"type": "class",
|
|
111
|
+
"name": "AuthManager",
|
|
112
|
+
"line": 334
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"type": "class",
|
|
116
|
+
"name": "NotebookLMSession",
|
|
117
|
+
"line": 431
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"type": "class",
|
|
121
|
+
"name": "SessionManager",
|
|
122
|
+
"line": 754
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"type": "class",
|
|
126
|
+
"name": "NotebookLMClient",
|
|
127
|
+
"line": 842
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"type": "function",
|
|
131
|
+
"name": "getClient",
|
|
132
|
+
"line": 902
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"type": "function",
|
|
136
|
+
"name": "handleNotebookLMCommand",
|
|
137
|
+
"line": 913
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
"links": [],
|
|
141
|
+
"codeBlocks": [],
|
|
142
|
+
"content": "/**\n * NotebookLM Browser Automation Module (Simplified)\n *\n * Provides browser automation for NotebookLM interactions:\n * - Authentication management\n * - Session management\n * - Question/answer with streaming detection\n * - Human-like behavior simulation\n *\n * This is a simplified version of notebooklm-mcp core modules\n * adapted for integration into sumulige-claude.\n */\n\nconst fs = require('fs');\nconst path = require('path');\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst DEFAULT_CONFIG = {\n // Browser settings\n headless: true,\n viewport: { width: 1024, height: 768 },\n\n // Timeout settings\n browserTimeout: 120000, // 2 minutes\n responseTimeout: 60000, // 1 minute\n sessionTimeout: 900000, // 15 minutes\n\n // Stealth settings\n stealthEnabled: true,\n humanTyping: true,\n typingWpmMin: 90,\n typingWpmMax: 120,\n randomDelays: true,\n minDelayMs: 100,\n maxDelayMs: 300,\n\n // URLs\n notebookUrl: 'https://notebooklm.google.com',\n authUrl: 'https://accounts.google.com'\n};\n\n// ============================================================================\n// Path Management\n// ============================================================================\n\nclass PathManager {\n constructor() {\n // Use cross-platform home directory\n const homeDir = require('os').homedir();\n const appName = 'sumulige-claude';\n\n // Platform-specific data directory\n const platform = require('os').platform();\n let baseDir;\n\n if (platform === 'darwin') {\n baseDir = path.join(homeDir, 'Library', 'Application Support', appName);\n } else if (platform === 'win32') {\n baseDir = path.join(homeDir, 'AppData', 'Roaming', appName);\n } else {\n baseDir = path.join(homeDir, '.local', 'share', appName);\n }\n\n // Fallback to home directory if platform-specific path fails\n this.dataDir = baseDir || path.join(homeDir, `.${appName}`);\n this.browserStateDir = path.join(this.dataDir, 'notebooklm-state');\n this.stateFilePath = path.join(this.browserStateDir, 'state.json');\n this.sessionFilePath = path.join(this.browserStateDir, 'session.json');\n this.ensureDirectories();\n }\n\n ensureDirectories() {\n if (!fs.existsSync(this.browserStateDir)) {\n fs.mkdirSync(this.browserStateDir, { recursive: true });\n }\n }\n\n getStatePath() {\n return fs.existsSync(this.stateFilePath) ? this.stateFilePath : null;\n }\n\n getSessionPath() {\n return this.sessionFilePath;\n }\n\n getDataDir() {\n return this.dataDir;\n }\n}\n\n// ============================================================================\n// Stealth Utilities (Human-like behavior)\n// ============================================================================\n\nclass StealthUtils {\n static sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n static randomInt(min, max) {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n }\n\n static randomFloat(min, max) {\n return Math.random() * (max - min) + min;\n }\n\n // Gaussian distribution (Box-Muller transform)\n static gaussian(mean, stdDev) {\n const u1 = Math.random();\n const u2 = Math.random();\n const z0 = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);\n return z0 * stdDev + mean;\n }\n\n static async randomDelay(minMs, maxMs) {\n minMs = minMs ?? DEFAULT_CONFIG.minDelayMs;\n maxMs = maxMs ?? DEFAULT_CONFIG.maxDelayMs;\n\n if (!DEFAULT_CONFIG.stealthEnabled || !DEFAULT_CONFIG.randomDelays) {\n const target = (minMs + maxMs) / 2;\n if (target > 0) await this.sleep(target);\n return;\n }\n\n const mean = minMs + (maxMs - minMs) * 0.6;\n const stdDev = (maxMs - minMs) * 0.2;\n let delay = this.gaussian(mean, stdDev);\n delay = Math.max(minMs, Math.min(maxMs, delay));\n\n await this.sleep(delay);\n }\n\n // Calculate delay per character based on WPM\n static getTypingDelay(wpm) {\n const charsPerMinute = wpm * 5;\n return (60 * 1000) / charsPerMinute;\n }\n\n // Get pause duration for punctuation\n static getPunctuationPause(char) {\n const pauses = {\n '.': 300,\n '!': 350,\n '?': 300,\n ',': 150,\n ';': 200,\n ':': 200,\n ' ': 50,\n '\\n': 400\n };\n return pauses[char] || 50;\n }\n}\n\n// ============================================================================\n// Page Utilities (NotebookLM specific)\n// ============================================================================\n\nclass PageUtils {\n // CSS selectors for NotebookLM responses\n static RESPONSE_SELECTORS = [\n // Primary NotebookLM selectors\n '.to-user-container .message-text-content',\n '.to-user-container .markdown-content',\n '.to-user-container',\n // Generic bot/assistant selectors\n '[data-message-author=\"bot\"]',\n '[data-message-author=\"assistant\"]',\n '[data-message-role=\"assistant\"]',\n // Content containers\n 'markdown-response',\n '.response-text',\n '.message-text-content',\n // Data attributes\n '[data-automation-id=\"response-text\"]',\n '[data-automation-id=\"assistant-response\"]',\n // Last resort\n '.message-text',\n '[role=\"log\"]'\n ];\n\n // Placeholder texts to ignore\n static PLACEHOLDERS = new Set([\n '',\n '...',\n 'Thinking...',\n 'Generating response...',\n 'Please wait...',\n 'Finding relevant info...',\n 'Searching',\n 'Loading'\n ]);\n\n /**\n * Extract latest response from NotebookLM page\n */\n static async extractLatestResponse(page, existingHashes = new Set()) {\n for (const selector of this.RESPONSE_SELECTORS) {\n try {\n const elements = await page.$$(selector);\n if (elements.length > 0) {\n // Get the last (latest) element\n const latest = elements[elements.length - 1];\n const text = await latest.evaluate(el => el.textContent?.trim() || '');\n const hash = this.hashString(text);\n\n // Only return if it's a new response (not in existing hashes)\n if (text && !this.PLACEHOLDERS.has(text) && !existingHashes.has(hash)) {\n return { text, hash, selector };\n }\n }\n } catch (e) {\n continue;\n }\n }\n return null;\n }\n\n /**\n * Snapshot all existing responses before asking a question\n */\n static async snapshotAllResponses(page) {\n const hashes = new Set();\n const texts = [];\n\n try {\n const containers = await page.$$('.to-user-container');\n for (const container of containers) {\n try {\n const textEl = await container.$('.message-text-content');\n if (textEl) {\n const text = await textEl.evaluate(el => el.textContent?.trim() || '');\n if (text) {\n hashes.add(this.hashString(text));\n texts.push(text);\n }\n }\n } catch (e) {\n continue;\n }\n }\n } catch (e) {\n // Ignore errors\n }\n\n return { hashes, texts };\n }\n\n /**\n * Wait for new response with streaming detection\n */\n static async waitForNewResponse(page, existingHashes, timeout = 120000) {\n const startTime = Date.now();\n let stableCount = 0;\n const STABLE_THRESHOLD = 5; // Increased for better detection\n const POLL_INTERVAL = 1000; // Increased to reduce CPU usage\n let lastResponse = null;\n let lastLength = 0;\n\n console.log(`⏳ Waiting for response (timeout: ${timeout/1000}s)...`);\n\n while (Date.now() - startTime < timeout) {\n await StealthUtils.sleep(POLL_INTERVAL);\n\n const response = await this.extractLatestResponse(page, existingHashes);\n\n if (response) {\n const currentLength = response.text.length;\n\n // Check if response is growing (streaming) or stable\n if (lastResponse && lastResponse.text === response.text) {\n stableCount++;\n if (stableCount >= STABLE_THRESHOLD) {\n console.log(`✅ Response complete (${response.text.length} chars)`);\n return response.text;\n }\n } else if (currentLength > lastLength) {\n // Response is still growing\n stableCount = 0;\n lastResponse = response;\n lastLength = currentLength;\n console.log(`⏳ Receiving... (${currentLength} chars)`);\n } else {\n // Different response (unlikely but handle it)\n stableCount = 0;\n lastResponse = response;\n lastLength = currentLength;\n }\n } else {\n // No new response yet, check existing ones\n const allTexts = await this.snapshotAllResponses(page);\n if (allTexts.texts.length > 0) {\n const latest = allTexts.texts[allTexts.texts.length - 1];\n if (latest && latest.length > 0 && !this.PLACEHOLDERS.has(latest)) {\n lastResponse = { text: latest };\n lastLength = latest.length;\n }\n }\n }\n\n // Progress indicator\n const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);\n process.stdout.write(`\\r⏳ ${elapsed}s / ${timeout/1000}s`);\n }\n\n process.stdout.write('\\n');\n\n if (lastResponse) {\n console.log(`✅ Got response (${lastResponse.text.length} chars)`);\n return lastResponse.text;\n }\n\n return null;\n }\n\n static hashString(str) {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = ((hash << 5) - hash + char) & 0xffffffff;\n }\n return hash.toString(36);\n }\n}\n\n// ============================================================================\n// Authentication Manager\n// ============================================================================\n\nclass AuthManager {\n constructor() {\n this.paths = new PathManager();\n }\n\n /**\n * Check if saved state exists and is valid\n */\n hasValidState() {\n const statePath = this.paths.getStatePath();\n if (!statePath) return false;\n\n // Check if state is less than 24 hours old\n const stats = fs.statSync(statePath);\n const age = Date.now() - stats.mtime.getTime();\n const maxAge = 24 * 60 * 60 * 1000; // 24 hours\n\n return age < maxAge;\n }\n\n /**\n * Get the state file path for loading\n */\n getStatePath() {\n return this.paths.getStatePath();\n }\n\n /**\n * Save browser state after authentication\n */\n async saveState(context, page) {\n try {\n // Save cookies and localStorage\n await context.storageState({ path: this.paths.stateFilePath });\n\n // Save sessionStorage separately\n if (page) {\n const sessionStorage = await page.evaluate(() => {\n const storage = {};\n for (let i = 0; i < sessionStorage.length; i++) {\n const key = sessionStorage.key(i);\n if (key) {\n storage[key] = sessionStorage.getItem(key) || '';\n }\n }\n return JSON.stringify(storage);\n });\n\n await fs.promises.writeFile(\n this.paths.sessionFilePath,\n sessionStorage,\n 'utf-8'\n );\n }\n\n return true;\n } catch (error) {\n console.error('Failed to save state:', error.message);\n return false;\n }\n }\n\n /**\n * Clear all authentication data\n */\n async clearState() {\n try {\n if (fs.existsSync(this.paths.stateFilePath)) {\n fs.unlinkSync(this.paths.stateFilePath);\n }\n if (fs.existsSync(this.paths.sessionFilePath)) {\n fs.unlinkSync(this.paths.sessionFilePath);\n }\n return true;\n } catch (error) {\n console.error('Failed to clear state:', error.message);\n return false;\n }\n }\n\n /**\n * Load sessionStorage data\n */\n async loadSessionStorage() {\n try {\n const data = await fs.promises.readFile(this.paths.sessionFilePath, 'utf-8');\n return JSON.parse(data);\n } catch (error) {\n return null;\n }\n }\n}\n\n// ============================================================================\n// NotebookLM Session\n// ============================================================================\n\nclass NotebookLMSession {\n constructor(notebookUrl, options = {}) {\n this.notebookUrl = notebookUrl || DEFAULT_CONFIG.notebookUrl;\n this.options = { ...DEFAULT_CONFIG, ...options };\n this.authManager = new AuthManager();\n this.browser = null;\n this.context = null;\n this.page = null;\n this.isActive = false;\n this.createdAt = Date.now();\n this.lastActivity = Date.now();\n }\n\n /**\n * Initialize the browser session\n */\n async init() {\n // Check if patchright is available\n let patchright;\n try {\n patchright = require('patchright');\n } catch (e) {\n throw new Error('patchright is not installed. Install with: npm install patchright');\n }\n\n const paths = new PathManager();\n const statePath = this.authManager.getStatePath();\n const needsAuth = !this.authManager.hasValidState();\n\n console.log(`🔑 Authentication needed: ${needsAuth ? 'YES' : 'NO (using saved state)'}`);\n\n // Launch browser\n this.browser = await patchright.chromium.launchPersistentContext(\n paths.getDataDir(),\n {\n headless: this.options.headless && !needsAuth, // Show browser for auth\n channel: 'chrome',\n viewport: this.options.viewport,\n args: [\n '--disable-blink-features=AutomationControlled'\n ]\n }\n );\n\n // Get or create page\n const pages = this.browser.pages();\n this.page = pages[0] || await this.browser.newPage();\n this.context = this.browser;\n\n // Load existing state if available\n if (statePath && !needsAuth) {\n // State is loaded automatically by launchPersistentContext\n\n // Restore sessionStorage\n const sessionData = await this.authManager.loadSessionStorage();\n if (sessionData) {\n await this.page.evaluate((data) => {\n for (const [key, value] of Object.entries(data)) {\n sessionStorage.setItem(key, String(value));\n }\n }, sessionData);\n }\n }\n\n // Navigate to NotebookLM with longer timeout\n await this.page.goto(this.notebookUrl, { waitUntil: 'domcontentloaded', timeout: 120000 });\n\n // Wait for page to be fully loaded\n await StealthUtils.sleep(2000);\n\n // Check if we need to create/select a notebook\n const currentUrl = this.page.url();\n console.log(`Current URL: ${currentUrl}`);\n\n // Check if authentication is needed\n if (needsAuth) {\n console.log('🔐 Please authenticate in the browser window...');\n console.log('✋ Sign in with your Google account, then close the browser when done.');\n\n // Wait for authentication (user will close browser when done)\n await this.waitForAuthentication();\n\n // Save the authenticated state\n await this.authManager.saveState(this.context, this.page);\n console.log('✅ Authentication state saved!');\n console.log('');\n console.log('💡 Next: Create a notebook in NotebookLM and save its URL for asking questions.');\n }\n\n this.isActive = true;\n this.lastActivity = Date.now();\n\n return true;\n }\n\n /**\n * Wait for user to complete authentication\n */\n async waitForAuthentication() {\n // Wait for navigation to authenticated state\n // This is a simple implementation - in production you'd poll for auth cookies\n return new Promise((resolve) => {\n const checkAuth = async () => {\n const cookies = await this.context.cookies();\n const hasAuthCookie = cookies.some(c =>\n c.name === 'SID' || c.name === 'HSID' || c.name === 'SSID'\n );\n\n if (hasAuthCookie) {\n // Wait a bit more for page to load\n await StealthUtils.sleep(2000);\n resolve();\n } else {\n setTimeout(checkAuth, 2000);\n }\n };\n\n // Start checking after initial page load\n setTimeout(checkAuth, 3000);\n });\n }\n\n /**\n * Ask a question to NotebookLM\n */\n async ask(question, progressCallback) {\n if (!this.isActive) {\n throw new Error('Session is not active. Call init() first.');\n }\n\n await progressCallback?.('Asking NotebookLM...');\n\n // Snapshot existing responses\n const { hashes: existingHashes } = await PageUtils.snapshotAllResponses(this.page);\n\n // Find and click the input field - expanded selectors for NotebookLM\n const inputSelectors = [\n // Google-specific selectors\n 'textarea[aria-label*=\"ask\" i]',\n 'textarea[aria-label*=\"message\" i]',\n 'textarea[aria-label*=\"chat\" i]',\n 'textarea[placeholder*=\"ask\" i]',\n 'textarea[placeholder*=\"Ask\" i]',\n 'textarea[placeholder*=\"Chat\" i]',\n // Generic selectors\n 'textarea.contenteditable',\n 'rich-textarea',\n 'div[contenteditable=\"true\"] textarea',\n 'div[contenteditable=\"true\"]',\n 'textarea',\n // Data attributes\n '[data-test-id=\"chat-input\"]',\n '[data-testid=\"chat-input\"]',\n '[data-test-id=\"prompt-input\"]',\n '[data-testid=\"prompt-input\"]',\n '[data-automation-id=\"chat-input\"]',\n // Specific classes\n '.ql-editor',\n '.ProseMirror',\n 'markdown-textarea',\n // If all else fails, get all textareas\n 'textarea'\n ];\n\n let inputField = null;\n let usedSelector = null;\n\n for (const selector of inputSelectors) {\n try {\n const elements = await this.page.$$(selector);\n for (const el of elements) {\n try {\n const isVisible = await el.isVisible();\n const isEnabled = await el.isEnabled().catch(() => true);\n if (isVisible && isEnabled) {\n inputField = el;\n usedSelector = selector;\n break;\n }\n } catch (e) {\n continue;\n }\n }\n if (inputField) break;\n } catch (e) {\n continue;\n }\n }\n\n if (!inputField) {\n // Debug: list all potential input elements on page\n const allTextareas = await this.page.$$('textarea, [contenteditable=\"true\"]');\n console.log(`Found ${allTextareas.length} potential input elements on page`);\n\n // Try to get page title for debugging\n const title = await this.page.title().catch(() => 'Unknown');\n const url = this.page.url();\n console.log(`Page title: ${title}`);\n console.log(`Current URL: ${url}`);\n\n // Check if we're on the NotebookLM homepage\n const isHomepage = url.includes('notebooklm.google') &&\n (url === 'https://notebooklm.google.com/' ||\n url === 'https://notebooklm.google.com' ||\n url.includes('notebooklm.google/?'));\n\n if (isHomepage) {\n throw new Error('You are on the NotebookLM homepage. Please:\\n' +\n '1. Create a new notebook or open an existing one\\n' +\n '2. Copy the notebook URL\\n' +\n '3. Use that URL with the command: smc notebooklm ask \"<question>\" <notebook-url>');\n }\n\n throw new Error('Could not find input field on the page. Make sure you are on a NotebookLM notebook page.');\n }\n\n console.log(`✅ Found input field using selector: ${usedSelector}`);\n\n // Type the question with human-like behavior\n await progressCallback?.('Typing question...');\n\n if (this.options.humanTyping) {\n // Use the selector we found, not inputField.selector()\n await this.humanType(usedSelector, question);\n } else {\n await inputField.fill(question);\n }\n\n await StealthUtils.randomDelay(200, 500);\n\n // Submit (press Enter)\n await this.page.keyboard.press('Enter');\n\n await progressCallback?.('Waiting for response...');\n\n // Wait for new response\n const response = await PageUtils.waitForNewResponse(\n this.page,\n existingHashes,\n this.options.responseTimeout\n );\n\n this.lastActivity = Date.now();\n\n if (!response) {\n throw new Error('No response received within timeout period');\n }\n\n await progressCallback?.('Response received!');\n\n return response;\n }\n\n /**\n * Type text with human-like behavior\n */\n async humanType(selector, text) {\n const avgDelay = StealthUtils.getTypingDelay(\n StealthUtils.randomInt(this.options.typingWpmMin, this.options.typingWpmMax)\n );\n\n await this.page.fill(selector, '');\n await this.page.click(selector);\n await StealthUtils.randomDelay(50, 150);\n\n for (const char of text) {\n await this.page.keyboard.type(char);\n const pause = StealthUtils.getPunctuationPause(char);\n const variance = StealthUtils.randomFloat(-30, 30);\n await StealthUtils.sleep(Math.max(20, avgDelay + pause + variance));\n }\n }\n\n /**\n * Check if session has expired\n */\n isExpired() {\n const age = Date.now() - this.lastActivity;\n return age > this.options.sessionTimeout;\n }\n\n /**\n * Close the session\n */\n async close() {\n this.isActive = false;\n\n if (this.page) {\n await this.page.close().catch(() => {});\n }\n\n if (this.context) {\n await this.context.close().catch(() => {});\n }\n\n if (this.browser) {\n await this.browser.close().catch(() => {});\n }\n\n this.page = null;\n this.context = null;\n this.browser = null;\n }\n\n /**\n * Get session info\n */\n getInfo() {\n return {\n notebookUrl: this.notebookUrl,\n isActive: this.isActive,\n createdAt: this.createdAt,\n lastActivity: this.lastActivity,\n age: Date.now() - this.createdAt,\n inactiveTime: Date.now() - this.lastActivity\n };\n }\n}\n\n// ============================================================================\n// Session Manager\n// ============================================================================\n\nclass SessionManager {\n constructor() {\n this.sessions = new Map();\n this.maxSessions = 5;\n }\n\n /**\n * Get or create a session for a notebook\n */\n async getSession(notebookUrl, options = {}) {\n // Clean up expired sessions first\n this.cleanupExpired();\n\n // Find existing session for this notebook\n for (const [id, session] of this.sessions) {\n if (session.notebookUrl === notebookUrl && session.isActive && !session.isExpired()) {\n session.lastActivity = Date.now();\n return session;\n }\n }\n\n // Create new session\n if (this.sessions.size >= this.maxSessions) {\n // Close oldest inactive session\n const oldest = [...this.sessions.entries()].sort((a, b) => a[1].lastActivity - b[1].lastActivity)[0];\n await this.sessions.get(oldest)?.close();\n this.sessions.delete(oldest);\n }\n\n const session = new NotebookLMSession(notebookUrl, options);\n await session.init();\n const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;\n this.sessions.set(sessionId, session);\n\n return session;\n }\n\n /**\n * Close a specific session\n */\n async closeSession(sessionId) {\n const session = this.sessions.get(sessionId);\n if (session) {\n await session.close();\n this.sessions.delete(sessionId);\n return true;\n }\n return false;\n }\n\n /**\n * Close all sessions\n */\n async closeAll() {\n const promises = [...this.sessions.values()].map(s => s.close());\n await Promise.all(promises);\n this.sessions.clear();\n }\n\n /**\n * Clean up expired sessions\n */\n cleanupExpired() {\n for (const [id, session] of this.sessions) {\n if (session.isExpired()) {\n session.close().catch(() => {});\n this.sessions.delete(id);\n }\n }\n }\n\n /**\n * Get session statistics\n */\n getStats() {\n const active = [...this.sessions.values()].filter(s => s.isActive).length;\n return {\n total: this.sessions.size,\n active,\n maxSessions: this.maxSessions\n };\n }\n}\n\n// ============================================================================\n// Main NotebookLM Client\n// ============================================================================\n\nclass NotebookLMClient {\n constructor() {\n this.sessionManager = new SessionManager();\n this.authManager = new AuthManager();\n }\n\n /**\n * Check if authenticated\n */\n isAuthenticated() {\n return this.authManager.hasValidState();\n }\n\n /**\n * Setup authentication (opens browser for manual login)\n */\n async setup(progressCallback) {\n await progressCallback?.('Launching browser for authentication...');\n\n const session = new NotebookLMSession(null, { headless: false });\n await session.init();\n await session.close();\n\n await progressCallback?.('Authentication complete!');\n\n return true;\n }\n\n /**\n * Ask a question to NotebookLM\n */\n async ask(notebookUrl, question, progressCallback) {\n const session = await this.sessionManager.getSession(notebookUrl);\n return await session.ask(question, progressCallback);\n }\n\n /**\n * Close all sessions\n */\n async closeAll() {\n await this.sessionManager.closeAll();\n }\n\n /**\n * Get statistics\n */\n getStats() {\n return {\n authenticated: this.isAuthenticated(),\n sessions: this.sessionManager.getStats()\n };\n }\n}\n\n// ============================================================================\n// Module Singleton\n// ============================================================================\n\nlet clientInstance = null;\n\nfunction getClient() {\n if (!clientInstance) {\n clientInstance = new NotebookLMClient();\n }\n return clientInstance;\n}\n\n// ============================================================================\n// CLI Command Handlers\n// ============================================================================\n\nasync function handleNotebookLMCommand(args) {\n const client = getClient();\n const [action, ...rest] = args;\n\n switch (action) {\n case 'auth':\n case 'setup': {\n console.log('\\n🔐 NotebookLM Authentication\\n');\n\n if (client.isAuthenticated()) {\n console.log('✅ Already authenticated!');\n console.log('💡 To re-authenticate, run: smc notebooklm clear && smc notebooklm auth');\n return;\n }\n\n try {\n await client.setup((msg) => console.log(` ${msg}`));\n console.log('\\n✅ Authentication successful!');\n } catch (error) {\n console.error(`\\n❌ Authentication failed: ${error.message}`);\n console.log('\\n💡 Make sure you have Google Chrome installed');\n console.log('💡 Install patchright: npm install patchright');\n process.exit(1);\n }\n break;\n }\n\n case 'ask': {\n const [question, ...urlParts] = rest;\n const notebookUrl = urlParts.join(' ') || null;\n\n if (!question) {\n console.error('Usage: smc notebooklm ask \"<question>\" [notebook-url]');\n console.error('');\n console.error('Examples:');\n console.error(' smc notebooklm ask \"What are best practices for API design?\"');\n console.error(' smc notebooklm ask \"Summarize this document\" https://notebooklm.google.com/notebook/...');\n process.exit(1);\n }\n\n console.log(`\\n📓 Asking: ${question}\\n`);\n\n if (!notebookUrl) {\n console.log('⚠️ No notebook URL provided. You need to:');\n console.log(' 1. Go to https://notebooklm.google.com');\n console.log(' 2. Create a new notebook (or open an existing one)');\n console.log(' 3. Add sources to your notebook (PDFs, docs, etc.)');\n console.log(' 4. Copy the notebook URL from the address bar');\n console.log(' 5. Run: smc notebooklm ask \"' + question + '\" <your-notebook-url>');\n console.log('');\n console.log('💡 The notebook URL looks like:');\n console.log(' https://notebooklm.google.com/notebook/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');\n console.log('');\n process.exit(1);\n }\n\n try {\n const response = await client.ask(notebookUrl, question, (msg) => {\n console.log(` ${msg}`);\n });\n\n console.log('\\n📝 Response:\\n');\n console.log(response);\n console.log('\\n');\n } catch (error) {\n console.error(`\\n❌ Error: ${error.message}`);\n process.exit(1);\n }\n break;\n }\n\n case 'status': {\n const stats = client.getStats();\n\n console.log('\\n📊 NotebookLM Status\\n');\n console.log(` Authenticated: ${stats.authenticated ? '✅ Yes' : '❌ No'}`);\n console.log(` Active Sessions: ${stats.sessions.active}/${stats.sessions.maxSessions}`);\n console.log('');\n break;\n }\n\n case 'clear': {\n const authManager = new AuthManager();\n await authManager.clearState();\n await client.closeAll();\n console.log('\\n✅ Cleared authentication data and closed all sessions\\n');\n break;\n }\n\n default:\n console.log(`\nNotebookLM Commands:\n\n smc notebooklm auth Authenticate with NotebookLM\n smc notebooklm ask \"<question>\" Ask a question\n smc notebooklm status Show authentication status\n smc notebooklm clear Clear authentication data\n\nExamples:\n smc notebooklm auth\n smc notebooklm ask \"What are the best practices for REST API design?\"\n smc notebooklm status\n `);\n }\n}\n\nmodule.exports = {\n NotebookLMClient,\n NotebookLMSession,\n AuthManager,\n SessionManager,\n getClient,\n handleNotebookLMCommand,\n PageUtils,\n StealthUtils\n};\n",
|
|
143
|
+
"snippet": null,
|
|
144
|
+
"checksum": "b6de61ed325c475b0c6cef5237428473",
|
|
145
|
+
"lastModified": 1768580159455.6853,
|
|
146
|
+
"indexedAt": 1768581173602,
|
|
147
|
+
"id": "src_1768581173602_hsjdr5xm0",
|
|
148
|
+
"addedAt": 1768581173602,
|
|
149
|
+
"lastAccessed": 1768581173602
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"type": "local_file",
|
|
153
|
+
"path": "/Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/phases/phase1-research.js",
|
|
154
|
+
"title": "phase1-research.js",
|
|
155
|
+
"description": "File: /Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/phases/phase1-research.js",
|
|
156
|
+
"tags": [],
|
|
157
|
+
"size": 15150,
|
|
158
|
+
"contentType": "text/javascript",
|
|
159
|
+
"scannable": true,
|
|
160
|
+
"wordCount": 1656,
|
|
161
|
+
"lineCount": 561,
|
|
162
|
+
"headings": [
|
|
163
|
+
{
|
|
164
|
+
"type": "class",
|
|
165
|
+
"name": "FeasibilityValidator",
|
|
166
|
+
"line": 18
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"type": "class",
|
|
170
|
+
"name": "Phase1ResearchExecutor",
|
|
171
|
+
"line": 258
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"type": "function",
|
|
175
|
+
"name": "generateProjectId",
|
|
176
|
+
"line": 513
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"type": "function",
|
|
180
|
+
"name": "createProject",
|
|
181
|
+
"line": 519
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"type": "function",
|
|
185
|
+
"name": "listProjects",
|
|
186
|
+
"line": 531
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
"links": [],
|
|
190
|
+
"codeBlocks": [],
|
|
191
|
+
"content": "/**\n * Phase 1: Research - NotebookLM Feasibility Analysis (JavaScript version)\n */\n\nconst fs = require('fs');\nconst path = require('path');\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst PROJECTS_DIR = path.join(process.cwd(), 'development/projects');\n\n// ============================================================================\n// Feasibility Validator\n// ============================================================================\n\nclass FeasibilityValidator {\n /**\n * Validate a feasibility report from markdown content\n */\n static validateFromMarkdown(content) {\n const checks = [];\n const blockers = [];\n const warnings = [];\n\n // Check 1: Has requirement summary\n const hasRequirementSummary =\n content.includes('## Requirements Summary') ||\n content.includes('## 需求概述') ||\n content.includes('# 需求') ||\n content.match(/需求|问题|目标|用户/);\n\n checks.push({\n name: 'Requirement Summary',\n passed: hasRequirementSummary,\n message: hasRequirementSummary\n ? 'Requirement summary found'\n : 'Missing requirement summary section'\n });\n\n if (!hasRequirementSummary) {\n blockers.push('Add a requirements summary section describing what we are building');\n }\n\n // Check 2: Has correlation analysis\n const hasCorrelationAnalysis =\n content.includes('## Correlation Analysis') ||\n content.includes('## 关联分析') ||\n content.includes('## 关联发现') ||\n content.includes('Related Projects') ||\n content.includes('相关项目') ||\n content.includes('reusable');\n\n checks.push({\n name: 'Correlation Analysis',\n passed: hasCorrelationAnalysis,\n message: hasCorrelationAnalysis\n ? 'Correlation analysis found'\n : 'Missing correlation analysis - connect the dots with related work'\n });\n\n if (!hasCorrelationAnalysis) {\n warnings.push('Add correlation analysis to find related work and reusable components');\n }\n\n // Check 3: Has best practices\n const hasBestPractices =\n content.includes('## Best Practices') ||\n content.includes('## 业界最佳实践') ||\n content.includes('## 最佳实践') ||\n content.includes('Industry') ||\n content.includes('sources') ||\n content.includes('参考');\n\n checks.push({\n name: 'Best Practices Research',\n passed: hasBestPractices,\n message: hasBestPractices\n ? 'Best practices research found'\n : 'Missing industry best practices research'\n });\n\n if (!hasBestPractices) {\n blockers.push('Add best practices research with cited sources');\n }\n\n // Check 4: Has feasibility conclusion\n const hasFeasibilityConclusion =\n content.includes('## Feasibility Assessment') ||\n content.includes('## 可行性评估') ||\n content.includes('feasibility') ||\n content.includes('可行性') ||\n content.match(/⭐\\s*\\d|难度|复杂度|Technical/);\n\n checks.push({\n name: 'Feasibility Conclusion',\n passed: hasFeasibilityConclusion,\n message: hasFeasibilityConclusion\n ? 'Feasibility assessment found'\n : 'Missing feasibility assessment with concrete ratings'\n });\n\n if (!hasFeasibilityConclusion) {\n blockers.push('Add feasibility assessment with technical complexity rating (⭐ 1-5)');\n }\n\n // Check 5: Has time estimate\n const hasTimeEstimate =\n content.includes('Time Estimate') ||\n content.includes('时间预估') ||\n content.includes('estimated') ||\n content.match(/\\d+\\s*(hour|h|小时|day|天)/);\n\n checks.push({\n name: 'Time Estimate',\n passed: hasTimeEstimate,\n message: hasTimeEstimate\n ? 'Time estimate found'\n : 'Missing time estimate for implementation'\n });\n\n if (!hasTimeEstimate) {\n warnings.push('Add concrete time estimate (e.g., \"4 hours\")');\n }\n\n // Check 6: Has risk assessment\n const hasRiskAssessment =\n content.includes('## Risk') ||\n content.includes('## 风险') ||\n content.includes('risk') ||\n content.includes('mitigation') ||\n content.includes('缓解') ||\n content.match(/挑战|问题|Risk/);\n\n checks.push({\n name: 'Risk Assessment',\n passed: hasRiskAssessment,\n message: hasRiskAssessment\n ? 'Risk assessment found'\n : 'Missing risk assessment with mitigation strategies'\n });\n\n if (!hasRiskAssessment) {\n blockers.push('Add risk assessment with identified risks and mitigation strategies');\n }\n\n // Calculate score\n const passedChecks = checks.filter(c => c.passed).length;\n const score = Math.round((passedChecks / checks.length) * 100);\n\n // Determine if passed (need at least 80% and no blockers)\n const passed = score >= 80 && blockers.length === 0;\n\n return {\n passed,\n score,\n checks,\n blockers,\n warnings\n };\n }\n\n /**\n * Validate a feasibility report file\n */\n static validateFile(filePath) {\n if (!fs.existsSync(filePath)) {\n return {\n passed: false,\n score: 0,\n checks: [],\n blockers: [`File not found: ${filePath}`],\n warnings: []\n };\n }\n\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n return this.validateFromMarkdown(content);\n } catch (error) {\n return {\n passed: false,\n score: 0,\n checks: [],\n blockers: [`Failed to read file: ${error.message}`],\n warnings: []\n };\n }\n }\n\n /**\n * Generate a validation report for display\n */\n static generateReport(result) {\n const lines = [];\n\n lines.push('═══════════════════════════════════════════════════════');\n lines.push(' Feasibility Report Validation');\n lines.push('═══════════════════════════════════════════════════════');\n lines.push('');\n\n // Status\n const status = result.passed ? '✅ PASSED' : '❌ FAILED';\n const statusColor = result.passed ? '🟢' : '🔴';\n lines.push(`Status: ${statusColor} ${status} (Score: ${result.score}/100)`);\n lines.push('');\n\n // Checks\n lines.push('Quality Checks:');\n lines.push('───────────────────────────────────────────────────────');\n\n result.checks.forEach(check => {\n const icon = check.passed ? '✅' : '❌';\n lines.push(` ${icon} ${check.name}: ${check.message || 'Failed'}`);\n });\n\n lines.push('');\n\n // Blockers\n if (result.blockers.length > 0) {\n lines.push('🚫 BLOCKERS (must fix before proceeding):');\n lines.push('───────────────────────────────────────────────────────');\n result.blockers.forEach((blocker, i) => {\n lines.push(` ${i + 1}. ${blocker}`);\n });\n lines.push('');\n }\n\n // Warnings\n if (result.warnings.length > 0) {\n lines.push('⚠️ WARNINGS (recommended improvements):');\n lines.push('───────────────────────────────────────────────────────');\n result.warnings.forEach((warning, i) => {\n lines.push(` ${i + 1}. ${warning}`);\n });\n lines.push('');\n }\n\n // Recommendation\n if (result.passed) {\n lines.push('🎉 Report meets quality standards! Ready for Phase 2 (Approval).');\n } else {\n lines.push('📝 Report needs improvements. Address blockers and re-validate.');\n }\n\n lines.push('');\n lines.push('═══════════════════════════════════════════════════════');\n\n return lines.join('\\n');\n }\n}\n\n// ============================================================================\n// Phase 1 Research Executor\n// ============================================================================\n\nclass Phase1ResearchExecutor {\n constructor(projectId) {\n this.projectId = projectId;\n this.projectDir = path.join(PROJECTS_DIR, projectId);\n this.phaseDir = path.join(this.projectDir, 'phase1');\n this.reportPath = path.join(this.phaseDir, 'feasibility-report.md');\n }\n\n /**\n * Ensure project directories exist\n */\n ensureDirectories() {\n if (!fs.existsSync(this.projectDir)) {\n fs.mkdirSync(this.projectDir, { recursive: true });\n }\n if (!fs.existsSync(this.phaseDir)) {\n fs.mkdirSync(this.phaseDir, { recursive: true });\n }\n }\n\n /**\n * Generate report template\n */\n generateReportTemplate(idea, context) {\n const date = new Date().toLocaleDateString();\n const time = new Date().toLocaleTimeString();\n\n return `# Feasibility Analysis Report\n\n**Project**: ${this.projectId}\n**Date**: ${date} ${time}\n**Phase**: 1 - Research\n**Status**: 🚧 In Progress\n\n---\n\n## Executive Summary\n\n> Brief overview of the project and feasibility assessment\n\n---\n\n## Requirements Summary\n\n### Problem Statement\n[What problem are we solving?]\n\n### Target Users\n[Who will use this? What are their pain points?]\n\n### Key Features\n1. [Feature 1]\n2. [Feature 2]\n3. [Feature 3]\n\n### Constraints\n- [Constraint 1]\n- [Constraint 2]\n\n### Assumptions\n- [Assumption 1]\n- [Assumption 2]\n\n---\n\n## Original Idea\n\n${idea}\n\n${context ? `### Additional Context\\n${context}` : ''}\n\n---\n\n## Correlation Analysis (Connect The Dots)\n\n### Related Projects\n| Project | Similarity | Reusable Components |\n|---------|------------|---------------------|\n| [Project A] | 85% | [Component list] |\n| [Project B] | 60% | [Component list] |\n\n### Overlapping Technology\n- [Tech stack overlap]\n- [Shared libraries]\n- [Common patterns]\n\n### Lessons from History\n- [Lesson 1]: [Context and outcome]\n- [Lesson 2]: [Context and outcome]\n\n---\n\n## Industry Best Practices\n\n### Architecture\n**Practice**: [Specific practice]\n**Rationale**: [Why this is recommended]\n**Sources**: [Citations]\n\n### Security\n**Practice**: [Security best practice]\n**Rationale**: [Why this matters]\n\n---\n\n## Feasibility Assessment\n\n### Technical Feasibility: ⭐⭐⭐☆☆ (3/5)\n\n**Strengths**:\n- [Strength 1]\n- [Strength 2]\n\n**Challenges**:\n- [Challenge 1]: [Mitigation strategy]\n\n### Time Estimate: X hours\n\n**Breakdown**:\n- Research & Planning: Xh\n- Design: Xh\n- Implementation: Xh\n- Testing: Xh\n\n**Total**: X hours\n\n### Complexity: Medium\n\n**Reasoning**: [Explain complexity assessment]\n\n### Risk Assessment\n\n| Risk | Severity | Probability | Mitigation |\n|------|----------|-------------|------------|\n| [Risk 1] | High | Medium | [Mitigation strategy] |\n| [Risk 2] | Medium | Low | [Mitigation strategy] |\n\n---\n\n## Recommendations\n\n### Recommended Tech Stack\n\n**Frontend**:\n- [Choice 1] - [Rationale]\n\n**Backend**:\n- [Choice 1] - [Rationale]\n\n### Suggested Architecture\n\n[High-level architecture description]\n\n### Potential Issues to Watch\n\n1. **[Issue 1]**: [Monitoring approach]\n2. **[Issue 2]**: [Monitoring approach]\n\n### Next Steps (Phase 2: Approval)\n\n1. [ ] Review this report and ensure all sections are complete\n2. [ ] Run quality gate: \\`smc workflow validate ${this.reportPath}\\`\n3. [ ] Address any blockers identified\n4. [ ] Proceed to Phase 2 for Claude review and consensus\n\n---\n\n## Quality Checklist\n\n- [x] Requirement summary is clear and complete\n- [ ] Correlation analysis found related work/patterns\n- [ ] Best practices are cited with sources\n- [ ] Feasibility has concrete ratings (not vague)\n- [ ] Time estimate is justified\n- [ ] Risks have mitigation strategies\n- [ ] Recommendations are actionable\n\n---\n\n## Metadata\n\n- **Generated**: ${date} ${time}\n- **Confidence Level**: [To be filled by AI]\n\n---\n\n*This report was generated by the Phase 1 Research Executor.*\n`;\n }\n\n /**\n * Execute Phase 1 research workflow\n */\n async execute(idea, context = '', progressCallback) {\n await progressCallback?.('Initializing Phase 1 research...', 0, 5);\n\n // Ensure directories exist\n this.ensureDirectories();\n\n // Step 1: Create directories\n await progressCallback?.('Setting up project structure...', 1, 5);\n\n // Step 2: Generate report template\n await progressCallback?.('Generating feasibility report template...', 2, 5);\n\n const reportTemplate = this.generateReportTemplate(idea, context);\n await fs.promises.writeFile(this.reportPath, reportTemplate, 'utf-8');\n\n await progressCallback?.('Phase 1 research context prepared. Ready for AI analysis.', 3, 5);\n\n return {\n projectId: this.projectId,\n reportPath: this.reportPath,\n nextSteps: [\n 'Complete the feasibility report',\n 'Validate with: smc workflow validate',\n 'Proceed to Phase 2'\n ]\n };\n }\n\n /**\n * Check if report exists\n */\n reportExists() {\n return fs.existsSync(this.reportPath);\n }\n\n /**\n * Read the existing report\n */\n readReport() {\n if (!this.reportExists()) return null;\n return fs.readFileSync(this.reportPath, 'utf-8');\n }\n\n /**\n * Validate the report\n */\n validateReport() {\n return FeasibilityValidator.validateFile(this.reportPath);\n }\n\n /**\n * Get the report path\n */\n getReportPath() {\n return this.reportPath;\n }\n}\n\n// ============================================================================\n// Project Management Helpers\n// ============================================================================\n\nfunction generateProjectId() {\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).substr(2, 5);\n return `proj_${timestamp}_${random}`;\n}\n\nasync function createProject(idea, context = '') {\n const projectId = generateProjectId();\n const executor = new Phase1ResearchExecutor(projectId);\n\n await executor.execute(idea, context, (msg, current, total) => {\n const progress = Math.round((current / total) * 100);\n console.log(`[${progress}%] ${msg}`);\n });\n\n return projectId;\n}\n\nfunction listProjects() {\n if (!fs.existsSync(PROJECTS_DIR)) {\n return [];\n }\n\n const projects = [];\n const entries = fs.readdirSync(PROJECTS_DIR, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isDirectory() && entry.name.startsWith('proj_')) {\n const projectPath = path.join(PROJECTS_DIR, entry.name);\n const reportPath = path.join(projectPath, 'phase1', 'feasibility-report.md');\n projects.push({\n id: entry.name,\n path: projectPath,\n hasReport: fs.existsSync(reportPath)\n });\n }\n }\n\n return projects.sort((a, b) => b.id.localeCompare(a.id));\n}\n\nmodule.exports = {\n Phase1ResearchExecutor,\n FeasibilityValidator,\n generateProjectId,\n createProject,\n listProjects\n};\n",
|
|
192
|
+
"snippet": null,
|
|
193
|
+
"checksum": "597627d627cec711a32c5254209dff7c",
|
|
194
|
+
"lastModified": 1768578037268.3965,
|
|
195
|
+
"indexedAt": 1768581173602,
|
|
196
|
+
"id": "src_1768581173602_jyjku7v1d",
|
|
197
|
+
"addedAt": 1768581173602,
|
|
198
|
+
"lastAccessed": 1768581173602
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"type": "local_file",
|
|
202
|
+
"path": "/Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/phases/phase1-research.ts",
|
|
203
|
+
"title": "phase1-research.ts",
|
|
204
|
+
"description": "File: /Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/phases/phase1-research.ts",
|
|
205
|
+
"tags": [],
|
|
206
|
+
"size": 11736,
|
|
207
|
+
"contentType": "text/typescript",
|
|
208
|
+
"scannable": true,
|
|
209
|
+
"wordCount": 1457,
|
|
210
|
+
"lineCount": 466,
|
|
211
|
+
"headings": [
|
|
212
|
+
{
|
|
213
|
+
"type": "function",
|
|
214
|
+
"name": "generateProjectId",
|
|
215
|
+
"line": 418
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"type": "function",
|
|
219
|
+
"name": "createProject",
|
|
220
|
+
"line": 424
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"type": "function",
|
|
224
|
+
"name": "listProjects",
|
|
225
|
+
"line": 436
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
"links": [
|
|
229
|
+
{
|
|
230
|
+
"type": "import",
|
|
231
|
+
"name": "fs",
|
|
232
|
+
"line": 13
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"type": "import",
|
|
236
|
+
"name": "path",
|
|
237
|
+
"line": 14
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"type": "import",
|
|
241
|
+
"name": "../knowledge-engine",
|
|
242
|
+
"line": 15
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"type": "import",
|
|
246
|
+
"name": "../validators/feasibility",
|
|
247
|
+
"line": 16
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"type": "import",
|
|
251
|
+
"name": "../types",
|
|
252
|
+
"line": 17
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
"codeBlocks": [],
|
|
256
|
+
"content": "/**\n * Phase 1: Research - NotebookLM Feasibility Analysis\n *\n * This phase uses NotebookLM (via notebooklm-mcp integration) to:\n * 1. Structure requirements\n * 2. Connect dots with related work\n * 3. Research best practices\n * 4. Assess feasibility\n *\n * Output: feasibility-report.md\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getKnowledgeEngine } from '../knowledge-engine';\nimport { FeasibilityValidator } from '../validators/feasibility';\nimport type { FeasibilityReport, ProjectPhase, ProgressCallback } from '../types';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst PROJECTS_DIR = path.join(process.cwd(), 'development/projects');\n\n// ============================================================================\n// Phase 1 Executor\n// ============================================================================\n\nexport class Phase1ResearchExecutor {\n private projectId: string;\n private projectDir: string;\n private phaseDir: string;\n private reportPath: string;\n\n constructor(projectId: string) {\n this.projectId = projectId;\n this.projectDir = path.join(PROJECTS_DIR, projectId);\n this.phaseDir = path.join(this.projectDir, 'phase1');\n this.reportPath = path.join(this.phaseDir, 'feasibility-report.md');\n }\n\n /**\n * Execute Phase 1 research workflow\n */\n async execute(\n idea: string,\n context: string = '',\n progressCallback?: ProgressCallback\n ): Promise<FeasibilityReport> {\n await progressCallback?.('Initializing Phase 1 research...', 0, 5);\n\n // Ensure directories exist\n await this.ensureDirectories();\n\n // Step 1: Gather knowledge\n await progressCallback?.('Gathering relevant knowledge...', 1, 5);\n const knowledgeContext = await this.gatherKnowledge(idea);\n\n // Step 2: Generate research prompt\n await progressCallback?.('Preparing research prompt...', 2, 5);\n const prompt = this.generatePrompt(idea, context, knowledgeContext);\n\n // Step 3: Save prompt for Claude/AI to execute\n await progressCallback?.('Generating feasibility report...', 3, 5);\n await this.savePrompt(prompt);\n\n // Step 4: Return structured result for AI execution\n await progressCallback?.('Preparing AI execution context...', 4, 5);\n\n const reportTemplate = this.generateReportTemplate(idea, context, knowledgeContext);\n await fs.promises.writeFile(this.reportPath, reportTemplate, 'utf-8');\n\n await progressCallback?.('Phase 1 research context prepared. Ready for AI analysis.', 5, 5);\n\n return {\n projectId: this.projectId,\n createdAt: Date.now(),\n requirements: {\n summary: idea,\n keyFeatures: [],\n constraints: [],\n assumptions: []\n },\n correlations: {\n relatedProjects: [],\n overlappingTech: [],\n lessonsLearned: []\n },\n bestPractices: [],\n feasibility: {\n technical: 0,\n time: 0,\n complexity: 'medium',\n risks: []\n },\n recommendations: {\n techStack: [],\n architecture: '',\n potentialIssues: [],\n nextSteps: [\n 'Review and complete the feasibility report',\n 'Validate with quality gate: smc workflow validate',\n 'Proceed to Phase 2: Claude approval'\n ]\n },\n quality: {\n completeness: 0,\n confidence: 0,\n sourcesCount: knowledgeContext.sourceCount,\n webSearchPerformed: false\n }\n };\n }\n\n /**\n * Gather relevant knowledge from knowledge base\n */\n private async gatherKnowledge(idea: string): Promise<{\n sources: Array<{ title: string; type: string; relevance: number }>;\n excerpts: string[];\n sourceCount: number;\n }> {\n const engine = getKnowledgeEngine();\n const result = await engine.query(idea, { includeWeb: true });\n\n return {\n sources: result.sources.map(s => ({\n title: s.title,\n type: s.type,\n relevance: s.relevance\n })),\n excerpts: result.sources.map(s => s.excerpt || '').filter(Boolean),\n sourceCount: result.sources.length\n };\n }\n\n /**\n * Generate research prompt for AI execution\n */\n private generatePrompt(idea: string, context: string, knowledgeContext: any): string {\n return `\n# Phase 1 Research Task\n\n## User Idea\n${idea}\n\n${context ? `## Additional Context\\n${context}\\n` : ''}\n\n## Knowledge Context\nFound ${knowledgeContext.sourceCount} relevant sources in the knowledge base.\n\n${knowledgeContext.sources.length > 0 ? `\n### Relevant Sources\n${knowledgeContext.sources.map((s: any) => `- ${s.title} (${(s.relevance * 100).toFixed(0)}% relevance)`).join('\\n')}\n` : ''}\n\n## Your Task\n\nPlease complete a comprehensive feasibility analysis following the template in:\n${path.join(__dirname, '../templates/research.md')}\n\n## Output\n\nSave the complete feasibility report to:\n${this.reportPath}\n\nThen validate with: smc workflow validate ${this.reportPath}\n`.trim();\n }\n\n /**\n * Generate report template for AI to fill\n */\n private generateReportTemplate(idea: string, context: string, knowledgeContext: any): string {\n const date = new Date().toLocaleDateString();\n const time = new Date().toLocaleTimeString();\n\n return `# Feasibility Analysis Report\n\n**Project**: ${this.projectId}\n**Date**: ${date} ${time}\n**Phase**: 1 - Research\n**Status**: 🚧 In Progress\n\n---\n\n## Executive Summary\n\n> Brief overview of the project and feasibility assessment\n\n---\n\n## Requirements Summary\n\n### Problem Statement\n[What problem are we solving?]\n\n### Target Users\n[Who will use this? What are their pain points?]\n\n### Key Features\n1. [Feature 1]\n2. [Feature 2]\n3. [Feature 3]\n\n### Constraints\n- [Constraint 1]\n- [Constraint 2]\n\n### Assumptions\n- [Assumption 1]\n- [Assumption 2]\n\n---\n\n## Original Idea\n\n${idea}\n\n${context ? `### Additional Context\\n${context}` : ''}\n\n---\n\n## Correlation Analysis (Connect The Dots)\n\n### Related Projects\n| Project | Similarity | Reusable Components |\n|---------|------------|---------------------|\n| [Project A] | 85% | [Component list] |\n| [Project B] | 60% | [Component list] |\n\n### Overlapping Technology\n- [Tech stack overlap]\n- [Shared libraries]\n- [Common patterns]\n\n### Lessons from History\n- [Lesson 1]: [Context and outcome]\n- [Lesson 2]: [Context and outcome]\n\n### Risks Based on History\n- [Risk from past project]: [How we'll address it]\n\n---\n\n## Industry Best Practices\n\n### Architecture\n**Practice**: [Specific practice]\n**Rationale**: [Why this is recommended]\n**Sources**: [Citations]\n**Applicability**: [How this applies to current project]\n\n### Security\n**Practice**: [Security best practice]\n**Rationale**: [Why this matters]\n**Sources**: [Citations]\n\n### Performance\n**Practice**: [Performance approach]\n**Rationale**: [Performance considerations]\n\n### UX/UI\n**Practice**: [UX principle]\n**Rationale**: [User experience impact]\n\n---\n\n## Feasibility Assessment\n\n### Technical Feasibility: ⭐⭐⭐☆☆ (3/5)\n\n**Strengths**:\n- [Strength 1]\n- [Strength 2]\n\n**Challenges**:\n- [Challenge 1]: [Mitigation strategy]\n- [Challenge 2]: [Mitigation strategy]\n\n### Time Estimate: X hours\n\n**Breakdown**:\n- Research & Planning: Xh\n- Design: Xh\n- Implementation: Xh\n- Testing: Xh\n- Documentation: Xh\n\n**Total**: X hours\n\n### Complexity: Medium\n\n**Reasoning**: [Explain complexity assessment]\n\n### Risk Assessment\n\n| Risk | Severity | Probability | Mitigation |\n|------|----------|-------------|------------|\n| [Risk 1] | High | Medium | [Mitigation strategy] |\n| [Risk 2] | Medium | Low | [Mitigation strategy] |\n| [Risk 3] | Low | Low | [Mitigation strategy] |\n\n---\n\n## Recommendations\n\n### Recommended Tech Stack\n\n**Frontend**:\n- [Choice 1] - [Rationale]\n- [Choice 2] - [Rationale]\n\n**Backend**:\n- [Choice 1] - [Rationale]\n- [Choice 2] - [Rationale]\n\n**Database**:\n- [Choice] - [Rationale]\n\n**Other**:\n- [Choice] - [Rationale]\n\n### Suggested Architecture\n\n[High-level architecture description - could include diagrams]\n\n### Potential Issues to Watch\n\n1. **[Issue 1]**: [Monitoring approach, early warning signs]\n2. **[Issue 2]**: [Monitoring approach, early warning signs]\n3. **[Issue 3]**: [Monitoring approach, early warning signs]\n\n### Next Steps (Phase 2: Approval)\n\n1. [ ] Review this report and ensure all sections are complete\n2. [ ] Run quality gate: \\`smc workflow validate ${this.reportPath}\\`\n3. [ ] Address any blockers identified\n4. [ ] Proceed to Phase 2 for Claude review and consensus\n\n---\n\n## Quality Checklist\n\n- [x] Requirement summary is clear and complete\n- [ ] Correlation analysis found related work/patterns\n- [ ] Best practices are cited with sources\n- [ ] Feasibility has concrete ratings (not vague)\n- [ ] Time estimate is justified\n- [ ] Risks have mitigation strategies\n- [ ] Recommendations are actionable\n\n---\n\n## Metadata\n\n- **Generated**: ${date} ${time}\n- **Knowledge Sources Queried**: ${knowledgeContext.sourceCount}\n- **Web Search Performed**: No\n- **Confidence Level**: [To be filled by AI]\n\n---\n\n*This report was generated by the Phase 1 Research Executor. Please complete all sections before proceeding to Phase 2.*\n`;\n }\n\n /**\n * Save prompt for AI execution\n */\n private async savePrompt(prompt: string): Promise<void> {\n const promptPath = path.join(this.phaseDir, 'research-prompt.md');\n await fs.promises.writeFile(promptPath, prompt, 'utf-8');\n }\n\n /**\n * Ensure project directories exist\n */\n private async ensureDirectories(): Promise<void> {\n await fs.promises.mkdir(this.projectDir, { recursive: true });\n await fs.promises.mkdir(this.phaseDir, { recursive: true });\n }\n\n /**\n * Get the report path\n */\n getReportPath(): string {\n return this.reportPath;\n }\n\n /**\n * Check if report exists\n */\n reportExists(): boolean {\n return fs.existsSync(this.reportPath);\n }\n\n /**\n * Read the existing report\n */\n readReport(): string | null {\n if (!this.reportExists()) return null;\n return fs.readFileSync(this.reportPath, 'utf-8');\n }\n\n /**\n * Validate the report\n */\n validateReport() {\n return FeasibilityValidator.validateFile(this.reportPath);\n }\n}\n\n// ============================================================================\n// Project Management Helpers\n// ============================================================================\n\nexport function generateProjectId(): string {\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).substr(2, 5);\n return `proj_${timestamp}_${random}`;\n}\n\nexport async function createProject(idea: string, context: string = ''): Promise<string> {\n const projectId = generateProjectId();\n const executor = new Phase1ResearchExecutor(projectId);\n\n await executor.execute(idea, context, (msg, current, total) => {\n const progress = Math.round((current / total) * 100);\n console.log(`[${progress}%] ${msg}`);\n });\n\n return projectId;\n}\n\nexport function listProjects(): Array<{\n id: string;\n path: string;\n hasReport: boolean;\n}> {\n if (!fs.existsSync(PROJECTS_DIR)) {\n return [];\n }\n\n const projects: Array<{\n id: string;\n path: string;\n hasReport: boolean;\n }> = [];\n\n const entries = fs.readdirSync(PROJECTS_DIR, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && entry.name.startsWith('proj_')) {\n const projectPath = path.join(PROJECTS_DIR, entry.name);\n const reportPath = path.join(projectPath, 'phase1', 'feasibility-report.md');\n projects.push({\n id: entry.name,\n path: projectPath,\n hasReport: fs.existsSync(reportPath)\n });\n }\n }\n\n return projects.sort((a, b) => b.id.localeCompare(a.id));\n}\n",
|
|
257
|
+
"snippet": null,
|
|
258
|
+
"checksum": "7767db89bb3e26a855ee28c3ffeeff3d",
|
|
259
|
+
"lastModified": 1768577931701.0537,
|
|
260
|
+
"indexedAt": 1768581173602,
|
|
261
|
+
"id": "src_1768581173602_6zw45e9mh",
|
|
262
|
+
"addedAt": 1768581173602,
|
|
263
|
+
"lastAccessed": 1768581173602
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"type": "local_file",
|
|
267
|
+
"path": "/Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/templates/research.md",
|
|
268
|
+
"title": "research.md",
|
|
269
|
+
"description": "File: /Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/templates/research.md",
|
|
270
|
+
"tags": [],
|
|
271
|
+
"size": 4845,
|
|
272
|
+
"contentType": "text/markdown",
|
|
273
|
+
"scannable": true,
|
|
274
|
+
"wordCount": 695,
|
|
275
|
+
"lineCount": 223,
|
|
276
|
+
"headings": [
|
|
277
|
+
{
|
|
278
|
+
"level": 1,
|
|
279
|
+
"text": "Phase 1 Research Prompt Template",
|
|
280
|
+
"line": 1
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"level": 2,
|
|
284
|
+
"text": "System Context",
|
|
285
|
+
"line": 8
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"level": 2,
|
|
289
|
+
"text": "Input Format",
|
|
290
|
+
"line": 21
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"level": 2,
|
|
294
|
+
"text": "Research Process",
|
|
295
|
+
"line": 32
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"level": 3,
|
|
299
|
+
"text": "Step 1: Requirement Structuring (30 minutes)",
|
|
300
|
+
"line": 34
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"level": 3,
|
|
304
|
+
"text": "Step 2: Correlation Analysis (Connect The Dots) (45 minutes)",
|
|
305
|
+
"line": 69
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
"level": 3,
|
|
309
|
+
"text": "Step 3: Best Practices Research (45 minutes)",
|
|
310
|
+
"line": 102
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"level": 3,
|
|
314
|
+
"text": "Step 4: Feasibility Assessment (30 minutes)",
|
|
315
|
+
"line": 126
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"level": 3,
|
|
319
|
+
"text": "Step 5: Recommendations (15 minutes)",
|
|
320
|
+
"line": 161
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"level": 2,
|
|
324
|
+
"text": "Quality Checklist",
|
|
325
|
+
"line": 187
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"level": 2,
|
|
329
|
+
"text": "Output Format",
|
|
330
|
+
"line": 201
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"level": 2,
|
|
334
|
+
"text": "Example Output",
|
|
335
|
+
"line": 210
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"level": 2,
|
|
339
|
+
"text": "Notes for AI",
|
|
340
|
+
"line": 216
|
|
341
|
+
}
|
|
342
|
+
],
|
|
343
|
+
"links": [],
|
|
344
|
+
"codeBlocks": [
|
|
345
|
+
{
|
|
346
|
+
"language": "text",
|
|
347
|
+
"lineStart": 23,
|
|
348
|
+
"preview": "USER IDEA: {{userIdea}}\n\nCONTEXT:"
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"language": "markdown",
|
|
352
|
+
"lineStart": 44,
|
|
353
|
+
"preview": "## Requirements Summary\n\n### Problem Statement"
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
"language": "markdown",
|
|
357
|
+
"lineStart": 78,
|
|
358
|
+
"preview": "## Correlation Analysis\n\n### Related Projects"
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
"language": "markdown",
|
|
362
|
+
"lineStart": 111,
|
|
363
|
+
"preview": "## Industry Best Practices\n\n### [Area: e.g., Frontend Architecture]"
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"language": "markdown",
|
|
367
|
+
"lineStart": 130,
|
|
368
|
+
"preview": "## Feasibility Assessment\n\n### Technical Feasibility: ⭐⭐⭐⭐☆ (4/5)"
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
"language": "markdown",
|
|
372
|
+
"lineStart": 163,
|
|
373
|
+
"preview": "## Recommendations\n\n### Recommended Tech Stack"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"language": "text",
|
|
377
|
+
"lineStart": 204,
|
|
378
|
+
"preview": "development/projects/{projectId}/phase1/feasibility-report.md"
|
|
379
|
+
}
|
|
380
|
+
],
|
|
381
|
+
"content": "# Phase 1 Research Prompt Template\n\n> **Role**: NotebookLM Knowledge Engine\n> **Goal**: Transform user idea into comprehensive feasibility analysis\n\n---\n\n## System Context\n\nYou are the **Phase 1 Research Engine** for a 5-phase AI-assisted development workflow. Your role is to:\n\n1. **Understand** the user's idea/requirement deeply\n2. **Connect dots** by finding related work, patterns, and decisions\n3. **Research** industry best practices using both local knowledge and web search\n4. **Assess** feasibility with concrete technical analysis\n\n**Key Principle**: You provide **zero-hallucination** analysis. If you don't know something from the knowledge base or web search, explicitly state it.\n\n---\n\n## Input Format\n\n```\nUSER IDEA: {{userIdea}}\n\nCONTEXT:\n{{additionalContext}}\n```\n\n---\n\n## Research Process\n\n### Step 1: Requirement Structuring (30 minutes)\n\nExtract and clarify:\n- **Core problem** being solved\n- **Target users** and their pain points\n- **Key features** requested\n- **Constraints** (technical, time, resources)\n- **Assumptions** made\n\nOutput format:\n```markdown\n## Requirements Summary\n\n### Problem Statement\n[What problem are we solving?]\n\n### Target Users\n[Who will use this? What are their pain points?]\n\n### Key Features\n1. [Feature 1]\n2. [Feature 2]\n...\n\n### Constraints\n- [Constraint 1]\n- [Constraint 2]\n\n### Assumptions\n- [Assumption 1]\n- [Assumption 2]\n```\n\n---\n\n### Step 2: Correlation Analysis (Connect The Dots) (45 minutes)\n\nSearch the knowledge base for:\n- **Related projects** with similar features\n- **Reusable components** and patterns\n- **Historical decisions** and their outcomes\n- **Lessons learned** from past projects\n\nOutput format:\n```markdown\n## Correlation Analysis\n\n### Related Projects\n| Project | Similarity | Reusable Components |\n|---------|------------|---------------------|\n| [Project A] | 85% | [Component list] |\n| [Project B] | 60% | [Component list] |\n\n### Overlapping Technology\n- [Tech stack overlap]\n- [Shared libraries]\n- [Common patterns]\n\n### Lessons from History\n- [Lesson 1]: [Context]\n- [Lesson 2]: [Context]\n\n### Risks Based on History\n- [Risk from past project]: [How we'll address it]\n```\n\n---\n\n### Step 3: Best Practices Research (45 minutes)\n\nFor each key area (architecture, tech stack, security, UX, etc.):\n\n1. **Query local knowledge base** first\n2. **Search web** for latest practices if needed\n3. **Synthesize** recommendations\n\nOutput format:\n```markdown\n## Industry Best Practices\n\n### [Area: e.g., Frontend Architecture]\n**Practice**: [Specific practice]\n**Rationale**: [Why this is recommended]\n**Sources**: [Citations]\n**Applicability**: [How this applies to current project]\n\n### [Area: e.g., API Design]\n...\n```\n\n---\n\n### Step 4: Feasibility Assessment (30 minutes)\n\nAssess across multiple dimensions:\n\n```markdown\n## Feasibility Assessment\n\n### Technical Feasibility: ⭐⭐⭐⭐☆ (4/5)\n**Strengths**:\n- [Strength 1]\n- [Strength 2]\n\n**Challenges**:\n- [Challenge 1]: [Mitigation strategy]\n- [Challenge 2]: [Mitigation strategy]\n\n### Time Estimate: 4 hours\n**Breakdown**:\n- Research & Planning: 30m\n- Design: 1h\n- Implementation: 2h\n- Testing: 30m\n\n### Complexity: Medium\n**Reasoning**: [Explain complexity assessment]\n\n### Risk Assessment\n| Risk | Severity | Mitigation |\n|------|----------|------------|\n| [Risk 1] | High | [Mitigation] |\n| [Risk 2] | Medium | [Mitigation] |\n```\n\n---\n\n### Step 5: Recommendations (15 minutes)\n\n```markdown\n## Recommendations\n\n### Recommended Tech Stack\n- [Frontend]: [Choice + rationale]\n- [Backend]: [Choice + rationale]\n- [Database]: [Choice + rationale]\n- [Other]: [Choice + rationale]\n\n### Suggested Architecture\n[High-level architecture description]\n\n### Potential Issues to Watch\n1. [Issue 1]: [Monitoring approach]\n2. [Issue 2]: [Monitoring approach]\n\n### Next Steps (Phase 2)\n1. [Step 1]\n2. [Step 2]\n3. [Step 3]\n```\n\n---\n\n## Quality Checklist\n\nBefore finalizing, ensure:\n\n- [ ] Requirement summary is clear and complete\n- [ ] Correlation analysis found related work/patterns\n- [ ] Best practices are cited with sources\n- [ ] Feasibility has concrete ratings (not vague)\n- [ ] Time estimate is justified\n- [ ] Risks have mitigation strategies\n- [ ] Recommendations are actionable\n\n---\n\n## Output Format\n\nFinal output should be saved as `feasibility-report.md` in:\n```\ndevelopment/projects/{projectId}/phase1/feasibility-report.md\n```\n\n---\n\n## Example Output\n\nSee `development/projects/examples/phase1/feasibility-report.md` for a complete example.\n\n---\n\n## Notes for AI\n\n- **Be specific**: Use concrete examples, not abstract advice\n- **Cite sources**: Always reference where information comes from\n- **Quantify**: Use numbers when possible (similarity scores, time estimates)\n- **Be honest**: If knowledge is missing, state it explicitly\n- **Think ahead**: Consider Phase 2 (approval) - what will Claude need to know?\n",
|
|
382
|
+
"snippet": null,
|
|
383
|
+
"checksum": "835b7ade8fe7e38c19e92b143c30bdca",
|
|
384
|
+
"lastModified": 1768577830923.451,
|
|
385
|
+
"indexedAt": 1768581173603,
|
|
386
|
+
"id": "src_1768581173603_62iuqnfvk",
|
|
387
|
+
"addedAt": 1768581173603,
|
|
388
|
+
"lastAccessed": 1768581173603
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
"type": "local_file",
|
|
392
|
+
"path": "/Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/types.js",
|
|
393
|
+
"title": "types.js",
|
|
394
|
+
"description": "File: /Users/sumulige/Documents/Antigravity/sumulige-claude/.claude/workflow/types.js",
|
|
395
|
+
"tags": [],
|
|
396
|
+
"size": 926,
|
|
397
|
+
"contentType": "text/javascript",
|
|
398
|
+
"scannable": true,
|
|
399
|
+
"wordCount": 101,
|
|
400
|
+
"lineCount": 38,
|
|
401
|
+
"headings": [],
|
|
402
|
+
"links": [],
|
|
403
|
+
"codeBlocks": [],
|
|
404
|
+
"content": "/**\n * Workflow Type Definitions (JavaScript version)\n * Shared types for the NotebookLM + Claude collaboration workflow\n */\n\n// Project phase enum\nconst ProjectPhase = {\n RESEARCH: 'research', // Phase 1: NotebookLM feasibility analysis\n APPROVAL: 'approval', // Phase 2: Claude review and consensus\n PLANNING: 'planning', // Phase 3: PRD and prototype\n DEVELOPMENT: 'development',// Phase 4: Implementation\n DEPLOYMENT: 'deployment' // Phase 5: Release\n};\n\n// Project status\nconst ProjectStatus = {\n PENDING: 'pending',\n IN_PROGRESS: 'in_progress',\n COMPLETED: 'completed',\n FAILED: 'failed',\n ON_HOLD: 'on_hold'\n};\n\n// Knowledge source types\nconst KnowledgeSourceType = {\n LOCAL_FILE: 'local_file',\n LOCAL_DIRECTORY: 'local_directory',\n NOTEBOOKLM: 'notebooklm',\n WEB_SEARCH: 'web_search',\n WEB_URL: 'web_url'\n};\n\nmodule.exports = {\n ProjectPhase,\n ProjectStatus,\n KnowledgeSourceType\n};\n",
|
|
405
|
+
"snippet": null,
|
|
406
|
+
"checksum": "4003db50e3d402a1fcfa786c4b621f6c",
|
|
407
|
+
"lastModified": 1768577997157.5554,
|
|
408
|
+
"indexedAt": 1768581173602,
|
|
409
|
+
"id": "src_1768581173602_fdw5unloh",
|
|
410
|
+
"addedAt": 1768581173602,
|
|
411
|
+
"lastAccessed": 1768581173602
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"type": "local_file",
|
|
415
|
+
"path": "/Users/sumulige/Documents/Antigravity/sumulige-claude/development/knowledge-base/test-best-practices.md",
|
|
416
|
+
"title": "test-best-practices.md",
|
|
417
|
+
"description": "Local file: /Users/sumulige/Documents/Antigravity/sumulige-claude/development/knowledge-base/test-best-practices.md",
|
|
418
|
+
"tags": [
|
|
419
|
+
"api",
|
|
420
|
+
"architecture"
|
|
421
|
+
],
|
|
422
|
+
"size": 524,
|
|
423
|
+
"contentType": "text/markdown",
|
|
424
|
+
"scannable": true,
|
|
425
|
+
"wordCount": 88,
|
|
426
|
+
"lineCount": 30,
|
|
427
|
+
"headings": [
|
|
428
|
+
{
|
|
429
|
+
"level": 1,
|
|
430
|
+
"text": "Best Practices for API Design",
|
|
431
|
+
"line": 1
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
"level": 2,
|
|
435
|
+
"text": "Introduction",
|
|
436
|
+
"line": 3
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
"level": 2,
|
|
440
|
+
"text": "Core Principles",
|
|
441
|
+
"line": 6
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
"level": 2,
|
|
445
|
+
"text": "RESTful Guidelines",
|
|
446
|
+
"line": 12
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"level": 2,
|
|
450
|
+
"text": "Authentication",
|
|
451
|
+
"line": 18
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
"level": 2,
|
|
455
|
+
"text": "Security Best Practices",
|
|
456
|
+
"line": 24
|
|
457
|
+
}
|
|
458
|
+
],
|
|
459
|
+
"links": [],
|
|
460
|
+
"codeBlocks": [],
|
|
461
|
+
"content": "# Best Practices for API Design\n\n## Introduction\nThis document covers API design best practices for building scalable web services.\n\n## Core Principles\n\n1. **Consistency**: Use consistent naming conventions\n2. **Versioning**: Always version your APIs\n3. **Error Handling**: Return meaningful error messages\n\n## RESTful Guidelines\n\n- Use HTTP verbs correctly (GET, POST, PUT, DELETE)\n- Design resource-oriented URLs\n- Implement proper status codes\n\n## Authentication\n\nConsider using JWT tokens for stateless authentication.\n\n\n\n## Security Best Practices\n\n- Always use HTTPS\n- Implement rate limiting\n- Validate input data\n\n",
|
|
462
|
+
"snippet": null,
|
|
463
|
+
"checksum": "f9469c9710b88c3b1fd2ea3c63da3d58",
|
|
464
|
+
"lastModified": 1768581166516.8215,
|
|
465
|
+
"indexedAt": 1768581168158,
|
|
466
|
+
"id": "src_1768581159415_npe9vbxvj",
|
|
467
|
+
"addedAt": 1768581159415,
|
|
468
|
+
"lastAccessed": 1768581159415
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
"type": "local_file",
|
|
472
|
+
"path": "/Users/sumulige/Documents/Antigravity/sumulige-claude/package.json",
|
|
473
|
+
"title": "package.json",
|
|
474
|
+
"description": "Local file: /Users/sumulige/Documents/Antigravity/sumulige-claude/package.json",
|
|
475
|
+
"tags": [
|
|
476
|
+
"typescript"
|
|
477
|
+
],
|
|
478
|
+
"size": 1339,
|
|
479
|
+
"contentType": "application/json",
|
|
480
|
+
"id": "src_1768578057753_vsf5w6ui1",
|
|
481
|
+
"addedAt": 1768578057753,
|
|
482
|
+
"lastAccessed": 1768578057753
|
|
483
|
+
}
|
|
484
|
+
],
|
|
485
|
+
"lastUpdated": 1768588106330
|
|
486
|
+
}
|