@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,404 @@
|
|
|
1
|
+
// lib/wizard/index.js
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { createWizardUI, sep } from './prompt.js';
|
|
6
|
+
import { detect as detectProject } from './project-detector.js';
|
|
7
|
+
import { SKILL_LIMIT } from './skill-recommender.js';
|
|
8
|
+
import { detectIntegrations } from './integration-detector.js';
|
|
9
|
+
import { SkillFetcher, countAllSlots, countInstalledSlots, listInstalledSkillNames, installSkill } from '../skill-fetcher.js';
|
|
10
|
+
import { BookLibIndexer } from '../engine/indexer.js';
|
|
11
|
+
import { BookLibSearcher } from '../engine/searcher.js';
|
|
12
|
+
import { AgentDetector } from '../agent-detector.js';
|
|
13
|
+
import { ProjectInitializer } from '../project-initializer.js';
|
|
14
|
+
import { resolveBookLibPaths } from '../paths.js';
|
|
15
|
+
import { writeMCPConfig, MCP_CAPABLE } from '../mcp-config-writer.js';
|
|
16
|
+
|
|
17
|
+
const AGENT_LABELS = {
|
|
18
|
+
claude: 'Claude Code', cursor: 'Cursor', copilot: 'Copilot',
|
|
19
|
+
gemini: 'Gemini CLI', codex: 'Codex', windsurf: 'Windsurf',
|
|
20
|
+
'roo-code': 'Roo Code', openhands: 'OpenHands', junie: 'Junie',
|
|
21
|
+
goose: 'Goose', opencode: 'OpenCode', letta: 'Letta',
|
|
22
|
+
};
|
|
23
|
+
const ALL_AGENTS = Object.keys(AGENT_LABELS);
|
|
24
|
+
|
|
25
|
+
export async function runWizard(cwd = process.cwd(), opts = {}) {
|
|
26
|
+
const markerPath = path.join(cwd, '.booklib', 'initialized');
|
|
27
|
+
|
|
28
|
+
if (opts.reset) {
|
|
29
|
+
if (fs.existsSync(markerPath)) fs.unlinkSync(markerPath);
|
|
30
|
+
return runSetup(cwd);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (fs.existsSync(markerPath)) {
|
|
34
|
+
console.log('\n Already initialized. Running relevance check...');
|
|
35
|
+
console.log(' (to re-run full setup: rm -rf .booklib && booklib init)\n');
|
|
36
|
+
return runRelevanceAudit(cwd);
|
|
37
|
+
}
|
|
38
|
+
return runSetup(cwd);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Setup flow ───────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
async function runSetup(cwd) {
|
|
44
|
+
const ui = createWizardUI();
|
|
45
|
+
|
|
46
|
+
// Banner
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log(' \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2726');
|
|
49
|
+
console.log(' \u2502 \u2500\u2500\u2500 \u2502 \u2500\u2500\u2500 \u2502');
|
|
50
|
+
console.log(' \u2502 \u2500\u2500 \u2502 \u2500\u2500 \u2502 BookLib');
|
|
51
|
+
console.log(' \u2502 \u2500\u2500\u2500 \u2502 \u2500\u2500\u2500 \u2502');
|
|
52
|
+
console.log(' \u2502 \u2500\u2500 \u2502 \u2500\u2500 \u2502 AI-agent skills from');
|
|
53
|
+
console.log(' \u2502 \u2500\u2500\u2500 \u2502 \u2500\u2500\u2500 \u2502 expert knowledge');
|
|
54
|
+
console.log(' \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2518');
|
|
55
|
+
console.log('');
|
|
56
|
+
|
|
57
|
+
ui.intro('Setup Wizard');
|
|
58
|
+
|
|
59
|
+
// Step 1: Project detection
|
|
60
|
+
const project = await stepProjectDetection(ui, cwd);
|
|
61
|
+
|
|
62
|
+
// Step 2: Profile selection
|
|
63
|
+
const profile = await stepProfileSelection(ui);
|
|
64
|
+
|
|
65
|
+
// Save profile to config
|
|
66
|
+
try {
|
|
67
|
+
const { configPath } = resolveBookLibPaths(cwd);
|
|
68
|
+
let savedConfig = {};
|
|
69
|
+
try { savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { /* no config */ }
|
|
70
|
+
savedConfig.profile = profile;
|
|
71
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
72
|
+
fs.writeFileSync(configPath, JSON.stringify(savedConfig, null, 2));
|
|
73
|
+
} catch { /* best-effort */ }
|
|
74
|
+
|
|
75
|
+
// Step 3: Health check
|
|
76
|
+
const slotsUsed = countAllSlots();
|
|
77
|
+
const installedNames = listInstalledSkillNames();
|
|
78
|
+
if (slotsUsed > SKILL_LIMIT) {
|
|
79
|
+
ui.log.warn(
|
|
80
|
+
`${slotsUsed} skills installed (limit: ${SKILL_LIMIT}).\n` +
|
|
81
|
+
'Agent context is overloaded \u2014 most skills get truncated.\n' +
|
|
82
|
+
'After indexing, I\'ll find the best skills for your stack and help clean up.'
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Step 4: Tool detection
|
|
87
|
+
const selectedAgents = await stepToolSelection(ui, cwd);
|
|
88
|
+
|
|
89
|
+
// Step 5: Index build with spinner
|
|
90
|
+
await stepIndexBuild(ui);
|
|
91
|
+
|
|
92
|
+
// Step 6: Recommend + install + cleanup
|
|
93
|
+
const selectedSkills = await stepRecommendAndInstall(ui, project, slotsUsed, installedNames);
|
|
94
|
+
|
|
95
|
+
// Step 7: Write config files
|
|
96
|
+
const skillsForConfig = selectedSkills.length > 0 ? selectedSkills : installedNames.slice(0, 10);
|
|
97
|
+
const stack = project.languages.join(', ');
|
|
98
|
+
await stepWriteConfigs(ui, cwd, selectedAgents, skillsForConfig, profile, stack);
|
|
99
|
+
|
|
100
|
+
// Step 8: Summary
|
|
101
|
+
ui.outro('BookLib is ready');
|
|
102
|
+
|
|
103
|
+
const finalSlots = countInstalledSlots();
|
|
104
|
+
console.log('');
|
|
105
|
+
if (selectedSkills.length > 0) console.log(` \u2713 ${selectedSkills.length} skills configured`);
|
|
106
|
+
console.log(` \u2713 ${finalSlots} total skills loaded`);
|
|
107
|
+
console.log('');
|
|
108
|
+
console.log(` ${sep()}`);
|
|
109
|
+
console.log(' Quick reference:');
|
|
110
|
+
console.log('');
|
|
111
|
+
console.log(' booklib search "query" find patterns');
|
|
112
|
+
console.log(' booklib search "q" --graph include graph links');
|
|
113
|
+
console.log(' booklib capture --title "..." save knowledge');
|
|
114
|
+
console.log(' booklib doctor health check');
|
|
115
|
+
console.log(' booklib doctor --cure auto-fix issues');
|
|
116
|
+
console.log(' booklib init --reset re-run setup');
|
|
117
|
+
console.log(` ${sep()}`);
|
|
118
|
+
console.log('');
|
|
119
|
+
|
|
120
|
+
// Mark initialized
|
|
121
|
+
const markerPath = path.join(cwd, '.booklib', 'initialized');
|
|
122
|
+
fs.mkdirSync(path.dirname(markerPath), { recursive: true });
|
|
123
|
+
fs.writeFileSync(markerPath, new Date().toISOString());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function stepProjectDetection(ui, cwd) {
|
|
127
|
+
const project = detectProject(cwd);
|
|
128
|
+
|
|
129
|
+
if (project.languages.length > 0) {
|
|
130
|
+
const langs = project.languages.join(', ');
|
|
131
|
+
const fw = project.frameworks.length ? ` (${project.frameworks.join(', ')})` : '';
|
|
132
|
+
const ok = await ui.confirm(`Detected: ${langs}${fw}. Correct?`, true);
|
|
133
|
+
if (!ok) {
|
|
134
|
+
const answer = await ui.text('Describe your stack:', 'e.g. React + Node.js, Kotlin Android');
|
|
135
|
+
return { languages: [answer], frameworks: [], signals: [] };
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
const answer = await ui.text('What are you building?', 'e.g. React + Node.js, Kotlin Android');
|
|
139
|
+
return { languages: [answer], frameworks: [], signals: [] };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return project;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function stepProfileSelection(ui) {
|
|
146
|
+
const profile = await ui.select('What kind of work is this project for?', [
|
|
147
|
+
{ value: 'software-development', label: 'Software development', hint: 'recommended' },
|
|
148
|
+
{ value: 'writing-content', label: 'Writing & content' },
|
|
149
|
+
{ value: 'research-analysis', label: 'Research & analysis' },
|
|
150
|
+
{ value: 'design', label: 'Design' },
|
|
151
|
+
{ value: 'general', label: 'General / other' },
|
|
152
|
+
]);
|
|
153
|
+
return profile;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function stepToolSelection(ui, cwd) {
|
|
157
|
+
const detector = new AgentDetector({ cwd });
|
|
158
|
+
const detected = detector.detect();
|
|
159
|
+
const detectedSet = new Set(detected);
|
|
160
|
+
|
|
161
|
+
const options = ALL_AGENTS.map(a => ({
|
|
162
|
+
value: a,
|
|
163
|
+
label: AGENT_LABELS[a],
|
|
164
|
+
hint: detectedSet.has(a) ? 'detected' : undefined,
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
const selected = await ui.multiselect('Which AI tools do you use?', options, {
|
|
168
|
+
initialValues: detected,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Save to config
|
|
172
|
+
try {
|
|
173
|
+
const { configPath } = resolveBookLibPaths(cwd);
|
|
174
|
+
let savedConfig = {};
|
|
175
|
+
try { savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { /* no config */ }
|
|
176
|
+
savedConfig.tools = selected;
|
|
177
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
178
|
+
fs.writeFileSync(configPath, JSON.stringify(savedConfig, null, 2));
|
|
179
|
+
} catch { /* best-effort */ }
|
|
180
|
+
|
|
181
|
+
const integrations = detectIntegrations({ cwd });
|
|
182
|
+
if (integrations.superpowers) {
|
|
183
|
+
ui.log.info('Detected: obra/superpowers plugin (skills auto-synced)');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return selected;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function stepIndexBuild(ui) {
|
|
190
|
+
const s = ui.spinner();
|
|
191
|
+
s.start('Building knowledge index...');
|
|
192
|
+
const indexer = new BookLibIndexer();
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const { skillsPath } = resolveBookLibPaths();
|
|
196
|
+
let lastFile = '';
|
|
197
|
+
await indexer.indexDirectory(skillsPath, false, {
|
|
198
|
+
quiet: true,
|
|
199
|
+
onProgress({ current, total, file }) {
|
|
200
|
+
lastFile = file.split('/')[0] ?? file;
|
|
201
|
+
s.message(`Building knowledge index... [${current}/${total}] ${lastFile}`);
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
s.stop('Index ready');
|
|
205
|
+
} catch (err) {
|
|
206
|
+
s.stop(`Index build failed: ${err.message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function stepRecommendAndInstall(ui, project, slotsUsed, installedNames) {
|
|
211
|
+
const s = ui.spinner();
|
|
212
|
+
s.start('Finding best skills for your project...');
|
|
213
|
+
|
|
214
|
+
const searcher = new BookLibSearcher();
|
|
215
|
+
const queryText = project.languages.join(' ') + ' best practices';
|
|
216
|
+
let results;
|
|
217
|
+
try {
|
|
218
|
+
results = await searcher.search(queryText, 20, 0);
|
|
219
|
+
} catch {
|
|
220
|
+
s.stop('Search index not available');
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const bySkill = new Map();
|
|
225
|
+
for (const r of results) {
|
|
226
|
+
const name = r.metadata?.name;
|
|
227
|
+
if (!name) continue;
|
|
228
|
+
if (!bySkill.has(name) || r.score > bySkill.get(name).score) {
|
|
229
|
+
const snippet = (r.text ?? '').replace(/\n/g, ' ').slice(0, 80).trim();
|
|
230
|
+
bySkill.set(name, { name, score: r.score, displayScore: r.displayScore, snippet, description: r.metadata?.description ?? '' });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const recommended = [...bySkill.values()].sort((a, b) => b.score - a.score).slice(0, 10);
|
|
234
|
+
s.stop(`Found ${recommended.length} matching skills`);
|
|
235
|
+
|
|
236
|
+
if (recommended.length === 0) return [];
|
|
237
|
+
|
|
238
|
+
const installedSet = new Set(installedNames.map(n => n.toLowerCase()));
|
|
239
|
+
|
|
240
|
+
const options = recommended.map(sk => ({
|
|
241
|
+
value: sk.name,
|
|
242
|
+
label: `${sk.name} [${sk.displayScore ?? Math.round((sk.score ?? 0) * 100)}%]`,
|
|
243
|
+
hint: installedSet.has(sk.name.toLowerCase()) ? 'installed' : (sk.snippet || sk.description.slice(0, 50)),
|
|
244
|
+
}));
|
|
245
|
+
|
|
246
|
+
const selected = await ui.multiselect('Top skills for your project:', options);
|
|
247
|
+
|
|
248
|
+
if (selected.length === 0) return installedNames;
|
|
249
|
+
|
|
250
|
+
// Install selected skills
|
|
251
|
+
const toInstall = selected.filter(n => !installedSet.has(n.toLowerCase()));
|
|
252
|
+
const installed = [];
|
|
253
|
+
|
|
254
|
+
for (const name of selected) {
|
|
255
|
+
if (installedSet.has(name.toLowerCase())) {
|
|
256
|
+
ui.log.info(`${name} (already installed)`);
|
|
257
|
+
installed.push(name);
|
|
258
|
+
} else {
|
|
259
|
+
const result = installSkill(name);
|
|
260
|
+
if (result === 'installed') {
|
|
261
|
+
ui.log.success(`${name}`);
|
|
262
|
+
installed.push(name);
|
|
263
|
+
} else if (result === 'already-installed') {
|
|
264
|
+
ui.log.info(`${name} (already installed)`);
|
|
265
|
+
installed.push(name);
|
|
266
|
+
} else {
|
|
267
|
+
ui.log.warn(`${name}: not found in any catalog`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Cleanup offer
|
|
273
|
+
if (slotsUsed > SKILL_LIMIT && installed.length > 0) {
|
|
274
|
+
const toRemove = installedNames.filter(n => !selected.includes(n));
|
|
275
|
+
const cleanup = await ui.select(`You have ${slotsUsed} skills but only need ~${installed.length}.`, [
|
|
276
|
+
{ value: 'clean', label: `Clean up \u2014 keep only recommended (remove ${toRemove.length})` },
|
|
277
|
+
{ value: 'keep', label: 'Keep all + add recommended' },
|
|
278
|
+
{ value: 'skip', label: 'Skip \u2014 I\'ll handle it manually' },
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
if (cleanup === 'clean') {
|
|
282
|
+
const fetcher = new SkillFetcher();
|
|
283
|
+
let removed = 0;
|
|
284
|
+
for (const name of toRemove) {
|
|
285
|
+
fetcher.desyncFromClaudeSkills({ name });
|
|
286
|
+
removed++;
|
|
287
|
+
}
|
|
288
|
+
ui.log.success(`Removed ${removed} skills. Kept ${installed.length}.`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return installed;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function stepWriteConfigs(ui, cwd, selectedAgents, skillNames, profile, stack) {
|
|
296
|
+
if (selectedAgents.length === 0 || skillNames.length === 0) return;
|
|
297
|
+
|
|
298
|
+
const s = ui.spinner();
|
|
299
|
+
s.start('Writing config files...');
|
|
300
|
+
|
|
301
|
+
const initializer = new ProjectInitializer({ projectCwd: cwd });
|
|
302
|
+
const target = selectedAgents.length === ALL_AGENTS.length ? 'all' : selectedAgents.join(',');
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const written = await initializer.init({ skills: skillNames, target, dryRun: false, quiet: true, profile, stack });
|
|
306
|
+
if (written.length > 0) {
|
|
307
|
+
s.stop('Config files written');
|
|
308
|
+
for (const file of written) {
|
|
309
|
+
ui.log.success(file);
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
s.stop('No config files needed');
|
|
313
|
+
}
|
|
314
|
+
} catch (err) {
|
|
315
|
+
s.stop(`Config write failed: ${err.message}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Write MCP configs for capable tools
|
|
319
|
+
const mcpWritten = [];
|
|
320
|
+
for (const tool of selectedAgents) {
|
|
321
|
+
if (MCP_CAPABLE.has(tool)) {
|
|
322
|
+
try {
|
|
323
|
+
const mcpPath = writeMCPConfig(tool, cwd);
|
|
324
|
+
if (mcpPath) mcpWritten.push(mcpPath);
|
|
325
|
+
} catch { /* best-effort */ }
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (mcpWritten.length > 0) {
|
|
329
|
+
for (const p of mcpWritten) {
|
|
330
|
+
const rel = path.relative(cwd, p);
|
|
331
|
+
ui.log.success(`${rel} (MCP)`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ── Re-run flow (already initialized) ────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
async function runRelevanceAudit(cwd) {
|
|
339
|
+
const { cosine } = await import('./skill-recommender.js');
|
|
340
|
+
const { getEmbeddings } = await import('./registry-embeddings.js');
|
|
341
|
+
const { detect: detectProj } = await import('./project-detector.js');
|
|
342
|
+
|
|
343
|
+
console.log('\n BookLib \u2014 Relevance Check\n');
|
|
344
|
+
|
|
345
|
+
const project = detectProj(cwd);
|
|
346
|
+
const installedNames = listInstalledSkillNames();
|
|
347
|
+
|
|
348
|
+
if (installedNames.length === 0) {
|
|
349
|
+
console.log(' No BookLib-managed skills installed. Run "booklib init" to set up.');
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
process.stdout.write(`\u25ba Scoring ${installedNames.length} skill(s) against your project`);
|
|
354
|
+
const dotInterval = setInterval(() => { process.stdout.write('.'); }, 300);
|
|
355
|
+
|
|
356
|
+
const embeddings = await getEmbeddings();
|
|
357
|
+
const searcher = new BookLibSearcher();
|
|
358
|
+
const queryText = project.languages.map(l => `${l} programming`).join('. ') || 'software engineering';
|
|
359
|
+
const queryVec = await searcher.getEmbedding(queryText);
|
|
360
|
+
|
|
361
|
+
clearInterval(dotInterval);
|
|
362
|
+
process.stdout.write('\n\n');
|
|
363
|
+
|
|
364
|
+
const RELEVANCE_THRESHOLD = 0.35;
|
|
365
|
+
const scored = installedNames
|
|
366
|
+
.map(name => ({ name, score: embeddings.has(name) ? cosine(queryVec, embeddings.get(name)) : null }))
|
|
367
|
+
.filter(s => s.score !== null)
|
|
368
|
+
.sort((a, b) => b.score - a.score);
|
|
369
|
+
|
|
370
|
+
const unindexedCount = installedNames.length - scored.length;
|
|
371
|
+
if (unindexedCount > 0) {
|
|
372
|
+
process.stdout.write(` ${unindexedCount} skill(s) not yet indexed \u2014 run "booklib index" to score them\n\n`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (scored.length === 0) {
|
|
376
|
+
process.stdout.write(' Nothing to score yet. Run "booklib index" first.\n\n');
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const relevant = scored.filter(s => s.score >= RELEVANCE_THRESHOLD);
|
|
381
|
+
const lowRelevance = scored.filter(s => s.score < RELEVANCE_THRESHOLD);
|
|
382
|
+
|
|
383
|
+
for (const { name, score } of relevant.slice(0, 5)) {
|
|
384
|
+
process.stdout.write(` \u2713 ${name.padEnd(30)} ${(score * 100).toFixed(0)}% match\n`);
|
|
385
|
+
}
|
|
386
|
+
if (relevant.length > 5) {
|
|
387
|
+
process.stdout.write(` \u2026 and ${relevant.length - 5} more relevant skill(s)\n`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (lowRelevance.length === 0) {
|
|
391
|
+
process.stdout.write(`\n All ${scored.length} scored skill(s) are relevant to this project.\n\n`);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
process.stdout.write('\n Low relevance for this project:\n');
|
|
396
|
+
for (const { name, score } of lowRelevance.slice(0, 10)) {
|
|
397
|
+
process.stdout.write(` \u00b7 ${name.padEnd(30)} ${(score * 100).toFixed(0)}% match\n`);
|
|
398
|
+
}
|
|
399
|
+
if (lowRelevance.length > 10) {
|
|
400
|
+
process.stdout.write(` \u2026 and ${lowRelevance.length - 10} more\n`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
process.stdout.write('\n Tip: run "booklib uninstall <skill>" to free slots for more relevant skills.\n\n');
|
|
404
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// lib/wizard/integration-detector.js
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detects installed integrations and tools.
|
|
8
|
+
*
|
|
9
|
+
* @param {{ cwd?: string, home?: string }} opts - override for testing
|
|
10
|
+
* @returns {{ superpowers: boolean, ruflo: boolean, claudeCode: boolean }}
|
|
11
|
+
*/
|
|
12
|
+
export function detectIntegrations({ cwd = process.cwd(), home = os.homedir() } = {}) {
|
|
13
|
+
return {
|
|
14
|
+
superpowers: _detectSuperpowers(home),
|
|
15
|
+
ruflo: _detectRuflo(cwd, home),
|
|
16
|
+
claudeCode: _detectClaudeCode(home),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function _detectSuperpowers(home) {
|
|
21
|
+
const pluginsDir = path.join(home, '.claude', 'plugins');
|
|
22
|
+
if (!fs.existsSync(pluginsDir)) return false;
|
|
23
|
+
try {
|
|
24
|
+
return fs.readdirSync(pluginsDir).some(d => d.toLowerCase().includes('superpowers'));
|
|
25
|
+
} catch { return false; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function _detectRuflo(cwd, home) {
|
|
29
|
+
const candidates = [
|
|
30
|
+
path.join(cwd, 'ruflo.config.js'),
|
|
31
|
+
path.join(cwd, 'ruflo.config.ts'),
|
|
32
|
+
path.join(cwd, 'ruflo.config.json'),
|
|
33
|
+
path.join(cwd, '.ruflo'),
|
|
34
|
+
path.join(home, '.ruflo'),
|
|
35
|
+
];
|
|
36
|
+
return candidates.some(p => fs.existsSync(p));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _detectClaudeCode(home) {
|
|
40
|
+
return fs.existsSync(path.join(home, '.claude'));
|
|
41
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// lib/wizard/project-detector.js
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
const FILE_SIGNALS = [
|
|
6
|
+
{ file: 'go.mod', lang: 'go' },
|
|
7
|
+
{ file: 'Cargo.toml', lang: 'rust' },
|
|
8
|
+
{ file: 'pom.xml', lang: 'java' },
|
|
9
|
+
{ file: 'build.gradle', lang: 'java' },
|
|
10
|
+
{ file: 'build.gradle.kts', lang: 'kotlin' },
|
|
11
|
+
{ file: 'pyproject.toml', lang: 'python' },
|
|
12
|
+
{ file: 'requirements.txt', lang: 'python' },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const EXT_SIGNALS = {
|
|
16
|
+
'.kt': 'kotlin', '.kts': 'kotlin',
|
|
17
|
+
'.java': 'java',
|
|
18
|
+
'.py': 'python',
|
|
19
|
+
'.ts': 'typescript', '.tsx': 'typescript',
|
|
20
|
+
'.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript',
|
|
21
|
+
'.rs': 'rust',
|
|
22
|
+
'.go': 'go',
|
|
23
|
+
'.rb': 'ruby',
|
|
24
|
+
'.swift': 'swift',
|
|
25
|
+
'.dart': 'dart',
|
|
26
|
+
'.cs': 'csharp',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const FRAMEWORK_SIGNALS = {
|
|
30
|
+
python: { fastapi: 'FastAPI', django: 'Django', flask: 'Flask', pytest: 'testing' },
|
|
31
|
+
javascript: { express: 'Express', next: 'Next.js', react: 'React', vue: 'Vue', '@nestjs': 'NestJS' },
|
|
32
|
+
typescript: { express: 'Express', next: 'Next.js', react: 'React', vue: 'Vue', '@nestjs': 'NestJS' },
|
|
33
|
+
java: { 'springframework': 'Spring Boot', micronaut: 'Micronaut' },
|
|
34
|
+
kotlin: { 'springframework': 'Spring Boot', ktor: 'Ktor' },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Scans cwd for language and framework signals.
|
|
39
|
+
* @returns {{ languages: string[], frameworks: string[], signals: string[] }}
|
|
40
|
+
*/
|
|
41
|
+
export function detect(cwd = process.cwd()) {
|
|
42
|
+
const languages = new Set();
|
|
43
|
+
const frameworks = new Set();
|
|
44
|
+
const signals = [];
|
|
45
|
+
|
|
46
|
+
for (const { file, lang } of FILE_SIGNALS) {
|
|
47
|
+
if (fs.existsSync(path.join(cwd, file))) {
|
|
48
|
+
languages.add(lang);
|
|
49
|
+
signals.push(file);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Scan cwd + one level deep for extension signals
|
|
54
|
+
try {
|
|
55
|
+
for (const entry of fs.readdirSync(cwd, { withFileTypes: true })) {
|
|
56
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
57
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
58
|
+
if (EXT_SIGNALS[ext]) languages.add(EXT_SIGNALS[ext]);
|
|
59
|
+
if (entry.isDirectory()) {
|
|
60
|
+
try {
|
|
61
|
+
for (const sub of fs.readdirSync(path.join(cwd, entry.name))) {
|
|
62
|
+
const subExt = path.extname(sub).toLowerCase();
|
|
63
|
+
if (EXT_SIGNALS[subExt]) languages.add(EXT_SIGNALS[subExt]);
|
|
64
|
+
}
|
|
65
|
+
} catch { /* skip */ }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch { /* skip */ }
|
|
69
|
+
|
|
70
|
+
// Detect frameworks from package.json
|
|
71
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
72
|
+
if (fs.existsSync(pkgPath)) {
|
|
73
|
+
try {
|
|
74
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
75
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
76
|
+
for (const lang of ['javascript', 'typescript']) {
|
|
77
|
+
if (!languages.has(lang)) continue;
|
|
78
|
+
for (const [dep, label] of Object.entries(FRAMEWORK_SIGNALS[lang] ?? {})) {
|
|
79
|
+
if (Object.keys(deps).some(d => d.includes(dep))) frameworks.add(label);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (Object.keys(deps).includes('typescript')) languages.add('typescript');
|
|
83
|
+
} catch { /* skip */ }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Detect Python / JVM frameworks from text files
|
|
87
|
+
for (const lang of ['python', 'java', 'kotlin']) {
|
|
88
|
+
if (!languages.has(lang)) continue;
|
|
89
|
+
for (const f of ['requirements.txt', 'pyproject.toml', 'pom.xml', 'build.gradle', 'build.gradle.kts']) {
|
|
90
|
+
try {
|
|
91
|
+
const content = fs.readFileSync(path.join(cwd, f), 'utf8').toLowerCase();
|
|
92
|
+
for (const [dep, label] of Object.entries(FRAMEWORK_SIGNALS[lang] ?? {})) {
|
|
93
|
+
if (content.includes(dep.toLowerCase())) frameworks.add(label);
|
|
94
|
+
}
|
|
95
|
+
} catch { /* skip */ }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { languages: [...languages], frameworks: [...frameworks], signals };
|
|
100
|
+
}
|