project-mcp 3.2.2 → 3.3.0
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/lib/constants.js +10 -0
- package/src/lib/files.js +19 -1
- package/src/lib/search.js +39 -5
- package/src/prompts/definitions.js +20 -0
- package/src/prompts/index.js +27 -0
- package/src/tools/lint.js +32 -22
- package/src/tools/search.js +14 -3
- package/src/tools/thoughts.js +278 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
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/lib/constants.js
CHANGED
|
@@ -14,19 +14,29 @@ export const ARCHIVE_DIR = join(PROJECT_DIR, 'archive');
|
|
|
14
14
|
export const BACKLOG_FILE = join(PROJECT_DIR, 'BACKLOG.md');
|
|
15
15
|
export const THOUGHTS_DIR = join(PROJECT_DIR, 'thoughts');
|
|
16
16
|
export const THOUGHTS_TODOS_DIR = join(THOUGHTS_DIR, 'todos');
|
|
17
|
+
export const THOUGHTS_ARCHIVE_DIR = join(THOUGHTS_TODOS_DIR, '.archive');
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Intent to source mapping.
|
|
20
21
|
* Maps user intent to which directories should be searched.
|
|
22
|
+
*
|
|
23
|
+
* IMPORTANT DISTINCTION:
|
|
24
|
+
* - "project" (operational) → .project/ management files (status, todos, roadmap, backlog)
|
|
25
|
+
* - "project_docs" (documentation) → docs/ folder + DECISIONS.md (application documentation)
|
|
26
|
+
*
|
|
27
|
+
* When users say "project docs" or "project documentation", they mean APPLICATION
|
|
28
|
+
* documentation (how the system works), NOT project management (tracking work).
|
|
21
29
|
*/
|
|
22
30
|
export const INTENT_SOURCES = {
|
|
23
31
|
project: ['project', 'root', 'docs'], // .project/, root files, docs/
|
|
24
32
|
docs: ['docs'], // Only docs/
|
|
33
|
+
project_docs: ['docs', 'decisions'], // Application documentation: docs/ + DECISIONS.md
|
|
25
34
|
plan: ['project'], // Only .project/
|
|
26
35
|
todos: ['project'],
|
|
27
36
|
roadmap: ['project'],
|
|
28
37
|
status: ['project'],
|
|
29
38
|
operational: ['project'],
|
|
39
|
+
decisions: ['decisions'], // Architecture decisions only (DECISIONS.md)
|
|
30
40
|
};
|
|
31
41
|
|
|
32
42
|
/**
|
package/src/lib/files.js
CHANGED
|
@@ -5,7 +5,14 @@
|
|
|
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 {
|
|
8
|
+
import {
|
|
9
|
+
PROJECT_DIR,
|
|
10
|
+
TODOS_DIR,
|
|
11
|
+
ARCHIVE_DIR,
|
|
12
|
+
THOUGHTS_DIR,
|
|
13
|
+
THOUGHTS_TODOS_DIR,
|
|
14
|
+
THOUGHTS_ARCHIVE_DIR,
|
|
15
|
+
} from './constants.js';
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* Ensure .project directory exists
|
|
@@ -62,6 +69,17 @@ export async function ensureThoughtsTodosDir() {
|
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Ensure .project/thoughts/todos/.archive directory exists
|
|
74
|
+
*/
|
|
75
|
+
export async function ensureThoughtsArchiveDir() {
|
|
76
|
+
try {
|
|
77
|
+
await mkdir(THOUGHTS_ARCHIVE_DIR, { recursive: true });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// Directory might already exist
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
65
83
|
/**
|
|
66
84
|
* Check if a file exists
|
|
67
85
|
* @param {string} filePath - Path to check
|
package/src/lib/search.js
CHANGED
|
@@ -31,12 +31,28 @@ export function detectIntent(query, explicitIntent) {
|
|
|
31
31
|
|
|
32
32
|
const queryLower = query.toLowerCase();
|
|
33
33
|
|
|
34
|
-
// Check for
|
|
35
|
-
|
|
34
|
+
// Check for "project docs/documents/documentation" - this means APPLICATION documentation
|
|
35
|
+
// NOT project management. Routes to docs/ folder + DECISIONS.md
|
|
36
|
+
if (
|
|
37
|
+
/\b(project\s+doc(s|ument(s|ation)?)?|update\s+(project\s+)?doc(s|ument(s|ation)?)?|application\s+doc(s|ument(s|ation)?)?)\b/.test(
|
|
38
|
+
queryLower
|
|
39
|
+
)
|
|
40
|
+
) {
|
|
41
|
+
return 'project_docs';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check for architecture decisions specifically
|
|
45
|
+
if (/\b(decision(s)?|adr|architecture\s+decision(s)?|technical\s+decision(s)?)\b/.test(queryLower)) {
|
|
46
|
+
return 'decisions';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check for operational/project management keywords (status, todos, roadmap, backlog)
|
|
50
|
+
// This is DIFFERENT from "project docs" - this is about tracking work, not documenting the system
|
|
51
|
+
if (/\b(plan|plans|todo|todos|roadmap|status|operational|current state|backlog)\b/.test(queryLower)) {
|
|
36
52
|
return 'plan';
|
|
37
53
|
}
|
|
38
54
|
|
|
39
|
-
// Check for docs-
|
|
55
|
+
// Check for docs-only keywords (just "docs" or "documentation" without "project")
|
|
40
56
|
if (/\b(docs|documentation|reference|guide|guides|api docs)\b/.test(queryLower)) {
|
|
41
57
|
return 'docs';
|
|
42
58
|
}
|
|
@@ -51,7 +67,12 @@ export function detectIntent(query, explicitIntent) {
|
|
|
51
67
|
* @returns {string[]} Array of source names
|
|
52
68
|
*/
|
|
53
69
|
export function getSourcesForIntent(intent) {
|
|
54
|
-
|
|
70
|
+
const sources = INTENT_SOURCES[intent] || INTENT_SOURCES.project;
|
|
71
|
+
|
|
72
|
+
// 'decisions' is a virtual source that maps to DECISIONS.md in .project/
|
|
73
|
+
// When we see 'decisions' in the sources, we need to include it for filtering
|
|
74
|
+
// The actual loading happens in loadAllFiles() which tags DECISIONS.md with source='decisions'
|
|
75
|
+
return sources;
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
/**
|
|
@@ -67,8 +88,21 @@ export async function loadAllFiles(force = false) {
|
|
|
67
88
|
const allFiles = [];
|
|
68
89
|
|
|
69
90
|
// Load .project/ directory
|
|
91
|
+
// DECISIONS.md gets special treatment - it's tagged as both 'project' and 'decisions'
|
|
92
|
+
// because it's application documentation (explains WHY the system is built this way)
|
|
93
|
+
// not just project management
|
|
70
94
|
try {
|
|
71
|
-
|
|
95
|
+
const projectFiles = [];
|
|
96
|
+
await scanDirectory(PROJECT_DIR, '.project', projectFiles, 'project');
|
|
97
|
+
|
|
98
|
+
// Tag DECISIONS.md with source='decisions' so it appears in project_docs queries
|
|
99
|
+
for (const file of projectFiles) {
|
|
100
|
+
if (file.path.endsWith('DECISIONS.md')) {
|
|
101
|
+
// DECISIONS.md is application documentation, not just project management
|
|
102
|
+
file.source = 'decisions';
|
|
103
|
+
}
|
|
104
|
+
allFiles.push(file);
|
|
105
|
+
}
|
|
72
106
|
} catch (error) {
|
|
73
107
|
// .project/ might not exist
|
|
74
108
|
}
|
|
@@ -24,6 +24,7 @@ export const promptToolMapping = {
|
|
|
24
24
|
get_backlog: ['get_backlog'],
|
|
25
25
|
add_decision: ['add_decision'],
|
|
26
26
|
update_status: ['update_project_status'],
|
|
27
|
+
update_project_docs: ['search_project', 'get_doc', 'add_decision', 'list_docs'],
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -204,6 +205,25 @@ export const prompts = [
|
|
|
204
205
|
},
|
|
205
206
|
],
|
|
206
207
|
},
|
|
208
|
+
{
|
|
209
|
+
name: 'update_project_docs',
|
|
210
|
+
description:
|
|
211
|
+
'Update project documentation - the APPLICATION documentation that explains how the system works. Use when user says "update project docs", "update project documents", "update project documentation", "update application docs", or "document this". This is DIFFERENT from project management (status, todos, roadmap) - this updates the docs/ folder and DECISIONS.md which contain reference documentation about the application itself.',
|
|
212
|
+
arguments: [
|
|
213
|
+
{
|
|
214
|
+
name: 'content',
|
|
215
|
+
description:
|
|
216
|
+
'What to document or update. The model will determine whether this belongs in docs/ (application documentation) or DECISIONS.md (architecture decisions).',
|
|
217
|
+
required: true,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'doc_type',
|
|
221
|
+
description:
|
|
222
|
+
'Hint for documentation type: "decision" for architecture decisions (DECISIONS.md), "release" for release notes, "guide" for user guides, "api" for API docs, or "auto" to let the model decide based on content.',
|
|
223
|
+
required: false,
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
},
|
|
207
227
|
];
|
|
208
228
|
|
|
209
229
|
/**
|
package/src/prompts/index.js
CHANGED
|
@@ -203,6 +203,32 @@ This adds a timestamped status entry to STATUS.md.`,
|
|
|
203
203
|
},
|
|
204
204
|
},
|
|
205
205
|
],
|
|
206
|
+
update_project_docs: [
|
|
207
|
+
{
|
|
208
|
+
role: 'user',
|
|
209
|
+
content: {
|
|
210
|
+
type: 'text',
|
|
211
|
+
text: `Update project documentation with: "${args.content || '[CONTENT]'}".
|
|
212
|
+
|
|
213
|
+
IMPORTANT: This is about APPLICATION DOCUMENTATION (how the system works), NOT project management (status, todos, roadmap).
|
|
214
|
+
|
|
215
|
+
${args.doc_type === 'decision' ? `This is an architecture decision. Use the \`add_decision\` tool to add it to DECISIONS.md.` : args.doc_type === 'release' ? `This is a release note. Look in docs/releases/ and update the appropriate file.` : `Based on the content, determine where this documentation belongs:
|
|
216
|
+
|
|
217
|
+
1. **Architecture Decision?** (design choice, trade-off, technical rationale)
|
|
218
|
+
→ Use \`add_decision\` tool to add to DECISIONS.md
|
|
219
|
+
|
|
220
|
+
2. **Application Documentation?** (how-to, API docs, guides, reference)
|
|
221
|
+
→ First use \`search_project\` with intent "project_docs" to find relevant existing docs
|
|
222
|
+
→ Then use \`list_docs\` to see the docs/ structure
|
|
223
|
+
→ Update the appropriate file in docs/
|
|
224
|
+
|
|
225
|
+
3. **Release Notes?** (version changes, features, fixes)
|
|
226
|
+
→ Check docs/releases/ for the right file
|
|
227
|
+
|
|
228
|
+
The model decides where this content best fits based on its nature.`}`,
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
],
|
|
206
232
|
};
|
|
207
233
|
|
|
208
234
|
return (
|
|
@@ -236,6 +262,7 @@ export function getMessageHandlerKeys() {
|
|
|
236
262
|
get_backlog: true,
|
|
237
263
|
add_decision: true,
|
|
238
264
|
update_status: true,
|
|
265
|
+
update_project_docs: true,
|
|
239
266
|
};
|
|
240
267
|
return Object.keys(messages);
|
|
241
268
|
}
|
package/src/tools/lint.js
CHANGED
|
@@ -378,42 +378,52 @@ ${project_description}
|
|
|
378
378
|
|
|
379
379
|
## Contract for AI Agents
|
|
380
380
|
|
|
381
|
-
|
|
381
|
+
### Critical Distinction: Project Management vs Project Documentation
|
|
382
382
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
383
|
+
| Term | Means | Sources |
|
|
384
|
+
|------|-------|---------|
|
|
385
|
+
| **"project docs"** / **"project documentation"** | Application documentation (HOW the system works) | \`docs/\` + \`DECISIONS.md\` |
|
|
386
|
+
| **"project status"** / **"todos"** / **"roadmap"** | Project management (WHAT we're doing) | \`.project/\` management files |
|
|
387
|
+
|
|
388
|
+
**DECISIONS.md is special**: It's application documentation (explains WHY decisions were made) even though it lives in \`.project/\`.
|
|
386
389
|
|
|
387
390
|
## Source Mappings
|
|
388
391
|
|
|
389
|
-
### "project" / "
|
|
390
|
-
|
|
392
|
+
### "project docs" / "project documents" / "project documentation"
|
|
393
|
+
APPLICATION documentation — how the system works, why it was built this way.
|
|
394
|
+
Searches: \`docs/\` + \`DECISIONS.md\`
|
|
391
395
|
|
|
392
|
-
### "docs" / "documentation"
|
|
396
|
+
### "docs" / "documentation" / "reference"
|
|
397
|
+
Reference documentation only.
|
|
393
398
|
Searches: \`docs/\` only
|
|
394
399
|
|
|
395
|
-
### "plan" / "todos" / "roadmap" / "status"
|
|
396
|
-
|
|
400
|
+
### "plan" / "todos" / "roadmap" / "status" / "backlog"
|
|
401
|
+
Project MANAGEMENT — tracking work, not documenting the system.
|
|
402
|
+
Searches: \`.project/\` (excluding DECISIONS.md)
|
|
403
|
+
|
|
404
|
+
### "project" / "the project"
|
|
405
|
+
Everything (when intent is ambiguous).
|
|
406
|
+
Searches: \`.project/\` + root files + \`docs/\`
|
|
397
407
|
|
|
398
408
|
## File Structure
|
|
399
409
|
|
|
400
|
-
| File | Purpose |
|
|
401
|
-
|
|
402
|
-
| \`index.md\` | This file -
|
|
403
|
-
| \`
|
|
404
|
-
| \`
|
|
405
|
-
| \`
|
|
406
|
-
| \`
|
|
407
|
-
| \`
|
|
408
|
-
| \`todos/\` | Active task files
|
|
409
|
-
| \`archive/\` | Completed tasks
|
|
410
|
+
| File | Type | Purpose |
|
|
411
|
+
|------|------|---------|
|
|
412
|
+
| \`index.md\` | Contract | This file - source mappings |
|
|
413
|
+
| \`DECISIONS.md\` | **Documentation** | Architecture decisions (WHY) |
|
|
414
|
+
| \`BACKLOG.md\` | Management | Prioritized work queue |
|
|
415
|
+
| \`TODO.md\` | Management | Task dashboard |
|
|
416
|
+
| \`ROADMAP.md\` | Management | Project phases/milestones |
|
|
417
|
+
| \`STATUS.md\` | Management | Project health/progress |
|
|
418
|
+
| \`todos/\` | Management | Active task files |
|
|
419
|
+
| \`archive/\` | Management | Completed tasks |
|
|
410
420
|
|
|
411
421
|
## Principles
|
|
412
422
|
|
|
413
|
-
- **
|
|
423
|
+
- **"Project docs" ≠ "Project management"** — Users saying "update project docs" want application documentation updated, not task tracking
|
|
424
|
+
- **DECISIONS.md is documentation** — It explains the system, not tracks work
|
|
425
|
+
- **Natural language stays natural** — "Project docs" maps to docs/ + DECISIONS.md
|
|
414
426
|
- **Agents don't guess** — Explicit mappings defined here
|
|
415
|
-
- **Intent over structure** — Language maps to intent, not directory names
|
|
416
|
-
- **Operational truth** — This directory is the source of truth for current state
|
|
417
427
|
|
|
418
428
|
---
|
|
419
429
|
*Last Updated: ${date}*
|
package/src/tools/search.js
CHANGED
|
@@ -16,7 +16,7 @@ export const definitions = [
|
|
|
16
16
|
{
|
|
17
17
|
name: 'search_project',
|
|
18
18
|
description:
|
|
19
|
-
'Search across
|
|
19
|
+
'Search across project sources with smart intent detection. IMPORTANT: "project docs" means APPLICATION documentation (docs/ + DECISIONS.md), NOT project management. Use intent "project_docs" when user says "project docs/documents/documentation" to search application documentation. Use intent "plan" for project management (status, todos, roadmap, backlog).',
|
|
20
20
|
inputSchema: {
|
|
21
21
|
type: 'object',
|
|
22
22
|
properties: {
|
|
@@ -28,8 +28,19 @@ export const definitions = [
|
|
|
28
28
|
intent: {
|
|
29
29
|
type: 'string',
|
|
30
30
|
description:
|
|
31
|
-
'
|
|
32
|
-
enum: [
|
|
31
|
+
'Intent type to map to sources. "project_docs" searches docs/ + DECISIONS.md (application documentation). "docs" searches only docs/. "plan/todos/roadmap/status/operational" searches .project/ (project management). "project" searches everything. "decisions" searches only DECISIONS.md.',
|
|
32
|
+
enum: [
|
|
33
|
+
'project',
|
|
34
|
+
'project_docs',
|
|
35
|
+
'docs',
|
|
36
|
+
'decisions',
|
|
37
|
+
'plan',
|
|
38
|
+
'todos',
|
|
39
|
+
'roadmap',
|
|
40
|
+
'status',
|
|
41
|
+
'operational',
|
|
42
|
+
'',
|
|
43
|
+
],
|
|
33
44
|
},
|
|
34
45
|
maxResults: {
|
|
35
46
|
type: 'number',
|
package/src/tools/thoughts.js
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Thought processing tools.
|
|
3
|
-
* Handles: process_thoughts, list_thoughts, get_thought
|
|
3
|
+
* Handles: process_thoughts, list_thoughts, get_thought, archive_thought, list_archived_thoughts
|
|
4
4
|
*
|
|
5
5
|
* This tool reads brain dump files and provides context for the LLM to analyze.
|
|
6
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 {
|
|
10
|
+
import { THOUGHTS_TODOS_DIR, THOUGHTS_ARCHIVE_DIR } from '../lib/constants.js';
|
|
11
|
+
import {
|
|
12
|
+
readFile,
|
|
13
|
+
writeFile,
|
|
14
|
+
rename,
|
|
15
|
+
join,
|
|
16
|
+
fileExists,
|
|
17
|
+
ensureThoughtsTodosDir,
|
|
18
|
+
ensureThoughtsArchiveDir,
|
|
19
|
+
matter,
|
|
20
|
+
} from '../lib/files.js';
|
|
21
|
+
import { getISODate, getCurrentDate } from '../lib/dates.js';
|
|
12
22
|
import { loadAllTasks } from '../lib/tasks.js';
|
|
13
23
|
import { loadAllFiles, getCachedFiles } from '../lib/search.js';
|
|
14
24
|
|
|
25
|
+
const ARCHIVE_LOG_FILE = '.archive-log.md';
|
|
26
|
+
|
|
15
27
|
/**
|
|
16
28
|
* Tool definitions
|
|
17
29
|
*/
|
|
@@ -30,6 +42,7 @@ YOU (the LLM) should then analyze the content to:
|
|
|
30
42
|
- Identify logical task groupings (consolidate related items)
|
|
31
43
|
- Determine appropriate priorities based on context
|
|
32
44
|
- Create well-structured tasks using create_task
|
|
45
|
+
- **After creating tasks, use archive_thought to archive the processed file**
|
|
33
46
|
|
|
34
47
|
The tool does NOT automatically create tasks - it provides you with everything needed to make intelligent decisions about task creation.`,
|
|
35
48
|
inputSchema: {
|
|
@@ -48,6 +61,31 @@ The tool does NOT automatically create tasks - it provides you with everything n
|
|
|
48
61
|
required: ['project'],
|
|
49
62
|
},
|
|
50
63
|
},
|
|
64
|
+
{
|
|
65
|
+
name: 'archive_thought',
|
|
66
|
+
description: `Archives a processed thought file by moving it to .project/thoughts/todos/.archive/.
|
|
67
|
+
Use this after you've created tasks from a thought file to keep the active thoughts folder clean.
|
|
68
|
+
Also logs the archive action with timestamp and created task IDs.`,
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
file: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: 'The thought file to archive (e.g., "my-ideas.md").',
|
|
75
|
+
},
|
|
76
|
+
created_tasks: {
|
|
77
|
+
type: 'array',
|
|
78
|
+
items: { type: 'string' },
|
|
79
|
+
description: 'Array of task IDs that were created from this thought (e.g., ["AUTH-001", "AUTH-002"]).',
|
|
80
|
+
},
|
|
81
|
+
notes: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'Optional notes about the processing (e.g., "Consolidated 5 items into 2 tasks").',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
required: ['file'],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
51
89
|
{
|
|
52
90
|
name: 'list_thoughts',
|
|
53
91
|
description:
|
|
@@ -60,6 +98,26 @@ The tool does NOT automatically create tasks - it provides you with everything n
|
|
|
60
98
|
description: 'Optional: Filter by thought category. Currently supported: "todos".',
|
|
61
99
|
enum: ['todos', ''],
|
|
62
100
|
},
|
|
101
|
+
include_archived: {
|
|
102
|
+
type: 'boolean',
|
|
103
|
+
description: 'Include archived thoughts in the listing. Default: false.',
|
|
104
|
+
default: false,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'list_archived_thoughts',
|
|
111
|
+
description:
|
|
112
|
+
'Lists all archived thought files with their processing history. Shows what thoughts were processed, when, and what tasks were created.',
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
limit: {
|
|
117
|
+
type: 'number',
|
|
118
|
+
description: 'Maximum number of archived thoughts to show. Default: 20.',
|
|
119
|
+
default: 20,
|
|
120
|
+
},
|
|
63
121
|
},
|
|
64
122
|
},
|
|
65
123
|
},
|
|
@@ -78,6 +136,11 @@ The tool does NOT automatically create tasks - it provides you with everything n
|
|
|
78
136
|
description: 'The category/subdirectory. Default: "todos".',
|
|
79
137
|
default: 'todos',
|
|
80
138
|
},
|
|
139
|
+
from_archive: {
|
|
140
|
+
type: 'boolean',
|
|
141
|
+
description: 'Read from archive instead of active thoughts. Default: false.',
|
|
142
|
+
default: false,
|
|
143
|
+
},
|
|
81
144
|
},
|
|
82
145
|
required: ['file'],
|
|
83
146
|
},
|
|
@@ -113,7 +176,7 @@ async function getProjectContext() {
|
|
|
113
176
|
// Get roadmap content
|
|
114
177
|
const roadmapFile = files.find(f => f.path.includes('ROADMAP'));
|
|
115
178
|
if (roadmapFile) {
|
|
116
|
-
context.roadmap = roadmapFile.content.substring(0, 2000);
|
|
179
|
+
context.roadmap = roadmapFile.content.substring(0, 2000);
|
|
117
180
|
}
|
|
118
181
|
|
|
119
182
|
// Get decisions
|
|
@@ -126,7 +189,7 @@ async function getProjectContext() {
|
|
|
126
189
|
// Get current status
|
|
127
190
|
const statusFile = files.find(f => f.path.includes('STATUS'));
|
|
128
191
|
if (statusFile) {
|
|
129
|
-
context.status = statusFile.content.substring(0, 1000);
|
|
192
|
+
context.status = statusFile.content.substring(0, 1000);
|
|
130
193
|
}
|
|
131
194
|
} catch {
|
|
132
195
|
// Context loading failed, continue without it
|
|
@@ -163,7 +226,7 @@ async function processThoughts(args) {
|
|
|
163
226
|
try {
|
|
164
227
|
const files = await readdir(THOUGHTS_TODOS_DIR);
|
|
165
228
|
filesToProcess = files
|
|
166
|
-
.filter(f => f.endsWith('.md'))
|
|
229
|
+
.filter(f => f.endsWith('.md') && !f.startsWith('.'))
|
|
167
230
|
.map(f => ({ name: f, path: join(THOUGHTS_TODOS_DIR, f) }));
|
|
168
231
|
} catch {
|
|
169
232
|
// Directory might not exist yet
|
|
@@ -227,6 +290,10 @@ Analyze the thought content below and create appropriate tasks. Consider:
|
|
|
227
290
|
- tags: Relevant categorization
|
|
228
291
|
- subtasks: Array of subtask strings (for consolidated items)
|
|
229
292
|
|
|
293
|
+
5. **After creating tasks, archive the thought file** using \`archive_thought\`:
|
|
294
|
+
- Pass the filename and array of created task IDs
|
|
295
|
+
- This keeps the thoughts folder clean and maintains a processing log
|
|
296
|
+
|
|
230
297
|
---
|
|
231
298
|
|
|
232
299
|
## Thought Files to Process
|
|
@@ -275,6 +342,7 @@ Use this to understand what already exists and align new tasks appropriately.
|
|
|
275
342
|
}
|
|
276
343
|
}
|
|
277
344
|
|
|
345
|
+
const filenames = thoughtContents.map(t => t.filename).join('", "');
|
|
278
346
|
result += `
|
|
279
347
|
---
|
|
280
348
|
|
|
@@ -285,6 +353,10 @@ Now analyze the thought content above and:
|
|
|
285
353
|
1. Identify the distinct tasks/initiatives (consolidate related items)
|
|
286
354
|
2. For each task, determine title, priority, and relevant context
|
|
287
355
|
3. Use \`create_task\` to create well-structured tasks
|
|
356
|
+
4. **After creating tasks, use \`archive_thought\`** to archive each processed file:
|
|
357
|
+
\`\`\`
|
|
358
|
+
archive_thought(file: "${thoughtContents[0]?.filename || 'filename.md'}", created_tasks: ["${project.toUpperCase()}-001", ...])
|
|
359
|
+
\`\`\`
|
|
288
360
|
|
|
289
361
|
Remember: Quality over quantity. Create fewer, well-scoped tasks rather than many granular ones.
|
|
290
362
|
`;
|
|
@@ -294,11 +366,171 @@ Remember: Quality over quantity. Create fewer, well-scoped tasks rather than man
|
|
|
294
366
|
};
|
|
295
367
|
}
|
|
296
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Archive thought handler
|
|
371
|
+
*/
|
|
372
|
+
async function archiveThought(args) {
|
|
373
|
+
const { file, created_tasks = [], notes } = args;
|
|
374
|
+
|
|
375
|
+
await ensureThoughtsTodosDir();
|
|
376
|
+
await ensureThoughtsArchiveDir();
|
|
377
|
+
|
|
378
|
+
const sourcePath = join(THOUGHTS_TODOS_DIR, file);
|
|
379
|
+
const archivePath = join(THOUGHTS_ARCHIVE_DIR, file);
|
|
380
|
+
const logPath = join(THOUGHTS_ARCHIVE_DIR, ARCHIVE_LOG_FILE);
|
|
381
|
+
|
|
382
|
+
// Check if file exists
|
|
383
|
+
if (!(await fileExists(sourcePath))) {
|
|
384
|
+
return {
|
|
385
|
+
content: [
|
|
386
|
+
{
|
|
387
|
+
type: 'text',
|
|
388
|
+
text: `❌ File not found: ${file}\n\nUse \`list_thoughts\` to see available files.`,
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
isError: true,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Read the original content for the log
|
|
396
|
+
const originalContent = await readFile(sourcePath, 'utf-8');
|
|
397
|
+
const lineCount = originalContent.split('\n').length;
|
|
398
|
+
|
|
399
|
+
// Move file to archive
|
|
400
|
+
await rename(sourcePath, archivePath);
|
|
401
|
+
|
|
402
|
+
// Update archive log
|
|
403
|
+
let logContent = '';
|
|
404
|
+
try {
|
|
405
|
+
if (await fileExists(logPath)) {
|
|
406
|
+
logContent = await readFile(logPath, 'utf-8');
|
|
407
|
+
}
|
|
408
|
+
} catch {
|
|
409
|
+
// Log file doesn't exist yet
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Create log entry
|
|
413
|
+
const logEntry = `
|
|
414
|
+
## ${file}
|
|
415
|
+
|
|
416
|
+
**Archived:** ${getCurrentDate()}
|
|
417
|
+
**Original Lines:** ${lineCount}
|
|
418
|
+
**Tasks Created:** ${created_tasks.length > 0 ? created_tasks.join(', ') : 'None specified'}
|
|
419
|
+
${notes ? `**Notes:** ${notes}` : ''}
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
`;
|
|
423
|
+
|
|
424
|
+
// Prepend new entry to log (newest first)
|
|
425
|
+
if (!logContent.includes('# Thought Archive Log')) {
|
|
426
|
+
logContent = `# Thought Archive Log
|
|
427
|
+
|
|
428
|
+
This file tracks all processed thoughts and the tasks created from them.
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
${logEntry}`;
|
|
432
|
+
} else {
|
|
433
|
+
logContent = logContent.replace(
|
|
434
|
+
'---\n',
|
|
435
|
+
`---
|
|
436
|
+
${logEntry}`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
await writeFile(logPath, logContent, 'utf-8');
|
|
441
|
+
|
|
442
|
+
let result = `## Thought Archived: ${file}\n\n`;
|
|
443
|
+
result += `**Moved to:** \`.project/thoughts/todos/.archive/${file}\`\n`;
|
|
444
|
+
result += `**Archived:** ${getCurrentDate()}\n`;
|
|
445
|
+
result += `**Lines:** ${lineCount}\n`;
|
|
446
|
+
|
|
447
|
+
if (created_tasks.length > 0) {
|
|
448
|
+
result += `\n**Tasks Created:**\n`;
|
|
449
|
+
for (const taskId of created_tasks) {
|
|
450
|
+
result += `- ${taskId}\n`;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (notes) {
|
|
455
|
+
result += `\n**Notes:** ${notes}\n`;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
result += `\n✅ Thought file archived successfully. Use \`list_archived_thoughts\` to see archive history.`;
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
content: [{ type: 'text', text: result }],
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* List archived thoughts handler
|
|
467
|
+
*/
|
|
468
|
+
async function listArchivedThoughts(args) {
|
|
469
|
+
const { limit = 20 } = args || {};
|
|
470
|
+
|
|
471
|
+
await ensureThoughtsArchiveDir();
|
|
472
|
+
|
|
473
|
+
let result = `## Archived Thoughts\n\n`;
|
|
474
|
+
result += `**Path:** \`.project/thoughts/todos/.archive/\`\n\n`;
|
|
475
|
+
|
|
476
|
+
// Read archive log if exists
|
|
477
|
+
const logPath = join(THOUGHTS_ARCHIVE_DIR, ARCHIVE_LOG_FILE);
|
|
478
|
+
let logContent = '';
|
|
479
|
+
|
|
480
|
+
try {
|
|
481
|
+
if (await fileExists(logPath)) {
|
|
482
|
+
logContent = await readFile(logPath, 'utf-8');
|
|
483
|
+
}
|
|
484
|
+
} catch {
|
|
485
|
+
// Log doesn't exist
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (logContent) {
|
|
489
|
+
result += `### Archive Log\n\n`;
|
|
490
|
+
// Show the log content (it's already formatted)
|
|
491
|
+
const entries = logContent.split('## ').slice(1, limit + 1);
|
|
492
|
+
for (const entry of entries) {
|
|
493
|
+
result += `## ${entry}\n`;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Also list actual files in archive
|
|
498
|
+
try {
|
|
499
|
+
const files = await readdir(THOUGHTS_ARCHIVE_DIR);
|
|
500
|
+
const mdFiles = files.filter(f => f.endsWith('.md') && f !== ARCHIVE_LOG_FILE);
|
|
501
|
+
|
|
502
|
+
if (mdFiles.length > 0) {
|
|
503
|
+
result += `\n### Archived Files (${mdFiles.length})\n\n`;
|
|
504
|
+
result += `| File | Size |\n`;
|
|
505
|
+
result += `|------|------|\n`;
|
|
506
|
+
for (const file of mdFiles.slice(0, limit)) {
|
|
507
|
+
const filePath = join(THOUGHTS_ARCHIVE_DIR, file);
|
|
508
|
+
const content = await readFile(filePath, 'utf-8');
|
|
509
|
+
const lines = content.split('\n').length;
|
|
510
|
+
result += `| ${file} | ${lines} lines |\n`;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
} catch {
|
|
514
|
+
// Archive directory might not exist
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (!logContent && result.includes('Archived Files') === false) {
|
|
518
|
+
result += `*No archived thoughts yet. Use \`archive_thought\` after processing thoughts.*\n`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
result += `\n---\n`;
|
|
522
|
+
result += `**Tools:** \`get_thought\` with \`from_archive: true\` to read archived files`;
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
content: [{ type: 'text', text: result }],
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
297
529
|
/**
|
|
298
530
|
* List thoughts handler
|
|
299
531
|
*/
|
|
300
532
|
async function listThoughts(args) {
|
|
301
|
-
const { category } = args || {};
|
|
533
|
+
const { category, include_archived = false } = args || {};
|
|
302
534
|
|
|
303
535
|
await ensureThoughtsTodosDir();
|
|
304
536
|
|
|
@@ -311,7 +543,7 @@ async function listThoughts(args) {
|
|
|
311
543
|
try {
|
|
312
544
|
if (await fileExists(catDir)) {
|
|
313
545
|
const files = await readdir(catDir);
|
|
314
|
-
const mdFiles = files.filter(f => f.endsWith('.md'));
|
|
546
|
+
const mdFiles = files.filter(f => f.endsWith('.md') && !f.startsWith('.'));
|
|
315
547
|
|
|
316
548
|
results[cat] = [];
|
|
317
549
|
for (const file of mdFiles) {
|
|
@@ -353,9 +585,24 @@ async function listThoughts(args) {
|
|
|
353
585
|
}
|
|
354
586
|
}
|
|
355
587
|
|
|
588
|
+
// Show archived count if requested
|
|
589
|
+
if (include_archived) {
|
|
590
|
+
try {
|
|
591
|
+
await ensureThoughtsArchiveDir();
|
|
592
|
+
const archivedFiles = await readdir(THOUGHTS_ARCHIVE_DIR);
|
|
593
|
+
const archivedMd = archivedFiles.filter(f => f.endsWith('.md') && f !== ARCHIVE_LOG_FILE);
|
|
594
|
+
if (archivedMd.length > 0) {
|
|
595
|
+
result += `### 📦 .archive/ (${archivedMd.length} files)\n\n`;
|
|
596
|
+
result += `Use \`list_archived_thoughts\` to see details.\n\n`;
|
|
597
|
+
}
|
|
598
|
+
} catch {
|
|
599
|
+
// Archive doesn't exist
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
356
603
|
result += `---\n`;
|
|
357
|
-
result += `**Total files:** ${totalFiles}\n\n`;
|
|
358
|
-
result += `**Tools:** \`get_thought\` to read | \`process_thoughts\` to analyze`;
|
|
604
|
+
result += `**Total active files:** ${totalFiles}\n\n`;
|
|
605
|
+
result += `**Tools:** \`get_thought\` to read | \`process_thoughts\` to analyze | \`archive_thought\` after processing`;
|
|
359
606
|
|
|
360
607
|
return {
|
|
361
608
|
content: [{ type: 'text', text: result }],
|
|
@@ -366,19 +613,27 @@ async function listThoughts(args) {
|
|
|
366
613
|
* Get thought handler
|
|
367
614
|
*/
|
|
368
615
|
async function getThought(args) {
|
|
369
|
-
const { file, category = 'todos' } = args;
|
|
616
|
+
const { file, category = 'todos', from_archive = false } = args;
|
|
370
617
|
|
|
371
618
|
await ensureThoughtsTodosDir();
|
|
372
619
|
|
|
373
|
-
|
|
620
|
+
let catDir;
|
|
621
|
+
if (from_archive) {
|
|
622
|
+
await ensureThoughtsArchiveDir();
|
|
623
|
+
catDir = THOUGHTS_ARCHIVE_DIR;
|
|
624
|
+
} else {
|
|
625
|
+
catDir = category === 'todos' ? THOUGHTS_TODOS_DIR : join(THOUGHTS_TODOS_DIR, '..', category);
|
|
626
|
+
}
|
|
627
|
+
|
|
374
628
|
const filePath = join(catDir, file);
|
|
375
629
|
|
|
376
630
|
if (!(await fileExists(filePath))) {
|
|
631
|
+
const location = from_archive ? '.archive' : category;
|
|
377
632
|
return {
|
|
378
633
|
content: [
|
|
379
634
|
{
|
|
380
635
|
type: 'text',
|
|
381
|
-
text: `❌ File not found: \`.project/thoughts/${
|
|
636
|
+
text: `❌ File not found: \`.project/thoughts/todos/${location}/${file}\`\n\nUse \`list_thoughts\` or \`list_archived_thoughts\` to see available files.`,
|
|
382
637
|
},
|
|
383
638
|
],
|
|
384
639
|
isError: true,
|
|
@@ -388,9 +643,10 @@ async function getThought(args) {
|
|
|
388
643
|
const content = await readFile(filePath, 'utf-8');
|
|
389
644
|
const parsed = matter(content);
|
|
390
645
|
|
|
646
|
+
const location = from_archive ? 'todos/.archive' : category;
|
|
391
647
|
let result = `## Thought File: ${file}\n\n`;
|
|
392
|
-
result += `**Path:** \`.project/thoughts/${
|
|
393
|
-
result += `**
|
|
648
|
+
result += `**Path:** \`.project/thoughts/${location}/${file}\`\n`;
|
|
649
|
+
result += `**Status:** ${from_archive ? '📦 Archived' : '📝 Active'}\n\n`;
|
|
394
650
|
|
|
395
651
|
if (Object.keys(parsed.data).length > 0) {
|
|
396
652
|
result += `### Frontmatter\n\n`;
|
|
@@ -405,7 +661,11 @@ async function getThought(args) {
|
|
|
405
661
|
result += parsed.content;
|
|
406
662
|
|
|
407
663
|
result += `\n\n---\n`;
|
|
408
|
-
|
|
664
|
+
if (from_archive) {
|
|
665
|
+
result += `**This file has been archived.** It was already processed into tasks.`;
|
|
666
|
+
} else {
|
|
667
|
+
result += `**Tools:** \`process_thoughts\` to analyze | \`archive_thought\` after creating tasks`;
|
|
668
|
+
}
|
|
409
669
|
|
|
410
670
|
return {
|
|
411
671
|
content: [{ type: 'text', text: result }],
|
|
@@ -417,6 +677,8 @@ async function getThought(args) {
|
|
|
417
677
|
*/
|
|
418
678
|
export const handlers = {
|
|
419
679
|
process_thoughts: processThoughts,
|
|
680
|
+
archive_thought: archiveThought,
|
|
420
681
|
list_thoughts: listThoughts,
|
|
682
|
+
list_archived_thoughts: listArchivedThoughts,
|
|
421
683
|
get_thought: getThought,
|
|
422
684
|
};
|