project-mcp 3.2.0 → 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 +77 -77
- package/src/lib/constants.js +2 -0
- package/src/lib/files.js +24 -2
- package/src/lib/search.js +2 -4
- package/src/lib/tasks.js +4 -10
- package/src/prompts/definitions.js +1 -1
- package/src/prompts/index.js +3 -6
- package/src/resources/index.js +3 -6
- package/src/server.js +1 -1
- package/src/tools/backlog.js +17 -34
- package/src/tools/index.js +3 -2
- package/src/tools/lint.js +6 -12
- package/src/tools/project-files.js +30 -85
- package/src/tools/search.js +16 -24
- package/src/tools/tasks.js +33 -47
- package/src/tools/thoughts.js +422 -0
package/package.json
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
2
|
+
"name": "project-mcp",
|
|
3
|
+
"version": "3.2.2",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"project-mcp": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js",
|
|
12
|
+
"test": "node --test test/*.test.js",
|
|
13
|
+
"lint": "node --check src/index.js",
|
|
14
|
+
"format": "prettier --write \"**/*.{js,md,json}\"",
|
|
15
|
+
"format:check": "prettier --check \"**/*.{js,md,json}\"",
|
|
16
|
+
"prepublishOnly": "npm run lint && npm test"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"model-context-protocol",
|
|
21
|
+
"mcp-server",
|
|
22
|
+
"documentation",
|
|
23
|
+
"documentation-search",
|
|
24
|
+
"docs-search",
|
|
25
|
+
"search",
|
|
26
|
+
"fuzzy-search",
|
|
27
|
+
"semantic-search",
|
|
28
|
+
"markdown",
|
|
29
|
+
"markdown-search",
|
|
30
|
+
"project-documentation",
|
|
31
|
+
"project-management",
|
|
32
|
+
"intent-based-search",
|
|
33
|
+
"intent-mapping",
|
|
34
|
+
"ai-agent",
|
|
35
|
+
"ai-assistant",
|
|
36
|
+
"claude",
|
|
37
|
+
"cursor",
|
|
38
|
+
"anthropic",
|
|
39
|
+
"ai-tools",
|
|
40
|
+
"developer-tools",
|
|
41
|
+
"documentation-tools",
|
|
42
|
+
"knowledge-base",
|
|
43
|
+
"project-knowledge",
|
|
44
|
+
"operational-truth",
|
|
45
|
+
"reference-documentation",
|
|
46
|
+
"fuse.js",
|
|
47
|
+
"natural-language",
|
|
48
|
+
"nlp",
|
|
49
|
+
"zero-config",
|
|
50
|
+
"automatic-indexing"
|
|
51
|
+
],
|
|
52
|
+
"author": "",
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/pouyanafisi/project-mcp.git"
|
|
57
|
+
},
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/pouyanafisi/project-mcp/issues"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/pouyanafisi/project-mcp#readme",
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=18.0.0"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
67
|
+
"fuse.js": "^7.0.0",
|
|
68
|
+
"gray-matter": "^4.0.3",
|
|
69
|
+
"mime-types": "^2.1.35"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"prettier": "^3.7.4"
|
|
73
|
+
},
|
|
74
|
+
"files": [
|
|
75
|
+
"src/",
|
|
76
|
+
"README.md",
|
|
77
|
+
"LICENSE"
|
|
78
|
+
]
|
|
79
79
|
}
|
package/src/lib/constants.js
CHANGED
|
@@ -12,6 +12,8 @@ export const PROJECT_DIR = join(PROJECT_ROOT, '.project');
|
|
|
12
12
|
export const TODOS_DIR = join(PROJECT_DIR, 'todos');
|
|
13
13
|
export const ARCHIVE_DIR = join(PROJECT_DIR, 'archive');
|
|
14
14
|
export const BACKLOG_FILE = join(PROJECT_DIR, 'BACKLOG.md');
|
|
15
|
+
export const THOUGHTS_DIR = join(PROJECT_DIR, 'thoughts');
|
|
16
|
+
export const THOUGHTS_TODOS_DIR = join(THOUGHTS_DIR, 'todos');
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Intent to source mapping.
|
package/src/lib/files.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { readFile, readdir, stat, writeFile, mkdir, unlink, rename } from 'fs/promises';
|
|
6
6
|
import { join, extname, basename } from 'path';
|
|
7
7
|
import matter from 'gray-matter';
|
|
8
|
-
import { PROJECT_DIR, TODOS_DIR, ARCHIVE_DIR } from './constants.js';
|
|
8
|
+
import { PROJECT_DIR, TODOS_DIR, ARCHIVE_DIR, THOUGHTS_DIR, THOUGHTS_TODOS_DIR } from './constants.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Ensure .project directory exists
|
|
@@ -40,6 +40,28 @@ export async function ensureArchiveDir() {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Ensure .project/thoughts directory exists
|
|
45
|
+
*/
|
|
46
|
+
export async function ensureThoughtsDir() {
|
|
47
|
+
try {
|
|
48
|
+
await mkdir(THOUGHTS_DIR, { recursive: true });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
// Directory might already exist
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Ensure .project/thoughts/todos directory exists
|
|
56
|
+
*/
|
|
57
|
+
export async function ensureThoughtsTodosDir() {
|
|
58
|
+
try {
|
|
59
|
+
await mkdir(THOUGHTS_TODOS_DIR, { recursive: true });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// Directory might already exist
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
43
65
|
/**
|
|
44
66
|
* Check if a file exists
|
|
45
67
|
* @param {string} filePath - Path to check
|
|
@@ -91,7 +113,7 @@ export function extractTitle(content) {
|
|
|
91
113
|
* @returns {string|null}
|
|
92
114
|
*/
|
|
93
115
|
export function extractDescription(content) {
|
|
94
|
-
const lines = content.split('\n').filter(
|
|
116
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
95
117
|
for (let i = 1; i < Math.min(5, lines.length); i++) {
|
|
96
118
|
const line = lines[i].trim();
|
|
97
119
|
if (line && !line.startsWith('#') && line.length > 20) {
|
package/src/lib/search.js
CHANGED
|
@@ -32,9 +32,7 @@ export function detectIntent(query, explicitIntent) {
|
|
|
32
32
|
const queryLower = query.toLowerCase();
|
|
33
33
|
|
|
34
34
|
// Check for operational keywords
|
|
35
|
-
if (
|
|
36
|
-
/\b(plan|plans|todo|todos|roadmap|status|operational|current state|decisions)\b/.test(queryLower)
|
|
37
|
-
) {
|
|
35
|
+
if (/\b(plan|plans|todo|todos|roadmap|status|operational|current state|decisions)\b/.test(queryLower)) {
|
|
38
36
|
return 'plan';
|
|
39
37
|
}
|
|
40
38
|
|
|
@@ -161,7 +159,7 @@ export async function searchFiles(query, sources, maxResults = 10) {
|
|
|
161
159
|
await loadAllFiles();
|
|
162
160
|
|
|
163
161
|
// Filter files by source
|
|
164
|
-
const filesToSearch = allFilesCache.filter(
|
|
162
|
+
const filesToSearch = allFilesCache.filter(file => sources.includes(file.source));
|
|
165
163
|
|
|
166
164
|
// Rebuild index with filtered files
|
|
167
165
|
const index = new Fuse(filesToSearch, {
|
package/src/lib/tasks.js
CHANGED
|
@@ -5,13 +5,7 @@
|
|
|
5
5
|
import { readFile, readdir } from 'fs/promises';
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
import matter from 'gray-matter';
|
|
8
|
-
import {
|
|
9
|
-
TODOS_DIR,
|
|
10
|
-
ARCHIVE_DIR,
|
|
11
|
-
BACKLOG_FILE,
|
|
12
|
-
PRIORITY_ORDER,
|
|
13
|
-
PRIORITY_KEYWORDS,
|
|
14
|
-
} from './constants.js';
|
|
8
|
+
import { TODOS_DIR, ARCHIVE_DIR, BACKLOG_FILE, PRIORITY_ORDER, PRIORITY_KEYWORDS } from './constants.js';
|
|
15
9
|
import { ensureTodosDir, fileExists } from './files.js';
|
|
16
10
|
|
|
17
11
|
/**
|
|
@@ -78,7 +72,7 @@ export async function loadAllTasks() {
|
|
|
78
72
|
*/
|
|
79
73
|
export function areDependenciesMet(task, allTasks) {
|
|
80
74
|
if (!task.depends_on || task.depends_on.length === 0) return true;
|
|
81
|
-
const taskMap = new Map(allTasks.map(
|
|
75
|
+
const taskMap = new Map(allTasks.map(t => [t.id, t]));
|
|
82
76
|
for (const depId of task.depends_on) {
|
|
83
77
|
const dep = taskMap.get(depId);
|
|
84
78
|
if (!dep || dep.status !== 'done') return false;
|
|
@@ -218,7 +212,7 @@ export function parseTasksFromContent(content, project, defaultPriority) {
|
|
|
218
212
|
|
|
219
213
|
if (isSubtask && currentParent) {
|
|
220
214
|
// Add as subtask to parent
|
|
221
|
-
const parent = tasks.find(
|
|
215
|
+
const parent = tasks.find(t => t.tempId === currentParent);
|
|
222
216
|
if (parent) {
|
|
223
217
|
parent.subtasks = parent.subtasks || [];
|
|
224
218
|
parent.subtasks.push(title);
|
|
@@ -239,7 +233,7 @@ export function parseTasksFromContent(content, project, defaultPriority) {
|
|
|
239
233
|
// Extract tags from brackets
|
|
240
234
|
const tagMatch = title.match(/\[([^\]]+)\]/g);
|
|
241
235
|
if (tagMatch) {
|
|
242
|
-
task.tags = tagMatch.map(
|
|
236
|
+
task.tags = tagMatch.map(t => t.slice(1, -1).toLowerCase());
|
|
243
237
|
task.title = title.replace(/\[[^\]]+\]/g, '').trim();
|
|
244
238
|
}
|
|
245
239
|
|
package/src/prompts/index.js
CHANGED
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
* Prompt handlers for the MCP server.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
ListPromptsRequestSchema,
|
|
7
|
-
GetPromptRequestSchema,
|
|
8
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
9
6
|
import { prompts, promptToolMapping } from './definitions.js';
|
|
10
7
|
|
|
11
8
|
/**
|
|
@@ -254,9 +251,9 @@ export function setupPrompts(server) {
|
|
|
254
251
|
}));
|
|
255
252
|
|
|
256
253
|
// Handle get prompt request
|
|
257
|
-
server.setRequestHandler(GetPromptRequestSchema, async
|
|
254
|
+
server.setRequestHandler(GetPromptRequestSchema, async request => {
|
|
258
255
|
const { name, arguments: args } = request.params;
|
|
259
|
-
const prompt = prompts.find(
|
|
256
|
+
const prompt = prompts.find(p => p.name === name);
|
|
260
257
|
|
|
261
258
|
if (!prompt) {
|
|
262
259
|
throw new Error(`Prompt not found: ${name}`);
|
package/src/resources/index.js
CHANGED
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
* Resource handlers for the MCP server.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
ListResourcesRequestSchema,
|
|
7
|
-
ReadResourceRequestSchema,
|
|
8
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
9
6
|
import { lookup } from 'mime-types';
|
|
10
7
|
import { PROJECT_ROOT } from '../lib/constants.js';
|
|
11
8
|
import { readFile, join } from '../lib/files.js';
|
|
@@ -19,7 +16,7 @@ async function listResources() {
|
|
|
19
16
|
await loadAllFiles();
|
|
20
17
|
const allFilesCache = getCachedFiles();
|
|
21
18
|
|
|
22
|
-
return allFilesCache.map(
|
|
19
|
+
return allFilesCache.map(doc => ({
|
|
23
20
|
uri: `project://${doc.path}`,
|
|
24
21
|
name: doc.title,
|
|
25
22
|
description: doc.description || `File: ${doc.path} [${doc.source}]`,
|
|
@@ -63,7 +60,7 @@ export function setupResources(server) {
|
|
|
63
60
|
resources: await listResources(),
|
|
64
61
|
}));
|
|
65
62
|
|
|
66
|
-
server.setRequestHandler(ReadResourceRequestSchema, async
|
|
63
|
+
server.setRequestHandler(ReadResourceRequestSchema, async request => {
|
|
67
64
|
const { uri } = request.params;
|
|
68
65
|
return await readResource(uri);
|
|
69
66
|
});
|
package/src/server.js
CHANGED
|
@@ -48,7 +48,7 @@ export class ProjectMCPServer {
|
|
|
48
48
|
tools: toolDefinitions,
|
|
49
49
|
}));
|
|
50
50
|
|
|
51
|
-
this.server.setRequestHandler(CallToolRequestSchema, async
|
|
51
|
+
this.server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
52
52
|
const { name, arguments: args } = request.params;
|
|
53
53
|
|
|
54
54
|
try {
|
package/src/tools/backlog.js
CHANGED
|
@@ -3,13 +3,7 @@
|
|
|
3
3
|
* Handles: import_tasks, promote_task, archive_task, add_to_backlog, get_backlog, update_backlog_item, remove_from_backlog, list_archived_tasks, unarchive_task
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
PROJECT_ROOT,
|
|
8
|
-
PROJECT_DIR,
|
|
9
|
-
TODOS_DIR,
|
|
10
|
-
ARCHIVE_DIR,
|
|
11
|
-
BACKLOG_FILE,
|
|
12
|
-
} from '../lib/constants.js';
|
|
6
|
+
import { PROJECT_ROOT, PROJECT_DIR, TODOS_DIR, ARCHIVE_DIR, BACKLOG_FILE } from '../lib/constants.js';
|
|
13
7
|
import {
|
|
14
8
|
readFile,
|
|
15
9
|
writeFile,
|
|
@@ -42,8 +36,7 @@ export const definitions = [
|
|
|
42
36
|
},
|
|
43
37
|
source_type: {
|
|
44
38
|
type: 'string',
|
|
45
|
-
description:
|
|
46
|
-
'Type of source: "file" (path to file) or "content" (raw markdown). Default: "file".',
|
|
39
|
+
description: 'Type of source: "file" (path to file) or "content" (raw markdown). Default: "file".',
|
|
47
40
|
enum: ['file', 'content'],
|
|
48
41
|
default: 'file',
|
|
49
42
|
},
|
|
@@ -63,8 +56,7 @@ export const definitions = [
|
|
|
63
56
|
},
|
|
64
57
|
dry_run: {
|
|
65
58
|
type: 'boolean',
|
|
66
|
-
description:
|
|
67
|
-
'If true, shows what would be imported without modifying BACKLOG.md. Default: false.',
|
|
59
|
+
description: 'If true, shows what would be imported without modifying BACKLOG.md. Default: false.',
|
|
68
60
|
default: false,
|
|
69
61
|
},
|
|
70
62
|
},
|
|
@@ -329,7 +321,7 @@ async function importTasks(args) {
|
|
|
329
321
|
|
|
330
322
|
// Filter by phase if specified
|
|
331
323
|
if (filterPhase) {
|
|
332
|
-
tasks = tasks.filter(
|
|
324
|
+
tasks = tasks.filter(t => t.phase && t.phase.toLowerCase().includes(filterPhase.toLowerCase()));
|
|
333
325
|
}
|
|
334
326
|
|
|
335
327
|
if (tasks.length === 0) {
|
|
@@ -452,10 +444,7 @@ updated: ${getISODate()}
|
|
|
452
444
|
}
|
|
453
445
|
|
|
454
446
|
// Update timestamp
|
|
455
|
-
backlogContent = backlogContent.replace(
|
|
456
|
-
/\*\*Last Updated:\*\* .*/,
|
|
457
|
-
`**Last Updated:** ${getCurrentDate()}`
|
|
458
|
-
);
|
|
447
|
+
backlogContent = backlogContent.replace(/\*\*Last Updated:\*\* .*/, `**Last Updated:** ${getCurrentDate()}`);
|
|
459
448
|
backlogContent = backlogContent.replace(/updated: .*/, `updated: ${getISODate()}`);
|
|
460
449
|
|
|
461
450
|
await writeFile(BACKLOG_FILE, backlogContent, 'utf-8');
|
|
@@ -523,7 +512,7 @@ async function promoteTask(args) {
|
|
|
523
512
|
}
|
|
524
513
|
|
|
525
514
|
const title = match[1].trim();
|
|
526
|
-
const tags = match[2] ? match[2].split(',').map(
|
|
515
|
+
const tags = match[2] ? match[2].split(',').map(t => t.trim()) : [];
|
|
527
516
|
const phase = match[3] || null;
|
|
528
517
|
|
|
529
518
|
// Detect priority from backlog section
|
|
@@ -720,10 +709,7 @@ updated: ${getISODate()}
|
|
|
720
709
|
}
|
|
721
710
|
|
|
722
711
|
// Update timestamp
|
|
723
|
-
backlogContent = backlogContent.replace(
|
|
724
|
-
/\*\*Last Updated:\*\* .*/,
|
|
725
|
-
`**Last Updated:** ${getCurrentDate()}`
|
|
726
|
-
);
|
|
712
|
+
backlogContent = backlogContent.replace(/\*\*Last Updated:\*\* .*/, `**Last Updated:** ${getCurrentDate()}`);
|
|
727
713
|
backlogContent = backlogContent.replace(/updated: .*/, `updated: ${getISODate()}`);
|
|
728
714
|
|
|
729
715
|
await writeFile(BACKLOG_FILE, backlogContent, 'utf-8');
|
|
@@ -763,15 +749,14 @@ async function getBacklog(args) {
|
|
|
763
749
|
|
|
764
750
|
// Parse backlog items
|
|
765
751
|
const items = [];
|
|
766
|
-
const itemRegex =
|
|
767
|
-
/^- \[([ x]|promoted)\] \*\*([A-Z]+-\d+)\*\*:\s*(.+?)(?:\s*\[([^\]]+)\])?(?:\s*\(([^)]+)\))?$/gm;
|
|
752
|
+
const itemRegex = /^- \[([ x]|promoted)\] \*\*([A-Z]+-\d+)\*\*:\s*(.+?)(?:\s*\[([^\]]+)\])?(?:\s*\(([^)]+)\))?$/gm;
|
|
768
753
|
let match;
|
|
769
754
|
|
|
770
755
|
while ((match = itemRegex.exec(backlogContent)) !== null) {
|
|
771
756
|
const status = match[1] === ' ' ? 'pending' : match[1] === 'x' ? 'done' : 'promoted';
|
|
772
757
|
const id = match[2];
|
|
773
758
|
const title = match[3].trim();
|
|
774
|
-
const tags = match[4] ? match[4].split(',').map(
|
|
759
|
+
const tags = match[4] ? match[4].split(',').map(t => t.trim()) : [];
|
|
775
760
|
const phase = match[5] || null;
|
|
776
761
|
|
|
777
762
|
// Detect priority from section
|
|
@@ -788,13 +773,13 @@ async function getBacklog(args) {
|
|
|
788
773
|
// Apply filters
|
|
789
774
|
let filtered = items;
|
|
790
775
|
if (!include_promoted) {
|
|
791
|
-
filtered = filtered.filter(
|
|
776
|
+
filtered = filtered.filter(i => i.status !== 'promoted');
|
|
792
777
|
}
|
|
793
778
|
if (priority) {
|
|
794
|
-
filtered = filtered.filter(
|
|
779
|
+
filtered = filtered.filter(i => i.priority === priority);
|
|
795
780
|
}
|
|
796
781
|
if (project) {
|
|
797
|
-
filtered = filtered.filter(
|
|
782
|
+
filtered = filtered.filter(i => i.id.startsWith(project.toUpperCase()));
|
|
798
783
|
}
|
|
799
784
|
|
|
800
785
|
// Group by priority
|
|
@@ -866,7 +851,7 @@ async function updateBacklogItem(args) {
|
|
|
866
851
|
}
|
|
867
852
|
|
|
868
853
|
const currentTitle = match[2].trim();
|
|
869
|
-
const currentTags = match[3] ? match[3].split(',').map(
|
|
854
|
+
const currentTags = match[3] ? match[3].split(',').map(t => t.trim()) : [];
|
|
870
855
|
const currentPhase = match[4] || null;
|
|
871
856
|
|
|
872
857
|
// Build new entry
|
|
@@ -919,7 +904,7 @@ async function updateBacklogItem(args) {
|
|
|
919
904
|
await writeFile(BACKLOG_FILE, backlog, 'utf-8');
|
|
920
905
|
|
|
921
906
|
let result = `## Updated Backlog Item: ${id}\n\n`;
|
|
922
|
-
result += `**Changes:**\n${changes.map(
|
|
907
|
+
result += `**Changes:**\n${changes.map(c => `- ${c}`).join('\n')}\n\n`;
|
|
923
908
|
result += `✅ BACKLOG.md updated.`;
|
|
924
909
|
|
|
925
910
|
return {
|
|
@@ -1000,12 +985,10 @@ async function listArchivedTasks(args) {
|
|
|
1000
985
|
};
|
|
1001
986
|
}
|
|
1002
987
|
|
|
1003
|
-
const mdFiles = files.filter(
|
|
988
|
+
const mdFiles = files.filter(f => f.endsWith('.md'));
|
|
1004
989
|
if (mdFiles.length === 0) {
|
|
1005
990
|
return {
|
|
1006
|
-
content: [
|
|
1007
|
-
{ type: 'text', text: `📦 Archive is empty. No completed tasks have been archived yet.` },
|
|
1008
|
-
],
|
|
991
|
+
content: [{ type: 'text', text: `📦 Archive is empty. No completed tasks have been archived yet.` }],
|
|
1009
992
|
};
|
|
1010
993
|
}
|
|
1011
994
|
|
|
@@ -1024,7 +1007,7 @@ async function listArchivedTasks(args) {
|
|
|
1024
1007
|
// Filter by project if specified
|
|
1025
1008
|
let filtered = tasks;
|
|
1026
1009
|
if (project) {
|
|
1027
|
-
filtered = filtered.filter(
|
|
1010
|
+
filtered = filtered.filter(t => t.project === project.toUpperCase());
|
|
1028
1011
|
}
|
|
1029
1012
|
|
|
1030
1013
|
// Sort by archived date (newest first)
|
package/src/tools/index.js
CHANGED
|
@@ -7,16 +7,17 @@ import * as projectFiles from './project-files.js';
|
|
|
7
7
|
import * as tasks from './tasks.js';
|
|
8
8
|
import * as backlog from './backlog.js';
|
|
9
9
|
import * as lint from './lint.js';
|
|
10
|
+
import * as thoughts from './thoughts.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* All tool modules
|
|
13
14
|
*/
|
|
14
|
-
const modules = [search, projectFiles, tasks, backlog, lint];
|
|
15
|
+
const modules = [search, projectFiles, tasks, backlog, lint, thoughts];
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Combined tool definitions
|
|
18
19
|
*/
|
|
19
|
-
export const definitions = modules.flatMap(
|
|
20
|
+
export const definitions = modules.flatMap(m => m.definitions);
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Combined handler map
|
package/src/tools/lint.js
CHANGED
|
@@ -3,13 +3,7 @@
|
|
|
3
3
|
* Handles: lint_project_docs, init_project
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
PROJECT_DIR,
|
|
8
|
-
TODOS_DIR,
|
|
9
|
-
ARCHIVE_DIR,
|
|
10
|
-
VALID_STATUSES,
|
|
11
|
-
VALID_PRIORITIES,
|
|
12
|
-
} from '../lib/constants.js';
|
|
6
|
+
import { PROJECT_DIR, TODOS_DIR, ARCHIVE_DIR, VALID_STATUSES, VALID_PRIORITIES } from '../lib/constants.js';
|
|
13
7
|
import {
|
|
14
8
|
readFile,
|
|
15
9
|
writeFile,
|
|
@@ -150,7 +144,7 @@ async function lintProjectDocs(args) {
|
|
|
150
144
|
// Check task files
|
|
151
145
|
if (scope === 'all' || scope === 'tasks') {
|
|
152
146
|
const tasks = await loadAllTasks();
|
|
153
|
-
const taskIds = new Set(tasks.map(
|
|
147
|
+
const taskIds = new Set(tasks.map(t => t.id));
|
|
154
148
|
|
|
155
149
|
// Required task fields
|
|
156
150
|
const requiredFields = ['id', 'title', 'project', 'status', 'priority'];
|
|
@@ -290,8 +284,8 @@ async function lintProjectDocs(args) {
|
|
|
290
284
|
// Check for orphaned tasks (done tasks that others depend on)
|
|
291
285
|
for (const task of tasks) {
|
|
292
286
|
if (task.status === 'done' && task.depends_on?.length > 0) {
|
|
293
|
-
const unresolvedDeps = task.depends_on.filter(
|
|
294
|
-
const dep = tasks.find(
|
|
287
|
+
const unresolvedDeps = task.depends_on.filter(depId => {
|
|
288
|
+
const dep = tasks.find(t => t.id === depId);
|
|
295
289
|
return dep && dep.status !== 'done';
|
|
296
290
|
});
|
|
297
291
|
if (unresolvedDeps.length > 0) {
|
|
@@ -306,8 +300,8 @@ async function lintProjectDocs(args) {
|
|
|
306
300
|
}
|
|
307
301
|
|
|
308
302
|
// Build result
|
|
309
|
-
const errorCount = issues.filter(
|
|
310
|
-
const warningCount = issues.filter(
|
|
303
|
+
const errorCount = issues.filter(i => i.type === 'error').length;
|
|
304
|
+
const warningCount = issues.filter(i => i.type === 'warning').length + warnings.length;
|
|
311
305
|
|
|
312
306
|
let result = `## Documentation Lint Results\n\n`;
|
|
313
307
|
result += `**Scope:** ${scope} | **Strict:** ${strict} | **Auto-fix:** ${fix}\n\n`;
|