project-mcp 3.2.1 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/tools/thoughts.js +119 -522
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-mcp",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.2",
|
|
4
4
|
"description": "Intent-based MCP server for project documentation search. Maps natural language queries to the right sources automatically—no configuration needed. The standard for AI agent documentation search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/tools/thoughts.js
CHANGED
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
* Thought processing tools.
|
|
3
3
|
* Handles: process_thoughts, list_thoughts, get_thought
|
|
4
4
|
*
|
|
5
|
-
* This tool
|
|
6
|
-
*
|
|
5
|
+
* This tool reads brain dump files and provides context for the LLM to analyze.
|
|
6
|
+
* The LLM does the natural language understanding - the tool just gathers data.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { readdir } from 'fs/promises';
|
|
10
|
-
import { THOUGHTS_TODOS_DIR,
|
|
11
|
-
import { readFile,
|
|
12
|
-
import {
|
|
13
|
-
import { loadAllTasks, getNextTaskId, getExistingTaskIds } from '../lib/tasks.js';
|
|
10
|
+
import { THOUGHTS_TODOS_DIR, PROJECT_DIR } from '../lib/constants.js';
|
|
11
|
+
import { readFile, join, fileExists, ensureThoughtsTodosDir, matter } from '../lib/files.js';
|
|
12
|
+
import { loadAllTasks } from '../lib/tasks.js';
|
|
14
13
|
import { loadAllFiles, getCachedFiles } from '../lib/search.js';
|
|
15
14
|
|
|
16
15
|
/**
|
|
@@ -19,20 +18,20 @@ import { loadAllFiles, getCachedFiles } from '../lib/search.js';
|
|
|
19
18
|
export const definitions = [
|
|
20
19
|
{
|
|
21
20
|
name: 'process_thoughts',
|
|
22
|
-
description: `
|
|
21
|
+
description: `Reads brain dump markdown files from .project/thoughts/todos/ and returns the content along with project context for analysis.
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
This tool gathers:
|
|
24
|
+
1. **Raw thought content** - The unstructured brain dump as written
|
|
25
|
+
2. **Project context** - Existing tasks, roadmap milestones, decisions for reference
|
|
26
|
+
3. **Task format guide** - The YAML structure for creating tasks
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
- Output properly formatted YAML tasks ready for creation
|
|
28
|
+
YOU (the LLM) should then analyze the content to:
|
|
29
|
+
- Understand the user's intent (explicit, shadow/underlying, practical)
|
|
30
|
+
- Identify logical task groupings (consolidate related items)
|
|
31
|
+
- Determine appropriate priorities based on context
|
|
32
|
+
- Create well-structured tasks using create_task
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
The tool does NOT automatically create tasks - it provides you with everything needed to make intelligent decisions about task creation.`,
|
|
36
35
|
inputSchema: {
|
|
37
36
|
type: 'object',
|
|
38
37
|
properties: {
|
|
@@ -43,26 +42,7 @@ Use this when you have messy notes or brain dumps that need to be converted into
|
|
|
43
42
|
},
|
|
44
43
|
project: {
|
|
45
44
|
type: 'string',
|
|
46
|
-
description:
|
|
47
|
-
'Project prefix for generated task IDs (e.g., "AUTH", "API"). Required for task creation.',
|
|
48
|
-
},
|
|
49
|
-
mode: {
|
|
50
|
-
type: 'string',
|
|
51
|
-
description:
|
|
52
|
-
'Processing mode: "analyze" (returns analysis without creating tasks), "create" (creates tasks directly), "preview" (shows what would be created). Default: "analyze".',
|
|
53
|
-
enum: ['analyze', 'create', 'preview'],
|
|
54
|
-
default: 'analyze',
|
|
55
|
-
},
|
|
56
|
-
default_owner: {
|
|
57
|
-
type: 'string',
|
|
58
|
-
description: 'Default owner for generated tasks. Default: "unassigned".',
|
|
59
|
-
default: 'unassigned',
|
|
60
|
-
},
|
|
61
|
-
include_context: {
|
|
62
|
-
type: 'boolean',
|
|
63
|
-
description:
|
|
64
|
-
'Include project context analysis (searches project docs for relevant info). Default: true.',
|
|
65
|
-
default: true,
|
|
45
|
+
description: 'Project prefix for task IDs when you create tasks (e.g., "AUTH", "API").',
|
|
66
46
|
},
|
|
67
47
|
},
|
|
68
48
|
required: ['project'],
|
|
@@ -71,14 +51,13 @@ Use this when you have messy notes or brain dumps that need to be converted into
|
|
|
71
51
|
{
|
|
72
52
|
name: 'list_thoughts',
|
|
73
53
|
description:
|
|
74
|
-
'Lists all thought files in the .project/thoughts/ directory structure. Shows available brain dump files organized by category
|
|
54
|
+
'Lists all thought files in the .project/thoughts/ directory structure. Shows available brain dump files organized by category.',
|
|
75
55
|
inputSchema: {
|
|
76
56
|
type: 'object',
|
|
77
57
|
properties: {
|
|
78
58
|
category: {
|
|
79
59
|
type: 'string',
|
|
80
|
-
description:
|
|
81
|
-
'Optional: Filter by thought category. Currently supported: "todos". More categories coming in the future.',
|
|
60
|
+
description: 'Optional: Filter by thought category. Currently supported: "todos".',
|
|
82
61
|
enum: ['todos', ''],
|
|
83
62
|
},
|
|
84
63
|
},
|
|
@@ -86,14 +65,13 @@ Use this when you have messy notes or brain dumps that need to be converted into
|
|
|
86
65
|
},
|
|
87
66
|
{
|
|
88
67
|
name: 'get_thought',
|
|
89
|
-
description:
|
|
90
|
-
'Reads a specific thought file and returns its raw content. Use this to review a brain dump before processing.',
|
|
68
|
+
description: 'Reads a specific thought file and returns its raw content for review.',
|
|
91
69
|
inputSchema: {
|
|
92
70
|
type: 'object',
|
|
93
71
|
properties: {
|
|
94
72
|
file: {
|
|
95
73
|
type: 'string',
|
|
96
|
-
description: 'The thought file to read (e.g., "my-ideas.md").
|
|
74
|
+
description: 'The thought file to read (e.g., "my-ideas.md").',
|
|
97
75
|
},
|
|
98
76
|
category: {
|
|
99
77
|
type: 'string',
|
|
@@ -107,256 +85,14 @@ Use this when you have messy notes or brain dumps that need to be converted into
|
|
|
107
85
|
];
|
|
108
86
|
|
|
109
87
|
/**
|
|
110
|
-
*
|
|
111
|
-
*/
|
|
112
|
-
const INTENT_MARKERS = {
|
|
113
|
-
// Explicit intent markers - direct statements of what to do
|
|
114
|
-
explicit: [
|
|
115
|
-
/\b(need to|have to|must|should|will|going to|want to|plan to)\b/i,
|
|
116
|
-
/\b(implement|create|build|add|fix|update|change|remove|delete)\b/i,
|
|
117
|
-
/\b(task|todo|action item|deliverable)\b/i,
|
|
118
|
-
],
|
|
119
|
-
// Shadow intent markers - underlying motivations
|
|
120
|
-
shadow: [
|
|
121
|
-
/\b(because|since|so that|in order to|to enable|to allow|to prevent)\b/i,
|
|
122
|
-
/\b(worried about|concerned|frustrated|annoying|painful|tedious)\b/i,
|
|
123
|
-
/\b(would be nice|could|might|maybe|possibly|eventually)\b/i,
|
|
124
|
-
/\b(users|customers|team|stakeholders)\s+(want|need|expect|complain)/i,
|
|
125
|
-
],
|
|
126
|
-
// Practical intent markers - concrete actions
|
|
127
|
-
practical: [
|
|
128
|
-
/\b(step \d+|first|then|next|finally|after that)\b/i,
|
|
129
|
-
/\b(file|function|class|module|component|api|endpoint|database)\b/i,
|
|
130
|
-
/\b(test|deploy|configure|setup|install|migrate)\b/i,
|
|
131
|
-
],
|
|
132
|
-
// Urgency markers
|
|
133
|
-
urgency: {
|
|
134
|
-
P0: [/\b(critical|blocker|urgent|asap|immediately|breaking|down|outage)\b/i],
|
|
135
|
-
P1: [/\b(important|high priority|soon|this week|pressing|significant)\b/i],
|
|
136
|
-
P2: [/\b(medium|normal|standard|regular|when possible)\b/i],
|
|
137
|
-
P3: [/\b(low priority|nice to have|eventually|someday|minor|trivial)\b/i],
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Extract todos from unstructured markdown content
|
|
143
|
-
* @param {string} content - Raw markdown content
|
|
144
|
-
* @returns {Array} Extracted todo items with metadata
|
|
145
|
-
*/
|
|
146
|
-
function extractTodosFromContent(content) {
|
|
147
|
-
const todos = [];
|
|
148
|
-
const lines = content.split('\n');
|
|
149
|
-
|
|
150
|
-
let currentContext = [];
|
|
151
|
-
let currentSection = null;
|
|
152
|
-
|
|
153
|
-
for (let i = 0; i < lines.length; i++) {
|
|
154
|
-
const line = lines[i];
|
|
155
|
-
const trimmed = line.trim();
|
|
156
|
-
|
|
157
|
-
// Track section headers for context
|
|
158
|
-
const headerMatch = trimmed.match(/^(#{1,6})\s+(.+)/);
|
|
159
|
-
if (headerMatch) {
|
|
160
|
-
currentSection = headerMatch[2];
|
|
161
|
-
currentContext = [];
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Skip empty lines but reset context after multiple empties
|
|
166
|
-
if (!trimmed) {
|
|
167
|
-
if (currentContext.length > 0 && lines[i - 1]?.trim() === '') {
|
|
168
|
-
currentContext = [];
|
|
169
|
-
}
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Check for explicit todo markers
|
|
174
|
-
const todoMatch =
|
|
175
|
-
trimmed.match(/^[-*]\s*\[[ x]\]\s*(.+)/) ||
|
|
176
|
-
trimmed.match(/^[-*]\s+(.+)/) ||
|
|
177
|
-
trimmed.match(/^(\d+)\.\s+(.+)/);
|
|
178
|
-
|
|
179
|
-
if (todoMatch) {
|
|
180
|
-
const text = todoMatch[2] || todoMatch[1];
|
|
181
|
-
if (text && text.length >= 5) {
|
|
182
|
-
todos.push({
|
|
183
|
-
raw: text.trim(),
|
|
184
|
-
section: currentSection,
|
|
185
|
-
context: [...currentContext],
|
|
186
|
-
lineNumber: i + 1,
|
|
187
|
-
isExplicitTodo: /^\[[ x]\]/.test(trimmed),
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
} else if (hasActionableIntent(trimmed)) {
|
|
191
|
-
// Lines with strong action intent even without list markers
|
|
192
|
-
todos.push({
|
|
193
|
-
raw: trimmed,
|
|
194
|
-
section: currentSection,
|
|
195
|
-
context: [...currentContext],
|
|
196
|
-
lineNumber: i + 1,
|
|
197
|
-
isExplicitTodo: false,
|
|
198
|
-
});
|
|
199
|
-
} else {
|
|
200
|
-
// Add to context for following items
|
|
201
|
-
currentContext.push(trimmed);
|
|
202
|
-
if (currentContext.length > 3) {
|
|
203
|
-
currentContext.shift();
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return todos;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Check if a line has actionable intent
|
|
213
|
-
* @param {string} line - Line to check
|
|
214
|
-
* @returns {boolean}
|
|
215
|
-
*/
|
|
216
|
-
function hasActionableIntent(line) {
|
|
217
|
-
// Must have at least one explicit intent marker
|
|
218
|
-
const hasExplicit = INTENT_MARKERS.explicit.some(rx => rx.test(line));
|
|
219
|
-
if (!hasExplicit) return false;
|
|
220
|
-
|
|
221
|
-
// Must be long enough to be meaningful
|
|
222
|
-
if (line.length < 15) return false;
|
|
223
|
-
|
|
224
|
-
// Must not be a question or observation
|
|
225
|
-
if (/^(what|how|why|when|where|who|is|are|was|were|do|does)\b/i.test(line)) return false;
|
|
226
|
-
if (line.endsWith('?')) return false;
|
|
227
|
-
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Analyze intent layers for a todo item
|
|
233
|
-
* @param {object} todo - Todo item with raw text and context
|
|
234
|
-
* @returns {object} Intent analysis
|
|
235
|
-
*/
|
|
236
|
-
function analyzeIntent(todo) {
|
|
237
|
-
const text = todo.raw;
|
|
238
|
-
const context = todo.context.join(' ');
|
|
239
|
-
const combined = `${text} ${context}`;
|
|
240
|
-
|
|
241
|
-
const analysis = {
|
|
242
|
-
explicit: null,
|
|
243
|
-
shadow: null,
|
|
244
|
-
practical: null,
|
|
245
|
-
priority: 'P2',
|
|
246
|
-
confidence: 0,
|
|
247
|
-
tags: [],
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
// Extract explicit intent - what they say they want
|
|
251
|
-
analysis.explicit = text;
|
|
252
|
-
|
|
253
|
-
// Extract shadow intent - why they want it
|
|
254
|
-
const shadowMatches = [];
|
|
255
|
-
INTENT_MARKERS.shadow.forEach(rx => {
|
|
256
|
-
const match = combined.match(rx);
|
|
257
|
-
if (match) {
|
|
258
|
-
// Get surrounding context
|
|
259
|
-
const idx = combined.indexOf(match[0]);
|
|
260
|
-
const start = Math.max(0, idx - 20);
|
|
261
|
-
const end = Math.min(combined.length, idx + match[0].length + 50);
|
|
262
|
-
shadowMatches.push(combined.substring(start, end).trim());
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
if (shadowMatches.length > 0) {
|
|
266
|
-
analysis.shadow = shadowMatches.join('; ');
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Extract practical intent - concrete actions
|
|
270
|
-
const practicalMatches = [];
|
|
271
|
-
INTENT_MARKERS.practical.forEach(rx => {
|
|
272
|
-
if (rx.test(combined)) {
|
|
273
|
-
practicalMatches.push(rx.source.replace(/\\b|\(|\)/g, ''));
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
if (practicalMatches.length > 0) {
|
|
277
|
-
analysis.practical = `Involves: ${practicalMatches.join(', ')}`;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Determine priority from urgency markers
|
|
281
|
-
for (const [priority, patterns] of Object.entries(INTENT_MARKERS.urgency)) {
|
|
282
|
-
if (patterns.some(rx => rx.test(combined))) {
|
|
283
|
-
analysis.priority = priority;
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Also check keyword-based priority
|
|
289
|
-
const textLower = combined.toLowerCase();
|
|
290
|
-
for (const [keyword, pri] of Object.entries(PRIORITY_KEYWORDS)) {
|
|
291
|
-
if (textLower.includes(keyword)) {
|
|
292
|
-
// Only upgrade priority, don't downgrade
|
|
293
|
-
const currentOrder = { P0: 0, P1: 1, P2: 2, P3: 3 };
|
|
294
|
-
if (currentOrder[pri] < currentOrder[analysis.priority]) {
|
|
295
|
-
analysis.priority = pri;
|
|
296
|
-
}
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Extract potential tags from brackets or hashtags
|
|
302
|
-
const tagMatches = text.match(/\[([^\]]+)\]/g) || [];
|
|
303
|
-
const hashTags = text.match(/#(\w+)/g) || [];
|
|
304
|
-
analysis.tags = [
|
|
305
|
-
...tagMatches.map(t => t.slice(1, -1).toLowerCase()),
|
|
306
|
-
...hashTags.map(t => t.slice(1).toLowerCase()),
|
|
307
|
-
];
|
|
308
|
-
|
|
309
|
-
// Calculate confidence based on markers found
|
|
310
|
-
let confidence = 0;
|
|
311
|
-
if (todo.isExplicitTodo) confidence += 40;
|
|
312
|
-
if (INTENT_MARKERS.explicit.some(rx => rx.test(text))) confidence += 30;
|
|
313
|
-
if (analysis.shadow) confidence += 15;
|
|
314
|
-
if (analysis.practical) confidence += 15;
|
|
315
|
-
analysis.confidence = Math.min(100, confidence);
|
|
316
|
-
|
|
317
|
-
return analysis;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Generate a clean title from raw todo text
|
|
322
|
-
* @param {string} raw - Raw todo text
|
|
323
|
-
* @returns {string} Clean title
|
|
324
|
-
*/
|
|
325
|
-
function generateTitle(raw) {
|
|
326
|
-
let title = raw
|
|
327
|
-
// Remove checkbox markers
|
|
328
|
-
.replace(/^\[[ x]\]\s*/, '')
|
|
329
|
-
// Remove tag brackets
|
|
330
|
-
.replace(/\[[^\]]+\]/g, '')
|
|
331
|
-
// Remove hashtags
|
|
332
|
-
.replace(/#\w+/g, '')
|
|
333
|
-
// Remove leading action words that are too generic
|
|
334
|
-
.replace(/^(need to|have to|must|should|will|want to)\s+/i, '')
|
|
335
|
-
// Clean up whitespace
|
|
336
|
-
.replace(/\s+/g, ' ')
|
|
337
|
-
.trim();
|
|
338
|
-
|
|
339
|
-
// Capitalize first letter
|
|
340
|
-
title = title.charAt(0).toUpperCase() + title.slice(1);
|
|
341
|
-
|
|
342
|
-
// Truncate if too long
|
|
343
|
-
if (title.length > 80) {
|
|
344
|
-
title = title.substring(0, 77) + '...';
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return title;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Get project context from existing docs and tasks
|
|
352
|
-
* @returns {Promise<object>} Project context summary
|
|
88
|
+
* Get project context for the LLM
|
|
353
89
|
*/
|
|
354
90
|
async function getProjectContext() {
|
|
355
91
|
const context = {
|
|
356
92
|
existingTasks: [],
|
|
357
|
-
|
|
93
|
+
roadmap: null,
|
|
358
94
|
decisions: [],
|
|
359
|
-
|
|
95
|
+
status: null,
|
|
360
96
|
};
|
|
361
97
|
|
|
362
98
|
try {
|
|
@@ -367,83 +103,43 @@ async function getProjectContext() {
|
|
|
367
103
|
title: t.title,
|
|
368
104
|
status: t.status,
|
|
369
105
|
priority: t.priority,
|
|
106
|
+
tags: t.tags || [],
|
|
370
107
|
}));
|
|
371
108
|
|
|
372
|
-
// Load project files
|
|
109
|
+
// Load project files
|
|
373
110
|
await loadAllFiles();
|
|
374
111
|
const files = getCachedFiles();
|
|
375
112
|
|
|
376
|
-
//
|
|
113
|
+
// Get roadmap content
|
|
377
114
|
const roadmapFile = files.find(f => f.path.includes('ROADMAP'));
|
|
378
115
|
if (roadmapFile) {
|
|
379
|
-
|
|
380
|
-
context.roadmapItems = milestones.map(m => m.replace('##', '').trim());
|
|
116
|
+
context.roadmap = roadmapFile.content.substring(0, 2000); // First 2000 chars
|
|
381
117
|
}
|
|
382
118
|
|
|
383
|
-
//
|
|
119
|
+
// Get decisions
|
|
384
120
|
const decisionsFile = files.find(f => f.path.includes('DECISIONS'));
|
|
385
121
|
if (decisionsFile) {
|
|
386
122
|
const adrs = decisionsFile.content.match(/## ADR-\d+: [^\n]+/g) || [];
|
|
387
123
|
context.decisions = adrs.map(a => a.replace('## ', ''));
|
|
388
124
|
}
|
|
389
125
|
|
|
390
|
-
//
|
|
391
|
-
const
|
|
392
|
-
if (
|
|
393
|
-
|
|
394
|
-
const todoCount = context.existingTasks.filter(t => t.status === 'todo').length;
|
|
395
|
-
parts.push(
|
|
396
|
-
`${context.existingTasks.length} existing tasks (${inProgress.length} in progress, ${todoCount} todo)`
|
|
397
|
-
);
|
|
126
|
+
// Get current status
|
|
127
|
+
const statusFile = files.find(f => f.path.includes('STATUS'));
|
|
128
|
+
if (statusFile) {
|
|
129
|
+
context.status = statusFile.content.substring(0, 1000); // First 1000 chars
|
|
398
130
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
if (context.decisions.length > 0) {
|
|
403
|
-
parts.push(`${context.decisions.length} architecture decisions`);
|
|
404
|
-
}
|
|
405
|
-
context.summary = parts.join(', ') || 'No existing project context found';
|
|
406
|
-
} catch (error) {
|
|
407
|
-
context.summary = 'Unable to load project context';
|
|
131
|
+
} catch {
|
|
132
|
+
// Context loading failed, continue without it
|
|
408
133
|
}
|
|
409
134
|
|
|
410
135
|
return context;
|
|
411
136
|
}
|
|
412
137
|
|
|
413
138
|
/**
|
|
414
|
-
*
|
|
415
|
-
* @param {string} title - Task title to check
|
|
416
|
-
* @param {Array} existingTasks - Existing tasks
|
|
417
|
-
* @returns {Array} Related tasks
|
|
418
|
-
*/
|
|
419
|
-
function findRelatedTasks(title, existingTasks) {
|
|
420
|
-
const titleWords = title
|
|
421
|
-
.toLowerCase()
|
|
422
|
-
.split(/\s+/)
|
|
423
|
-
.filter(w => w.length > 3);
|
|
424
|
-
const related = [];
|
|
425
|
-
|
|
426
|
-
for (const task of existingTasks) {
|
|
427
|
-
const taskTitle = task.title.toLowerCase();
|
|
428
|
-
const matchingWords = titleWords.filter(w => taskTitle.includes(w));
|
|
429
|
-
if (matchingWords.length >= 2 || matchingWords.length / titleWords.length > 0.5) {
|
|
430
|
-
related.push({
|
|
431
|
-
id: task.id,
|
|
432
|
-
title: task.title,
|
|
433
|
-
status: task.status,
|
|
434
|
-
similarity: matchingWords.length / titleWords.length,
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return related.sort((a, b) => b.similarity - a.similarity).slice(0, 3);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* Process thoughts handler
|
|
139
|
+
* Process thoughts handler - returns data for LLM analysis
|
|
444
140
|
*/
|
|
445
141
|
async function processThoughts(args) {
|
|
446
|
-
const { file, project
|
|
142
|
+
const { file, project } = args;
|
|
447
143
|
|
|
448
144
|
await ensureThoughtsTodosDir();
|
|
449
145
|
|
|
@@ -479,222 +175,123 @@ async function processThoughts(args) {
|
|
|
479
175
|
content: [
|
|
480
176
|
{
|
|
481
177
|
type: 'text',
|
|
482
|
-
text: `⚠️ No thought files found in \`.project/thoughts/todos/\`\n\nCreate markdown files with your brain dumps, then run this tool to process them
|
|
178
|
+
text: `⚠️ No thought files found in \`.project/thoughts/todos/\`\n\nCreate markdown files with your brain dumps, then run this tool to process them.`,
|
|
483
179
|
},
|
|
484
180
|
],
|
|
485
181
|
};
|
|
486
182
|
}
|
|
487
183
|
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
if (include_context) {
|
|
491
|
-
projectContext = await getProjectContext();
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Process each file
|
|
495
|
-
const allAnalyzedTodos = [];
|
|
496
|
-
const processedFiles = [];
|
|
497
|
-
|
|
184
|
+
// Read all thought files
|
|
185
|
+
const thoughtContents = [];
|
|
498
186
|
for (const thoughtFile of filesToProcess) {
|
|
499
187
|
const content = await readFile(thoughtFile.path, 'utf-8');
|
|
500
188
|
const parsed = matter(content);
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
// Analyze each todo
|
|
507
|
-
for (const todo of extractedTodos) {
|
|
508
|
-
const intent = analyzeIntent(todo);
|
|
509
|
-
const title = generateTitle(todo.raw);
|
|
510
|
-
|
|
511
|
-
// Skip if too low confidence
|
|
512
|
-
if (intent.confidence < 30) continue;
|
|
513
|
-
|
|
514
|
-
// Find related existing tasks
|
|
515
|
-
const related = projectContext ? findRelatedTasks(title, projectContext.existingTasks) : [];
|
|
516
|
-
|
|
517
|
-
allAnalyzedTodos.push({
|
|
518
|
-
sourceFile: thoughtFile.name,
|
|
519
|
-
lineNumber: todo.lineNumber,
|
|
520
|
-
section: todo.section,
|
|
521
|
-
raw: todo.raw,
|
|
522
|
-
title,
|
|
523
|
-
intent,
|
|
524
|
-
related,
|
|
525
|
-
taskData: {
|
|
526
|
-
title,
|
|
527
|
-
project: project.toUpperCase(),
|
|
528
|
-
priority: intent.priority,
|
|
529
|
-
status: 'todo',
|
|
530
|
-
owner: default_owner,
|
|
531
|
-
tags: intent.tags,
|
|
532
|
-
description: buildDescription(todo, intent),
|
|
533
|
-
},
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
processedFiles.push({
|
|
538
|
-
name: thoughtFile.name,
|
|
539
|
-
todosFound: extractedTodos.length,
|
|
540
|
-
todosKept: allAnalyzedTodos.filter(t => t.sourceFile === thoughtFile.name).length,
|
|
189
|
+
thoughtContents.push({
|
|
190
|
+
filename: thoughtFile.name,
|
|
191
|
+
content: parsed.content,
|
|
192
|
+
frontmatter: parsed.data,
|
|
541
193
|
});
|
|
542
194
|
}
|
|
543
195
|
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
result += `**Mode:** ${mode}\n`;
|
|
547
|
-
result += `**Project:** ${project.toUpperCase()}\n`;
|
|
548
|
-
result += `**Files Processed:** ${processedFiles.length}\n`;
|
|
549
|
-
result += `**Todos Extracted:** ${allAnalyzedTodos.length}\n`;
|
|
196
|
+
// Get project context
|
|
197
|
+
const projectContext = await getProjectContext();
|
|
550
198
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
199
|
+
// Build the response for the LLM
|
|
200
|
+
let result = `# Thought Processing Data
|
|
554
201
|
|
|
555
|
-
|
|
556
|
-
for (const pf of processedFiles) {
|
|
557
|
-
result += `- **${pf.name}**: ${pf.todosFound} items found, ${pf.todosKept} actionable\n`;
|
|
558
|
-
}
|
|
202
|
+
## Instructions for You (the LLM)
|
|
559
203
|
|
|
560
|
-
|
|
561
|
-
result += `\n⚠️ No actionable todos found. The content may not contain clear task items, or confidence was too low.\n`;
|
|
562
|
-
result += `\n**Tips:**\n`;
|
|
563
|
-
result += `- Use checkbox syntax: \`- [ ] Task description\`\n`;
|
|
564
|
-
result += `- Include action verbs: implement, create, fix, add, update\n`;
|
|
565
|
-
result += `- Add urgency markers: critical, urgent, important, soon\n`;
|
|
204
|
+
Analyze the thought content below and create appropriate tasks. Consider:
|
|
566
205
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
206
|
+
1. **Intent Analysis**
|
|
207
|
+
- **Explicit intent**: What does the user literally say they want?
|
|
208
|
+
- **Shadow intent**: What's the underlying motivation? Why do they want this?
|
|
209
|
+
- **Practical intent**: What concrete actions are needed?
|
|
571
210
|
|
|
572
|
-
|
|
211
|
+
2. **Task Consolidation**
|
|
212
|
+
- Group related items into single tasks with subtasks
|
|
213
|
+
- Don't create a separate task for every bullet point
|
|
214
|
+
- Section headers often indicate a logical task grouping
|
|
573
215
|
|
|
574
|
-
|
|
216
|
+
3. **Priority Assessment**
|
|
217
|
+
- P0: Critical/blocker/urgent - system down, security issue
|
|
218
|
+
- P1: High priority - important, needed soon
|
|
219
|
+
- P2: Medium (default) - normal work items
|
|
220
|
+
- P3: Low - nice to have, eventually
|
|
575
221
|
|
|
576
|
-
|
|
577
|
-
|
|
222
|
+
4. **Use \`create_task\` to create tasks** with this structure:
|
|
223
|
+
- title: Clear, actionable task title
|
|
224
|
+
- project: "${project.toUpperCase()}"
|
|
225
|
+
- description: Include context and subtasks
|
|
226
|
+
- priority: P0-P3 based on your analysis
|
|
227
|
+
- tags: Relevant categorization
|
|
228
|
+
- subtasks: Array of subtask strings (for consolidated items)
|
|
578
229
|
|
|
579
|
-
|
|
580
|
-
result += `**Source:** \`${todo.sourceFile}\` (line ${todo.lineNumber})\n`;
|
|
581
|
-
result += `**Priority:** ${todo.intent.priority} (confidence: ${todo.intent.confidence}%)\n`;
|
|
230
|
+
---
|
|
582
231
|
|
|
583
|
-
|
|
584
|
-
result += `**Section:** ${todo.section}\n`;
|
|
585
|
-
}
|
|
232
|
+
## Thought Files to Process
|
|
586
233
|
|
|
587
|
-
|
|
588
|
-
result += `- **Explicit:** ${todo.intent.explicit}\n`;
|
|
589
|
-
if (todo.intent.shadow) {
|
|
590
|
-
result += `- **Shadow (why):** ${todo.intent.shadow}\n`;
|
|
591
|
-
}
|
|
592
|
-
if (todo.intent.practical) {
|
|
593
|
-
result += `- **Practical:** ${todo.intent.practical}\n`;
|
|
594
|
-
}
|
|
234
|
+
`;
|
|
595
235
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
236
|
+
for (const thought of thoughtContents) {
|
|
237
|
+
result += `### File: ${thought.filename}\n\n`;
|
|
238
|
+
result += '```markdown\n';
|
|
239
|
+
result += thought.content;
|
|
240
|
+
result += '\n```\n\n';
|
|
241
|
+
}
|
|
599
242
|
|
|
600
|
-
|
|
601
|
-
result += `\n**⚠️ Potentially Related Tasks:**\n`;
|
|
602
|
-
for (const rel of todo.related) {
|
|
603
|
-
result += `- ${rel.id}: ${rel.title} (${rel.status})\n`;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
243
|
+
result += `---
|
|
606
244
|
|
|
607
|
-
|
|
608
|
-
result += `\n**Generated YAML:**\n`;
|
|
609
|
-
result += `\`\`\`yaml\n`;
|
|
610
|
-
result += `title: "${todo.taskData.title}"\n`;
|
|
611
|
-
result += `project: ${todo.taskData.project}\n`;
|
|
612
|
-
result += `priority: ${todo.taskData.priority}\n`;
|
|
613
|
-
result += `status: ${todo.taskData.status}\n`;
|
|
614
|
-
result += `owner: ${todo.taskData.owner}\n`;
|
|
615
|
-
if (todo.taskData.tags.length > 0) {
|
|
616
|
-
result += `tags: [${todo.taskData.tags.join(', ')}]\n`;
|
|
617
|
-
}
|
|
618
|
-
result += `\`\`\`\n\n`;
|
|
245
|
+
## Project Context
|
|
619
246
|
|
|
620
|
-
|
|
621
|
-
if (mode === 'create') {
|
|
622
|
-
try {
|
|
623
|
-
// Import create_task handler dynamically to avoid circular deps
|
|
624
|
-
const { handlers: taskHandlers } = await import('./tasks.js');
|
|
625
|
-
const createResult = await taskHandlers.create_task({
|
|
626
|
-
title: todo.taskData.title,
|
|
627
|
-
project: todo.taskData.project,
|
|
628
|
-
description: todo.taskData.description,
|
|
629
|
-
owner: todo.taskData.owner,
|
|
630
|
-
priority: todo.taskData.priority,
|
|
631
|
-
tags: todo.taskData.tags,
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
// Extract task ID from result
|
|
635
|
-
const idMatch = createResult.content[0].text.match(/\*\*([A-Z]+-\d+)\*\*/);
|
|
636
|
-
if (idMatch) {
|
|
637
|
-
createdTasks.push({
|
|
638
|
-
id: idMatch[1],
|
|
639
|
-
title: todo.taskData.title,
|
|
640
|
-
});
|
|
641
|
-
result += `✅ **Created:** ${idMatch[1]}\n\n`;
|
|
642
|
-
}
|
|
643
|
-
} catch (error) {
|
|
644
|
-
result += `❌ **Failed to create:** ${error.message}\n\n`;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
247
|
+
Use this to understand what already exists and align new tasks appropriately.
|
|
647
248
|
|
|
648
|
-
|
|
649
|
-
|
|
249
|
+
### Existing Tasks (${projectContext.existingTasks.length} total)
|
|
250
|
+
|
|
251
|
+
`;
|
|
650
252
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
result +=
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
result += `3. Run with \`mode: "create"\` to create the tasks\n`;
|
|
657
|
-
result += `\nOr use \`create_task\` manually for more control over individual tasks.\n`;
|
|
658
|
-
} else if (mode === 'create' && createdTasks.length > 0) {
|
|
659
|
-
result += `\n## Created Tasks Summary\n\n`;
|
|
660
|
-
result += `**${createdTasks.length} tasks created:**\n`;
|
|
661
|
-
for (const task of createdTasks) {
|
|
662
|
-
result += `- ${task.id}: ${task.title}\n`;
|
|
253
|
+
if (projectContext.existingTasks.length > 0) {
|
|
254
|
+
result += '| ID | Title | Status | Priority |\n';
|
|
255
|
+
result += '|----|-------|--------|----------|\n';
|
|
256
|
+
for (const task of projectContext.existingTasks.slice(0, 20)) {
|
|
257
|
+
result += `| ${task.id} | ${task.title.substring(0, 40)}${task.title.length > 40 ? '...' : ''} | ${task.status} | ${task.priority} |\n`;
|
|
663
258
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
259
|
+
if (projectContext.existingTasks.length > 20) {
|
|
260
|
+
result += `\n*...and ${projectContext.existingTasks.length - 20} more tasks*\n`;
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
result += '*No existing tasks*\n';
|
|
668
264
|
}
|
|
669
265
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
/**
|
|
676
|
-
* Build task description from todo and intent analysis
|
|
677
|
-
*/
|
|
678
|
-
function buildDescription(todo, intent) {
|
|
679
|
-
let desc = '';
|
|
266
|
+
if (projectContext.roadmap) {
|
|
267
|
+
result += `\n### Roadmap Overview\n\n`;
|
|
268
|
+
result += '```\n' + projectContext.roadmap + '\n```\n';
|
|
269
|
+
}
|
|
680
270
|
|
|
681
|
-
if (
|
|
682
|
-
|
|
271
|
+
if (projectContext.decisions.length > 0) {
|
|
272
|
+
result += `\n### Architecture Decisions\n\n`;
|
|
273
|
+
for (const decision of projectContext.decisions) {
|
|
274
|
+
result += `- ${decision}\n`;
|
|
275
|
+
}
|
|
683
276
|
}
|
|
684
277
|
|
|
685
|
-
|
|
278
|
+
result += `
|
|
279
|
+
---
|
|
686
280
|
|
|
687
|
-
|
|
688
|
-
desc += `**Context (why):**\n${intent.shadow}\n\n`;
|
|
689
|
-
}
|
|
281
|
+
## Your Task
|
|
690
282
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
283
|
+
Now analyze the thought content above and:
|
|
284
|
+
|
|
285
|
+
1. Identify the distinct tasks/initiatives (consolidate related items)
|
|
286
|
+
2. For each task, determine title, priority, and relevant context
|
|
287
|
+
3. Use \`create_task\` to create well-structured tasks
|
|
694
288
|
|
|
695
|
-
|
|
289
|
+
Remember: Quality over quantity. Create fewer, well-scoped tasks rather than many granular ones.
|
|
290
|
+
`;
|
|
696
291
|
|
|
697
|
-
return
|
|
292
|
+
return {
|
|
293
|
+
content: [{ type: 'text', text: result }],
|
|
294
|
+
};
|
|
698
295
|
}
|
|
699
296
|
|
|
700
297
|
/**
|
|
@@ -758,7 +355,7 @@ async function listThoughts(args) {
|
|
|
758
355
|
|
|
759
356
|
result += `---\n`;
|
|
760
357
|
result += `**Total files:** ${totalFiles}\n\n`;
|
|
761
|
-
result += `**Tools:** \`get_thought\` to read | \`process_thoughts\` to
|
|
358
|
+
result += `**Tools:** \`get_thought\` to read | \`process_thoughts\` to analyze`;
|
|
762
359
|
|
|
763
360
|
return {
|
|
764
361
|
content: [{ type: 'text', text: result }],
|
|
@@ -808,7 +405,7 @@ async function getThought(args) {
|
|
|
808
405
|
result += parsed.content;
|
|
809
406
|
|
|
810
407
|
result += `\n\n---\n`;
|
|
811
|
-
result += `**Tools:** \`process_thoughts\` to
|
|
408
|
+
result += `**Tools:** \`process_thoughts\` to analyze and create tasks`;
|
|
812
409
|
|
|
813
410
|
return {
|
|
814
411
|
content: [{ type: 'text', text: result }],
|