@kinqs/brainrouter-mcp-server 0.3.4
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/.env.example +144 -0
- package/README.md +56 -0
- package/agents/README.md +120 -0
- package/agents/code-reviewer.md +97 -0
- package/agents/security-auditor.md +101 -0
- package/agents/test-engineer.md +95 -0
- package/dist/__tests__/agent_mode.test.d.ts +1 -0
- package/dist/__tests__/api-routes.test.d.ts +1 -0
- package/dist/__tests__/api-routes.test.js +170 -0
- package/dist/__tests__/crypto.test.d.ts +1 -0
- package/dist/__tests__/crypto.test.js +28 -0
- package/dist/__tests__/host-integrations.test.d.ts +1 -0
- package/dist/__tests__/host-integrations.test.js +82 -0
- package/dist/__tests__/integration.test.d.ts +1 -0
- package/dist/__tests__/integration.test.js +50 -0
- package/dist/__tests__/loader.test.d.ts +1 -0
- package/dist/__tests__/loader.test.js +89 -0
- package/dist/__tests__/neural-spark.test.d.ts +1 -0
- package/dist/__tests__/neural-spark.test.js +112 -0
- package/dist/__tests__/pagination.test.d.ts +1 -0
- package/dist/__tests__/pagination.test.js +23 -0
- package/dist/__tests__/redaction.test.d.ts +1 -0
- package/dist/__tests__/redaction.test.js +17 -0
- package/dist/__tests__/registry.test.d.ts +1 -0
- package/dist/__tests__/registry.test.js +56 -0
- package/dist/__tests__/retry.test.d.ts +1 -0
- package/dist/__tests__/retry.test.js +30 -0
- package/dist/__tests__/skill-activation.test.d.ts +1 -0
- package/dist/__tests__/skill-activation.test.js +112 -0
- package/dist/__tests__/working-memory.test.d.ts +1 -0
- package/dist/__tests__/working-memory.test.js +200 -0
- package/dist/__tests__/workspace-paths.test.d.ts +1 -0
- package/dist/__tests__/workspace-paths.test.js +56 -0
- package/dist/__tests__/writer.test.d.ts +1 -0
- package/dist/__tests__/writer.test.js +94 -0
- package/dist/api/auth/crypto.d.ts +4 -0
- package/dist/api/auth/crypto.js +54 -0
- package/dist/api/middleware/auth.d.ts +12 -0
- package/dist/api/middleware/auth.js +90 -0
- package/dist/api/pagination.d.ts +18 -0
- package/dist/api/pagination.js +32 -0
- package/dist/api/routes/auth.d.ts +1 -0
- package/dist/api/routes/auth.js +130 -0
- package/dist/api/routes/chat-completions.d.ts +7 -0
- package/dist/api/routes/chat-completions.js +474 -0
- package/dist/api/routes/contradictions.d.ts +1 -0
- package/dist/api/routes/contradictions.js +28 -0
- package/dist/api/routes/evidence.d.ts +1 -0
- package/dist/api/routes/evidence.js +59 -0
- package/dist/api/routes/governance.d.ts +1 -0
- package/dist/api/routes/governance.js +95 -0
- package/dist/api/routes/graph.d.ts +1 -0
- package/dist/api/routes/graph.js +25 -0
- package/dist/api/routes/hooks.d.ts +1 -0
- package/dist/api/routes/hooks.js +88 -0
- package/dist/api/routes/memories.d.ts +1 -0
- package/dist/api/routes/memories.js +92 -0
- package/dist/api/routes/persona.d.ts +1 -0
- package/dist/api/routes/persona.js +9 -0
- package/dist/api/routes/scenes.d.ts +1 -0
- package/dist/api/routes/scenes.js +35 -0
- package/dist/api/routes/skills.d.ts +1 -0
- package/dist/api/routes/skills.js +14 -0
- package/dist/api/routes/stats.d.ts +1 -0
- package/dist/api/routes/stats.js +8 -0
- package/dist/api/routes/users.d.ts +1 -0
- package/dist/api/routes/users.js +82 -0
- package/dist/api/routes/working.d.ts +1 -0
- package/dist/api/routes/working.js +88 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +492 -0
- package/dist/integrations/claude-code.d.ts +12 -0
- package/dist/integrations/claude-code.js +35 -0
- package/dist/integrations/codex.d.ts +12 -0
- package/dist/integrations/codex.js +34 -0
- package/dist/integrations/generic-mcp.d.ts +52 -0
- package/dist/integrations/generic-mcp.js +118 -0
- package/dist/loader.d.ts +29 -0
- package/dist/loader.js +200 -0
- package/dist/memory/capture.d.ts +35 -0
- package/dist/memory/capture.js +230 -0
- package/dist/memory/config.d.ts +2 -0
- package/dist/memory/config.js +3 -0
- package/dist/memory/engine.d.ts +203 -0
- package/dist/memory/engine.js +626 -0
- package/dist/memory/llm-semaphore.d.ts +41 -0
- package/dist/memory/llm-semaphore.js +81 -0
- package/dist/memory/memory-type-config.d.ts +11 -0
- package/dist/memory/memory-type-config.js +65 -0
- package/dist/memory/pipeline/cognitive-contradiction.d.ts +7 -0
- package/dist/memory/pipeline/cognitive-contradiction.js +59 -0
- package/dist/memory/pipeline/cognitive-dedup.d.ts +23 -0
- package/dist/memory/pipeline/cognitive-dedup.js +38 -0
- package/dist/memory/pipeline/cognitive-extractor.d.ts +21 -0
- package/dist/memory/pipeline/cognitive-extractor.js +183 -0
- package/dist/memory/pipeline/contextual-focus-builder.d.ts +13 -0
- package/dist/memory/pipeline/contextual-focus-builder.js +135 -0
- package/dist/memory/pipeline/focus-direction-shift.d.ts +10 -0
- package/dist/memory/pipeline/focus-direction-shift.js +27 -0
- package/dist/memory/pipeline/graph-builder.d.ts +11 -0
- package/dist/memory/pipeline/graph-builder.js +88 -0
- package/dist/memory/pipeline/graph-recall.d.ts +13 -0
- package/dist/memory/pipeline/graph-recall.js +55 -0
- package/dist/memory/pipeline/identity-distiller.d.ts +15 -0
- package/dist/memory/pipeline/identity-distiller.js +40 -0
- package/dist/memory/pipeline/l1-contradiction.d.ts +7 -0
- package/dist/memory/pipeline/l1-contradiction.js +66 -0
- package/dist/memory/pipeline/l1-dedup.d.ts +23 -0
- package/dist/memory/pipeline/l1-dedup.js +39 -0
- package/dist/memory/pipeline/l1-extractor.d.ts +21 -0
- package/dist/memory/pipeline/l1-extractor.js +180 -0
- package/dist/memory/pipeline/l2-direction-shift.d.ts +10 -0
- package/dist/memory/pipeline/l2-direction-shift.js +27 -0
- package/dist/memory/pipeline/l2-scene.d.ts +15 -0
- package/dist/memory/pipeline/l2-scene.js +140 -0
- package/dist/memory/pipeline/l3-distiller.d.ts +15 -0
- package/dist/memory/pipeline/l3-distiller.js +40 -0
- package/dist/memory/pipeline/neural-spark.d.ts +27 -0
- package/dist/memory/pipeline/neural-spark.js +78 -0
- package/dist/memory/pipeline/skill-prewarm.d.ts +63 -0
- package/dist/memory/pipeline/skill-prewarm.js +127 -0
- package/dist/memory/pipeline/task-queue.d.ts +54 -0
- package/dist/memory/pipeline/task-queue.js +117 -0
- package/dist/memory/prompts/cognitive-contradiction.d.ts +1 -0
- package/dist/memory/prompts/cognitive-contradiction.js +25 -0
- package/dist/memory/prompts/cognitive-extraction.d.ts +10 -0
- package/dist/memory/prompts/cognitive-extraction.js +114 -0
- package/dist/memory/prompts/core-identity.d.ts +6 -0
- package/dist/memory/prompts/core-identity.js +60 -0
- package/dist/memory/prompts/focus-direction-shift.d.ts +5 -0
- package/dist/memory/prompts/focus-direction-shift.js +32 -0
- package/dist/memory/prompts/focus-scene-cluster.d.ts +2 -0
- package/dist/memory/prompts/focus-scene-cluster.js +33 -0
- package/dist/memory/prompts/focus-scene.d.ts +7 -0
- package/dist/memory/prompts/focus-scene.js +40 -0
- package/dist/memory/prompts/graph-extraction-batch.d.ts +14 -0
- package/dist/memory/prompts/graph-extraction-batch.js +54 -0
- package/dist/memory/prompts/graph-extraction.d.ts +2 -0
- package/dist/memory/prompts/graph-extraction.js +53 -0
- package/dist/memory/prompts/l1-contradiction-batch.d.ts +16 -0
- package/dist/memory/prompts/l1-contradiction-batch.js +47 -0
- package/dist/memory/prompts/l1-contradiction.d.ts +1 -0
- package/dist/memory/prompts/l1-contradiction.js +25 -0
- package/dist/memory/prompts/l1-extraction.d.ts +10 -0
- package/dist/memory/prompts/l1-extraction.js +114 -0
- package/dist/memory/prompts/l2-direction-shift.d.ts +5 -0
- package/dist/memory/prompts/l2-direction-shift.js +32 -0
- package/dist/memory/prompts/l2-scene-cluster.d.ts +2 -0
- package/dist/memory/prompts/l2-scene-cluster.js +33 -0
- package/dist/memory/prompts/l2-scene.d.ts +7 -0
- package/dist/memory/prompts/l2-scene.js +40 -0
- package/dist/memory/prompts/l3-persona.d.ts +6 -0
- package/dist/memory/prompts/l3-persona.js +60 -0
- package/dist/memory/recall.d.ts +47 -0
- package/dist/memory/recall.js +427 -0
- package/dist/memory/redaction.d.ts +1 -0
- package/dist/memory/redaction.js +24 -0
- package/dist/memory/retry.d.ts +13 -0
- package/dist/memory/retry.js +53 -0
- package/dist/memory/scheduler.d.ts +9 -0
- package/dist/memory/scheduler.js +16 -0
- package/dist/memory/skill-hints-loader.d.ts +30 -0
- package/dist/memory/skill-hints-loader.js +100 -0
- package/dist/memory/store/embedding.d.ts +16 -0
- package/dist/memory/store/embedding.js +68 -0
- package/dist/memory/store/reranker.d.ts +24 -0
- package/dist/memory/store/reranker.js +83 -0
- package/dist/memory/store/sqlite.d.ts +167 -0
- package/dist/memory/store/sqlite.js +1816 -0
- package/dist/memory/store/types.d.ts +101 -0
- package/dist/memory/store/types.js +1 -0
- package/dist/memory/types.d.ts +207 -0
- package/dist/memory/types.js +7 -0
- package/dist/memory/validation.d.ts +441 -0
- package/dist/memory/validation.js +129 -0
- package/dist/memory/working/canvas.d.ts +5 -0
- package/dist/memory/working/canvas.js +43 -0
- package/dist/memory/working/offload.d.ts +71 -0
- package/dist/memory/working/offload.js +211 -0
- package/dist/memory/working/step-log.d.ts +16 -0
- package/dist/memory/working/step-log.js +35 -0
- package/dist/registry.d.ts +34 -0
- package/dist/registry.js +305 -0
- package/dist/resolver.d.ts +17 -0
- package/dist/resolver.js +126 -0
- package/dist/scripts/validate-foreign-workspace-path.d.ts +1 -0
- package/dist/scripts/validate-foreign-workspace-path.js +39 -0
- package/dist/tools/agent_memory_tools.d.ts +485 -0
- package/dist/tools/agent_memory_tools.js +793 -0
- package/dist/tools/create_skill.d.ts +46 -0
- package/dist/tools/create_skill.js +46 -0
- package/dist/tools/get_doc.d.ts +21 -0
- package/dist/tools/get_doc.js +24 -0
- package/dist/tools/get_persona.d.ts +15 -0
- package/dist/tools/get_persona.js +20 -0
- package/dist/tools/get_reference.d.ts +15 -0
- package/dist/tools/get_reference.js +20 -0
- package/dist/tools/get_skill.d.ts +34 -0
- package/dist/tools/get_skill.js +65 -0
- package/dist/tools/get_template_doc.d.ts +21 -0
- package/dist/tools/get_template_doc.js +24 -0
- package/dist/tools/list_docs.d.ts +15 -0
- package/dist/tools/list_docs.js +16 -0
- package/dist/tools/list_skills.d.ts +18 -0
- package/dist/tools/list_skills.js +17 -0
- package/dist/tools/list_template_docs.d.ts +15 -0
- package/dist/tools/list_template_docs.js +16 -0
- package/dist/tools/memory-engineering.d.ts +225 -0
- package/dist/tools/memory-engineering.js +284 -0
- package/dist/tools/memory-explain.d.ts +34 -0
- package/dist/tools/memory-explain.js +109 -0
- package/dist/tools/memory-governance.d.ts +171 -0
- package/dist/tools/memory-governance.js +224 -0
- package/dist/tools/memory-hooks.d.ts +67 -0
- package/dist/tools/memory-hooks.js +102 -0
- package/dist/tools/memory-working.d.ts +98 -0
- package/dist/tools/memory-working.js +101 -0
- package/dist/tools/memory_capture_turn.d.ts +66 -0
- package/dist/tools/memory_capture_turn.js +85 -0
- package/dist/tools/memory_consolidate.d.ts +55 -0
- package/dist/tools/memory_consolidate.js +176 -0
- package/dist/tools/memory_contradictions.d.ts +53 -0
- package/dist/tools/memory_contradictions.js +52 -0
- package/dist/tools/memory_graph_query.d.ts +51 -0
- package/dist/tools/memory_graph_query.js +35 -0
- package/dist/tools/memory_mark_cited.d.ts +43 -0
- package/dist/tools/memory_mark_cited.js +63 -0
- package/dist/tools/memory_recall.d.ts +77 -0
- package/dist/tools/memory_recall.js +81 -0
- package/dist/tools/memory_register_skill_hints.d.ts +49 -0
- package/dist/tools/memory_register_skill_hints.js +55 -0
- package/dist/tools/memory_resolve_session.d.ts +24 -0
- package/dist/tools/memory_resolve_session.js +133 -0
- package/dist/tools/memory_search.d.ts +146 -0
- package/dist/tools/memory_search.js +84 -0
- package/dist/tools/search_skills.d.ts +18 -0
- package/dist/tools/search_skills.js +17 -0
- package/dist/tools/update_doc.d.ts +24 -0
- package/dist/tools/update_doc.js +35 -0
- package/dist/tools/update_skill.d.ts +30 -0
- package/dist/tools/update_skill.js +80 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.js +4 -0
- package/dist/writer.d.ts +30 -0
- package/dist/writer.js +220 -0
- package/docs/TEMPLATE ONLY +1 -0
- package/docs/api/API.md +64 -0
- package/docs/api/security/SECURITY.md +58 -0
- package/docs/deployment/DockerDeployment.md +30 -0
- package/docs/design/Design.md +59 -0
- package/docs/design/themes/apple.md +101 -0
- package/docs/design/themes/dieter-grid.md +100 -0
- package/docs/design/themes/gallery-white.md +100 -0
- package/docs/design/themes/pinterest.md +101 -0
- package/docs/design/themes/realty-open-house.md +101 -0
- package/docs/design/themes/vodafone.md +101 -0
- package/docs/hooks/Hooks.md +30 -0
- package/docs/schema/Schema.md +35 -0
- package/docs/strategy/ScalingStrategy.md +19 -0
- package/package.json +88 -0
- package/references/accessibility-checklist.md +160 -0
- package/references/orchestration-patterns.md +370 -0
- package/references/performance-checklist.md +153 -0
- package/references/security-checklist.md +134 -0
- package/references/testing-patterns.md +236 -0
- package/skills/agent/adr-skill/SKILL.md +299 -0
- package/skills/agent/agentic-engineering-workflow/SKILL.md +95 -0
- package/skills/agent/bootstrap-skill/SKILL.md +103 -0
- package/skills/agent/context-engineering/SKILL.md +307 -0
- package/skills/agent/debugging-and-error-recovery/SKILL.md +308 -0
- package/skills/agent/developer-growth-analysis/SKILL.md +328 -0
- package/skills/agent/doubt-driven-skill/SKILL.md +249 -0
- package/skills/agent/handover-skill/SKILL.md +112 -0
- package/skills/agent/idea-refine-skill/SKILL.md +185 -0
- package/skills/agent/idea-refine-skill/examples.md +238 -0
- package/skills/agent/idea-refine-skill/frameworks.md +99 -0
- package/skills/agent/idea-refine-skill/refinement-criteria.md +113 -0
- package/skills/agent/interview-skill/SKILL.md +226 -0
- package/skills/agent/planning-skill/SKILL.md +270 -0
- package/skills/agent/skill-authoring/SKILL.md +189 -0
- package/skills/agent/source-driven-skill/SKILL.md +197 -0
- package/skills/agent/spec-driven-skill/SKILL.md +221 -0
- package/skills/agent/sync-skill/SKILL.md +92 -0
- package/skills/agent/using-agent-skills/SKILL.md +189 -0
- package/skills/api/a11y-skill/SKILL.md +88 -0
- package/skills/api/api-skill/SKILL.md +123 -0
- package/skills/api/auth-skill/SKILL.md +80 -0
- package/skills/api/debug-skill/SKILL.md +535 -0
- package/skills/api/performance-skill/SKILL.md +100 -0
- package/skills/api/testing-skill/SKILL.md +100 -0
- package/skills/codebase/code-review-and-quality/SKILL.md +228 -0
- package/skills/codebase/code-simplification/SKILL.md +352 -0
- package/skills/codebase/code-structure-cleanup/SKILL.md +142 -0
- package/skills/codebase/concerns-skill/SKILL.md +89 -0
- package/skills/codebase/conventions-skill/SKILL.md +95 -0
- package/skills/codebase/doc-management-skill/SKILL.md +47 -0
- package/skills/codebase/git-workflow-skill/SKILL.md +312 -0
- package/skills/communication/1-3-1-rule/SKILL.md +120 -0
- package/skills/design/brutalist-skill/SKILL.md +131 -0
- package/skills/design/concept-diagrams/SKILL.md +387 -0
- package/skills/design/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
- package/skills/design/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
- package/skills/design/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
- package/skills/design/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
- package/skills/design/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
- package/skills/design/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
- package/skills/design/concept-diagrams/examples/electricity-grid-flow.md +182 -0
- package/skills/design/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
- package/skills/design/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
- package/skills/design/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
- package/skills/design/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
- package/skills/design/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
- package/skills/design/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
- package/skills/design/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
- package/skills/design/concept-diagrams/examples/wind-turbine-structure.md +338 -0
- package/skills/design/concept-diagrams/references/dashboard-patterns.md +43 -0
- package/skills/design/concept-diagrams/references/infrastructure-patterns.md +144 -0
- package/skills/design/concept-diagrams/references/physical-shape-cookbook.md +42 -0
- package/skills/design/concept-diagrams/templates/template.html +174 -0
- package/skills/design/gpt-tasteskill/SKILL.md +114 -0
- package/skills/design/minimalist-skill/SKILL.md +116 -0
- package/skills/design/output-skill/SKILL.md +87 -0
- package/skills/design/redesign-skill/SKILL.md +213 -0
- package/skills/design/soft-skill/SKILL.md +132 -0
- package/skills/design/stitch-skill/EXAMPLE.md +121 -0
- package/skills/design/stitch-skill/SKILL.md +222 -0
- package/skills/design/taste-skill/SKILL.md +269 -0
- package/skills/devops/ci-cd-skill/SKILL.md +402 -0
- package/skills/devops/docker-skill/SKILL.md +297 -0
- package/skills/devops/domain-skill/SKILL.md +234 -0
- package/skills/lifecycle/changelog-generator/SKILL.md +135 -0
- package/skills/lifecycle/incremental-skill/SKILL.md +257 -0
- package/skills/lifecycle/migration-skill/SKILL.md +218 -0
- package/skills/lifecycle/shipping-skill/SKILL.md +321 -0
- package/skills/memory/agent-memory/SKILL.md +122 -0
- package/skills/qa/browser-testing-skill/SKILL.md +314 -0
- package/skills/ux/adversarial-ux-skill/SKILL.md +168 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { randomUUID, createHash } from "node:crypto";
|
|
5
|
+
import { isForeignAbsolutePath, resolveRegistryConfig } from "../../resolver.js";
|
|
6
|
+
import { appendWorkingStep, compressStepLog, readWorkingSteps } from "./step-log.js";
|
|
7
|
+
import { buildAnnotatedCanvas, readWorkingCanvas, writeWorkingCanvas } from "./canvas.js";
|
|
8
|
+
function defaultWorkspacePath() {
|
|
9
|
+
return resolveRegistryConfig().localRoot ?? process.cwd();
|
|
10
|
+
}
|
|
11
|
+
function estimateTokens(text) {
|
|
12
|
+
return Math.ceil(text.length / 4);
|
|
13
|
+
}
|
|
14
|
+
function pathSegment(value) {
|
|
15
|
+
return encodeURIComponent(value.trim() || "default");
|
|
16
|
+
}
|
|
17
|
+
export function detectTokenPressure(estimatedTokens, contextWindowTokens) {
|
|
18
|
+
if (contextWindowTokens <= 0)
|
|
19
|
+
return "none";
|
|
20
|
+
const fillRatio = estimatedTokens / contextWindowTokens;
|
|
21
|
+
if (fillRatio > 0.85)
|
|
22
|
+
return "aggressive";
|
|
23
|
+
if (fillRatio > 0.5)
|
|
24
|
+
return "mild";
|
|
25
|
+
return "none";
|
|
26
|
+
}
|
|
27
|
+
function getWorkspaceId(workspacePath) {
|
|
28
|
+
if (!workspacePath)
|
|
29
|
+
return "global";
|
|
30
|
+
if (isWorkspaceId(workspacePath)) {
|
|
31
|
+
return workspacePath;
|
|
32
|
+
}
|
|
33
|
+
if (isForeignAbsolutePath(workspacePath)) {
|
|
34
|
+
return createHash("sha256").update(workspacePath).digest("hex").slice(0, 12);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const resolved = path.resolve(workspacePath);
|
|
38
|
+
return createHash("sha256").update(resolved).digest("hex").slice(0, 12);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return createHash("sha256").update(workspacePath).digest("hex").slice(0, 12);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function isWorkspaceId(value) {
|
|
45
|
+
return value === "global" || /^[a-f0-9]{12}$/i.test(value);
|
|
46
|
+
}
|
|
47
|
+
export function getWorkingMemoryDir(workspacePath, userId, sessionKey) {
|
|
48
|
+
// Working memory is per-session ephemeral state. It lives ENTIRELY under
|
|
49
|
+
// the user home — never inside the project tree — so the workspace stays
|
|
50
|
+
// clean and so two clones of the same repo don't share state.
|
|
51
|
+
//
|
|
52
|
+
// Why not write to `<workspace>/.brainrouter/work/`? Two reasons:
|
|
53
|
+
// 1. Pollution: the brainrouter CLI's migration archives anything that
|
|
54
|
+
// isn't `<workspace>/.brainrouter/workflows/` into
|
|
55
|
+
// `<workspace>/.brainrouter.migrated/` on each launch — so the
|
|
56
|
+
// MCP would re-create the dir, the CLI would re-archive it, and the
|
|
57
|
+
// user would see both folders reappear on every session.
|
|
58
|
+
// 2. Misuse: a non-absolute `workspacePath` like "global" (a skill
|
|
59
|
+
// scope token) would resolve relative to the MCP process cwd and
|
|
60
|
+
// build a phantom `<cwd>/global/.brainrouter/work/` that has
|
|
61
|
+
// nothing to do with any real workspace.
|
|
62
|
+
//
|
|
63
|
+
// Layout: ~/.brainrouter/work/<userId>/<workspaceId>/<sessionKey>/
|
|
64
|
+
const root = path.join(os.homedir(), ".brainrouter");
|
|
65
|
+
const workspaceId = getWorkspaceId(workspacePath);
|
|
66
|
+
const targetDir = path.join(root, "work", pathSegment(userId), workspaceId);
|
|
67
|
+
try {
|
|
68
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Best-effort. If the home dir is unwritable the next write call will
|
|
72
|
+
// surface the error with a useful path.
|
|
73
|
+
}
|
|
74
|
+
return path.join(targetDir, pathSegment(sessionKey));
|
|
75
|
+
}
|
|
76
|
+
function refPathFor(workDir, nodeId) {
|
|
77
|
+
return path.join(workDir, "refs", `${nodeId}.md`);
|
|
78
|
+
}
|
|
79
|
+
function writeState(workDir, state) {
|
|
80
|
+
fs.mkdirSync(workDir, { recursive: true });
|
|
81
|
+
fs.writeFileSync(path.join(workDir, "state.json"), JSON.stringify(state, null, 2), "utf8");
|
|
82
|
+
}
|
|
83
|
+
function buildState(sessionKey, workDir, steps, pressureLevel, contextWindowTokens, estimatedTokens) {
|
|
84
|
+
const recentSteps = pressureLevel === "none" ? steps.slice(-10) : steps.slice(-5);
|
|
85
|
+
const currentNode = recentSteps.at(-1);
|
|
86
|
+
return {
|
|
87
|
+
sessionKey,
|
|
88
|
+
workDir,
|
|
89
|
+
pressureLevel,
|
|
90
|
+
contextWindowTokens,
|
|
91
|
+
estimatedTokens,
|
|
92
|
+
injectedState: {
|
|
93
|
+
currentNode,
|
|
94
|
+
recentSteps,
|
|
95
|
+
refs: recentSteps
|
|
96
|
+
.filter((step) => step.refPath)
|
|
97
|
+
.map((step) => ({ nodeId: step.nodeId, refPath: step.refPath, title: step.title })),
|
|
98
|
+
rawPayloadsIncluded: false,
|
|
99
|
+
},
|
|
100
|
+
updatedAt: new Date().toISOString(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function offloadWorkingPayload(input) {
|
|
104
|
+
const workDir = getWorkingMemoryDir(input.workspacePath, input.userId, input.sessionKey);
|
|
105
|
+
const refsDir = path.join(workDir, "refs");
|
|
106
|
+
fs.mkdirSync(refsDir, { recursive: true });
|
|
107
|
+
const nodeId = `w${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
108
|
+
const absoluteRefPath = refPathFor(workDir, nodeId);
|
|
109
|
+
// Stored as a display-friendly path relative to the user home (where
|
|
110
|
+
// workDir now always lives). We don't reconstruct the workspace prefix
|
|
111
|
+
// here because the working-memory tree no longer touches the workspace.
|
|
112
|
+
const relativeRefPath = path.relative(os.homedir(), absoluteRefPath);
|
|
113
|
+
const observedAt = new Date().toISOString();
|
|
114
|
+
const tokenEstimate = estimateTokens(input.payload);
|
|
115
|
+
const estimatedTokens = input.estimatedTokens ?? tokenEstimate;
|
|
116
|
+
const contextWindowTokens = input.contextWindowTokens ?? 128_000;
|
|
117
|
+
const pressureLevel = input.forceAggressive
|
|
118
|
+
? "aggressive"
|
|
119
|
+
: detectTokenPressure(estimatedTokens, contextWindowTokens);
|
|
120
|
+
fs.writeFileSync(absoluteRefPath, [
|
|
121
|
+
`# ${input.title ?? "Working Memory Ref"}`,
|
|
122
|
+
"",
|
|
123
|
+
`- nodeId: ${nodeId}`,
|
|
124
|
+
`- kind: ${input.kind ?? "tool_output"}`,
|
|
125
|
+
`- observedAt: ${observedAt}`,
|
|
126
|
+
"",
|
|
127
|
+
input.payload,
|
|
128
|
+
].join("\n"), "utf8");
|
|
129
|
+
appendWorkingStep(workDir, {
|
|
130
|
+
nodeId,
|
|
131
|
+
title: input.title ?? "Working payload offloaded",
|
|
132
|
+
summary: input.summary ?? input.payload.slice(0, 240),
|
|
133
|
+
kind: input.kind ?? "tool_output",
|
|
134
|
+
createdAt: observedAt,
|
|
135
|
+
refPath: relativeRefPath,
|
|
136
|
+
tokenEstimate,
|
|
137
|
+
});
|
|
138
|
+
const steps = pressureLevel === "none"
|
|
139
|
+
? readWorkingSteps(workDir)
|
|
140
|
+
: compressStepLog(workDir, 5).steps;
|
|
141
|
+
const canvas = writeWorkingCanvas(workDir, steps);
|
|
142
|
+
const state = buildState(input.sessionKey, workDir, steps, pressureLevel, contextWindowTokens, estimatedTokens);
|
|
143
|
+
writeState(workDir, state);
|
|
144
|
+
return { nodeId, refPath: absoluteRefPath, pressureLevel, canvas, state };
|
|
145
|
+
}
|
|
146
|
+
export function getWorkingContext(workspacePath, userId, sessionKey, options) {
|
|
147
|
+
const workDir = getWorkingMemoryDir(workspacePath, userId, sessionKey);
|
|
148
|
+
fs.mkdirSync(path.join(workDir, "refs"), { recursive: true });
|
|
149
|
+
const estimatedTokens = options?.estimatedTokens ?? 0;
|
|
150
|
+
const contextWindowTokens = options?.contextWindowTokens ?? 128_000;
|
|
151
|
+
const pressureLevel = detectTokenPressure(estimatedTokens, contextWindowTokens);
|
|
152
|
+
const steps = pressureLevel === "none"
|
|
153
|
+
? readWorkingSteps(workDir)
|
|
154
|
+
: compressStepLog(workDir, 5).steps;
|
|
155
|
+
const canvas = steps.length > 0 ? writeWorkingCanvas(workDir, steps) : readWorkingCanvas(workDir);
|
|
156
|
+
const annotatedCanvas = options?.activeNodeId ? buildAnnotatedCanvas(steps, options.activeNodeId) : undefined;
|
|
157
|
+
const state = buildState(sessionKey, workDir, steps, pressureLevel, contextWindowTokens, estimatedTokens);
|
|
158
|
+
writeState(workDir, state);
|
|
159
|
+
const refPath = options?.nodeId ? refPathFor(workDir, options.nodeId) : undefined;
|
|
160
|
+
const ref = refPath && fs.existsSync(refPath)
|
|
161
|
+
? { nodeId: options.nodeId, path: refPath, content: fs.readFileSync(refPath, "utf8") }
|
|
162
|
+
: undefined;
|
|
163
|
+
return { sessionKey, workDir, canvas: annotatedCanvas ?? canvas, annotatedCanvas, state, steps, ref };
|
|
164
|
+
}
|
|
165
|
+
export function resetWorkingMemory(workspacePath, userId, sessionKey) {
|
|
166
|
+
const workDir = getWorkingMemoryDir(workspacePath, userId, sessionKey);
|
|
167
|
+
const deleted = fs.existsSync(workDir);
|
|
168
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
169
|
+
return { deleted, workDir };
|
|
170
|
+
}
|
|
171
|
+
export function listActiveSessions(userId) {
|
|
172
|
+
const root = path.join(os.homedir(), ".brainrouter", "work", pathSegment(userId));
|
|
173
|
+
if (!fs.existsSync(root))
|
|
174
|
+
return [];
|
|
175
|
+
const results = [];
|
|
176
|
+
try {
|
|
177
|
+
const workspaceIds = fs.readdirSync(root);
|
|
178
|
+
for (const workspaceId of workspaceIds) {
|
|
179
|
+
const workspacePath = path.join(root, workspaceId);
|
|
180
|
+
if (!fs.statSync(workspacePath).isDirectory())
|
|
181
|
+
continue;
|
|
182
|
+
const sessionKeys = fs.readdirSync(workspacePath);
|
|
183
|
+
for (const sessionKey of sessionKeys) {
|
|
184
|
+
const sessionPath = path.join(workspacePath, sessionKey);
|
|
185
|
+
if (!fs.statSync(sessionPath).isDirectory())
|
|
186
|
+
continue;
|
|
187
|
+
let updatedAt = new Date(fs.statSync(sessionPath).mtime).toISOString();
|
|
188
|
+
try {
|
|
189
|
+
const stateFile = path.join(sessionPath, "state.json");
|
|
190
|
+
if (fs.existsSync(stateFile)) {
|
|
191
|
+
const state = JSON.parse(fs.readFileSync(stateFile, "utf8"));
|
|
192
|
+
if (state.updatedAt)
|
|
193
|
+
updatedAt = state.updatedAt;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Ignore
|
|
198
|
+
}
|
|
199
|
+
results.push({
|
|
200
|
+
sessionKey: decodeURIComponent(sessionKey),
|
|
201
|
+
workspaceId,
|
|
202
|
+
updatedAt,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Ignore
|
|
209
|
+
}
|
|
210
|
+
return results.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
211
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface WorkingStep {
|
|
2
|
+
nodeId: string;
|
|
3
|
+
title: string;
|
|
4
|
+
summary: string;
|
|
5
|
+
kind: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
refPath?: string;
|
|
8
|
+
tokenEstimate: number;
|
|
9
|
+
}
|
|
10
|
+
export interface StepLogCompressionResult {
|
|
11
|
+
steps: WorkingStep[];
|
|
12
|
+
compressed: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function appendWorkingStep(workDir: string, step: WorkingStep): void;
|
|
15
|
+
export declare function readWorkingSteps(workDir: string): WorkingStep[];
|
|
16
|
+
export declare function compressStepLog(workDir: string, keepLast?: number): StepLogCompressionResult;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function appendWorkingStep(workDir, step) {
|
|
4
|
+
fs.mkdirSync(workDir, { recursive: true });
|
|
5
|
+
fs.appendFileSync(path.join(workDir, "steps.jsonl"), `${JSON.stringify(step)}\n`, "utf8");
|
|
6
|
+
}
|
|
7
|
+
export function readWorkingSteps(workDir) {
|
|
8
|
+
const stepsPath = path.join(workDir, "steps.jsonl");
|
|
9
|
+
if (!fs.existsSync(stepsPath))
|
|
10
|
+
return [];
|
|
11
|
+
return fs.readFileSync(stepsPath, "utf8")
|
|
12
|
+
.split("\n")
|
|
13
|
+
.map((line) => line.trim())
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.map((line) => JSON.parse(line));
|
|
16
|
+
}
|
|
17
|
+
export function compressStepLog(workDir, keepLast = 5) {
|
|
18
|
+
const steps = readWorkingSteps(workDir);
|
|
19
|
+
if (steps.length <= keepLast) {
|
|
20
|
+
return { steps, compressed: false };
|
|
21
|
+
}
|
|
22
|
+
const archived = steps.slice(0, -keepLast);
|
|
23
|
+
const retained = steps.slice(-keepLast);
|
|
24
|
+
const archiveStep = {
|
|
25
|
+
nodeId: `summary-${Date.now()}`,
|
|
26
|
+
title: `Compressed ${archived.length} earlier steps`,
|
|
27
|
+
summary: archived.map((step) => `${step.title}: ${step.summary}`).join(" | "),
|
|
28
|
+
kind: "compressed_summary",
|
|
29
|
+
createdAt: new Date().toISOString(),
|
|
30
|
+
tokenEstimate: archived.reduce((total, step) => total + step.tokenEstimate, 0),
|
|
31
|
+
};
|
|
32
|
+
const nextSteps = [archiveStep, ...retained];
|
|
33
|
+
fs.writeFileSync(path.join(workDir, "steps.jsonl"), `${nextSteps.map((step) => JSON.stringify(step)).join("\n")}\n`, "utf8");
|
|
34
|
+
return { steps: nextSteps, compressed: true };
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SkillManifest, DocManifest, PersonaManifest, ReferenceManifest, RegistryConfig, SkillScope, DocCategory } from './types.js';
|
|
2
|
+
export declare class Registry {
|
|
3
|
+
private skills;
|
|
4
|
+
private docs;
|
|
5
|
+
private personas;
|
|
6
|
+
private references;
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config: RegistryConfig);
|
|
9
|
+
/** Scan all roots and populate the in-memory manifests. */
|
|
10
|
+
build(): void;
|
|
11
|
+
private indexSkills;
|
|
12
|
+
private indexPersonas;
|
|
13
|
+
private indexReferences;
|
|
14
|
+
private indexDocs;
|
|
15
|
+
/** Re-scan after a write operation. */
|
|
16
|
+
refresh(): void;
|
|
17
|
+
getSkill(name: string): SkillManifest | undefined;
|
|
18
|
+
listSkills(category?: string, scope?: SkillScope | 'all'): SkillManifest[];
|
|
19
|
+
searchSkills(query: string, scope?: SkillScope | 'all'): Array<SkillManifest & {
|
|
20
|
+
relevance: string;
|
|
21
|
+
}>;
|
|
22
|
+
getDoc(name: string): DocManifest | undefined;
|
|
23
|
+
listDocs(category?: DocCategory): DocManifest[];
|
|
24
|
+
getPersona(name: string): PersonaManifest | undefined;
|
|
25
|
+
listPersonas(): PersonaManifest[];
|
|
26
|
+
getReference(name: string): ReferenceManifest | undefined;
|
|
27
|
+
listReferences(): ReferenceManifest[];
|
|
28
|
+
/** The resolved local root (for write operations). */
|
|
29
|
+
getLocalRoot(): string | undefined;
|
|
30
|
+
/** The global root (BrainRouter repo). */
|
|
31
|
+
getGlobalRoot(): string;
|
|
32
|
+
/** The local project name. */
|
|
33
|
+
getLocalProjectName(): string | undefined;
|
|
34
|
+
}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────
|
|
2
|
+
// BrainRouter MCP Server — Dual Registry
|
|
3
|
+
// Scans global + local roots, merges manifests.
|
|
4
|
+
// Local always shadows global on name conflict.
|
|
5
|
+
// ─────────────────────────────────────────────
|
|
6
|
+
import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
|
|
7
|
+
import { join, relative, basename } from 'path';
|
|
8
|
+
import matter from 'gray-matter';
|
|
9
|
+
// ─── Helpers ────────────────────────────────
|
|
10
|
+
/**
|
|
11
|
+
* Recursively find all files matching a filename in a directory.
|
|
12
|
+
*/
|
|
13
|
+
function findFiles(dir, filename) {
|
|
14
|
+
if (!existsSync(dir))
|
|
15
|
+
return [];
|
|
16
|
+
const results = [];
|
|
17
|
+
function walk(current) {
|
|
18
|
+
for (const entry of readdirSync(current)) {
|
|
19
|
+
const full = join(current, entry);
|
|
20
|
+
try {
|
|
21
|
+
const stat = statSync(full);
|
|
22
|
+
if (stat.isDirectory()) {
|
|
23
|
+
walk(full);
|
|
24
|
+
}
|
|
25
|
+
else if (entry === filename) {
|
|
26
|
+
results.push(full);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Skip unreadable entries
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
walk(dir);
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Find all direct .md files in a directory (non-recursive).
|
|
39
|
+
*/
|
|
40
|
+
function findMdFiles(dir) {
|
|
41
|
+
if (!existsSync(dir))
|
|
42
|
+
return [];
|
|
43
|
+
return readdirSync(dir)
|
|
44
|
+
.filter((f) => f.endsWith('.md'))
|
|
45
|
+
.map((f) => join(dir, f));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Derive the category from a skill file path.
|
|
49
|
+
* e.g. .../skills/agent/bootstrap-skill/SKILL.md → "agent"
|
|
50
|
+
*/
|
|
51
|
+
function deriveCategory(filePath, skillsRoot) {
|
|
52
|
+
const rel = relative(skillsRoot, filePath);
|
|
53
|
+
const parts = rel.split('/');
|
|
54
|
+
return parts.length >= 2 ? parts[0] : 'uncategorized';
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Parse frontmatter from a SKILL.md. Returns name + description.
|
|
58
|
+
*/
|
|
59
|
+
function parseSkillFrontmatter(filePath) {
|
|
60
|
+
try {
|
|
61
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
62
|
+
const { data } = matter(raw);
|
|
63
|
+
if (data.name && data.description) {
|
|
64
|
+
return {
|
|
65
|
+
name: String(data.name),
|
|
66
|
+
description: String(data.description),
|
|
67
|
+
category: data.category ? String(data.category) : undefined
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Skip unparseable files
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Derive the doc category from its file path.
|
|
78
|
+
* e.g. .../docs/api/API.md → "api"
|
|
79
|
+
*/
|
|
80
|
+
function deriveDocCategory(filePath, docsRoot) {
|
|
81
|
+
const rel = relative(docsRoot, filePath);
|
|
82
|
+
const parts = rel.split('/');
|
|
83
|
+
const cat = parts.length >= 2 ? parts[0].toLowerCase() : 'other';
|
|
84
|
+
const valid = ['api', 'design', 'schema', 'deployment', 'hooks', 'strategy'];
|
|
85
|
+
return valid.includes(cat) ? cat : 'other';
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Derive a doc name from its file path.
|
|
89
|
+
* e.g. .../docs/api/API.md → "api"
|
|
90
|
+
*/
|
|
91
|
+
function deriveDocName(filePath) {
|
|
92
|
+
return basename(filePath, '.md').toLowerCase();
|
|
93
|
+
}
|
|
94
|
+
// ─── Registry Class ──────────────────────────
|
|
95
|
+
export class Registry {
|
|
96
|
+
skills = new Map();
|
|
97
|
+
docs = new Map();
|
|
98
|
+
personas = new Map();
|
|
99
|
+
references = new Map();
|
|
100
|
+
config;
|
|
101
|
+
constructor(config) {
|
|
102
|
+
this.config = config;
|
|
103
|
+
}
|
|
104
|
+
// ─── Build ──────────────────────────────
|
|
105
|
+
/** Scan all roots and populate the in-memory manifests. */
|
|
106
|
+
build() {
|
|
107
|
+
this.skills.clear();
|
|
108
|
+
this.docs.clear();
|
|
109
|
+
this.personas.clear();
|
|
110
|
+
this.references.clear();
|
|
111
|
+
// 1. Index global skills (BrainRouter repo) first
|
|
112
|
+
this.indexSkills(this.config.globalRoot, 'global');
|
|
113
|
+
this.indexPersonas(this.config.globalRoot);
|
|
114
|
+
this.indexReferences(this.config.globalRoot);
|
|
115
|
+
// 2. Index local skills — these shadow global on name conflict
|
|
116
|
+
if (this.config.localRoot && this.config.localRoot !== this.config.globalRoot) {
|
|
117
|
+
this.indexSkills(this.config.localRoot, 'local');
|
|
118
|
+
this.indexPersonas(this.config.localRoot);
|
|
119
|
+
this.indexReferences(this.config.localRoot);
|
|
120
|
+
}
|
|
121
|
+
// Local docs (project-specific source-of-truth)
|
|
122
|
+
if (this.config.localRoot) {
|
|
123
|
+
this.indexDocs(this.config.localRoot);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// ─── Indexing ───────────────────────────
|
|
127
|
+
indexSkills(root, scope) {
|
|
128
|
+
const universalSkillsDir = join(root, 'skills');
|
|
129
|
+
const projectSkillsDir = join(root, 'projects');
|
|
130
|
+
// Index Universal Skills
|
|
131
|
+
if (existsSync(universalSkillsDir)) {
|
|
132
|
+
const skillFiles = findFiles(universalSkillsDir, 'SKILL.md');
|
|
133
|
+
for (const filePath of skillFiles) {
|
|
134
|
+
const meta = parseSkillFrontmatter(filePath);
|
|
135
|
+
if (!meta)
|
|
136
|
+
continue;
|
|
137
|
+
const category = deriveCategory(filePath, universalSkillsDir);
|
|
138
|
+
this.skills.set(meta.name, {
|
|
139
|
+
name: meta.name,
|
|
140
|
+
category,
|
|
141
|
+
description: meta.description,
|
|
142
|
+
filePath,
|
|
143
|
+
scope,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Index Project-Specific Skills
|
|
148
|
+
if (existsSync(projectSkillsDir)) {
|
|
149
|
+
const skillFiles = findFiles(projectSkillsDir, 'SKILL.md');
|
|
150
|
+
for (const filePath of skillFiles) {
|
|
151
|
+
const meta = parseSkillFrontmatter(filePath);
|
|
152
|
+
if (!meta)
|
|
153
|
+
continue;
|
|
154
|
+
const rel = relative(projectSkillsDir, filePath);
|
|
155
|
+
const parts = rel.split('/');
|
|
156
|
+
// project should normally always exist since parts > 1 for valid structure
|
|
157
|
+
const project = parts.length > 0 ? parts[0] : '';
|
|
158
|
+
// New structure: <project>/skills/<category>/<name>/SKILL.md
|
|
159
|
+
// parts[0]=project, parts[1]=skills, parts[2]=category, parts[3]=name
|
|
160
|
+
const category = meta.category || (parts.length >= 4 ? parts[2] : 'api');
|
|
161
|
+
this.skills.set(meta.name, {
|
|
162
|
+
name: meta.name,
|
|
163
|
+
category,
|
|
164
|
+
project,
|
|
165
|
+
description: meta.description,
|
|
166
|
+
filePath,
|
|
167
|
+
scope,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
indexPersonas(root) {
|
|
173
|
+
const agentsDir = join(root, 'agents');
|
|
174
|
+
const files = findMdFiles(agentsDir);
|
|
175
|
+
for (const filePath of files) {
|
|
176
|
+
const name = basename(filePath, '.md');
|
|
177
|
+
if (name === 'README')
|
|
178
|
+
continue;
|
|
179
|
+
this.personas.set(name, { name, filePath });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
indexReferences(root) {
|
|
183
|
+
const refsDir = join(root, 'references');
|
|
184
|
+
const files = findMdFiles(refsDir);
|
|
185
|
+
for (const filePath of files) {
|
|
186
|
+
const name = basename(filePath, '.md');
|
|
187
|
+
this.references.set(name, { name, filePath });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
indexDocs(root) {
|
|
191
|
+
const docsDir = join(root, 'docs');
|
|
192
|
+
if (!existsSync(docsDir))
|
|
193
|
+
return;
|
|
194
|
+
// Recursively find all .md files under docs/
|
|
195
|
+
function walkDocs(dir, results) {
|
|
196
|
+
if (!existsSync(dir))
|
|
197
|
+
return;
|
|
198
|
+
for (const entry of readdirSync(dir)) {
|
|
199
|
+
const full = join(dir, entry);
|
|
200
|
+
try {
|
|
201
|
+
const stat = statSync(full);
|
|
202
|
+
if (stat.isDirectory()) {
|
|
203
|
+
walkDocs(full, results);
|
|
204
|
+
}
|
|
205
|
+
else if (entry.endsWith('.md')) {
|
|
206
|
+
results.push(full);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch { /* skip */ }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const docFiles = [];
|
|
213
|
+
walkDocs(docsDir, docFiles);
|
|
214
|
+
for (const filePath of docFiles) {
|
|
215
|
+
const name = deriveDocName(filePath);
|
|
216
|
+
const category = deriveDocCategory(filePath, docsDir);
|
|
217
|
+
const stat = statSync(filePath);
|
|
218
|
+
this.docs.set(name, {
|
|
219
|
+
name,
|
|
220
|
+
category,
|
|
221
|
+
filePath,
|
|
222
|
+
lastModified: stat.mtime,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// ─── Refresh ────────────────────────────
|
|
227
|
+
/** Re-scan after a write operation. */
|
|
228
|
+
refresh() {
|
|
229
|
+
this.build();
|
|
230
|
+
}
|
|
231
|
+
// ─── Queries ────────────────────────────
|
|
232
|
+
getSkill(name) {
|
|
233
|
+
const skill = this.skills.get(name);
|
|
234
|
+
if (!skill)
|
|
235
|
+
return undefined;
|
|
236
|
+
// Filter by project: allow if universal (!skill.project) or matches current project
|
|
237
|
+
if (this.config.localProjectName && skill.project && skill.project !== this.config.localProjectName) {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
return skill;
|
|
241
|
+
}
|
|
242
|
+
listSkills(category, scope) {
|
|
243
|
+
let all = Array.from(this.skills.values());
|
|
244
|
+
// Filter by project
|
|
245
|
+
if (this.config.localProjectName) {
|
|
246
|
+
all = all.filter((s) => !s.project || s.project === this.config.localProjectName);
|
|
247
|
+
}
|
|
248
|
+
if (category)
|
|
249
|
+
all = all.filter((s) => s.category === category);
|
|
250
|
+
if (scope && scope !== 'all')
|
|
251
|
+
all = all.filter((s) => s.scope === scope);
|
|
252
|
+
return all.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
|
|
253
|
+
}
|
|
254
|
+
searchSkills(query, scope) {
|
|
255
|
+
const q = query.toLowerCase();
|
|
256
|
+
let all = Array.from(this.skills.values());
|
|
257
|
+
// Filter by project
|
|
258
|
+
if (this.config.localProjectName) {
|
|
259
|
+
all = all.filter((s) => !s.project || s.project === this.config.localProjectName);
|
|
260
|
+
}
|
|
261
|
+
if (scope && scope !== 'all')
|
|
262
|
+
all = all.filter((s) => s.scope === scope);
|
|
263
|
+
return all
|
|
264
|
+
.filter((s) => s.name.includes(q) ||
|
|
265
|
+
s.description.toLowerCase().includes(q) ||
|
|
266
|
+
s.category.includes(q))
|
|
267
|
+
.map((s) => ({
|
|
268
|
+
...s,
|
|
269
|
+
relevance: s.name.includes(q) ? 'name match' : s.description.toLowerCase().includes(q) ? 'description match' : 'category match',
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
getDoc(name) {
|
|
273
|
+
return this.docs.get(name);
|
|
274
|
+
}
|
|
275
|
+
listDocs(category) {
|
|
276
|
+
let all = Array.from(this.docs.values());
|
|
277
|
+
if (category)
|
|
278
|
+
all = all.filter((d) => d.category === category);
|
|
279
|
+
return all.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
|
|
280
|
+
}
|
|
281
|
+
getPersona(name) {
|
|
282
|
+
return this.personas.get(name);
|
|
283
|
+
}
|
|
284
|
+
listPersonas() {
|
|
285
|
+
return Array.from(this.personas.values());
|
|
286
|
+
}
|
|
287
|
+
getReference(name) {
|
|
288
|
+
return this.references.get(name);
|
|
289
|
+
}
|
|
290
|
+
listReferences() {
|
|
291
|
+
return Array.from(this.references.values());
|
|
292
|
+
}
|
|
293
|
+
/** The resolved local root (for write operations). */
|
|
294
|
+
getLocalRoot() {
|
|
295
|
+
return this.config.localRoot;
|
|
296
|
+
}
|
|
297
|
+
/** The global root (BrainRouter repo). */
|
|
298
|
+
getGlobalRoot() {
|
|
299
|
+
return this.config.globalRoot;
|
|
300
|
+
}
|
|
301
|
+
/** The local project name. */
|
|
302
|
+
getLocalProjectName() {
|
|
303
|
+
return this.config.localProjectName;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RegistryConfig, BrainRouterConfig } from './types.js';
|
|
2
|
+
export declare function isForeignAbsolutePath(workspacePath: string | undefined): boolean;
|
|
3
|
+
export declare function getSafeWorkspacePath(workspacePath: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* Read and parse brainrouter.config.json from a directory, if present.
|
|
6
|
+
*/
|
|
7
|
+
export declare function readBrainRouterConfig(dir: string): BrainRouterConfig;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the final RegistryConfig.
|
|
10
|
+
*
|
|
11
|
+
* Priority:
|
|
12
|
+
* 1. --root <path> CLI flag
|
|
13
|
+
* 2. BRAINROUTER_LOCAL_ROOT env var
|
|
14
|
+
* 3. Auto-detect by walking up from CWD
|
|
15
|
+
* 4. Fallback: single-repo mode (localRoot = globalRoot)
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveRegistryConfig(): RegistryConfig;
|