#
tokens: 32646/50000 1/128 files (page 6/13)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 6 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

--------------------------------------------------------------------------------
/student/index.ts:
--------------------------------------------------------------------------------

```typescript
   1 | #!/usr/bin/env node
   2 | // Updated imports using the modern MCP SDK API
   3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
   4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
   5 | import { z } from "zod";
   6 | 
   7 | // Node.js type declarations
   8 | import * as fs from 'fs/promises';
   9 | import * as path from 'path';
  10 | import { fileURLToPath } from 'url';
  11 | import { readFileSync, existsSync } from "fs";
  12 | 
  13 | 
  14 | 
  15 | // Define memory file path using environment variable with fallback
  16 | const parentPath = path.dirname(fileURLToPath(import.meta.url));
  17 | const defaultMemoryPath = path.join(parentPath, 'memory.json');
  18 | const defaultSessionsPath = path.join(parentPath, 'sessions.json');
  19 | 
  20 | // Properly handle absolute and relative paths for MEMORY_FILE_PATH
  21 | const MEMORY_FILE_PATH = process.env.MEMORY_FILE_PATH
  22 |   ? path.isAbsolute(process.env.MEMORY_FILE_PATH)
  23 |     ? process.env.MEMORY_FILE_PATH  // Use absolute path as is
  24 |     : path.join(process.cwd(), process.env.MEMORY_FILE_PATH)  // Relative to current working directory
  25 |   : defaultMemoryPath;  // Default fallback
  26 | 
  27 | // Properly handle absolute and relative paths for SESSIONS_FILE_PATH
  28 | const SESSIONS_FILE_PATH = process.env.SESSIONS_FILE_PATH
  29 |   ? path.isAbsolute(process.env.SESSIONS_FILE_PATH)
  30 |     ? process.env.SESSIONS_FILE_PATH  // Use absolute path as is
  31 |     : path.join(process.cwd(), process.env.SESSIONS_FILE_PATH)  // Relative to current working directory
  32 |   : defaultSessionsPath;  // Default fallback
  33 | 
  34 | // Student education specific entity types
  35 | const validEntityTypes = [
  36 |   'course',
  37 |   'assignment',
  38 |   'exam',
  39 |   'concept',
  40 |   'resource',
  41 |   'note',
  42 |   'lecture',
  43 |   'project',
  44 |   'question', 
  45 |   'term',
  46 |   'goal',
  47 |   'professor',
  48 |   'status',   // Entity status
  49 |   'priority'  // Entity priority
  50 | ] as const;
  51 | 
  52 | // Type for entity types to ensure type safety
  53 | type EntityType = typeof validEntityTypes[number];
  54 | 
  55 | // Function to validate entity type
  56 | function isValidEntityType(type: string): type is EntityType {
  57 |   return (validEntityTypes as readonly string[]).includes(type);
  58 | }
  59 | 
  60 | // Explicit validation function for TypeScript
  61 | function validateEntityType(type: string): void {
  62 |   if (!isValidEntityType(type)) {
  63 |     throw new Error(`Invalid entity type: ${type}. Valid types are: ${validEntityTypes.join(', ')}`);
  64 |   }
  65 | }
  66 | 
  67 | // Student education specific relation types
  68 | const VALID_RELATION_TYPES = [
  69 |   'enrolled_in',     // Student is taking a course
  70 |   'assigned_in',     // Assignment is part of a course
  71 |   'due_on',          // Assignment/exam has specific due date
  72 |   'covers',          // Lecture/resource covers concept
  73 |   'references',      // Note references concept
  74 |   'prerequisite_for', // Concept is foundation for another
  75 |   'taught_by',       // Course taught by professor
  76 |   'scheduled_for',   // Lecture/exam scheduled for specific time
  77 |   'contains',        // Course contains lectures/assignments
  78 |   'requires',        // Assignment requires specific concepts
  79 |   'related_to',      // Concept related to another concept
  80 |   'created_for',     // Note created for specific lecture
  81 |   'studies',         // Study session focuses on concept/exam
  82 |   'helps_with',      // Resource helps with assignment/concept
  83 |   'submitted',       // Assignment submitted on date
  84 |   'part_of',         // Entity is part of another entity
  85 |   'included_in',     // Included in a larger component
  86 |   'follows',         // Entity follows another in sequence
  87 |   'attends',         // Student attends lecture
  88 |   'graded_with',     // Assignment/exam graded with specific criteria
  89 |   'has_status',      // Entity has a specific status
  90 |   'has_priority'     // Entity has a specific priority
  91 | ];
  92 | 
  93 | // Define valid status values for education entities
  94 | const VALID_STATUS_VALUES = ['not_started', 'in_progress', 'complete'];
  95 | 
  96 | // Define valid priority values for education entities
  97 | const VALID_PRIORITY_VALUES = ['low', 'high'];
  98 | 
  99 | const __filename = fileURLToPath(import.meta.url);
 100 | const __dirname = path.dirname(__filename);
 101 | 
 102 | // Collect tool descriptions from text files
 103 | const toolDescriptions: Record<string, string> = {
 104 |   'startsession': '',
 105 |   'loadcontext': '',
 106 |   'deletecontext': '',
 107 |   'buildcontext': '',
 108 |   'advancedcontext': '',
 109 |   'endsession': '',
 110 | };
 111 | for (const tool of Object.keys(toolDescriptions)) {
 112 |   const descriptionFilePath = path.resolve(
 113 |     __dirname,
 114 |     `student_${tool}.txt`
 115 |   );
 116 |   if (existsSync(descriptionFilePath)) {
 117 |     toolDescriptions[tool] = readFileSync(descriptionFilePath, 'utf-8');
 118 |   }
 119 | }
 120 | 
 121 | // We are storing our memory using entities, relations, and observations in a graph structure
 122 | interface Entity {
 123 |   name: string;
 124 |   entityType: EntityType;
 125 |   observations: string[];
 126 |   embedding?: any; // Changed from Embedding to any to avoid import error
 127 | }
 128 | 
 129 | interface Relation {
 130 |   from: string;
 131 |   to: string;
 132 |   relationType: string;
 133 |   observations?: string[];
 134 | }
 135 | 
 136 | interface KnowledgeGraph {
 137 |   entities: Entity[];
 138 |   relations: Relation[];
 139 | }
 140 | 
 141 | // The KnowledgeGraphManager class contains all operations to interact with the knowledge graph
 142 | class KnowledgeGraphManager {
 143 |   private async loadGraph(): Promise<KnowledgeGraph> {
 144 |     try {
 145 |       const fileContent = await fs.readFile(MEMORY_FILE_PATH, 'utf-8');
 146 |       return JSON.parse(fileContent);
 147 |     } catch (error) {
 148 |       // If the file doesn't exist, return an empty graph
 149 |       return {
 150 |         entities: [],
 151 |         relations: []
 152 |       };
 153 |     }
 154 |   }
 155 | 
 156 |   private async saveGraph(graph: KnowledgeGraph): Promise<void> {
 157 |     await fs.writeFile(MEMORY_FILE_PATH, JSON.stringify(graph, null, 2), 'utf-8');
 158 |   }
 159 | 
 160 |   // Initialize status and priority entities
 161 |   async initializeStatusAndPriority(): Promise<void> {
 162 |     const graph = await this.loadGraph();
 163 |     
 164 |     // Create status entities if they don't exist
 165 |     for (const statusValue of VALID_STATUS_VALUES) {
 166 |       const statusName = `status:${statusValue}`;
 167 |       if (!graph.entities.some(e => e.name === statusName && e.entityType === 'status')) {
 168 |         graph.entities.push({
 169 |           name: statusName,
 170 |           entityType: 'status' as EntityType,
 171 |           observations: [`A ${statusValue} status value`]
 172 |         });
 173 |       }
 174 |     }
 175 |     
 176 |     // Create priority entities if they don't exist
 177 |     for (const priorityValue of VALID_PRIORITY_VALUES) {
 178 |       const priorityName = `priority:${priorityValue}`;
 179 |       if (!graph.entities.some(e => e.name === priorityName && e.entityType === 'priority')) {
 180 |         graph.entities.push({
 181 |           name: priorityName,
 182 |           entityType: 'priority' as EntityType,
 183 |           observations: [`A ${priorityValue} priority value`]
 184 |         });
 185 |       }
 186 |     }
 187 |     
 188 |     await this.saveGraph(graph);
 189 |   }
 190 | 
 191 |   // Helper method to get status of an entity
 192 |   async getEntityStatus(entityName: string): Promise<string | null> {
 193 |     const graph = await this.loadGraph();
 194 |     
 195 |     // Find status relation for this entity
 196 |     const statusRelation = graph.relations.find(r => 
 197 |       r.from === entityName && 
 198 |       r.relationType === 'has_status'
 199 |     );
 200 |     
 201 |     if (statusRelation) {
 202 |       // Extract status value from the status entity name (status:value)
 203 |       return statusRelation.to.split(':')[1];
 204 |     }
 205 |     
 206 |     return null;
 207 |   }
 208 |   
 209 |   // Helper method to get priority of an entity
 210 |   async getEntityPriority(entityName: string): Promise<string | null> {
 211 |     const graph = await this.loadGraph();
 212 |     
 213 |     // Find priority relation for this entity
 214 |     const priorityRelation = graph.relations.find(r => 
 215 |       r.from === entityName && 
 216 |       r.relationType === 'has_priority'
 217 |     );
 218 |     
 219 |     if (priorityRelation) {
 220 |       // Extract priority value from the priority entity name (priority:value)
 221 |       return priorityRelation.to.split(':')[1];
 222 |     }
 223 |     
 224 |     return null;
 225 |   }
 226 |   
 227 |   // Helper method to set status of an entity
 228 |   async setEntityStatus(entityName: string, statusValue: string): Promise<void> {
 229 |     if (!VALID_STATUS_VALUES.includes(statusValue)) {
 230 |       throw new Error(`Invalid status value: ${statusValue}. Valid values are: ${VALID_STATUS_VALUES.join(', ')}`);
 231 |     }
 232 |     
 233 |     const graph = await this.loadGraph();
 234 |     
 235 |     // Remove any existing status relations for this entity
 236 |     graph.relations = graph.relations.filter(r => 
 237 |       !(r.from === entityName && r.relationType === 'has_status')
 238 |     );
 239 |     
 240 |     // Add new status relation
 241 |     graph.relations.push({
 242 |       from: entityName,
 243 |       to: `status:${statusValue}`,
 244 |       relationType: 'has_status'
 245 |     });
 246 |     
 247 |     await this.saveGraph(graph);
 248 |   }
 249 |   
 250 |   // Helper method to set priority of an entity
 251 |   async setEntityPriority(entityName: string, priorityValue: string): Promise<void> {
 252 |     if (!VALID_PRIORITY_VALUES.includes(priorityValue)) {
 253 |       throw new Error(`Invalid priority value: ${priorityValue}. Valid values are: ${VALID_PRIORITY_VALUES.join(', ')}`);
 254 |     }
 255 |     
 256 |     const graph = await this.loadGraph();
 257 |     
 258 |     // Remove any existing priority relations for this entity
 259 |     graph.relations = graph.relations.filter(r => 
 260 |       !(r.from === entityName && r.relationType === 'has_priority')
 261 |     );
 262 |     
 263 |     // Add new priority relation
 264 |     graph.relations.push({
 265 |       from: entityName,
 266 |       to: `priority:${priorityValue}`,
 267 |       relationType: 'has_priority'
 268 |     });
 269 |     
 270 |     await this.saveGraph(graph);
 271 |   }
 272 | 
 273 |   async createEntities(entities: Entity[]): Promise<KnowledgeGraph> {
 274 |     const graph = await this.loadGraph();
 275 |     
 276 |     // Validate entity names don't already exist
 277 |     for (const entity of entities) {
 278 |       if (graph.entities.some(e => e.name === entity.name)) {
 279 |         throw new Error(`Entity with name ${entity.name} already exists`);
 280 |       }
 281 |       validateEntityType(entity.entityType);
 282 |     }
 283 |     
 284 |     // Add new entities
 285 |     graph.entities.push(...entities);
 286 |     
 287 |     // Save updated graph
 288 |     await this.saveGraph(graph);
 289 |     return graph;
 290 |   }
 291 | 
 292 |   async createRelations(relations: Relation[]): Promise<KnowledgeGraph> {
 293 |     const graph = await this.loadGraph();
 294 |     
 295 |     // Validate relations
 296 |     for (const relation of relations) {
 297 |       // Check if entities exist
 298 |       if (!graph.entities.some(e => e.name === relation.from)) {
 299 |         throw new Error(`Entity '${relation.from}' not found`);
 300 |       }
 301 |       if (!graph.entities.some(e => e.name === relation.to)) {
 302 |         throw new Error(`Entity '${relation.to}' not found`);
 303 |       }
 304 |       if (!VALID_RELATION_TYPES.includes(relation.relationType)) {
 305 |         throw new Error(`Invalid relation type: ${relation.relationType}. Valid types are: ${VALID_RELATION_TYPES.join(', ')}`);
 306 |       }
 307 |       
 308 |       // Check if relation already exists
 309 |       if (graph.relations.some(r => 
 310 |         r.from === relation.from && 
 311 |         r.to === relation.to && 
 312 |         r.relationType === relation.relationType
 313 |       )) {
 314 |         throw new Error(`Relation from '${relation.from}' to '${relation.to}' with type '${relation.relationType}' already exists`);
 315 |       }
 316 |     }
 317 |     
 318 |     // Add relations
 319 |     graph.relations.push(...relations);
 320 |     
 321 |     // Save updated graph
 322 |     await this.saveGraph(graph);
 323 |     return graph;
 324 |   }
 325 | 
 326 |   async addObservations(entityName: string, observations: string[]): Promise<KnowledgeGraph> {
 327 |     const graph = await this.loadGraph();
 328 |     
 329 |     // Find the entity
 330 |     const entity = graph.entities.find(e => e.name === entityName);
 331 |     if (!entity) {
 332 |       throw new Error(`Entity '${entityName}' not found`);
 333 |     }
 334 |     
 335 |     // Add observations
 336 |     entity.observations.push(...observations);
 337 |     
 338 |     // Save updated graph
 339 |     await this.saveGraph(graph);
 340 |     return graph;
 341 |   }
 342 | 
 343 |   async deleteEntities(entityNames: string[]): Promise<void> {
 344 |     const graph = await this.loadGraph();
 345 |     
 346 |     // Remove the entities
 347 |     graph.entities = graph.entities.filter(e => !entityNames.includes(e.name));
 348 |     
 349 |     // Remove relations that involve the deleted entities
 350 |     graph.relations = graph.relations.filter(
 351 |       r => !entityNames.includes(r.from) && !entityNames.includes(r.to)
 352 |     );
 353 |     
 354 |     await this.saveGraph(graph);
 355 |   }
 356 | 
 357 |   async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void> {
 358 |     const graph = await this.loadGraph();
 359 |     
 360 |     for (const deletion of deletions) {
 361 |       const entity = graph.entities.find(e => e.name === deletion.entityName);
 362 |       if (entity) {
 363 |         // Remove the specified observations
 364 |         entity.observations = entity.observations.filter(
 365 |           o => !deletion.observations.includes(o)
 366 |         );
 367 |       }
 368 |     }
 369 |     
 370 |     await this.saveGraph(graph);
 371 |   }
 372 | 
 373 |   async deleteRelations(relations: Relation[]): Promise<void> {
 374 |     const graph = await this.loadGraph();
 375 |     
 376 |     // Remove specified relations
 377 |     graph.relations = graph.relations.filter(r => 
 378 |       !relations.some(toDelete => 
 379 |         r.from === toDelete.from && 
 380 |         r.to === toDelete.to && 
 381 |         r.relationType === toDelete.relationType
 382 |       )
 383 |     );
 384 |     
 385 |     await this.saveGraph(graph);
 386 |   }
 387 | 
 388 |   async readGraph(): Promise<KnowledgeGraph> {
 389 |     return this.loadGraph();
 390 |   }
 391 | 
 392 |   async searchNodes(query: string): Promise<KnowledgeGraph> {
 393 |     const graph = await this.loadGraph();
 394 |     
 395 |     // Split query into search terms
 396 |     const terms = query.toLowerCase().split(/\s+/);
 397 |     
 398 |     // Find matching entities
 399 |     const matchingEntityNames = new Set<string>();
 400 |     
 401 |     for (const entity of graph.entities) {
 402 |       // Check if all terms match
 403 |       const matchesAllTerms = terms.every(term => {
 404 |         // Check entity name
 405 |         if (entity.name.toLowerCase().includes(term)) {
 406 |           return true;
 407 |         }
 408 |         
 409 |         // Check entity type
 410 |         if (entity.entityType.toLowerCase().includes(term)) {
 411 |           return true;
 412 |         }
 413 |         
 414 |         // Check observations
 415 |         for (const observation of entity.observations) {
 416 |           if (observation.toLowerCase().includes(term)) {
 417 |             return true;
 418 |           }
 419 |         }
 420 |         
 421 |         return false;
 422 |       });
 423 |       
 424 |       if (matchesAllTerms) {
 425 |         matchingEntityNames.add(entity.name);
 426 |       }
 427 |     }
 428 |     
 429 |     // Find relations between matching entities
 430 |     const matchingRelations = graph.relations.filter(r => 
 431 |       matchingEntityNames.has(r.from) && matchingEntityNames.has(r.to)
 432 |     );
 433 |     
 434 |     // Return matching entities and their relations
 435 |     return {
 436 |       entities: graph.entities.filter(e => matchingEntityNames.has(e.name)),
 437 |       relations: matchingRelations
 438 |     };
 439 |   }
 440 | 
 441 |   async openNodes(names: string[]): Promise<KnowledgeGraph> {
 442 |     const graph = await this.loadGraph();
 443 |     
 444 |     // Find the specified entities
 445 |     const entities = graph.entities.filter(e => names.includes(e.name));
 446 |     
 447 |     // Find relations between the specified entities
 448 |     const relations = graph.relations.filter(r => 
 449 |       names.includes(r.from) && names.includes(r.to)
 450 |     );
 451 |     
 452 |     return {
 453 |       entities,
 454 |       relations
 455 |     };
 456 |   }
 457 | 
 458 |   // Get summary of course including lectures, assignments, exams, textbooks
 459 |   async getCourseOverview(courseName: string): Promise<any> {
 460 |     const graph = await this.loadGraph();
 461 |     
 462 |     // Find the course
 463 |     const course = graph.entities.find(e => e.name === courseName && e.entityType === 'course');
 464 |     if (!course) {
 465 |       throw new Error(`Course '${courseName}' not found`);
 466 |     }
 467 |     
 468 |     // Find term this course belongs to
 469 |     let term: Entity | undefined;
 470 |     for (const relation of graph.relations) {
 471 |       if (relation.relationType === 'part_of' && relation.from === courseName) {
 472 |         const potentialTerm = graph.entities.find(e => e.name === relation.to && e.entityType === 'term');
 473 |         if (potentialTerm) {
 474 |           term = potentialTerm;
 475 |           break;
 476 |         }
 477 |       }
 478 |     }
 479 |     
 480 |     // Find professor who teaches this course
 481 |     let professor: Entity | undefined;
 482 |     for (const relation of graph.relations) {
 483 |       if (relation.relationType === 'taught_by' && relation.from === courseName) {
 484 |         professor = graph.entities.find(e => e.name === relation.to && e.entityType === 'professor');
 485 |         if (professor) {
 486 |           break;
 487 |         }
 488 |       }
 489 |     }
 490 |     
 491 |     // Find lectures for this course
 492 |     const lectures: Entity[] = [];
 493 |     for (const relation of graph.relations) {
 494 |       if (relation.relationType === 'part_of' && relation.to === courseName) {
 495 |         const lecture = graph.entities.find(e => e.name === relation.from && e.entityType === 'lecture');
 496 |         if (lecture) {
 497 |           lectures.push(lecture);
 498 |         }
 499 |       }
 500 |     }
 501 |     
 502 |     // Sort lectures by date if available
 503 |     lectures.sort((a, b) => {
 504 |       const aDateObs = a.observations.find(o => o.startsWith('Date:'));
 505 |       const bDateObs = b.observations.find(o => o.startsWith('Date:'));
 506 |       
 507 |       if (aDateObs && bDateObs) {
 508 |         const aDate = new Date(aDateObs.split(':')[1].trim());
 509 |         const bDate = new Date(bDateObs.split(':')[1].trim());
 510 |         return aDate.getTime() - bDate.getTime();
 511 |       }
 512 |       return 0;
 513 |     });
 514 |     
 515 |     // Find assignments for this course
 516 |     const assignments: Entity[] = [];
 517 |     for (const relation of graph.relations) {
 518 |       if (relation.relationType === 'assigned_in' && relation.to === courseName) {
 519 |         const assignment = graph.entities.find(e => e.name === relation.from && e.entityType === 'assignment');
 520 |         if (assignment) {
 521 |           assignments.push(assignment);
 522 |         }
 523 |       }
 524 |     }
 525 |     
 526 |     // Sort assignments by due date if available
 527 |     assignments.sort((a, b) => {
 528 |       const aDueDateObs = a.observations.find(o => o.startsWith('Due:'));
 529 |       const bDueDateObs = b.observations.find(o => o.startsWith('Due:'));
 530 |       
 531 |       if (aDueDateObs && bDueDateObs) {
 532 |         const aDate = new Date(aDueDateObs.split(':')[1].trim());
 533 |         const bDate = new Date(bDueDateObs.split(':')[1].trim());
 534 |         return aDate.getTime() - bDate.getTime();
 535 |       }
 536 |       return 0;
 537 |     });
 538 |     
 539 |     // Find exams for this course
 540 |     const exams: Entity[] = [];
 541 |     for (const relation of graph.relations) {
 542 |       if (relation.relationType === 'scheduled_for' && relation.from === courseName) {
 543 |         const exam = graph.entities.find(e => e.name === relation.to && e.entityType === 'exam');
 544 |         if (exam) {
 545 |           exams.push(exam);
 546 |         }
 547 |       }
 548 |     }
 549 |     
 550 |     // Sort exams by date if available
 551 |     exams.sort((a, b) => {
 552 |       const aDateObs = a.observations.find(o => o.startsWith('Date:'));
 553 |       const bDateObs = b.observations.find(o => o.startsWith('Date:'));
 554 |       
 555 |       if (aDateObs && bDateObs) {
 556 |         const aDate = new Date(aDateObs.split(':')[1].trim());
 557 |         const bDate = new Date(bDateObs.split(':')[1].trim());
 558 |         return aDate.getTime() - bDate.getTime();
 559 |       }
 560 |       return 0;
 561 |     });
 562 |     
 563 |     // Find concepts covered in this course
 564 |     const concepts: Entity[] = [];
 565 |     for (const relation of graph.relations) {
 566 |       if (relation.relationType === 'covers' && relation.from === courseName) {
 567 |         const concept = graph.entities.find(e => e.name === relation.to && e.entityType === 'concept');
 568 |         if (concept) {
 569 |           concepts.push(concept);
 570 |         }
 571 |       }
 572 |     }
 573 |     
 574 |     // Find resources for this course (textbooks, articles, etc.)
 575 |     const resources: Entity[] = [];
 576 |     for (const relation of graph.relations) {
 577 |       if (relation.relationType === 'helps_with' && relation.to === courseName) {
 578 |         const resource = graph.entities.find(e => e.name === relation.from && e.entityType === 'resource');
 579 |         if (resource) {
 580 |           resources.push(resource);
 581 |         }
 582 |       }
 583 |     }
 584 |     
 585 |     // Find notes for this course
 586 |     const notes: Entity[] = [];
 587 |     for (const relation of graph.relations) {
 588 |       if (relation.relationType === 'created_for' && relation.to === courseName) {
 589 |         const note = graph.entities.find(e => e.name === relation.from && e.entityType === 'note');
 590 |         if (note) {
 591 |           notes.push(note);
 592 |         }
 593 |       }
 594 |     }
 595 |     
 596 |     // Extract course info from observations
 597 |     const courseCode = course.observations.find(o => o.startsWith('Code:'))?.split(':')[1].trim() || 'N/A';
 598 |     const courseLocation = course.observations.find(o => o.startsWith('Location:'))?.split(':')[1].trim() || 'N/A';
 599 |     const courseSchedule = course.observations.find(o => o.startsWith('Schedule:'))?.split(':')[1].trim() || 'N/A';
 600 |     const courseStatus = course.observations.find(o => o.startsWith('Status:'))?.split(':')[1].trim() || 'N/A';
 601 |     
 602 |     return {
 603 |       course,
 604 |       term,
 605 |       professor,
 606 |       info: {
 607 |         code: courseCode,
 608 |         location: courseLocation,
 609 |         schedule: courseSchedule,
 610 |         status: courseStatus
 611 |       },
 612 |       summary: {
 613 |         lectureCount: lectures.length,
 614 |         assignmentCount: assignments.length,
 615 |         examCount: exams.length,
 616 |         conceptCount: concepts.length,
 617 |         resourceCount: resources.length,
 618 |         noteCount: notes.length
 619 |       },
 620 |       lectures,
 621 |       assignments,
 622 |       exams,
 623 |       concepts,
 624 |       resources,
 625 |       notes
 626 |     };
 627 |   }
 628 | 
 629 |   // Returns assignments and exams with approaching due dates
 630 |   async getUpcomingDeadlines(termName?: string, courseName?: string, daysAhead: number = 14): Promise<any> {
 631 |     const graph = await this.loadGraph();
 632 |     const today = new Date();
 633 |     const endDate = new Date();
 634 |     endDate.setDate(today.getDate() + daysAhead);
 635 |     
 636 |     // Filter for specific term if provided
 637 |     let relevantCourses: Entity[] = [];
 638 |     if (termName) {
 639 |       // Find the specific term
 640 |       const term = graph.entities.find(e => e.name === termName && e.entityType === 'term');
 641 |       if (!term) {
 642 |         throw new Error(`Term '${termName}' not found`);
 643 |       }
 644 |       
 645 |       // Find courses in this term
 646 |       for (const relation of graph.relations) {
 647 |         if (relation.relationType === 'part_of' && relation.from === relation.to && relation.to === termName) {
 648 |           const course = graph.entities.find(e => e.name === relation.from && e.entityType === 'course');
 649 |           if (course) {
 650 |             relevantCourses.push(course);
 651 |           }
 652 |         }
 653 |       }
 654 |     } else {
 655 |       // Get all courses if no term specified
 656 |       relevantCourses = graph.entities.filter(e => e.entityType === 'course');
 657 |     }
 658 |     
 659 |     // Filter for specific course if provided
 660 |     if (courseName) {
 661 |       relevantCourses = relevantCourses.filter(c => c.name === courseName);
 662 |       if (relevantCourses.length === 0) {
 663 |         throw new Error(`Course '${courseName}' not found`);
 664 |       }
 665 |     }
 666 |     
 667 |     // Find all assignments and exams for these courses
 668 |     const deadlines: { entity: Entity; dueDate: Date; course: Entity; daysRemaining: number }[] = [];
 669 |     
 670 |     for (const course of relevantCourses) {
 671 |       // Find assignments for this course
 672 |       for (const relation of graph.relations) {
 673 |         if (relation.relationType === 'assigned_in' && relation.to === course.name) {
 674 |           const assignment = graph.entities.find(e => e.name === relation.from && e.entityType === 'assignment');
 675 |           if (assignment) {
 676 |             // Check due date
 677 |             const dueDateObs = assignment.observations.find(o => o.startsWith('Due:'));
 678 |             if (dueDateObs) {
 679 |               const dueDateStr = dueDateObs.split(':')[1].trim();
 680 |               const dueDate = new Date(dueDateStr);
 681 |               
 682 |               // Check if it's in our date range
 683 |               if (dueDate >= today && dueDate <= endDate) {
 684 |                 const daysRemaining = Math.ceil((dueDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
 685 |                 deadlines.push({
 686 |                   entity: assignment,
 687 |                   dueDate,
 688 |                   course,
 689 |                   daysRemaining
 690 |                 });
 691 |               }
 692 |             }
 693 |           }
 694 |         }
 695 |       }
 696 |       
 697 |       // Find exams for this course
 698 |       for (const relation of graph.relations) {
 699 |         if (relation.relationType === 'scheduled_for' && relation.from === course.name) {
 700 |           const exam = graph.entities.find(e => e.name === relation.to && e.entityType === 'exam');
 701 |           if (exam) {
 702 |             // Check exam date
 703 |             const dateObs = exam.observations.find(o => o.startsWith('Date:'));
 704 |             if (dateObs) {
 705 |               const dateStr = dateObs.split(':')[1].trim();
 706 |               const examDate = new Date(dateStr);
 707 |               
 708 |               // Check if it's in our date range
 709 |               if (examDate >= today && examDate <= endDate) {
 710 |                 const daysRemaining = Math.ceil((examDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
 711 |                 deadlines.push({
 712 |                   entity: exam,
 713 |                   dueDate: examDate,
 714 |                   course,
 715 |                   daysRemaining
 716 |                 });
 717 |               }
 718 |             }
 719 |           }
 720 |         }
 721 |       }
 722 |     }
 723 |     
 724 |     // Sort by due date
 725 |     deadlines.sort((a, b) => a.dueDate.getTime() - b.dueDate.getTime());
 726 |     
 727 |     return {
 728 |       deadlines,
 729 |       startDate: today.toISOString().slice(0, 10),
 730 |       endDate: endDate.toISOString().slice(0, 10),
 731 |       courseFilter: courseName,
 732 |       termFilter: termName,
 733 |       count: deadlines.length
 734 |     };
 735 |   }
 736 | 
 737 |   // Get detailed information about assignment status, including progress, related concepts, and resources
 738 |   async getAssignmentStatus(assignmentName: string): Promise<any> {
 739 |     const graph = await this.loadGraph();
 740 |     
 741 |     // Find the assignment
 742 |     const assignment = graph.entities.find(e => e.name === assignmentName && e.entityType === 'assignment');
 743 |     if (!assignment) {
 744 |       throw new Error(`Assignment '${assignmentName}' not found`);
 745 |     }
 746 |     
 747 |     // Find the course this assignment belongs to
 748 |     let course: Entity | undefined;
 749 |     for (const relation of graph.relations) {
 750 |       if (relation.relationType === 'assigned_in' && relation.from === assignmentName) {
 751 |         course = graph.entities.find(e => e.name === relation.to && e.entityType === 'course');
 752 |         if (course) {
 753 |           break;
 754 |         }
 755 |       }
 756 |     }
 757 |     
 758 |     // Get status using the relation-based approach
 759 |     const status = await this.getEntityStatus(assignmentName) || 'not_started';
 760 |     // Get priority using the relation-based approach
 761 |     const priority = await this.getEntityPriority(assignmentName);
 762 |     
 763 |     const dueDate = assignment.observations.find(o => o.startsWith('Due:'))?.split(':')[1].trim();
 764 |     const pointsWorth = assignment.observations.find(o => o.startsWith('Points:'))?.split(':')[1].trim();
 765 |     const instructions = assignment.observations.find(o => o.startsWith('Instructions:'))?.split(':')[1].trim();
 766 |     
 767 |     // Calculate time remaining if due date exists
 768 |     let timeRemaining: number | null = null;
 769 |     let daysRemaining: number | null = null;
 770 |     let isOverdue = false;
 771 |     
 772 |     if (dueDate) {
 773 |       const dueDateTime = new Date(dueDate).getTime();
 774 |       const now = new Date().getTime();
 775 |       timeRemaining = dueDateTime - now;
 776 |       daysRemaining = Math.ceil(timeRemaining / (1000 * 60 * 60 * 24));
 777 |       isOverdue = timeRemaining < 0;
 778 |     }
 779 |     
 780 |     // Find concepts related to this assignment
 781 |     const concepts: Entity[] = [];
 782 |     for (const relation of graph.relations) {
 783 |       if (relation.relationType === 'covers' && relation.from === assignmentName) {
 784 |         const concept = graph.entities.find(e => e.name === relation.to && e.entityType === 'concept');
 785 |         if (concept) {
 786 |           concepts.push(concept);
 787 |         }
 788 |       }
 789 |     }
 790 |     
 791 |     // Find resources that might help with this assignment
 792 |     const resources: Entity[] = [];
 793 |     
 794 |     // Direct resources for the assignment
 795 |     for (const relation of graph.relations) {
 796 |       if (relation.relationType === 'helps_with' && relation.to === assignmentName) {
 797 |         const resource = graph.entities.find(e => e.name === relation.from && e.entityType === 'resource');
 798 |         if (resource) {
 799 |           resources.push(resource);
 800 |         }
 801 |       }
 802 |     }
 803 |     
 804 |     // Resources for concepts related to the assignment
 805 |     for (const concept of concepts) {
 806 |       for (const relation of graph.relations) {
 807 |         if (relation.relationType === 'helps_with' && relation.to === concept.name) {
 808 |           const resource = graph.entities.find(e => e.name === relation.from && e.entityType === 'resource');
 809 |           if (resource && !resources.some(r => r.name === resource.name)) {
 810 |             resources.push(resource);
 811 |           }
 812 |         }
 813 |       }
 814 |     }
 815 |     
 816 |     // Find notes related to this assignment
 817 |     const notes: Entity[] = [];
 818 |     for (const relation of graph.relations) {
 819 |       if (relation.relationType === 'created_for' && relation.to === assignmentName) {
 820 |         const note = graph.entities.find(e => e.name === relation.from && e.entityType === 'note');
 821 |         if (note) {
 822 |           notes.push(note);
 823 |         }
 824 |       }
 825 |     }
 826 |     
 827 |     // Add notes related to concepts covered by the assignment
 828 |     for (const concept of concepts) {
 829 |       for (const relation of graph.relations) {
 830 |         if (relation.relationType === 'references' && relation.to === concept.name) {
 831 |           const note = graph.entities.find(e => e.name === relation.from && e.entityType === 'note');
 832 |           if (note && !notes.some(n => n.name === note.name)) {
 833 |             notes.push(note);
 834 |           }
 835 |         }
 836 |       }
 837 |     }
 838 |     
 839 |     return {
 840 |       assignment,
 841 |       course,
 842 |       info: {
 843 |         status,
 844 |         dueDate,
 845 |         pointsWorth,
 846 |         instructions,
 847 |         timeRemaining,
 848 |         daysRemaining,
 849 |         isOverdue
 850 |       },
 851 |       concepts,
 852 |       resources,
 853 |       notes
 854 |     };
 855 |   }
 856 | 
 857 |   // Get exam preparation resources, related concepts, and study plan
 858 |   async getExamPrep(examName: string): Promise<any> {
 859 |     const graph = await this.loadGraph();
 860 |     
 861 |     // Find the exam
 862 |     const exam = graph.entities.find(e => e.name === examName && e.entityType === 'exam');
 863 |     if (!exam) {
 864 |       throw new Error(`Exam '${examName}' not found`);
 865 |     }
 866 |     
 867 |     // Find the course this exam is for
 868 |     let course: Entity | undefined;
 869 |     for (const relation of graph.relations) {
 870 |       if (relation.relationType === 'scheduled_for' && relation.to === examName) {
 871 |         course = graph.entities.find(e => e.name === relation.from && e.entityType === 'course');
 872 |         if (course) {
 873 |           break;
 874 |         }
 875 |       }
 876 |     }
 877 |     
 878 |     // Get exam info from observations
 879 |     const examDate = exam.observations.find(o => o.startsWith('Date:'))?.split(':')[1].trim();
 880 |     const examLocation = exam.observations.find(o => o.startsWith('Location:'))?.split(':')[1].trim();
 881 |     const examFormat = exam.observations.find(o => o.startsWith('Format:'))?.split(':')[1].trim();
 882 |     const examDuration = exam.observations.find(o => o.startsWith('Duration:'))?.split(':')[1].trim();
 883 |     
 884 |     // Calculate time remaining if exam date exists
 885 |     let timeRemaining: number | null = null;
 886 |     let daysRemaining: number | null = null;
 887 |     
 888 |     if (examDate) {
 889 |       const examDateTime = new Date(examDate).getTime();
 890 |       const now = new Date().getTime();
 891 |       timeRemaining = examDateTime - now;
 892 |       daysRemaining = Math.ceil(timeRemaining / (1000 * 60 * 60 * 24));
 893 |     }
 894 |     
 895 |     // Find concepts covered in the exam
 896 |     const concepts: Entity[] = [];
 897 |     for (const relation of graph.relations) {
 898 |       if (relation.relationType === 'covers' && relation.from === examName) {
 899 |         const concept = graph.entities.find(e => e.name === relation.to && e.entityType === 'concept');
 900 |         if (concept) {
 901 |           concepts.push(concept);
 902 |         }
 903 |       }
 904 |     }
 905 |     
 906 |     // If no concepts directly related to exam, get concepts from the course
 907 |     if (concepts.length === 0 && course) {
 908 |       for (const relation of graph.relations) {
 909 |         if (relation.relationType === 'covers' && relation.from === course.name) {
 910 |           const concept = graph.entities.find(e => e.name === relation.to && e.entityType === 'concept');
 911 |           if (concept) {
 912 |             concepts.push(concept);
 913 |           }
 914 |         }
 915 |       }
 916 |     }
 917 |     
 918 |     // Find resources helpful for the exam
 919 |     const resources: Entity[] = [];
 920 |     
 921 |     // Direct resources for the exam
 922 |     for (const relation of graph.relations) {
 923 |       if (relation.relationType === 'helps_with' && relation.to === examName) {
 924 |         const resource = graph.entities.find(e => e.name === relation.from && e.entityType === 'resource');
 925 |         if (resource) {
 926 |           resources.push(resource);
 927 |         }
 928 |       }
 929 |     }
 930 |     
 931 |     // Resources for concepts covered by the exam
 932 |     for (const concept of concepts) {
 933 |       for (const relation of graph.relations) {
 934 |         if (relation.relationType === 'helps_with' && relation.to === concept.name) {
 935 |           const resource = graph.entities.find(e => e.name === relation.from && e.entityType === 'resource');
 936 |           if (resource && !resources.some(r => r.name === resource.name)) {
 937 |             resources.push(resource);
 938 |           }
 939 |         }
 940 |       }
 941 |     }
 942 |     
 943 |     // Resources for the course
 944 |     if (course) {
 945 |       for (const relation of graph.relations) {
 946 |         if (relation.relationType === 'helps_with' && relation.to === course.name) {
 947 |           const resource = graph.entities.find(e => e.name === relation.from && e.entityType === 'resource');
 948 |           if (resource && !resources.some(r => r.name === resource.name)) {
 949 |             resources.push(resource);
 950 |           }
 951 |         }
 952 |       }
 953 |     }
 954 |     
 955 |     // Find notes related to the exam
 956 |     const notes: Entity[] = [];
 957 |     
 958 |     // Direct notes for the exam
 959 |     for (const relation of graph.relations) {
 960 |       if (relation.relationType === 'created_for' && relation.to === examName) {
 961 |         const note = graph.entities.find(e => e.name === relation.from && e.entityType === 'note');
 962 |         if (note) {
 963 |           notes.push(note);
 964 |         }
 965 |       }
 966 |     }
 967 |     
 968 |     // Notes for concepts covered in the exam
 969 |     for (const concept of concepts) {
 970 |       for (const relation of graph.relations) {
 971 |         if (relation.relationType === 'references' && relation.to === concept.name) {
 972 |           const note = graph.entities.find(e => e.name === relation.from && e.entityType === 'note');
 973 |           if (note && !notes.some(n => n.name === note.name)) {
 974 |             notes.push(note);
 975 |           }
 976 |         }
 977 |       }
 978 |     }
 979 |     
 980 |     // Find previous exams for the course
 981 |     const previousExams: Entity[] = [];
 982 |     if (course) {
 983 |       for (const relation of graph.relations) {
 984 |         if (relation.relationType === 'scheduled_for' && relation.from === course.name && relation.to !== examName) {
 985 |           const prevExam = graph.entities.find(e => e.name === relation.to && e.entityType === 'exam');
 986 |           if (prevExam) {
 987 |             const prevExamDate = prevExam.observations.find(o => o.startsWith('Date:'))?.split(':')[1].trim();
 988 |             if (prevExamDate && new Date(prevExamDate) < new Date()) {
 989 |               previousExams.push(prevExam);
 990 |             }
 991 |           }
 992 |         }
 993 |       }
 994 |     }
 995 |     
 996 |     // Find study sessions scheduled for this exam
 997 |     // Note: We no longer use studySession entities, this section is removed
 998 |     const studySessions: Entity[] = [];
 999 |     
1000 |     // Get concepts covered in this exam
1001 |     const conceptsCovered: Entity[] = [];
1002 |     
1003 |     return {
1004 |       exam,
1005 |       course,
1006 |       info: {
1007 |         examDate,
1008 |         examLocation,
1009 |         examFormat,
1010 |         examDuration,
1011 |         timeRemaining,
1012 |         daysRemaining
1013 |       },
1014 |       concepts,
1015 |       resources,
1016 |       notes,
1017 |       previousExams,
1018 |       studySessions,
1019 |       summary: {
1020 |         conceptCount: concepts.length,
1021 |         resourceCount: resources.length,
1022 |         noteCount: notes.length,
1023 |         previousExamCount: previousExams.length,
1024 |         studySessionCount: studySessions.length
1025 |       }
1026 |     };
1027 |   }
1028 | 
1029 |   // Find concepts related to a given concept and how they're connected
1030 |   async findRelatedConcepts(conceptName: string, depth: number = 1): Promise<any> {
1031 |     const graph = await this.loadGraph();
1032 |     
1033 |     // Find the concept
1034 |     const concept = graph.entities.find(e => e.name === conceptName && e.entityType === 'concept');
1035 |     if (!concept) {
1036 |       throw new Error(`Concept '${conceptName}' not found`);
1037 |     }
1038 |     
1039 |     // Initialize results
1040 |     const relatedConcepts: {
1041 |       concept: Entity;
1042 |       relationPath: string[];
1043 |       depth: number;
1044 |       courses: Entity[];
1045 |       resources: Entity[];
1046 |     }[] = [];
1047 |     
1048 |     // Set to track processed concepts to avoid duplicates
1049 |     const processedConcepts = new Set<string>();
1050 |     processedConcepts.add(conceptName);
1051 |     
1052 |     // Queue of concepts to process with their current depth and path
1053 |     const queue: {
1054 |       name: string;
1055 |       currentDepth: number;
1056 |       path: string[];
1057 |     }[] = [{ name: conceptName, currentDepth: 0, path: [] }];
1058 |     
1059 |     // Process the queue
1060 |     while (queue.length > 0) {
1061 |       const { name, currentDepth, path } = queue.shift()!;
1062 |       
1063 |       // Skip if we've reached max depth (except for the initial concept)
1064 |       if (currentDepth > depth && name !== conceptName) continue;
1065 |       
1066 |       // Find the concept entity
1067 |       const currentConcept = graph.entities.find(e => e.name === name && e.entityType === 'concept');
1068 |       if (!currentConcept) continue;
1069 |       
1070 |       // Skip the initial concept for the results
1071 |       if (name !== conceptName) {
1072 |         // Find courses that cover this concept
1073 |         const courses: Entity[] = [];
1074 |         for (const relation of graph.relations) {
1075 |           if (relation.relationType === 'covers' && relation.to === name) {
1076 |             const course = graph.entities.find(e => e.name === relation.from && e.entityType === 'course');
1077 |             if (course) {
1078 |               courses.push(course);
1079 |             }
1080 |           }
1081 |         }
1082 |         
1083 |         // Find resources that help with this concept
1084 |         const resources: Entity[] = [];
1085 |         for (const relation of graph.relations) {
1086 |           if (relation.relationType === 'helps_with' && relation.to === name) {
1087 |             const resource = graph.entities.find(e => e.name === relation.from && e.entityType === 'resource');
1088 |             if (resource) {
1089 |               resources.push(resource);
1090 |             }
1091 |           }
1092 |         }
1093 |         
1094 |         relatedConcepts.push({
1095 |           concept: currentConcept,
1096 |           relationPath: [...path],
1097 |           depth: currentDepth,
1098 |           courses,
1099 |           resources
1100 |         });
1101 |       }
1102 |       
1103 |       // Find directly related concepts through 'related_to'
1104 |       for (const relation of graph.relations) {
1105 |         if (relation.relationType === 'related_to') {
1106 |           let nextConcept: string | null = null;
1107 |           
1108 |           // Check bidirectional relation
1109 |           if (relation.from === name) {
1110 |             nextConcept = relation.to;
1111 |           } else if (relation.to === name) {
1112 |             nextConcept = relation.from;
1113 |           }
1114 |           
1115 |           if (nextConcept && !processedConcepts.has(nextConcept)) {
1116 |             processedConcepts.add(nextConcept);
1117 |             queue.push({
1118 |               name: nextConcept,
1119 |               currentDepth: currentDepth + 1,
1120 |               path: [...path, `related_to ${nextConcept}`]
1121 |             });
1122 |           }
1123 |         }
1124 |       }
1125 |       
1126 |       // Find prerequisites
1127 |       for (const relation of graph.relations) {
1128 |         if (relation.relationType === 'prerequisite_for') {
1129 |           let nextConcept: string | null = null;
1130 |           let relationDescription: string = '';
1131 |           
1132 |           if (relation.from === name) {
1133 |             nextConcept = relation.to;
1134 |             relationDescription = `prerequisite_for ${nextConcept}`;
1135 |           } else if (relation.to === name) {
1136 |             nextConcept = relation.from;
1137 |             relationDescription = `${nextConcept} is_prerequisite_for this`;
1138 |           }
1139 |           
1140 |           if (nextConcept && !processedConcepts.has(nextConcept)) {
1141 |             processedConcepts.add(nextConcept);
1142 |             queue.push({
1143 |               name: nextConcept,
1144 |               currentDepth: currentDepth + 1,
1145 |               path: [...path, relationDescription]
1146 |             });
1147 |           }
1148 |         }
1149 |       }
1150 |     }
1151 |     
1152 |     // Sort related concepts by depth
1153 |     relatedConcepts.sort((a, b) => a.depth - b.depth);
1154 |     
1155 |     return {
1156 |       concept,
1157 |       relatedConcepts,
1158 |       summary: {
1159 |         totalRelated: relatedConcepts.length,
1160 |         maxDepth: depth
1161 |       }
1162 |     };
1163 |   }
1164 | 
1165 |   // Track lecture notes and find related concepts and resources
1166 |   async trackLectureNotes(courseName: string): Promise<any> {
1167 |     const graph = await this.loadGraph();
1168 |     
1169 |     // Find the course
1170 |     const course = graph.entities.find(e => e.name === courseName && e.entityType === 'course');
1171 |     if (!course) {
1172 |       throw new Error(`Course '${courseName}' not found`);
1173 |     }
1174 |     
1175 |     // Find lectures for this course
1176 |     const lectures: Entity[] = [];
1177 |     for (const relation of graph.relations) {
1178 |       if (relation.relationType === 'part_of' && relation.to === courseName) {
1179 |         const lecture = graph.entities.find(e => e.name === relation.from && e.entityType === 'lecture');
1180 |         if (lecture) {
1181 |           lectures.push(lecture);
1182 |         }
1183 |       }
1184 |     }
1185 |     
1186 |     // Sort lectures by date if available
1187 |     lectures.sort((a, b) => {
1188 |       const aDateObs = a.observations.find(o => o.startsWith('Date:'));
1189 |       const bDateObs = b.observations.find(o => o.startsWith('Date:'));
1190 |       
1191 |       if (aDateObs && bDateObs) {
1192 |         const aDate = new Date(aDateObs.split(':')[1].trim());
1193 |         const bDate = new Date(bDateObs.split(':')[1].trim());
1194 |         return aDate.getTime() - bDate.getTime();
1195 |       }
1196 |       return 0;
1197 |     });
1198 |     
1199 |     // Create a structure to hold lecture data with notes and concepts
1200 |     const lectureData: Array<{
1201 |       lecture: Entity;
1202 |       info: {
1203 |         date: string | undefined;
1204 |         topic: string | undefined;
1205 |       };
1206 |       notes: Entity[];
1207 |       concepts: Entity[];
1208 |       resources: Entity[];
1209 |       summary: {
1210 |         noteCount: number;
1211 |         conceptCount: number;
1212 |         resourceCount: number;
1213 |       };
1214 |     }> = [];
1215 |     
1216 |     for (const lecture of lectures) {
1217 |       // Get details about the lecture
1218 |       const lectureDate = lecture.observations.find(o => o.startsWith('Date:'))?.split(':')[1].trim();
1219 |       const lectureTopic = lecture.observations.find(o => o.startsWith('Topic:'))?.split(':')[1].trim();
1220 |       
1221 |       // Find notes for this lecture
1222 |       const notes: Entity[] = [];
1223 |       for (const relation of graph.relations) {
1224 |         if (relation.relationType === 'created_for' && relation.to === lecture.name) {
1225 |           const note = graph.entities.find(e => e.name === relation.from && e.entityType === 'note');
1226 |           if (note) {
1227 |             notes.push(note);
1228 |           }
1229 |         }
1230 |       }
1231 |       
1232 |       // Find concepts covered in this lecture
1233 |       const concepts: Entity[] = [];
1234 |       for (const relation of graph.relations) {
1235 |         if (relation.relationType === 'covers' && relation.from === lecture.name) {
1236 |           const concept = graph.entities.find(e => e.name === relation.to && e.entityType === 'concept');
1237 |           if (concept) {
1238 |             concepts.push(concept);
1239 |           }
1240 |         }
1241 |       }
1242 |       
1243 |       // Get resources related to this lecture
1244 |       const resources: Entity[] = [];
1245 |       for (const relation of graph.relations) {
1246 |         if (relation.relationType === 'helps_with' && relation.to === lecture.name) {
1247 |           const resource = graph.entities.find(e => e.name === relation.from && e.entityType === 'resource');
1248 |           if (resource) {
1249 |             resources.push(resource);
1250 |           }
1251 |         }
1252 |       }
1253 |       
1254 |       // Add resources related to concepts in this lecture
1255 |       for (const concept of concepts) {
1256 |         for (const relation of graph.relations) {
1257 |           if (relation.relationType === 'helps_with' && relation.to === concept.name) {
1258 |             const resource = graph.entities.find(e => e.name === relation.from && e.entityType === 'resource');
1259 |             if (resource && !resources.some(r => r.name === resource.name)) {
1260 |               resources.push(resource);
1261 |             }
1262 |           }
1263 |         }
1264 |       }
1265 |       
1266 |       lectureData.push({
1267 |         lecture,
1268 |         info: {
1269 |           date: lectureDate,
1270 |           topic: lectureTopic
1271 |         },
1272 |         notes,
1273 |         concepts,
1274 |         resources,
1275 |         summary: {
1276 |           noteCount: notes.length,
1277 |           conceptCount: concepts.length,
1278 |           resourceCount: resources.length
1279 |         }
1280 |       });
1281 |     }
1282 |     
1283 |     return {
1284 |       course,
1285 |       lectures: lectureData,
1286 |       summary: {
1287 |         lectureCount: lectures.length,
1288 |         totalNotes: lectureData.reduce((sum, ld) => sum + ld.notes.length, 0),
1289 |         totalConcepts: lectureData.reduce((sum, ld) => sum + ld.concepts.length, 0)
1290 |       }
1291 |     };
1292 |   }
1293 | 
1294 |   // Get term overview including courses, progress, and important dates
1295 |   async getTermOverview(termName: string): Promise<any> {
1296 |     const graph = await this.loadGraph();
1297 |     
1298 |     // Find the term
1299 |     const term = graph.entities.find(e => e.name === termName && e.entityType === 'term');
1300 |     if (!term) {
1301 |       throw new Error(`Term '${termName}' not found`);
1302 |     }
1303 |     
1304 |     // Get term info
1305 |     const startDate = term.observations.find(o => o.startsWith('StartDate:'))?.split(':')[1].trim();
1306 |     const endDate = term.observations.find(o => o.startsWith('EndDate:'))?.split(':')[1].trim();
1307 |     const status = term.observations.find(o => o.startsWith('Status:'))?.split(':')[1].trim() || 'in_progress';
1308 |     
1309 |     // Find courses for this term
1310 |     const courses: Entity[] = [];
1311 |     for (const relation of graph.relations) {
1312 |       if (relation.relationType === 'part_of' && relation.to === termName) {
1313 |         const course = graph.entities.find(e => e.name === relation.from && e.entityType === 'course');
1314 |         if (course) {
1315 |           courses.push(course);
1316 |         }
1317 |       }
1318 |     }
1319 |     
1320 |     // Get detailed information for each course
1321 |     const courseData: Array<{
1322 |       course: Entity;
1323 |       professor: Entity | undefined;
1324 |       info: {
1325 |         code: string | undefined;
1326 |         schedule: string | undefined;
1327 |         status: string;
1328 |       };
1329 |       progress: {
1330 |         completedAssignments: number;
1331 |         totalAssignments: number;
1332 |         completionRate: number;
1333 |       };
1334 |       upcomingExam: Entity | undefined;
1335 |       summary: {
1336 |         assignmentCount: number;
1337 |         examCount: number;
1338 |       };
1339 |     }> = [];
1340 |     
1341 |     for (const course of courses) {
1342 |       // Get course info
1343 |       const courseCode = course.observations.find(o => o.startsWith('Code:'))?.split(':')[1].trim();
1344 |       const courseSchedule = course.observations.find(o => o.startsWith('Schedule:'))?.split(':')[1].trim();
1345 |       const courseStatus = course.observations.find(o => o.startsWith('Status:'))?.split(':')[1].trim() || 'in_progress';
1346 |       
1347 |       // Find professor
1348 |       let professor: Entity | undefined;
1349 |       for (const relation of graph.relations) {
1350 |         if (relation.relationType === 'taught_by' && relation.from === course.name) {
1351 |           professor = graph.entities.find(e => e.name === relation.to && e.entityType === 'professor');
1352 |           if (professor) {
1353 |             break;
1354 |           }
1355 |         }
1356 |       }
1357 |       
1358 |       // Find assignments for this course
1359 |       const assignments: Entity[] = [];
1360 |       for (const relation of graph.relations) {
1361 |         if (relation.relationType === 'assigned_in' && relation.to === course.name) {
1362 |           const assignment = graph.entities.find(e => e.name === relation.from && e.entityType === 'assignment');
1363 |           if (assignment) {
1364 |             assignments.push(assignment);
1365 |           }
1366 |         }
1367 |       }
1368 |       
1369 |       // Count completed and total assignments
1370 |       const completedAssignments = assignments.filter(a => 
1371 |         a.observations.find(o => o.startsWith('Status:'))?.split(':')[1].trim() === 'completed'
1372 |       ).length;
1373 |       
1374 |       // Find exams for this course
1375 |       const exams: Entity[] = [];
1376 |       for (const relation of graph.relations) {
1377 |         if (relation.relationType === 'scheduled_for' && relation.from === course.name) {
1378 |           const exam = graph.entities.find(e => e.name === relation.to && e.entityType === 'exam');
1379 |           if (exam) {
1380 |             exams.push(exam);
1381 |           }
1382 |         }
1383 |       }
1384 |       
1385 |       // Sort exams by date
1386 |       exams.sort((a, b) => {
1387 |         const aDateObs = a.observations.find(o => o.startsWith('Date:'));
1388 |         const bDateObs = b.observations.find(o => o.startsWith('Date:'));
1389 |         
1390 |         if (aDateObs && bDateObs) {
1391 |           const aDate = new Date(aDateObs.split(':')[1].trim());
1392 |           const bDate = new Date(bDateObs.split(':')[1].trim());
1393 |           return aDate.getTime() - bDate.getTime();
1394 |         }
1395 |         return 0;
1396 |       });
1397 |       
1398 |       // Get the next upcoming exam
1399 |       const upcomingExam = exams.find(e => {
1400 |         const dateObs = e.observations.find(o => o.startsWith('Date:'));
1401 |         if (dateObs) {
1402 |           const examDate = new Date(dateObs.split(':')[1].trim());
1403 |           return examDate > new Date();
1404 |         }
1405 |         return false;
1406 |       });
1407 |       
1408 |       courseData.push({
1409 |         course,
1410 |         professor,
1411 |         info: {
1412 |           code: courseCode,
1413 |           schedule: courseSchedule,
1414 |           status: courseStatus
1415 |         },
1416 |         progress: {
1417 |           completedAssignments,
1418 |           totalAssignments: assignments.length,
1419 |           completionRate: assignments.length > 0 ? 
1420 |             Math.round((completedAssignments / assignments.length) * 100) : 0
1421 |         },
1422 |         upcomingExam,
1423 |         summary: {
1424 |           assignmentCount: assignments.length,
1425 |           examCount: exams.length
1426 |         }
1427 |       });
1428 |     }
1429 |     
1430 |     // Find all deadlines (assignments and exams) in this term
1431 |     const allDeadlines: {
1432 |       entity: Entity;
1433 |       type: 'assignment' | 'exam';
1434 |       course: Entity;
1435 |       date: Date;
1436 |       daysRemaining: number;
1437 |     }[] = [];
1438 |     
1439 |     // Current date for comparisons
1440 |     const today = new Date();
1441 |     
1442 |     // Process assignments
1443 |     for (const course of courses) {
1444 |       // Find assignments for this course
1445 |       for (const relation of graph.relations) {
1446 |         if (relation.relationType === 'assigned_in' && relation.to === course.name) {
1447 |           const assignment = graph.entities.find(e => e.name === relation.from && e.entityType === 'assignment');
1448 |           if (assignment) {
1449 |             // Check due date
1450 |             const dueDateObs = assignment.observations.find(o => o.startsWith('Due:'));
1451 |             if (dueDateObs) {
1452 |               const dueDateStr = dueDateObs.split(':')[1].trim();
1453 |               const dueDate = new Date(dueDateStr);
1454 |               
1455 |               // Only include future deadlines
1456 |               if (dueDate >= today) {
1457 |                 const daysRemaining = Math.ceil((dueDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
1458 |                 allDeadlines.push({
1459 |                   entity: assignment,
1460 |                   type: 'assignment',
1461 |                   course,
1462 |                   date: dueDate,
1463 |                   daysRemaining
1464 |                 });
1465 |               }
1466 |             }
1467 |           }
1468 |         }
1469 |       }
1470 |       
1471 |       // Find exams for this course
1472 |       for (const relation of graph.relations) {
1473 |         if (relation.relationType === 'scheduled_for' && relation.from === course.name) {
1474 |           const exam = graph.entities.find(e => e.name === relation.to && e.entityType === 'exam');
1475 |           if (exam) {
1476 |             // Check exam date
1477 |             const dateObs = exam.observations.find(o => o.startsWith('Date:'));
1478 |             if (dateObs) {
1479 |               const dateStr = dateObs.split(':')[1].trim();
1480 |               const examDate = new Date(dateStr);
1481 |               
1482 |               // Only include future dates
1483 |               if (examDate >= today) {
1484 |                 const daysRemaining = Math.ceil((examDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
1485 |                 allDeadlines.push({
1486 |                   entity: exam,
1487 |                   type: 'exam',
1488 |                   course,
1489 |                   date: examDate,
1490 |                   daysRemaining
1491 |                 });
1492 |               }
1493 |             }
1494 |           }
1495 |         }
1496 |       }
1497 |     }
1498 |     
1499 |     // Sort all deadlines by date
1500 |     allDeadlines.sort((a, b) => a.date.getTime() - b.date.getTime());
1501 |     
1502 |     return {
1503 |       term,
1504 |       info: {
1505 |         startDate,
1506 |         endDate,
1507 |         status
1508 |       },
1509 |       courses: courseData,
1510 |       upcomingDeadlines: allDeadlines.slice(0, 10), // Return the next 10 deadlines
1511 |       summary: {
1512 |         courseCount: courses.length,
1513 |         deadlineCount: allDeadlines.length
1514 |       }
1515 |     };
1516 |   }
1517 | }
1518 | 
1519 | // Session management functions
1520 | async function loadSessionStates(): Promise<Map<string, any[]>> {
1521 |   try {
1522 |     const fileContent = await fs.readFile(SESSIONS_FILE_PATH, 'utf-8');
1523 |     const sessions = JSON.parse(fileContent);
1524 |     // Convert from object to Map
1525 |     const sessionsMap = new Map<string, any[]>();
1526 |     for (const [key, value] of Object.entries(sessions)) {
1527 |       sessionsMap.set(key, value as any[]);
1528 |     }
1529 |     return sessionsMap;
1530 |   } catch (error) {
1531 |     if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") {
1532 |       return new Map<string, any[]>();
1533 |     }
1534 |     throw error;
1535 |   }
1536 | }
1537 | 
1538 | async function saveSessionStates(sessionsMap: Map<string, any[]>): Promise<void> {
1539 |   // Convert from Map to object
1540 |   const sessions: Record<string, any[]> = {};
1541 |   for (const [key, value] of sessionsMap.entries()) {
1542 |     sessions[key] = value;
1543 |   }
1544 |   await fs.writeFile(SESSIONS_FILE_PATH, JSON.stringify(sessions, null, 2), 'utf-8');
1545 | }
1546 | 
1547 | // Generate a unique session ID
1548 | function generateSessionId(): string {
1549 |   return `stud_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
1550 | }
1551 | 
1552 | // Setup the MCP server
1553 | async function main() {
1554 |   const knowledgeGraphManager = new KnowledgeGraphManager();
1555 |   
1556 |   // Initialize status and priority entities
1557 |   await knowledgeGraphManager.initializeStatusAndPriority();
1558 |   
1559 |   // Helper function to get current term
1560 |   async function getCurrentTerm(): Promise<string | null> {
1561 |     // Find the most recent term with status "active"
1562 |     const termQuery = await knowledgeGraphManager.searchNodes("entityType:term status:active");
1563 |     if (termQuery.entities.length > 0) {
1564 |       return termQuery.entities[0].name;
1565 |     }
1566 |     return null;
1567 |   }
1568 |   
1569 |   // Create the MCP server using the new API
1570 |   const server = new McpServer({
1571 |     name: "Context Manager",
1572 |     version: "1.0.0"
1573 |   });
1574 | 
1575 |   // Define a resource that exposes the entire graph
1576 |   server.resource(
1577 |     "graph",
1578 |     "graph://student",
1579 |     async (uri) => ({
1580 |       contents: [{
1581 |         uri: uri.href,
1582 |         text: JSON.stringify(await knowledgeGraphManager.readGraph(), null, 2)
1583 |       }]
1584 |     })
1585 |   );
1586 | 
1587 |   /**
1588 |    * Load context for a specific entity
1589 |    */
1590 |   server.tool(
1591 |     "loadcontext",
1592 |     toolDescriptions["loadcontext"],
1593 |     {
1594 |       entityName: z.string(),
1595 |       entityType: z.enum(validEntityTypes).optional().describe("Type of entity to load, defaults to 'course'"),
1596 |       sessionId: z.string().optional().describe("Session ID from startsession to track context loading")
1597 |     },
1598 |     async ({ entityName, entityType = "course", sessionId }) => {
1599 |       try {
1600 |         // Validate session if ID is provided
1601 |         if (sessionId) {
1602 |           const sessionStates = await loadSessionStates();
1603 |           if (!sessionStates.has(sessionId)) {
1604 |             console.warn(`Warning: Session ${sessionId} not found, but proceeding with context load`);
1605 |             // Initialize it anyway for more robustness
1606 |             sessionStates.set(sessionId, []);
1607 |             await saveSessionStates(sessionStates);
1608 |           }
1609 |           
1610 |           // Track that this entity was loaded in this session
1611 |           const sessionState = sessionStates.get(sessionId) || [];
1612 |           const loadEvent = {
1613 |             type: 'context_loaded',
1614 |             timestamp: new Date().toISOString(),
1615 |             entityName,
1616 |             entityType
1617 |           };
1618 |           sessionState.push(loadEvent);
1619 |           sessionStates.set(sessionId, sessionState);
1620 |           await saveSessionStates(sessionStates);
1621 |         }
1622 |         
1623 |         // Get the entity
1624 |         const entityGraph = await knowledgeGraphManager.searchNodes(entityName);
1625 |         if (entityGraph.entities.length === 0) {
1626 |           throw new Error(`Entity ${entityName} not found`);
1627 |         }
1628 |         
1629 |         // Find the exact entity by name (case-sensitive match)
1630 |         const entity = entityGraph.entities.find(e => e.name === entityName);
1631 |         if (!entity) {
1632 |           throw new Error(`Entity ${entityName} not found`);
1633 |         }
1634 |         
1635 |         // Get status and priority
1636 |         const status = await knowledgeGraphManager.getEntityStatus(entityName) || "not_started";
1637 |         const priority = await knowledgeGraphManager.getEntityPriority(entityName);
1638 |         
1639 |         // Format observations for display
1640 |         const observationsList = entity.observations.length > 0 
1641 |           ? entity.observations.map(obs => `- ${obs}`).join("\n")
1642 |           : "No observations";
1643 |         
1644 |         // Different context loading based on entity type
1645 |         let contextMessage = "";
1646 |         
1647 |         if (entityType === "course") {
1648 |           // Get course overview
1649 |           const courseOverview = await knowledgeGraphManager.getCourseOverview(entityName);
1650 |           
1651 |           // Format course context message
1652 |           const code = entity.observations.find(o => o.startsWith("Code:"))?.substring(5) || "No code";
1653 |           const schedule = entity.observations.find(o => o.startsWith("Schedule:"))?.substring(9) || "No schedule";
1654 |           const location = entity.observations.find(o => o.startsWith("Location:"))?.substring(9) || "No location";
1655 |           
1656 |           // Format lectures
1657 |           const lecturesText = courseOverview.lectures?.map((lecture: Entity) => {
1658 |             return `- **${lecture.name}**: ${lecture.observations.join(", ")}`;
1659 |           }).join("\n") || "No lectures found";
1660 |           
1661 |           // Format assignments - use status relation
1662 |           const assignmentsText = courseOverview.assignments?.map(async (assignment: Entity) => {
1663 |             const assignmentStatus = await knowledgeGraphManager.getEntityStatus(assignment.name) || "not_started";
1664 |             const assignmentPriority = await knowledgeGraphManager.getEntityPriority(assignment.name);
1665 |             const priorityText = assignmentPriority ? `, Priority: ${assignmentPriority}` : "";
1666 |             return `- **${assignment.name}** (Status: ${assignmentStatus}${priorityText}): ${assignment.observations.join(", ")}`;
1667 |           });
1668 |           
1669 |           const resolvedAssignmentsText = assignmentsText ? 
1670 |             await Promise.all(assignmentsText).then(texts => texts.join("\n")) : 
1671 |             "No assignments found";
1672 |           
1673 |           // Format exams - use status relation
1674 |           const examsText = courseOverview.exams?.map(async (exam: Entity) => {
1675 |             const examStatus = await knowledgeGraphManager.getEntityStatus(exam.name) || "not_started";
1676 |             return `- **${exam.name}** (Status: ${examStatus}): ${exam.observations.join(", ")}`;
1677 |           });
1678 |           
1679 |           const resolvedExamsText = examsText ? 
1680 |             await Promise.all(examsText).then(texts => texts.join("\n")) : 
1681 |             "No exams found";
1682 |           
1683 |           // Format concepts
1684 |           const conceptsText = courseOverview.concepts?.map((concept: Entity) => {
1685 |             return `- **${concept.name}**: ${concept.observations.join(", ")}`;
1686 |           }).join("\n") || "No concepts found";
1687 |           
1688 |           // Format resources
1689 |           const resourcesText = courseOverview.resources?.map((resource: Entity) => {
1690 |             return `- **${resource.name}**: ${resource.observations.join(", ")}`;
1691 |           }).join("\n") || "No resources found";
1692 |           
1693 |           // Add professor info if available
1694 |           const professorText = courseOverview.professor ? 
1695 |             `**Professor**: ${courseOverview.professor.name}
1696 | ${courseOverview.professor.observations.join("\n")}` : "No professor information";
1697 |           
1698 |           // Add term info if available
1699 |           const termText = courseOverview.term ? 
1700 |             `**Term**: ${courseOverview.term.name}` : "No term information";
1701 |           
1702 |           contextMessage = `# Course Context: ${entityName}
1703 | 
1704 | ## Course Overview
1705 | - **Code**: ${code}
1706 | - **Status**: ${status}
1707 | - **Priority**: ${priority || "N/A"}
1708 | - **Schedule**: ${schedule}
1709 | - **Location**: ${location}
1710 | 
1711 | ## Observations
1712 | ${observationsList}
1713 | 
1714 | - ${termText}
1715 | - ${professorText}
1716 | 
1717 | ## Lectures
1718 | ${lecturesText}
1719 | 
1720 | ## Assignments
1721 | ${resolvedAssignmentsText}
1722 | 
1723 | ## Exams
1724 | ${resolvedExamsText}
1725 | 
1726 | ## Key Concepts
1727 | ${conceptsText}
1728 | 
1729 | ## Resources
1730 | ${resourcesText}`;
1731 |         } 
1732 |         else if (entityType === "assignment") {
1733 |           // Get assignment status
1734 |           const assignmentStatus = await knowledgeGraphManager.getAssignmentStatus(entityName);
1735 |           
1736 |           // Get course name
1737 |           const courseName = assignmentStatus.course?.name || "Unknown course";
1738 |           
1739 |           // Format assignment context using relations instead of observations
1740 |           const dueDate = entity.observations.find(o => o.startsWith("Due:"))?.substring(4) || "No due date";
1741 |           const points = entity.observations.find(o => o.startsWith("Points:"))?.substring(7) || "Not specified";
1742 |           const instructions = entity.observations.find(o => o.startsWith("Instructions:"))?.substring(13) || "No instructions provided";
1743 |           
1744 |           // Calculate time remaining
1745 |           let timeRemainingText = "No due date specified";
1746 |           if (assignmentStatus.timeRemaining !== null) {
1747 |             if (assignmentStatus.isOverdue) {
1748 |               timeRemainingText = `OVERDUE by ${Math.abs(assignmentStatus.daysRemaining)} days`;
1749 |             } else {
1750 |               timeRemainingText = `${assignmentStatus.daysRemaining} days remaining`;
1751 |             }
1752 |           }
1753 |           
1754 |           // Format related concepts
1755 |           const conceptsText = assignmentStatus.concepts?.map((concept: Entity) => {
1756 |             return `- **${concept.name}**: ${concept.observations.join(", ")}`;
1757 |           }).join("\n") || "No related concepts found";
1758 |           
1759 |           // Format related resources
1760 |           const resourcesText = assignmentStatus.resources?.map((resource: Entity) => {
1761 |             return `- **${resource.name}**: ${resource.observations.join(", ")}`;
1762 |           }).join("\n") || "No resources found";
1763 |           
1764 |           // Format notes
1765 |           const notesText = assignmentStatus.notes?.map((note: Entity) => {
1766 |             return `- **${note.name}**: ${note.observations.join(", ")}`;
1767 |           }).join("\n") || "No notes found";
1768 |           
1769 |           contextMessage = `# Assignment Context: ${entityName}
1770 | 
1771 | ## Assignment Overview
1772 | - **Course**: ${courseName}
1773 | - **Status**: ${status}
1774 | - **Priority**: ${priority || "N/A"}
1775 | - **Due Date**: ${dueDate}
1776 | - **Points**: ${points}
1777 | - **Time Remaining**: ${timeRemainingText}
1778 | 
1779 | ## Observations
1780 | ${observationsList}
1781 | 
1782 | ## Instructions
1783 | ${instructions}
1784 | 
1785 | ## Related Concepts
1786 | ${conceptsText}
1787 | 
1788 | ## Helpful Resources
1789 | ${resourcesText}
1790 | 
1791 | ## Your Notes
1792 | ${notesText}`;
1793 |         }
1794 |         else if (entityType === "exam") {
1795 |           // Get exam prep information
1796 |           const examPrep = await knowledgeGraphManager.getExamPrep(entityName);
1797 |           
1798 |           // Format exam context
1799 |           const examDate = entity.observations.find(o => o.startsWith("Date:"))?.substring(5) || "No date scheduled";
1800 |           const examLocation = entity.observations.find(o => o.startsWith("Location:"))?.substring(9) || "No location specified";
1801 |           const examFormat = entity.observations.find(o => o.startsWith("Format:"))?.substring(7) || "No format specified";
1802 |           const examDuration = entity.observations.find(o => o.startsWith("Duration:"))?.substring(9) || "No duration specified";
1803 |           
1804 |           // Calculate time remaining
1805 |           let timeRemainingText = "No exam date specified";
1806 |           if (examPrep.daysRemaining !== null) {
1807 |             timeRemainingText = `${examPrep.daysRemaining} days until exam`;
1808 |           }
1809 |           
1810 |           // Get course name
1811 |           const courseName = examPrep.course?.name || "Unknown course";
1812 |           
1813 |           // Format concepts covered
1814 |           const conceptsText = examPrep.concepts?.map((concept: Entity) => {
1815 |             return `- **${concept.name}**: ${concept.observations.join(", ")}`;
1816 |           }).join("\n") || "No concepts listed";
1817 |           
1818 |           // Format study resources
1819 |           const resourcesText = examPrep.resources?.map((resource: Entity) => {
1820 |             return `- **${resource.name}**: ${resource.observations.join(", ")}`;
1821 |           }).join("\n") || "No resources found";
1822 |           
1823 |           // Format lectures
1824 |           const lecturesText = examPrep.lectures?.map((lecture: Entity) => {
1825 |             return `- **${lecture.name}**: ${lecture.observations.join(", ")}`;
1826 |           }).join("\n") || "No lectures found";
1827 |           
1828 |           contextMessage = `# Exam Context: ${entityName}
1829 | 
1830 | ## Exam Details
1831 | - **Course**: ${courseName}
1832 | - **Date**: ${examDate}
1833 | - **Time Remaining**: ${timeRemainingText}
1834 | - **Location**: ${examLocation}
1835 | - **Format**: ${examFormat}
1836 | - **Duration**: ${examDuration}
1837 | 
1838 | ## Concepts to Study
1839 | ${conceptsText}
1840 | 
1841 | ## Key Lectures
1842 | ${lecturesText}
1843 | 
1844 | ## Study Resources
1845 | ${resourcesText}`;
1846 |         }
1847 |         else if (entityType === "concept") {
1848 |           // Get related concepts
1849 |           const relatedConceptsData = await knowledgeGraphManager.findRelatedConcepts(entityName);
1850 |           
1851 |           // Format concept context
1852 |           const description = entity.observations.find(o => !o.startsWith("Level:")) || "No description available";
1853 |           const level = entity.observations.find(o => o.startsWith("Level:"))?.substring(6) || "Beginner";
1854 |           
1855 |           // Format related concepts
1856 |           const relatedConceptsText = relatedConceptsData.relatedConcepts?.map((related: {
1857 |             concept: Entity;
1858 |             relationPath: string[];
1859 |             depth: number;
1860 |           }) => {
1861 |             return `- **${related.concept.name}**: ${related.relationPath.join(' → ')}`;
1862 |           }).join("\n") || "No related concepts found";
1863 |           
1864 |           // Format courses that cover this concept
1865 |           const coursesText = relatedConceptsData.courses?.map((course: Entity) => {
1866 |             return `- **${course.name}**: ${course.observations.join(", ")}`;
1867 |           }).join("\n") || "No courses found";
1868 |           
1869 |           // Format resources about this concept
1870 |           const resourcesText = relatedConceptsData.resources?.map((resource: Entity) => {
1871 |             return `- **${resource.name}**: ${resource.observations.join(", ")}`;
1872 |           }).join("\n") || "No resources found";
1873 |           
1874 |           contextMessage = `# Concept Context: ${entityName}
1875 | 
1876 | ## Concept Details
1877 | - **Difficulty Level**: ${level}
1878 | - **Description**: ${description}
1879 | 
1880 | ## Related Concepts
1881 | ${relatedConceptsText}
1882 | 
1883 | ## Covered in Courses
1884 | ${coursesText}
1885 | 
1886 | ## Learning Resources
1887 | ${resourcesText}`;
1888 |         }
1889 |         else if (entityType === "term") {
1890 |           // Get term overview
1891 |           const termOverview = await knowledgeGraphManager.getTermOverview(entityName);
1892 |           
1893 |           // Format term context
1894 |           const startDate = entity.observations.find(o => o.startsWith("StartDate:"))?.substring(10) || "No start date";
1895 |           const endDate = entity.observations.find(o => o.startsWith("EndDate:"))?.substring(8) || "No end date";
1896 |           const status = entity.observations.find(o => o.startsWith("Status:"))?.substring(7) || "Unknown status";
1897 |           
1898 |           // Format courses in this term
1899 |           const coursesText = termOverview.courseData?.map((courseData: {
1900 |             course: Entity;
1901 |             professor: Entity | undefined;
1902 |             info: any;
1903 |             progress: any;
1904 |           }) => {
1905 |             return `- **${courseData.course.name}**: ${courseData.info.code || "No code"}, ${courseData.info.status}, ${courseData.progress.completionRate || 0}% complete`;
1906 |           }).join("\n\n") || "No courses found";
1907 |           
1908 |           // Format upcoming deadlines
1909 |           const deadlinesText = termOverview.upcomingDeadlines?.map((deadline: {
1910 |             entity: Entity;
1911 |             dueDate: string;
1912 |             course: Entity;
1913 |             daysRemaining: number;
1914 |           }) => {
1915 |             return `- **${deadline.entity.name}** (${deadline.entity.entityType})
1916 |   Course: ${deadline.course.name}
1917 |   Due: ${deadline.dueDate} (${deadline.daysRemaining} days remaining)`;
1918 |           }).join("\n\n") || "No upcoming deadlines";
1919 |           
1920 |           contextMessage = `# Term Context: ${entityName}
1921 | 
1922 | ## Term Details
1923 | - **Start Date**: ${startDate}
1924 | - **End Date**: ${endDate}
1925 | - **Status**: ${status}
1926 | 
1927 | ## Courses This Term
1928 | ${coursesText}
1929 | 
1930 | ## Upcoming Deadlines
1931 | ${deadlinesText}`;
1932 |         }
1933 |         else {
1934 |           // Generic entity context for other entity types
1935 |           // Find all relations involving this entity
1936 |           const relations = await knowledgeGraphManager.openNodes([entityName]);
1937 |           
1938 |           // Build a text representation of related entities
1939 |           const incomingRelations = relations.relations.filter(r => r.to === entityName);
1940 |           const outgoingRelations = relations.relations.filter(r => r.from === entityName);
1941 |           
1942 |           const incomingText = incomingRelations.map(rel => {
1943 |             const sourceEntity = relations.entities.find(e => e.name === rel.from);
1944 |             if (!sourceEntity) return null;
1945 |             return `- **${sourceEntity.name}** (${sourceEntity.entityType}) → ${rel.relationType} → ${entityName}`;
1946 |           }).filter(Boolean).join("\n") || "No incoming relations";
1947 |           
1948 |           const outgoingText = outgoingRelations.map(rel => {
1949 |             const targetEntity = relations.entities.find(e => e.name === rel.to);
1950 |             if (!targetEntity) return null;
1951 |             return `- **${entityName}** → ${rel.relationType} → **${targetEntity.name}** (${targetEntity.entityType})`;
1952 |           }).filter(Boolean).join("\n") || "No outgoing relations";
1953 |           
1954 |           // Format observations
1955 |           const observationsText = entity.observations.map(obs => `- ${obs}`).join("\n") || "No observations";
1956 |           
1957 |           contextMessage = `# Entity Context: ${entityName} (${entityType})
1958 | 
1959 | ## Observations
1960 | ${observationsText}
1961 | 
1962 | ## Incoming Relations
1963 | ${incomingText}
1964 | 
1965 | ## Outgoing Relations
1966 | ${outgoingText}`;
1967 |         }
1968 |         
1969 |         return {
1970 |           content: [{
1971 |             type: "text",
1972 |             text: contextMessage
1973 |           }]
1974 |         };
1975 |       } catch (error) {
1976 |         return {
1977 |           content: [{
1978 |             type: "text",
1979 |             text: JSON.stringify({ 
1980 |               success: false,
1981 |               error: error instanceof Error ? error.message : String(error)
1982 |             }, null, 2)
1983 |           }]
1984 |         };
1985 |       }
1986 |     }
1987 |   );
1988 | 
1989 |   // Helper function to process each stage of endsession
1990 |   async function processStage(params: {
1991 |     sessionId: string;
1992 |     stage: string;
1993 |     stageNumber: number;
1994 |     totalStages: number;
1995 |     analysis?: string;
1996 |     stageData?: any;
1997 |     nextStageNeeded: boolean;
1998 |     isRevision?: boolean;
1999 |     revisesStage?: number;
2000 |   }, previousStages: any[]): Promise<any> {
2001 |     // Process based on the stage
2002 |     switch (params.stage) {
2003 |       case "summary":
2004 |         // Process summary stage
2005 |         return {
2006 |           stage: "summary",
2007 |           stageNumber: params.stageNumber,
2008 |           analysis: params.analysis || "",
2009 |           stageData: params.stageData || { 
2010 |             summary: "",
2011 |             duration: "",
2012 |             course: ""
2013 |           },
2014 |           completed: !params.nextStageNeeded
2015 |         };
2016 |         
2017 |       case "conceptsLearned":
2018 |         // Process concepts learned stage
2019 |         return {
2020 |           stage: "conceptsLearned",
2021 |           stageNumber: params.stageNumber,
2022 |           analysis: params.analysis || "",
2023 |           stageData: params.stageData || { concepts: [] },
2024 |           completed: !params.nextStageNeeded
2025 |         };
2026 |         
2027 |       case "assignmentUpdates":
2028 |         // Process assignment updates stage
2029 |         return {
2030 |           stage: "assignmentUpdates",
2031 |           stageNumber: params.stageNumber,
2032 |           analysis: params.analysis || "",
2033 |           stageData: params.stageData || { updates: [] },
2034 |           completed: !params.nextStageNeeded
2035 |         };
2036 |         
2037 |       case "newConcepts":
2038 |         // Process new concepts stage
2039 |         return {
2040 |           stage: "newConcepts",
2041 |           stageNumber: params.stageNumber,
2042 |           analysis: params.analysis || "",
2043 |           stageData: params.stageData || { concepts: [] },
2044 |           completed: !params.nextStageNeeded
2045 |         };
2046 |         
2047 |       case "courseStatus":
2048 |         // Process course status stage
2049 |         return {
2050 |           stage: "courseStatus",
2051 |           stageNumber: params.stageNumber,
2052 |           analysis: params.analysis || "",
2053 |           stageData: params.stageData || { 
2054 |             courseStatus: "",
2055 |             courseObservation: ""
2056 |           },
2057 |           completed: !params.nextStageNeeded
2058 |         };
2059 |         
2060 |       case "assembly":
2061 |         // Final assembly stage - compile all arguments for end-session
2062 |         return {
2063 |           stage: "assembly",
2064 |           stageNumber: params.stageNumber,
2065 |           analysis: "Final assembly of end-session arguments",
2066 |           stageData: assembleEndSessionArgs(previousStages),
2067 |           completed: true
2068 |         };
2069 |         
2070 |       default:
2071 |         throw new Error(`Unknown stage: ${params.stage}`);
2072 |     }
2073 |   }
2074 | 
2075 |   // Helper function to assemble the final end-session arguments
2076 |   function assembleEndSessionArgs(stages: any[]): any {
2077 |     const summaryStage = stages.find(s => s.stage === "summary");
2078 |     const conceptsLearnedStage = stages.find(s => s.stage === "conceptsLearned");
2079 |     const assignmentUpdatesStage = stages.find(s => s.stage === "assignmentUpdates");
2080 |     const newConceptsStage = stages.find(s => s.stage === "newConcepts");
2081 |     const courseStatusStage = stages.find(s => s.stage === "courseStatus");
2082 |     
2083 |     return {
2084 |       summary: summaryStage?.stageData?.summary || "",
2085 |       duration: summaryStage?.stageData?.duration || "unknown",
2086 |       course: summaryStage?.stageData?.course || "",
2087 |       conceptsLearned: JSON.stringify(conceptsLearnedStage?.stageData?.concepts || []),
2088 |       assignmentUpdates: JSON.stringify(assignmentUpdatesStage?.stageData?.updates || []),
2089 |       courseStatus: courseStatusStage?.stageData?.courseStatus || "",
2090 |       courseObservation: courseStatusStage?.stageData?.courseObservation || "",
2091 |       newConcepts: JSON.stringify(newConceptsStage?.stageData?.concepts || [])
2092 |     };
2093 |   }
2094 | 
2095 |   /**
2096 |    * End session by processing all stages and recording the final results.
2097 |    * Only use this tool if the user asks for it.
2098 |    * 
2099 |    * Usage examples:
2100 |    * 
2101 |    * 1. Starting the end session process with the summary stage:
2102 |    * {
2103 |    *   "sessionId": "stu_1234567890_abc123",  // From startsession
2104 |    *   "stage": "summary",
2105 |    *   "stageNumber": 1,
2106 |    *   "totalStages": 6, 
2107 |    *   "analysis": "Analyzed progress on studying for the final exam",
2108 |    *   "stageData": {
2109 |    *     "summary": "Reviewed course materials and completed practice problems",
2110 |    *     "duration": "2 hours",
2111 |    *     "focus": "Data Structures"  // Course name
2112 |    *   },
2113 |    *   "nextStageNeeded": true,  // More stages coming
2114 |    *   "isRevision": false
2115 |    * }
2116 |    * 
2117 |    * 2. Middle stage for concepts:
2118 |    * {
2119 |    *   "sessionId": "stu_1234567890_abc123",
2120 |    *   "stage": "conceptsLearned",
2121 |    *   "stageNumber": 2,
2122 |    *   "totalStages": 6,
2123 |    *   "analysis": "Listed key concepts studied",
2124 |    *   "stageData": {
2125 |    *     "concepts": [
2126 |    *       "Balanced binary trees",
2127 |    *       "Red-black trees",
2128 |    *       "Tree traversal algorithms"
2129 |    *     ]
2130 |    *   },
2131 |    *   "nextStageNeeded": true,
2132 |    *   "isRevision": false
2133 |    * }
2134 |    * 
2135 |    * 3. Final assembly stage:
2136 |    * {
2137 |    *   "sessionId": "stu_1234567890_abc123",
2138 |    *   "stage": "assembly",
2139 |    *   "stageNumber": 6,
2140 |    *   "totalStages": 6,
2141 |    *   "nextStageNeeded": false,  // This completes the session
2142 |    *   "isRevision": false
2143 |    * }
2144 |    */
2145 |   server.tool(
2146 |     "endsession",
2147 |     toolDescriptions["endsession"],
2148 |     {
2149 |       sessionId: z.string().describe("The unique session identifier obtained from startsession"),
2150 |       stage: z.string().describe("Current stage of analysis: 'summary', 'conceptsLearned', 'assignmentProgress', 'questions', 'nextSteps', or 'assembly'"),
2151 |       stageNumber: z.number().int().positive().describe("The sequence number of the current stage (starts at 1)"),
2152 |       totalStages: z.number().int().positive().describe("Total number of stages in the workflow (typically 5 for standard workflow)"),
2153 |       analysis: z.string().optional().describe("Text analysis or observations for the current stage"),
2154 |       stageData: z.record(z.string(), z.any()).optional().describe(`Stage-specific data structure - format depends on the stage type:
2155 |       - For 'summary' stage: { summary: "Session summary text", duration: "2 hours", focus: "CourseName" }
2156 |       - For 'conceptsLearned' stage: { concepts: ["Concept A", "Concept B", "Concept C"] }
2157 |       - For 'assignmentProgress' stage: { assignments: [{ name: "Assignment1", status: "completed" }, { name: "Assignment2", status: "in_progress" }] }
2158 |       - For 'questions' stage: { questions: ["Question about topic X", "Question about concept Y"] }
2159 |       - For 'nextSteps' stage: { nextSteps: ["Review chapter 7", "Complete practice problems", "Attend office hours"] }
2160 |       - For 'assembly' stage: no stageData needed - automatic assembly of previous stages`),
2161 |       nextStageNeeded: z.boolean().describe("Whether additional stages are needed after this one (false for final stage)"),
2162 |       isRevision: z.boolean().optional().describe("Whether this is revising a previous stage"),
2163 |       revisesStage: z.number().int().positive().optional().describe("If revising, which stage number is being revised")
2164 |     },
2165 |     async (params) => {
2166 |       try {
2167 |         // Load session states from persistent storage
2168 |         const sessionStates = await loadSessionStates();
2169 |         
2170 |         // Validate session ID
2171 |         if (!sessionStates.has(params.sessionId)) {
2172 |           return {
2173 |             content: [{
2174 |               type: "text",
2175 |               text: JSON.stringify({ 
2176 |                 success: false,
2177 |                 error: `Session with ID ${params.sessionId} not found. Please start a new session with startsession.`
2178 |               }, null, 2)
2179 |             }]
2180 |           };
2181 |         }
2182 |         
2183 |         // Get or initialize session state
2184 |         let sessionState = sessionStates.get(params.sessionId) || [];
2185 |         
2186 |         // Process the current stage
2187 |         const stageResult = await processStage(params, sessionState);
2188 |         
2189 |         // Store updated state
2190 |         if (params.isRevision && params.revisesStage) {
2191 |           // Find the analysis stages in the session state
2192 |           const analysisStages = sessionState.filter(item => item.type === 'analysis_stage') || [];
2193 |           
2194 |           if (params.revisesStage <= analysisStages.length) {
2195 |             // Replace the revised stage
2196 |             analysisStages[params.revisesStage - 1] = {
2197 |               type: 'analysis_stage',
2198 |               ...stageResult
2199 |             };
2200 |           } else {
2201 |             // Add as a new stage
2202 |             analysisStages.push({
2203 |               type: 'analysis_stage',
2204 |               ...stageResult
2205 |             });
2206 |           }
2207 |           
2208 |           // Update the session state with the modified analysis stages
2209 |           sessionState = [
2210 |             ...sessionState.filter(item => item.type !== 'analysis_stage'),
2211 |             ...analysisStages
2212 |           ];
2213 |         } else {
2214 |           // Add new stage
2215 |           sessionState.push({
2216 |             type: 'analysis_stage',
2217 |             ...stageResult
2218 |           });
2219 |         }
2220 |         
2221 |         // Update in persistent storage
2222 |         sessionStates.set(params.sessionId, sessionState);
2223 |         await saveSessionStates(sessionStates);
2224 |         
2225 |         // Check if this is the final assembly stage and no more stages are needed
2226 |         if (params.stage === "assembly" && !params.nextStageNeeded) {
2227 |           // Get the assembled arguments
2228 |           const args = stageResult.stageData;
2229 |           
2230 |           try {
2231 |             // Parse arguments
2232 |             const summary = args.summary;
2233 |             const duration = args.duration;
2234 |             const course = args.course;
2235 |             const conceptsLearned = args.conceptsLearned ? JSON.parse(args.conceptsLearned) : [];
2236 |             const assignmentUpdates = args.assignmentUpdates ? JSON.parse(args.assignmentUpdates) : [];
2237 |             const courseStatus = args.courseStatus;
2238 |             const courseObservation = args.courseObservation;
2239 |             const newConcepts = args.newConcepts ? JSON.parse(args.newConcepts) : [];
2240 |             
2241 |             // Create concept entities for new concepts learned
2242 |             const timestamp = new Date().getTime();
2243 |             const conceptEntities = conceptsLearned.map((concept: string, i: number) => ({
2244 |               name: `Concept_${timestamp}_${i + 1}`,
2245 |               entityType: "concept" as EntityType,
2246 |               observations: [concept]
2247 |             }));
2248 |             
2249 |             if (conceptEntities.length > 0) {
2250 |               await knowledgeGraphManager.createEntities(conceptEntities);
2251 |               
2252 |               // Link concepts to course
2253 |               const conceptRelations = conceptEntities.map((concept: {name: string}) => ({
2254 |                 from: course,
2255 |                 to: concept.name,
2256 |                 relationType: "contains"
2257 |               }));
2258 |               
2259 |               await knowledgeGraphManager.createRelations(conceptRelations);
2260 |             }
2261 |             
2262 |             // Update assignment statuses using the relation-based approach
2263 |             for (const assignment of assignmentUpdates) {
2264 |               try {
2265 |                 // Map status values to standard status values
2266 |                 let statusValue;
2267 |                 switch (assignment.status.toLowerCase()) {
2268 |                   case "completed":
2269 |                   case "complete":
2270 |                   case "done":
2271 |                   case "finished":
2272 |                   case "submitted":
2273 |                     statusValue = "complete";
2274 |                     break;
2275 |                   case "in_progress":
2276 |                   case "started":
2277 |                   case "working":
2278 |                   case "active":
2279 |                     statusValue = "in_progress";
2280 |                     break;
2281 |                   default:
2282 |                     statusValue = "not_started";
2283 |                 }
2284 |                 
2285 |                 // Set the status using the new method
2286 |                 await knowledgeGraphManager.setEntityStatus(assignment.name, statusValue);
2287 |                 
2288 |                 // If completed, also create a 'resolves' relation to the course
2289 |                 if (statusValue === "complete") {
2290 |                   await knowledgeGraphManager.createRelations([{
2291 |                     from: course,
2292 |                     to: assignment.name,
2293 |                     relationType: "resolves"
2294 |                   }]);
2295 |                 }
2296 |               } catch (error) {
2297 |                 console.error(`Error updating status for assignment ${assignment.name}:`, error);
2298 |               }
2299 |             }
2300 |             
2301 |             // Update course status using the relation-based approach
2302 |             try {
2303 |               // Map course status to standard values
2304 |               let courseStatusValue;
2305 |               switch (courseStatus.toLowerCase()) {
2306 |                 case "completed":
2307 |                 case "complete":
2308 |                 case "done":
2309 |                 case "finished":
2310 |                   courseStatusValue = "complete";
2311 |                   break;
2312 |                 case "in_progress":
2313 |                 case "active":
2314 |                 case "current":
2315 |                   courseStatusValue = "in_progress";
2316 |                   break;
2317 |                 default:
2318 |                   courseStatusValue = "not_started";
2319 |               }
2320 |               
2321 |               // Set the course status using the new method
2322 |               await knowledgeGraphManager.setEntityStatus(course, courseStatusValue);
2323 |               
2324 |               // Add observation if provided
2325 |               if (courseObservation) {
2326 |                 await knowledgeGraphManager.addObservations(course, [courseObservation]);
2327 |               }
2328 |             } catch (error) {
2329 |               console.error(`Error updating status for course ${course}:`, error);
2330 |             }
2331 |             
2332 |             // Create new concept entities
2333 |             if (newConcepts && newConcepts.length > 0) {
2334 |               const newConceptEntities = newConcepts.map((concept: {name: string, description: string}) => ({
2335 |                 name: concept.name,
2336 |                 entityType: "concept" as EntityType,
2337 |                 observations: [concept.description]
2338 |               }));
2339 |               
2340 |               await knowledgeGraphManager.createEntities(newConceptEntities);
2341 |               
2342 |               // Link concepts to course
2343 |               const conceptRelations = newConceptEntities.map((concept: {name: string}) => ({
2344 |                 from: course,
2345 |                 to: concept.name,
2346 |                 relationType: "contains"
2347 |               }));
2348 |               
2349 |               await knowledgeGraphManager.createRelations(conceptRelations);
2350 |             }
2351 |             
2352 |             // Record session completion in persistent storage
2353 |             sessionState.push({
2354 |               type: 'session_completed',
2355 |               timestamp: new Date().toISOString(),
2356 |               summary: summary,
2357 |               course: course
2358 |             });
2359 |             
2360 |             sessionStates.set(params.sessionId, sessionState);
2361 |             await saveSessionStates(sessionStates);
2362 |             
2363 |             // Prepare the summary message
2364 |             const summaryMessage = `# Study Session Recorded
2365 | 
2366 | I've recorded your study session focusing on ${course}.
2367 | 
2368 | ## Concepts Learned
2369 | ${conceptsLearned.map((c: string) => `- ${c}`).join('\n') || "No specific concepts recorded."}
2370 | 
2371 | ## Assignment Updates
2372 | ${assignmentUpdates.map((a: {name: string, status: string}) => `- ${a.name}: ${a.status}`).join('\n') || "No assignment updates."}
2373 | 
2374 | ## Course Status
2375 | Course ${course} has been updated to: ${courseStatus}
2376 | 
2377 | ${newConcepts && newConcepts.length > 0 ? `## New Concepts Added
2378 | ${newConcepts.map((c: {name: string, description: string}) => `- ${c.name}: ${c.description}`).join('\n')}` : "No new concepts added."}
2379 | 
2380 | ## Session Summary
2381 | ${summary}
2382 | 
2383 | Would you like me to perform any additional updates to your student knowledge graph?`;
2384 |             
2385 |             // Return the final result with the session recorded message
2386 |             return {
2387 |               content: [{
2388 |                 type: "text",
2389 |                 text: JSON.stringify({
2390 |                   success: true,
2391 |                   stageCompleted: params.stage,
2392 |                   nextStageNeeded: false,
2393 |                   stageResult: stageResult,
2394 |                   sessionRecorded: true,
2395 |                   summaryMessage: summaryMessage
2396 |                 }, null, 2)
2397 |               }]
2398 |             };
2399 |           } catch (error) {
2400 |             return {
2401 |               content: [{
2402 |                 type: "text",
2403 |                 text: JSON.stringify({
2404 |                   success: false,
2405 |                   error: `Error recording study session: ${error instanceof Error ? error.message : String(error)}`
2406 |                 }, null, 2)
2407 |               }]
2408 |             };
2409 |           }
2410 |         } else {
2411 |           // This is not the final stage or more stages are needed
2412 |           // Return intermediate result
2413 |           return {
2414 |             content: [{
2415 |               type: "text",
2416 |               text: JSON.stringify({
2417 |                 success: true,
2418 |                 stageCompleted: params.stage,
2419 |                 nextStageNeeded: params.nextStageNeeded,
2420 |                 stageResult: stageResult,
2421 |                 endSessionArgs: params.stage === "assembly" ? stageResult.stageData : null
2422 |               }, null, 2)
2423 |             }]
2424 |           };
2425 |         }
2426 |       } catch (error) {
2427 |         return {
2428 |           content: [{
2429 |             type: "text",
2430 |             text: JSON.stringify({ 
2431 |               success: false,
2432 |               error: error instanceof Error ? error.message : String(error)
2433 |             }, null, 2)
2434 |           }]
2435 |         };
2436 |       }
2437 |     }
2438 |   );
2439 | 
2440 |   /**
2441 |    * Start a new study session. Returns session ID, recent study sessions, active courses, and upcoming deadlines.
2442 |    * The output allows the user to easily choose what to focus on and which specific context to load.
2443 |    */
2444 |   server.tool(
2445 |     "startsession",
2446 |     toolDescriptions["startsession"],
2447 |     {},
2448 |     async () => {
2449 |       try {
2450 |         // Generate a unique session ID
2451 |         const sessionId = generateSessionId();
2452 |         
2453 |         // Get the current active term
2454 |         const currentTerm = await getCurrentTerm();
2455 |         
2456 |         // Get recent sessions from persistent storage instead of entities
2457 |         const sessionStates = await loadSessionStates();
2458 | 
2459 |         // Initialize the session state
2460 |         sessionStates.set(sessionId, []);
2461 |         await saveSessionStates(sessionStates);
2462 |         
2463 |         // Convert sessions map to array, sort, and take most recent ones
2464 |         const recentSessions = Array.from(sessionStates.entries())
2465 |           .map(([id, stages]) => {
2466 |             // Extract summary data from the first stage (if it exists)
2467 |             const summaryStage = stages.find(s => s.stage === "summary");
2468 |             return {
2469 |               id,
2470 |               course: summaryStage?.stageData?.course || "Unknown course",
2471 |               summary: summaryStage?.stageData?.summary || "No summary available"
2472 |             };
2473 |           })
2474 |           .slice(0, 3); // Default to 3 recent sessions
2475 |         
2476 |         // Get active courses - look for courses with "in_progress" status
2477 |         const allCoursesQuery = await knowledgeGraphManager.searchNodes("entityType:course");
2478 |         const courses = [];
2479 |         
2480 |         // Filter courses with active status using the relation
2481 |         for (const course of allCoursesQuery.entities) {
2482 |           const status = await knowledgeGraphManager.getEntityStatus(course.name);
2483 |           if (status === "in_progress" || status === "active" || !status) {
2484 |             courses.push(course);
2485 |           }
2486 |         }
2487 |         
2488 |         // Get upcoming deadlines (default to 14 days)
2489 |         const deadlines = await knowledgeGraphManager.getUpcomingDeadlines(
2490 |           currentTerm || undefined, 
2491 |           undefined, 
2492 |           14
2493 |         );
2494 | 
2495 |         // Get recent concepts studied
2496 |         const recentConceptsQuery = await knowledgeGraphManager.searchNodes("entityType:concept");
2497 |         const recentConcepts = recentConceptsQuery.entities.slice(0, 5);
2498 |         
2499 |         // Prepare message content
2500 |         const coursesText = courses.map(async c => {
2501 |           const status = await knowledgeGraphManager.getEntityStatus(c.name) || "not_started";
2502 |           const priority = await knowledgeGraphManager.getEntityPriority(c.name);
2503 |           const priorityText = priority ? ` (Priority: ${priority})` : "";
2504 |           
2505 |           const preview = c.observations.length > 0 
2506 |             ? `${c.observations[0].substring(0, 60)}${c.observations[0].length > 60 ? '...' : ''}`
2507 |             : "No description";
2508 |             
2509 |           return `- **${c.name}** (Status: ${status}${priorityText}): ${preview}`;
2510 |         });
2511 |         
2512 |         const resolvedCoursesText = await Promise.all(coursesText);
2513 |         
2514 |         const sessionsText = recentSessions.map(s => {
2515 |           return `- ${s.course} - ${s.summary.substring(0, 60)}${s.summary.length > 60 ? '...' : ''}`;
2516 |         }).join("\n");
2517 |         
2518 |         const deadlinesText = deadlines.deadlines.map((d: any) => {
2519 |           const daysUntil = d.daysRemaining;
2520 |           return `- **${d.entity.name}** (${d.course.name}): Due in ${daysUntil} day${daysUntil !== 1 ? 's' : ''}`;
2521 |         }).join("\n");
2522 |         
2523 |         const conceptsText = recentConcepts.map(c => {
2524 |           const preview = c.observations.length > 0 
2525 |             ? `${c.observations[0].substring(0, 60)}${c.observations[0].length > 60 ? '...' : ''}`
2526 |             : "No description";
2527 |           return `- **${c.name}**: ${preview}`;
2528 |         }).join("\n");
2529 |         
2530 |         return {
2531 |           content: [{
2532 |             type: "text",
2533 |             text: `# Choose what to focus on in this session
2534 | 
2535 | ## Recent Study Sessions
2536 | ${sessionsText || "No recent sessions found."}
2537 | 
2538 | ## Current Courses
2539 | ${resolvedCoursesText.join("\n") || "No active courses found."}
2540 | 
2541 | ## Upcoming Deadlines (Next 14 Days)
2542 | ${deadlinesText || "No upcoming deadlines in the next 14 days."}
2543 | 
2544 | ## Recently Studied Concepts
2545 | ${conceptsText || "No recently studied concepts found."}
2546 | 
2547 | To load the context for a specific entity, use the \`loadcontext\` tool with the entity name and session ID - ${sessionId}`
2548 |           }]
2549 |         };
2550 |       } catch (error) {
2551 |         return {
2552 |           content: [{
2553 |             type: "text",
2554 |             text: JSON.stringify({ 
2555 |               success: false,
2556 |               error: error instanceof Error ? error.message : String(error)
2557 |             }, null, 2)
2558 |           }]
2559 |         };
2560 |       }
2561 |     }
2562 |   );
2563 | 
2564 |   /**
2565 |    * Create new entities, relations, and observations.
2566 |    */
2567 |   server.tool(
2568 |     "buildcontext",
2569 |     toolDescriptions["buildcontext"],
2570 |     {
2571 |       type: z.enum(["entities", "relations", "observations"]).describe("Type of creation operation: 'entities', 'relations', or 'observations'"),
2572 |       data: z.array(z.any()).describe("Data for the creation operation, structure varies by type but must be an array")
2573 |     },
2574 |     async ({ type, data }) => {
2575 |       try {
2576 |         let result;
2577 |         
2578 |         switch (type) {
2579 |           case "entities":
2580 |             // Validate entity types
2581 |             for (const entity of data) {
2582 |               validateEntityType(entity.entityType);
2583 |             }
2584 |             
2585 |             // Ensure entities match the Entity interface
2586 |             const typedEntities: Entity[] = data.map((e: any) => ({
2587 |               name: e.name,
2588 |               entityType: e.entityType,
2589 |               observations: e.observations
2590 |             }));
2591 |             result = await knowledgeGraphManager.createEntities(typedEntities);
2592 |             return {
2593 |               content: [{
2594 |                 type: "text",
2595 |                 text: JSON.stringify({ success: true, created: result }, null, 2)
2596 |               }]
2597 |             };
2598 |             
2599 |           case "relations":
2600 |             // Ensure relations match the Relation interface
2601 |             const typedRelations: Relation[] = data.map((r: any) => ({
2602 |               from: r.from,
2603 |               to: r.to,
2604 |               relationType: r.relationType
2605 |             }));
2606 |             result = await knowledgeGraphManager.createRelations(typedRelations);
2607 |             return {
2608 |               content: [{
2609 |                 type: "text",
2610 |                 text: JSON.stringify({ success: true, created: result }, null, 2)
2611 |               }]
2612 |             };
2613 |             
2614 |           case "observations":
2615 |             for (const item of data) {
2616 |               if (item.entityName && Array.isArray(item.contents)) {
2617 |                 await knowledgeGraphManager.addObservations(item.entityName, item.contents);
2618 |               }
2619 |             }
2620 |             return {
2621 |               content: [{
2622 |                 type: "text",
2623 |                 text: JSON.stringify({ success: true, message: "Added observations to entities" }, null, 2)
2624 |               }]
2625 |             };
2626 |             
2627 |           default:
2628 |             throw new Error(`Invalid type: ${type}. Must be 'entities', 'relations', or 'observations'.`);
2629 |         }
2630 |       } catch (error) {
2631 |         return {
2632 |           content: [{
2633 |             type: "text",
2634 |             text: JSON.stringify({ 
2635 |               success: false,
2636 |               error: error instanceof Error ? error.message : String(error)
2637 |             }, null, 2)
2638 |           }]
2639 |         };
2640 |       }
2641 |     }
2642 |   );
2643 |   
2644 |   /**
2645 |    * Delete entities, relations, or observations.
2646 |    */
2647 |   server.tool(
2648 |     "deletecontext",
2649 |     toolDescriptions["deletecontext"],
2650 |     {
2651 |       type: z.enum(["entities", "relations", "observations"]).describe("Type of deletion operation: 'entities', 'relations', or 'observations'"),
2652 |       data: z.array(z.any()).describe("Data for the deletion operation, structure varies by type but must be an array")
2653 |     },
2654 |     async ({ type, data }) => {
2655 |       try {
2656 |         switch (type) {
2657 |           case "entities":
2658 |             await knowledgeGraphManager.deleteEntities(data);
2659 |             return {
2660 |               content: [{
2661 |                 type: "text",
2662 |                 text: JSON.stringify({ success: true, message: `Deleted ${data.length} entities` }, null, 2)
2663 |               }]
2664 |             };
2665 |             
2666 |           case "relations":
2667 |             // Ensure relations match the Relation interface
2668 |             const typedRelations: Relation[] = data.map((r: any) => ({
2669 |               from: r.from,
2670 |               to: r.to,
2671 |               relationType: r.relationType
2672 |             }));
2673 |             await knowledgeGraphManager.deleteRelations(typedRelations);
2674 |             return {
2675 |               content: [{
2676 |                 type: "text",
2677 |                 text: JSON.stringify({ success: true, message: `Deleted ${data.length} relations` }, null, 2)
2678 |               }]
2679 |             };
2680 |             
2681 |           case "observations":
2682 |             // Ensure deletions match the required interface
2683 |             const typedDeletions: { entityName: string; observations: string[] }[] = data.map((d: any) => ({
2684 |               entityName: d.entityName,
2685 |               observations: d.observations
2686 |             }));
2687 |             await knowledgeGraphManager.deleteObservations(typedDeletions);
2688 |             return {
2689 |               content: [{
2690 |                 type: "text",
2691 |                 text: JSON.stringify({ success: true, message: `Deleted observations from ${data.length} entities` }, null, 2)
2692 |               }]
2693 |             };
2694 |             
2695 |           default:
2696 |             throw new Error(`Invalid type: ${type}. Must be 'entities', 'relations', or 'observations'.`);
2697 |         }
2698 |       } catch (error) {
2699 |         return {
2700 |           content: [{
2701 |             type: "text",
2702 |             text: JSON.stringify({ 
2703 |               success: false,
2704 |               error: error instanceof Error ? error.message : String(error)
2705 |             }, null, 2)
2706 |           }]
2707 |         };
2708 |       }
2709 |     }
2710 |   );
2711 |   
2712 |   /**
2713 |    * Get information about the knowledge graph, search for nodes, get course overview, get upcoming deadlines, get assignment status, get exam prep, find related concepts, track lecture notes, or get term overview.
2714 |    */
2715 |   server.tool(
2716 |     "advancedcontext",
2717 |     toolDescriptions["advancedcontext"],
2718 |     {
2719 |       type: z.enum(["graph", "search", "nodes", "course", "deadlines", "assignment", "exam", "concepts", "lecture", "term"]).describe("Type of get operation: 'graph', 'search', 'nodes', 'course', 'deadlines', 'assignment', 'exam', 'concepts', 'lecture', or 'term'"),
2720 |       params: z.record(z.string(), z.any()).describe("Parameters for the operation, structure varies by type")
2721 |     },
2722 |     async ({ type, params }) => {
2723 |       try {
2724 |         let result;
2725 |         
2726 |         switch (type) {
2727 |           case "graph":
2728 |             result = await knowledgeGraphManager.readGraph();
2729 |             return {
2730 |               content: [{
2731 |                 type: "text",
2732 |                 text: JSON.stringify({ success: true, graph: result }, null, 2)
2733 |               }]
2734 |             };
2735 |             
2736 |           case "search":
2737 |             result = await knowledgeGraphManager.searchNodes(params.query);
2738 |             return {
2739 |               content: [{
2740 |                 type: "text",
2741 |                 text: JSON.stringify({ success: true, results: result }, null, 2)
2742 |               }]
2743 |             };
2744 |             
2745 |           case "nodes":
2746 |             result = await knowledgeGraphManager.openNodes(params.names);
2747 |             return {
2748 |               content: [{
2749 |                 type: "text",
2750 |                 text: JSON.stringify({ success: true, nodes: result }, null, 2)
2751 |               }]
2752 |             };
2753 |             
2754 |           case "course":
2755 |             result = await knowledgeGraphManager.getCourseOverview(params.courseName);
2756 |             return {
2757 |               content: [{
2758 |                 type: "text",
2759 |                 text: JSON.stringify({ success: true, course: result }, null, 2)
2760 |               }]
2761 |             };
2762 |             
2763 |           case "deadlines":
2764 |             result = await knowledgeGraphManager.getUpcomingDeadlines(
2765 |               params.termName, 
2766 |               params.courseName, 
2767 |               params.daysAhead || 14
2768 |             );
2769 |             return {
2770 |               content: [{
2771 |                 type: "text",
2772 |                 text: JSON.stringify({ success: true, deadlines: result }, null, 2)
2773 |               }]
2774 |             };
2775 |             
2776 |           case "assignment":
2777 |             result = await knowledgeGraphManager.getAssignmentStatus(params.assignmentName);
2778 |             return {
2779 |               content: [{
2780 |                 type: "text",
2781 |                 text: JSON.stringify({ success: true, assignment: result }, null, 2)
2782 |               }]
2783 |             };
2784 |             
2785 |           case "exam":
2786 |             result = await knowledgeGraphManager.getExamPrep(params.examName);
2787 |             return {
2788 |               content: [{
2789 |                 type: "text",
2790 |                 text: JSON.stringify({ success: true, exam: result }, null, 2)
2791 |               }]
2792 |             };
2793 |             
2794 |           case "concepts":
2795 |             result = await knowledgeGraphManager.findRelatedConcepts(
2796 |               params.conceptName,
2797 |               params.depth || 1
2798 |             );
2799 |             return {
2800 |               content: [{
2801 |                 type: "text",
2802 |                 text: JSON.stringify({ success: true, concepts: result }, null, 2)
2803 |               }]
2804 |             };
2805 |             
2806 |           case "lecture":
2807 |             result = await knowledgeGraphManager.trackLectureNotes(params.courseName);
2808 |             return {
2809 |               content: [{
2810 |                 type: "text",
2811 |                 text: JSON.stringify({ success: true, lectures: result }, null, 2)
2812 |               }]
2813 |             };
2814 |             
2815 |           case "term":
2816 |             result = await knowledgeGraphManager.getTermOverview(params.termName);
2817 |             return {
2818 |               content: [{
2819 |                 type: "text",
2820 |                 text: JSON.stringify({ success: true, term: result }, null, 2)
2821 |               }]
2822 |             };
2823 |             
2824 |           default:
2825 |             throw new Error(`Invalid type: ${type}. Must be one of the supported get operation types.`);
2826 |         }
2827 |       } catch (error) {
2828 |         return {
2829 |           content: [{
2830 |             type: "text",
2831 |             text: JSON.stringify({ 
2832 |               success: false,
2833 |               error: error instanceof Error ? error.message : String(error)
2834 |             }, null, 2)
2835 |           }]
2836 |         };
2837 |       }
2838 |     }
2839 |   );
2840 | 
2841 |   // Start the server
2842 |   try {
2843 |     const transport = new StdioServerTransport();
2844 |     await server.connect(transport);
2845 |   } catch (error) {
2846 |     console.error("Error starting server:", error);
2847 |     process.exit(1);
2848 |   }
2849 | }
2850 | 
2851 | main().catch(error => {
2852 |   console.error('Fatal error:', error);
2853 |   process.exit(1);
2854 | }); 
2855 | 
2856 | // Export the KnowledgeGraphManager class for testing
2857 | export { KnowledgeGraphManager }; 
```
Page 6/13FirstPrevNextLast