@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,151 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolveBookLibPaths } from '../paths.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handles project-wide compliance scanning against the entire BookLib library.
|
|
7
|
+
* Uses audit.json rule sets for fast, accurate static analysis.
|
|
8
|
+
* Falls back to skipping files whose skill has no audit.json.
|
|
9
|
+
*/
|
|
10
|
+
export class BookLibScanner {
|
|
11
|
+
constructor() {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Scans a directory and generates a 'Wisdom Compliance' heatmap.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} dirPath - The project directory to scan.
|
|
17
|
+
* @param {object} opts
|
|
18
|
+
* @param {string} opts.mode - 'code' (default) or 'docs' (scans .md/.txt files)
|
|
19
|
+
* @returns {string} - A comprehensive project health report.
|
|
20
|
+
*/
|
|
21
|
+
scan(dirPath, { mode = 'code' } = {}) {
|
|
22
|
+
const files = this.getFiles(dirPath);
|
|
23
|
+
const reports = [];
|
|
24
|
+
const stats = {
|
|
25
|
+
filesScanned: files.length,
|
|
26
|
+
filesMatched: 0,
|
|
27
|
+
filesAudited: 0,
|
|
28
|
+
violations: 0,
|
|
29
|
+
healthyFiles: 0,
|
|
30
|
+
debtBySkill: {},
|
|
31
|
+
noRulesSkills: new Set(),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
console.log(`Starting deep scan of ${files.length} files...`);
|
|
35
|
+
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const skillName = this.detectSkill(file, mode);
|
|
38
|
+
if (!skillName) continue;
|
|
39
|
+
|
|
40
|
+
stats.filesMatched++;
|
|
41
|
+
const auditJsonPath = this._findAuditJson(skillName);
|
|
42
|
+
if (!auditJsonPath) {
|
|
43
|
+
stats.noRulesSkills.add(skillName);
|
|
44
|
+
continue; // skill has no static rules — skip
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let rules;
|
|
48
|
+
try {
|
|
49
|
+
({ rules } = JSON.parse(fs.readFileSync(auditJsonPath, 'utf8')));
|
|
50
|
+
} catch { continue; }
|
|
51
|
+
|
|
52
|
+
const lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
53
|
+
let violationCount = 0;
|
|
54
|
+
for (const rule of rules) {
|
|
55
|
+
const regex = new RegExp(rule.pattern, 'g');
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
if (regex.test(line)) violationCount++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
stats.filesAudited++;
|
|
62
|
+
stats.violations += violationCount;
|
|
63
|
+
if (violationCount === 0) stats.healthyFiles++;
|
|
64
|
+
stats.debtBySkill[skillName] = (stats.debtBySkill[skillName] || 0) + violationCount;
|
|
65
|
+
if (violationCount > 0) {
|
|
66
|
+
reports.push({ file, skill: skillName, violationCount });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return this.formatDashboard(stats, reports);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Detects which BookLib skill is most relevant to a file extension.
|
|
75
|
+
*/
|
|
76
|
+
detectSkill(filePath, mode = 'code') {
|
|
77
|
+
const ext = path.extname(filePath);
|
|
78
|
+
if (mode === 'docs') {
|
|
79
|
+
if (ext === '.md' || ext === '.mdx' || ext === '.mdc') return 'writing-plans';
|
|
80
|
+
if (ext === '.txt') return 'writing-plans';
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (ext === '.kt' || ext === '.kts') return 'effective-kotlin';
|
|
84
|
+
if (ext === '.ts' || ext === '.tsx') return 'effective-typescript';
|
|
85
|
+
if (ext === '.java') return 'effective-java';
|
|
86
|
+
if (ext === '.py') return 'effective-python';
|
|
87
|
+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') return 'clean-code-reviewer';
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Finds audit.json for a skill — checks bundled skills first, then community cache.
|
|
93
|
+
*/
|
|
94
|
+
_findAuditJson(skillName) {
|
|
95
|
+
const { skillsPath, cachePath } = resolveBookLibPaths();
|
|
96
|
+
const bundled = path.join(skillsPath, skillName, 'audit.json');
|
|
97
|
+
if (fs.existsSync(bundled)) return bundled;
|
|
98
|
+
const community = path.join(cachePath, 'skills', skillName, 'audit.json');
|
|
99
|
+
if (fs.existsSync(community)) return community;
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Helper to recursively list files, excluding common noise directories.
|
|
105
|
+
*/
|
|
106
|
+
getFiles(dir) {
|
|
107
|
+
let results = [];
|
|
108
|
+
const list = fs.readdirSync(dir);
|
|
109
|
+
const ignore = ['.git', 'node_modules', '.booklib', '.claude', '.idea', 'dist', 'build', 'coverage', '.cache'];
|
|
110
|
+
|
|
111
|
+
list.forEach(file => {
|
|
112
|
+
if (ignore.includes(file)) return;
|
|
113
|
+
|
|
114
|
+
const fullPath = path.join(dir, file);
|
|
115
|
+
const stat = fs.statSync(fullPath);
|
|
116
|
+
if (stat && stat.isDirectory()) {
|
|
117
|
+
results = results.concat(this.getFiles(fullPath));
|
|
118
|
+
} else {
|
|
119
|
+
results.push(fullPath);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return results;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
formatDashboard(stats, reports) {
|
|
126
|
+
const sortedReports = reports.sort((a, b) => b.violationCount - a.violationCount);
|
|
127
|
+
const topDebt = sortedReports.slice(0, 5);
|
|
128
|
+
|
|
129
|
+
return `
|
|
130
|
+
# 📊 BookLib Wisdom Heatmap
|
|
131
|
+
**Project Health Dashboard**
|
|
132
|
+
|
|
133
|
+
## 📈 Executive Summary
|
|
134
|
+
- **Files Scanned**: ${stats.filesScanned} (${stats.filesMatched} matched a skill lens, ${stats.filesAudited} audited)
|
|
135
|
+
- **Healthy Files**: ${stats.healthyFiles} / ${stats.filesAudited} audited
|
|
136
|
+
- **Total Violations**: ${stats.violations}
|
|
137
|
+
- **Compliance**: ${((stats.healthyFiles / (stats.filesAudited || 1)) * 100).toFixed(1)}% of audited files violation-free
|
|
138
|
+
|
|
139
|
+
## 🛠 Architectural Debt by Skill
|
|
140
|
+
${Object.entries(stats.debtBySkill).map(([skill, count]) => `- **${skill}**: ${count} violations`).join('\n')}
|
|
141
|
+
|
|
142
|
+
## 🚨 Top 5 Refactoring Priorities (High Debt)
|
|
143
|
+
${topDebt.map(r => `- \`${path.relative(process.cwd(), r.file)}\` (${r.violationCount} violations | Lens: ${r.skill})`).join('\n')}
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
> **Note**: This heatmap is based on 'Light Audits' (Static Pattern Detection).
|
|
147
|
+
> Use \`booklib audit <skill> <file>\` for a deep-reasoning review of high-priority files.
|
|
148
|
+
${stats.noRulesSkills.size > 0 ? `> **No static rules loaded for:** ${[...stats.noRulesSkills].join(', ')} — add an \`audit.json\` to enable pattern checks.` : ''}
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { LocalIndex } from 'vectra';
|
|
4
|
+
import { createEmbeddingPipeline } from './embedding-provider.js';
|
|
5
|
+
import { resolveBookLibPaths } from '../paths.js';
|
|
6
|
+
import { BM25Index } from './bm25-index.js';
|
|
7
|
+
import { reciprocalRankFusion } from './rrf.js';
|
|
8
|
+
import { expandQuery } from './query-expander.js';
|
|
9
|
+
import { Reranker } from './reranker.js';
|
|
10
|
+
import { loadEdges, traverseEdges } from './graph.js';
|
|
11
|
+
|
|
12
|
+
const RRF_ORIGINAL_WEIGHT = 2;
|
|
13
|
+
const RRF_EXPANDED_WEIGHT = 1;
|
|
14
|
+
const RERANK_CANDIDATES = 20;
|
|
15
|
+
|
|
16
|
+
export class BookLibSearcher {
|
|
17
|
+
constructor(indexPath) {
|
|
18
|
+
const paths = resolveBookLibPaths();
|
|
19
|
+
this.indexPath = indexPath ?? paths.readIndexPath;
|
|
20
|
+
this.index = new LocalIndex(this.indexPath);
|
|
21
|
+
this.extractor = null;
|
|
22
|
+
this.reranker = new Reranker();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async loadModel() {
|
|
26
|
+
if (!this.extractor) {
|
|
27
|
+
const { extractor } = await createEmbeddingPipeline({ quiet: true });
|
|
28
|
+
this.extractor = extractor;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getEmbedding(text) {
|
|
33
|
+
await this.loadModel();
|
|
34
|
+
const output = await this.extractor(text, { pooling: 'mean', normalize: true });
|
|
35
|
+
return Array.from(output.data);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get bm25Path() {
|
|
39
|
+
return path.join(path.dirname(this.indexPath), 'bm25.json');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get graphFile() {
|
|
43
|
+
return path.join(path.dirname(this.indexPath), 'knowledge', 'graph.jsonl');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async _vectorSearch(query, topK) {
|
|
47
|
+
const vector = await this.getEmbedding(query);
|
|
48
|
+
const results = await this.index.queryItems(vector, '', topK);
|
|
49
|
+
return results.map(r => ({
|
|
50
|
+
score: r.score,
|
|
51
|
+
text: r.item.metadata.text,
|
|
52
|
+
metadata: { ...r.item.metadata, text: undefined },
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async search(query, limit = 5, minScore = 0.5, options = {}) {
|
|
57
|
+
const { useGraph = false } = options;
|
|
58
|
+
if (!(await this.index.isIndexCreated())) {
|
|
59
|
+
throw new Error('Index not found. Please run "booklib index" first.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Fallback: no BM25 index present — use pure vector search (backwards compatible)
|
|
63
|
+
if (!fs.existsSync(this.bm25Path)) {
|
|
64
|
+
const vector = await this.getEmbedding(query);
|
|
65
|
+
const results = await this.index.queryItems(vector, '', limit);
|
|
66
|
+
const filtered = results
|
|
67
|
+
.filter(r => r.score >= minScore)
|
|
68
|
+
.map(r => ({
|
|
69
|
+
score: r.score,
|
|
70
|
+
text: r.item.metadata.text,
|
|
71
|
+
metadata: { ...r.item.metadata, text: undefined },
|
|
72
|
+
}));
|
|
73
|
+
return addDisplayScores(useGraph ? this._appendGraphResults(filtered) : filtered);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const bm25 = BM25Index.load(this.bm25Path);
|
|
77
|
+
const { expanded } = expandQuery(query);
|
|
78
|
+
const allQueries = [query, ...expanded];
|
|
79
|
+
|
|
80
|
+
const resultLists = [];
|
|
81
|
+
const weights = [];
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < allQueries.length; i++) {
|
|
84
|
+
const q = allQueries[i];
|
|
85
|
+
const w = i === 0 ? RRF_ORIGINAL_WEIGHT : RRF_EXPANDED_WEIGHT;
|
|
86
|
+
|
|
87
|
+
const [vecResults, bm25Results] = await Promise.all([
|
|
88
|
+
this._vectorSearch(q, RERANK_CANDIDATES),
|
|
89
|
+
Promise.resolve(bm25.search(q, RERANK_CANDIDATES)),
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
resultLists.push(vecResults, bm25Results);
|
|
93
|
+
weights.push(w, w);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const merged = reciprocalRankFusion(resultLists, { weights });
|
|
97
|
+
const candidates = merged.slice(0, RERANK_CANDIDATES);
|
|
98
|
+
const reranked = await this.reranker.rerank(query, candidates);
|
|
99
|
+
|
|
100
|
+
const filtered = reranked.filter(r => r.score >= minScore);
|
|
101
|
+
|
|
102
|
+
// Sibling expansion: when ≥50% of a section's chunks match, pull in the rest.
|
|
103
|
+
// This prevents fragmented results — if the search finds "import createClient",
|
|
104
|
+
// expansion pulls in the full config block and services list from the same section.
|
|
105
|
+
let withSiblings;
|
|
106
|
+
try {
|
|
107
|
+
withSiblings = this._expandSiblings(filtered, bm25);
|
|
108
|
+
} catch {
|
|
109
|
+
withSiblings = filtered;
|
|
110
|
+
}
|
|
111
|
+
const results = withSiblings.slice(0, limit);
|
|
112
|
+
|
|
113
|
+
return addDisplayScores(useGraph ? this._appendGraphResults(results) : results);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Expands search results by pulling in missing sibling chunks when ≥50%
|
|
118
|
+
* of a parent section's chunks already appear in results.
|
|
119
|
+
* @param {Array} results - search results with metadata.parentId, siblingIndex, siblingCount
|
|
120
|
+
* @param {BM25Index} bm25 - BM25 index to find sibling chunks
|
|
121
|
+
* @returns {Array} expanded results with siblings inserted after the first match
|
|
122
|
+
*/
|
|
123
|
+
_expandSiblings(results, bm25) {
|
|
124
|
+
// Group results by parentId
|
|
125
|
+
const byParent = new Map();
|
|
126
|
+
for (const r of results) {
|
|
127
|
+
const pid = r.metadata?.parentId;
|
|
128
|
+
if (!pid) continue;
|
|
129
|
+
if (!byParent.has(pid)) byParent.set(pid, []);
|
|
130
|
+
byParent.get(pid).push(r);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Find parents where ≥50% of siblings are in results
|
|
134
|
+
const expandIds = new Set();
|
|
135
|
+
for (const [pid, hits] of byParent) {
|
|
136
|
+
const total = hits[0].metadata?.siblingCount ?? 1;
|
|
137
|
+
if (total <= 1) continue;
|
|
138
|
+
if (hits.length / total >= 0.5) expandIds.add(pid);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (expandIds.size === 0) return results;
|
|
142
|
+
|
|
143
|
+
// Find missing siblings from BM25 index's internal document store
|
|
144
|
+
const allDocs = bm25._docs ?? [];
|
|
145
|
+
const siblingPool = new Map(); // parentId -> [docs sorted by siblingIndex]
|
|
146
|
+
for (const doc of allDocs) {
|
|
147
|
+
const pid = doc.metadata?.parentId;
|
|
148
|
+
if (!pid || !expandIds.has(pid)) continue;
|
|
149
|
+
if (!siblingPool.has(pid)) siblingPool.set(pid, []);
|
|
150
|
+
siblingPool.get(pid).push(doc);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Sort siblings by siblingIndex
|
|
154
|
+
for (const [, siblings] of siblingPool) {
|
|
155
|
+
siblings.sort((a, b) => (a.metadata?.siblingIndex ?? 0) - (b.metadata?.siblingIndex ?? 0));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Build expanded results: insert missing siblings after the first match
|
|
159
|
+
const seenTexts = new Set(results.map(r => r.text));
|
|
160
|
+
const expanded = [];
|
|
161
|
+
const insertedParents = new Set();
|
|
162
|
+
|
|
163
|
+
for (const r of results) {
|
|
164
|
+
expanded.push(r);
|
|
165
|
+
const pid = r.metadata?.parentId;
|
|
166
|
+
if (pid && expandIds.has(pid) && !insertedParents.has(pid)) {
|
|
167
|
+
insertedParents.add(pid);
|
|
168
|
+
const siblings = siblingPool.get(pid) ?? [];
|
|
169
|
+
for (const sib of siblings) {
|
|
170
|
+
if (!seenTexts.has(sib.text)) {
|
|
171
|
+
expanded.push({
|
|
172
|
+
text: sib.text,
|
|
173
|
+
score: r.score * 0.95, // slightly lower than the matched sibling
|
|
174
|
+
metadata: sib.metadata,
|
|
175
|
+
_expanded: true,
|
|
176
|
+
});
|
|
177
|
+
seenTexts.add(sib.text);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return expanded;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_appendGraphResults(results) {
|
|
187
|
+
const GRAPH_EDGE_TYPES = new Set(['see-also', 'applies-to', 'extends']);
|
|
188
|
+
let edges;
|
|
189
|
+
try {
|
|
190
|
+
edges = loadEdges({ graphFile: this.graphFile });
|
|
191
|
+
} catch {
|
|
192
|
+
return results;
|
|
193
|
+
}
|
|
194
|
+
if (edges.length === 0) return results;
|
|
195
|
+
|
|
196
|
+
const seenIds = new Set(
|
|
197
|
+
results.map(r => r.metadata?.name ?? r.metadata?.id).filter(Boolean)
|
|
198
|
+
);
|
|
199
|
+
const graphLinked = [];
|
|
200
|
+
|
|
201
|
+
for (const result of results) {
|
|
202
|
+
const nodeId = result.metadata?.name ?? result.metadata?.id;
|
|
203
|
+
if (!nodeId) continue;
|
|
204
|
+
|
|
205
|
+
for (const { id: neighborId, edge } of traverseEdges(nodeId, edges, 1)) {
|
|
206
|
+
if (!GRAPH_EDGE_TYPES.has(edge.type)) continue;
|
|
207
|
+
if (seenIds.has(neighborId)) continue;
|
|
208
|
+
seenIds.add(neighborId);
|
|
209
|
+
graphLinked.push({
|
|
210
|
+
score: 0,
|
|
211
|
+
text: '',
|
|
212
|
+
metadata: { name: neighborId, source: 'graph', edgeType: edge.type },
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return [...results, ...graphLinked];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function addDisplayScores(results) {
|
|
222
|
+
return results.map((r, i) => ({ ...r, displayScore: r.displayScore ?? Math.round(100 / (i + 1)) }));
|
|
223
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Coordinates handoff sessions across multiple agents.
|
|
7
|
+
* Enables session merging, lineage tracking, and cross-agent insights.
|
|
8
|
+
*/
|
|
9
|
+
export class BookLibSessionCoordinator {
|
|
10
|
+
constructor(handoffDir = path.join(process.cwd(), '.booklib', 'sessions')) {
|
|
11
|
+
this.handoffDir = handoffDir;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Lists all available sessions with metadata.
|
|
16
|
+
*/
|
|
17
|
+
listAllSessions() {
|
|
18
|
+
if (!fs.existsSync(this.handoffDir)) return [];
|
|
19
|
+
|
|
20
|
+
const sessions = fs.readdirSync(this.handoffDir)
|
|
21
|
+
.filter(f => f.endsWith('.md'))
|
|
22
|
+
.map(f => {
|
|
23
|
+
const sessionName = f.replace('.md', '');
|
|
24
|
+
const filePath = path.join(this.handoffDir, f);
|
|
25
|
+
const stats = fs.statSync(filePath);
|
|
26
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
27
|
+
|
|
28
|
+
// Extract key info without full XML parsing (faster)
|
|
29
|
+
const goalMatch = content.match(/<goal>(.*?)<\/goal>/);
|
|
30
|
+
const progressMatch = content.match(/<progress>(.*?)<\/progress>/);
|
|
31
|
+
const branchMatch = content.match(/<branch>(.*?)<\/branch>/);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
id: sessionName,
|
|
35
|
+
goal: goalMatch ? goalMatch[1] : 'Unknown',
|
|
36
|
+
progress: progressMatch ? progressMatch[1] : 'Unknown',
|
|
37
|
+
branch: branchMatch ? branchMatch[1] : 'Unknown',
|
|
38
|
+
timestamp: stats.mtime,
|
|
39
|
+
size: stats.size
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return sessions.sort((a, b) => b.timestamp - a.timestamp);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Merges multiple sessions into a combined session.
|
|
48
|
+
* Useful for: combining work from parallel agents, multi-agent reviews, etc.
|
|
49
|
+
*
|
|
50
|
+
* Usage:
|
|
51
|
+
* coordinator.mergeSessions(['agent-python', 'agent-kotlin'], 'combined-review')
|
|
52
|
+
* → Creates combined-review.md with insights from both
|
|
53
|
+
*/
|
|
54
|
+
mergeSessions(sessionIds, outputSessionId) {
|
|
55
|
+
const sessions = sessionIds.map(id => {
|
|
56
|
+
const filePath = path.join(this.handoffDir, `${id}.md`);
|
|
57
|
+
if (!fs.existsSync(filePath)) {
|
|
58
|
+
throw new Error(`Session not found: ${id}`);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
id,
|
|
62
|
+
content: fs.readFileSync(filePath, 'utf8')
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Extract metadata from all sessions
|
|
67
|
+
const merged = {
|
|
68
|
+
metadata: {
|
|
69
|
+
timestamp: new Date().toISOString(),
|
|
70
|
+
session_id: outputSessionId,
|
|
71
|
+
parent_sessions: sessionIds.join(', '),
|
|
72
|
+
merge_type: 'multi-agent-combined'
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const outputPath = path.join(this.handoffDir, `${outputSessionId}.md`);
|
|
77
|
+
const content = `
|
|
78
|
+
<session_handoff>
|
|
79
|
+
<metadata>
|
|
80
|
+
<timestamp>${merged.metadata.timestamp}</timestamp>
|
|
81
|
+
<session_id>${merged.metadata.session_id}</session_id>
|
|
82
|
+
<parent_sessions>${merged.metadata.parent_sessions}</parent_sessions>
|
|
83
|
+
<merge_type>${merged.metadata.merge_type}</merge_type>
|
|
84
|
+
</metadata>
|
|
85
|
+
|
|
86
|
+
<context>
|
|
87
|
+
<goal>Combined context from multiple agents</goal>
|
|
88
|
+
<progress>Merged from: ${sessionIds.join(', ')}</progress>
|
|
89
|
+
<pending_tasks>Review merged insights below</pending_tasks>
|
|
90
|
+
<note>This session combines work from multiple agents. See merged_agent_contexts below.</note>
|
|
91
|
+
</context>
|
|
92
|
+
|
|
93
|
+
<merged_agent_contexts>
|
|
94
|
+
${sessions.map(s => {
|
|
95
|
+
const goal = s.content.match(/<goal>(.*?)<\/goal>/)?.[1] || 'Unknown';
|
|
96
|
+
const progress = s.content.match(/<progress>(.*?)<\/progress>/)?.[1] || 'Unknown';
|
|
97
|
+
const branch = s.content.match(/<branch>(.*?)<\/branch>/)?.[1] || 'Unknown';
|
|
98
|
+
return ` <agent id="${s.id}">
|
|
99
|
+
<goal>${goal}</goal>
|
|
100
|
+
<progress>${progress}</progress>
|
|
101
|
+
<branch>${branch}</branch>
|
|
102
|
+
</agent>`;
|
|
103
|
+
}).join('\n')}
|
|
104
|
+
</merged_agent_contexts>
|
|
105
|
+
|
|
106
|
+
<cross_agent_insights>
|
|
107
|
+
<note>Each agent contributed unique insights. Review their branches below:</note>
|
|
108
|
+
${sessions.map(s => {
|
|
109
|
+
const commits = s.content.match(/<recent_commit_history>([\s\S]*?)<\/recent_commit_history>/)?.[1] || '';
|
|
110
|
+
return ` <agent_work id="${s.id}">
|
|
111
|
+
${commits.trim()}
|
|
112
|
+
</agent_work>`;
|
|
113
|
+
}).join('\n')}
|
|
114
|
+
</cross_agent_insights>
|
|
115
|
+
|
|
116
|
+
<recovery_instructions>
|
|
117
|
+
<step1>This is a merged session combining: ${sessionIds.join(', ')}</step1>
|
|
118
|
+
<step2>Review merged_agent_contexts above to see what each agent accomplished</step2>
|
|
119
|
+
<step3>Review cross_agent_insights for commits from each agent</step3>
|
|
120
|
+
<step4>Decide: continue on one branch or synthesize both?</step4>
|
|
121
|
+
<step5>If synthesizing: git merge the branches with appropriate strategy</step5>
|
|
122
|
+
</recovery_instructions>
|
|
123
|
+
</session_handoff>
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
fs.writeFileSync(outputPath, content.trim());
|
|
127
|
+
return outputPath;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Tracks session lineage (parent-child relationships).
|
|
132
|
+
* For example: agent-1 creates "main" session, agent-2 branches to "feature-x"
|
|
133
|
+
*
|
|
134
|
+
* Usage:
|
|
135
|
+
* coordinator.trackLineage('main', 'feature-x', 'Agent 2 branched from main')
|
|
136
|
+
* → Records parent-child relationship
|
|
137
|
+
*/
|
|
138
|
+
trackLineage(parentSessionId, childSessionId, reason = '') {
|
|
139
|
+
const lineageFile = path.join(this.handoffDir, '_lineage.json');
|
|
140
|
+
|
|
141
|
+
let lineage = {};
|
|
142
|
+
if (fs.existsSync(lineageFile)) {
|
|
143
|
+
lineage = JSON.parse(fs.readFileSync(lineageFile, 'utf8'));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!lineage[childSessionId]) {
|
|
147
|
+
lineage[childSessionId] = {
|
|
148
|
+
parent: parentSessionId,
|
|
149
|
+
reason,
|
|
150
|
+
createdAt: new Date().toISOString(),
|
|
151
|
+
children: []
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!lineage[parentSessionId]) {
|
|
156
|
+
lineage[parentSessionId] = { children: [] };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!lineage[parentSessionId].children) {
|
|
160
|
+
lineage[parentSessionId].children = [];
|
|
161
|
+
}
|
|
162
|
+
lineage[parentSessionId].children.push(childSessionId);
|
|
163
|
+
|
|
164
|
+
fs.writeFileSync(lineageFile, JSON.stringify(lineage, null, 2));
|
|
165
|
+
return lineage;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Displays session lineage as a tree.
|
|
170
|
+
* Shows parent-child relationships visually.
|
|
171
|
+
*/
|
|
172
|
+
displayLineageTree() {
|
|
173
|
+
const lineageFile = path.join(this.handoffDir, '_lineage.json');
|
|
174
|
+
if (!fs.existsSync(lineageFile)) {
|
|
175
|
+
return 'No session lineage tracked yet. Start with trackLineage().';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const lineage = JSON.parse(fs.readFileSync(lineageFile, 'utf8'));
|
|
179
|
+
const roots = Object.entries(lineage)
|
|
180
|
+
.filter(([_, info]) => !info.parent)
|
|
181
|
+
.map(([id]) => id);
|
|
182
|
+
|
|
183
|
+
let output = 'SESSION LINEAGE TREE\n═══════════════════\n\n';
|
|
184
|
+
|
|
185
|
+
const drawTree = (sessionId, depth = 0) => {
|
|
186
|
+
const info = lineage[sessionId];
|
|
187
|
+
const indent = ' '.repeat(depth);
|
|
188
|
+
const prefix = depth === 0 ? '📍' : '└─';
|
|
189
|
+
|
|
190
|
+
output += `${indent}${prefix} ${sessionId}\n`;
|
|
191
|
+
if (info.reason) output += `${indent} (${info.reason})\n`;
|
|
192
|
+
|
|
193
|
+
if (info.children && info.children.length > 0) {
|
|
194
|
+
info.children.forEach(child => {
|
|
195
|
+
drawTree(child, depth + 1);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
roots.forEach(root => drawTree(root));
|
|
201
|
+
return output;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Gets the full lineage path of a session (all ancestors).
|
|
206
|
+
*
|
|
207
|
+
* Usage:
|
|
208
|
+
* coordinator.getLineagePath('feature-x')
|
|
209
|
+
* → Returns: ['main', 'feature-base', 'feature-x']
|
|
210
|
+
*/
|
|
211
|
+
getLineagePath(sessionId) {
|
|
212
|
+
const lineageFile = path.join(this.handoffDir, '_lineage.json');
|
|
213
|
+
if (!fs.existsSync(lineageFile)) return [sessionId];
|
|
214
|
+
|
|
215
|
+
const lineage = JSON.parse(fs.readFileSync(lineageFile, 'utf8'));
|
|
216
|
+
const path = [sessionId];
|
|
217
|
+
|
|
218
|
+
let current = sessionId;
|
|
219
|
+
while (lineage[current] && lineage[current].parent) {
|
|
220
|
+
path.unshift(lineage[current].parent);
|
|
221
|
+
current = lineage[current].parent;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return path;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Creates a session comparing multiple agents' audits.
|
|
229
|
+
* Useful for: "Compare Python audit vs Kotlin audit of same file"
|
|
230
|
+
*/
|
|
231
|
+
compareAudits(auditSessionIds, targetFile, outputSessionId) {
|
|
232
|
+
const audits = auditSessionIds.map(id => {
|
|
233
|
+
const filePath = path.join(this.handoffDir, `${id}.md`);
|
|
234
|
+
if (!fs.existsSync(filePath)) {
|
|
235
|
+
throw new Error(`Audit session not found: ${id}`);
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
id,
|
|
239
|
+
content: fs.readFileSync(filePath, 'utf8')
|
|
240
|
+
};
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const content = `
|
|
244
|
+
<session_handoff>
|
|
245
|
+
<metadata>
|
|
246
|
+
<timestamp>${new Date().toISOString()}</timestamp>
|
|
247
|
+
<session_id>${outputSessionId}</session_id>
|
|
248
|
+
<type>audit-comparison</type>
|
|
249
|
+
<target_file>${targetFile}</target_file>
|
|
250
|
+
</metadata>
|
|
251
|
+
|
|
252
|
+
<context>
|
|
253
|
+
<goal>Compare audit findings from multiple agents for ${targetFile}</goal>
|
|
254
|
+
<progress>Aggregated ${auditSessionIds.length} audits</progress>
|
|
255
|
+
<pending_tasks>Review and prioritize findings, decide which issues to fix first</pending_tasks>
|
|
256
|
+
</context>
|
|
257
|
+
|
|
258
|
+
<audit_comparison>
|
|
259
|
+
<note>Each agent audited ${targetFile} against their own skill framework</note>
|
|
260
|
+
${audits.map(a => ` <agent_audit id="${a.id}">
|
|
261
|
+
${this._extractContext(a.content)}
|
|
262
|
+
</agent_audit>`).join('\n')}
|
|
263
|
+
</audit_comparison>
|
|
264
|
+
|
|
265
|
+
<recovery_instructions>
|
|
266
|
+
<step1>This session compares audits from: ${auditSessionIds.join(', ')}</step1>
|
|
267
|
+
<step2>Review each agent's findings in audit_comparison above</step2>
|
|
268
|
+
<step3>Note overlapping issues (high priority)</step3>
|
|
269
|
+
<step4>Note unique issues per agent (domain-specific)</step4>
|
|
270
|
+
<step5>Prioritize fixes: overlapping first, then unique critical ones</step5>
|
|
271
|
+
</recovery_instructions>
|
|
272
|
+
</session_handoff>
|
|
273
|
+
`;
|
|
274
|
+
|
|
275
|
+
const outputPath = path.join(this.handoffDir, `${outputSessionId}.md`);
|
|
276
|
+
fs.writeFileSync(outputPath, content.trim());
|
|
277
|
+
return outputPath;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ─── HELPER METHODS ───
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Extracts context snippet from session.
|
|
284
|
+
*/
|
|
285
|
+
_extractContext(content) {
|
|
286
|
+
const goal = content.match(/<goal>(.*?)<\/goal>/)?.[1] || 'Unknown';
|
|
287
|
+
const progress = content.match(/<progress>(.*?)<\/progress>/)?.[1] || 'Unknown';
|
|
288
|
+
const tasks = content.match(/<pending_tasks>(.*?)<\/pending_tasks>/)?.[1] || 'None specified';
|
|
289
|
+
return `Goal: ${goal}\nProgress: ${progress}\nNext: ${tasks}`;
|
|
290
|
+
}
|
|
291
|
+
}
|