@mseep/core 3.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/CHANGELOG.md +285 -0
- package/LICENSE +21 -0
- package/README.ja.md +14 -0
- package/README.ko.md +14 -0
- package/README.md +227 -0
- package/README.pt-BR.md +14 -0
- package/README.skills.md +50 -0
- package/README.uk.md +14 -0
- package/README.zh-CN.md +14 -0
- package/bin/booklib-mcp.js +458 -0
- package/bin/booklib.js +2394 -0
- package/bin/skills.cjs +1292 -0
- package/community/registry.json +1616 -0
- package/hooks/hooks.json +52 -0
- package/hooks/posttooluse-capture.mjs +67 -0
- package/hooks/posttooluse-contradict.mjs +76 -0
- package/hooks/posttooluse-imports.mjs +67 -0
- package/hooks/pretooluse-inject.mjs +82 -0
- package/hooks/suggest.js +153 -0
- package/lib/agent-detector.js +96 -0
- package/lib/config-loader.js +39 -0
- package/lib/conflict-resolver.js +148 -0
- package/lib/connectors/context7.js +167 -0
- package/lib/connectors/github.js +223 -0
- package/lib/connectors/local.js +120 -0
- package/lib/connectors/notion.js +436 -0
- package/lib/connectors/web.js +134 -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/auditor.js +103 -0
- package/lib/engine/auto-linker.js +177 -0
- package/lib/engine/bm25-index.js +178 -0
- package/lib/engine/capture.js +120 -0
- package/lib/engine/context-map.js +641 -0
- package/lib/engine/corrections.js +194 -0
- package/lib/engine/decision-checker.js +203 -0
- package/lib/engine/doctor.js +207 -0
- package/lib/engine/embedding-provider.js +72 -0
- package/lib/engine/gap-detector.js +138 -0
- package/lib/engine/gap-resolver.js +135 -0
- package/lib/engine/graph-injector.js +137 -0
- package/lib/engine/graph-search.js +183 -0
- package/lib/engine/graph.js +170 -0
- package/lib/engine/handoff.js +411 -0
- package/lib/engine/import-checker.js +249 -0
- package/lib/engine/import-parser.js +145 -0
- package/lib/engine/indexer.js +334 -0
- package/lib/engine/lookup-priority.js +15 -0
- package/lib/engine/parser.js +257 -0
- package/lib/engine/principle-extractor.js +116 -0
- package/lib/engine/project-analyzer.js +353 -0
- package/lib/engine/query-expander.js +42 -0
- package/lib/engine/reasoning-modes.js +353 -0
- package/lib/engine/registries.js +524 -0
- package/lib/engine/reranker.js +45 -0
- package/lib/engine/rrf.js +59 -0
- package/lib/engine/scanner.js +151 -0
- package/lib/engine/searcher.js +223 -0
- package/lib/engine/session-coordinator.js +291 -0
- package/lib/engine/session-manager.js +375 -0
- package/lib/engine/source-detector.js +240 -0
- package/lib/engine/source-manager.js +142 -0
- package/lib/engine/structured-response.js +47 -0
- package/lib/engine/synthesis-templates.js +364 -0
- package/lib/installer.js +70 -0
- package/lib/instinct-block.js +21 -0
- package/lib/mcp-config-writer.js +107 -0
- package/lib/paths.js +62 -0
- package/lib/project-initializer.js +856 -0
- package/lib/registry/skills.js +102 -0
- package/lib/registry-searcher.js +107 -0
- package/lib/rules/rules-manager.js +169 -0
- package/lib/skill-fetcher.js +333 -0
- package/lib/well-known-builder.js +74 -0
- package/lib/wizard/index.js +1389 -0
- package/lib/wizard/integration-detector.js +41 -0
- package/lib/wizard/project-detector.js +146 -0
- package/lib/wizard/prompt.js +221 -0
- package/lib/wizard/registry-embeddings.js +107 -0
- package/lib/wizard/skill-recommender.js +69 -0
- package/package.json +70 -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/booklib-mcp-guide/SKILL.md +129 -0
- package/skills/booklib-mcp-guide/evals/evals.json +37 -0
- package/skills/booklib-mcp-guide/examples/after.md +34 -0
- package/skills/booklib-mcp-guide/examples/before.md +27 -0
- package/skills/booklib-mcp-guide/references/tool-catalog.md +9 -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
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// A/B Test Findings (April 2026):
|
|
2
|
+
//
|
|
3
|
+
// Three processing modes tested against codegen quality:
|
|
4
|
+
// fast (regex extraction): No model curation. Returns raw chunks.
|
|
5
|
+
// local (Qwen 2.5 14B): Fixed by atomic chunking (was broken with blob chunks).
|
|
6
|
+
// api (Haiku): Best for retrieval quality (+60%). For codegen, the
|
|
7
|
+
// "synthesize a skill" pattern outperforms raw filtering.
|
|
8
|
+
//
|
|
9
|
+
// Key finding: the prompt matters more than the mode.
|
|
10
|
+
// "Be strict" prompt → over-filters, loses specific details (SameSite, @Pattern)
|
|
11
|
+
// "Synthesize a skill" prompt → produces structured Principles/Anti-Patterns/Code
|
|
12
|
+
// that the codegen model follows more consistently.
|
|
13
|
+
//
|
|
14
|
+
// Best codegen results: search → top 15 small chunks → synthesize task-specific skill
|
|
15
|
+
// See: webshop-test/codegen-ab.js, webshop-test/codegen-ui-test.js
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { buildStructuredResponse } from './structured-response.js';
|
|
20
|
+
import { getSynthesisPrompt } from './synthesis-templates.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Groups search results by source and formats them as clean bullet points.
|
|
24
|
+
* Strips frontmatter and avoids mid-sentence cuts.
|
|
25
|
+
*
|
|
26
|
+
* @param {Array} results - raw search results with text and metadata
|
|
27
|
+
* @returns {string} formatted text grouped by source
|
|
28
|
+
*/
|
|
29
|
+
export function formatResultsForModel(results) {
|
|
30
|
+
if (!results || results.length === 0) return '(no results)';
|
|
31
|
+
|
|
32
|
+
// Group by source + parentTitle. Option to join siblings controlled by joinSiblings param.
|
|
33
|
+
const groups = new Map();
|
|
34
|
+
for (const r of results) {
|
|
35
|
+
const name = r.metadata?.name ?? r.metadata?.title ?? r.metadata?.id ?? 'unknown';
|
|
36
|
+
const parentTitle = r.metadata?.parentTitle ?? '';
|
|
37
|
+
const key = parentTitle ? `${name}|||${parentTitle}` : name;
|
|
38
|
+
|
|
39
|
+
if (!groups.has(key)) {
|
|
40
|
+
groups.set(key, { name, parentTitle, bullets: [] });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const text = (r.text ?? '').trim();
|
|
44
|
+
if (text) {
|
|
45
|
+
const cleaned = text.replace(/^---[\s\S]*?---\s*/m, '').trim();
|
|
46
|
+
if (cleaned) groups.get(key).bullets.push(cleaned);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let index = 0;
|
|
51
|
+
const sections = [];
|
|
52
|
+
for (const { name, parentTitle, bullets } of groups.values()) {
|
|
53
|
+
if (bullets.length === 0) continue;
|
|
54
|
+
index++;
|
|
55
|
+
const title = parentTitle
|
|
56
|
+
? `[${index}] Source: ${name} — "${parentTitle}"`
|
|
57
|
+
: `[${index}] Source: ${name}`;
|
|
58
|
+
const body = bullets.map(b => ` • ${b}`).join('\n');
|
|
59
|
+
sections.push(`${title}\n${body}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return sections.length > 0 ? sections.join('\n\n') : '(no results)';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Loads API key from .env file if not already in process.env.
|
|
67
|
+
* Checks project-local .env first, then home directory .env.
|
|
68
|
+
*/
|
|
69
|
+
function loadEnvKey() {
|
|
70
|
+
if (process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY) return;
|
|
71
|
+
|
|
72
|
+
const candidates = [
|
|
73
|
+
path.join(process.cwd(), '.env'),
|
|
74
|
+
path.join(process.env.HOME ?? '', '.env'),
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const envPath of candidates) {
|
|
78
|
+
if (!fs.existsSync(envPath)) continue;
|
|
79
|
+
try {
|
|
80
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
81
|
+
for (const line of content.split('\n')) {
|
|
82
|
+
const match = line.match(/^(ANTHROPIC_API_KEY|OPENAI_API_KEY)=(.+)$/);
|
|
83
|
+
if (match) {
|
|
84
|
+
process.env[match[1]] = match[2].trim();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch { /* best-effort */ }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Process search results through the configured reasoning mode.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} query - the search query
|
|
95
|
+
* @param {Array} results - raw search results
|
|
96
|
+
* @param {string} mode - 'fast' | 'local' | 'api'
|
|
97
|
+
* @param {object} opts
|
|
98
|
+
* @param {number} [opts.maxPrinciples=3]
|
|
99
|
+
* @param {string} [opts.file]
|
|
100
|
+
* @param {string} [opts.apiKey] - for API mode
|
|
101
|
+
* @param {string} [opts.apiModel] - for API mode
|
|
102
|
+
* @param {string} [opts.apiProvider] - 'anthropic' | 'openai'
|
|
103
|
+
* @param {string} [opts.ollamaModel] - for local mode
|
|
104
|
+
* @returns {Promise<object>} structured response
|
|
105
|
+
*/
|
|
106
|
+
export async function processResults(query, results, mode = 'fast', opts = {}) {
|
|
107
|
+
// Auto-load .env keys if not in environment
|
|
108
|
+
if (mode === 'api' && !opts.apiKey) {
|
|
109
|
+
loadEnvKey();
|
|
110
|
+
if (!opts.apiKey) {
|
|
111
|
+
opts.apiKey = process.env.ANTHROPIC_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
112
|
+
opts.apiProvider = process.env.ANTHROPIC_API_KEY ? 'anthropic' : 'openai';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
switch (mode) {
|
|
117
|
+
case 'local':
|
|
118
|
+
return processLocal(query, results, opts);
|
|
119
|
+
case 'api':
|
|
120
|
+
return processAPI(query, results, opts);
|
|
121
|
+
case 'fast':
|
|
122
|
+
default:
|
|
123
|
+
return buildStructuredResponse(query, results, opts);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Local model processing via Ollama.
|
|
129
|
+
* Sends search results to a local Ollama model for relevance reasoning.
|
|
130
|
+
* Falls back to fast mode if Ollama is not running.
|
|
131
|
+
*/
|
|
132
|
+
async function processLocal(query, results, opts = {}) {
|
|
133
|
+
const { maxPrinciples = 3, file, ollamaModel = 'phi3', sourceType } = opts;
|
|
134
|
+
|
|
135
|
+
if (!results || results.length === 0) {
|
|
136
|
+
return { query, file: file ?? null, results: [], mode: 'local', note: 'No relevant knowledge found.' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check if Ollama is running
|
|
140
|
+
const ollamaUrl = process.env.OLLAMA_HOST ?? 'http://localhost:11434';
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const healthCheck = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(2000) });
|
|
144
|
+
if (!healthCheck.ok) throw new Error('Ollama not responding');
|
|
145
|
+
} catch {
|
|
146
|
+
// Ollama not running — fall back to fast mode
|
|
147
|
+
const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
|
|
148
|
+
fallback.mode = 'local-fallback';
|
|
149
|
+
fallback.note = 'Local AI configured but Ollama not running. Using fast mode instead.\n' +
|
|
150
|
+
'To fix: start Ollama with: ollama serve\n' +
|
|
151
|
+
'Or switch to API mode: set "reasoning": "api" in booklib.config.json.';
|
|
152
|
+
return fallback;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const resultsText = formatResultsForModel(results);
|
|
157
|
+
|
|
158
|
+
const prompt = sourceType
|
|
159
|
+
? getSynthesisPrompt(sourceType, { query, file, results: resultsText })
|
|
160
|
+
: `You are a knowledge curator. Given a query and search results, synthesize actionable guidance. Keep specific details — code examples, annotations, configuration values. Drop only completely unrelated results.
|
|
161
|
+
|
|
162
|
+
Query: "${query}"
|
|
163
|
+
${file ? `File: ${file}` : ''}
|
|
164
|
+
|
|
165
|
+
Results:
|
|
166
|
+
${resultsText}
|
|
167
|
+
|
|
168
|
+
Return JSON array: [{"principle": "specific actionable guidance", "context": "why and how to apply", "source": "..."}]
|
|
169
|
+
Include all results that could help. Only drop completely unrelated ones.
|
|
170
|
+
Maximum ${maxPrinciples} entries.`;
|
|
171
|
+
|
|
172
|
+
const res = await fetch(`${ollamaUrl}/api/generate`, {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: { 'Content-Type': 'application/json' },
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
model: ollamaModel,
|
|
177
|
+
prompt,
|
|
178
|
+
stream: false,
|
|
179
|
+
options: { temperature: 0.1, num_predict: 1024 },
|
|
180
|
+
}),
|
|
181
|
+
signal: AbortSignal.timeout(30000),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (!res.ok) {
|
|
185
|
+
throw new Error(`Ollama error: ${res.status}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const data = await res.json();
|
|
189
|
+
const response = data.response ?? '';
|
|
190
|
+
|
|
191
|
+
// DEBUG: log raw Ollama response to stderr
|
|
192
|
+
console.error(`[booklib-debug] Ollama raw response (${ollamaModel}, ${response.length} chars): ${response.slice(0, 500)}`);
|
|
193
|
+
|
|
194
|
+
// Try JSON array first (well-behaved models)
|
|
195
|
+
const jsonMatch = response.match(/\[[\s\S]*\]/);
|
|
196
|
+
if (jsonMatch) {
|
|
197
|
+
try {
|
|
198
|
+
const principles = JSON.parse(jsonMatch[0]).slice(0, maxPrinciples);
|
|
199
|
+
return {
|
|
200
|
+
query, file: file ?? null, results: principles,
|
|
201
|
+
mode: 'local', model: ollamaModel,
|
|
202
|
+
note: `${principles.length} result(s) after local AI reasoning (${ollamaModel}).`,
|
|
203
|
+
};
|
|
204
|
+
} catch { /* JSON parse failed — try markdown fallback */ }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Fallback: parse markdown response (small models often return markdown not JSON)
|
|
208
|
+
if (response.length > 20) {
|
|
209
|
+
const lines = response.split('\n').filter(l => l.trim() && !l.startsWith('#'));
|
|
210
|
+
const principles = lines
|
|
211
|
+
.filter(l => l.startsWith('- ') || l.startsWith('* ') || l.match(/^\d+\./))
|
|
212
|
+
.map(l => l.replace(/^[-*]\s+|^\d+\.\s+/, '').trim())
|
|
213
|
+
.filter(l => l.length > 10)
|
|
214
|
+
.slice(0, maxPrinciples)
|
|
215
|
+
.map(p => ({ principle: p, context: '', source: ollamaModel }));
|
|
216
|
+
|
|
217
|
+
if (principles.length > 0) {
|
|
218
|
+
return {
|
|
219
|
+
query, file: file ?? null, results: principles,
|
|
220
|
+
mode: 'local', model: ollamaModel,
|
|
221
|
+
note: `${principles.length} result(s) synthesized by ${ollamaModel}.`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Last resort — fast mode
|
|
227
|
+
const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
|
|
228
|
+
fallback.mode = 'local-fallback';
|
|
229
|
+
fallback.note = `Local model response too short. Using fast mode.`;
|
|
230
|
+
return fallback;
|
|
231
|
+
|
|
232
|
+
} catch (err) {
|
|
233
|
+
const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
|
|
234
|
+
fallback.mode = 'local-error';
|
|
235
|
+
fallback.note = `Local reasoning failed: ${err.message}. Using fast mode.`;
|
|
236
|
+
return fallback;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* API processing — sends results to an external LLM for reasoning.
|
|
242
|
+
* Requires an API key (Anthropic or OpenAI).
|
|
243
|
+
*
|
|
244
|
+
* The LLM receives the query + raw results and returns synthesized,
|
|
245
|
+
* relevant principles. This produces the highest quality output.
|
|
246
|
+
*/
|
|
247
|
+
async function processAPI(query, results, opts = {}) {
|
|
248
|
+
const { maxPrinciples = 3, file, apiKey, apiModel, apiProvider = 'anthropic', sourceType } = opts;
|
|
249
|
+
|
|
250
|
+
if (!apiKey) {
|
|
251
|
+
// Fall back to fast mode if no API key
|
|
252
|
+
const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
|
|
253
|
+
fallback.mode = 'api-fallback';
|
|
254
|
+
fallback.note = 'API mode configured but no API key found. Using fast mode instead.\n' +
|
|
255
|
+
'To fix: add your key to .env (echo "ANTHROPIC_API_KEY=sk-ant-..." >> .env)\n' +
|
|
256
|
+
'Or try local AI: set "reasoning": "local" in booklib.config.json and run Ollama.';
|
|
257
|
+
return fallback;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!results || results.length === 0) {
|
|
261
|
+
return { query, file: file ?? null, results: [], mode: 'api', note: 'No relevant knowledge found.' };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
// Build the prompt for the reasoning model
|
|
266
|
+
const resultsText = formatResultsForModel(results);
|
|
267
|
+
|
|
268
|
+
const prompt = sourceType
|
|
269
|
+
? getSynthesisPrompt(sourceType, { query, file, results: resultsText })
|
|
270
|
+
: `You are a knowledge curator for a coding assistant. Given a query and search results from a knowledge base, synthesize actionable guidance. Keep specific details — code examples, exact annotations, configuration values, security headers. Drop only results that are completely unrelated to the query.
|
|
271
|
+
|
|
272
|
+
Query: "${query}"
|
|
273
|
+
${file ? `File being edited: ${file}` : ''}
|
|
274
|
+
|
|
275
|
+
Search results:
|
|
276
|
+
${resultsText}
|
|
277
|
+
|
|
278
|
+
Return a JSON array. Each entry: {"principle": "the specific actionable guidance", "context": "why this matters and how to apply it", "source": "where this came from"}
|
|
279
|
+
Include ALL results that could help with this task — keep specific details like annotation names, config values, and code patterns. Only drop results that are about a completely different topic.
|
|
280
|
+
Maximum ${maxPrinciples} entries.`;
|
|
281
|
+
|
|
282
|
+
let response;
|
|
283
|
+
|
|
284
|
+
if (apiProvider === 'anthropic') {
|
|
285
|
+
response = await callAnthropic(prompt, apiKey, apiModel ?? 'claude-haiku-4-5-20251001');
|
|
286
|
+
} else {
|
|
287
|
+
response = await callOpenAI(prompt, apiKey, apiModel ?? 'gpt-4o-mini');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Parse LLM response
|
|
291
|
+
const jsonMatch = response.match(/\[[\s\S]*\]/);
|
|
292
|
+
if (jsonMatch) {
|
|
293
|
+
const principles = JSON.parse(jsonMatch[0]).slice(0, maxPrinciples);
|
|
294
|
+
return {
|
|
295
|
+
query,
|
|
296
|
+
file: file ?? null,
|
|
297
|
+
results: principles,
|
|
298
|
+
mode: 'api',
|
|
299
|
+
note: `${principles.length} result(s) after API reasoning (${apiProvider}).`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Couldn't parse — fall back to fast
|
|
304
|
+
const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
|
|
305
|
+
fallback.mode = 'api-parse-error';
|
|
306
|
+
fallback.note = 'API reasoning returned unparseable response. Using fast mode.';
|
|
307
|
+
return fallback;
|
|
308
|
+
|
|
309
|
+
} catch (err) {
|
|
310
|
+
// API call failed — fall back to fast
|
|
311
|
+
const fallback = buildStructuredResponse(query, results, { maxPrinciples, file });
|
|
312
|
+
fallback.mode = 'api-error';
|
|
313
|
+
fallback.note = `API reasoning failed: ${err.message}. Using fast mode.`;
|
|
314
|
+
return fallback;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function callAnthropic(prompt, apiKey, model) {
|
|
319
|
+
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
320
|
+
method: 'POST',
|
|
321
|
+
headers: {
|
|
322
|
+
'Content-Type': 'application/json',
|
|
323
|
+
'x-api-key': apiKey,
|
|
324
|
+
'anthropic-version': '2023-06-01',
|
|
325
|
+
},
|
|
326
|
+
body: JSON.stringify({
|
|
327
|
+
model,
|
|
328
|
+
max_tokens: 1024,
|
|
329
|
+
messages: [{ role: 'user', content: prompt }],
|
|
330
|
+
}),
|
|
331
|
+
});
|
|
332
|
+
if (!res.ok) throw new Error(`Anthropic API error: ${res.status}`);
|
|
333
|
+
const data = await res.json();
|
|
334
|
+
return data.content?.[0]?.text ?? '';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function callOpenAI(prompt, apiKey, model) {
|
|
338
|
+
const res = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
339
|
+
method: 'POST',
|
|
340
|
+
headers: {
|
|
341
|
+
'Content-Type': 'application/json',
|
|
342
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
343
|
+
},
|
|
344
|
+
body: JSON.stringify({
|
|
345
|
+
model,
|
|
346
|
+
max_tokens: 1024,
|
|
347
|
+
messages: [{ role: 'user', content: prompt }],
|
|
348
|
+
}),
|
|
349
|
+
});
|
|
350
|
+
if (!res.ok) throw new Error(`OpenAI API error: ${res.status}`);
|
|
351
|
+
const data = await res.json();
|
|
352
|
+
return data.choices?.[0]?.message?.content ?? '';
|
|
353
|
+
}
|