@booklib/core 2.0.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/.cursor/rules/booklib-standards.mdc +40 -0
- package/.gemini/context.md +372 -0
- package/AGENTS.md +166 -0
- package/CHANGELOG.md +226 -0
- package/CLAUDE.md +81 -0
- package/CODE_OF_CONDUCT.md +31 -0
- package/CONTRIBUTING.md +304 -0
- package/LICENSE +21 -0
- package/PLAN.md +28 -0
- package/README.ja.md +198 -0
- package/README.ko.md +198 -0
- package/README.md +503 -0
- package/README.pt-BR.md +198 -0
- package/README.uk.md +241 -0
- package/README.zh-CN.md +198 -0
- package/SECURITY.md +9 -0
- package/agents/architecture-reviewer.md +136 -0
- package/agents/booklib-reviewer.md +90 -0
- package/agents/data-reviewer.md +107 -0
- package/agents/jvm-reviewer.md +146 -0
- package/agents/python-reviewer.md +128 -0
- package/agents/rust-reviewer.md +115 -0
- package/agents/ts-reviewer.md +110 -0
- package/agents/ui-reviewer.md +117 -0
- package/assets/logo.svg +36 -0
- package/bin/booklib-mcp.js +304 -0
- package/bin/booklib.js +1705 -0
- package/bin/skills.cjs +1292 -0
- package/booklib-router.mdc +36 -0
- package/booklib.config.json +19 -0
- package/commands/animation-at-work.md +10 -0
- package/commands/clean-code-reviewer.md +10 -0
- package/commands/data-intensive-patterns.md +10 -0
- package/commands/data-pipelines.md +10 -0
- package/commands/design-patterns.md +10 -0
- package/commands/domain-driven-design.md +10 -0
- package/commands/effective-java.md +10 -0
- package/commands/effective-kotlin.md +10 -0
- package/commands/effective-python.md +10 -0
- package/commands/effective-typescript.md +10 -0
- package/commands/kotlin-in-action.md +10 -0
- package/commands/lean-startup.md +10 -0
- package/commands/microservices-patterns.md +10 -0
- package/commands/programming-with-rust.md +10 -0
- package/commands/refactoring-ui.md +10 -0
- package/commands/rust-in-action.md +10 -0
- package/commands/skill-router.md +10 -0
- package/commands/spring-boot-in-action.md +10 -0
- package/commands/storytelling-with-data.md +10 -0
- package/commands/system-design-interview.md +10 -0
- package/commands/using-asyncio-python.md +10 -0
- package/commands/web-scraping-python.md +10 -0
- package/community/registry.json +1616 -0
- package/hooks/hooks.json +23 -0
- package/hooks/posttooluse-capture.mjs +67 -0
- package/hooks/suggest.js +153 -0
- package/lib/agent-behaviors.js +40 -0
- package/lib/agent-detector.js +96 -0
- package/lib/config-loader.js +39 -0
- package/lib/conflict-resolver.js +148 -0
- package/lib/context-builder.js +574 -0
- package/lib/discovery-engine.js +298 -0
- package/lib/doctor/hook-installer.js +83 -0
- package/lib/doctor/usage-tracker.js +87 -0
- package/lib/engine/ai-features.js +253 -0
- package/lib/engine/auditor.js +103 -0
- package/lib/engine/bm25-index.js +178 -0
- package/lib/engine/capture.js +120 -0
- package/lib/engine/corrections.js +198 -0
- package/lib/engine/doctor.js +195 -0
- package/lib/engine/graph-injector.js +137 -0
- package/lib/engine/graph.js +161 -0
- package/lib/engine/handoff.js +405 -0
- package/lib/engine/indexer.js +242 -0
- package/lib/engine/parser.js +53 -0
- package/lib/engine/query-expander.js +42 -0
- package/lib/engine/reranker.js +40 -0
- package/lib/engine/rrf.js +59 -0
- package/lib/engine/scanner.js +151 -0
- package/lib/engine/searcher.js +139 -0
- package/lib/engine/session-coordinator.js +306 -0
- package/lib/engine/session-manager.js +429 -0
- package/lib/engine/synthesizer.js +70 -0
- package/lib/installer.js +70 -0
- package/lib/instinct-block.js +33 -0
- package/lib/mcp-config-writer.js +88 -0
- package/lib/paths.js +57 -0
- package/lib/profiles/design.md +19 -0
- package/lib/profiles/general.md +16 -0
- package/lib/profiles/research-analysis.md +22 -0
- package/lib/profiles/software-development.md +23 -0
- package/lib/profiles/writing-content.md +19 -0
- package/lib/project-initializer.js +916 -0
- package/lib/registry/skills.js +102 -0
- package/lib/registry-searcher.js +99 -0
- package/lib/rules/rules-manager.js +169 -0
- package/lib/skill-fetcher.js +333 -0
- package/lib/well-known-builder.js +70 -0
- package/lib/wizard/index.js +404 -0
- package/lib/wizard/integration-detector.js +41 -0
- package/lib/wizard/project-detector.js +100 -0
- package/lib/wizard/prompt.js +156 -0
- package/lib/wizard/registry-embeddings.js +107 -0
- package/lib/wizard/skill-recommender.js +69 -0
- package/llms-full.txt +254 -0
- package/llms.txt +70 -0
- package/package.json +45 -0
- package/research-reports/2026-04-01-current-architecture.md +160 -0
- package/research-reports/IDEAS.md +93 -0
- package/rules/common/clean-code.md +42 -0
- package/rules/java/effective-java.md +42 -0
- package/rules/kotlin/effective-kotlin.md +37 -0
- package/rules/python/effective-python.md +38 -0
- package/rules/rust/rust.md +37 -0
- package/rules/typescript/effective-typescript.md +42 -0
- package/scripts/gen-llms-full.mjs +36 -0
- package/scripts/gen-og.mjs +142 -0
- package/scripts/validate-frontmatter.js +25 -0
- package/skills/animation-at-work/SKILL.md +270 -0
- package/skills/animation-at-work/assets/example_asset.txt +1 -0
- package/skills/animation-at-work/evals/evals.json +44 -0
- package/skills/animation-at-work/evals/results.json +13 -0
- package/skills/animation-at-work/examples/after.md +64 -0
- package/skills/animation-at-work/examples/before.md +35 -0
- package/skills/animation-at-work/references/api_reference.md +369 -0
- package/skills/animation-at-work/references/review-checklist.md +79 -0
- package/skills/animation-at-work/scripts/audit_animations.py +295 -0
- package/skills/animation-at-work/scripts/example.py +1 -0
- package/skills/clean-code-reviewer/SKILL.md +444 -0
- package/skills/clean-code-reviewer/audit.json +35 -0
- package/skills/clean-code-reviewer/evals/evals.json +185 -0
- package/skills/clean-code-reviewer/evals/results.json +13 -0
- package/skills/clean-code-reviewer/examples/after.md +48 -0
- package/skills/clean-code-reviewer/examples/before.md +33 -0
- package/skills/clean-code-reviewer/references/api_reference.md +158 -0
- package/skills/clean-code-reviewer/references/practices-catalog.md +282 -0
- package/skills/clean-code-reviewer/references/review-checklist.md +254 -0
- package/skills/clean-code-reviewer/scripts/pre-review.py +206 -0
- package/skills/data-intensive-patterns/SKILL.md +267 -0
- package/skills/data-intensive-patterns/assets/example_asset.txt +1 -0
- package/skills/data-intensive-patterns/evals/evals.json +54 -0
- package/skills/data-intensive-patterns/evals/results.json +13 -0
- package/skills/data-intensive-patterns/examples/after.md +61 -0
- package/skills/data-intensive-patterns/examples/before.md +38 -0
- package/skills/data-intensive-patterns/references/api_reference.md +34 -0
- package/skills/data-intensive-patterns/references/patterns-catalog.md +551 -0
- package/skills/data-intensive-patterns/references/review-checklist.md +193 -0
- package/skills/data-intensive-patterns/scripts/adr.py +213 -0
- package/skills/data-intensive-patterns/scripts/example.py +1 -0
- package/skills/data-pipelines/SKILL.md +259 -0
- package/skills/data-pipelines/assets/example_asset.txt +1 -0
- package/skills/data-pipelines/evals/evals.json +45 -0
- package/skills/data-pipelines/evals/results.json +13 -0
- package/skills/data-pipelines/examples/after.md +97 -0
- package/skills/data-pipelines/examples/before.md +37 -0
- package/skills/data-pipelines/references/api_reference.md +301 -0
- package/skills/data-pipelines/references/review-checklist.md +181 -0
- package/skills/data-pipelines/scripts/example.py +1 -0
- package/skills/data-pipelines/scripts/new_pipeline.py +444 -0
- package/skills/design-patterns/SKILL.md +271 -0
- package/skills/design-patterns/assets/example_asset.txt +1 -0
- package/skills/design-patterns/evals/evals.json +46 -0
- package/skills/design-patterns/evals/results.json +13 -0
- package/skills/design-patterns/examples/after.md +52 -0
- package/skills/design-patterns/examples/before.md +29 -0
- package/skills/design-patterns/references/api_reference.md +1 -0
- package/skills/design-patterns/references/patterns-catalog.md +726 -0
- package/skills/design-patterns/references/review-checklist.md +173 -0
- package/skills/design-patterns/scripts/example.py +1 -0
- package/skills/design-patterns/scripts/scaffold.py +807 -0
- package/skills/domain-driven-design/SKILL.md +142 -0
- package/skills/domain-driven-design/assets/example_asset.txt +1 -0
- package/skills/domain-driven-design/evals/evals.json +48 -0
- package/skills/domain-driven-design/evals/results.json +13 -0
- package/skills/domain-driven-design/examples/after.md +80 -0
- package/skills/domain-driven-design/examples/before.md +43 -0
- package/skills/domain-driven-design/references/api_reference.md +1 -0
- package/skills/domain-driven-design/references/patterns-catalog.md +545 -0
- package/skills/domain-driven-design/references/review-checklist.md +158 -0
- package/skills/domain-driven-design/scripts/example.py +1 -0
- package/skills/domain-driven-design/scripts/scaffold.py +421 -0
- package/skills/effective-java/SKILL.md +227 -0
- package/skills/effective-java/assets/example_asset.txt +1 -0
- package/skills/effective-java/evals/evals.json +46 -0
- package/skills/effective-java/evals/results.json +13 -0
- package/skills/effective-java/examples/after.md +83 -0
- package/skills/effective-java/examples/before.md +37 -0
- package/skills/effective-java/references/api_reference.md +1 -0
- package/skills/effective-java/references/items-catalog.md +955 -0
- package/skills/effective-java/references/review-checklist.md +216 -0
- package/skills/effective-java/scripts/checkstyle_setup.py +211 -0
- package/skills/effective-java/scripts/example.py +1 -0
- package/skills/effective-kotlin/SKILL.md +271 -0
- package/skills/effective-kotlin/assets/example_asset.txt +1 -0
- package/skills/effective-kotlin/audit.json +29 -0
- package/skills/effective-kotlin/evals/evals.json +45 -0
- package/skills/effective-kotlin/evals/results.json +13 -0
- package/skills/effective-kotlin/examples/after.md +36 -0
- package/skills/effective-kotlin/examples/before.md +38 -0
- package/skills/effective-kotlin/references/api_reference.md +1 -0
- package/skills/effective-kotlin/references/practices-catalog.md +1228 -0
- package/skills/effective-kotlin/references/review-checklist.md +126 -0
- package/skills/effective-kotlin/scripts/example.py +1 -0
- package/skills/effective-python/SKILL.md +441 -0
- package/skills/effective-python/evals/evals.json +44 -0
- package/skills/effective-python/evals/results.json +13 -0
- package/skills/effective-python/examples/after.md +56 -0
- package/skills/effective-python/examples/before.md +40 -0
- package/skills/effective-python/ref-01-pythonic-thinking.md +202 -0
- package/skills/effective-python/ref-02-lists-and-dicts.md +146 -0
- package/skills/effective-python/ref-03-functions.md +186 -0
- package/skills/effective-python/ref-04-comprehensions-generators.md +211 -0
- package/skills/effective-python/ref-05-classes-interfaces.md +188 -0
- package/skills/effective-python/ref-06-metaclasses-attributes.md +209 -0
- package/skills/effective-python/ref-07-concurrency.md +213 -0
- package/skills/effective-python/ref-08-robustness-performance.md +248 -0
- package/skills/effective-python/ref-09-testing-debugging.md +253 -0
- package/skills/effective-python/ref-10-collaboration.md +175 -0
- package/skills/effective-python/references/api_reference.md +218 -0
- package/skills/effective-python/references/practices-catalog.md +483 -0
- package/skills/effective-python/references/review-checklist.md +190 -0
- package/skills/effective-python/scripts/lint.py +173 -0
- package/skills/effective-typescript/SKILL.md +262 -0
- package/skills/effective-typescript/audit.json +29 -0
- package/skills/effective-typescript/evals/evals.json +37 -0
- package/skills/effective-typescript/evals/results.json +13 -0
- package/skills/effective-typescript/examples/after.md +70 -0
- package/skills/effective-typescript/examples/before.md +47 -0
- package/skills/effective-typescript/references/api_reference.md +118 -0
- package/skills/effective-typescript/references/practices-catalog.md +371 -0
- package/skills/effective-typescript/scripts/review.py +169 -0
- package/skills/kotlin-in-action/SKILL.md +261 -0
- package/skills/kotlin-in-action/assets/example_asset.txt +1 -0
- package/skills/kotlin-in-action/evals/evals.json +43 -0
- package/skills/kotlin-in-action/evals/results.json +13 -0
- package/skills/kotlin-in-action/examples/after.md +53 -0
- package/skills/kotlin-in-action/examples/before.md +39 -0
- package/skills/kotlin-in-action/references/api_reference.md +1 -0
- package/skills/kotlin-in-action/references/practices-catalog.md +436 -0
- package/skills/kotlin-in-action/references/review-checklist.md +204 -0
- package/skills/kotlin-in-action/scripts/example.py +1 -0
- package/skills/kotlin-in-action/scripts/setup_detekt.py +224 -0
- package/skills/lean-startup/SKILL.md +160 -0
- package/skills/lean-startup/assets/example_asset.txt +1 -0
- package/skills/lean-startup/evals/evals.json +43 -0
- package/skills/lean-startup/evals/results.json +13 -0
- package/skills/lean-startup/examples/after.md +80 -0
- package/skills/lean-startup/examples/before.md +34 -0
- package/skills/lean-startup/references/api_reference.md +319 -0
- package/skills/lean-startup/references/review-checklist.md +137 -0
- package/skills/lean-startup/scripts/example.py +1 -0
- package/skills/lean-startup/scripts/new_experiment.py +286 -0
- package/skills/microservices-patterns/SKILL.md +384 -0
- package/skills/microservices-patterns/evals/evals.json +45 -0
- package/skills/microservices-patterns/evals/results.json +13 -0
- package/skills/microservices-patterns/examples/after.md +69 -0
- package/skills/microservices-patterns/examples/before.md +40 -0
- package/skills/microservices-patterns/references/patterns-catalog.md +391 -0
- package/skills/microservices-patterns/references/review-checklist.md +169 -0
- package/skills/microservices-patterns/scripts/new_service.py +583 -0
- package/skills/programming-with-rust/SKILL.md +209 -0
- package/skills/programming-with-rust/evals/evals.json +37 -0
- package/skills/programming-with-rust/evals/results.json +13 -0
- package/skills/programming-with-rust/examples/after.md +107 -0
- package/skills/programming-with-rust/examples/before.md +59 -0
- package/skills/programming-with-rust/references/api_reference.md +152 -0
- package/skills/programming-with-rust/references/practices-catalog.md +335 -0
- package/skills/programming-with-rust/scripts/review.py +142 -0
- package/skills/refactoring-ui/SKILL.md +362 -0
- package/skills/refactoring-ui/assets/example_asset.txt +1 -0
- package/skills/refactoring-ui/evals/evals.json +45 -0
- package/skills/refactoring-ui/evals/results.json +13 -0
- package/skills/refactoring-ui/examples/after.md +85 -0
- package/skills/refactoring-ui/examples/before.md +58 -0
- package/skills/refactoring-ui/references/api_reference.md +355 -0
- package/skills/refactoring-ui/references/review-checklist.md +114 -0
- package/skills/refactoring-ui/scripts/audit_css.py +250 -0
- package/skills/refactoring-ui/scripts/example.py +1 -0
- package/skills/rust-in-action/SKILL.md +350 -0
- package/skills/rust-in-action/evals/evals.json +38 -0
- package/skills/rust-in-action/evals/results.json +13 -0
- package/skills/rust-in-action/examples/after.md +156 -0
- package/skills/rust-in-action/examples/before.md +56 -0
- package/skills/rust-in-action/references/practices-catalog.md +346 -0
- package/skills/rust-in-action/scripts/review.py +147 -0
- package/skills/skill-router/SKILL.md +186 -0
- package/skills/skill-router/evals/evals.json +38 -0
- package/skills/skill-router/evals/results.json +13 -0
- package/skills/skill-router/examples/after.md +63 -0
- package/skills/skill-router/examples/before.md +39 -0
- package/skills/skill-router/references/api_reference.md +24 -0
- package/skills/skill-router/references/routing-heuristics.md +89 -0
- package/skills/skill-router/references/skill-catalog.md +174 -0
- package/skills/skill-router/scripts/route.py +266 -0
- package/skills/spring-boot-in-action/SKILL.md +340 -0
- package/skills/spring-boot-in-action/evals/evals.json +39 -0
- package/skills/spring-boot-in-action/evals/results.json +13 -0
- package/skills/spring-boot-in-action/examples/after.md +185 -0
- package/skills/spring-boot-in-action/examples/before.md +84 -0
- package/skills/spring-boot-in-action/references/practices-catalog.md +403 -0
- package/skills/spring-boot-in-action/scripts/review.py +184 -0
- package/skills/storytelling-with-data/SKILL.md +241 -0
- package/skills/storytelling-with-data/assets/example_asset.txt +1 -0
- package/skills/storytelling-with-data/evals/evals.json +47 -0
- package/skills/storytelling-with-data/evals/results.json +13 -0
- package/skills/storytelling-with-data/examples/after.md +50 -0
- package/skills/storytelling-with-data/examples/before.md +33 -0
- package/skills/storytelling-with-data/references/api_reference.md +379 -0
- package/skills/storytelling-with-data/references/review-checklist.md +111 -0
- package/skills/storytelling-with-data/scripts/chart_review.py +301 -0
- package/skills/storytelling-with-data/scripts/example.py +1 -0
- package/skills/system-design-interview/SKILL.md +233 -0
- package/skills/system-design-interview/assets/example_asset.txt +1 -0
- package/skills/system-design-interview/evals/evals.json +46 -0
- package/skills/system-design-interview/evals/results.json +13 -0
- package/skills/system-design-interview/examples/after.md +94 -0
- package/skills/system-design-interview/examples/before.md +27 -0
- package/skills/system-design-interview/references/api_reference.md +582 -0
- package/skills/system-design-interview/references/review-checklist.md +201 -0
- package/skills/system-design-interview/scripts/example.py +1 -0
- package/skills/system-design-interview/scripts/new_design.py +421 -0
- package/skills/using-asyncio-python/SKILL.md +290 -0
- package/skills/using-asyncio-python/assets/example_asset.txt +1 -0
- package/skills/using-asyncio-python/evals/evals.json +43 -0
- package/skills/using-asyncio-python/evals/results.json +13 -0
- package/skills/using-asyncio-python/examples/after.md +68 -0
- package/skills/using-asyncio-python/examples/before.md +39 -0
- package/skills/using-asyncio-python/references/api_reference.md +267 -0
- package/skills/using-asyncio-python/references/review-checklist.md +149 -0
- package/skills/using-asyncio-python/scripts/check_blocking.py +270 -0
- package/skills/using-asyncio-python/scripts/example.py +1 -0
- package/skills/web-scraping-python/SKILL.md +280 -0
- package/skills/web-scraping-python/assets/example_asset.txt +1 -0
- package/skills/web-scraping-python/evals/evals.json +46 -0
- package/skills/web-scraping-python/evals/results.json +13 -0
- package/skills/web-scraping-python/examples/after.md +109 -0
- package/skills/web-scraping-python/examples/before.md +40 -0
- package/skills/web-scraping-python/references/api_reference.md +393 -0
- package/skills/web-scraping-python/references/review-checklist.md +163 -0
- package/skills/web-scraping-python/scripts/example.py +1 -0
- package/skills/web-scraping-python/scripts/new_scraper.py +231 -0
- package/skills/writing-plans/audit.json +34 -0
- package/tests/agent-detector.test.js +83 -0
- package/tests/corrections.test.js +245 -0
- package/tests/doctor/hook-installer.test.js +72 -0
- package/tests/doctor/usage-tracker.test.js +140 -0
- package/tests/engine/benchmark-eval.test.js +31 -0
- package/tests/engine/bm25-index.test.js +85 -0
- package/tests/engine/capture-command.test.js +35 -0
- package/tests/engine/capture.test.js +17 -0
- package/tests/engine/graph-augmented-search.test.js +107 -0
- package/tests/engine/graph-injector.test.js +44 -0
- package/tests/engine/graph.test.js +216 -0
- package/tests/engine/hybrid-searcher.test.js +74 -0
- package/tests/engine/indexer-bm25.test.js +37 -0
- package/tests/engine/mcp-tools.test.js +73 -0
- package/tests/engine/project-initializer-mcp.test.js +99 -0
- package/tests/engine/query-expander.test.js +36 -0
- package/tests/engine/reranker.test.js +51 -0
- package/tests/engine/rrf.test.js +49 -0
- package/tests/engine/srag-prefix.test.js +47 -0
- package/tests/instinct-block.test.js +23 -0
- package/tests/mcp-config-writer.test.js +60 -0
- package/tests/project-initializer-new-agents.test.js +48 -0
- package/tests/rules/rules-manager.test.js +230 -0
- package/tests/well-known-builder.test.js +40 -0
- package/tests/wizard/integration-detector.test.js +31 -0
- package/tests/wizard/project-detector.test.js +51 -0
- package/tests/wizard/prompt-session.test.js +61 -0
- package/tests/wizard/prompt.test.js +16 -0
- package/tests/wizard/registry-embeddings.test.js +35 -0
- package/tests/wizard/skill-recommender.test.js +34 -0
- package/tests/wizard/slot-count.test.js +25 -0
- package/vercel.json +21 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export class BookLibAIFeatures {
|
|
5
|
+
constructor(projectRoot = process.cwd()) {
|
|
6
|
+
this.projectRoot = projectRoot;
|
|
7
|
+
this.sessionsDir = path.join(projectRoot, '.booklib/sessions');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async generateSessionSummary(sessionName, modelProvider = 'claude') {
|
|
11
|
+
const sessionPath = path.join(this.sessionsDir, `${sessionName}.md`);
|
|
12
|
+
if (!fs.existsSync(sessionPath)) {
|
|
13
|
+
return { error: 'Session not found' };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const content = fs.readFileSync(sessionPath, 'utf8');
|
|
17
|
+
const data = this._parseSession(content);
|
|
18
|
+
|
|
19
|
+
const summary = this._generateMockSummary(data);
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
session: sessionName,
|
|
23
|
+
summary,
|
|
24
|
+
tokens_estimated: 100,
|
|
25
|
+
model: modelProvider
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
recommendSkills(goal, availableSkills = []) {
|
|
30
|
+
const skillKeywords = {
|
|
31
|
+
'effective-typescript': ['typescript', 'type', 'interface', 'generic', 'null safety'],
|
|
32
|
+
'clean-code-reviewer': ['code quality', 'refactor', 'clean', 'maintainability'],
|
|
33
|
+
'microservices-patterns': ['service', 'distributed', 'saga', 'cqrs', 'message queue'],
|
|
34
|
+
'effective-python': ['python', 'async', 'decorator', 'context manager'],
|
|
35
|
+
'design-patterns': ['pattern', 'factory', 'observer', 'singleton', 'decorator'],
|
|
36
|
+
'domain-driven-design': ['domain', 'entity', 'aggregate', 'ubiquitous language'],
|
|
37
|
+
'system-design-interview': ['scale', 'load balance', 'cache', 'database sharding'],
|
|
38
|
+
'refactoring-ui': ['ui', 'design', 'layout', 'typography', 'color palette'],
|
|
39
|
+
'data-pipelines': ['data', 'etl', 'pipeline', 'orchestration', 'warehouse'],
|
|
40
|
+
'animation-at-work': ['animation', 'transition', 'motion', 'keyframe']
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const lowerGoal = goal.toLowerCase();
|
|
44
|
+
const recommendations = {};
|
|
45
|
+
|
|
46
|
+
for (const [skill, keywords] of Object.entries(skillKeywords)) {
|
|
47
|
+
const matchCount = keywords.filter(kw => lowerGoal.includes(kw)).length;
|
|
48
|
+
if (matchCount > 0) {
|
|
49
|
+
recommendations[skill] = matchCount;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sorted = Object.entries(recommendations)
|
|
54
|
+
.sort((a, b) => b[1] - a[1])
|
|
55
|
+
.slice(0, 5)
|
|
56
|
+
.map(([skill, score]) => ({
|
|
57
|
+
skill,
|
|
58
|
+
confidence: Math.min(100, score * 20),
|
|
59
|
+
reason: this._getSkillReason(skill, goal)
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
return { recommendations: sorted };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getExtensionData() {
|
|
66
|
+
const sessions = this._loadAllSessions();
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
version: '1.0.0',
|
|
70
|
+
sessions: sessions.map(s => ({
|
|
71
|
+
id: s.session_id,
|
|
72
|
+
name: s.session_id,
|
|
73
|
+
goal: s.goal,
|
|
74
|
+
branch: s.branch,
|
|
75
|
+
timestamp: s.timestamp
|
|
76
|
+
})),
|
|
77
|
+
status_bar_data: sessions[0] ? {
|
|
78
|
+
current_session: sessions[0].session_id,
|
|
79
|
+
branch: sessions[0].branch,
|
|
80
|
+
pending_tasks: (sessions[0].pending_tasks || '').split('\n').length
|
|
81
|
+
} : null,
|
|
82
|
+
quick_actions: [
|
|
83
|
+
{ command: 'booklib.saveSession', title: 'Save Session', icon: 'save' },
|
|
84
|
+
{ command: 'booklib.recoverSession', title: 'Recover Session', icon: 'refresh' },
|
|
85
|
+
{ command: 'booklib.viewTasks', title: 'View Tasks', icon: 'list' },
|
|
86
|
+
{ command: 'booklib.switchSession', title: 'Switch Session', icon: 'branch' }
|
|
87
|
+
]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getGitHubIntegrationData(sessionName) {
|
|
92
|
+
const sessionPath = path.join(this.sessionsDir, `${sessionName}.md`);
|
|
93
|
+
if (!fs.existsSync(sessionPath)) {
|
|
94
|
+
return { error: 'Session not found' };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const content = fs.readFileSync(sessionPath, 'utf8');
|
|
98
|
+
const data = this._parseSession(content);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
wiki_page: {
|
|
102
|
+
title: `Session: ${data.session_id}`,
|
|
103
|
+
content: this._generateWikiMarkdown(data),
|
|
104
|
+
slug: `session-${data.session_id}`
|
|
105
|
+
},
|
|
106
|
+
issues_to_create: (data.pending_tasks || '')
|
|
107
|
+
.split('\n')
|
|
108
|
+
.filter(t => t.trim())
|
|
109
|
+
.map((task, i) => ({
|
|
110
|
+
title: task.trim(),
|
|
111
|
+
body: `From session: ${data.session_id}\n\nContext: ${data.goal}`,
|
|
112
|
+
labels: ['from-session', ...data.skills],
|
|
113
|
+
priority: i === 0 ? 'high' : 'medium'
|
|
114
|
+
})),
|
|
115
|
+
pr_context: {
|
|
116
|
+
session: data.session_id,
|
|
117
|
+
goal: data.goal,
|
|
118
|
+
branch: data.branch,
|
|
119
|
+
summary: `Working on: ${data.goal}`
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
getSlackIntegrationData(sessionName) {
|
|
125
|
+
const sessionPath = path.join(this.sessionsDir, `${sessionName}.md`);
|
|
126
|
+
if (!fs.existsSync(sessionPath)) {
|
|
127
|
+
return { error: 'Session not found' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const content = fs.readFileSync(sessionPath, 'utf8');
|
|
131
|
+
const data = this._parseSession(content);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
webhook_enabled: !!process.env.SLACK_WEBHOOK,
|
|
135
|
+
message: {
|
|
136
|
+
text: `📝 Session Update: ${data.session_id}`,
|
|
137
|
+
blocks: [
|
|
138
|
+
{
|
|
139
|
+
type: 'header',
|
|
140
|
+
text: {
|
|
141
|
+
type: 'plain_text',
|
|
142
|
+
text: `📝 ${data.session_id}`
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: 'section',
|
|
147
|
+
fields: [
|
|
148
|
+
{
|
|
149
|
+
type: 'mrkdwn',
|
|
150
|
+
text: `*Goal:*\n${data.goal}`
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
type: 'mrkdwn',
|
|
154
|
+
text: `*Progress:*\n${data.progress.substring(0, 100)}...`
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: 'section',
|
|
160
|
+
text: {
|
|
161
|
+
type: 'mrkdwn',
|
|
162
|
+
text: `*Skills:* ${data.skills.join(', ')}`
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
notification_types: {
|
|
168
|
+
on_save: true,
|
|
169
|
+
on_complete: true,
|
|
170
|
+
team_mention: data.pending_tasks ? '@team' : null,
|
|
171
|
+
branch_mention: `#${data.branch}`
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_parseSession(content) {
|
|
177
|
+
const extract = (tag) => {
|
|
178
|
+
const regex = new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`);
|
|
179
|
+
const match = content.match(regex);
|
|
180
|
+
return match ? match[1].trim() : '';
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const skillRegex = /<skill id="([^"]+)"/g;
|
|
184
|
+
const skills = [];
|
|
185
|
+
let match;
|
|
186
|
+
while ((match = skillRegex.exec(content)) !== null) {
|
|
187
|
+
skills.push(match[1]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
session_id: extract('session_id'),
|
|
192
|
+
timestamp: extract('timestamp'),
|
|
193
|
+
goal: extract('goal'),
|
|
194
|
+
progress: extract('progress'),
|
|
195
|
+
pending_tasks: extract('pending_tasks'),
|
|
196
|
+
branch: extract('branch'),
|
|
197
|
+
skills
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_generateMockSummary(data) {
|
|
202
|
+
const parts = [
|
|
203
|
+
`Building: ${data.goal.split(' ').slice(0, 3).join(' ')}`,
|
|
204
|
+
`Progress: ${data.progress.split('\n')[0]}`,
|
|
205
|
+
data.pending_tasks ? `Next: ${data.pending_tasks.split('\n')[0]}` : 'Ready for review'
|
|
206
|
+
];
|
|
207
|
+
return parts.join('. ');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
_getSkillReason(skill, goal) {
|
|
211
|
+
const reasons = {
|
|
212
|
+
'effective-typescript': 'Goal mentions TypeScript or type safety concerns',
|
|
213
|
+
'clean-code-reviewer': 'Goal includes code quality or refactoring',
|
|
214
|
+
'microservices-patterns': 'Goal mentions distributed systems or services',
|
|
215
|
+
'system-design-interview': 'Goal includes scaling or architecture',
|
|
216
|
+
'design-patterns': 'Goal mentions patterns or architecture',
|
|
217
|
+
'refactoring-ui': 'Goal includes UI/design work'
|
|
218
|
+
};
|
|
219
|
+
return reasons[skill] || 'Relevant to development goals';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
_generateWikiMarkdown(data) {
|
|
223
|
+
return `# Session: ${data.session_id}
|
|
224
|
+
|
|
225
|
+
## Goal
|
|
226
|
+
${data.goal}
|
|
227
|
+
|
|
228
|
+
## Progress
|
|
229
|
+
${data.progress}
|
|
230
|
+
|
|
231
|
+
## Next Tasks
|
|
232
|
+
${data.pending_tasks || 'No tasks defined'}
|
|
233
|
+
|
|
234
|
+
## Skills
|
|
235
|
+
- ${data.skills.join('\n- ')}
|
|
236
|
+
|
|
237
|
+
## Metadata
|
|
238
|
+
- **Branch:** ${data.branch}
|
|
239
|
+
- **Created:** ${new Date(data.timestamp).toLocaleString()}
|
|
240
|
+
`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
_loadAllSessions() {
|
|
244
|
+
if (!fs.existsSync(this.sessionsDir)) return [];
|
|
245
|
+
|
|
246
|
+
return fs.readdirSync(this.sessionsDir)
|
|
247
|
+
.filter(f => f.endsWith('.md') && !f.startsWith('_'))
|
|
248
|
+
.map(file => {
|
|
249
|
+
const content = fs.readFileSync(path.join(this.sessionsDir, file), 'utf8');
|
|
250
|
+
return this._parseSession(content);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { parseSkillFile } from './parser.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handles systematic audits of content against BookLib skills.
|
|
7
|
+
*/
|
|
8
|
+
export class BookLibAuditor {
|
|
9
|
+
constructor() {}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Performs an audit of a file against a specific skill.
|
|
13
|
+
*/
|
|
14
|
+
async audit(skillPath, targetFilePath) {
|
|
15
|
+
if (!fs.existsSync(skillPath)) {
|
|
16
|
+
throw new Error(`Skill not found at ${skillPath}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!fs.existsSync(targetFilePath)) {
|
|
20
|
+
throw new Error(`Target file not found at ${targetFilePath}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const skillMdPath = fs.statSync(skillPath).isDirectory()
|
|
24
|
+
? path.join(skillPath, 'SKILL.md')
|
|
25
|
+
: skillPath;
|
|
26
|
+
|
|
27
|
+
const skillContent = fs.readFileSync(skillMdPath, 'utf8');
|
|
28
|
+
const chunks = parseSkillFile(skillContent, skillMdPath);
|
|
29
|
+
|
|
30
|
+
const targetContent = fs.readFileSync(targetFilePath, 'utf8');
|
|
31
|
+
const targetLines = targetContent.split('\n');
|
|
32
|
+
|
|
33
|
+
// Support both legacy and universal tags
|
|
34
|
+
const principles = chunks.find(c => c.metadata.type === 'framework' || c.metadata.type === 'core_principles')?.text || 'See metadata';
|
|
35
|
+
const pitfalls = chunks.find(c => c.metadata.type === 'pitfalls' || c.metadata.type === 'anti_patterns')?.text || 'See metadata';
|
|
36
|
+
|
|
37
|
+
// 1. Static Analysis (Domain-specific low-hanging fruit)
|
|
38
|
+
const auditJsonPath = path.join(path.dirname(skillMdPath), 'audit.json');
|
|
39
|
+
let staticFindings = '';
|
|
40
|
+
if (fs.existsSync(auditJsonPath)) {
|
|
41
|
+
const { rules } = JSON.parse(fs.readFileSync(auditJsonPath, 'utf8'));
|
|
42
|
+
const findings = [];
|
|
43
|
+
|
|
44
|
+
for (const rule of rules) {
|
|
45
|
+
const regex = new RegExp(rule.pattern, 'g');
|
|
46
|
+
targetLines.forEach((line, index) => {
|
|
47
|
+
if (regex.test(line)) {
|
|
48
|
+
findings.push(`- ${rule.severity} **${rule.id}**: ${rule.message} (Line ${index + 1}: \`${line.trim()}\`)`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (findings.length > 0) {
|
|
54
|
+
staticFindings = `### 🤖 Automated Detections\n${findings.join('\n')}\n`;
|
|
55
|
+
} else {
|
|
56
|
+
staticFindings = `### 🤖 Automated Detections\n*No common pitfalls detected automatically.*\n`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return `
|
|
61
|
+
# BookLib Active Audit: ${path.basename(skillPath)}
|
|
62
|
+
**Target Content**: \`${targetFilePath}\`
|
|
63
|
+
|
|
64
|
+
## 🛠 Instructions for the Agent
|
|
65
|
+
You are tasked with a systematic audit of the provided content against expert frameworks.
|
|
66
|
+
Follow these steps:
|
|
67
|
+
1. **Analyze**: Read the target content carefully.
|
|
68
|
+
2. **Evaluate**: Compare the work against the "Expert Framework" and "Pitfalls to Avoid".
|
|
69
|
+
3. **Report**: Document where the content aligns or violates these expert standards.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 📜 Expert Framework
|
|
74
|
+
${principles}
|
|
75
|
+
|
|
76
|
+
## 🚫 Pitfalls to Avoid
|
|
77
|
+
${pitfalls}
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 🔍 Audit Findings
|
|
82
|
+
|
|
83
|
+
${staticFindings}
|
|
84
|
+
|
|
85
|
+
### 🔴 Critical Misalignments (Manual)
|
|
86
|
+
* (List major deviations from the expert framework)
|
|
87
|
+
|
|
88
|
+
### 🟡 Opportunities for Improvement
|
|
89
|
+
* (List areas where the content can be sharpened)
|
|
90
|
+
|
|
91
|
+
### 🟢 Strengths
|
|
92
|
+
* (Acknowledge where the content perfectly embodies the principles)
|
|
93
|
+
|
|
94
|
+
### 🚀 Recommended Refinement
|
|
95
|
+
\`\`\`
|
|
96
|
+
// Provide a surgical improvement or rewritten section here
|
|
97
|
+
\`\`\`
|
|
98
|
+
|
|
99
|
+
> **Note**: This audit was generated by the BookLib Universal Engine.
|
|
100
|
+
> Always cite the specific expert items in your findings.
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
const K1 = 1.5;
|
|
4
|
+
const B = 0.75;
|
|
5
|
+
const MIN_TOKEN_LENGTH = 2;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tokenizes text into lowercase alphanumeric terms of at least 2 characters.
|
|
9
|
+
* @param {string} text
|
|
10
|
+
* @returns {string[]}
|
|
11
|
+
*/
|
|
12
|
+
function tokenize(text) {
|
|
13
|
+
return text
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.split(/[^a-z0-9]+/)
|
|
16
|
+
.filter(token => token.length >= MIN_TOKEN_LENGTH);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Counts term frequencies in a token array.
|
|
21
|
+
* @param {string[]} tokens
|
|
22
|
+
* @returns {Map<string, number>}
|
|
23
|
+
*/
|
|
24
|
+
function countTermFrequencies(tokens) {
|
|
25
|
+
const freq = new Map();
|
|
26
|
+
for (const token of tokens) {
|
|
27
|
+
freq.set(token, (freq.get(token) ?? 0) + 1);
|
|
28
|
+
}
|
|
29
|
+
return freq;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Computes BM25 IDF for a term.
|
|
34
|
+
* Formula: log((N - df + 0.5) / (df + 0.5) + 1)
|
|
35
|
+
* @param {number} docCount - Total number of documents
|
|
36
|
+
* @param {number} docFrequency - Number of documents containing the term
|
|
37
|
+
* @returns {number}
|
|
38
|
+
*/
|
|
39
|
+
function computeIdf(docCount, docFrequency) {
|
|
40
|
+
return Math.log((docCount - docFrequency + 0.5) / (docFrequency + 0.5) + 1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Scores a single document against a set of query terms using BM25.
|
|
45
|
+
* @param {Object} doc - Document with freq map and len
|
|
46
|
+
* @param {string[]} queryTerms
|
|
47
|
+
* @param {Map<string, number>} df - Document frequency map
|
|
48
|
+
* @param {number} docCount
|
|
49
|
+
* @param {number} avgDocLen
|
|
50
|
+
* @returns {number}
|
|
51
|
+
*/
|
|
52
|
+
function scoreBm25(doc, queryTerms, df, docCount, avgDocLen) {
|
|
53
|
+
let score = 0;
|
|
54
|
+
for (const term of queryTerms) {
|
|
55
|
+
const termFreq = doc.freq[term] ?? 0;
|
|
56
|
+
if (termFreq === 0) continue;
|
|
57
|
+
|
|
58
|
+
const docFrequency = df[term] ?? 0;
|
|
59
|
+
const idf = computeIdf(docCount, docFrequency);
|
|
60
|
+
const normalizedTf =
|
|
61
|
+
(termFreq * (K1 + 1)) /
|
|
62
|
+
(termFreq + K1 * (1 - B + B * (doc.len / avgDocLen)));
|
|
63
|
+
|
|
64
|
+
score += idf * normalizedTf;
|
|
65
|
+
}
|
|
66
|
+
return score;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* BM25 full-text search index.
|
|
71
|
+
*
|
|
72
|
+
* Supports incremental document addition, JSON persistence, and
|
|
73
|
+
* Robertson BM25 ranking.
|
|
74
|
+
*/
|
|
75
|
+
export class BM25Index {
|
|
76
|
+
constructor() {
|
|
77
|
+
this._docs = [];
|
|
78
|
+
this._df = {};
|
|
79
|
+
this._avgLen = 0;
|
|
80
|
+
this._totalLen = 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Builds the index from scratch from an array of chunks.
|
|
85
|
+
* @param {{ text: string, metadata: object }[]} chunks
|
|
86
|
+
*/
|
|
87
|
+
build(chunks) {
|
|
88
|
+
this._docs = [];
|
|
89
|
+
this._df = {};
|
|
90
|
+
this._avgLen = 0;
|
|
91
|
+
this._totalLen = 0;
|
|
92
|
+
|
|
93
|
+
for (const chunk of chunks) {
|
|
94
|
+
this._appendDoc(chunk);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Incrementally adds one document to the index, updating avgLen and df.
|
|
100
|
+
* @param {{ text: string, metadata: object }} chunk
|
|
101
|
+
*/
|
|
102
|
+
add(chunk) {
|
|
103
|
+
this._appendDoc(chunk);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Searches the index and returns top-K results sorted by score descending.
|
|
108
|
+
* @param {string} query
|
|
109
|
+
* @param {number} topK
|
|
110
|
+
* @returns {{ score: number, text: string, metadata: object }[]}
|
|
111
|
+
*/
|
|
112
|
+
search(query, topK = 20) {
|
|
113
|
+
if (this._docs.length === 0) return [];
|
|
114
|
+
|
|
115
|
+
const queryTerms = tokenize(query);
|
|
116
|
+
if (queryTerms.length === 0) return [];
|
|
117
|
+
|
|
118
|
+
const scoredDocs = this._docs.map(doc => ({
|
|
119
|
+
score: scoreBm25(doc, queryTerms, this._df, this._docs.length, this._avgLen),
|
|
120
|
+
text: doc.text,
|
|
121
|
+
metadata: doc.metadata,
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
const matchingDocs = scoredDocs.filter(r => r.score > 0);
|
|
125
|
+
return matchingDocs
|
|
126
|
+
.sort((a, b) => b.score - a.score)
|
|
127
|
+
.slice(0, topK);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Persists the index to a JSON file.
|
|
132
|
+
* @param {string} filePath
|
|
133
|
+
*/
|
|
134
|
+
save(filePath) {
|
|
135
|
+
const serialized = JSON.stringify({
|
|
136
|
+
docs: this._docs,
|
|
137
|
+
df: this._df,
|
|
138
|
+
avgLen: this._avgLen,
|
|
139
|
+
});
|
|
140
|
+
fs.writeFileSync(filePath, serialized, 'utf8');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Restores a BM25Index instance from a JSON file.
|
|
145
|
+
* @param {string} filePath
|
|
146
|
+
* @returns {BM25Index}
|
|
147
|
+
*/
|
|
148
|
+
static load(filePath) {
|
|
149
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
150
|
+
const { docs, df, avgLen } = JSON.parse(raw);
|
|
151
|
+
const idx = new BM25Index();
|
|
152
|
+
idx._docs = docs;
|
|
153
|
+
idx._df = df;
|
|
154
|
+
idx._avgLen = avgLen;
|
|
155
|
+
idx._totalLen = docs.reduce((sum, d) => sum + d.len, 0);
|
|
156
|
+
return idx;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Internal: adds one document and updates df and avgLen.
|
|
161
|
+
* @param {{ text: string, metadata: object }} chunk
|
|
162
|
+
*/
|
|
163
|
+
_appendDoc(chunk) {
|
|
164
|
+
const tokens = tokenize(chunk.text);
|
|
165
|
+
const freqMap = countTermFrequencies(tokens);
|
|
166
|
+
const freqObj = Object.fromEntries(freqMap);
|
|
167
|
+
|
|
168
|
+
for (const term of freqMap.keys()) {
|
|
169
|
+
this._df[term] = (this._df[term] ?? 0) + 1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const len = tokens.length;
|
|
173
|
+
this._docs.push({ text: chunk.text, metadata: chunk.metadata, freq: freqObj, len });
|
|
174
|
+
|
|
175
|
+
this._totalLen += len;
|
|
176
|
+
this._avgLen = this._totalLen / this._docs.length;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// lib/engine/capture.js
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { writeFileSync, readFileSync, unlinkSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
// ── AI prompt builders (exported for testing) ─────────────────────────────────
|
|
8
|
+
|
|
9
|
+
/** Returns the prompt used to ask Claude to structure raw dictation into a clean note. */
|
|
10
|
+
export function buildDictatePrompt(rawText) {
|
|
11
|
+
return `You are a knowledge management assistant. Structure the following raw notes into a clean markdown knowledge note.
|
|
12
|
+
|
|
13
|
+
Raw input:
|
|
14
|
+
${rawText}
|
|
15
|
+
|
|
16
|
+
Return ONLY valid YAML frontmatter + markdown. The frontmatter must include:
|
|
17
|
+
- title: (concise, descriptive title extracted or inferred from the content)
|
|
18
|
+
- tags: (2-5 relevant tags as a YAML list)
|
|
19
|
+
- type: note
|
|
20
|
+
|
|
21
|
+
Fix grammar and typos. Preserve all meaning. Do not add information not present in the input.
|
|
22
|
+
Return nothing except the markdown document starting with ---.`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Returns the prompt used to summarize a conversation transcript into a structured note. */
|
|
26
|
+
export function buildSummarizePrompt(transcript, title = '') {
|
|
27
|
+
const titleLine = title ? `Title: ${title}\n\n` : '';
|
|
28
|
+
return `You are a knowledge management assistant. Summarize this AI conversation into a structured knowledge note.
|
|
29
|
+
|
|
30
|
+
${titleLine}Conversation:
|
|
31
|
+
${transcript}
|
|
32
|
+
|
|
33
|
+
Return ONLY a markdown document starting with ---. Include this frontmatter:
|
|
34
|
+
- title: (descriptive title${title ? ` — use "${title}" as basis` : ''})
|
|
35
|
+
- tags: (2-5 relevant tags)
|
|
36
|
+
- type: note
|
|
37
|
+
|
|
38
|
+
And these sections in the body:
|
|
39
|
+
## Key Decisions
|
|
40
|
+
(bullet list of decisions made)
|
|
41
|
+
|
|
42
|
+
## Findings
|
|
43
|
+
(bullet list of key findings or conclusions)
|
|
44
|
+
|
|
45
|
+
## Context
|
|
46
|
+
(1-2 sentences of background)
|
|
47
|
+
|
|
48
|
+
Then append the full transcript inside a details element:
|
|
49
|
+
<details>
|
|
50
|
+
<summary>Full conversation transcript</summary>
|
|
51
|
+
|
|
52
|
+
${transcript}
|
|
53
|
+
|
|
54
|
+
</details>`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── AI call (requires ANTHROPIC_API_KEY env var) ──────────────────────────────
|
|
58
|
+
|
|
59
|
+
/** Calls claude-haiku via the Anthropic Messages API. Requires ANTHROPIC_API_KEY. */
|
|
60
|
+
export async function callAnthropicAPI(prompt) {
|
|
61
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
62
|
+
if (!apiKey) {
|
|
63
|
+
throw new Error('ANTHROPIC_API_KEY not set. Export it or add to .env.local');
|
|
64
|
+
}
|
|
65
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
'x-api-key': apiKey,
|
|
70
|
+
'anthropic-version': '2023-06-01',
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
model: 'claude-haiku-4-5-20251001',
|
|
74
|
+
max_tokens: 2048,
|
|
75
|
+
messages: [{ role: 'user', content: prompt }],
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
const body = await response.text();
|
|
80
|
+
throw new Error(`Anthropic API ${response.status}: ${body}`);
|
|
81
|
+
}
|
|
82
|
+
const data = await response.json();
|
|
83
|
+
return data.content[0].text;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Input helpers ─────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/** Opens $EDITOR (or vi) with optional initial content. Returns edited content. */
|
|
89
|
+
export function openEditor(initialContent = '') {
|
|
90
|
+
const tmpFile = join(tmpdir(), `booklib-edit-${Date.now()}.md`);
|
|
91
|
+
writeFileSync(tmpFile, initialContent, 'utf8');
|
|
92
|
+
const editor = process.env.EDITOR ?? 'vi';
|
|
93
|
+
spawnSync(editor, [tmpFile], { stdio: 'inherit' });
|
|
94
|
+
const content = readFileSync(tmpFile, 'utf8');
|
|
95
|
+
unlinkSync(tmpFile);
|
|
96
|
+
return content.trim();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Reads all of stdin when piped. Returns empty string when stdin is a TTY. */
|
|
100
|
+
export async function readStdin() {
|
|
101
|
+
if (process.stdin.isTTY) return '';
|
|
102
|
+
return new Promise(resolve => {
|
|
103
|
+
let data = '';
|
|
104
|
+
process.stdin.setEncoding('utf8');
|
|
105
|
+
process.stdin.on('data', chunk => { data += chunk; });
|
|
106
|
+
process.stdin.on('end', () => resolve(data.trim()));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Prompts user to type input interactively until Ctrl+D. */
|
|
111
|
+
export async function readInteractive(prompt = 'Type your note (Ctrl+D when done):') {
|
|
112
|
+
process.stdout.write(prompt + '\n');
|
|
113
|
+
return new Promise(resolve => {
|
|
114
|
+
let data = '';
|
|
115
|
+
process.stdin.setEncoding('utf8');
|
|
116
|
+
process.stdin.resume();
|
|
117
|
+
process.stdin.on('data', chunk => { data += chunk; });
|
|
118
|
+
process.stdin.on('end', () => resolve(data.trim()));
|
|
119
|
+
});
|
|
120
|
+
}
|