@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,574 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextBuilder — task-aware cross-skill knowledge synthesizer.
|
|
3
|
+
*
|
|
4
|
+
* Given a task description:
|
|
5
|
+
* 1. Decomposes the task into search sub-queries
|
|
6
|
+
* 2. Searches all indexed skills for each sub-query
|
|
7
|
+
* 3. Groups results by skill — keeps the best chunk per skill
|
|
8
|
+
* 4. Within each matched chunk, extracts the most relevant paragraph
|
|
9
|
+
* and labels it with book title + section (where in the skill it came from)
|
|
10
|
+
* 5. Resolves conflicts using ConflictResolver:
|
|
11
|
+
* - Auto-resolved: shown with full prose rationale, non-blocking
|
|
12
|
+
* - Genuine conflict: user is prompted to choose interactively
|
|
13
|
+
* 6. Compiles a sharp, dense system prompt from all resolved knowledge,
|
|
14
|
+
* each piece cited with its source book and section
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* const builder = new ContextBuilder();
|
|
18
|
+
* const output = await builder.build('implement a Kotlin payment service with async error handling');
|
|
19
|
+
* // pipe-friendly: builder.build(task, { promptOnly: true })
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import * as rl from 'node:readline/promises';
|
|
23
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
24
|
+
import { BookLibSearcher } from './engine/searcher.js';
|
|
25
|
+
import { ConflictResolver } from './conflict-resolver.js';
|
|
26
|
+
import { resolveBookLibPaths } from './paths.js';
|
|
27
|
+
import { buildGraphContext } from './engine/graph-injector.js';
|
|
28
|
+
|
|
29
|
+
// ── Book label map ──────────────────────────────────────────────────────────
|
|
30
|
+
const BOOK_LABELS = {
|
|
31
|
+
'clean-code-reviewer': 'Clean Code — Robert C. Martin',
|
|
32
|
+
'effective-kotlin': 'Effective Kotlin — Marcin Moskała',
|
|
33
|
+
'effective-java': 'Effective Java — Joshua Bloch',
|
|
34
|
+
'effective-python': 'Effective Python — Brett Slatkin',
|
|
35
|
+
'effective-typescript': 'Effective TypeScript — Dan Vanderkam',
|
|
36
|
+
'domain-driven-design': 'Domain-Driven Design — Eric Evans',
|
|
37
|
+
'microservices-patterns': 'Microservices Patterns — Chris Richardson',
|
|
38
|
+
'system-design-interview':'System Design Interview — Alex Xu',
|
|
39
|
+
'data-intensive-patterns':'Designing Data-Intensive Applications — Martin Kleppmann',
|
|
40
|
+
'data-pipelines': 'Data Pipelines Pocket Reference — James Densmore',
|
|
41
|
+
'design-patterns': 'Head First Design Patterns — Freeman & Robson',
|
|
42
|
+
'kotlin-in-action': 'Kotlin in Action — Elizarov & Isakova',
|
|
43
|
+
'programming-with-rust': 'Programming with Rust — Donis Marshall',
|
|
44
|
+
'rust-in-action': 'Rust in Action — Tim McNamara',
|
|
45
|
+
'refactoring-ui': 'Refactoring UI — Wathan & Schoger',
|
|
46
|
+
'storytelling-with-data': 'Storytelling with Data — Cole Knaflic',
|
|
47
|
+
'animation-at-work': 'Animation at Work — Rachel Nabors',
|
|
48
|
+
'spring-boot-in-action': 'Spring Boot in Action — Craig Walls',
|
|
49
|
+
'lean-startup': 'The Lean Startup — Eric Ries',
|
|
50
|
+
'using-asyncio-python': 'Using Asyncio in Python — Caleb Hattingh',
|
|
51
|
+
'web-scraping-python': 'Web Scraping with Python — Ryan Mitchell',
|
|
52
|
+
'skill-router': 'BookLib skill-router',
|
|
53
|
+
// Non-code community skills
|
|
54
|
+
'writing-plans': 'Writing Plans — BookLib Community',
|
|
55
|
+
'writing-skills': 'Writing Skills — BookLib Community',
|
|
56
|
+
'article-writing': 'Article Writing — BookLib Community',
|
|
57
|
+
'strategic-compact': 'Strategic Compact — BookLib Community',
|
|
58
|
+
'product-lens': 'Product Lens — BookLib Community',
|
|
59
|
+
'brand-guidelines': 'Brand Guidelines — BookLib Community',
|
|
60
|
+
'web-design-guidelines': 'Web Design Guidelines — BookLib Community',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Maps stored chunk type/tag to a human-readable section name
|
|
64
|
+
const SECTION_LABELS = {
|
|
65
|
+
'framework': 'core principles',
|
|
66
|
+
'core_principles': 'core principles',
|
|
67
|
+
'pitfalls': 'anti-patterns',
|
|
68
|
+
'anti_patterns':'anti-patterns',
|
|
69
|
+
'case_studies': 'examples',
|
|
70
|
+
'examples': 'examples',
|
|
71
|
+
'summary': 'overview',
|
|
72
|
+
'content': 'guidance',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function bookLabel(skillName) {
|
|
76
|
+
return BOOK_LABELS[skillName] ?? skillName;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function sectionLabel(chunk) {
|
|
80
|
+
const raw = chunk.metadata?.originalTag ?? chunk.metadata?.type ?? 'guidance';
|
|
81
|
+
return SECTION_LABELS[raw] ?? raw;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Split a skill chunk into individual items (principles, rules, anti-patterns).
|
|
86
|
+
*
|
|
87
|
+
* Recognises three item shapes:
|
|
88
|
+
* - Bold-headed bullets: "- **Item Name** — body text"
|
|
89
|
+
* - Numbered items: "1. **Item Name** — body" or "1. Plain body"
|
|
90
|
+
* - Markdown headings: "## § Section Name\nbody paragraphs"
|
|
91
|
+
* - Plain bullets: "- body without a bold header"
|
|
92
|
+
*
|
|
93
|
+
* Returns Array<{ label: string|null, body: string, raw: string }>
|
|
94
|
+
*/
|
|
95
|
+
function extractItems(text) {
|
|
96
|
+
if (!text) return [];
|
|
97
|
+
|
|
98
|
+
// Strip YAML frontmatter block (ECC community skills include it in chunk text)
|
|
99
|
+
text = text.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, '').trim();
|
|
100
|
+
if (!text) return [];
|
|
101
|
+
|
|
102
|
+
const items = [];
|
|
103
|
+
|
|
104
|
+
// Split into blocks at heading or double-newline boundaries
|
|
105
|
+
const blocks = text
|
|
106
|
+
.split(/\n(?=#{1,3} )|(?<=\n)\n(?=\S)|\n{3,}/)
|
|
107
|
+
.map(b => b.trim())
|
|
108
|
+
.filter(b => b.length > 10);
|
|
109
|
+
|
|
110
|
+
for (const block of blocks) {
|
|
111
|
+
// Case 1: Markdown heading block
|
|
112
|
+
const headingMatch = block.match(/^(#{1,3})\s+(.+)\n([\s\S]*)/);
|
|
113
|
+
if (headingMatch) {
|
|
114
|
+
const label = headingMatch[2].replace(/^\*+|\*+$/g, '').trim();
|
|
115
|
+
const body = headingMatch[3].trim();
|
|
116
|
+
items.push({ label, body: body.slice(0, 300), raw: block });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Case 2: Bold-headed bullet list — split each bullet
|
|
121
|
+
if (/^- \*\*/.test(block) || /^\d+\.\s+\*\*/.test(block)) {
|
|
122
|
+
const bulletRe = /^(?:-|\d+\.)\s+\*\*([^*]+)\*\*\s*[—–:-]?\s*([\s\S]*?)(?=\n(?:-|\d+\.)\s+\*\*|\n#{1,3} |$)/gm;
|
|
123
|
+
let m;
|
|
124
|
+
while ((m = bulletRe.exec(block)) !== null) {
|
|
125
|
+
const label = m[1].trim();
|
|
126
|
+
const body = m[2].replace(/\n/g, ' ').trim().slice(0, 250);
|
|
127
|
+
if (label && body) items.push({ label, body, raw: m[0] });
|
|
128
|
+
}
|
|
129
|
+
// If regex matched nothing, fall through to plain bullet handling
|
|
130
|
+
if (items.length > 0) continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Case 3: Plain bullets — each bullet becomes its own item (no label)
|
|
134
|
+
if (/^- /.test(block)) {
|
|
135
|
+
const bullets = block
|
|
136
|
+
.split(/\n(?=- )/)
|
|
137
|
+
.map(b => b.replace(/^- /, '').trim())
|
|
138
|
+
.filter(b => b.length > 15);
|
|
139
|
+
for (const b of bullets) {
|
|
140
|
+
items.push({ label: null, body: b.slice(0, 250), raw: b });
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Case 4: Plain paragraph — skip if it looks like a heading, code fence, or XML
|
|
146
|
+
if (/^#{1,3} /.test(block) || /^```/.test(block) || /^<[a-z]/.test(block)) continue;
|
|
147
|
+
|
|
148
|
+
// Case 4a: Long prose — split into individual sentences so rankItems can score each one.
|
|
149
|
+
// Covers narrative books (strategy, writing, product, legal) that don't use bullet structure.
|
|
150
|
+
if (block.length > 150) {
|
|
151
|
+
const sentences = block
|
|
152
|
+
.split(/(?<=[.!?])\s+(?=[A-Z"'])/)
|
|
153
|
+
.map(s => s.trim())
|
|
154
|
+
.filter(s => s.length > 30 && !/^\[!\[/.test(s) && !/^!\[/.test(s));
|
|
155
|
+
if (sentences.length >= 2) {
|
|
156
|
+
for (const s of sentences) {
|
|
157
|
+
items.push({ label: null, body: s.slice(0, 300), raw: s });
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
items.push({ label: null, body: block.slice(0, 300), raw: block });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Filter and clean items
|
|
166
|
+
return items
|
|
167
|
+
.map(item => {
|
|
168
|
+
let b = item.body.trim();
|
|
169
|
+
// Strip "You should generate:" prefix from example chunks — keep the list
|
|
170
|
+
b = b.replace(/^You should generate:\s*/i, '');
|
|
171
|
+
// Strip "Actions:" prefix
|
|
172
|
+
b = b.replace(/^Actions:\s*/i, '');
|
|
173
|
+
return { ...item, body: b.trim() };
|
|
174
|
+
})
|
|
175
|
+
.filter(item => {
|
|
176
|
+
const b = item.body.trim();
|
|
177
|
+
if (b.length < 20) return false;
|
|
178
|
+
if (/^#+ /.test(b)) return false; // naked heading
|
|
179
|
+
if (/^<[a-z]/.test(b)) return false; // XML tag
|
|
180
|
+
if (/^```/.test(b)) return false; // code fence
|
|
181
|
+
if (/^[-─═]{3,}/.test(b)) return false; // horizontal rule / YAML separator
|
|
182
|
+
if (/^You are (an? |the )/.test(b)) return false; // skill meta-instruction
|
|
183
|
+
if (/^You help (developers|teams|users)/.test(b)) return false;
|
|
184
|
+
if (/^(name|description|origin|version|tags|author):\s+\S/.test(b)) return false; // YAML frontmatter lines
|
|
185
|
+
if (/^\[!\[/.test(b)) return false; // badge markdown [](url)
|
|
186
|
+
if (/^!\[/.test(b)) return false; // inline image 
|
|
187
|
+
return true;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Score items by keyword overlap with the query, return top N.
|
|
193
|
+
* Items with a label score higher (they're named principles, not noise).
|
|
194
|
+
*/
|
|
195
|
+
function rankItems(items, queryWords, topN = 3) {
|
|
196
|
+
if (items.length === 0) return [];
|
|
197
|
+
|
|
198
|
+
const scored = items.map(item => {
|
|
199
|
+
const text = `${item.label ?? ''} ${item.body}`.toLowerCase();
|
|
200
|
+
const hits = queryWords.reduce((n, w) => n + (text.includes(w) ? 1 : 0), 0);
|
|
201
|
+
const labelBonus = item.label ? 0.5 : 0;
|
|
202
|
+
return { item, score: hits + labelBonus };
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
scored.sort((a, b) => b.score - a.score);
|
|
206
|
+
return scored.slice(0, topN).map(s => s.item);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function hr(label = '', width = 64) {
|
|
210
|
+
const pad = label ? ` ${label} ` : '';
|
|
211
|
+
const dashes = '─'.repeat(Math.max(2, width - pad.length - 4));
|
|
212
|
+
return `── ${pad}${dashes}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── ContextBuilder ──────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
export class ContextBuilder {
|
|
218
|
+
constructor(options = {}) {
|
|
219
|
+
const paths = resolveBookLibPaths(options.projectCwd);
|
|
220
|
+
this._searcher = new BookLibSearcher(paths.indexPath);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Build context for a task.
|
|
225
|
+
* @param {string} task
|
|
226
|
+
* @param {object} opts
|
|
227
|
+
* @param {boolean} opts.promptOnly - Skip the report, output only the prompt block
|
|
228
|
+
*/
|
|
229
|
+
async build(task, { promptOnly = false } = {}) {
|
|
230
|
+
const queries = this._decomposeTask(task);
|
|
231
|
+
const queryWords = task.toLowerCase().split(/\W+/).filter(w => w.length > 3);
|
|
232
|
+
|
|
233
|
+
const bySkill = await this._searchAndGroup(queries);
|
|
234
|
+
|
|
235
|
+
if (bySkill.size === 0) {
|
|
236
|
+
return 'No indexed skills found. Run `booklib index` first.';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Enrich each chunk: extract item-level knowledge + section label
|
|
240
|
+
for (const [, chunk] of bySkill) {
|
|
241
|
+
chunk._section = sectionLabel(chunk);
|
|
242
|
+
chunk._items = rankItems(extractItems(chunk.text), queryWords, 3);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Resolve conflicts
|
|
246
|
+
const chunks = [...bySkill.values()];
|
|
247
|
+
const resolver = new ConflictResolver();
|
|
248
|
+
const { winners, suppressed, conflicts } = resolver.resolveChunks(chunks);
|
|
249
|
+
|
|
250
|
+
// Build suppression lookup: skillName → { rationale, winnerName }
|
|
251
|
+
const suppressionMap = new Map(
|
|
252
|
+
suppressed.map(s => {
|
|
253
|
+
const winnerMatch = (s._rationale ?? '').match(/`([^`]+)`/);
|
|
254
|
+
return [s._skill, { rationale: s._rationale, winner: winnerMatch?.[1] }];
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Resolve genuine conflicts interactively or auto
|
|
259
|
+
const decisions = [];
|
|
260
|
+
const extraWinners = [];
|
|
261
|
+
|
|
262
|
+
for (const conflict of conflicts) {
|
|
263
|
+
if (promptOnly || !process.stdin.isTTY) {
|
|
264
|
+
const winner = chunks.find(c => c._skill === conflict.options[0].name);
|
|
265
|
+
if (winner) {
|
|
266
|
+
extraWinners.push({ ...winner, _decision: 'auto-conflict' });
|
|
267
|
+
decisions.push({
|
|
268
|
+
conflict,
|
|
269
|
+
chosen: conflict.options[0].name,
|
|
270
|
+
rejected: conflict.options.slice(1).map(o => o.name),
|
|
271
|
+
auto: true,
|
|
272
|
+
reason: `auto-resolved: highest specificity (${conflict.options[0].specificity})`,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
const choice = await this._promptConflict(conflict, chunks, queryWords);
|
|
277
|
+
extraWinners.push({ ...choice.chunk, _decision: 'user' });
|
|
278
|
+
decisions.push({
|
|
279
|
+
conflict,
|
|
280
|
+
chosen: choice.skillName,
|
|
281
|
+
rejected: conflict.options.map(o => o.name).filter(n => n !== choice.skillName),
|
|
282
|
+
auto: false,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const allWinners = [...winners, ...extraWinners];
|
|
288
|
+
|
|
289
|
+
if (promptOnly) {
|
|
290
|
+
return this._compilePrompt(task, allWinners, suppressionMap, decisions, queryWords);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
this._compileReport(task, allWinners, suppressionMap, bySkill, decisions) +
|
|
295
|
+
'\n\n' + hr('Final prompt') + '\n\n' +
|
|
296
|
+
this._compilePrompt(task, allWinners, suppressionMap, decisions, queryWords)
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ── Private ─────────────────────────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
_decomposeTask(task) {
|
|
303
|
+
const queries = [task];
|
|
304
|
+
|
|
305
|
+
// Split on connectors (avoid 'in' — too common, breaks phrases like "service in Kotlin")
|
|
306
|
+
const parts = task
|
|
307
|
+
.split(/\s+(?:and|with|using|via|plus|,)\s+/i)
|
|
308
|
+
.map(p => p.trim())
|
|
309
|
+
.filter(p => p.length > 4 && p !== task);
|
|
310
|
+
|
|
311
|
+
// Include all parts (up to 5), not just first 3
|
|
312
|
+
for (const p of parts.slice(0, 5)) queries.push(p);
|
|
313
|
+
|
|
314
|
+
// Also add multi-word noun phrases (2–3 consecutive capitalised/content words)
|
|
315
|
+
// e.g. "domain driven design", "async error handling"
|
|
316
|
+
const nounPhrases = task.match(/(?:[a-z]+ ){1,3}[a-z]+/gi) ?? [];
|
|
317
|
+
for (const phrase of nounPhrases) {
|
|
318
|
+
if (phrase.split(' ').length >= 2 && !queries.includes(phrase)) {
|
|
319
|
+
queries.push(phrase);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return [...new Set(queries)].slice(0, 8); // cap at 8 queries to keep search fast
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async _searchAndGroup(queries) {
|
|
327
|
+
const bySkill = new Map();
|
|
328
|
+
for (const query of queries) {
|
|
329
|
+
let results = [];
|
|
330
|
+
// MiniLM scores for domain-specific technical content cluster at 0.28-0.55.
|
|
331
|
+
// Principled sections (core knowledge) get the lowest floor — they're worth
|
|
332
|
+
// surfacing even at modest similarity. Generic/review sections need stronger match.
|
|
333
|
+
const SCORE_FLOORS = {
|
|
334
|
+
framework: 0.27, core_principles: 0.27, pitfalls: 0.27, anti_patterns: 0.27,
|
|
335
|
+
content: 0.29, guidelines: 0.29,
|
|
336
|
+
summary: 0.38, overview: 0.38, // generic meta-descriptions — need stronger match
|
|
337
|
+
case_studies: 0.40, examples: 0.40, // narrow examples — need strong match
|
|
338
|
+
strengths_to_praise: 0.40, // review-mode content, not design guidance
|
|
339
|
+
};
|
|
340
|
+
const DEFAULT_FLOOR = 0.35;
|
|
341
|
+
|
|
342
|
+
// Fetch 30 candidates — duplicates in the index can fill slots, so we over-fetch then deduplicate
|
|
343
|
+
try { results = await this._searcher.search(query, 30, 0.25); } catch { /* not indexed */ }
|
|
344
|
+
// Deduplicate by (skillName, type, content fingerprint) — index can have duplicate entries
|
|
345
|
+
const seen = new Set();
|
|
346
|
+
results = results.filter(chunk => {
|
|
347
|
+
const key = `${chunk.metadata?.name}|${chunk.metadata?.type}|${(chunk.text ?? '').slice(0, 60)}`;
|
|
348
|
+
if (seen.has(key)) return false;
|
|
349
|
+
seen.add(key);
|
|
350
|
+
return true;
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
for (const chunk of results) {
|
|
354
|
+
// name is in frontmatter for SKILL.md chunks; reference files derive it from filePath
|
|
355
|
+
const skillName = chunk.metadata?.name
|
|
356
|
+
?? chunk.metadata?.filePath?.split('/')[0]
|
|
357
|
+
?? null;
|
|
358
|
+
if (!skillName) continue;
|
|
359
|
+
|
|
360
|
+
// Apply per-type score floor
|
|
361
|
+
const chunkType = chunk.metadata?.type ?? '';
|
|
362
|
+
const floor = SCORE_FLOORS[chunkType] ?? DEFAULT_FLOOR;
|
|
363
|
+
if (chunk.score < floor) continue;
|
|
364
|
+
|
|
365
|
+
// Normalise chunk so _skill and metadata.name are always set
|
|
366
|
+
const normChunk = { ...chunk, _skill: skillName, metadata: { ...chunk.metadata, name: skillName } };
|
|
367
|
+
const existing = bySkill.get(skillName);
|
|
368
|
+
if (!existing) {
|
|
369
|
+
bySkill.set(skillName, normChunk);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
// Prefer principled sections over overview/examples
|
|
373
|
+
const principled = ['framework', 'core_principles', 'pitfalls', 'anti_patterns', 'content', 'guidelines'];
|
|
374
|
+
const newType = chunkType;
|
|
375
|
+
const existingType = existing.metadata?.type ?? '';
|
|
376
|
+
const newIsPrincipled = principled.includes(newType);
|
|
377
|
+
const existingIsPrincipled = principled.includes(existingType);
|
|
378
|
+
const scoreDelta = chunk.score - existing.score;
|
|
379
|
+
if (
|
|
380
|
+
(newIsPrincipled && !existingIsPrincipled) ||
|
|
381
|
+
(newIsPrincipled === existingIsPrincipled && scoreDelta > 0)
|
|
382
|
+
) {
|
|
383
|
+
bySkill.set(skillName, normChunk);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return bySkill;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async _promptConflict(conflict, chunks, queryWords) {
|
|
391
|
+
const iface = rl.createInterface({ input, output });
|
|
392
|
+
const options = conflict.options;
|
|
393
|
+
|
|
394
|
+
console.log('');
|
|
395
|
+
console.log(hr(`Conflict — ${conflict.topic}`));
|
|
396
|
+
console.log(` Both skills are equally applicable. Which should guide this decision?\n`);
|
|
397
|
+
|
|
398
|
+
for (let i = 0; i < options.length; i++) {
|
|
399
|
+
const letter = String.fromCharCode(97 + i);
|
|
400
|
+
const chunk = chunks.find(c => c._skill === options[i].name);
|
|
401
|
+
const book = bookLabel(options[i].name);
|
|
402
|
+
const section = chunk?._section ?? 'guidance';
|
|
403
|
+
const items = chunk?._items ?? [];
|
|
404
|
+
|
|
405
|
+
console.log(` [${letter}] ${book} (${section})`);
|
|
406
|
+
for (const item of items.slice(0, 2)) {
|
|
407
|
+
if (item.label) {
|
|
408
|
+
console.log(` • ${item.label}: ${item.body.slice(0, 100)}…`);
|
|
409
|
+
} else {
|
|
410
|
+
console.log(` • ${item.body.slice(0, 110)}…`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
console.log('');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
let answer = '';
|
|
417
|
+
while (true) {
|
|
418
|
+
answer = (await iface.question(' → Your choice: ')).trim().toLowerCase();
|
|
419
|
+
const idx = answer.charCodeAt(0) - 97;
|
|
420
|
+
if (idx >= 0 && idx < options.length) {
|
|
421
|
+
iface.close();
|
|
422
|
+
return {
|
|
423
|
+
skillName: options[idx].name,
|
|
424
|
+
chunk: chunks.find(c => c._skill === options[idx].name),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
console.log(` Invalid — enter a–${String.fromCharCode(97 + options.length - 1)}.`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ── Report (full explanation view) ─────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
_compileReport(task, winners, suppressionMap, bySkill, decisions) {
|
|
434
|
+
const lines = [
|
|
435
|
+
`Context for: "${task}"`,
|
|
436
|
+
'─'.repeat(64),
|
|
437
|
+
`${bySkill.size} skills matched · ${winners.length} selected · ${suppressionMap.size} suppressed`,
|
|
438
|
+
'',
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
for (const w of winners) {
|
|
442
|
+
const book = bookLabel(w._skill);
|
|
443
|
+
const section = w._section ?? 'guidance';
|
|
444
|
+
const items = w._items ?? [];
|
|
445
|
+
const score = (w.score ?? 0).toFixed(2);
|
|
446
|
+
|
|
447
|
+
const confidence = w.score >= 0.45 ? '' : w.score >= 0.35 ? ' ⚠ low confidence' : ' ⚠ borderline match';
|
|
448
|
+
lines.push(hr(w._skill));
|
|
449
|
+
lines.push(` 📖 ${book}`);
|
|
450
|
+
lines.push(` Section: ${section} Relevance: ${score}${confidence}`);
|
|
451
|
+
lines.push('');
|
|
452
|
+
|
|
453
|
+
if (items.length > 0) {
|
|
454
|
+
for (const item of items) {
|
|
455
|
+
if (item.label) {
|
|
456
|
+
lines.push(` § ${item.label}`);
|
|
457
|
+
lines.push(` ${item.body}`);
|
|
458
|
+
} else {
|
|
459
|
+
lines.push(` • ${item.body}`);
|
|
460
|
+
}
|
|
461
|
+
lines.push('');
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
// Show auto-decision rationale (non-blocking, always visible)
|
|
467
|
+
if (w._decision === 'auto' && w._rationale) {
|
|
468
|
+
lines.push(` ✓ Auto-selected`);
|
|
469
|
+
lines.push(` ${w._rationale}`);
|
|
470
|
+
|
|
471
|
+
// Show what was suppressed in favour of this skill and why
|
|
472
|
+
for (const [suppName, { rationale, winner }] of suppressionMap) {
|
|
473
|
+
if (winner === w._skill) {
|
|
474
|
+
lines.push(` ↳ suppressed: ${bookLabel(suppName)}`);
|
|
475
|
+
lines.push(` Reason: ${rationale}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
lines.push('');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Show user decision rationale
|
|
482
|
+
const dec = decisions.find(d => d.chosen === w._skill);
|
|
483
|
+
if (dec && w._decision === 'user') {
|
|
484
|
+
const rejected = dec.rejected.map(r => bookLabel(r)).join(', ');
|
|
485
|
+
lines.push(` ✓ Your choice — selected over: ${rejected}`);
|
|
486
|
+
lines.push('');
|
|
487
|
+
}
|
|
488
|
+
if (dec && w._decision === 'auto-conflict') {
|
|
489
|
+
const rejected = dec.rejected.map(r => bookLabel(r)).join(', ');
|
|
490
|
+
lines.push(` ✓ Auto-resolved conflict`);
|
|
491
|
+
lines.push(` ${dec.reason}`);
|
|
492
|
+
lines.push(` ↳ suppressed: ${rejected}`);
|
|
493
|
+
lines.push('');
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return lines.join('\n');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ── Graph-augmented context ─────────────────────────────────────────────────
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Builds combined context: skill chunks + knowledge graph nodes.
|
|
504
|
+
* @param {string} task - Task description
|
|
505
|
+
* @param {string|null} filePath - Current file path for component matching
|
|
506
|
+
* @returns {Promise<string>} Formatted context string
|
|
507
|
+
*/
|
|
508
|
+
async buildWithGraph(task, filePath = null) {
|
|
509
|
+
const skillContext = await this.build(task);
|
|
510
|
+
|
|
511
|
+
const searcher = new BookLibSearcher();
|
|
512
|
+
const graphNodes = await buildGraphContext({ filePath, taskContext: task, searcher });
|
|
513
|
+
|
|
514
|
+
if (graphNodes.length === 0) return skillContext;
|
|
515
|
+
|
|
516
|
+
const nodeSection = graphNodes
|
|
517
|
+
.map(node => `### 📝 ${node.title} [${node.type}]\n${node.body}`)
|
|
518
|
+
.join('\n\n---\n\n');
|
|
519
|
+
|
|
520
|
+
return `${skillContext}\n\n---\n\n## Knowledge Graph Context\n\n${nodeSection}`;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ── Prompt (sharp injectable block) ────────────────────────────────────────
|
|
524
|
+
|
|
525
|
+
_compilePrompt(task, winners, suppressionMap, decisions, queryWords) {
|
|
526
|
+
const lines = [
|
|
527
|
+
`You are working on: ${task}`,
|
|
528
|
+
'',
|
|
529
|
+
'Apply the following principles from canonical books and sources:',
|
|
530
|
+
'',
|
|
531
|
+
];
|
|
532
|
+
|
|
533
|
+
for (const w of winners) {
|
|
534
|
+
const book = bookLabel(w._skill);
|
|
535
|
+
const section = w._section ?? 'guidance';
|
|
536
|
+
const items = w._items ?? [];
|
|
537
|
+
|
|
538
|
+
lines.push(`**${book}** — ${section}`);
|
|
539
|
+
|
|
540
|
+
// Show each specific item with its label
|
|
541
|
+
for (const item of items) {
|
|
542
|
+
if (item.label) {
|
|
543
|
+
lines.push(`- **${item.label}**: ${item.body}`);
|
|
544
|
+
} else {
|
|
545
|
+
lines.push(`- ${item.body}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Inline decision note
|
|
550
|
+
const dec = decisions.find(d => d.chosen === w._skill);
|
|
551
|
+
if (dec) {
|
|
552
|
+
const how = dec.auto ? 'auto-resolved' : 'your choice';
|
|
553
|
+
const over = dec.rejected.map(r => bookLabel(r)).join(', ');
|
|
554
|
+
lines.push(`*(${how} over: ${over})*`);
|
|
555
|
+
} else if (w._decision === 'auto' && w._rationale) {
|
|
556
|
+
lines.push(`*(auto-selected: ${w._rationale})*`);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
lines.push('');
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const suppressed = [...suppressionMap.keys()];
|
|
563
|
+
if (suppressed.length > 0) {
|
|
564
|
+
lines.push('---');
|
|
565
|
+
lines.push('*Additional books matched but were suppressed — more specific guidance above covers their domain:*');
|
|
566
|
+
for (const s of suppressed) {
|
|
567
|
+
const info = suppressionMap.get(s);
|
|
568
|
+
lines.push(`- **${bookLabel(s)}** — ${info.rationale ?? 'lower specificity'}`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return lines.join('\n');
|
|
573
|
+
}
|
|
574
|
+
}
|