sumulige-claude 1.5.1 โ 1.5.2
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/hook-registry.json +0 -15
- package/.claude/rules/coding-style.md +18 -7
- package/.claude/rules/hooks.md +15 -4
- package/.claude/rules/performance.md +15 -5
- package/.claude/rules/security.md +140 -4
- package/.claude/rules/testing.md +138 -9
- package/.claude/rules/web-design-standard.md +16 -5
- package/.claude/skills/algorithmic-art/metadata.yaml +28 -0
- package/.claude/skills/api-tester/SKILL.md +61 -0
- package/.claude/skills/api-tester/examples/basic.md +3 -0
- package/.claude/skills/api-tester/metadata.yaml +30 -0
- package/.claude/skills/api-tester/templates/default.md +3 -0
- package/.claude/skills/brand-guidelines/metadata.yaml +26 -0
- package/.claude/skills/canvas-design/metadata.yaml +27 -0
- package/.claude/skills/code-reviewer-123/SKILL.md +61 -0
- package/.claude/skills/code-reviewer-123/examples/basic.md +3 -0
- package/.claude/skills/code-reviewer-123/metadata.yaml +30 -0
- package/.claude/skills/code-reviewer-123/templates/default.md +3 -0
- package/.claude/skills/doc-coauthoring/metadata.yaml +27 -0
- package/.claude/skills/docx/metadata.yaml +30 -0
- package/.claude/skills/frontend-design/metadata.yaml +28 -0
- package/.claude/skills/internal-comms/metadata.yaml +28 -0
- package/.claude/skills/mcp-builder/metadata.yaml +26 -0
- package/.claude/skills/my-skill/SKILL.md +61 -0
- package/.claude/skills/my-skill/examples/basic.md +3 -0
- package/.claude/skills/my-skill/metadata.yaml +30 -0
- package/.claude/skills/my-skill/templates/default.md +3 -0
- package/.claude/skills/pdf/metadata.yaml +29 -0
- package/.claude/skills/pptx/metadata.yaml +29 -0
- package/.claude/skills/react-best-practices/metadata.yaml +26 -0
- package/.claude/skills/react-node-practices/SKILL.md +409 -0
- package/.claude/skills/react-node-practices/metadata.yaml +56 -0
- package/.claude/skills/skill-creator/metadata.yaml +25 -0
- package/.claude/skills/slack-gif-creator/metadata.yaml +28 -0
- package/.claude/skills/test-skill-name/SKILL.md +61 -0
- package/.claude/skills/test-skill-name/examples/basic.md +3 -0
- package/.claude/skills/test-skill-name/metadata.yaml +30 -0
- package/.claude/skills/test-skill-name/templates/default.md +3 -0
- package/.claude/skills/test-workflow/metadata.yaml +32 -0
- package/.claude/skills/theme-factory/metadata.yaml +26 -0
- package/.claude/skills/threejs-fundamentals/metadata.yaml +27 -0
- package/.claude/skills/web-artifacts-builder/metadata.yaml +30 -0
- package/.claude/skills/web-design-guidelines/metadata.yaml +26 -0
- package/.claude/skills/webapp-testing/metadata.yaml +26 -0
- package/.claude/skills/xlsx/metadata.yaml +29 -0
- package/LICENSE +21 -0
- package/cli.js +1 -1
- package/package.json +25 -3
- package/.claude/.kickoff-hint.txt +0 -52
- package/.claude/.sumulige-claude-version +0 -1
- package/.claude/.version +0 -1
- package/.claude/AGENTS.md +0 -42
- package/.claude/ANCHORS.md +0 -40
- package/.claude/CLAUDE.md +0 -138
- package/.claude/MEMORY.md +0 -69
- package/.claude/PROJECT_LOG.md +0 -101
- package/.claude/THINKING_CHAIN_GUIDE.md +0 -287
- package/.claude/USAGE.md +0 -175
- package/.claude/boris-optimizations.md +0 -167
- package/.claude/handoffs/INDEX.md +0 -21
- package/.claude/handoffs/LATEST.md +0 -76
- package/.claude/handoffs/handoff_2026-01-22T13-07-04-757Z.md +0 -76
- package/.claude/quality-gate.json +0 -82
- package/.claude/rag/skill-index.json +0 -135
- package/.claude/settings.json +0 -99
- package/.claude/settings.local.json +0 -175
- package/.claude/templates/PROJECT_KICKOFF.md +0 -89
- package/.claude/templates/PROJECT_PROPOSAL.md +0 -227
- package/.claude/templates/TASK_PLAN.md +0 -121
- package/.claude/templates/hooks/README.md +0 -302
- package/.claude/templates/hooks/hook.sh.template +0 -94
- package/.claude/templates/hooks/user-prompt-submit.cjs.template +0 -116
- package/.claude/templates/hooks/user-response-submit.cjs.template +0 -94
- package/.claude/templates/hooks/validate.js +0 -173
- package/.claude/templates/tasks/develop.md +0 -69
- package/.claude/templates/tasks/research.md +0 -64
- package/.claude/templates/tasks/test.md +0 -96
- package/.claude/thinking-routes/.last-sync +0 -1
- package/.claude/thinking-routes/QUICKREF.md +0 -98
- package/.claude/workflow/document-scanner.js +0 -426
- package/.claude/workflow/knowledge-engine.js +0 -941
- package/.claude/workflow/notebooklm/browser.js +0 -1028
- package/.claude/workflow/phases/phase1-research.js +0 -578
- package/.claude/workflow/phases/phase1-research.ts +0 -465
- package/.claude/workflow/phases/phase2-approve.js +0 -722
- package/.claude/workflow/phases/phase3-plan.js +0 -1200
- package/.claude/workflow/phases/phase4-develop.js +0 -894
- package/.claude/workflow/search-cache.js +0 -230
- package/.claude/workflow/templates/approval.md +0 -315
- package/.claude/workflow/templates/development.md +0 -377
- package/.claude/workflow/templates/planning.md +0 -328
- package/.claude/workflow/templates/research.md +0 -250
- package/.claude/workflow/types.js +0 -37
- package/.claude/workflow/web-search.js +0 -278
- package/.claude-plugin/marketplace.json +0 -71
- package/.github/workflows/sync-skills.yml +0 -74
- package/.versionrc +0 -25
- package/AGENTS.md +0 -580
- package/CHANGELOG.md +0 -481
- package/CLAUDE-template.md +0 -114
- package/DEV_TOOLS_GUIDE.md +0 -190
- package/PROJECT_STRUCTURE.md +0 -266
- package/Q&A.md +0 -325
- package/config/defaults.json +0 -34
- package/config/official-skills.json +0 -183
- package/config/quality-gate.json +0 -67
- package/config/skill-categories.json +0 -40
- package/config/version-manifest.json +0 -85
- package/demos/power-3d-scatter.html +0 -683
- package/development/cache/web-search/search_1193d605f8eb364651fc2f2041b58a31.json +0 -36
- package/development/cache/web-search/search_3798bf06960edc125f744a1abb5b72c5.json +0 -36
- package/development/cache/web-search/search_37c7d4843a53f0d83f1122a6f908a2a3.json +0 -36
- package/development/cache/web-search/search_44166fa0153709ee168485a22aa0ab40.json +0 -36
- package/development/cache/web-search/search_4deaebb1f77e86a8ca066dc5a49c59fd.json +0 -36
- package/development/cache/web-search/search_94da91789466070a7f545612e73c7372.json +0 -36
- package/development/cache/web-search/search_dd5de8491b8b803a3cb01339cd210fb0.json +0 -36
- package/development/knowledge-base/.index.clean.json +0 -1
- package/development/knowledge-base/.index.json +0 -486
- package/development/knowledge-base/test-best-practices.md +0 -29
- package/development/projects/proj_mkh1pazz_ixmt1/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh4jvnb_z7rwf/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh4jxkd_ewz5a/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh4k84n_ni73k/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh4wfyd_u9w88/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh4wsbo_iahvf/development/projects/proj_mkh4xbpg_4na5w/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh4wsbo_iahvf/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh4xulg_1ka8x/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh4xwhj_gch8j/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh4y2qk_9lm8z/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh4y2qk_9lm8z/phase2/requirements.md +0 -226
- package/development/projects/proj_mkh4y2qk_9lm8z/phase3/PRD.md +0 -345
- package/development/projects/proj_mkh4y2qk_9lm8z/phase3/TASK_PLAN.md +0 -284
- package/development/projects/proj_mkh4y2qk_9lm8z/phase3/prototype/README.md +0 -14
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/DEVELOPMENT_LOG.md +0 -35
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/TASKS.md +0 -34
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/.env.example +0 -5
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/README.md +0 -60
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/package.json +0 -25
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/index.js +0 -70
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/routes/index.js +0 -48
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/health.test.js +0 -20
- package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/jest.config.js +0 -21
- package/development/projects/proj_mkh7veqg_3lypc/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh7veqg_3lypc/phase2/requirements.md +0 -226
- package/development/projects/proj_mkh7veqg_3lypc/phase3/PRD.md +0 -345
- package/development/projects/proj_mkh7veqg_3lypc/phase3/TASK_PLAN.md +0 -284
- package/development/projects/proj_mkh7veqg_3lypc/phase3/prototype/README.md +0 -14
- package/development/projects/proj_mkh8k8fo_rmqn5/phase1/feasibility-report.md +0 -160
- package/development/projects/proj_mkh8xyhy_1vshq/phase1/feasibility-report.md +0 -178
- package/development/projects/proj_mkh8zddd_dhamf/phase1/feasibility-report.md +0 -377
- package/development/projects/proj_mkh8zddd_dhamf/phase2/requirements.md +0 -442
- package/development/projects/proj_mkh8zddd_dhamf/phase3/api-design.md +0 -800
- package/development/projects/proj_mkh8zddd_dhamf/phase3/architecture.md +0 -625
- package/development/projects/proj_mkh8zddd_dhamf/phase3/data-model.md +0 -830
- package/development/projects/proj_mkh8zddd_dhamf/phase3/risks.md +0 -957
- package/development/projects/proj_mkh8zddd_dhamf/phase3/wbs.md +0 -381
- package/development/todos/.state.json +0 -19
- package/development/todos/INDEX.md +0 -63
- package/development/todos/active/_README.md +0 -49
- package/development/todos/archived/_README.md +0 -11
- package/development/todos/backlog/_README.md +0 -11
- package/development/todos/backlog/mcp-integration.md +0 -35
- package/development/todos/completed/_README.md +0 -11
- package/development/todos/completed/boris-optimizations.md +0 -39
- package/development/todos/completed/develop/local-knowledge-index.md +0 -85
- package/development/todos/completed/develop/todo-system.md +0 -47
- package/development/todos/completed/develop/web-search-integration.md +0 -83
- package/development/todos/completed/test/phase1-e2e-test.md +0 -103
- package/docs/DEVELOPMENT.md +0 -461
- package/docs/MARKETPLACE.md +0 -352
- package/docs/RELEASE.md +0 -93
- package/jest.config.js +0 -63
- package/lib/commands.js +0 -3588
- package/lib/config-manager.js +0 -441
- package/lib/config-schema.js +0 -408
- package/lib/config-validator.js +0 -330
- package/lib/config.js +0 -122
- package/lib/errors.js +0 -305
- package/lib/incremental-sync.js +0 -274
- package/lib/marketplace.js +0 -487
- package/lib/migrations.js +0 -154
- package/lib/permission-audit.js +0 -255
- package/lib/quality-gate.js +0 -431
- package/lib/quality-rules.js +0 -373
- package/lib/utils.js +0 -150
- package/lib/version-check.js +0 -169
- package/lib/version-manifest.js +0 -171
- package/project-paradigm.md +0 -313
- package/prompts/how-to-find.md +0 -163
- package/prompts/linus-architect.md +0 -71
- package/prompts/software-architect.md +0 -173
- package/prompts/web-designer.md +0 -249
- package/scripts/fix-hooks.mjs +0 -97
- package/scripts/sync-external.mjs +0 -298
- package/scripts/sync-to-home.sh +0 -108
- package/scripts/update-registry.mjs +0 -325
- package/sources.yaml +0 -83
- package/tests/README.md +0 -263
- package/tests/commands.test.js +0 -1086
- package/tests/config-manager.test.js +0 -677
- package/tests/config-schema.test.js +0 -425
- package/tests/config-validator.test.js +0 -436
- package/tests/config.test.js +0 -100
- package/tests/errors.test.js +0 -477
- package/tests/manual/phase1-e2e.sh +0 -389
- package/tests/manual/phase2-test-cases.md +0 -311
- package/tests/manual/phase3-test-cases.md +0 -309
- package/tests/manual/phase4-test-cases.md +0 -414
- package/tests/manual/test-cases.md +0 -417
- package/tests/marketplace.test.js +0 -420
- package/tests/migrations.test.js +0 -187
- package/tests/quality-gate.test.js +0 -679
- package/tests/quality-rules.test.js +0 -619
- package/tests/sync-external.test.js +0 -214
- package/tests/update-registry.test.js +0 -251
- package/tests/utils.test.js +0 -171
- package/tests/version-check.test.js +0 -75
- package/tests/web-search.test.js +0 -392
- package/thinkinglens-silent.md +0 -138
|
@@ -1,941 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Knowledge Engine (JavaScript version)
|
|
3
|
-
*
|
|
4
|
-
* Integrates NotebookLM knowledge capabilities with local knowledge base
|
|
5
|
-
* and web search for Phase 1 research.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
// Import DocumentScanner
|
|
12
|
-
const { DocumentScanner } = require('./document-scanner.js');
|
|
13
|
-
|
|
14
|
-
// Import WebSearch and SearchCache
|
|
15
|
-
const { WebSearch } = require('./web-search.js');
|
|
16
|
-
const { SearchCache } = require('./search-cache.js');
|
|
17
|
-
|
|
18
|
-
// Try to import NotebookLM browser module
|
|
19
|
-
let NotebookLMClient = null;
|
|
20
|
-
let getClient = null;
|
|
21
|
-
try {
|
|
22
|
-
const notebooklm = require('./notebooklm/browser.js');
|
|
23
|
-
NotebookLMClient = notebooklm.NotebookLMClient;
|
|
24
|
-
getClient = notebooklm.getClient;
|
|
25
|
-
} catch (e) {
|
|
26
|
-
// NotebookLM not available, will use fallback
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ============================================================================
|
|
30
|
-
// Configuration
|
|
31
|
-
// ============================================================================
|
|
32
|
-
|
|
33
|
-
const KNOWLEDGE_BASE_DIR = path.join(process.cwd(), 'development/knowledge-base');
|
|
34
|
-
const PROJECTS_DIR = path.join(process.cwd(), 'development/projects');
|
|
35
|
-
const KNOWLEDGE_INDEX_FILE = path.join(KNOWLEDGE_BASE_DIR, '.index.json');
|
|
36
|
-
|
|
37
|
-
// ============================================================================
|
|
38
|
-
// Knowledge Engine Class
|
|
39
|
-
// ============================================================================
|
|
40
|
-
|
|
41
|
-
class KnowledgeEngine {
|
|
42
|
-
constructor() {
|
|
43
|
-
this.index = { sources: [], lastUpdated: 0 };
|
|
44
|
-
this.indexLoaded = false;
|
|
45
|
-
this.ensureDirectories();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// --------------------------------------------------------------------------
|
|
49
|
-
// Initialization
|
|
50
|
-
// --------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
ensureDirectories() {
|
|
53
|
-
[KNOWLEDGE_BASE_DIR, PROJECTS_DIR].forEach(dir => {
|
|
54
|
-
if (!fs.existsSync(dir)) {
|
|
55
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
loadIndex() {
|
|
61
|
-
if (this.indexLoaded) return;
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
if (fs.existsSync(KNOWLEDGE_INDEX_FILE)) {
|
|
65
|
-
const content = fs.readFileSync(KNOWLEDGE_INDEX_FILE, 'utf-8');
|
|
66
|
-
this.index = JSON.parse(content);
|
|
67
|
-
}
|
|
68
|
-
this.indexLoaded = true;
|
|
69
|
-
} catch (error) {
|
|
70
|
-
console.warn('Failed to load knowledge index, starting fresh:', error);
|
|
71
|
-
this.index = { sources: [], lastUpdated: Date.now() };
|
|
72
|
-
this.indexLoaded = true;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
saveIndex() {
|
|
77
|
-
try {
|
|
78
|
-
fs.writeFileSync(
|
|
79
|
-
KNOWLEDGE_INDEX_FILE,
|
|
80
|
-
JSON.stringify(this.index, null, 2),
|
|
81
|
-
'utf-8'
|
|
82
|
-
);
|
|
83
|
-
this.index.lastUpdated = Date.now();
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error('Failed to save knowledge index:', error);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// --------------------------------------------------------------------------
|
|
90
|
-
// Knowledge Source Management
|
|
91
|
-
// --------------------------------------------------------------------------
|
|
92
|
-
|
|
93
|
-
generateId() {
|
|
94
|
-
return `src_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
getMimeType(ext) {
|
|
98
|
-
const mimeTypes = {
|
|
99
|
-
'.md': 'text/markdown',
|
|
100
|
-
'.txt': 'text/plain',
|
|
101
|
-
'.json': 'application/json',
|
|
102
|
-
'.pdf': 'application/pdf',
|
|
103
|
-
'.html': 'text/html',
|
|
104
|
-
'.js': 'text/javascript',
|
|
105
|
-
'.ts': 'text/typescript',
|
|
106
|
-
'.py': 'text/x-python',
|
|
107
|
-
'.rs': 'text/x-rust'
|
|
108
|
-
};
|
|
109
|
-
return mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Add a knowledge source to the index
|
|
114
|
-
*/
|
|
115
|
-
addSource(source) {
|
|
116
|
-
this.loadIndex();
|
|
117
|
-
|
|
118
|
-
const id = this.generateId();
|
|
119
|
-
const newSource = {
|
|
120
|
-
...source,
|
|
121
|
-
id,
|
|
122
|
-
addedAt: Date.now(),
|
|
123
|
-
lastAccessed: Date.now()
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
this.index.sources.push(newSource);
|
|
127
|
-
this.saveIndex();
|
|
128
|
-
|
|
129
|
-
return id;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Add a local file as knowledge source
|
|
134
|
-
*/
|
|
135
|
-
addFile(filePath, tags = [], options = {}) {
|
|
136
|
-
const resolvedPath = path.resolve(filePath);
|
|
137
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
138
|
-
throw new Error(`File not found: ${filePath}`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const stats = fs.statSync(resolvedPath);
|
|
142
|
-
const ext = path.extname(resolvedPath);
|
|
143
|
-
|
|
144
|
-
// Scan file for content and metadata
|
|
145
|
-
const scanResult = DocumentScanner.scanFile(resolvedPath, {
|
|
146
|
-
includeContent: true,
|
|
147
|
-
maxContentSize: options.maxContentSize || 500 * 1024
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// Build title from front matter or filename
|
|
151
|
-
let title = path.basename(resolvedPath);
|
|
152
|
-
let description = `Local file: ${resolvedPath}`;
|
|
153
|
-
|
|
154
|
-
if (scanResult.frontMatter && scanResult.frontMatter.title) {
|
|
155
|
-
title = scanResult.frontMatter.title;
|
|
156
|
-
}
|
|
157
|
-
if (scanResult.frontMatter && scanResult.frontMatter.description) {
|
|
158
|
-
description = scanResult.frontMatter.description;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return this.addSource({
|
|
162
|
-
type: 'local_file',
|
|
163
|
-
path: resolvedPath,
|
|
164
|
-
title,
|
|
165
|
-
description,
|
|
166
|
-
tags: tags.concat(scanResult.frontMatter?.tags?.split(',') || []),
|
|
167
|
-
size: stats.size,
|
|
168
|
-
contentType: scanResult.contentType,
|
|
169
|
-
|
|
170
|
-
// New fields from scanning
|
|
171
|
-
scannable: scanResult.scannable,
|
|
172
|
-
wordCount: scanResult.wordCount,
|
|
173
|
-
lineCount: scanResult.lineCount,
|
|
174
|
-
headings: scanResult.headings,
|
|
175
|
-
links: scanResult.links,
|
|
176
|
-
codeBlocks: scanResult.codeBlocks,
|
|
177
|
-
content: scanResult.content,
|
|
178
|
-
snippet: scanResult.snippet,
|
|
179
|
-
checksum: scanResult.checksum,
|
|
180
|
-
lastModified: scanResult.lastModified,
|
|
181
|
-
indexedAt: Date.now()
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Add a local directory as knowledge source
|
|
187
|
-
* Recursively scans all supported files in the directory
|
|
188
|
-
*/
|
|
189
|
-
addDirectory(dirPath, tags = [], options = {}) {
|
|
190
|
-
const resolvedPath = path.resolve(dirPath);
|
|
191
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
192
|
-
throw new Error(`Directory not found: ${dirPath}`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const {
|
|
196
|
-
recursive = true,
|
|
197
|
-
maxDepth = 10,
|
|
198
|
-
includePatterns = [],
|
|
199
|
-
excludePatterns = ['node_modules', '.git', 'dist', 'build', 'coverage', 'sessions', '.claude']
|
|
200
|
-
} = options;
|
|
201
|
-
|
|
202
|
-
// Scan the directory for files
|
|
203
|
-
const scanResults = DocumentScanner.scanDirectory(resolvedPath, {
|
|
204
|
-
recursive,
|
|
205
|
-
maxDepth,
|
|
206
|
-
includePatterns,
|
|
207
|
-
excludePatterns
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// Add each file as a separate source
|
|
211
|
-
const addedIds = [];
|
|
212
|
-
for (const result of scanResults) {
|
|
213
|
-
if (!result.scannable) continue;
|
|
214
|
-
|
|
215
|
-
let title = path.basename(result.path);
|
|
216
|
-
let description = `File: ${result.path}`;
|
|
217
|
-
|
|
218
|
-
// Use front matter if available
|
|
219
|
-
if (result.frontMatter && result.frontMatter.title) {
|
|
220
|
-
title = result.frontMatter.title;
|
|
221
|
-
}
|
|
222
|
-
if (result.frontMatter && result.frontMatter.description) {
|
|
223
|
-
description = result.frontMatter.description;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const id = this.addSource({
|
|
227
|
-
type: 'local_file',
|
|
228
|
-
path: result.path,
|
|
229
|
-
title,
|
|
230
|
-
description,
|
|
231
|
-
tags: tags.concat(result.frontMatter?.tags?.split(',') || []),
|
|
232
|
-
size: result.size,
|
|
233
|
-
contentType: result.contentType,
|
|
234
|
-
scannable: result.scannable,
|
|
235
|
-
wordCount: result.wordCount,
|
|
236
|
-
lineCount: result.lineCount,
|
|
237
|
-
headings: result.headings,
|
|
238
|
-
links: result.links,
|
|
239
|
-
codeBlocks: result.codeBlocks,
|
|
240
|
-
content: result.content,
|
|
241
|
-
snippet: result.snippet,
|
|
242
|
-
checksum: result.checksum,
|
|
243
|
-
lastModified: result.lastModified,
|
|
244
|
-
indexedAt: Date.now()
|
|
245
|
-
});
|
|
246
|
-
addedIds.push(id);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Also add the directory itself as a container
|
|
250
|
-
this.addSource({
|
|
251
|
-
type: 'local_directory',
|
|
252
|
-
path: resolvedPath,
|
|
253
|
-
title: path.basename(resolvedPath),
|
|
254
|
-
description: `Directory with ${addedIds.length} indexed files`,
|
|
255
|
-
tags,
|
|
256
|
-
fileCount: addedIds.length
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
return addedIds;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Add a NotebookLM notebook as knowledge source
|
|
264
|
-
*/
|
|
265
|
-
addNotebook(notebookUrl, title, tags = []) {
|
|
266
|
-
return this.addSource({
|
|
267
|
-
type: 'notebooklm',
|
|
268
|
-
notebookUrl,
|
|
269
|
-
title,
|
|
270
|
-
description: `NotebookLM: ${title}`,
|
|
271
|
-
tags
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* List all knowledge sources
|
|
277
|
-
*/
|
|
278
|
-
listSources(filter = {}) {
|
|
279
|
-
this.loadIndex();
|
|
280
|
-
|
|
281
|
-
let sources = [...this.index.sources];
|
|
282
|
-
|
|
283
|
-
if (filter.type) {
|
|
284
|
-
sources = sources.filter(s => s.type === filter.type);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (filter.tag) {
|
|
288
|
-
sources = sources.filter(s => s.tags.includes(filter.tag));
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return sources.sort((a, b) => b.lastAccessed - a.lastAccessed);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Get a specific knowledge source by ID
|
|
296
|
-
*/
|
|
297
|
-
getSource(id) {
|
|
298
|
-
this.loadIndex();
|
|
299
|
-
return this.index.sources.find(s => s.id === id) || null;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Remove a knowledge source
|
|
304
|
-
*/
|
|
305
|
-
removeSource(id) {
|
|
306
|
-
this.loadIndex();
|
|
307
|
-
const initialLength = this.index.sources.length;
|
|
308
|
-
this.index.sources = this.index.sources.filter(s => s.id !== id);
|
|
309
|
-
|
|
310
|
-
if (this.index.sources.length < initialLength) {
|
|
311
|
-
this.saveIndex();
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Check if a source needs reindexing due to file changes
|
|
319
|
-
*/
|
|
320
|
-
needsReindex(source) {
|
|
321
|
-
if (source.type !== 'local_file') return false;
|
|
322
|
-
if (!fs.existsSync(source.path)) return false; // File deleted
|
|
323
|
-
|
|
324
|
-
try {
|
|
325
|
-
const stats = fs.statSync(source.path);
|
|
326
|
-
// Check if file was modified since last index
|
|
327
|
-
if (source.lastModified && stats.mtimeMs > source.lastModified) {
|
|
328
|
-
return true;
|
|
329
|
-
}
|
|
330
|
-
// Also check checksum if available
|
|
331
|
-
if (source.checksum) {
|
|
332
|
-
const currentContent = fs.readFileSync(source.path, 'utf-8');
|
|
333
|
-
const currentChecksum = require('crypto')
|
|
334
|
-
.createHash('md5')
|
|
335
|
-
.update(currentContent)
|
|
336
|
-
.digest('hex');
|
|
337
|
-
return currentChecksum !== source.checksum;
|
|
338
|
-
}
|
|
339
|
-
} catch (error) {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Reindex a single file source
|
|
348
|
-
*/
|
|
349
|
-
reindexFile(source) {
|
|
350
|
-
try {
|
|
351
|
-
const scanResult = DocumentScanner.scanFile(source.path, {
|
|
352
|
-
includeContent: true
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
// Update the source with new data
|
|
356
|
-
Object.assign(source, {
|
|
357
|
-
wordCount: scanResult.wordCount,
|
|
358
|
-
lineCount: scanResult.lineCount,
|
|
359
|
-
headings: scanResult.headings,
|
|
360
|
-
links: scanResult.links,
|
|
361
|
-
codeBlocks: scanResult.codeBlocks,
|
|
362
|
-
content: scanResult.content,
|
|
363
|
-
snippet: scanResult.snippet,
|
|
364
|
-
checksum: scanResult.checksum,
|
|
365
|
-
lastModified: scanResult.lastModified,
|
|
366
|
-
indexedAt: Date.now()
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
this.saveIndex();
|
|
370
|
-
return true;
|
|
371
|
-
} catch (error) {
|
|
372
|
-
console.warn(`Failed to reindex ${source.path}:`, error.message);
|
|
373
|
-
return false;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Update index for all files that have changed
|
|
379
|
-
*/
|
|
380
|
-
updateIndex(options = {}) {
|
|
381
|
-
const { progressCallback } = options;
|
|
382
|
-
this.loadIndex();
|
|
383
|
-
|
|
384
|
-
const fileSources = this.index.sources.filter(s => s.type === 'local_file');
|
|
385
|
-
let updatedCount = 0;
|
|
386
|
-
|
|
387
|
-
for (const source of fileSources) {
|
|
388
|
-
if (this.needsReindex(source)) {
|
|
389
|
-
progressCallback?.(`Reindexing: ${source.title}`);
|
|
390
|
-
if (this.reindexFile(source)) {
|
|
391
|
-
updatedCount++;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Remove sources for files that no longer exist
|
|
397
|
-
const initialLength = this.index.sources.length;
|
|
398
|
-
this.index.sources = this.index.sources.filter(s => {
|
|
399
|
-
if (s.type === 'local_file') {
|
|
400
|
-
return fs.existsSync(s.path);
|
|
401
|
-
}
|
|
402
|
-
return true;
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
if (this.index.sources.length < initialLength) {
|
|
406
|
-
this.saveIndex();
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return { updatedCount, removedCount: initialLength - this.index.sources.length };
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// --------------------------------------------------------------------------
|
|
413
|
-
// Knowledge Query
|
|
414
|
-
// --------------------------------------------------------------------------
|
|
415
|
-
|
|
416
|
-
calculateRelevance(question, source) {
|
|
417
|
-
const questionLower = question.toLowerCase();
|
|
418
|
-
const questionWords = questionLower.split(/\s+/).filter(w => w.length > 2);
|
|
419
|
-
|
|
420
|
-
let score = 0;
|
|
421
|
-
const weights = {
|
|
422
|
-
title: 0.35,
|
|
423
|
-
tags: 0.25,
|
|
424
|
-
content: 0.30,
|
|
425
|
-
headings: 0.10
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
// 1. Title matching
|
|
429
|
-
const titleLower = (source.title || '').toLowerCase();
|
|
430
|
-
const titleWords = titleLower.split(/\s+/);
|
|
431
|
-
let titleMatches = 0;
|
|
432
|
-
questionWords.forEach(word => {
|
|
433
|
-
if (titleWords.some(t => t.includes(word) || word.includes(t))) {
|
|
434
|
-
titleMatches++;
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
score += (titleMatches / Math.max(questionWords.length, 1)) * weights.title;
|
|
438
|
-
|
|
439
|
-
// 2. Tag matching
|
|
440
|
-
if (source.tags && source.tags.length > 0) {
|
|
441
|
-
let tagMatches = 0;
|
|
442
|
-
questionWords.forEach(word => {
|
|
443
|
-
if (source.tags.some(t => t.toLowerCase().includes(word))) {
|
|
444
|
-
tagMatches++;
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
score += (tagMatches / Math.max(questionWords.length, 1)) * weights.tags;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// 3. Content matching (full-text search)
|
|
451
|
-
if (source.content || source.snippet) {
|
|
452
|
-
const contentLower = (source.content || source.snippet || '').toLowerCase();
|
|
453
|
-
let contentMatches = 0;
|
|
454
|
-
questionWords.forEach(word => {
|
|
455
|
-
if (contentLower.includes(word)) {
|
|
456
|
-
contentMatches++;
|
|
457
|
-
}
|
|
458
|
-
});
|
|
459
|
-
score += (contentMatches / Math.max(questionWords.length, 1)) * weights.content;
|
|
460
|
-
|
|
461
|
-
// Boost for exact phrase match
|
|
462
|
-
if (contentLower.includes(questionLower)) {
|
|
463
|
-
score += 0.2;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// 4. Headings matching
|
|
468
|
-
if (source.headings && source.headings.length > 0) {
|
|
469
|
-
let headingMatches = 0;
|
|
470
|
-
questionWords.forEach(word => {
|
|
471
|
-
if (source.headings.some(h => h.text && h.text.toLowerCase().includes(word))) {
|
|
472
|
-
headingMatches++;
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
score += (headingMatches / Math.max(questionWords.length, 1)) * weights.headings;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return Math.min(score, 1);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Query the knowledge base with NotebookLM integration
|
|
483
|
-
*/
|
|
484
|
-
async query(question, options = {}) {
|
|
485
|
-
this.loadIndex();
|
|
486
|
-
|
|
487
|
-
const {
|
|
488
|
-
includeWeb = false,
|
|
489
|
-
maxSources = 5,
|
|
490
|
-
progressCallback,
|
|
491
|
-
useNotebookLM = true
|
|
492
|
-
} = options;
|
|
493
|
-
|
|
494
|
-
const sources = [];
|
|
495
|
-
const webResults = [];
|
|
496
|
-
let notebooklmAnswer = null;
|
|
497
|
-
|
|
498
|
-
// Step 1: Search local knowledge base
|
|
499
|
-
await progressCallback?.('Searching local knowledge base...', 1, 5);
|
|
500
|
-
|
|
501
|
-
const localSources = this.index.sources.filter(
|
|
502
|
-
s => s.type === 'local_file' || s.type === 'local_directory'
|
|
503
|
-
);
|
|
504
|
-
|
|
505
|
-
for (const source of localSources.slice(0, maxSources)) {
|
|
506
|
-
const relevance = this.calculateRelevance(question, source);
|
|
507
|
-
if (relevance > 0.3) {
|
|
508
|
-
sources.push({
|
|
509
|
-
title: source.title,
|
|
510
|
-
type: source.type,
|
|
511
|
-
relevance,
|
|
512
|
-
excerpt: `Excerpt from ${source.title}`
|
|
513
|
-
});
|
|
514
|
-
source.lastAccessed = Date.now();
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// Step 2: Query NotebookLM if available and enabled
|
|
519
|
-
await progressCallback?.('Querying NotebookLM...', 2, 5);
|
|
520
|
-
|
|
521
|
-
const notebooklmSources = this.index.sources.filter(s => s.type === 'notebooklm');
|
|
522
|
-
|
|
523
|
-
if (useNotebookLM && notebooklmSources.length > 0 && getClient) {
|
|
524
|
-
try {
|
|
525
|
-
const client = getClient();
|
|
526
|
-
|
|
527
|
-
// Check if authenticated
|
|
528
|
-
const stats = client.getStats();
|
|
529
|
-
if (!stats.authenticated) {
|
|
530
|
-
sources.push({
|
|
531
|
-
title: 'NotebookLM (requires auth)',
|
|
532
|
-
type: 'notebooklm',
|
|
533
|
-
relevance: 0.9,
|
|
534
|
-
excerpt: 'NotebookLM requires authentication. Run: smc notebooklm auth'
|
|
535
|
-
});
|
|
536
|
-
} else {
|
|
537
|
-
// Use the first NotebookLM source
|
|
538
|
-
const notebookUrl = notebooklmSources[0].notebookUrl || null;
|
|
539
|
-
notebooklmAnswer = await client.ask(notebookUrl, question, (msg) => {
|
|
540
|
-
// Forward progress
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
sources.push({
|
|
544
|
-
title: 'NotebookLM',
|
|
545
|
-
type: 'notebooklm',
|
|
546
|
-
relevance: 1.0,
|
|
547
|
-
excerpt: notebooklmAnswer?.substring(0, 200) + '...'
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
} catch (error) {
|
|
551
|
-
sources.push({
|
|
552
|
-
title: 'NotebookLM (error)',
|
|
553
|
-
type: 'notebooklm',
|
|
554
|
-
relevance: 0.5,
|
|
555
|
-
excerpt: `NotebookLM query failed: ${error.message}`
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
} else if (notebooklmSources.length === 0) {
|
|
559
|
-
sources.push({
|
|
560
|
-
title: 'NotebookLM',
|
|
561
|
-
type: 'notebooklm',
|
|
562
|
-
relevance: 0.5,
|
|
563
|
-
excerpt: 'No NotebookLM sources added. Add one with: smc knowledge notebook <url> <title>'
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Step 3: Web search
|
|
568
|
-
if (includeWeb) {
|
|
569
|
-
await progressCallback?.('Searching web for latest information...', 3, 5);
|
|
570
|
-
|
|
571
|
-
try {
|
|
572
|
-
// Check cache first
|
|
573
|
-
const cached = SearchCache.get(question);
|
|
574
|
-
if (cached && cached.length > 0) {
|
|
575
|
-
webResults.push(...cached);
|
|
576
|
-
await progressCallback?.('Using cached web results...', 4, 5);
|
|
577
|
-
} else {
|
|
578
|
-
// Perform fresh search
|
|
579
|
-
const freshResults = await WebSearch.search(question, { maxResults: 5 });
|
|
580
|
-
if (freshResults.length > 0) {
|
|
581
|
-
webResults.push(...freshResults);
|
|
582
|
-
// Cache the results
|
|
583
|
-
SearchCache.set(question, freshResults);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
} catch (error) {
|
|
587
|
-
// Graceful degradation - search failed but continue
|
|
588
|
-
webResults.push({
|
|
589
|
-
title: 'Web Search (unavailable)',
|
|
590
|
-
url: '#',
|
|
591
|
-
excerpt: `Web search is currently unavailable: ${error.message}. Try again later.`,
|
|
592
|
-
source: 'error'
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// Step 4: Synthesize answer
|
|
598
|
-
await progressCallback?.('Synthesizing answer...', 4, 5);
|
|
599
|
-
|
|
600
|
-
// Use NotebookLM answer if available, otherwise fall back to synthesis
|
|
601
|
-
let finalAnswer;
|
|
602
|
-
if (notebooklmAnswer) {
|
|
603
|
-
finalAnswer = notebooklmAnswer;
|
|
604
|
-
} else {
|
|
605
|
-
finalAnswer = this.synthesizeAnswer(question, sources, webResults);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
await progressCallback?.('Query complete!', 5, 5);
|
|
609
|
-
|
|
610
|
-
return {
|
|
611
|
-
answer: finalAnswer,
|
|
612
|
-
sources: sources.slice(0, maxSources).sort((a, b) => b.relevance - a.relevance),
|
|
613
|
-
webResults,
|
|
614
|
-
confidence: this.calculateConfidence(sources, webResults),
|
|
615
|
-
notebooklmUsed: !!notebooklmAnswer
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* Query NotebookLM directly (bypass local knowledge)
|
|
621
|
-
*/
|
|
622
|
-
async queryNotebookLM(question, notebookUrl = null, progressCallback) {
|
|
623
|
-
if (!getClient) {
|
|
624
|
-
throw new Error('NotebookLM module not available. Install patchright: npm install patchright');
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
const client = getClient();
|
|
628
|
-
const stats = client.getStats();
|
|
629
|
-
|
|
630
|
-
if (!stats.authenticated) {
|
|
631
|
-
throw new Error('Not authenticated. Run: smc notebooklm auth');
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
await progressCallback?.('Asking NotebookLM...');
|
|
635
|
-
const answer = await client.ask(notebookUrl, question, (msg) => {
|
|
636
|
-
// Forward progress silently or log
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
return {
|
|
640
|
-
answer,
|
|
641
|
-
notebooklmUsed: true,
|
|
642
|
-
confidence: 0.95
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
synthesizeAnswer(question, sources, webResults) {
|
|
647
|
-
if (sources.length === 0 && (!webResults || webResults.length === 0)) {
|
|
648
|
-
return `No relevant information found in the knowledge base for: "${question}"`;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
let answer = `Based on the knowledge base, here's what I found regarding "${question}":\n\n`;
|
|
652
|
-
|
|
653
|
-
if (sources.length > 0) {
|
|
654
|
-
answer += `**Local Knowledge:**\n`;
|
|
655
|
-
sources.forEach(source => {
|
|
656
|
-
answer += `- ${source.title} (relevance: ${(source.relevance * 100).toFixed(0)}%)\n`;
|
|
657
|
-
if (source.excerpt && source.excerpt !== 'Excerpt from ' + source.title) {
|
|
658
|
-
answer += ` ${source.excerpt.substring(0, 150)}${source.excerpt.length > 150 ? '...' : ''}\n`;
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
answer += '\n';
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
if (webResults && webResults.length > 0) {
|
|
665
|
-
answer += `**Web Sources:**\n`;
|
|
666
|
-
webResults.forEach(result => {
|
|
667
|
-
// Format URL for display (decode if needed)
|
|
668
|
-
let displayUrl = result.url;
|
|
669
|
-
if (displayUrl.startsWith('a1aHR0c')) {
|
|
670
|
-
// URL appears to be still encoded
|
|
671
|
-
try {
|
|
672
|
-
displayUrl = Buffer.from(displayUrl, 'base64').toString('ascii');
|
|
673
|
-
// Extract the actual URL from the encoded string
|
|
674
|
-
const urlMatch = displayUrl.match(/https?:\/\/[^&\s]+/);
|
|
675
|
-
if (urlMatch) displayUrl = urlMatch[0];
|
|
676
|
-
} catch (e) {}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
answer += `- [${result.title}](${displayUrl})\n`;
|
|
680
|
-
if (result.excerpt && result.excerpt !== 'No description available.') {
|
|
681
|
-
answer += ` ${result.excerpt.substring(0, 200)}${result.excerpt.length > 200 ? '...' : ''}\n`;
|
|
682
|
-
}
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
return answer;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
calculateConfidence(sources, webResults) {
|
|
690
|
-
const sourceCount = sources.length + (webResults?.length || 0);
|
|
691
|
-
const avgRelevance = sources.reduce((sum, s) => sum + s.relevance, 0) / Math.max(sources.length, 1);
|
|
692
|
-
|
|
693
|
-
return Math.min((sourceCount / 5) * 0.5 + avgRelevance * 0.5, 1);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// --------------------------------------------------------------------------
|
|
697
|
-
// Statistics
|
|
698
|
-
// --------------------------------------------------------------------------
|
|
699
|
-
|
|
700
|
-
getStats() {
|
|
701
|
-
this.loadIndex();
|
|
702
|
-
|
|
703
|
-
const sourcesByType = {};
|
|
704
|
-
this.index.sources.forEach(s => {
|
|
705
|
-
sourcesByType[s.type] = (sourcesByType[s.type] || 0) + 1;
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
return {
|
|
709
|
-
totalSources: this.index.sources.length,
|
|
710
|
-
sourcesByType,
|
|
711
|
-
lastUpdated: this.index.lastUpdated
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// ============================================================================
|
|
717
|
-
// Singleton Instance
|
|
718
|
-
// ============================================================================
|
|
719
|
-
|
|
720
|
-
let knowledgeEngineInstance = null;
|
|
721
|
-
|
|
722
|
-
function getKnowledgeEngine() {
|
|
723
|
-
if (!knowledgeEngineInstance) {
|
|
724
|
-
knowledgeEngineInstance = new KnowledgeEngine();
|
|
725
|
-
}
|
|
726
|
-
return knowledgeEngineInstance;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// ============================================================================
|
|
730
|
-
// CLI Helpers
|
|
731
|
-
// ============================================================================
|
|
732
|
-
|
|
733
|
-
async function handleKnowledgeCommand(args) {
|
|
734
|
-
const engine = getKnowledgeEngine();
|
|
735
|
-
const [action, ...rest] = args;
|
|
736
|
-
|
|
737
|
-
switch (action) {
|
|
738
|
-
case 'add': {
|
|
739
|
-
const [filePath, ...restArgs] = rest;
|
|
740
|
-
if (!filePath) {
|
|
741
|
-
console.error('Usage: smc knowledge add <file|directory> [tags...] [--recursive] [--max-depth=N]');
|
|
742
|
-
process.exit(1);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
try {
|
|
746
|
-
// Parse options
|
|
747
|
-
const tags = restArgs.filter(arg => !arg.startsWith('--'));
|
|
748
|
-
const isRecursive = restArgs.includes('--recursive');
|
|
749
|
-
const maxDepthMatch = restArgs.find(arg => arg.startsWith('--max-depth='));
|
|
750
|
-
const maxDepth = maxDepthMatch ? parseInt(maxDepthMatch.split('=')[1]) : 10;
|
|
751
|
-
|
|
752
|
-
const resolvedPath = path.resolve(filePath);
|
|
753
|
-
const stats = fs.existsSync(resolvedPath) ? fs.statSync(resolvedPath) : null;
|
|
754
|
-
|
|
755
|
-
if (stats && stats.isDirectory()) {
|
|
756
|
-
// Add directory
|
|
757
|
-
console.log(`๐ Scanning directory: ${filePath}`);
|
|
758
|
-
const addedIds = engine.addDirectory(filePath, tags, {
|
|
759
|
-
recursive: isRecursive !== false, // default true
|
|
760
|
-
maxDepth
|
|
761
|
-
});
|
|
762
|
-
console.log(`โ
Added ${addedIds.length} files from directory`);
|
|
763
|
-
} else if (stats && stats.isFile()) {
|
|
764
|
-
// Add single file
|
|
765
|
-
const id = engine.addFile(filePath, tags);
|
|
766
|
-
const source = engine.getSource(id);
|
|
767
|
-
const scanInfo = source.scannable
|
|
768
|
-
? ` (${source.wordCount} words, ${source.headings?.length || 0} headings)`
|
|
769
|
-
: ' (not scannable)';
|
|
770
|
-
console.log(`โ
Added: ${source.title}${scanInfo}`);
|
|
771
|
-
} else {
|
|
772
|
-
console.error(`โ Error: File not found: ${filePath}`);
|
|
773
|
-
process.exit(1);
|
|
774
|
-
}
|
|
775
|
-
} catch (error) {
|
|
776
|
-
console.error(`โ Error: ${error.message}`);
|
|
777
|
-
process.exit(1);
|
|
778
|
-
}
|
|
779
|
-
break;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
case 'list': {
|
|
783
|
-
const sources = engine.listSources();
|
|
784
|
-
console.log(`\n๐ Knowledge Base (${sources.length} sources):\n`);
|
|
785
|
-
|
|
786
|
-
if (sources.length === 0) {
|
|
787
|
-
console.log(' No knowledge sources yet.');
|
|
788
|
-
console.log(' Add sources with: smc knowledge add <file|directory> [tags...]');
|
|
789
|
-
} else {
|
|
790
|
-
const icons = {
|
|
791
|
-
local_file: '๐',
|
|
792
|
-
local_directory: '๐',
|
|
793
|
-
notebooklm: '๐',
|
|
794
|
-
web_search: '๐',
|
|
795
|
-
web_url: '๐'
|
|
796
|
-
};
|
|
797
|
-
|
|
798
|
-
sources.forEach(source => {
|
|
799
|
-
const icon = icons[source.type] || '๐';
|
|
800
|
-
const tags = source.tags && source.tags.length > 0 ? ` [${source.tags.join(', ')}]` : '';
|
|
801
|
-
console.log(` ${icon} ${source.title}${tags}`);
|
|
802
|
-
|
|
803
|
-
// Show file info
|
|
804
|
-
if (source.scannable) {
|
|
805
|
-
const wordInfo = source.wordCount ? `${source.wordCount} words` : '';
|
|
806
|
-
const headingInfo = source.headings && source.headings.length > 0 ? `${source.headings.length} headings` : '';
|
|
807
|
-
const details = [wordInfo, headingInfo].filter(Boolean).join(', ');
|
|
808
|
-
if (details) {
|
|
809
|
-
console.log(` ๐ ${details}`);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
console.log(` Type: ${source.type} | Added: ${new Date(source.addedAt).toLocaleDateString()}`);
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
console.log('');
|
|
817
|
-
break;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
case 'query': {
|
|
821
|
-
const question = rest.join(' ').replace('--web', '').trim();
|
|
822
|
-
if (!question) {
|
|
823
|
-
console.error('Usage: smc knowledge query "<question>" [--web]');
|
|
824
|
-
process.exit(1);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
const includeWeb = rest.includes('--web');
|
|
828
|
-
const result = await engine.query(question, { includeWeb });
|
|
829
|
-
|
|
830
|
-
console.log(`\n${result.answer}\n`);
|
|
831
|
-
console.log(`Confidence: ${(result.confidence * 100).toFixed(0)}%`);
|
|
832
|
-
break;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
case 'stats': {
|
|
836
|
-
const stats = engine.getStats();
|
|
837
|
-
console.log('\n๐ Knowledge Base Statistics:\n');
|
|
838
|
-
console.log(` Total Sources: ${stats.totalSources}`);
|
|
839
|
-
console.log(' By Type:');
|
|
840
|
-
Object.entries(stats.sourcesByType).forEach(([type, count]) => {
|
|
841
|
-
console.log(` - ${type}: ${count}`);
|
|
842
|
-
});
|
|
843
|
-
console.log(` Last Updated: ${new Date(stats.lastUpdated).toLocaleString()}\n`);
|
|
844
|
-
break;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
case 'remove': {
|
|
848
|
-
const [id] = rest;
|
|
849
|
-
if (!id) {
|
|
850
|
-
console.error('Usage: smc knowledge remove <source-id>');
|
|
851
|
-
process.exit(1);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
if (engine.removeSource(id)) {
|
|
855
|
-
console.log(`โ
Removed knowledge source: ${id}`);
|
|
856
|
-
} else {
|
|
857
|
-
console.error(`โ Source not found: ${id}`);
|
|
858
|
-
process.exit(1);
|
|
859
|
-
}
|
|
860
|
-
break;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
case 'update': {
|
|
864
|
-
console.log('๐ Checking for file changes...\n');
|
|
865
|
-
const result = engine.updateIndex({
|
|
866
|
-
progressCallback: (msg) => console.log(` ${msg}`)
|
|
867
|
-
});
|
|
868
|
-
|
|
869
|
-
if (result.updatedCount === 0 && result.removedCount === 0) {
|
|
870
|
-
console.log('โ
Index is up to date!');
|
|
871
|
-
} else {
|
|
872
|
-
console.log(`\nโ
Updated: ${result.updatedCount} files`);
|
|
873
|
-
if (result.removedCount > 0) {
|
|
874
|
-
console.log(` Removed: ${result.removedCount} deleted files`);
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
break;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
case 'cache': {
|
|
881
|
-
const [action] = rest;
|
|
882
|
-
if (action === 'clear') {
|
|
883
|
-
const cleared = SearchCache.clear();
|
|
884
|
-
console.log(`๐๏ธ Cleared ${cleared} cached search results`);
|
|
885
|
-
} else if (action === 'clean') {
|
|
886
|
-
const cleaned = SearchCache.clean();
|
|
887
|
-
console.log(`๐งน Cleaned ${cleaned} expired cache entries`);
|
|
888
|
-
} else if (action === 'stats') {
|
|
889
|
-
const stats = SearchCache.getStats();
|
|
890
|
-
console.log('\n๐ Search Cache Statistics:\n');
|
|
891
|
-
console.log(` Total Entries: ${stats.totalEntries}`);
|
|
892
|
-
console.log(` Valid Entries: ${stats.validEntries}`);
|
|
893
|
-
console.log(` Cache Size: ${(stats.totalSize / 1024).toFixed(2)} KB`);
|
|
894
|
-
console.log(` Cache Dir: ${stats.cacheDir}\n`);
|
|
895
|
-
} else {
|
|
896
|
-
console.log(`
|
|
897
|
-
Search Cache Commands:
|
|
898
|
-
|
|
899
|
-
smc knowledge cache clear Clear all cached results
|
|
900
|
-
smc knowledge cache clean Remove expired entries
|
|
901
|
-
smc knowledge cache stats Show cache statistics
|
|
902
|
-
`);
|
|
903
|
-
}
|
|
904
|
-
break;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
case 'sync': {
|
|
908
|
-
console.log('๐ NotebookLM sync pending - requires notebooklm-mcp integration');
|
|
909
|
-
break;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
default:
|
|
913
|
-
console.log(`
|
|
914
|
-
Knowledge Base Commands:
|
|
915
|
-
|
|
916
|
-
smc knowledge add <file|directory> [tags...] Add a knowledge source
|
|
917
|
-
smc knowledge list List all sources
|
|
918
|
-
smc knowledge query "<question>" [--web] Query the knowledge base
|
|
919
|
-
smc knowledge remove <source-id> Remove a source
|
|
920
|
-
smc knowledge update Update index for changed files
|
|
921
|
-
smc knowledge cache <clear|clean|stats> Manage search cache
|
|
922
|
-
smc knowledge stats Show statistics
|
|
923
|
-
smc knowledge sync Sync with NotebookLM
|
|
924
|
-
|
|
925
|
-
Examples:
|
|
926
|
-
smc knowledge add ./docs/best-practices.md architecture
|
|
927
|
-
smc knowledge add ./docs --recursive
|
|
928
|
-
smc knowledge list
|
|
929
|
-
smc knowledge query "What are the best practices for API design?"
|
|
930
|
-
smc knowledge query --web "React 19 new features"
|
|
931
|
-
smc knowledge update
|
|
932
|
-
smc knowledge cache stats
|
|
933
|
-
`);
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
module.exports = {
|
|
938
|
-
KnowledgeEngine,
|
|
939
|
-
getKnowledgeEngine,
|
|
940
|
-
handleKnowledgeCommand
|
|
941
|
-
};
|