tachibot-mcp 2.0.2
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 +260 -0
- package/CHANGELOG.md +54 -0
- package/CODE_OF_CONDUCT.md +56 -0
- package/CONTRIBUTING.md +54 -0
- package/Dockerfile +36 -0
- package/LICENSE +644 -0
- package/README.md +201 -0
- package/SECURITY.md +95 -0
- package/dist/personality/komaai-expressions.js +12 -0
- package/dist/profiles/balanced.json +33 -0
- package/dist/profiles/code_focus.json +33 -0
- package/dist/profiles/full.json +33 -0
- package/dist/profiles/minimal.json +33 -0
- package/dist/profiles/research_power.json +33 -0
- package/dist/scripts/build-profiles.js +46 -0
- package/dist/src/application/services/focus/FocusModeRegistry.js +46 -0
- package/dist/src/application/services/focus/FocusTool.service.js +109 -0
- package/dist/src/application/services/focus/ModeRegistry.js +46 -0
- package/dist/src/application/services/focus/modes/focus-deep.mode.js +27 -0
- package/dist/src/application/services/focus/modes/status.mode.js +50 -0
- package/dist/src/application/services/focus/modes/tachibot-status.mode.js +50 -0
- package/dist/src/collaborative-orchestrator.js +391 -0
- package/dist/src/config/model-constants.js +188 -0
- package/dist/src/config/model-defaults.js +57 -0
- package/dist/src/config/model-preferences.js +382 -0
- package/dist/src/config/timeout-config.js +130 -0
- package/dist/src/config.js +173 -0
- package/dist/src/domain/interfaces/IFocusMode.js +5 -0
- package/dist/src/domain/interfaces/IProvider.js +6 -0
- package/dist/src/domain/interfaces/ITool.js +5 -0
- package/dist/src/focus-deep.js +245 -0
- package/dist/src/infrastructure/ascii/art/robots.ascii.js +16 -0
- package/dist/src/mcp-client.js +90 -0
- package/dist/src/memory/index.js +17 -0
- package/dist/src/memory/memory-config.js +135 -0
- package/dist/src/memory/memory-interface.js +174 -0
- package/dist/src/memory/memory-manager.js +383 -0
- package/dist/src/memory/providers/devlog-provider.js +385 -0
- package/dist/src/memory/providers/hybrid-provider.js +399 -0
- package/dist/src/memory/providers/local-provider.js +388 -0
- package/dist/src/memory/providers/mem0-provider.js +337 -0
- package/dist/src/modes/architect.js +477 -0
- package/dist/src/modes/auditor.js +362 -0
- package/dist/src/modes/challenger.js +841 -0
- package/dist/src/modes/code-reviewer.js +382 -0
- package/dist/src/modes/commit-guardian.js +424 -0
- package/dist/src/modes/documentation-writer.js +572 -0
- package/dist/src/modes/scout.js +587 -0
- package/dist/src/modes/shared/helpers/challenger-helpers.js +454 -0
- package/dist/src/modes/shared/helpers/index.js +17 -0
- package/dist/src/modes/shared/helpers/scout-helpers.js +270 -0
- package/dist/src/modes/shared/helpers/verifier-helpers.js +332 -0
- package/dist/src/modes/test-architect.js +767 -0
- package/dist/src/modes/verifier.js +378 -0
- package/dist/src/monitoring/performance-monitor.js +435 -0
- package/dist/src/optimization/batch-executor.js +121 -0
- package/dist/src/optimization/context-pruner.js +196 -0
- package/dist/src/optimization/cost-monitor.js +338 -0
- package/dist/src/optimization/index.js +65 -0
- package/dist/src/optimization/model-router.js +264 -0
- package/dist/src/optimization/result-cache.js +114 -0
- package/dist/src/optimization/token-optimizer.js +257 -0
- package/dist/src/optimization/token-tracker.js +118 -0
- package/dist/src/orchestrator-instructions.js +128 -0
- package/dist/src/orchestrator-lite.js +139 -0
- package/dist/src/orchestrator.js +191 -0
- package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionEngine.js +1 -0
- package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionStrategy.js +5 -0
- package/dist/src/orchestrators/collaborative/interfaces/IVisualizationRenderer.js +1 -0
- package/dist/src/orchestrators/collaborative/registries/ModelProviderRegistry.js +95 -0
- package/dist/src/orchestrators/collaborative/registries/ToolAdapterRegistry.js +64 -0
- package/dist/src/orchestrators/collaborative/services/tool-execution/ToolExecutionService.js +502 -0
- package/dist/src/orchestrators/collaborative/services/visualization/VisualizationService.js +206 -0
- package/dist/src/orchestrators/collaborative/types/session-types.js +5 -0
- package/dist/src/profiles/balanced.js +37 -0
- package/dist/src/profiles/code_focus.js +37 -0
- package/dist/src/profiles/debug_intensive.js +59 -0
- package/dist/src/profiles/full.js +37 -0
- package/dist/src/profiles/minimal.js +37 -0
- package/dist/src/profiles/research_code.js +59 -0
- package/dist/src/profiles/research_power.js +37 -0
- package/dist/src/profiles/types.js +5 -0
- package/dist/src/profiles/workflow_builder.js +53 -0
- package/dist/src/prompt-engineer-lite.js +78 -0
- package/dist/src/prompt-engineer.js +399 -0
- package/dist/src/reasoning-chain.js +508 -0
- package/dist/src/sequential-thinking.js +291 -0
- package/dist/src/server-diagnostic.js +74 -0
- package/dist/src/server-raw.js +158 -0
- package/dist/src/server-simple.js +58 -0
- package/dist/src/server.js +514 -0
- package/dist/src/session/session-logger.js +617 -0
- package/dist/src/session/session-manager.js +571 -0
- package/dist/src/session/session-tools.js +400 -0
- package/dist/src/tools/advanced-modes.js +200 -0
- package/dist/src/tools/claude-integration.js +356 -0
- package/dist/src/tools/consolidated/ai-router.js +174 -0
- package/dist/src/tools/consolidated/ai-tool.js +48 -0
- package/dist/src/tools/consolidated/brainstorm-tool.js +87 -0
- package/dist/src/tools/consolidated/environment-detector.js +80 -0
- package/dist/src/tools/consolidated/index.js +50 -0
- package/dist/src/tools/consolidated/search-tool.js +110 -0
- package/dist/src/tools/consolidated/workflow-tool.js +238 -0
- package/dist/src/tools/gemini-tools.js +329 -0
- package/dist/src/tools/grok-enhanced.js +376 -0
- package/dist/src/tools/grok-tools.js +299 -0
- package/dist/src/tools/lmstudio-tools.js +223 -0
- package/dist/src/tools/openai-tools.js +498 -0
- package/dist/src/tools/openrouter-tools.js +317 -0
- package/dist/src/tools/optimized-wrapper.js +204 -0
- package/dist/src/tools/perplexity-tools.js +294 -0
- package/dist/src/tools/pingpong-tool.js +343 -0
- package/dist/src/tools/qwen-wrapper.js +74 -0
- package/dist/src/tools/tool-router.js +444 -0
- package/dist/src/tools/unified-ai-provider.js +260 -0
- package/dist/src/tools/workflow-runner.js +425 -0
- package/dist/src/tools/workflow-validator-tool.js +107 -0
- package/dist/src/types.js +23 -0
- package/dist/src/utils/input-validator.js +130 -0
- package/dist/src/utils/model-router.js +91 -0
- package/dist/src/utils/progress-stream.js +255 -0
- package/dist/src/utils/provider-router.js +88 -0
- package/dist/src/utils/smart-api-client.js +146 -0
- package/dist/src/utils/table-builder.js +218 -0
- package/dist/src/utils/timestamp-formatter.js +134 -0
- package/dist/src/utils/tool-compressor.js +257 -0
- package/dist/src/utils/tool-config.js +201 -0
- package/dist/src/validators/dependency-graph-validator.js +147 -0
- package/dist/src/validators/interpolation-validator.js +222 -0
- package/dist/src/validators/output-usage-validator.js +151 -0
- package/dist/src/validators/syntax-validator.js +102 -0
- package/dist/src/validators/tool-registry-validator.js +123 -0
- package/dist/src/validators/tool-types.js +97 -0
- package/dist/src/validators/types.js +8 -0
- package/dist/src/validators/workflow-validator.js +134 -0
- package/dist/src/visualizer-lite.js +42 -0
- package/dist/src/visualizer.js +179 -0
- package/dist/src/workflows/circuit-breaker.js +199 -0
- package/dist/src/workflows/custom-workflows.js +451 -0
- package/dist/src/workflows/engine/AutoSynthesizer.js +97 -0
- package/dist/src/workflows/engine/StepParameterResolver.js +74 -0
- package/dist/src/workflows/engine/VariableInterpolator.js +123 -0
- package/dist/src/workflows/engine/WorkflowDiscovery.js +125 -0
- package/dist/src/workflows/engine/WorkflowExecutionEngine.js +485 -0
- package/dist/src/workflows/engine/WorkflowExecutor.js +113 -0
- package/dist/src/workflows/engine/WorkflowFileManager.js +244 -0
- package/dist/src/workflows/engine/WorkflowHelpers.js +114 -0
- package/dist/src/workflows/engine/WorkflowOutputFormatter.js +83 -0
- package/dist/src/workflows/engine/events/WorkflowEventBus.js +132 -0
- package/dist/src/workflows/engine/events/interfaces/IEventBus.js +5 -0
- package/dist/src/workflows/engine/handlers/ErrorRecoveryHandler.js +162 -0
- package/dist/src/workflows/engine/handlers/PromptEnhancementHandler.js +115 -0
- package/dist/src/workflows/engine/handlers/SessionPersistenceHandler.js +167 -0
- package/dist/src/workflows/engine/handlers/StepExecutionHandler.js +231 -0
- package/dist/src/workflows/engine/handlers/ToolInvocationHandler.js +46 -0
- package/dist/src/workflows/engine/interfaces/IAutoSynthesizer.js +5 -0
- package/dist/src/workflows/engine/interfaces/IStepParameterResolver.js +5 -0
- package/dist/src/workflows/engine/interfaces/IVariableInterpolator.js +5 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowDiscovery.js +4 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowFileManager.js +5 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowOutputFormatter.js +5 -0
- package/dist/src/workflows/engine/state/WorkflowStateMachine.js +194 -0
- package/dist/src/workflows/engine/state/interfaces/IStateMachine.js +17 -0
- package/dist/src/workflows/fallback-strategies.js +373 -0
- package/dist/src/workflows/message-queue.js +455 -0
- package/dist/src/workflows/model-router.js +189 -0
- package/dist/src/workflows/orchestrator-examples.js +174 -0
- package/dist/src/workflows/orchestrator-integration.js +200 -0
- package/dist/src/workflows/self-healing.js +524 -0
- package/dist/src/workflows/tool-mapper.js +407 -0
- package/dist/src/workflows/tool-orchestrator.js +796 -0
- package/dist/src/workflows/workflow-engine.js +573 -0
- package/dist/src/workflows/workflow-parser.js +283 -0
- package/dist/src/workflows/workflow-types.js +95 -0
- package/dist/src/workflows.js +568 -0
- package/dist/test-workflow-file-output.js +93 -0
- package/docs/API_KEYS.md +570 -0
- package/docs/CLAUDE_CODE_SETUP.md +181 -0
- package/docs/CLAUDE_DESKTOP_MANUAL.md +127 -0
- package/docs/CONFIGURATION.md +745 -0
- package/docs/FOCUS_MODES.md +240 -0
- package/docs/INSTALLATION_BOTH.md +145 -0
- package/docs/TERMS.md +352 -0
- package/docs/TOOLS_REFERENCE.md +1622 -0
- package/docs/TOOL_PARAMETERS.md +496 -0
- package/docs/TOOL_PROFILES.md +236 -0
- package/docs/WORKFLOWS.md +987 -0
- package/docs/WORKFLOW_OUTPUT.md +198 -0
- package/docs/WORKFLOW_PROGRESS_TRACKING.md +305 -0
- package/docs/workflows/design-brainstorm.md +335 -0
- package/package.json +97 -0
- package/profiles/balanced.json +37 -0
- package/profiles/code_focus.json +37 -0
- package/profiles/debug_intensive.json +34 -0
- package/profiles/full.json +37 -0
- package/profiles/minimal.json +37 -0
- package/profiles/research_power.json +37 -0
- package/profiles/workflow_builder.json +37 -0
- package/smithery.yaml +66 -0
- package/start.sh +8 -0
- package/tools.config.json +81 -0
- package/tsconfig.json +18 -0
- package/workflows/accessibility-code-audit.yaml +92 -0
- package/workflows/code-architecture-review.yaml +202 -0
- package/workflows/code-review.yaml +142 -0
- package/workflows/core/iterative-problem-solver.yaml +283 -0
- package/workflows/creative-brainstorm-yaml.yaml +215 -0
- package/workflows/pingpong.yaml +141 -0
- package/workflows/system/README.md +412 -0
- package/workflows/system/challenger.yaml +175 -0
- package/workflows/system/scout.yaml +164 -0
- package/workflows/system/verifier.yaml +133 -0
- package/workflows/ultra-creative-brainstorm.yaml +318 -0
- package/workflows/ux-research-flow.yaml +92 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Manager for Focus MCP Server
|
|
3
|
+
* Manages session lifecycle, analytics, and cross-session intelligence
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "fs/promises";
|
|
6
|
+
import * as fsSync from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import { SessionLogger } from "./session-logger.js";
|
|
9
|
+
export class SessionManager {
|
|
10
|
+
constructor(sessionDir = "./.focus-sessions") {
|
|
11
|
+
this.sessionCache = new Map();
|
|
12
|
+
this.analytics = null;
|
|
13
|
+
this.sessionLogger = new SessionLogger({ sessionDir });
|
|
14
|
+
this.sessionDir = path.resolve(sessionDir);
|
|
15
|
+
this.ensureSessionDirectorySync();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Ensure session directory exists (synchronous version for constructor)
|
|
19
|
+
*/
|
|
20
|
+
ensureSessionDirectorySync() {
|
|
21
|
+
try {
|
|
22
|
+
fsSync.mkdirSync(this.sessionDir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
// Ignore EEXIST errors (directory already exists)
|
|
26
|
+
if (error?.code !== 'EEXIST') {
|
|
27
|
+
console.error(`Failed to create session directory: ${error}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Ensure session directory exists (async version for other operations)
|
|
33
|
+
*/
|
|
34
|
+
async ensureSessionDirectory() {
|
|
35
|
+
try {
|
|
36
|
+
await fs.mkdir(this.sessionDir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error(`Failed to create session directory: ${error}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Start a new session
|
|
44
|
+
*/
|
|
45
|
+
async startSession(mode, query, config) {
|
|
46
|
+
return this.sessionLogger.startSession(mode, query);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get current active session
|
|
50
|
+
*/
|
|
51
|
+
getCurrentSession() {
|
|
52
|
+
return this.sessionLogger.getCurrentSession();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* End current session
|
|
56
|
+
*/
|
|
57
|
+
async endSession(save = true) {
|
|
58
|
+
await this.sessionLogger.endSession(save);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Load all sessions for analysis
|
|
62
|
+
*/
|
|
63
|
+
async loadAllSessions() {
|
|
64
|
+
const sessions = [];
|
|
65
|
+
try {
|
|
66
|
+
const files = await fs.readdir(this.sessionDir);
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
if (file.endsWith(".json")) {
|
|
69
|
+
const filepath = path.join(this.sessionDir, file);
|
|
70
|
+
const content = await fs.readFile(filepath, "utf-8");
|
|
71
|
+
const session = JSON.parse(content);
|
|
72
|
+
sessions.push(session);
|
|
73
|
+
this.sessionCache.set(session.id, session);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error(`Error loading sessions: ${error}`);
|
|
79
|
+
}
|
|
80
|
+
return sessions;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Analyze sessions for patterns and insights
|
|
84
|
+
*/
|
|
85
|
+
async analyzeSessions(dateRange, mode, metrics = ["model-usage", "response-time", "consensus-rate"]) {
|
|
86
|
+
const sessions = await this.loadAllSessions();
|
|
87
|
+
// Filter sessions by date range and mode
|
|
88
|
+
const filteredSessions = sessions.filter(session => {
|
|
89
|
+
const sessionDate = new Date(session.timestamp);
|
|
90
|
+
if (dateRange) {
|
|
91
|
+
if (sessionDate < dateRange.start || sessionDate > dateRange.end) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (mode && session.mode !== mode) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
});
|
|
100
|
+
// Calculate analytics
|
|
101
|
+
const analytics = {
|
|
102
|
+
totalSessions: filteredSessions.length,
|
|
103
|
+
averageDuration: 0,
|
|
104
|
+
averageSteps: 0,
|
|
105
|
+
modelUsageStats: new Map(),
|
|
106
|
+
modeDistribution: new Map(),
|
|
107
|
+
domainDistribution: new Map(),
|
|
108
|
+
consensusRate: 0,
|
|
109
|
+
topicClusters: []
|
|
110
|
+
};
|
|
111
|
+
if (filteredSessions.length === 0) {
|
|
112
|
+
return analytics;
|
|
113
|
+
}
|
|
114
|
+
// Calculate basic metrics
|
|
115
|
+
let totalDuration = 0;
|
|
116
|
+
let totalSteps = 0;
|
|
117
|
+
for (const session of filteredSessions) {
|
|
118
|
+
// Duration
|
|
119
|
+
if (session.totalDuration) {
|
|
120
|
+
totalDuration += session.totalDuration;
|
|
121
|
+
}
|
|
122
|
+
// Steps
|
|
123
|
+
totalSteps += session.steps.length;
|
|
124
|
+
// Mode distribution
|
|
125
|
+
const modeCount = analytics.modeDistribution.get(session.mode) || 0;
|
|
126
|
+
analytics.modeDistribution.set(session.mode, modeCount + 1);
|
|
127
|
+
// Domain distribution
|
|
128
|
+
if (session.domain) {
|
|
129
|
+
const domainCount = analytics.domainDistribution.get(session.domain) || 0;
|
|
130
|
+
analytics.domainDistribution.set(session.domain, domainCount + 1);
|
|
131
|
+
}
|
|
132
|
+
// Model usage stats
|
|
133
|
+
for (const step of session.steps) {
|
|
134
|
+
const modelKey = `${step.model}:${step.provider}`;
|
|
135
|
+
const stats = analytics.modelUsageStats.get(modelKey) || {
|
|
136
|
+
count: 0,
|
|
137
|
+
averageDuration: 0,
|
|
138
|
+
totalTokens: 0
|
|
139
|
+
};
|
|
140
|
+
stats.count++;
|
|
141
|
+
stats.averageDuration =
|
|
142
|
+
(stats.averageDuration * (stats.count - 1) + step.duration) / stats.count;
|
|
143
|
+
stats.totalTokens += step.tokens?.total || 0;
|
|
144
|
+
analytics.modelUsageStats.set(modelKey, stats);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
analytics.averageDuration = totalDuration / filteredSessions.length;
|
|
148
|
+
analytics.averageSteps = totalSteps / filteredSessions.length;
|
|
149
|
+
// Calculate consensus rate (simplified - looks for similar responses)
|
|
150
|
+
if (metrics.includes("consensus-rate")) {
|
|
151
|
+
analytics.consensusRate = this.calculateConsensusRate(filteredSessions);
|
|
152
|
+
}
|
|
153
|
+
// Topic clustering (simplified)
|
|
154
|
+
if (metrics.includes("topic-clustering")) {
|
|
155
|
+
analytics.topicClusters = this.clusterTopics(filteredSessions);
|
|
156
|
+
}
|
|
157
|
+
this.analytics = analytics;
|
|
158
|
+
return analytics;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Calculate consensus rate across sessions
|
|
162
|
+
*/
|
|
163
|
+
calculateConsensusRate(sessions) {
|
|
164
|
+
if (sessions.length < 2)
|
|
165
|
+
return 1.0;
|
|
166
|
+
let totalComparisons = 0;
|
|
167
|
+
let agreements = 0;
|
|
168
|
+
// Compare synthesis or final responses
|
|
169
|
+
for (let i = 0; i < sessions.length - 1; i++) {
|
|
170
|
+
for (let j = i + 1; j < sessions.length; j++) {
|
|
171
|
+
const session1 = sessions[i];
|
|
172
|
+
const session2 = sessions[j];
|
|
173
|
+
// Skip if different modes
|
|
174
|
+
if (session1.mode !== session2.mode)
|
|
175
|
+
continue;
|
|
176
|
+
totalComparisons++;
|
|
177
|
+
// Simple similarity check (could be enhanced with NLP)
|
|
178
|
+
const text1 = session1.synthesis || session1.steps[session1.steps.length - 1]?.response || "";
|
|
179
|
+
const text2 = session2.synthesis || session2.steps[session2.steps.length - 1]?.response || "";
|
|
180
|
+
const similarity = this.calculateTextSimilarity(text1, text2);
|
|
181
|
+
if (similarity > 0.7) {
|
|
182
|
+
agreements++;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return totalComparisons > 0 ? agreements / totalComparisons : 0;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Simple text similarity calculation
|
|
190
|
+
*/
|
|
191
|
+
calculateTextSimilarity(text1, text2) {
|
|
192
|
+
const words1 = new Set(text1.toLowerCase().split(/\s+/));
|
|
193
|
+
const words2 = new Set(text2.toLowerCase().split(/\s+/));
|
|
194
|
+
const intersection = new Set([...words1].filter(x => words2.has(x)));
|
|
195
|
+
const union = new Set([...words1, ...words2]);
|
|
196
|
+
return union.size > 0 ? intersection.size / union.size : 0;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Cluster sessions by topic
|
|
200
|
+
*/
|
|
201
|
+
clusterTopics(sessions) {
|
|
202
|
+
const clusters = new Map();
|
|
203
|
+
// Simple keyword-based clustering
|
|
204
|
+
const topicKeywords = {
|
|
205
|
+
"architecture": ["architecture", "design", "system", "pattern", "structure"],
|
|
206
|
+
"debugging": ["debug", "error", "bug", "fix", "issue"],
|
|
207
|
+
"optimization": ["optimize", "performance", "speed", "efficiency", "improve"],
|
|
208
|
+
"security": ["security", "vulnerability", "authentication", "authorization", "encryption"],
|
|
209
|
+
"testing": ["test", "testing", "unit", "integration", "coverage"],
|
|
210
|
+
"database": ["database", "sql", "query", "schema", "migration"],
|
|
211
|
+
"api": ["api", "endpoint", "rest", "graphql", "webhook"],
|
|
212
|
+
"frontend": ["frontend", "ui", "ux", "react", "vue", "css"],
|
|
213
|
+
"backend": ["backend", "server", "api", "database", "microservice"],
|
|
214
|
+
"devops": ["devops", "deployment", "ci", "cd", "docker", "kubernetes"]
|
|
215
|
+
};
|
|
216
|
+
for (const session of sessions) {
|
|
217
|
+
const queryLower = session.query.toLowerCase();
|
|
218
|
+
for (const [topic, keywords] of Object.entries(topicKeywords)) {
|
|
219
|
+
if (keywords.some(keyword => queryLower.includes(keyword))) {
|
|
220
|
+
const sessionIds = clusters.get(topic) || [];
|
|
221
|
+
sessionIds.push(session.id);
|
|
222
|
+
clusters.set(topic, sessionIds);
|
|
223
|
+
break; // Only assign to first matching topic
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Convert to result format
|
|
228
|
+
const result = [];
|
|
229
|
+
for (const [topic, sessionIds] of clusters.entries()) {
|
|
230
|
+
result.push({
|
|
231
|
+
topic,
|
|
232
|
+
sessions: sessionIds,
|
|
233
|
+
commonPatterns: this.findCommonPatterns(sessionIds, sessions)
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Find common patterns in a group of sessions
|
|
240
|
+
*/
|
|
241
|
+
findCommonPatterns(sessionIds, allSessions) {
|
|
242
|
+
const patterns = [];
|
|
243
|
+
const sessions = allSessions.filter(s => sessionIds.includes(s.id));
|
|
244
|
+
if (sessions.length < 2)
|
|
245
|
+
return patterns;
|
|
246
|
+
// Find common model sequences
|
|
247
|
+
const modelSequences = sessions.map(s => s.steps.map(step => step.model).join(" -> "));
|
|
248
|
+
// Find most common sequence
|
|
249
|
+
const sequenceCount = new Map();
|
|
250
|
+
for (const seq of modelSequences) {
|
|
251
|
+
sequenceCount.set(seq, (sequenceCount.get(seq) || 0) + 1);
|
|
252
|
+
}
|
|
253
|
+
const mostCommonSequence = [...sequenceCount.entries()]
|
|
254
|
+
.sort((a, b) => b[1] - a[1])[0];
|
|
255
|
+
if (mostCommonSequence && mostCommonSequence[1] > 1) {
|
|
256
|
+
patterns.push(`Model sequence: ${mostCommonSequence[0]}`);
|
|
257
|
+
}
|
|
258
|
+
// Find common response patterns (simplified)
|
|
259
|
+
const commonWords = this.findCommonWords(sessions);
|
|
260
|
+
if (commonWords.length > 0) {
|
|
261
|
+
patterns.push(`Common terms: ${commonWords.slice(0, 5).join(", ")}`);
|
|
262
|
+
}
|
|
263
|
+
return patterns;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Find common words across sessions
|
|
267
|
+
*/
|
|
268
|
+
findCommonWords(sessions) {
|
|
269
|
+
const wordFrequency = new Map();
|
|
270
|
+
for (const session of sessions) {
|
|
271
|
+
const text = session.synthesis ||
|
|
272
|
+
session.steps.map(s => s.response).join(" ");
|
|
273
|
+
const words = text.toLowerCase()
|
|
274
|
+
.split(/\s+/)
|
|
275
|
+
.filter(word => word.length > 4); // Filter short words
|
|
276
|
+
for (const word of words) {
|
|
277
|
+
wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Sort by frequency and return top words
|
|
281
|
+
return [...wordFrequency.entries()]
|
|
282
|
+
.sort((a, b) => b[1] - a[1])
|
|
283
|
+
.map(entry => entry[0])
|
|
284
|
+
.slice(0, 10);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Find sessions similar to a query
|
|
288
|
+
*/
|
|
289
|
+
async findSimilarSessions(query, limit = 5) {
|
|
290
|
+
const sessions = await this.loadAllSessions();
|
|
291
|
+
const results = [];
|
|
292
|
+
for (const session of sessions) {
|
|
293
|
+
const similarity = this.calculateTextSimilarity(query, session.query);
|
|
294
|
+
results.push({ session, similarity });
|
|
295
|
+
}
|
|
296
|
+
// Sort by similarity and return top results
|
|
297
|
+
return results
|
|
298
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
299
|
+
.slice(0, limit);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get session recommendations based on history
|
|
303
|
+
*/
|
|
304
|
+
async getRecommendations(currentQuery) {
|
|
305
|
+
const similar = await this.findSimilarSessions(currentQuery, 3);
|
|
306
|
+
// Extract suggested models from similar sessions
|
|
307
|
+
const modelCounts = new Map();
|
|
308
|
+
const modeCounts = new Map();
|
|
309
|
+
for (const { session } of similar) {
|
|
310
|
+
// Count models
|
|
311
|
+
for (const step of session.steps) {
|
|
312
|
+
const modelKey = `${step.model}:${step.provider}`;
|
|
313
|
+
modelCounts.set(modelKey, (modelCounts.get(modelKey) || 0) + 1);
|
|
314
|
+
}
|
|
315
|
+
// Count modes
|
|
316
|
+
modeCounts.set(session.mode, (modeCounts.get(session.mode) || 0) + 1);
|
|
317
|
+
}
|
|
318
|
+
// Get top models
|
|
319
|
+
const suggestedModels = [...modelCounts.entries()]
|
|
320
|
+
.sort((a, b) => b[1] - a[1])
|
|
321
|
+
.slice(0, 3)
|
|
322
|
+
.map(entry => entry[0]);
|
|
323
|
+
// Get most common mode
|
|
324
|
+
const suggestedMode = [...modeCounts.entries()]
|
|
325
|
+
.sort((a, b) => b[1] - a[1])[0]?.[0] || "brainstorm";
|
|
326
|
+
return {
|
|
327
|
+
similarSessions: similar.map(({ session, similarity }) => ({
|
|
328
|
+
id: session.id,
|
|
329
|
+
query: session.query,
|
|
330
|
+
similarity
|
|
331
|
+
})),
|
|
332
|
+
suggestedModels,
|
|
333
|
+
suggestedMode
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Export analytics report
|
|
338
|
+
*/
|
|
339
|
+
async exportAnalyticsReport(format = "markdown") {
|
|
340
|
+
if (!this.analytics) {
|
|
341
|
+
await this.analyzeSessions();
|
|
342
|
+
}
|
|
343
|
+
const filename = `analytics-${new Date().toISOString().split('T')[0]}.${format === "html" ? "html" : format === "json" ? "json" : "md"}`;
|
|
344
|
+
const filepath = path.join(this.sessionDir, filename);
|
|
345
|
+
let content;
|
|
346
|
+
switch (format) {
|
|
347
|
+
case "json":
|
|
348
|
+
content = this.formatAnalyticsAsJSON();
|
|
349
|
+
break;
|
|
350
|
+
case "html":
|
|
351
|
+
content = this.formatAnalyticsAsHTML();
|
|
352
|
+
break;
|
|
353
|
+
case "markdown":
|
|
354
|
+
default:
|
|
355
|
+
content = this.formatAnalyticsAsMarkdown();
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
await fs.writeFile(filepath, content, "utf-8");
|
|
359
|
+
return filepath;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Format analytics as Markdown
|
|
363
|
+
*/
|
|
364
|
+
formatAnalyticsAsMarkdown() {
|
|
365
|
+
if (!this.analytics)
|
|
366
|
+
return "No analytics data available";
|
|
367
|
+
const lines = [];
|
|
368
|
+
const a = this.analytics;
|
|
369
|
+
lines.push("# Focus MCP Session Analytics Report\n");
|
|
370
|
+
lines.push(`Generated: ${new Date().toISOString()}\n`);
|
|
371
|
+
lines.push("## Overview");
|
|
372
|
+
lines.push(`- **Total Sessions**: ${a.totalSessions}`);
|
|
373
|
+
lines.push(`- **Average Duration**: ${(a.averageDuration / 1000).toFixed(1)}s`);
|
|
374
|
+
lines.push(`- **Average Steps**: ${a.averageSteps.toFixed(1)}`);
|
|
375
|
+
lines.push(`- **Consensus Rate**: ${(a.consensusRate * 100).toFixed(1)}%\n`);
|
|
376
|
+
lines.push("## Mode Distribution");
|
|
377
|
+
for (const [mode, count] of a.modeDistribution.entries()) {
|
|
378
|
+
lines.push(`- ${mode}: ${count} sessions`);
|
|
379
|
+
}
|
|
380
|
+
lines.push("");
|
|
381
|
+
if (a.domainDistribution.size > 0) {
|
|
382
|
+
lines.push("## Domain Distribution");
|
|
383
|
+
for (const [domain, count] of a.domainDistribution.entries()) {
|
|
384
|
+
lines.push(`- ${domain}: ${count} sessions`);
|
|
385
|
+
}
|
|
386
|
+
lines.push("");
|
|
387
|
+
}
|
|
388
|
+
lines.push("## Model Usage Statistics");
|
|
389
|
+
lines.push("| Model | Provider | Count | Avg Duration | Total Tokens |");
|
|
390
|
+
lines.push("|-------|----------|-------|--------------|--------------|");
|
|
391
|
+
for (const [modelKey, stats] of a.modelUsageStats.entries()) {
|
|
392
|
+
const [model, provider] = modelKey.split(":");
|
|
393
|
+
lines.push(`| ${model} | ${provider} | ${stats.count} | ${(stats.averageDuration / 1000).toFixed(1)}s | ${stats.totalTokens} |`);
|
|
394
|
+
}
|
|
395
|
+
lines.push("");
|
|
396
|
+
if (a.topicClusters.length > 0) {
|
|
397
|
+
lines.push("## Topic Clusters");
|
|
398
|
+
for (const cluster of a.topicClusters) {
|
|
399
|
+
lines.push(`\n### ${cluster.topic}`);
|
|
400
|
+
lines.push(`- Sessions: ${cluster.sessions.length}`);
|
|
401
|
+
if (cluster.commonPatterns.length > 0) {
|
|
402
|
+
lines.push("- Common Patterns:");
|
|
403
|
+
for (const pattern of cluster.commonPatterns) {
|
|
404
|
+
lines.push(` - ${pattern}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return lines.join("\n");
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Format analytics as JSON
|
|
413
|
+
*/
|
|
414
|
+
formatAnalyticsAsJSON() {
|
|
415
|
+
if (!this.analytics)
|
|
416
|
+
return "{}";
|
|
417
|
+
return JSON.stringify({
|
|
418
|
+
...this.analytics,
|
|
419
|
+
modelUsageStats: Object.fromEntries(this.analytics.modelUsageStats),
|
|
420
|
+
modeDistribution: Object.fromEntries(this.analytics.modeDistribution),
|
|
421
|
+
domainDistribution: Object.fromEntries(this.analytics.domainDistribution)
|
|
422
|
+
}, null, 2);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Format analytics as HTML
|
|
426
|
+
*/
|
|
427
|
+
formatAnalyticsAsHTML() {
|
|
428
|
+
if (!this.analytics)
|
|
429
|
+
return "<p>No analytics data available</p>";
|
|
430
|
+
const a = this.analytics;
|
|
431
|
+
return `<!DOCTYPE html>
|
|
432
|
+
<html lang="en">
|
|
433
|
+
<head>
|
|
434
|
+
<meta charset="UTF-8">
|
|
435
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
436
|
+
<title>Focus MCP Analytics Report</title>
|
|
437
|
+
<style>
|
|
438
|
+
body {
|
|
439
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
440
|
+
line-height: 1.6;
|
|
441
|
+
max-width: 1200px;
|
|
442
|
+
margin: 0 auto;
|
|
443
|
+
padding: 20px;
|
|
444
|
+
background: #f5f5f5;
|
|
445
|
+
}
|
|
446
|
+
.header {
|
|
447
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
448
|
+
color: white;
|
|
449
|
+
padding: 30px;
|
|
450
|
+
border-radius: 10px;
|
|
451
|
+
margin-bottom: 30px;
|
|
452
|
+
}
|
|
453
|
+
.card {
|
|
454
|
+
background: white;
|
|
455
|
+
padding: 20px;
|
|
456
|
+
border-radius: 10px;
|
|
457
|
+
margin-bottom: 20px;
|
|
458
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
459
|
+
}
|
|
460
|
+
.stats-grid {
|
|
461
|
+
display: grid;
|
|
462
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
463
|
+
gap: 20px;
|
|
464
|
+
margin: 20px 0;
|
|
465
|
+
}
|
|
466
|
+
.stat-card {
|
|
467
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
468
|
+
color: white;
|
|
469
|
+
padding: 20px;
|
|
470
|
+
border-radius: 10px;
|
|
471
|
+
text-align: center;
|
|
472
|
+
}
|
|
473
|
+
.stat-value {
|
|
474
|
+
font-size: 32px;
|
|
475
|
+
font-weight: bold;
|
|
476
|
+
}
|
|
477
|
+
.stat-label {
|
|
478
|
+
font-size: 14px;
|
|
479
|
+
opacity: 0.9;
|
|
480
|
+
margin-top: 5px;
|
|
481
|
+
}
|
|
482
|
+
table {
|
|
483
|
+
width: 100%;
|
|
484
|
+
border-collapse: collapse;
|
|
485
|
+
}
|
|
486
|
+
th, td {
|
|
487
|
+
padding: 12px;
|
|
488
|
+
text-align: left;
|
|
489
|
+
border-bottom: 1px solid #ddd;
|
|
490
|
+
}
|
|
491
|
+
th {
|
|
492
|
+
background: #f0f0f0;
|
|
493
|
+
font-weight: 600;
|
|
494
|
+
}
|
|
495
|
+
</style>
|
|
496
|
+
</head>
|
|
497
|
+
<body>
|
|
498
|
+
<div class="header">
|
|
499
|
+
<h1>Focus MCP Analytics Report</h1>
|
|
500
|
+
<p>Generated: ${new Date().toISOString()}</p>
|
|
501
|
+
</div>
|
|
502
|
+
|
|
503
|
+
<div class="stats-grid">
|
|
504
|
+
<div class="stat-card">
|
|
505
|
+
<div class="stat-value">${a.totalSessions}</div>
|
|
506
|
+
<div class="stat-label">Total Sessions</div>
|
|
507
|
+
</div>
|
|
508
|
+
<div class="stat-card">
|
|
509
|
+
<div class="stat-value">${(a.averageDuration / 1000).toFixed(1)}s</div>
|
|
510
|
+
<div class="stat-label">Avg Duration</div>
|
|
511
|
+
</div>
|
|
512
|
+
<div class="stat-card">
|
|
513
|
+
<div class="stat-value">${a.averageSteps.toFixed(1)}</div>
|
|
514
|
+
<div class="stat-label">Avg Steps</div>
|
|
515
|
+
</div>
|
|
516
|
+
<div class="stat-card">
|
|
517
|
+
<div class="stat-value">${(a.consensusRate * 100).toFixed(1)}%</div>
|
|
518
|
+
<div class="stat-label">Consensus Rate</div>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
|
|
522
|
+
<div class="card">
|
|
523
|
+
<h2>Model Usage Statistics</h2>
|
|
524
|
+
<table>
|
|
525
|
+
<thead>
|
|
526
|
+
<tr>
|
|
527
|
+
<th>Model</th>
|
|
528
|
+
<th>Provider</th>
|
|
529
|
+
<th>Count</th>
|
|
530
|
+
<th>Avg Duration</th>
|
|
531
|
+
<th>Total Tokens</th>
|
|
532
|
+
</tr>
|
|
533
|
+
</thead>
|
|
534
|
+
<tbody>
|
|
535
|
+
${[...a.modelUsageStats.entries()].map(([modelKey, stats]) => {
|
|
536
|
+
const [model, provider] = modelKey.split(":");
|
|
537
|
+
return `
|
|
538
|
+
<tr>
|
|
539
|
+
<td>${model}</td>
|
|
540
|
+
<td>${provider}</td>
|
|
541
|
+
<td>${stats.count}</td>
|
|
542
|
+
<td>${(stats.averageDuration / 1000).toFixed(1)}s</td>
|
|
543
|
+
<td>${stats.totalTokens}</td>
|
|
544
|
+
</tr>
|
|
545
|
+
`;
|
|
546
|
+
}).join('')}
|
|
547
|
+
</tbody>
|
|
548
|
+
</table>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
${a.topicClusters.length > 0 ? `
|
|
552
|
+
<div class="card">
|
|
553
|
+
<h2>Topic Clusters</h2>
|
|
554
|
+
${a.topicClusters.map(cluster => `
|
|
555
|
+
<h3>${cluster.topic}</h3>
|
|
556
|
+
<p>Sessions: ${cluster.sessions.length}</p>
|
|
557
|
+
${cluster.commonPatterns.length > 0 ? `
|
|
558
|
+
<p>Common Patterns:</p>
|
|
559
|
+
<ul>
|
|
560
|
+
${cluster.commonPatterns.map(p => `<li>${p}</li>`).join('')}
|
|
561
|
+
</ul>
|
|
562
|
+
` : ''}
|
|
563
|
+
`).join('')}
|
|
564
|
+
</div>
|
|
565
|
+
` : ''}
|
|
566
|
+
</body>
|
|
567
|
+
</html>`;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
// Export singleton instance
|
|
571
|
+
export const sessionManager = new SessionManager();
|