@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,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts individual principles from a search result chunk.
|
|
3
|
+
* A chunk is typically the content of one XML tag section.
|
|
4
|
+
* Principles are bullet points, numbered items, or paragraphs.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} text - the chunk text
|
|
7
|
+
* @param {object} metadata - chunk metadata (name, type, section, etc.)
|
|
8
|
+
* @returns {Array<{principle: string, context: string, source: string, section: string}>}
|
|
9
|
+
*/
|
|
10
|
+
export function extractPrinciples(text, metadata = {}) {
|
|
11
|
+
if (!text || typeof text !== 'string') return [];
|
|
12
|
+
|
|
13
|
+
const source = metadata.name ?? metadata.title ?? 'unknown';
|
|
14
|
+
const section = metadata.type ?? metadata.originalTag ?? 'content';
|
|
15
|
+
const lines = text.split('\n');
|
|
16
|
+
|
|
17
|
+
const principles = [];
|
|
18
|
+
|
|
19
|
+
// Strategy 1: Bullet points and numbered items
|
|
20
|
+
let currentPrinciple = null;
|
|
21
|
+
let currentContext = [];
|
|
22
|
+
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
const trimmed = line.trim();
|
|
25
|
+
if (!trimmed) {
|
|
26
|
+
// Blank line — flush current principle
|
|
27
|
+
if (currentPrinciple) {
|
|
28
|
+
principles.push({
|
|
29
|
+
principle: currentPrinciple,
|
|
30
|
+
context: currentContext.join(' ').trim(),
|
|
31
|
+
source,
|
|
32
|
+
section,
|
|
33
|
+
});
|
|
34
|
+
currentPrinciple = null;
|
|
35
|
+
currentContext = [];
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// New bullet/numbered item
|
|
41
|
+
const bulletMatch = trimmed.match(/^[-*]\s+\*?\*?(.+?)(\*?\*?\s*[:—–-]\s*(.+))?$/);
|
|
42
|
+
const numberedMatch = trimmed.match(/^\d+\.\s+\*?\*?(.+?)(\*?\*?\s*[:—–-]\s*(.+))?$/);
|
|
43
|
+
const boldMatch = trimmed.match(/^\*\*(.+?)\*\*\s*[:—–-]?\s*(.*)/);
|
|
44
|
+
|
|
45
|
+
if (bulletMatch || numberedMatch || boldMatch) {
|
|
46
|
+
// Flush previous
|
|
47
|
+
if (currentPrinciple) {
|
|
48
|
+
principles.push({
|
|
49
|
+
principle: currentPrinciple,
|
|
50
|
+
context: currentContext.join(' ').trim(),
|
|
51
|
+
source,
|
|
52
|
+
section,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const match = boldMatch || bulletMatch || numberedMatch;
|
|
57
|
+
currentPrinciple = (match[1] ?? '').replace(/\*\*/g, '').trim();
|
|
58
|
+
currentContext = match[3] ? [match[3].trim()] : match[2] ? [match[2].replace(/\*\*/g, '').trim()] : [];
|
|
59
|
+
} else if (currentPrinciple) {
|
|
60
|
+
// Continuation line — add to context
|
|
61
|
+
currentContext.push(trimmed);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Flush last
|
|
66
|
+
if (currentPrinciple) {
|
|
67
|
+
principles.push({
|
|
68
|
+
principle: currentPrinciple,
|
|
69
|
+
context: currentContext.join(' ').trim(),
|
|
70
|
+
source,
|
|
71
|
+
section,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Strategy 2: If no structured items found, treat the whole chunk as one principle
|
|
76
|
+
if (principles.length === 0 && text.trim().length > 0) {
|
|
77
|
+
// Take first sentence or first 150 chars as the principle
|
|
78
|
+
const firstSentence = text.trim().split(/[.!?]\s/)[0];
|
|
79
|
+
const principle = firstSentence.length > 150 ? firstSentence.slice(0, 147) + '...' : firstSentence;
|
|
80
|
+
principles.push({
|
|
81
|
+
principle,
|
|
82
|
+
context: text.trim().slice(principle.length).trim().slice(0, 200),
|
|
83
|
+
source,
|
|
84
|
+
section,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return principles;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Extracts principles from multiple search results.
|
|
93
|
+
* Deduplicates by principle text.
|
|
94
|
+
*
|
|
95
|
+
* @param {Array<{text: string, metadata: object, score: number}>} results
|
|
96
|
+
* @param {number} [maxPrinciples=5] - max total principles to return
|
|
97
|
+
* @returns {Array<{principle: string, context: string, source: string, section: string}>}
|
|
98
|
+
*/
|
|
99
|
+
export function extractFromResults(results, maxPrinciples = 5) {
|
|
100
|
+
const all = [];
|
|
101
|
+
const seen = new Set();
|
|
102
|
+
|
|
103
|
+
for (const result of results) {
|
|
104
|
+
const principles = extractPrinciples(result.text, result.metadata);
|
|
105
|
+
for (const p of principles) {
|
|
106
|
+
const key = p.principle.toLowerCase().slice(0, 50);
|
|
107
|
+
if (!seen.has(key)) {
|
|
108
|
+
seen.add(key);
|
|
109
|
+
all.push(p);
|
|
110
|
+
}
|
|
111
|
+
if (all.length >= maxPrinciples) return all;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return all;
|
|
116
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseImports, detectLanguage } from './import-parser.js';
|
|
4
|
+
import { GapDetector } from './gap-detector.js';
|
|
5
|
+
|
|
6
|
+
const SKIP_DIRS = new Set([
|
|
7
|
+
'node_modules', '.git', '.booklib', 'dist', 'build', 'vendor',
|
|
8
|
+
'.next', '__pycache__', '.venv', 'venv',
|
|
9
|
+
'out', '.claude', '.idea', '.vscode', '.agents',
|
|
10
|
+
'.junie', '.specify', '.gemini', '.github',
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Icon library packages whose imports inflate API counts.
|
|
15
|
+
* Individual icon imports (e.g., `import { ArrowLeft } from 'lucide-react'`)
|
|
16
|
+
* are not meaningful API surface to track.
|
|
17
|
+
*/
|
|
18
|
+
const ICON_LIBRARIES = new Set([
|
|
19
|
+
'lucide-react', 'lucide-vue', 'lucide-svelte', 'lucide',
|
|
20
|
+
'@heroicons/react', '@heroicons/vue',
|
|
21
|
+
'react-icons',
|
|
22
|
+
'@phosphor-icons/react', '@phosphor-icons/vue',
|
|
23
|
+
'@tabler/icons-react', '@tabler/icons-vue',
|
|
24
|
+
'@radix-ui/react-icons',
|
|
25
|
+
'ionicons',
|
|
26
|
+
'@mdi/js', '@mdi/react',
|
|
27
|
+
'feather-icons', 'react-feather',
|
|
28
|
+
]);
|
|
29
|
+
const CODE_EXTENSIONS = /\.(js|mjs|cjs|ts|tsx|jsx|py|go|rs|java|kt|kts|rb|php|cs|swift|dart)$/i;
|
|
30
|
+
const MAX_FILES = 500;
|
|
31
|
+
const MAX_FILE_SIZE = 500_000;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Cross-references gap detection with import detection to identify
|
|
35
|
+
* exactly which files and APIs in the project are affected by
|
|
36
|
+
* post-training knowledge gaps.
|
|
37
|
+
*/
|
|
38
|
+
export class ProjectAnalyzer {
|
|
39
|
+
constructor(opts = {}) {
|
|
40
|
+
this.gapDetector = opts.gapDetector ?? new GapDetector(opts);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Full project analysis: find post-training deps, then find which
|
|
45
|
+
* source files import APIs from those deps.
|
|
46
|
+
* @param {string} projectDir
|
|
47
|
+
* @returns {Promise<{affected: Array, totalFiles: number, totalApis: number, gaps: object}>}
|
|
48
|
+
*/
|
|
49
|
+
async analyze(projectDir) {
|
|
50
|
+
const gaps = await this.gapDetector.detect(projectDir);
|
|
51
|
+
if (gaps.postTraining.length === 0) {
|
|
52
|
+
return { affected: [], totalFiles: 0, totalApis: 0, gaps };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const postTrainingDeps = new Map();
|
|
56
|
+
// Exclude the project itself so its own source files aren't flagged as "affected"
|
|
57
|
+
const selfName = readProjectName(projectDir);
|
|
58
|
+
for (const dep of gaps.postTraining) {
|
|
59
|
+
const normalized = normalizePkgName(dep.name);
|
|
60
|
+
if (selfName && normalized === selfName) continue;
|
|
61
|
+
|
|
62
|
+
postTrainingDeps.set(normalized, dep);
|
|
63
|
+
|
|
64
|
+
// Maven/Gradle deps use groupId:artifactId (e.g. com.google.code.gson:gson).
|
|
65
|
+
// Java imports use dot-notation (e.g. com.google.gson.Gson -> pkg com.google.gson).
|
|
66
|
+
// Add artifactId and groupId as extra keys so the lookup can match.
|
|
67
|
+
if (dep.name.includes(':')) {
|
|
68
|
+
const [groupId, artifactId] = dep.name.split(':');
|
|
69
|
+
if (artifactId) postTrainingDeps.set(normalizePkgName(artifactId), dep);
|
|
70
|
+
if (groupId) postTrainingDeps.set(normalizePkgName(groupId), dep);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const sourceFiles = findSourceFiles(projectDir);
|
|
75
|
+
const affected = [];
|
|
76
|
+
|
|
77
|
+
for (const filePath of sourceFiles) {
|
|
78
|
+
const language = detectLanguage(filePath);
|
|
79
|
+
if (!language) continue;
|
|
80
|
+
|
|
81
|
+
let code;
|
|
82
|
+
try {
|
|
83
|
+
const stat = fs.statSync(filePath);
|
|
84
|
+
if (stat.size > MAX_FILE_SIZE) continue;
|
|
85
|
+
code = fs.readFileSync(filePath, 'utf8');
|
|
86
|
+
} catch { continue; }
|
|
87
|
+
|
|
88
|
+
const imports = parseImports(code, language);
|
|
89
|
+
|
|
90
|
+
for (const imp of imports) {
|
|
91
|
+
const normalized = normalizePkgName(imp.module);
|
|
92
|
+
let dep = postTrainingDeps.get(normalized);
|
|
93
|
+
|
|
94
|
+
// For Java/Kotlin imports like com.google.gson, also try the last segment
|
|
95
|
+
// (e.g. "gson") which may match a Maven artifactId key.
|
|
96
|
+
if (!dep && (language === 'java' || language === 'kotlin') && imp.module.includes('.')) {
|
|
97
|
+
const lastSegment = imp.module.split('.').pop();
|
|
98
|
+
dep = postTrainingDeps.get(normalizePkgName(lastSegment));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// PHP: `illuminate/support` (from import) may not match dep `laravel/framework`
|
|
102
|
+
// directly. Check if any dep name contains the import as a segment.
|
|
103
|
+
if (!dep && language === 'php') {
|
|
104
|
+
const importSegment = normalized.split('-')[0]; // first segment, e.g. "illuminate"
|
|
105
|
+
for (const [depKey, depVal] of postTrainingDeps) {
|
|
106
|
+
if (depKey.includes(importSegment)) {
|
|
107
|
+
dep = depVal;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!dep) continue;
|
|
114
|
+
|
|
115
|
+
const apis = extractApiNames(code, imp.module, language);
|
|
116
|
+
const relPath = path.relative(projectDir, filePath);
|
|
117
|
+
|
|
118
|
+
const existing = affected.find(a => a.file === relPath && a.dep.name === dep.name);
|
|
119
|
+
if (existing) {
|
|
120
|
+
for (const api of apis) {
|
|
121
|
+
if (!existing.apis.includes(api)) existing.apis.push(api);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
affected.push({
|
|
125
|
+
file: relPath,
|
|
126
|
+
dep: {
|
|
127
|
+
name: dep.name,
|
|
128
|
+
version: dep.version,
|
|
129
|
+
ecosystem: dep.ecosystem,
|
|
130
|
+
publishDate: dep.publishDate,
|
|
131
|
+
},
|
|
132
|
+
apis,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Separate icon library imports from framework APIs
|
|
139
|
+
let iconApis = 0;
|
|
140
|
+
let frameworkApis = 0;
|
|
141
|
+
for (const entry of affected) {
|
|
142
|
+
const depName = normalizePkgName(entry.dep.name);
|
|
143
|
+
const isIcon = ICON_LIBRARIES.has(entry.dep.name) ||
|
|
144
|
+
[...ICON_LIBRARIES].some(lib => depName === normalizePkgName(lib));
|
|
145
|
+
entry.isIconLibrary = isIcon;
|
|
146
|
+
if (isIcon) {
|
|
147
|
+
iconApis += entry.apis.length;
|
|
148
|
+
} else {
|
|
149
|
+
frameworkApis += entry.apis.length;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const totalApis = frameworkApis;
|
|
154
|
+
return { affected, totalFiles: affected.length, totalApis, iconApis, frameworkApis, gaps };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Read the project's own name from the root manifest file.
|
|
160
|
+
* Returns normalized name or null if not found.
|
|
161
|
+
* @param {string} projectDir
|
|
162
|
+
* @returns {string|null}
|
|
163
|
+
*/
|
|
164
|
+
function readProjectName(projectDir) {
|
|
165
|
+
// package.json (npm)
|
|
166
|
+
try {
|
|
167
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json'), 'utf8'));
|
|
168
|
+
if (pkg.name) return normalizePkgName(pkg.name);
|
|
169
|
+
} catch { /* no package.json */ }
|
|
170
|
+
|
|
171
|
+
// pyproject.toml (Python)
|
|
172
|
+
try {
|
|
173
|
+
const toml = fs.readFileSync(path.join(projectDir, 'pyproject.toml'), 'utf8');
|
|
174
|
+
const nameMatch = toml.match(/\[project\][\s\S]*?name\s*=\s*"([^"]+)"/);
|
|
175
|
+
if (nameMatch) return normalizePkgName(nameMatch[1]);
|
|
176
|
+
const poetryMatch = toml.match(/\[tool\.poetry\][\s\S]*?name\s*=\s*"([^"]+)"/);
|
|
177
|
+
if (poetryMatch) return normalizePkgName(poetryMatch[1]);
|
|
178
|
+
} catch { /* no pyproject.toml */ }
|
|
179
|
+
|
|
180
|
+
// Cargo.toml (Rust)
|
|
181
|
+
try {
|
|
182
|
+
const cargo = fs.readFileSync(path.join(projectDir, 'Cargo.toml'), 'utf8');
|
|
183
|
+
const nameMatch = cargo.match(/\[package\][\s\S]*?name\s*=\s*"([^"]+)"/);
|
|
184
|
+
if (nameMatch) return normalizePkgName(nameMatch[1]);
|
|
185
|
+
} catch { /* no Cargo.toml */ }
|
|
186
|
+
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Normalize package name for cross-ecosystem matching.
|
|
192
|
+
* Python deps use hyphens (typing-inspection) but imports use underscores (typing_inspection).
|
|
193
|
+
* Rust crates have the same mismatch (tokio-tungstenite vs tokio_tungstenite).
|
|
194
|
+
* Ruby requires use slashes (dry/types) but gems use hyphens (dry-types).
|
|
195
|
+
*/
|
|
196
|
+
function normalizePkgName(name) {
|
|
197
|
+
return name.toLowerCase().replace(/[-_/]+/g, '-');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Escape special regex characters in a string. */
|
|
201
|
+
function escapeRegex(str) {
|
|
202
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Extract specific API/function names imported from a module.
|
|
207
|
+
* Looks for destructured imports like: import { cacheLife, after } from 'next/cache'
|
|
208
|
+
* @param {string} code - full source code
|
|
209
|
+
* @param {string} moduleName - the top-level package name
|
|
210
|
+
* @param {string} language - language key
|
|
211
|
+
* @returns {string[]} specific API names, or [moduleName] if none found
|
|
212
|
+
*/
|
|
213
|
+
function extractApiNames(code, moduleName, language) {
|
|
214
|
+
const apis = [];
|
|
215
|
+
|
|
216
|
+
if (language === 'js') {
|
|
217
|
+
// Destructured: import { X, Y } from 'module' or 'module/subpath'
|
|
218
|
+
const destructuredRe = new RegExp(
|
|
219
|
+
`import\\s*\\{([^}]+)\\}\\s*from\\s*['"]${escapeRegex(moduleName)}(?:/[^'"]*)?['"]`,
|
|
220
|
+
'gs',
|
|
221
|
+
);
|
|
222
|
+
let match;
|
|
223
|
+
while ((match = destructuredRe.exec(code)) !== null) {
|
|
224
|
+
const names = match[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
|
|
225
|
+
apis.push(...names);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Default: import React from 'react'
|
|
229
|
+
const defaultRe = new RegExp(
|
|
230
|
+
`import\\s+(\\w+)\\s+from\\s*['"]${escapeRegex(moduleName)}['"]`,
|
|
231
|
+
'g',
|
|
232
|
+
);
|
|
233
|
+
while ((match = defaultRe.exec(code)) !== null) {
|
|
234
|
+
apis.push(match[1]);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Require destructured: const { X, Y } = require('module')
|
|
238
|
+
const requireRe = new RegExp(
|
|
239
|
+
`\\{([^}]+)\\}\\s*=\\s*require\\s*\\(\\s*['"]${escapeRegex(moduleName)}['"]`,
|
|
240
|
+
'g',
|
|
241
|
+
);
|
|
242
|
+
while ((match = requireRe.exec(code)) !== null) {
|
|
243
|
+
const names = match[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
|
|
244
|
+
apis.push(...names);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (language === 'python') {
|
|
249
|
+
// from module import X, Y, Z
|
|
250
|
+
const fromRe = new RegExp(
|
|
251
|
+
`from\\s+${escapeRegex(moduleName)}(?:\\.\\S+)?\\s+import\\s+([^\\n]+)`,
|
|
252
|
+
'g',
|
|
253
|
+
);
|
|
254
|
+
let match;
|
|
255
|
+
while ((match = fromRe.exec(code)) !== null) {
|
|
256
|
+
const names = match[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
|
|
257
|
+
apis.push(...names);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (language === 'go') {
|
|
262
|
+
// Match pkg.FuncName( usage — extract FuncName
|
|
263
|
+
const lastPart = moduleName.split('/').pop();
|
|
264
|
+
const goRe = new RegExp(`${escapeRegex(lastPart)}\\.(\\w+)`, 'g');
|
|
265
|
+
let match;
|
|
266
|
+
while ((match = goRe.exec(code)) !== null) {
|
|
267
|
+
apis.push(match[1]);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (language === 'rust') {
|
|
272
|
+
// use crate::module::{Type1, Type2}
|
|
273
|
+
const rustRe = new RegExp(
|
|
274
|
+
`use\\s+${escapeRegex(moduleName)}(?:::\\w+)*::\\{([^}]+)\\}`,
|
|
275
|
+
'gs',
|
|
276
|
+
);
|
|
277
|
+
let match;
|
|
278
|
+
while ((match = rustRe.exec(code)) !== null) {
|
|
279
|
+
const names = match[1].split(',').map(n => n.trim()).filter(Boolean);
|
|
280
|
+
apis.push(...names);
|
|
281
|
+
}
|
|
282
|
+
// use module::SpecificType;
|
|
283
|
+
const rustSingleRe = new RegExp(
|
|
284
|
+
`use\\s+${escapeRegex(moduleName)}(?:::\\w+)*::(\\w+)\\s*;`,
|
|
285
|
+
'g',
|
|
286
|
+
);
|
|
287
|
+
while ((match = rustSingleRe.exec(code)) !== null) {
|
|
288
|
+
apis.push(match[1]);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (language === 'java' || language === 'kotlin') {
|
|
293
|
+
// import com.google.gson.Gson — extract Gson (last segment)
|
|
294
|
+
const javaRe = new RegExp(
|
|
295
|
+
`import\\s+(?:static\\s+)?${escapeRegex(moduleName)}\\.([\\w.]+)`,
|
|
296
|
+
'gm',
|
|
297
|
+
);
|
|
298
|
+
let match;
|
|
299
|
+
while ((match = javaRe.exec(code)) !== null) {
|
|
300
|
+
// Take the last segment: com.google.gson.Gson -> Gson
|
|
301
|
+
const segments = match[1].split('.');
|
|
302
|
+
apis.push(segments[segments.length - 1]);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (language === 'php') {
|
|
307
|
+
// use Namespace\Class; — extract Class (last segment)
|
|
308
|
+
// use Namespace\Class as Alias; — extract Alias
|
|
309
|
+
const phpRe = /use\s+[\w\\]+\\(\w+)(?:\s+as\s+(\w+))?\s*;/g;
|
|
310
|
+
let match;
|
|
311
|
+
while ((match = phpRe.exec(code)) !== null) {
|
|
312
|
+
apis.push(match[2] || match[1]);
|
|
313
|
+
}
|
|
314
|
+
// use Namespace\{Class1, Class2}; — extract all
|
|
315
|
+
const phpGroupRe = /use\s+[\w\\]+\\{([^}]+)}/g;
|
|
316
|
+
while ((match = phpGroupRe.exec(code)) !== null) {
|
|
317
|
+
const names = match[1].split(',').map(n => n.trim().split(/\s+as\s+/).pop().trim()).filter(Boolean);
|
|
318
|
+
apis.push(...names);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const unique = [...new Set(apis)];
|
|
323
|
+
return unique.length > 0 ? unique : [moduleName];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Recursively find source code files in a project directory.
|
|
328
|
+
* Skips node_modules, .git, .booklib, dist, build, vendor, etc.
|
|
329
|
+
* Caps at MAX_FILES for performance.
|
|
330
|
+
* @param {string} dirPath
|
|
331
|
+
* @returns {string[]}
|
|
332
|
+
*/
|
|
333
|
+
function findSourceFiles(dirPath) {
|
|
334
|
+
const results = [];
|
|
335
|
+
|
|
336
|
+
const walk = (dir) => {
|
|
337
|
+
if (results.length >= MAX_FILES) return;
|
|
338
|
+
let entries;
|
|
339
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
340
|
+
for (const entry of entries) {
|
|
341
|
+
if (results.length >= MAX_FILES) return;
|
|
342
|
+
if (entry.isDirectory()) {
|
|
343
|
+
if (!SKIP_DIRS.has(entry.name)) walk(path.join(dir, entry.name));
|
|
344
|
+
} else if (CODE_EXTENSIONS.test(entry.name)) {
|
|
345
|
+
results.push(path.join(dir, entry.name));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
walk(dirPath);
|
|
350
|
+
return results;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export { findSourceFiles, extractApiNames, normalizePkgName, readProjectName };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const STOPWORDS = new Set([
|
|
2
|
+
'a', 'an', 'the', 'in', 'on', 'at', 'to', 'for', 'of', 'and', 'or',
|
|
3
|
+
'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had',
|
|
4
|
+
'do', 'does', 'did', 'will', 'would', 'shall', 'should', 'may', 'might',
|
|
5
|
+
'must', 'can', 'could', 'not', 'no', 'nor', 'so', 'yet', 'both', 'with',
|
|
6
|
+
'about', 'from', 'up', 'down', 'out', 'how', 'what', 'when', 'where',
|
|
7
|
+
'who', 'why', 'which', 'by', 'as', 'if', 'then', 'than', 'too', 'very',
|
|
8
|
+
'just', 'more', 'also', 'its', 'it',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export function extractKeywords(query) {
|
|
12
|
+
const tokens = query
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.split(/[^a-z0-9]+/)
|
|
15
|
+
.filter(token => token.length > 1 && !STOPWORDS.has(token));
|
|
16
|
+
|
|
17
|
+
return tokens;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function expandQuery(query) {
|
|
21
|
+
const keywords = extractKeywords(query);
|
|
22
|
+
|
|
23
|
+
const expanded = [];
|
|
24
|
+
|
|
25
|
+
if (keywords.length > 0) {
|
|
26
|
+
const keywordJoined = keywords.join(' ');
|
|
27
|
+
const candidates = [
|
|
28
|
+
keywordJoined,
|
|
29
|
+
`best practices for ${keywordJoined}`,
|
|
30
|
+
`how to ${keywordJoined}`,
|
|
31
|
+
];
|
|
32
|
+
for (const v of candidates) {
|
|
33
|
+
if (v !== query) expanded.push(v);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
original: query,
|
|
39
|
+
keywords,
|
|
40
|
+
expanded,
|
|
41
|
+
};
|
|
42
|
+
}
|