This is page 5 of 13. Use http://codebase.md/tejpalvirk/contextmanager?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .gitattributes
├── .gitignore
├── build-all-domains.sh
├── developer
│ ├── .gitattributes
│ ├── developer_advancedcontext.txt
│ ├── developer_buildcontext.txt
│ ├── developer_deletecontext.txt
│ ├── developer_endsession_examples.txt
│ ├── developer_endsession.txt
│ ├── developer_loadcontext.txt
│ ├── developer_startsession.txt
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── README.md
│ └── tsconfig.json
├── dist
│ ├── developer
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── main
│ │ ├── descriptions
│ │ │ ├── common_advancedcontext.txt
│ │ │ ├── common_buildcontext.txt
│ │ │ ├── common_deletecontext.txt
│ │ │ ├── common_endsession.txt
│ │ │ ├── common_loadcontext.txt
│ │ │ ├── common_startsession.txt
│ │ │ ├── developer_advancedcontext.txt
│ │ │ ├── developer_buildcontext.txt
│ │ │ ├── developer_deletecontext.txt
│ │ │ ├── developer_endsession_examples.txt
│ │ │ ├── developer_endsession.txt
│ │ │ ├── developer_loadcontext.txt
│ │ │ ├── developer_startsession.txt
│ │ │ ├── project_advancedcontext.txt
│ │ │ ├── project_buildcontext.txt
│ │ │ ├── project_deletecontext.txt
│ │ │ ├── project_endsession_examples.txt
│ │ │ ├── project_endsession.txt
│ │ │ ├── project_loadcontext.txt
│ │ │ ├── project_startsession.txt
│ │ │ ├── qualitativeresearch_advancedcontext.txt
│ │ │ ├── qualitativeresearch_buildcontext.txt
│ │ │ ├── qualitativeresearch_deletecontext.txt
│ │ │ ├── qualitativeresearch_endsession_examples.txt
│ │ │ ├── qualitativeresearch_endsession.txt
│ │ │ ├── qualitativeresearch_loadcontext.txt
│ │ │ ├── qualitativeresearch_startsession.txt
│ │ │ ├── quantitativeresearch_advancedcontext.txt
│ │ │ ├── quantitativeresearch_buildcontext.txt
│ │ │ ├── quantitativeresearch_deletecontext.txt
│ │ │ ├── quantitativeresearch_endsession_examples.txt
│ │ │ ├── quantitativeresearch_endsession.txt
│ │ │ ├── quantitativeresearch_loadcontext.txt
│ │ │ ├── quantitativeresearch_startsession.txt
│ │ │ ├── student_advancedcontext.txt
│ │ │ ├── student_buildcontext.txt
│ │ │ ├── student_deletecontext.txt
│ │ │ ├── student_endsession_examples.txt
│ │ │ ├── student_endsession.txt
│ │ │ ├── student_loadcontext.txt
│ │ │ └── student_startsession.txt
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ ├── mcp.d.ts
│ │ └── mcp.js
│ ├── project
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── qualitativeresearch
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── quantitativeresearch
│ │ ├── index.d.ts
│ │ └── index.js
│ └── student
│ ├── index.d.ts
│ └── index.js
├── main
│ ├── descriptions
│ │ ├── common_advancedcontext.txt
│ │ ├── common_buildcontext.txt
│ │ ├── common_deletecontext.txt
│ │ ├── common_endsession.txt
│ │ ├── common_loadcontext.txt
│ │ ├── common_startsession.txt
│ │ ├── developer_advancedcontext.txt
│ │ ├── developer_buildcontext.txt
│ │ ├── developer_deletecontext.txt
│ │ ├── developer_endsession_examples.txt
│ │ ├── developer_endsession.txt
│ │ ├── developer_loadcontext.txt
│ │ ├── developer_startsession.txt
│ │ ├── project_advancedcontext.txt
│ │ ├── project_buildcontext.txt
│ │ ├── project_deletecontext.txt
│ │ ├── project_endsession_examples.txt
│ │ ├── project_endsession.txt
│ │ ├── project_loadcontext.txt
│ │ ├── project_startsession.txt
│ │ ├── qualitativeresearch_advancedcontext.txt
│ │ ├── qualitativeresearch_buildcontext.txt
│ │ ├── qualitativeresearch_deletecontext.txt
│ │ ├── qualitativeresearch_endsession_examples.txt
│ │ ├── qualitativeresearch_endsession.txt
│ │ ├── qualitativeresearch_loadcontext.txt
│ │ ├── qualitativeresearch_startsession.txt
│ │ ├── quantitativeresearch_advancedcontext.txt
│ │ ├── quantitativeresearch_buildcontext.txt
│ │ ├── quantitativeresearch_deletecontext.txt
│ │ ├── quantitativeresearch_endsession_examples.txt
│ │ ├── quantitativeresearch_endsession.txt
│ │ ├── quantitativeresearch_loadcontext.txt
│ │ ├── quantitativeresearch_startsession.txt
│ │ ├── student_advancedcontext.txt
│ │ ├── student_buildcontext.txt
│ │ ├── student_deletecontext.txt
│ │ ├── student_endsession_examples.txt
│ │ ├── student_endsession.txt
│ │ ├── student_loadcontext.txt
│ │ └── student_startsession.txt
│ ├── index.js
│ ├── index.ts
│ ├── mcp.ts
│ ├── package.json
│ ├── README.md
│ └── tsconfig.json
├── package-lock.json
├── package.json
├── project
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── project_advancedcontext.txt
│ ├── project_buildcontext.txt
│ ├── project_deletecontext.txt
│ ├── project_endsession_examples.txt
│ ├── project_endsession.txt
│ ├── project_loadcontext.txt
│ ├── project_startsession.txt
│ ├── README.md
│ └── tsconfig.json
├── qualitativeresearch
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── qualitativeresearch_advancedcontext.txt
│ ├── qualitativeresearch_buildcontext.txt
│ ├── qualitativeresearch_deletecontext.txt
│ ├── qualitativeresearch_endsession_examples.txt
│ ├── qualitativeresearch_endsession.txt
│ ├── qualitativeresearch_loadcontext.txt
│ ├── qualitativeresearch_startsession.txt
│ ├── README.md
│ └── tsconfig.json
├── quantitativeresearch
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── quantitativeresearch_advancedcontext.txt
│ ├── quantitativeresearch_buildcontext.txt
│ ├── quantitativeresearch_deletecontext.txt
│ ├── quantitativeresearch_endsession_examples.txt
│ ├── quantitativeresearch_endsession.txt
│ ├── quantitativeresearch_loadcontext.txt
│ ├── quantitativeresearch_startsession.txt
│ ├── README.md
│ └── tsconfig.json
├── README.md
├── student
│ ├── .gitattributes
│ ├── Dockerfile
│ ├── index.d.ts
│ ├── index.js
│ ├── index.ts
│ ├── package.json
│ ├── README.md
│ ├── student_advancedcontext.txt
│ ├── student_buildcontext.txt
│ ├── student_deletecontext.txt
│ ├── student_endsession_examples.txt
│ ├── student_endsession.txt
│ ├── student_loadcontext.txt
│ ├── student_startsession.txt
│ └── tsconfig.json
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/developer/index.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4 | import { promises as fs } from 'fs';
5 | import * as path from 'path';
6 | import { fileURLToPath } from 'url';
7 | import { z } from "zod";
8 | import { readFileSync, existsSync } from "fs";
9 | // Define memory file path using environment variable with fallback
10 | const parentPath = path.dirname(fileURLToPath(import.meta.url));
11 | const defaultMemoryPath = path.join(parentPath, 'memory.json');
12 | const defaultSessionsPath = path.join(parentPath, 'sessions.json');
13 | // Properly handle absolute and relative paths for MEMORY_FILE_PATH
14 | const MEMORY_FILE_PATH = process.env.MEMORY_FILE_PATH
15 | ? path.isAbsolute(process.env.MEMORY_FILE_PATH)
16 | ? process.env.MEMORY_FILE_PATH // Use absolute path as is
17 | : path.join(process.cwd(), process.env.MEMORY_FILE_PATH) // Relative to current working directory
18 | : defaultMemoryPath; // Default fallback
19 | // Properly handle absolute and relative paths for SESSIONS_FILE_PATH
20 | const SESSIONS_FILE_PATH = process.env.SESSIONS_FILE_PATH
21 | ? path.isAbsolute(process.env.SESSIONS_FILE_PATH)
22 | ? process.env.SESSIONS_FILE_PATH // Use absolute path as is
23 | : path.join(process.cwd(), process.env.SESSIONS_FILE_PATH) // Relative to current working directory
24 | : defaultSessionsPath; // Default fallback
25 | // Software Development specific entity types
26 | const VALID_ENTITY_TYPES = [
27 | 'project', // Overall software project
28 | 'component', // Module, service, or package within a project
29 | 'feature', // Specific functionality being developed
30 | 'issue', // Bug or problem to be fixed
31 | 'task', // Work item or activity needed for development
32 | 'technology', // Language, framework, or tool used
33 | 'decision', // Important technical or architectural decision
34 | 'milestone', // Key project deadline or phase
35 | 'environment', // Development, staging, production environments
36 | 'documentation', // Project documentation
37 | 'requirement', // Project requirement or specification
38 | 'status', // Entity status (inactive, active, or complete)
39 | 'priority' // Entity priority (low or high)
40 | ];
41 | // Software Development specific relation types
42 | const VALID_RELATION_TYPES = [
43 | 'depends_on', // Dependency relationship
44 | 'implements', // Component implements a feature
45 | 'blocked_by', // Task is blocked by an issue
46 | 'uses', // Component uses a technology
47 | 'part_of', // Component is part of a project
48 | 'contains', // Project contains a component
49 | 'related_to', // General relationship
50 | 'affects', // Issue affects a component
51 | 'resolves', // Task resolves an issue
52 | 'documented_in', // Component is documented in documentation
53 | 'decided_in', // Decision was made in a meeting
54 | 'required_by', // Feature is required by a requirement
55 | 'has_status', // Entity has a particular status
56 | 'has_priority', // Entity has a particular priority
57 | 'depends_on_milestone', // Task depends on reaching a milestone
58 | 'precedes', // Task precedes another task (for sequencing)
59 | 'tested_in' // Component is tested in an environment
60 | ];
61 | const __filename = fileURLToPath(import.meta.url);
62 | const __dirname = path.dirname(__filename);
63 | // Collect tool descriptions from text files
64 | const toolDescriptions = {
65 | 'startsession': '',
66 | 'loadcontext': '',
67 | 'deletecontext': '',
68 | 'buildcontext': '',
69 | 'advancedcontext': '',
70 | 'endsession': '',
71 | };
72 | for (const tool of Object.keys(toolDescriptions)) {
73 | try {
74 | const descriptionFilePath = path.resolve(__dirname, `developer_${tool}.txt`);
75 | if (existsSync(descriptionFilePath)) {
76 | toolDescriptions[tool] = readFileSync(descriptionFilePath, 'utf-8');
77 | }
78 | }
79 | catch (error) {
80 | console.error(`Error reading description file for tool '${tool}': ${error}`);
81 | }
82 | }
83 | // Session management functions
84 | async function loadSessionStates() {
85 | try {
86 | const fileContent = await fs.readFile(SESSIONS_FILE_PATH, 'utf-8');
87 | const sessions = JSON.parse(fileContent);
88 | // Convert from object to Map
89 | const sessionsMap = new Map();
90 | for (const [key, value] of Object.entries(sessions)) {
91 | sessionsMap.set(key, value);
92 | }
93 | return sessionsMap;
94 | }
95 | catch (error) {
96 | if (error instanceof Error && 'code' in error && error.code === "ENOENT") {
97 | return new Map();
98 | }
99 | throw error;
100 | }
101 | }
102 | async function saveSessionStates(sessionsMap) {
103 | // Convert from Map to object
104 | const sessions = {};
105 | for (const [key, value] of sessionsMap.entries()) {
106 | sessions[key] = value;
107 | }
108 | await fs.writeFile(SESSIONS_FILE_PATH, JSON.stringify(sessions, null, 2), 'utf-8');
109 | }
110 | // Generate a unique session ID
111 | function generateSessionId() {
112 | return `dev_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
113 | }
114 | // Basic validation functions
115 | function validateEntityType(entityType) {
116 | return VALID_ENTITY_TYPES.includes(entityType);
117 | }
118 | function validateRelationType(relationType) {
119 | return VALID_RELATION_TYPES.includes(relationType);
120 | }
121 | // Define the valid status and priority values
122 | const VALID_STATUS_VALUES = ['inactive', 'active', 'complete'];
123 | const VALID_PRIORITY_VALUES = ['low', 'high'];
124 | // The KnowledgeGraphManager class contains all operations to interact with the knowledge graph
125 | class KnowledgeGraphManager {
126 | async loadGraph() {
127 | try {
128 | const fileContent = await fs.readFile(MEMORY_FILE_PATH, 'utf-8');
129 | return JSON.parse(fileContent);
130 | }
131 | catch (error) {
132 | if (error instanceof Error && 'code' in error && error.code === "ENOENT") {
133 | return { entities: [], relations: [] };
134 | }
135 | throw error;
136 | }
137 | }
138 | async saveGraph(graph) {
139 | await fs.writeFile(MEMORY_FILE_PATH, JSON.stringify(graph, null, 2), 'utf-8');
140 | }
141 | // Initialize status and priority entities
142 | async initializeStatusAndPriority() {
143 | const graph = await this.loadGraph();
144 | // Create status entities if they don't exist
145 | for (const statusValue of VALID_STATUS_VALUES) {
146 | const statusName = `status:${statusValue}`;
147 | if (!graph.entities.some(e => e.name === statusName && e.entityType === 'status')) {
148 | graph.entities.push({
149 | name: statusName,
150 | entityType: 'status',
151 | observations: [`A ${statusValue} status value`]
152 | });
153 | }
154 | }
155 | // Create priority entities if they don't exist
156 | for (const priorityValue of VALID_PRIORITY_VALUES) {
157 | const priorityName = `priority:${priorityValue}`;
158 | if (!graph.entities.some(e => e.name === priorityName && e.entityType === 'priority')) {
159 | graph.entities.push({
160 | name: priorityName,
161 | entityType: 'priority',
162 | observations: [`A ${priorityValue} priority value`]
163 | });
164 | }
165 | }
166 | await this.saveGraph(graph);
167 | }
168 | // Helper method to get status of an entity
169 | async getEntityStatus(entityName) {
170 | const graph = await this.loadGraph();
171 | // Find status relation for this entity
172 | const statusRelation = graph.relations.find(r => r.from === entityName &&
173 | r.relationType === 'has_status');
174 | if (statusRelation) {
175 | // Extract status value from the status entity name (status:value)
176 | return statusRelation.to.split(':')[1];
177 | }
178 | return null;
179 | }
180 | // Helper method to get priority of an entity
181 | async getEntityPriority(entityName) {
182 | const graph = await this.loadGraph();
183 | // Find priority relation for this entity
184 | const priorityRelation = graph.relations.find(r => r.from === entityName &&
185 | r.relationType === 'has_priority');
186 | if (priorityRelation) {
187 | // Extract priority value from the priority entity name (priority:value)
188 | return priorityRelation.to.split(':')[1];
189 | }
190 | return null;
191 | }
192 | // Helper method to set status of an entity
193 | async setEntityStatus(entityName, statusValue) {
194 | if (!VALID_STATUS_VALUES.includes(statusValue)) {
195 | throw new Error(`Invalid status value: ${statusValue}. Valid values are: ${VALID_STATUS_VALUES.join(', ')}`);
196 | }
197 | const graph = await this.loadGraph();
198 | // Remove any existing status relations for this entity
199 | graph.relations = graph.relations.filter(r => !(r.from === entityName && r.relationType === 'has_status'));
200 | // Add new status relation
201 | graph.relations.push({
202 | from: entityName,
203 | to: `status:${statusValue}`,
204 | relationType: 'has_status'
205 | });
206 | await this.saveGraph(graph);
207 | }
208 | // Helper method to set priority of an entity
209 | async setEntityPriority(entityName, priorityValue) {
210 | if (!VALID_PRIORITY_VALUES.includes(priorityValue)) {
211 | throw new Error(`Invalid priority value: ${priorityValue}. Valid values are: ${VALID_PRIORITY_VALUES.join(', ')}`);
212 | }
213 | const graph = await this.loadGraph();
214 | // Remove any existing priority relations for this entity
215 | graph.relations = graph.relations.filter(r => !(r.from === entityName && r.relationType === 'has_priority'));
216 | // Add new priority relation
217 | graph.relations.push({
218 | from: entityName,
219 | to: `priority:${priorityValue}`,
220 | relationType: 'has_priority'
221 | });
222 | await this.saveGraph(graph);
223 | }
224 | async createEntities(entities) {
225 | // Validate entity types
226 | for (const entity of entities) {
227 | if (!validateEntityType(entity.entityType)) {
228 | throw new Error(`Invalid entity type: ${entity.entityType}. Valid types are: ${VALID_ENTITY_TYPES.join(', ')}`);
229 | }
230 | }
231 | const graph = await this.loadGraph();
232 | const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name));
233 | graph.entities.push(...newEntities);
234 | await this.saveGraph(graph);
235 | return newEntities;
236 | }
237 | async createRelations(relations) {
238 | // Validate relation types
239 | for (const relation of relations) {
240 | if (!validateRelationType(relation.relationType)) {
241 | throw new Error(`Invalid relation type: ${relation.relationType}. Valid types are: ${VALID_RELATION_TYPES.join(', ')}`);
242 | }
243 | }
244 | const graph = await this.loadGraph();
245 | // Check if entities exist
246 | for (const relation of relations) {
247 | const fromEntity = graph.entities.find(e => e.name === relation.from);
248 | const toEntity = graph.entities.find(e => e.name === relation.to);
249 | if (!fromEntity) {
250 | throw new Error(`Source entity '${relation.from}' does not exist. Please create it first.`);
251 | }
252 | if (!toEntity) {
253 | throw new Error(`Target entity '${relation.to}' does not exist. Please create it first.`);
254 | }
255 | }
256 | const newRelations = relations.filter(r => !graph.relations.some(existingRelation => existingRelation.from === r.from &&
257 | existingRelation.to === r.to &&
258 | existingRelation.relationType === r.relationType));
259 | graph.relations.push(...newRelations);
260 | await this.saveGraph(graph);
261 | return newRelations;
262 | }
263 | async addObservations(observations) {
264 | const graph = await this.loadGraph();
265 | const results = observations.map(o => {
266 | const entity = graph.entities.find(e => e.name === o.entityName);
267 | if (!entity) {
268 | throw new Error(`Entity with name ${o.entityName} not found`);
269 | }
270 | const newObservations = o.contents.filter(content => !entity.observations.includes(content));
271 | entity.observations.push(...newObservations);
272 | return { entityName: o.entityName, addedObservations: newObservations };
273 | });
274 | await this.saveGraph(graph);
275 | return results;
276 | }
277 | async deleteEntities(entityNames) {
278 | const graph = await this.loadGraph();
279 | graph.entities = graph.entities.filter(e => !entityNames.includes(e.name));
280 | graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to));
281 | await this.saveGraph(graph);
282 | }
283 | async deleteObservations(deletions) {
284 | const graph = await this.loadGraph();
285 | deletions.forEach(d => {
286 | const entity = graph.entities.find(e => e.name === d.entityName);
287 | if (entity) {
288 | entity.observations = entity.observations.filter(o => !d.observations.includes(o));
289 | }
290 | });
291 | await this.saveGraph(graph);
292 | }
293 | async deleteRelations(relations) {
294 | const graph = await this.loadGraph();
295 | graph.relations = graph.relations.filter(r => !relations.some(delRelation => r.from === delRelation.from &&
296 | r.to === delRelation.to &&
297 | r.relationType === delRelation.relationType));
298 | await this.saveGraph(graph);
299 | }
300 | async readGraph() {
301 | return this.loadGraph();
302 | }
303 | // Basic search function
304 | async searchNodes(query) {
305 | const graph = await this.loadGraph();
306 | // Filter entities
307 | const filteredEntities = graph.entities.filter(e => e.name.toLowerCase().includes(query.toLowerCase()) ||
308 | e.entityType.toLowerCase().includes(query.toLowerCase()) ||
309 | e.observations.some(o => o.toLowerCase().includes(query.toLowerCase())));
310 | // Create a Set of filtered entity names for quick lookup
311 | const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
312 | // Filter relations to only include those between filtered entities
313 | const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to));
314 | const filteredGraph = {
315 | entities: filteredEntities,
316 | relations: filteredRelations,
317 | };
318 | return filteredGraph;
319 | }
320 | async openNodes(names) {
321 | const graph = await this.loadGraph();
322 | // Filter entities
323 | const filteredEntities = graph.entities.filter(e => names.includes(e.name));
324 | // Create a Set of filtered entity names for quick lookup
325 | const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
326 | // Filter relations to only include those between filtered entities
327 | const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to));
328 | const filteredGraph = {
329 | entities: filteredEntities,
330 | relations: filteredRelations,
331 | };
332 | return filteredGraph;
333 | }
334 | // Software Development specific functions
335 | // Get project overview including components, features, issues, etc.
336 | async getProjectStatus(projectName) {
337 | const graph = await this.loadGraph();
338 | // Find the project entity
339 | const project = graph.entities.find(e => e.name === projectName && e.entityType === 'project');
340 | if (!project) {
341 | throw new Error(`Project '${projectName}' not found`);
342 | }
343 | // Find components that are part of this project
344 | const components = [];
345 | // Find features, issues, tasks, milestones related to this project
346 | const features = [];
347 | const issues = [];
348 | const tasks = [];
349 | const milestones = [];
350 | // Find entities directly related to the project
351 | for (const relation of graph.relations) {
352 | if (relation.from === projectName || relation.to === projectName) {
353 | const relatedEntity = graph.entities.find(e => (relation.from === projectName && e.name === relation.to) ||
354 | (relation.to === projectName && e.name === relation.from));
355 | if (relatedEntity) {
356 | if (relatedEntity.entityType === 'component')
357 | components.push(relatedEntity);
358 | if (relatedEntity.entityType === 'feature')
359 | features.push(relatedEntity);
360 | if (relatedEntity.entityType === 'issue')
361 | issues.push(relatedEntity);
362 | if (relatedEntity.entityType === 'task')
363 | tasks.push(relatedEntity);
364 | if (relatedEntity.entityType === 'milestone')
365 | milestones.push(relatedEntity);
366 | }
367 | }
368 | }
369 | // Find entities related to components of the project
370 | for (const component of components) {
371 | for (const relation of graph.relations) {
372 | if (relation.from === component.name || relation.to === component.name) {
373 | const relatedEntity = graph.entities.find(e => (relation.from === component.name && e.name === relation.to) ||
374 | (relation.to === component.name && e.name === relation.from));
375 | if (relatedEntity) {
376 | if (relatedEntity.entityType === 'feature' && !features.some(f => f.name === relatedEntity.name)) {
377 | features.push(relatedEntity);
378 | }
379 | if (relatedEntity.entityType === 'issue' && !issues.some(i => i.name === relatedEntity.name)) {
380 | issues.push(relatedEntity);
381 | }
382 | if (relatedEntity.entityType === 'task' && !tasks.some(t => t.name === relatedEntity.name)) {
383 | tasks.push(relatedEntity);
384 | }
385 | }
386 | }
387 | }
388 | }
389 | // Get active tasks and issues
390 | const statuses = {};
391 | const priorities = {};
392 | // Load status and priority for tasks and issues
393 | for (const entity of [...tasks, ...issues, ...features, ...milestones]) {
394 | const status = await this.getEntityStatus(entity.name);
395 | if (status) {
396 | statuses[entity.name] = status;
397 | }
398 | const priority = await this.getEntityPriority(entity.name);
399 | if (priority) {
400 | priorities[entity.name] = priority;
401 | }
402 | }
403 | // Filter active tasks and issues based on status
404 | const activeTasks = tasks.filter(task => {
405 | const status = statuses[task.name];
406 | return status ? status === 'active' : true;
407 | });
408 | const activeIssues = issues.filter(issue => {
409 | const status = statuses[issue.name];
410 | return status ? status === 'active' : true;
411 | });
412 | // Find upcoming milestones
413 | const upcomingMilestones = milestones.filter(milestone => {
414 | const status = statuses[milestone.name];
415 | return status ? status === 'active' : true;
416 | });
417 | // Get decision history
418 | const decisions = graph.entities.filter(e => e.entityType === 'decision' &&
419 | graph.relations.some(r => (r.from === e.name && r.to === projectName) ||
420 | (r.to === e.name && r.from === projectName)));
421 | // Find task sequencing
422 | const taskSequencing = {};
423 | for (const task of tasks) {
424 | const precedingTasks = [];
425 | const followingTasks = [];
426 | // Find tasks that this task precedes
427 | for (const relation of graph.relations) {
428 | if (relation.from === task.name && relation.relationType === 'precedes') {
429 | followingTasks.push(relation.to);
430 | }
431 | if (relation.to === task.name && relation.relationType === 'precedes') {
432 | precedingTasks.push(relation.from);
433 | }
434 | }
435 | if (precedingTasks.length > 0 || followingTasks.length > 0) {
436 | taskSequencing[task.name] = {
437 | precedingTasks,
438 | followingTasks
439 | };
440 | }
441 | }
442 | return {
443 | project,
444 | components,
445 | activeFeatures: features.filter(f => {
446 | const status = statuses[f.name];
447 | return status ? status === 'active' : true;
448 | }),
449 | activeTasks,
450 | activeIssues,
451 | upcomingMilestones,
452 | allFeatures: features,
453 | allIssues: issues,
454 | allTasks: tasks,
455 | allMilestones: milestones,
456 | recentDecisions: decisions.slice(0, 5), // Limit to 5 most recent decisions
457 | statuses, // Include status mapping for reference
458 | priorities, // Include priority mapping for reference
459 | taskSequencing // Include task sequencing information
460 | };
461 | }
462 | // Get detailed context for a specific component
463 | async getComponentContext(componentName) {
464 | const graph = await this.loadGraph();
465 | // Find the component entity
466 | const component = graph.entities.find(e => e.name === componentName && e.entityType === 'component');
467 | if (!component) {
468 | throw new Error(`Component '${componentName}' not found`);
469 | }
470 | // Find projects this component is part of
471 | const projects = [];
472 | for (const relation of graph.relations) {
473 | if (relation.relationType === 'contains' && relation.to === componentName) {
474 | const project = graph.entities.find(e => e.name === relation.from && e.entityType === 'project');
475 | if (project) {
476 | projects.push(project);
477 | }
478 | }
479 | }
480 | // Find features implemented by this component
481 | const features = [];
482 | for (const relation of graph.relations) {
483 | if (relation.relationType === 'implements' && relation.from === componentName) {
484 | const feature = graph.entities.find(e => e.name === relation.to && e.entityType === 'feature');
485 | if (feature) {
486 | features.push(feature);
487 | }
488 | }
489 | }
490 | // Find technologies used by this component
491 | const technologies = [];
492 | for (const relation of graph.relations) {
493 | if (relation.relationType === 'uses' && relation.from === componentName) {
494 | const technology = graph.entities.find(e => e.name === relation.to && e.entityType === 'technology');
495 | if (technology) {
496 | technologies.push(technology);
497 | }
498 | }
499 | }
500 | // Find issues affecting this component
501 | const issues = [];
502 | for (const relation of graph.relations) {
503 | if (relation.relationType === 'affects' && relation.to === componentName) {
504 | const issue = graph.entities.find(e => e.name === relation.from && e.entityType === 'issue');
505 | if (issue) {
506 | issues.push(issue);
507 | }
508 | }
509 | }
510 | // Find tasks related to this component
511 | const tasks = [];
512 | for (const relation of graph.relations) {
513 | if ((relation.from === componentName || relation.to === componentName) &&
514 | graph.entities.some(e => (e.name === relation.from || e.name === relation.to) &&
515 | e.name !== componentName &&
516 | e.entityType === 'task')) {
517 | const task = graph.entities.find(e => (e.name === relation.from || e.name === relation.to) &&
518 | e.name !== componentName &&
519 | e.entityType === 'task');
520 | if (task) {
521 | tasks.push(task);
522 | }
523 | }
524 | }
525 | // Find documentation for this component
526 | const documentation = [];
527 | for (const relation of graph.relations) {
528 | if (relation.relationType === 'documented_in' && relation.from === componentName) {
529 | const doc = graph.entities.find(e => e.name === relation.to && e.entityType === 'documentation');
530 | if (doc) {
531 | documentation.push(doc);
532 | }
533 | }
534 | }
535 | // Find dependencies
536 | const dependencies = [];
537 | for (const relation of graph.relations) {
538 | if (relation.relationType === 'depends_on' && relation.from === componentName) {
539 | const dependency = graph.entities.find(e => e.name === relation.to);
540 | if (dependency) {
541 | dependencies.push(dependency);
542 | }
543 | }
544 | }
545 | // Get statuses and priorities for tasks and issues
546 | const statuses = {};
547 | const priorities = {};
548 | // Load status and priority for tasks and issues
549 | for (const entity of [...tasks, ...issues, ...features]) {
550 | const status = await this.getEntityStatus(entity.name);
551 | if (status) {
552 | statuses[entity.name] = status;
553 | }
554 | const priority = await this.getEntityPriority(entity.name);
555 | if (priority) {
556 | priorities[entity.name] = priority;
557 | }
558 | }
559 | return {
560 | component,
561 | projects,
562 | features,
563 | technologies,
564 | activeIssues: issues.filter(issue => {
565 | const status = statuses[issue.name];
566 | return status ? status === 'active' : true;
567 | }),
568 | activeTasks: tasks.filter(task => {
569 | const status = statuses[task.name];
570 | return status ? status === 'active' : true;
571 | }),
572 | documentation,
573 | dependencies,
574 | allIssues: issues,
575 | allTasks: tasks,
576 | statuses,
577 | priorities
578 | };
579 | }
580 | // Get all entities related to a specific entity
581 | async getRelatedEntities(entityName, relationTypes) {
582 | const graph = await this.loadGraph();
583 | // Find the entity
584 | const entity = graph.entities.find(e => e.name === entityName);
585 | if (!entity) {
586 | throw new Error(`Entity '${entityName}' not found`);
587 | }
588 | // Find all relations involving this entity
589 | let relevantRelations = graph.relations.filter(r => r.from === entityName || r.to === entityName);
590 | // Filter by relation types if specified
591 | if (relationTypes && relationTypes.length > 0) {
592 | relevantRelations = relevantRelations.filter(r => relationTypes.includes(r.relationType));
593 | }
594 | // Get all related entities
595 | const related = {
596 | entity,
597 | incomingRelations: [],
598 | outgoingRelations: [],
599 | };
600 | for (const relation of relevantRelations) {
601 | if (relation.from === entityName) {
602 | const target = graph.entities.find(e => e.name === relation.to);
603 | if (target) {
604 | related.outgoingRelations.push({
605 | relation,
606 | target
607 | });
608 | }
609 | }
610 | else {
611 | const source = graph.entities.find(e => e.name === relation.from);
612 | if (source) {
613 | related.incomingRelations.push({
614 | relation,
615 | source
616 | });
617 | }
618 | }
619 | }
620 | return related;
621 | }
622 | // Get the history of decisions related to a project
623 | async getDecisionHistory(projectName) {
624 | const graph = await this.loadGraph();
625 | // Find the project
626 | const project = graph.entities.find(e => e.name === projectName && e.entityType === "project");
627 | if (!project) {
628 | throw new Error(`Project '${projectName}' not found`);
629 | }
630 | // Find all decision entities related to this project
631 | const decisions = [];
632 | // Direct decision relations to the project
633 | for (const relation of graph.relations) {
634 | if (relation.relationType === "related_to" && relation.to === projectName) {
635 | const decision = graph.entities.find(e => e.name === relation.from && e.entityType === "decision");
636 | if (decision) {
637 | decisions.push(decision);
638 | }
639 | }
640 | }
641 | // Decisions related to components of the project
642 | const components = [];
643 | for (const relation of graph.relations) {
644 | if (relation.relationType === "contains" && relation.from === projectName) {
645 | const component = graph.entities.find(e => e.name === relation.to && e.entityType === "component");
646 | if (component) {
647 | components.push(component);
648 | }
649 | }
650 | }
651 | for (const component of components) {
652 | for (const relation of graph.relations) {
653 | if (relation.relationType === "related_to" && relation.to === component.name) {
654 | const decision = graph.entities.find(e => e.name === relation.from && e.entityType === "decision");
655 | if (decision && !decisions.some(d => d.name === decision.name)) {
656 | decisions.push(decision);
657 | }
658 | }
659 | }
660 | }
661 | // Sort decisions chronologically if they have date observations
662 | const decisionsWithDates = decisions.map(decision => {
663 | const dateObs = decision.observations.find(o => o.startsWith('Date:'));
664 | return {
665 | decision,
666 | date: dateObs ? new Date(dateObs.split(':')[1].trim()) : new Date(0)
667 | };
668 | });
669 | decisionsWithDates.sort((a, b) => b.date.getTime() - a.date.getTime());
670 | return {
671 | project,
672 | decisions: decisionsWithDates.map(d => d.decision),
673 | };
674 | }
675 | // Get progress toward a milestone
676 | async getMilestoneProgress(milestoneName) {
677 | const graph = await this.loadGraph();
678 | // Find the milestone
679 | const milestone = graph.entities.find(e => e.name === milestoneName && e.entityType === "milestone");
680 | if (!milestone) {
681 | throw new Error(`Milestone '${milestoneName}' not found`);
682 | }
683 | // Find all tasks related to this milestone
684 | const tasks = [];
685 | for (const relation of graph.relations) {
686 | if (relation.relationType === "related_to" && relation.to === milestoneName) {
687 | const task = graph.entities.find(e => e.name === relation.from && e.entityType === "task");
688 | if (task) {
689 | tasks.push(task);
690 | }
691 | }
692 | }
693 | // Get statuses for all tasks
694 | const statuses = {};
695 | // Load status for tasks
696 | for (const task of tasks) {
697 | const status = await this.getEntityStatus(task.name);
698 | if (status) {
699 | statuses[task.name] = status;
700 | }
701 | }
702 | // Group tasks by status
703 | const completedTasks = [];
704 | const inProgressTasks = [];
705 | const notStartedTasks = [];
706 | for (const task of tasks) {
707 | const status = statuses[task.name] || 'inactive';
708 | if (status === 'complete') {
709 | completedTasks.push(task);
710 | }
711 | else if (status === 'active') {
712 | inProgressTasks.push(task);
713 | }
714 | else {
715 | notStartedTasks.push(task);
716 | }
717 | }
718 | // Calculate progress percentage
719 | const totalTasks = tasks.length;
720 | const progressPercentage = totalTasks > 0
721 | ? Math.round((completedTasks.length / totalTasks) * 100)
722 | : 0;
723 | // Find task sequencing
724 | const taskSequencing = {};
725 | for (const task of tasks) {
726 | const precedingTasks = [];
727 | const followingTasks = [];
728 | // Find tasks that this task precedes
729 | for (const relation of graph.relations) {
730 | if (relation.from === task.name && relation.relationType === 'precedes') {
731 | followingTasks.push(relation.to);
732 | }
733 | if (relation.to === task.name && relation.relationType === 'precedes') {
734 | precedingTasks.push(relation.from);
735 | }
736 | }
737 | if (precedingTasks.length > 0 || followingTasks.length > 0) {
738 | taskSequencing[task.name] = {
739 | precedingTasks,
740 | followingTasks
741 | };
742 | }
743 | }
744 | // Determine if milestone can be considered complete
745 | const milestoneComplete = tasks.length > 0 && tasks.every(task => statuses[task.name] === 'complete');
746 | return {
747 | milestone,
748 | progress: {
749 | totalTasks,
750 | completedTasks: completedTasks.length,
751 | inProgressTasks: inProgressTasks.length,
752 | notStartedTasks: notStartedTasks.length,
753 | percentage: progressPercentage,
754 | complete: milestoneComplete
755 | },
756 | tasks: {
757 | completed: completedTasks,
758 | inProgress: inProgressTasks,
759 | notStarted: notStartedTasks
760 | },
761 | taskSequencing,
762 | statuses
763 | };
764 | }
765 | }
766 | // Main function to set up the MCP server
767 | async function main() {
768 | try {
769 | const knowledgeGraphManager = new KnowledgeGraphManager();
770 | // Initialize status and priority entities
771 | await knowledgeGraphManager.initializeStatusAndPriority();
772 | // Initialize session states from persistent storage
773 | const sessionStates = await loadSessionStates();
774 | // Create the MCP server with a name and version
775 | const server = new McpServer({
776 | name: "Context Manager",
777 | version: "1.0.0"
778 | });
779 | // Define a resource that exposes the entire graph
780 | server.resource("graph", "graph://developer", async (uri) => ({
781 | contents: [{
782 | uri: uri.href,
783 | text: JSON.stringify(await knowledgeGraphManager.readGraph(), null, 2)
784 | }]
785 | }));
786 | // Define tools using zod for parameter validation
787 | // CRUD operations - these are now consolidated into buildcontext, deletecontext, and advancedcontext tools
788 | /**
789 | * Create new entities, relations, and observations.
790 | */
791 | server.tool("buildcontext", toolDescriptions["buildcontext"], {
792 | type: z.enum(["entities", "relations", "observations"]).describe("Type of creation operation: 'entities', 'relations', or 'observations'"),
793 | data: z.array(z.any()).describe("Data for the creation operation, structure varies by type but must be an array")
794 | }, async ({ type, data }) => {
795 | try {
796 | let result;
797 | switch (type) {
798 | case "entities":
799 | // Ensure entities match the Entity interface
800 | const typedEntities = data.map((e) => ({
801 | name: e.name,
802 | entityType: e.entityType,
803 | observations: e.observations
804 | }));
805 | result = await knowledgeGraphManager.createEntities(typedEntities);
806 | return {
807 | content: [{
808 | type: "text",
809 | text: JSON.stringify({ success: true, created: result }, null, 2)
810 | }]
811 | };
812 | case "relations":
813 | // Ensure relations match the Relation interface
814 | const typedRelations = data.map((r) => ({
815 | from: r.from,
816 | to: r.to,
817 | relationType: r.relationType
818 | }));
819 | result = await knowledgeGraphManager.createRelations(typedRelations);
820 | return {
821 | content: [{
822 | type: "text",
823 | text: JSON.stringify({ success: true, created: result }, null, 2)
824 | }]
825 | };
826 | case "observations":
827 | // Ensure observations match the required interface
828 | const typedObservations = data.map((o) => ({
829 | entityName: o.entityName,
830 | contents: o.contents
831 | }));
832 | result = await knowledgeGraphManager.addObservations(typedObservations);
833 | return {
834 | content: [{
835 | type: "text",
836 | text: JSON.stringify({ success: true, added: result }, null, 2)
837 | }]
838 | };
839 | default:
840 | throw new Error(`Invalid type: ${type}. Must be 'entities', 'relations', or 'observations'.`);
841 | }
842 | }
843 | catch (error) {
844 | return {
845 | content: [{
846 | type: "text",
847 | text: JSON.stringify({
848 | success: false,
849 | error: error instanceof Error ? error.message : String(error)
850 | }, null, 2)
851 | }]
852 | };
853 | }
854 | });
855 | /**
856 | * Delete entities, relations, and observations.
857 | */
858 | server.tool("deletecontext", toolDescriptions["deletecontext"], {
859 | type: z.enum(["entities", "relations", "observations"]).describe("Type of deletion operation: 'entities', 'relations', or 'observations'"),
860 | data: z.array(z.any()).describe("Data for the deletion operation, structure varies by type but must be an array")
861 | }, async ({ type, data }) => {
862 | try {
863 | switch (type) {
864 | case "entities":
865 | await knowledgeGraphManager.deleteEntities(data);
866 | return {
867 | content: [{
868 | type: "text",
869 | text: JSON.stringify({ success: true, message: `Deleted ${data.length} entities` }, null, 2)
870 | }]
871 | };
872 | case "relations":
873 | // Ensure relations match the Relation interface
874 | const typedRelations = data.map((r) => ({
875 | from: r.from,
876 | to: r.to,
877 | relationType: r.relationType
878 | }));
879 | await knowledgeGraphManager.deleteRelations(typedRelations);
880 | return {
881 | content: [{
882 | type: "text",
883 | text: JSON.stringify({ success: true, message: `Deleted ${data.length} relations` }, null, 2)
884 | }]
885 | };
886 | case "observations":
887 | // Ensure deletions match the required interface
888 | const typedDeletions = data.map((d) => ({
889 | entityName: d.entityName,
890 | observations: d.observations
891 | }));
892 | await knowledgeGraphManager.deleteObservations(typedDeletions);
893 | return {
894 | content: [{
895 | type: "text",
896 | text: JSON.stringify({ success: true, message: `Deleted observations from ${data.length} entities` }, null, 2)
897 | }]
898 | };
899 | default:
900 | throw new Error(`Invalid type: ${type}. Must be 'entities', 'relations', or 'observations'.`);
901 | }
902 | }
903 | catch (error) {
904 | return {
905 | content: [{
906 | type: "text",
907 | text: JSON.stringify({
908 | success: false,
909 | error: error instanceof Error ? error.message : String(error)
910 | }, null, 2)
911 | }]
912 | };
913 | }
914 | });
915 | /**
916 | * Get information about the graph, search for nodes, open nodes, get related entities, get decision history, and get milestone progress.
917 | */
918 | server.tool("advancedcontext", toolDescriptions["advancedcontext"], {
919 | type: z.enum(["graph", "search", "nodes", "related", "decisions", "milestone"]).describe("Type of get operation: 'graph', 'search', 'nodes', 'related', 'decisions', or 'milestone'"),
920 | params: z.record(z.string(), z.any()).describe("Parameters for the operation, structure varies by type")
921 | }, async ({ type, params }) => {
922 | try {
923 | let result;
924 | switch (type) {
925 | case "graph":
926 | result = await knowledgeGraphManager.readGraph();
927 | return {
928 | content: [{
929 | type: "text",
930 | text: JSON.stringify({ success: true, graph: result }, null, 2)
931 | }]
932 | };
933 | case "search":
934 | result = await knowledgeGraphManager.searchNodes(params.query);
935 | return {
936 | content: [{
937 | type: "text",
938 | text: JSON.stringify({ success: true, results: result }, null, 2)
939 | }]
940 | };
941 | case "nodes":
942 | result = await knowledgeGraphManager.openNodes(params.names);
943 | return {
944 | content: [{
945 | type: "text",
946 | text: JSON.stringify({ success: true, nodes: result }, null, 2)
947 | }]
948 | };
949 | case "related":
950 | result = await knowledgeGraphManager.getRelatedEntities(params.entityName, params.relationTypes);
951 | return {
952 | content: [{
953 | type: "text",
954 | text: JSON.stringify({ success: true, entities: result }, null, 2)
955 | }]
956 | };
957 | case "decisions":
958 | result = await knowledgeGraphManager.getDecisionHistory(params.projectName);
959 | return {
960 | content: [{
961 | type: "text",
962 | text: JSON.stringify({ success: true, decisions: result }, null, 2)
963 | }]
964 | };
965 | case "milestone":
966 | result = await knowledgeGraphManager.getMilestoneProgress(params.milestoneName);
967 | return {
968 | content: [{
969 | type: "text",
970 | text: JSON.stringify({ success: true, progress: result }, null, 2)
971 | }]
972 | };
973 | default:
974 | throw new Error(`Invalid type: ${type}. Must be 'graph', 'search', 'nodes', 'related', 'decisions', or 'milestone'.`);
975 | }
976 | }
977 | catch (error) {
978 | return {
979 | content: [{
980 | type: "text",
981 | text: JSON.stringify({
982 | success: false,
983 | error: error instanceof Error ? error.message : String(error)
984 | }, null, 2)
985 | }]
986 | };
987 | }
988 | });
989 | /**
990 | * Start a new development session. Returns session ID, recent development sessions, active projects, high-priority tasks, and upcoming milestones.
991 | * The output allows the user to easily choose what to focus on and which specific context to load.
992 | */
993 | server.tool("startsession", toolDescriptions["startsession"], {}, async () => {
994 | try {
995 | // Generate a unique session ID
996 | const sessionId = generateSessionId();
997 | // Get recent sessions from persistent storage
998 | const sessionStates = await loadSessionStates();
999 | // Initialize the session state
1000 | sessionStates.set(sessionId, []);
1001 | await saveSessionStates(sessionStates);
1002 | // Convert sessions map to array, sort by date, and take most recent ones
1003 | const recentSessions = Array.from(sessionStates.entries())
1004 | .map(([id, stages]) => {
1005 | // Extract summary data from the first stage (if it exists)
1006 | const summaryStage = stages.find(s => s.stage === "summary");
1007 | return {
1008 | id,
1009 | project: summaryStage?.stageData?.project || "Unknown project",
1010 | focus: summaryStage?.stageData?.focus || "Unknown focus",
1011 | summary: summaryStage?.stageData?.summary || "No summary available"
1012 | };
1013 | })
1014 | .slice(0, 3); // Default to showing 3 recent sessions
1015 | // Get active development projects
1016 | const graph = await knowledgeGraphManager.readGraph();
1017 | const activeProjects = [];
1018 | // Find projects with active status
1019 | for (const entity of graph.entities) {
1020 | if (entity.entityType === 'project') {
1021 | const status = await knowledgeGraphManager.getEntityStatus(entity.name);
1022 | if (status === 'active') {
1023 | activeProjects.push(entity);
1024 | }
1025 | }
1026 | }
1027 | // Get high-priority development tasks
1028 | const highPriorityTasks = [];
1029 | // Find tasks with high priority and active status
1030 | for (const entity of graph.entities) {
1031 | if (entity.entityType === 'task') {
1032 | const status = await knowledgeGraphManager.getEntityStatus(entity.name);
1033 | const priority = await knowledgeGraphManager.getEntityPriority(entity.name);
1034 | if (status === 'active' && priority === 'high') {
1035 | highPriorityTasks.push(entity);
1036 | }
1037 | }
1038 | }
1039 | // Get upcoming milestones
1040 | const upcomingMilestones = [];
1041 | // Find milestones with active status
1042 | for (const entity of graph.entities) {
1043 | if (entity.entityType === 'milestone') {
1044 | const status = await knowledgeGraphManager.getEntityStatus(entity.name);
1045 | if (status === 'active') {
1046 | upcomingMilestones.push(entity);
1047 | }
1048 | }
1049 | }
1050 | let sessionsText = "No recent sessions found.";
1051 | if (recentSessions.length > 0) {
1052 | sessionsText = recentSessions.map(session => `- ${session.project} - ${session.focus} - ${session.summary.substring(0, 100)}${session.summary.length > 100 ? '...' : ''}`).join('\n');
1053 | }
1054 | let projectsText = "No active projects found.";
1055 | if (activeProjects.length > 0) {
1056 | projectsText = activeProjects.map(project => {
1057 | const obsPreview = project.observations.length > 0 ?
1058 | `: ${project.observations[0].substring(0, 60)}${project.observations[0].length > 60 ? '...' : ''}` : '';
1059 | return `- ${project.name}${obsPreview}`;
1060 | }).join('\n');
1061 | }
1062 | let tasksText = "No high-priority tasks found.";
1063 | if (highPriorityTasks.length > 0) {
1064 | tasksText = highPriorityTasks.map(task => {
1065 | const obsPreview = task.observations.length > 0 ?
1066 | `: ${task.observations[0].substring(0, 60)}${task.observations[0].length > 60 ? '...' : ''}` : '';
1067 | return `- ${task.name}${obsPreview}`;
1068 | }).join('\n');
1069 | }
1070 | let milestonesText = "No upcoming milestones found.";
1071 | if (upcomingMilestones.length > 0) {
1072 | milestonesText = upcomingMilestones.map(milestone => {
1073 | const obsPreview = milestone.observations.length > 0 ?
1074 | `: ${milestone.observations[0].substring(0, 60)}${milestone.observations[0].length > 60 ? '...' : ''}` : '';
1075 | return `- ${milestone.name}${obsPreview}`;
1076 | }).join('\n');
1077 | }
1078 | return {
1079 | content: [{
1080 | type: "text",
1081 | text: `# Ask user to choose what to focus on in this session. Present the following options:
1082 |
1083 | ## Recent Development Sessions
1084 | ${sessionsText}
1085 |
1086 | ## Active Projects
1087 | ${projectsText}
1088 |
1089 | ## High-Priority Tasks
1090 | ${tasksText}
1091 |
1092 | ## Upcoming Milestones
1093 | ${milestonesText}
1094 |
1095 | To load specific context based on the user's choice, use the \`loadcontext\` tool with the entity name and developer session ID - ${sessionId}.`
1096 | }]
1097 | };
1098 | }
1099 | catch (error) {
1100 | return {
1101 | content: [{
1102 | type: "text",
1103 | text: JSON.stringify({
1104 | success: false,
1105 | error: error instanceof Error ? error.message : String(error)
1106 | }, null, 2)
1107 | }]
1108 | };
1109 | }
1110 | });
1111 | /**
1112 | * Load the context for a specific entity.
1113 | * Valid entity types are: project, component, task, issue, milestone, decision, feature, technology, documentation, dependency.
1114 | */
1115 | server.tool("loadcontext", toolDescriptions["loadcontext"], {
1116 | entityName: z.string(),
1117 | entityType: z.string().optional(),
1118 | sessionId: z.string().optional()
1119 | }, async ({ entityName, entityType = "project", sessionId }) => {
1120 | try {
1121 | // Validate session if ID is provided
1122 | if (sessionId) {
1123 | const sessionStates = await loadSessionStates();
1124 | if (!sessionStates.has(sessionId)) {
1125 | console.warn(`Warning: Session ${sessionId} not found, but proceeding with context load`);
1126 | // Initialize it anyway for more robustness
1127 | sessionStates.set(sessionId, []);
1128 | await saveSessionStates(sessionStates);
1129 | }
1130 | // Track that this entity was loaded in this session
1131 | const sessionState = sessionStates.get(sessionId) || [];
1132 | const loadEvent = {
1133 | type: 'context_loaded',
1134 | timestamp: new Date().toISOString(),
1135 | entityName,
1136 | entityType
1137 | };
1138 | sessionState.push(loadEvent);
1139 | sessionStates.set(sessionId, sessionState);
1140 | await saveSessionStates(sessionStates);
1141 | }
1142 | // Get entity
1143 | const entityGraph = await knowledgeGraphManager.searchNodes(entityName);
1144 | if (entityGraph.entities.length === 0) {
1145 | throw new Error(`Entity ${entityName} not found`);
1146 | }
1147 | // Find the exact entity by name (case-sensitive match)
1148 | const entity = entityGraph.entities.find(e => e.name === entityName);
1149 | if (!entity) {
1150 | throw new Error(`Entity ${entityName} not found`);
1151 | }
1152 | // Get status and priority
1153 | const status = await knowledgeGraphManager.getEntityStatus(entityName) || "unknown";
1154 | const priority = await knowledgeGraphManager.getEntityPriority(entityName);
1155 | // Format observations for display (show all observations)
1156 | const observationsList = entity.observations.length > 0
1157 | ? entity.observations.map(obs => `- ${obs}`).join("\n")
1158 | : "No observations";
1159 | // Different context loading based on entity type
1160 | let contextMessage = "";
1161 | if (entityType === "project") {
1162 | // Get project status
1163 | const projectStatus = await knowledgeGraphManager.getProjectStatus(entityName);
1164 | // Format project context
1165 | const componentsText = projectStatus.components?.map((component) => {
1166 | return `- **${component.name}**${component.observations.length > 0 ? `: ${component.observations[0]}` : ''}`;
1167 | }).join("\n") || "No components found";
1168 | const featuresText = projectStatus.activeFeatures?.map((feature) => {
1169 | const featureStatus = projectStatus.statuses[feature.name] || "unknown";
1170 | return `- **${feature.name}** (${featureStatus})${feature.observations.length > 0 ? `: ${feature.observations.join(', ')}` : ''}`;
1171 | }).join("\n") || "No active features found";
1172 | const tasksText = projectStatus.activeTasks?.map((task) => {
1173 | const taskStatus = projectStatus.statuses[task.name] || "unknown";
1174 | const taskPriority = projectStatus.priorities[task.name] || "normal";
1175 | return `- **${task.name}** (${taskStatus}, ${taskPriority} priority)${task.observations.length > 0 ? `: ${task.observations.join(', ')}` : ''}`;
1176 | }).join("\n") || "No active tasks found";
1177 | const issuesText = projectStatus.activeIssues?.map((issue) => {
1178 | const issueStatus = projectStatus.statuses[issue.name] || "unknown";
1179 | return `- **${issue.name}** (${issueStatus})${issue.observations.length > 0 ? `: ${issue.observations.join(', ')}` : ''}`;
1180 | }).join("\n") || "No active issues found";
1181 | const milestonesText = projectStatus.upcomingMilestones?.map((milestone) => {
1182 | const milestoneStatus = projectStatus.statuses[milestone.name] || "unknown";
1183 | return `- **${milestone.name}** (${milestoneStatus})${milestone.observations.length > 0 ? `: ${milestone.observations.join(', ')}` : ''}`;
1184 | }).join("\n") || "No upcoming milestones found";
1185 | const decisionsText = projectStatus.recentDecisions?.map((decision) => {
1186 | return `- **${decision.name}**${decision.observations.length > 0 ? `: ${decision.observations.join(', ')}` : ''}`;
1187 | }).join("\n") || "No recent decisions";
1188 | // Task sequencing information
1189 | const sequencingText = Object.keys(projectStatus.taskSequencing || {}).length > 0
1190 | ? Object.entries(projectStatus.taskSequencing).map(([taskName, sequence]) => {
1191 | return `- **${taskName}**:\n - Precedes: ${sequence.followingTasks.length > 0 ? sequence.followingTasks.join(', ') : 'None'}\n - Follows: ${sequence.precedingTasks.length > 0 ? sequence.precedingTasks.join(', ') : 'None'}`;
1192 | }).join("\n")
1193 | : "No task sequencing information available";
1194 | contextMessage = `# Software Development Project Context: ${entityName}
1195 |
1196 | ## Project Overview
1197 | - **Status**: ${status}
1198 | - **Priority**: ${priority || "N/A"}
1199 |
1200 | ## Observations
1201 | ${observationsList}
1202 |
1203 | ## Components
1204 | ${componentsText}
1205 |
1206 | ## Active Features
1207 | ${featuresText}
1208 |
1209 | ## Active Tasks
1210 | ${tasksText}
1211 |
1212 | ## Active Issues
1213 | ${issuesText}
1214 |
1215 | ## Upcoming Milestones
1216 | ${milestonesText}
1217 |
1218 | ## Recent Decisions
1219 | ${decisionsText}
1220 |
1221 | ## Task Sequencing
1222 | ${sequencingText}`;
1223 | }
1224 | else if (entityType === "component") {
1225 | // Get component context
1226 | const componentContext = await knowledgeGraphManager.getComponentContext(entityName);
1227 | const projectsText = componentContext.projects?.map((project) => {
1228 | return `- **${project.name}**${project.observations.length > 0 ? `: ${project.observations.join(', ')}` : ''}`;
1229 | }).join("\n") || "No parent projects found";
1230 | const featuresText = componentContext.features?.map((feature) => {
1231 | const featureStatus = componentContext.statuses[feature.name] || "unknown";
1232 | return `- **${feature.name}** (${featureStatus})${feature.observations.length > 0 ? `: ${feature.observations.join(', ')}` : ''}`;
1233 | }).join("\n") || "No implemented features found";
1234 | const technologiesText = componentContext.technologies?.map((tech) => {
1235 | return `- **${tech.name}**${tech.observations.length > 0 ? `: ${tech.observations.join(', ')}` : ''}`;
1236 | }).join("\n") || "No technologies specified";
1237 | const issuesText = componentContext.activeIssues?.map((issue) => {
1238 | const issueStatus = componentContext.statuses[issue.name] || "unknown";
1239 | return `- **${issue.name}** (${issueStatus})${issue.observations.length > 0 ? `: ${issue.observations.join(', ')}` : ''}`;
1240 | }).join("\n") || "No active issues found";
1241 | const dependenciesText = componentContext.dependencies?.map((dep) => {
1242 | return `- **${dep.name}** (${dep.entityType})${dep.observations.length > 0 ? `: ${dep.observations.join(', ')}` : ''}`;
1243 | }).join("\n") || "No dependencies found";
1244 | const documentationText = componentContext.documentation?.map((doc) => {
1245 | return `- **${doc.name}**${doc.observations.length > 0 ? `: ${doc.observations.join(', ')}` : ''}`;
1246 | }).join("\n") || "No documentation found";
1247 | contextMessage = `# Component Context: ${entityName}
1248 |
1249 | ## Overview
1250 | - **Status**: ${status}
1251 | - **Priority**: ${priority || "N/A"}
1252 |
1253 | ## Observations
1254 | ${observationsList}
1255 |
1256 | ## Part of Projects
1257 | ${projectsText}
1258 |
1259 | ## Technologies
1260 | ${technologiesText}
1261 |
1262 | ## Implemented Features
1263 | ${featuresText}
1264 |
1265 | ## Dependencies
1266 | ${dependenciesText}
1267 |
1268 | ## Active Issues
1269 | ${issuesText}
1270 |
1271 | ## Documentation
1272 | ${documentationText}`;
1273 | }
1274 | else if (entityType === "feature") {
1275 | // Get related entities
1276 | const relatedEntities = await knowledgeGraphManager.getRelatedEntities(entityName);
1277 | // Find implementing components
1278 | const implementingComponents = relatedEntities.incomingRelations
1279 | .filter((rel) => rel.relation.relationType === "implements")
1280 | .map((rel) => rel.source);
1281 | const componentsText = implementingComponents.map((component) => {
1282 | return `- **${component.name}**${component.observations.length > 0 ? `: ${component.observations.join(', ')}` : ''}`;
1283 | }).join("\n") || "No implementing components found";
1284 | // Find related tasks
1285 | const relatedTasks = [...relatedEntities.incomingRelations, ...relatedEntities.outgoingRelations]
1286 | .filter((rel) => rel.relation.relationType === "related_to" &&
1287 | (rel.source?.entityType === "task" || rel.target?.entityType === "task"))
1288 | .map((rel) => rel.source?.entityType === "task" ? rel.source : rel.target)
1289 | .filter((entity) => entity !== undefined);
1290 | // Get status for each task
1291 | const taskStatuses = {};
1292 | for (const task of relatedTasks) {
1293 | const taskStatus = await knowledgeGraphManager.getEntityStatus(task.name);
1294 | if (taskStatus) {
1295 | taskStatuses[task.name] = taskStatus;
1296 | }
1297 | }
1298 | const tasksText = relatedTasks.map((task) => {
1299 | const taskStatus = taskStatuses[task.name] || "unknown";
1300 | return `- **${task.name}** (${taskStatus})${task.observations.length > 0 ? `: ${task.observations.join(', ')}` : ''}`;
1301 | }).join("\n") || "No related tasks found";
1302 | // Find requirements
1303 | const requirements = relatedEntities.incomingRelations
1304 | .filter((rel) => rel.relation.relationType === "required_by")
1305 | .map((rel) => rel.source);
1306 | const requirementsText = requirements.map((req) => {
1307 | return `- **${req.name}**${req.observations.length > 0 ? `: ${req.observations.join(', ')}` : ''}`;
1308 | }).join("\n") || "No requirements specified";
1309 | contextMessage = `# Feature Context: ${entityName}
1310 |
1311 | ## Overview
1312 | - **Status**: ${status}
1313 | - **Priority**: ${priority || "normal"}
1314 |
1315 | ## Observations
1316 | ${observationsList}
1317 |
1318 | ## Requirements
1319 | ${requirementsText}
1320 |
1321 | ## Implementing Components
1322 | ${componentsText}
1323 |
1324 | ## Related Tasks
1325 | ${tasksText}`;
1326 | }
1327 | else if (entityType === "task") {
1328 | // Get related entities
1329 | const relatedEntities = await knowledgeGraphManager.getRelatedEntities(entityName);
1330 | // Find related issues
1331 | const relatedIssues = relatedEntities.outgoingRelations
1332 | .filter((rel) => rel.relation.relationType === "resolves")
1333 | .map((rel) => rel.target);
1334 | // Get status for each issue
1335 | const issueStatuses = {};
1336 | for (const issue of relatedIssues) {
1337 | const issueStatus = await knowledgeGraphManager.getEntityStatus(issue.name);
1338 | if (issueStatus) {
1339 | issueStatuses[issue.name] = issueStatus;
1340 | }
1341 | }
1342 | const issuesText = relatedIssues.map((issue) => {
1343 | const issueStatus = issueStatuses[issue.name] || "unknown";
1344 | return `- **${issue.name}** (${issueStatus})${issue.observations.length > 0 ? `: ${issue.observations.join(', ')}` : ''}`;
1345 | }).join("\n") || "No related issues found";
1346 | // Find parent project
1347 | const parentProjects = relatedEntities.incomingRelations
1348 | .filter((rel) => rel.relation.relationType === "contains" && rel.source.entityType === "project")
1349 | .map((rel) => rel.source);
1350 | const projectName = parentProjects.length > 0 ? parentProjects[0].name : "Unknown project";
1351 | // Find blocking tasks or issues
1352 | const blockingItems = relatedEntities.outgoingRelations
1353 | .filter((rel) => rel.relation.relationType === "blocked_by")
1354 | .map((rel) => rel.target);
1355 | // Get status for each blocking item
1356 | const blockingStatuses = {};
1357 | for (const item of blockingItems) {
1358 | const itemStatus = await knowledgeGraphManager.getEntityStatus(item.name);
1359 | if (itemStatus) {
1360 | blockingStatuses[item.name] = itemStatus;
1361 | }
1362 | }
1363 | const blockingText = blockingItems.map((item) => {
1364 | const itemStatus = blockingStatuses[item.name] || "unknown";
1365 | return `- **${item.name}** (${item.entityType}, ${itemStatus})${item.observations.length > 0 ? `: ${item.observations.join(', ')}` : ''}`;
1366 | }).join("\n") || "No blocking items";
1367 | // Find task sequencing
1368 | const precedingTasks = [];
1369 | const followingTasks = [];
1370 | // Get the graph to find sequencing relations
1371 | const graph = await knowledgeGraphManager.readGraph();
1372 | for (const relation of graph.relations) {
1373 | if (relation.from === entityName && relation.relationType === 'precedes') {
1374 | followingTasks.push(relation.to);
1375 | }
1376 | if (relation.to === entityName && relation.relationType === 'precedes') {
1377 | precedingTasks.push(relation.from);
1378 | }
1379 | }
1380 | const sequencingText = `### Preceding Tasks\n${precedingTasks.length > 0 ? precedingTasks.map(t => `- ${t}`).join('\n') : 'None'}\n\n### Following Tasks\n${followingTasks.length > 0 ? followingTasks.map(t => `- ${t}`).join('\n') : 'None'}`;
1381 | contextMessage = `# Task Context: ${entityName}
1382 |
1383 | ## Overview
1384 | - **Project**: ${projectName}
1385 | - **Status**: ${status}
1386 | - **Priority**: ${priority || "normal"}
1387 |
1388 | ## Observations
1389 | ${observationsList}
1390 |
1391 | ## Related Issues
1392 | ${issuesText}
1393 |
1394 | ## Blocked By
1395 | ${blockingText}
1396 |
1397 | ## Task Sequencing
1398 | ${sequencingText}`;
1399 | }
1400 | else if (entityType === "milestone") {
1401 | // Get milestone progress
1402 | const milestoneProgress = await knowledgeGraphManager.getMilestoneProgress(entityName);
1403 | contextMessage = `# Milestone Context: ${entityName}
1404 |
1405 | ## Overview
1406 | - **Status**: ${status}
1407 | - **Progress**: ${milestoneProgress.progress?.percentage || 0}% complete
1408 | - **Complete**: ${milestoneProgress.progress?.complete ? "Yes" : "No"}
1409 |
1410 | ## Observations
1411 | ${observationsList}
1412 |
1413 | ## Tasks
1414 | ### Completed (${milestoneProgress.tasks?.completed?.length || 0})
1415 | ${milestoneProgress.tasks?.completed?.map((task) => {
1416 | return `- **${task.name}**${task.observations.length > 0 ? `: ${task.observations.join(', ')}` : ''}`;
1417 | }).join("\n") || "No completed tasks"}
1418 |
1419 | ### In Progress (${milestoneProgress.tasks?.inProgress?.length || 0})
1420 | ${milestoneProgress.tasks?.inProgress?.map((task) => {
1421 | return `- **${task.name}**${task.observations.length > 0 ? `: ${task.observations.join(', ')}` : ''}`;
1422 | }).join("\n") || "No in-progress tasks"}
1423 |
1424 | ### Not Started (${milestoneProgress.tasks?.notStarted?.length || 0})
1425 | ${milestoneProgress.tasks?.notStarted?.map((task) => {
1426 | return `- **${task.name}**${task.observations.length > 0 ? `: ${task.observations.join(', ')}` : ''}`;
1427 | }).join("\n") || "No not-started tasks"}
1428 |
1429 | ## Task Sequencing
1430 | ${Object.keys(milestoneProgress.taskSequencing || {}).length > 0
1431 | ? Object.entries(milestoneProgress.taskSequencing).map(([taskName, sequence]) => {
1432 | return `- **${taskName}**:\n - Precedes: ${sequence.followingTasks.length > 0 ? sequence.followingTasks.join(', ') : 'None'}\n - Follows: ${sequence.precedingTasks.length > 0 ? sequence.precedingTasks.join(', ') : 'None'}`;
1433 | }).join("\n")
1434 | : "No task sequencing information available"}`;
1435 | }
1436 | return {
1437 | content: [{
1438 | type: "text",
1439 | text: contextMessage
1440 | }]
1441 | };
1442 | }
1443 | catch (error) {
1444 | return {
1445 | content: [{
1446 | type: "text",
1447 | text: JSON.stringify({
1448 | success: false,
1449 | error: error instanceof Error ? error.message : String(error)
1450 | }, null, 2)
1451 | }]
1452 | };
1453 | }
1454 | });
1455 | // Helper function to process each stage of endsession
1456 | async function processStage(params, previousStages) {
1457 | // Process based on the stage
1458 | switch (params.stage) {
1459 | case "summary":
1460 | // Process summary stage
1461 | return {
1462 | stage: "summary",
1463 | stageNumber: params.stageNumber,
1464 | analysis: params.analysis || "",
1465 | stageData: params.stageData || {
1466 | summary: "",
1467 | duration: "",
1468 | focus: ""
1469 | },
1470 | completed: !params.nextStageNeeded
1471 | };
1472 | case "achievements":
1473 | // Process achievements stage
1474 | return {
1475 | stage: "achievements",
1476 | stageNumber: params.stageNumber,
1477 | analysis: params.analysis || "",
1478 | stageData: params.stageData || { achievements: [] },
1479 | completed: !params.nextStageNeeded
1480 | };
1481 | case "taskUpdates":
1482 | // Process task updates stage
1483 | return {
1484 | stage: "taskUpdates",
1485 | stageNumber: params.stageNumber,
1486 | analysis: params.analysis || "",
1487 | stageData: params.stageData || { taskUpdates: [] },
1488 | completed: !params.nextStageNeeded
1489 | };
1490 | case "newTasks":
1491 | // Process new tasks stage
1492 | return {
1493 | stage: "newTasks",
1494 | stageNumber: params.stageNumber,
1495 | analysis: params.analysis || "",
1496 | stageData: params.stageData || { newTasks: [] },
1497 | completed: !params.nextStageNeeded
1498 | };
1499 | case "projectStatus":
1500 | // Process project status stage
1501 | return {
1502 | stage: "projectStatus",
1503 | stageNumber: params.stageNumber,
1504 | analysis: params.analysis || "",
1505 | stageData: params.stageData || {
1506 | projectName: "",
1507 | projectStatus: "",
1508 | projectObservation: ""
1509 | },
1510 | completed: !params.nextStageNeeded
1511 | };
1512 | case "assembly":
1513 | // Final assembly stage - compile all arguments for end session
1514 | return {
1515 | stage: "assembly",
1516 | stageNumber: params.stageNumber,
1517 | analysis: "Final assembly of endsession arguments",
1518 | stageData: assembleEndSessionArgs(previousStages),
1519 | completed: true
1520 | };
1521 | default:
1522 | throw new Error(`Unknown stage: ${params.stage}`);
1523 | }
1524 | }
1525 | // Helper function to assemble the final end session arguments
1526 | function assembleEndSessionArgs(stages) {
1527 | const summaryStage = stages.find(s => s.stage === "summary");
1528 | const achievementsStage = stages.find(s => s.stage === "achievements");
1529 | const taskUpdatesStage = stages.find(s => s.stage === "taskUpdates");
1530 | const newTasksStage = stages.find(s => s.stage === "newTasks");
1531 | const projectStatusStage = stages.find(s => s.stage === "projectStatus");
1532 | return {
1533 | summary: summaryStage?.stageData?.summary || "",
1534 | duration: summaryStage?.stageData?.duration || "unknown",
1535 | focus: summaryStage?.stageData?.focus || "",
1536 | achievements: JSON.stringify(achievementsStage?.stageData?.achievements || []),
1537 | taskUpdates: JSON.stringify(taskUpdatesStage?.stageData?.taskUpdates || []),
1538 | projectName: projectStatusStage?.stageData?.projectName || "",
1539 | projectStatus: projectStatusStage?.stageData?.projectStatus || "",
1540 | projectObservation: projectStatusStage?.stageData?.projectObservation || "",
1541 | newTasks: JSON.stringify(newTasksStage?.stageData?.newTasks || [])
1542 | };
1543 | }
1544 | /**
1545 | * End session by processing all stages and recording the final results.
1546 | * Only use this tool if the user asks for it.
1547 | *
1548 | * Usage examples:
1549 | *
1550 | * 1. Starting the end session process with the summary stage:
1551 | * {
1552 | * "sessionId": "dev_1234567890_abc123", // From startsession
1553 | * "stage": "summary",
1554 | * "stageNumber": 1,
1555 | * "totalStages": 6, // Total stages you plan to use
1556 | * "analysis": "Analyzed progress on the authentication system",
1557 | * "stageData": {
1558 | * "summary": "Completed the login functionality and fixed related bugs",
1559 | * "duration": "3 hours",
1560 | * "focus": "AuthSystem" // Project/component name
1561 | * },
1562 | * "nextStageNeeded": true, // More stages coming
1563 | * "isRevision": false
1564 | * }
1565 | *
1566 | * 2. Middle stage for achievements:
1567 | * {
1568 | * "sessionId": "dev_1234567890_abc123",
1569 | * "stage": "achievements",
1570 | * "stageNumber": 2,
1571 | * "totalStages": 6,
1572 | * "analysis": "Listed key accomplishments",
1573 | * "stageData": {
1574 | * "achievements": [
1575 | * "Implemented password reset functionality",
1576 | * "Fixed login redirect bug",
1577 | * "Added error handling for authentication failures"
1578 | * ]
1579 | * },
1580 | * "nextStageNeeded": true,
1581 | * "isRevision": false
1582 | * }
1583 | *
1584 | * 3. Final assembly stage:
1585 | * {
1586 | * "sessionId": "dev_1234567890_abc123",
1587 | * "stage": "assembly",
1588 | * "stageNumber": 6,
1589 | * "totalStages": 6,
1590 | * "nextStageNeeded": false, // This completes the session
1591 | * "isRevision": false
1592 | * }
1593 | */
1594 | server.tool("endsession", toolDescriptions["endsession"], {
1595 | sessionId: z.string().describe("The unique session identifier obtained from startsession"),
1596 | stage: z.string().describe("Current stage of analysis: 'summary', 'achievements', 'taskUpdates', 'newTasks', 'projectStatus', or 'assembly'"),
1597 | stageNumber: z.number().int().positive().describe("The sequence number of the current stage (starts at 1)"),
1598 | totalStages: z.number().int().positive().describe("Total number of stages in the workflow (typically 6 for standard workflow)"),
1599 | analysis: z.string().optional().describe("Text analysis or observations for the current stage"),
1600 | stageData: z.record(z.string(), z.any()).optional().describe(`Stage-specific data structure - format depends on the stage type:
1601 | - For 'summary' stage: { summary: "Session summary text", duration: "2 hours", focus: "ProjectName" }
1602 | - For 'achievements' stage: { achievements: ["Implemented feature X", "Fixed bug Y", "Refactored component Z"] }
1603 | - For 'taskUpdates' stage: { taskUpdates: [{ name: "Task1", status: "completed" }, { name: "Task2", status: "in_progress" }] }
1604 | - For 'newTasks' stage: { newTasks: [{ name: "NewTask1", description: "Implement feature A", priority: "high" }] }
1605 | - For 'projectStatus' stage: { projectName: "ProjectName", projectStatus: "in_progress", projectObservation: "Making good progress" }
1606 | - For 'assembly' stage: no stageData needed - automatic assembly of previous stages`),
1607 | nextStageNeeded: z.boolean().describe("Whether additional stages are needed after this one (false for final stage)"),
1608 | isRevision: z.boolean().optional().describe("Whether this is revising a previous stage"),
1609 | revisesStage: z.number().int().positive().optional().describe("If revising, which stage number is being revised")
1610 | }, async (params) => {
1611 | try {
1612 | // Load session states from persistent storage
1613 | const sessionStates = await loadSessionStates();
1614 | // Validate session ID
1615 | if (!sessionStates.has(params.sessionId)) {
1616 | return {
1617 | content: [{
1618 | type: "text",
1619 | text: JSON.stringify({
1620 | success: false,
1621 | error: `Session with ID ${params.sessionId} not found. Please start a new session with startsession.`
1622 | }, null, 2)
1623 | }]
1624 | };
1625 | }
1626 | // Get or initialize session state
1627 | let sessionState = sessionStates.get(params.sessionId) || [];
1628 | // Process the current stage
1629 | const stageResult = await processStage(params, sessionState);
1630 | // Store updated state
1631 | if (params.isRevision && params.revisesStage) {
1632 | // Find the analysis stages in the session state
1633 | const analysisStages = sessionState.filter(item => item.type === 'analysis_stage') || [];
1634 | if (params.revisesStage <= analysisStages.length) {
1635 | // Replace the revised stage
1636 | analysisStages[params.revisesStage - 1] = {
1637 | type: 'analysis_stage',
1638 | ...stageResult
1639 | };
1640 | }
1641 | else {
1642 | // Add as a new stage
1643 | analysisStages.push({
1644 | type: 'analysis_stage',
1645 | ...stageResult
1646 | });
1647 | }
1648 | // Update the session state with the modified analysis stages
1649 | sessionState = [
1650 | ...sessionState.filter(item => item.type !== 'analysis_stage'),
1651 | ...analysisStages
1652 | ];
1653 | }
1654 | else {
1655 | // Add new stage
1656 | sessionState.push({
1657 | type: 'analysis_stage',
1658 | ...stageResult
1659 | });
1660 | }
1661 | // Update in-memory and persistent storage
1662 | sessionStates.set(params.sessionId, sessionState);
1663 | await saveSessionStates(sessionStates);
1664 | // Check if this is the final assembly stage and no more stages are needed
1665 | if (params.stage === "assembly" && !params.nextStageNeeded) {
1666 | // Get the assembled arguments
1667 | const args = stageResult.stageData;
1668 | try {
1669 | // Parse arguments
1670 | const summary = args.summary;
1671 | const duration = args.duration;
1672 | const focus = args.focus;
1673 | const achievements = args.achievements ? JSON.parse(args.achievements) : [];
1674 | const taskUpdates = args.taskUpdates ? JSON.parse(args.taskUpdates) : [];
1675 | const projectUpdate = {
1676 | name: args.projectName,
1677 | status: args.projectStatus,
1678 | observation: args.projectObservation
1679 | };
1680 | const newTasks = args.newTasks ? JSON.parse(args.newTasks) : [];
1681 | // 2. Create achievement entities and link to focus project
1682 | const achievementEntities = achievements.map((achievement, i) => ({
1683 | name: `Achievement_${new Date().getTime()}_${i + 1}`,
1684 | entityType: "decision",
1685 | observations: [achievement]
1686 | }));
1687 | if (achievementEntities.length > 0) {
1688 | await knowledgeGraphManager.createEntities(achievementEntities);
1689 | // Link achievements to focus project
1690 | const achievementRelations = achievementEntities.map((achievement) => ({
1691 | from: focus,
1692 | to: achievement.name,
1693 | relationType: "contains"
1694 | }));
1695 | await knowledgeGraphManager.createRelations(achievementRelations);
1696 | }
1697 | // 3. Update task statuses
1698 | for (const task of taskUpdates) {
1699 | // First find the task entity
1700 | const taskGraph = await knowledgeGraphManager.searchNodes(`name:${task.name}`);
1701 | if (taskGraph.entities.length > 0) {
1702 | // Update the status observation
1703 | const taskEntity = taskGraph.entities[0];
1704 | // Set task status
1705 | try {
1706 | const statusValue = task.status === "completed" || task.status === "complete" ? "complete" :
1707 | task.status === "in_progress" ? "active" : "inactive";
1708 | await knowledgeGraphManager.setEntityStatus(task.name, statusValue);
1709 | }
1710 | catch (error) {
1711 | console.error(`Error updating status for task ${task.name}: ${error}`);
1712 | }
1713 | // If completed, link to this session
1714 | if (task.status === "complete" || task.status === "completed") {
1715 | await knowledgeGraphManager.createRelations([{
1716 | from: focus,
1717 | to: task.name,
1718 | relationType: "resolves"
1719 | }]);
1720 | }
1721 | }
1722 | }
1723 | // 4. Update project status
1724 | const projectGraph = await knowledgeGraphManager.searchNodes(`name:${projectUpdate.name}`);
1725 | if (projectGraph.entities.length > 0) {
1726 | const projectEntity = projectGraph.entities[0];
1727 | // Add project observation if specified
1728 | if (projectUpdate.observation) {
1729 | await knowledgeGraphManager.addObservations([{
1730 | entityName: projectUpdate.name,
1731 | contents: [projectUpdate.observation]
1732 | }]);
1733 | }
1734 | // Set project status
1735 | try {
1736 | const statusValue = projectUpdate.status === "completed" || projectUpdate.status === "complete" ? "complete" :
1737 | projectUpdate.status === "in_progress" || projectUpdate.status === "active" ? "active" : "inactive";
1738 | await knowledgeGraphManager.setEntityStatus(projectUpdate.name, statusValue);
1739 | }
1740 | catch (error) {
1741 | console.error(`Error updating status for project ${projectUpdate.name}: ${error}`);
1742 | }
1743 | }
1744 | // 5. Create new tasks
1745 | if (newTasks && newTasks.length > 0) {
1746 | const taskEntities = newTasks.map((task, i) => ({
1747 | name: task.name,
1748 | entityType: "task",
1749 | observations: [
1750 | task.description
1751 | ]
1752 | }));
1753 | await knowledgeGraphManager.createEntities(taskEntities);
1754 | // Set status, priority, and sequencing for each task
1755 | for (const task of newTasks) {
1756 | // Set task status to active by default
1757 | try {
1758 | await knowledgeGraphManager.setEntityStatus(task.name, "active");
1759 | }
1760 | catch (error) {
1761 | console.error(`Error setting status for new task ${task.name}: ${error}`);
1762 | }
1763 | // Set task priority if specified
1764 | if (task.priority) {
1765 | try {
1766 | const priorityValue = task.priority.toLowerCase() === "high" ? "high" : "low";
1767 | await knowledgeGraphManager.setEntityPriority(task.name, priorityValue);
1768 | }
1769 | catch (error) {
1770 | console.error(`Error setting priority for new task ${task.name}: ${error}`);
1771 | }
1772 | }
1773 | // Create sequencing relationships if specified
1774 | try {
1775 | // This task precedes another task
1776 | if (task.precedes) {
1777 | await knowledgeGraphManager.createRelations([{
1778 | from: task.name,
1779 | to: task.precedes,
1780 | relationType: "precedes"
1781 | }]);
1782 | }
1783 | // This task follows another task
1784 | if (task.follows) {
1785 | await knowledgeGraphManager.createRelations([{
1786 | from: task.follows,
1787 | to: task.name,
1788 | relationType: "precedes"
1789 | }]);
1790 | }
1791 | }
1792 | catch (error) {
1793 | console.error(`Error setting sequencing for task ${task.name}: ${error}`);
1794 | }
1795 | }
1796 | // Link tasks to project
1797 | const taskRelations = taskEntities.map((task) => ({
1798 | from: projectUpdate.name,
1799 | to: task.name,
1800 | relationType: "contains"
1801 | }));
1802 | await knowledgeGraphManager.createRelations(taskRelations);
1803 | }
1804 | // Record session completion in persistent storage
1805 | sessionState.push({
1806 | type: 'session_completed',
1807 | timestamp: new Date().toISOString(),
1808 | summary: summary,
1809 | project: focus
1810 | });
1811 | sessionStates.set(params.sessionId, sessionState);
1812 | await saveSessionStates(sessionStates);
1813 | // Prepare the summary message
1814 | const summaryMessage = `# Development Session Recorded
1815 |
1816 | I've recorded your development session focusing on ${focus}.
1817 |
1818 | ## Achievements Documented
1819 | ${achievements.map((a) => `- ${a}`).join('\n') || "No achievements recorded."}
1820 |
1821 | ## Task Updates
1822 | ${taskUpdates.map((t) => `- ${t.name}: ${t.status}`).join('\n') || "No task updates."}
1823 |
1824 | ## Project Status
1825 | Project ${projectUpdate.name} has been updated to: ${projectUpdate.status}
1826 |
1827 | ${newTasks && newTasks.length > 0 ? `## New Tasks Added
1828 | ${newTasks.map((t) => `- ${t.name}: ${t.description} (Priority: ${t.priority || "medium"})`).join('\n')}` : "No new tasks added."}
1829 |
1830 | ## Session Summary
1831 | ${summary}
1832 |
1833 | Would you like me to perform any additional updates to the development knowledge graph?`;
1834 | // Return the final result with the session recorded message
1835 | return {
1836 | content: [{
1837 | type: "text",
1838 | text: JSON.stringify({
1839 | success: true,
1840 | stageCompleted: params.stage,
1841 | nextStageNeeded: false,
1842 | stageResult: stageResult,
1843 | sessionRecorded: true,
1844 | summaryMessage: summaryMessage
1845 | }, null, 2)
1846 | }]
1847 | };
1848 | }
1849 | catch (error) {
1850 | return {
1851 | content: [{
1852 | type: "text",
1853 | text: JSON.stringify({
1854 | success: false,
1855 | error: `Error recording development session: ${error instanceof Error ? error.message : String(error)}`
1856 | }, null, 2)
1857 | }]
1858 | };
1859 | }
1860 | }
1861 | else {
1862 | // This is not the final stage or more stages are needed
1863 | // Return intermediate result
1864 | return {
1865 | content: [{
1866 | type: "text",
1867 | text: JSON.stringify({
1868 | success: true,
1869 | stageCompleted: params.stage,
1870 | nextStageNeeded: params.nextStageNeeded,
1871 | stageResult: stageResult,
1872 | endSessionArgs: params.stage === "assembly" ? stageResult.stageData : null
1873 | }, null, 2)
1874 | }]
1875 | };
1876 | }
1877 | }
1878 | catch (error) {
1879 | return {
1880 | content: [{
1881 | type: "text",
1882 | text: JSON.stringify({
1883 | success: false,
1884 | error: error instanceof Error ? error.message : String(error)
1885 | }, null, 2)
1886 | }]
1887 | };
1888 | }
1889 | });
1890 | // Connect the server to the transport
1891 | const transport = new StdioServerTransport();
1892 | await server.connect(transport);
1893 | }
1894 | catch (error) {
1895 | console.error("Error starting server:", error);
1896 | process.exit(1);
1897 | }
1898 | }
1899 | // Run the main function
1900 | main().catch(error => {
1901 | console.error("Unhandled error:", error);
1902 | process.exit(1);
1903 | });
1904 | // Export the KnowledgeGraphManager for testing
1905 | export { KnowledgeGraphManager };
1906 |
```