@@ -60,6 +60,7 @@ interface XMindNode {
6060 callouts ?: {
6161 title : string ;
6262 } [ ] ;
63+ relationships ?: XMindRelationship [ ] ;
6364}
6465
6566interface XMindTopic {
@@ -87,6 +88,13 @@ interface XMindTopic {
8788 labels ?: string [ ] ;
8889}
8990
91+ interface XMindRelationship {
92+ id : string ;
93+ end1Id : string ;
94+ end2Id : string ;
95+ title ?: string ;
96+ }
97+
9098// Class XMindParser
9199class XMindParser {
92100 private filePath : string ;
@@ -120,8 +128,17 @@ class XMindParser {
120128 private parseContentJson ( jsonContent : string ) : Promise < XMindNode [ ] > {
121129 try {
122130 const content = JSON . parse ( jsonContent ) ;
123- const allNodes = content . map ( ( sheet : { rootTopic : XMindTopic ; title ?: string } ) => {
124- return this . processNode ( sheet . rootTopic , sheet . title || "Untitled Map" ) ;
131+ const allNodes = content . map ( ( sheet : {
132+ rootTopic : XMindTopic ;
133+ title ?: string ;
134+ relationships ?: XMindRelationship [ ] ;
135+ } ) => {
136+ const rootNode = this . processNode ( sheet . rootTopic , sheet . title || "Untitled Map" ) ;
137+ // Ajouter les relations au nœud racine
138+ if ( sheet . relationships ) {
139+ rootNode . relationships = sheet . relationships ;
140+ }
141+ return rootNode ;
125142 } ) ;
126143 return Promise . resolve ( allNodes ) ;
127144 } catch ( error ) {
@@ -180,71 +197,11 @@ function getNodePath(node: XMindNode, parents: string[] = []): string {
180197 return parents . length > 0 ? `${ parents . join ( ' > ' ) } > ${ node . title } ` : node . title ;
181198}
182199
183- interface TodoTask {
184- path: string ;
185- sheet: string ;
186- title: string ;
187- status: 'todo' | 'done' ;
188- context ?: {
189- children : {
190- title : string ;
191- subChildren ?: string [ ] ;
192- } [ ] ;
193- } ;
194- notes ?: {
195- content ?: string ;
196- } ;
197- labels ?: string [ ] ;
198- }
199-
200- function findTodoTasks ( node : XMindNode , parents : string [ ] = [ ] ) : TodoTask [ ] {
201- const todos : TodoTask [ ] = [ ] ;
202-
203- if ( node . taskStatus ) {
204- const task : TodoTask = {
205- path : getNodePath ( node , parents ) ,
206- sheet : node . sheetTitle || 'Untitled Map' ,
207- title : node . title ,
208- status : node . taskStatus
209- } ;
210-
211- // Add notes, callouts and labels
212- if ( node . notes ) task . notes = node . notes ;
213- if ( node . labels ) task . labels = node . labels ;
214-
215- // Add child nodes as context
216- if ( node . children && node . children . length > 0 ) {
217- task . context = {
218- children : node . children . map ( child => ( {
219- title : child . title ,
220- subChildren : child . children ?. map ( sc => sc . title )
221- } ) )
222- } ;
223- }
224-
225- todos . push ( task ) ;
226- }
227-
228- // Recursive search in children only (callouts are now part of notes)
229- if ( node . children ) {
230- const currentPath = [ ...parents , node . title ] ;
231- node . children . forEach ( child => {
232- todos . push ( ...findTodoTasks ( child , currentPath ) ) ;
233- } ) ;
234- }
235-
236- return todos ;
237- }
238-
239200// Schema definitions
240201const ReadXMindArgsSchema = z . object ( {
241202 path : z . string ( ) ,
242203} ) ;
243204
244- const GetTodoTasksArgsSchema = z . object ( {
245- path : z . string ( ) ,
246- } ) ;
247-
248205const ListXMindDirectoryArgsSchema = z . object ( {
249206 directory : z . string ( ) . optional ( ) ,
250207} ) ;
@@ -272,8 +229,9 @@ const ExtractNodeByIdArgsSchema = z.object({
272229const SearchNodesArgsSchema = z . object ( {
273230 path : z . string ( ) ,
274231 query : z . string ( ) ,
275- searchIn : z . array ( z . enum ( [ 'title' , 'notes' , 'labels' , 'callouts' ] ) ) . optional ( ) ,
232+ searchIn : z . array ( z . enum ( [ 'title' , 'notes' , 'labels' , 'callouts' , 'tasks' ] ) ) . optional ( ) ,
276233 caseSensitive : z . boolean ( ) . optional ( ) ,
234+ taskStatus : z . enum ( [ 'todo' , 'done' ] ) . optional ( ) , // Ajout du filtre de statut de tâche
277235} ) ;
278236
279237interface MultipleXMindResult {
@@ -458,6 +416,7 @@ interface NodeMatch {
458416 callouts ?: {
459417 title : string ;
460418 } [ ] ;
419+ taskStatus ?: 'todo' | 'done' ;
461420}
462421
463422interface SearchResult {
@@ -473,13 +432,14 @@ function searchNodes(
473432 query : string ,
474433 options : {
475434 searchIn ?: string [ ] ,
476- caseSensitive ?: boolean
435+ caseSensitive ? : boolean ,
436+ taskStatus ?: 'todo' | 'done'
477437 } = { } ,
478438 parents : string [ ] = [ ]
479439) : NodeMatch [ ] {
480440 const matches : NodeMatch [ ] = [ ] ;
481441 const searchQuery = options . caseSensitive ? query : query . toLowerCase ( ) ;
482- const searchFields = options . searchIn || [ 'title' , 'notes' , 'labels' , 'callouts' ] ;
442+ const searchFields = options . searchIn || [ 'title' , 'notes' , 'labels' , 'callouts' , 'tasks' ] ;
483443
484444 const matchedIn : string [ ] = [ ] ;
485445
@@ -490,6 +450,14 @@ function searchNodes(
490450 return searchIn . includes ( searchQuery ) ;
491451 } ;
492452
453+ // Vérification du statut de tâche si spécifié
454+ if ( options . taskStatus && node . taskStatus ) {
455+ if ( node . taskStatus !== options . taskStatus ) {
456+ // Si le statut ne correspond pas, ignorer ce nœud
457+ return [ ] ;
458+ }
459+ }
460+
493461 // Vérifier chaque champ configuré
494462 if ( searchFields . includes ( 'title' ) && matchesText ( node . title ) ) {
495463 matchedIn . push ( 'title' ) ;
@@ -503,9 +471,15 @@ function searchNodes(
503471 if ( searchFields . includes ( 'callouts' ) && node . callouts ?. some ( callout => matchesText ( callout . title ) ) ) {
504472 matchedIn . push ( 'callouts' ) ;
505473 }
474+ if ( searchFields . includes ( 'tasks' ) && node . taskStatus ) {
475+ matchedIn . push ( 'tasks' ) ;
476+ }
477+
478+ // Si on a trouvé des correspondances ou si c'est une tâche correspondante, ajouter ce nœud
479+ const shouldIncludeNode = matchedIn . length > 0 ||
480+ ( options . taskStatus && node . taskStatus === options . taskStatus ) ;
506481
507- // Si on a trouvé des correspondances, ajouter ce nœud
508- if ( matchedIn . length > 0 && node . id ) { // Vérifier que l'ID existe
482+ if ( shouldIncludeNode && node . id ) {
509483 matches . push ( {
510484 id : node . id ,
511485 title : node . title ,
@@ -514,7 +488,8 @@ function searchNodes(
514488 matchedIn,
515489 notes : node . notes ?. content ,
516490 labels : node . labels ,
517- callouts : node . callouts
491+ callouts : node . callouts ,
492+ taskStatus : node . taskStatus // Ajout du statut de tâche dans les résultats
518493 } ) ;
519494 }
520495
@@ -633,33 +608,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
633608 name : "read_xmind" ,
634609 description : `Parse and analyze XMind files with multiple capabilities:
635610 - Extract complete mind map structure in JSON format
636- - Generate text or markdown summaries of the entire map or specific nodes
637- - Search for specific content using keywords or regular expressions
638- - Extract relationships between nodes
611+ - Include all relationships between nodes with their IDs and titles
612+ - Extract callouts attached to topics
613+ - Generate text or markdown summaries
614+ - Search for specific content
639615 - Get hierarchical path to any node
640616 - Filter content by labels, task status, or node depth
641617 - Extract all URLs and external references
642- - Generate outline view of the mind map
643- - Count nodes, tasks, and get map statistics
618+ - Analyze relationships and connections between topics
644619 Input: File path to .xmind file
645- Output: JSON structure or formatted text based on query parameters ` ,
620+ Output: JSON structure containing nodes, relationships, and callouts ` ,
646621 inputSchema : zodToJsonSchema ( ReadXMindArgsSchema ) ,
647622 } ,
648- {
649- name : "get_todo_tasks" ,
650- description : `Advanced task management and analysis tool for XMind files:
651- - Extract all tasks marked as TODO with their full context path
652- - Group tasks by priority, labels, or categories
653- - Find dependencies between tasks
654- - Calculate task completion statistics
655- - Identify task bottlenecks in projects
656- - Extract deadlines and timeline information
657- - Generate task reports in various formats
658- - Track task status changes
659- Input: File path to .xmind file
660- Output: Structured list of tasks with contextual information` ,
661- inputSchema : zodToJsonSchema ( GetTodoTasksArgsSchema ) ,
662- } ,
663623 {
664624 name : "list_xmind_directory" ,
665625 description : `Comprehensive XMind file discovery and analysis tool:
@@ -747,20 +707,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
747707 {
748708 name : "search_nodes" ,
749709 description : `Advanced node search with multiple criteria:
750- - Search through titles, notes, and labels
710+ - Search through titles, notes, labels, callouts and tasks
711+ - Filter by task status (todo/done)
712+ - Find nodes by their relationships
751713 - Configure which fields to search in
752714 - Case-sensitive or insensitive search
753- - Get full context including path and sheet
715+ - Get full context including task status
754716 - Returns all matching nodes with their IDs
755- - Includes match location information
756- Note: Use "extract_node" with the found path to get detailed node content and full context
717+ - Includes relationship information and task status
757718 Input: {
758719 path: Path to .xmind file,
759720 query: Search text,
760- searchIn: Array of fields to search in ['title', 'notes', 'labels', 'callouts'],
721+ searchIn: Array of fields to search in ['title', 'notes', 'labels', 'callouts', 'tasks'],
722+ taskStatus: 'todo' | 'done' (optional),
761723 caseSensitive: Boolean (optional)
762724 }
763- Output: Detailed search results with context` ,
725+ Output: Detailed search results with task status and context` ,
764726 inputSchema : zodToJsonSchema ( SearchNodesArgsSchema ) ,
765727 } ,
766728 ] ,
@@ -787,22 +749,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
787749 } ;
788750 }
789751
790- case "get_todo_tasks" : {
791- const parsed = GetTodoTasksArgsSchema . safeParse ( args ) ;
792- if ( ! parsed . success ) {
793- throw new Error ( `Invalid arguments for get_todo_tasks: ${ parsed . error } ` ) ;
794- }
795- if ( ! isPathAllowed ( parsed . data . path ) ) {
796- throw new Error ( `Access denied: ${ parsed . data . path } is not in an allowed directory` ) ;
797- }
798- const parser = new XMindParser ( parsed . data . path ) ;
799- const mindmap = await parser . parse ( ) ;
800- const todos = mindmap . flatMap ( node => findTodoTasks ( node , [ ] ) ) ;
801- return {
802- content : [ { type : "text" , text : JSON . stringify ( todos , null , 2 ) } ] ,
803- } ;
804- }
805-
806752 case "list_xmind_directory" : {
807753 const parsed = ListXMindDirectoryArgsSchema . safeParse ( args ) ;
808754 if ( ! parsed . success ) {
@@ -906,15 +852,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
906852 const matches : NodeMatch [ ] = mindmap . flatMap ( sheet =>
907853 searchNodes ( sheet , parsed . data . query , {
908854 searchIn : parsed . data . searchIn ,
909- caseSensitive : parsed . data . caseSensitive
855+ caseSensitive : parsed . data . caseSensitive ,
856+ taskStatus : parsed . data . taskStatus
910857 } )
911858 ) ;
912859
913860 const result : SearchResult = {
914861 query : parsed . data . query ,
915862 matches,
916863 totalMatches : matches . length ,
917- searchedIn : parsed . data . searchIn || [ 'title' , 'notes' , 'labels' ]
864+ searchedIn : parsed . data . searchIn || [ 'title' , 'notes' , 'labels' , 'callouts' , 'tasks' ]
918865 } ;
919866
920867 return {
0 commit comments