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

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