@projitive/mcp 2.0.3 → 2.0.4
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/output/package.json +8 -2
- package/output/source/common/artifacts.js +1 -1
- package/output/source/common/artifacts.test.js +11 -11
- package/output/source/common/errors.js +19 -19
- package/output/source/common/files.js +11 -11
- package/output/source/common/files.test.js +14 -14
- package/output/source/common/index.js +10 -10
- package/output/source/common/linter.js +27 -27
- package/output/source/common/linter.test.js +9 -9
- package/output/source/common/markdown.js +3 -3
- package/output/source/common/markdown.test.js +15 -15
- package/output/source/common/response.js +74 -74
- package/output/source/common/response.test.js +30 -30
- package/output/source/common/store.js +40 -40
- package/output/source/common/store.test.js +72 -72
- package/output/source/common/types.js +3 -3
- package/output/source/common/utils.js +8 -8
- package/output/source/index.js +16 -16
- package/output/source/index.test.js +64 -64
- package/output/source/prompts/index.js +3 -3
- package/output/source/prompts/quickStart.js +96 -96
- package/output/source/prompts/taskDiscovery.js +184 -184
- package/output/source/prompts/taskExecution.js +148 -148
- package/output/source/resources/designs.js +26 -26
- package/output/source/resources/designs.test.js +88 -88
- package/output/source/resources/governance.js +19 -19
- package/output/source/resources/index.js +2 -2
- package/output/source/resources/readme.js +7 -7
- package/output/source/resources/readme.test.js +113 -113
- package/output/source/resources/reports.js +10 -10
- package/output/source/resources/reports.test.js +83 -83
- package/output/source/tools/index.js +3 -3
- package/output/source/tools/project.js +191 -191
- package/output/source/tools/project.test.js +174 -173
- package/output/source/tools/roadmap.js +110 -95
- package/output/source/tools/roadmap.test.js +54 -46
- package/output/source/tools/task.js +305 -277
- package/output/source/tools/task.test.js +117 -110
- package/output/source/types.js +22 -22
- package/package.json +8 -2
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import { afterEach, describe, expect, it } from
|
|
4
|
-
import { ensureStore, getMarkdownViewState, getStoreVersion, loadActionableTasksFromStore, loadRoadmapIdsFromStore, loadRoadmapsFromStore, loadTaskStatusStatsFromStore, loadTasksFromStore, markMarkdownViewBuilt, markMarkdownViewDirty, replaceRoadmapsInStore, replaceTasksInStore, upsertRoadmapInStore, upsertTaskInStore, } from
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { ensureStore, getMarkdownViewState, getStoreVersion, loadActionableTasksFromStore, loadRoadmapIdsFromStore, loadRoadmapsFromStore, loadTaskStatusStatsFromStore, loadTasksFromStore, markMarkdownViewBuilt, markMarkdownViewDirty, replaceRoadmapsInStore, replaceTasksInStore, upsertRoadmapInStore, upsertTaskInStore, } from './store.js';
|
|
5
5
|
const tempPaths = [];
|
|
6
6
|
async function createTempDbPath() {
|
|
7
|
-
const sandboxRoot = path.join(process.cwd(),
|
|
7
|
+
const sandboxRoot = path.join(process.cwd(), '.tmp', 'store-tests');
|
|
8
8
|
await fs.mkdir(sandboxRoot, { recursive: true });
|
|
9
|
-
const dir = await fs.mkdtemp(path.join(sandboxRoot,
|
|
9
|
+
const dir = await fs.mkdtemp(path.join(sandboxRoot, 'case-'));
|
|
10
10
|
tempPaths.push(dir);
|
|
11
|
-
return path.join(dir,
|
|
11
|
+
return path.join(dir, '.projitive');
|
|
12
12
|
}
|
|
13
13
|
async function readRawStore(dbPath) {
|
|
14
|
-
const content = await fs.readFile(dbPath,
|
|
14
|
+
const content = await fs.readFile(dbPath, 'utf8');
|
|
15
15
|
return JSON.parse(content);
|
|
16
16
|
}
|
|
17
17
|
function task(input) {
|
|
18
18
|
return {
|
|
19
19
|
id: input.id,
|
|
20
20
|
title: input.title,
|
|
21
|
-
status: input.status ??
|
|
22
|
-
owner: input.owner ??
|
|
23
|
-
summary: input.summary ??
|
|
21
|
+
status: input.status ?? 'TODO',
|
|
22
|
+
owner: input.owner ?? '',
|
|
23
|
+
summary: input.summary ?? '',
|
|
24
24
|
updatedAt: input.updatedAt ?? new Date().toISOString(),
|
|
25
25
|
links: input.links ?? [],
|
|
26
26
|
roadmapRefs: input.roadmapRefs ?? [],
|
|
@@ -32,7 +32,7 @@ function milestone(input) {
|
|
|
32
32
|
return {
|
|
33
33
|
id: input.id,
|
|
34
34
|
title: input.title,
|
|
35
|
-
status: input.status ??
|
|
35
|
+
status: input.status ?? 'active',
|
|
36
36
|
time: input.time,
|
|
37
37
|
updatedAt: input.updatedAt ?? new Date().toISOString(),
|
|
38
38
|
};
|
|
@@ -40,12 +40,12 @@ function milestone(input) {
|
|
|
40
40
|
afterEach(async () => {
|
|
41
41
|
await Promise.all(tempPaths.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
|
42
42
|
});
|
|
43
|
-
describe(
|
|
44
|
-
it(
|
|
43
|
+
describe('store', () => {
|
|
44
|
+
it('initializes JSON store with default meta/view state', async () => {
|
|
45
45
|
const dbPath = await createTempDbPath();
|
|
46
46
|
await ensureStore(dbPath);
|
|
47
47
|
const store = await readRawStore(dbPath);
|
|
48
|
-
expect(store.schema).toBe(
|
|
48
|
+
expect(store.schema).toBe('projitive-json-store');
|
|
49
49
|
expect(store.meta.store_schema_version).toBe(3);
|
|
50
50
|
expect(store.meta.tasks_version).toBe(0);
|
|
51
51
|
expect(store.meta.roadmaps_version).toBe(0);
|
|
@@ -55,65 +55,65 @@ describe("store", () => {
|
|
|
55
55
|
expect(store.view_state.roadmaps_markdown.lastSourceVersion).toBe(0);
|
|
56
56
|
expect(Array.isArray(store.migration_history)).toBe(true);
|
|
57
57
|
});
|
|
58
|
-
it(
|
|
58
|
+
it('tracks view state dirty/build transitions', async () => {
|
|
59
59
|
const dbPath = await createTempDbPath();
|
|
60
60
|
await ensureStore(dbPath);
|
|
61
|
-
const initial = await getMarkdownViewState(dbPath,
|
|
61
|
+
const initial = await getMarkdownViewState(dbPath, 'tasks_markdown');
|
|
62
62
|
expect(initial.dirty).toBe(true);
|
|
63
|
-
await markMarkdownViewBuilt(dbPath,
|
|
64
|
-
const built = await getMarkdownViewState(dbPath,
|
|
63
|
+
await markMarkdownViewBuilt(dbPath, 'tasks_markdown', 7, '2026-03-13T00:00:00.000Z');
|
|
64
|
+
const built = await getMarkdownViewState(dbPath, 'tasks_markdown');
|
|
65
65
|
expect(built.dirty).toBe(false);
|
|
66
66
|
expect(built.lastSourceVersion).toBe(7);
|
|
67
|
-
expect(built.lastBuiltAt).toBe(
|
|
68
|
-
await markMarkdownViewDirty(dbPath,
|
|
69
|
-
const dirtyAgain = await getMarkdownViewState(dbPath,
|
|
67
|
+
expect(built.lastBuiltAt).toBe('2026-03-13T00:00:00.000Z');
|
|
68
|
+
await markMarkdownViewDirty(dbPath, 'tasks_markdown');
|
|
69
|
+
const dirtyAgain = await getMarkdownViewState(dbPath, 'tasks_markdown');
|
|
70
70
|
expect(dirtyAgain.dirty).toBe(true);
|
|
71
71
|
expect(dirtyAgain.lastSourceVersion).toBe(7);
|
|
72
72
|
});
|
|
73
|
-
it(
|
|
73
|
+
it('upserts tasks and updates source/view versions', async () => {
|
|
74
74
|
const dbPath = await createTempDbPath();
|
|
75
75
|
await ensureStore(dbPath);
|
|
76
76
|
await upsertTaskInStore(dbPath, task({
|
|
77
|
-
id:
|
|
78
|
-
title:
|
|
79
|
-
status:
|
|
80
|
-
owner:
|
|
81
|
-
summary:
|
|
82
|
-
updatedAt:
|
|
83
|
-
roadmapRefs: [
|
|
77
|
+
id: 'TASK-0001',
|
|
78
|
+
title: 'First',
|
|
79
|
+
status: 'TODO',
|
|
80
|
+
owner: 'alice',
|
|
81
|
+
summary: 'first',
|
|
82
|
+
updatedAt: '2026-03-13T00:00:00.000Z',
|
|
83
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
84
84
|
}));
|
|
85
85
|
await upsertTaskInStore(dbPath, task({
|
|
86
|
-
id:
|
|
87
|
-
title:
|
|
88
|
-
status:
|
|
89
|
-
owner:
|
|
90
|
-
summary:
|
|
91
|
-
updatedAt:
|
|
92
|
-
roadmapRefs: [
|
|
93
|
-
links: [
|
|
86
|
+
id: 'TASK-0001',
|
|
87
|
+
title: 'First Updated',
|
|
88
|
+
status: 'IN_PROGRESS',
|
|
89
|
+
owner: 'alice',
|
|
90
|
+
summary: 'updated',
|
|
91
|
+
updatedAt: '2026-03-13T01:00:00.000Z',
|
|
92
|
+
roadmapRefs: ['ROADMAP-0001'],
|
|
93
|
+
links: ['reports/r1.md'],
|
|
94
94
|
}));
|
|
95
95
|
const tasks = await loadTasksFromStore(dbPath);
|
|
96
96
|
expect(tasks).toHaveLength(1);
|
|
97
|
-
expect(tasks[0].title).toBe(
|
|
98
|
-
expect(tasks[0].status).toBe(
|
|
99
|
-
expect(tasks[0].links).toEqual([
|
|
100
|
-
const tasksVersion = await getStoreVersion(dbPath,
|
|
97
|
+
expect(tasks[0].title).toBe('First Updated');
|
|
98
|
+
expect(tasks[0].status).toBe('IN_PROGRESS');
|
|
99
|
+
expect(tasks[0].links).toEqual(['reports/r1.md']);
|
|
100
|
+
const tasksVersion = await getStoreVersion(dbPath, 'tasks');
|
|
101
101
|
expect(tasksVersion).toBe(2);
|
|
102
|
-
const viewState = await getMarkdownViewState(dbPath,
|
|
102
|
+
const viewState = await getMarkdownViewState(dbPath, 'tasks_markdown');
|
|
103
103
|
expect(viewState.dirty).toBe(true);
|
|
104
104
|
const store = await readRawStore(dbPath);
|
|
105
|
-
const updated = store.tasks.find((item) => item.id ===
|
|
105
|
+
const updated = store.tasks.find((item) => item.id === 'TASK-0001');
|
|
106
106
|
expect(updated?.recordVersion).toBe(2);
|
|
107
107
|
});
|
|
108
|
-
it(
|
|
108
|
+
it('replaces task set and computes actionable ranking/stats', async () => {
|
|
109
109
|
const dbPath = await createTempDbPath();
|
|
110
110
|
await ensureStore(dbPath);
|
|
111
111
|
await replaceTasksInStore(dbPath, [
|
|
112
|
-
task({ id:
|
|
113
|
-
task({ id:
|
|
114
|
-
task({ id:
|
|
115
|
-
task({ id:
|
|
116
|
-
task({ id:
|
|
112
|
+
task({ id: 'TASK-0001', title: 'Todo older', status: 'TODO', updatedAt: '2026-03-10T00:00:00.000Z' }),
|
|
113
|
+
task({ id: 'TASK-0002', title: 'In progress', status: 'IN_PROGRESS', updatedAt: '2026-03-12T00:00:00.000Z' }),
|
|
114
|
+
task({ id: 'TASK-0003', title: 'Todo newer', status: 'TODO', updatedAt: '2026-03-13T00:00:00.000Z' }),
|
|
115
|
+
task({ id: 'TASK-0004', title: 'Blocked', status: 'BLOCKED', updatedAt: '2026-03-11T00:00:00.000Z' }),
|
|
116
|
+
task({ id: 'TASK-0005', title: 'Done', status: 'DONE', updatedAt: '2026-03-09T00:00:00.000Z' }),
|
|
117
117
|
]);
|
|
118
118
|
const stats = await loadTaskStatusStatsFromStore(dbPath);
|
|
119
119
|
expect(stats.todo).toBe(2);
|
|
@@ -121,44 +121,44 @@ describe("store", () => {
|
|
|
121
121
|
expect(stats.blocked).toBe(1);
|
|
122
122
|
expect(stats.done).toBe(1);
|
|
123
123
|
expect(stats.total).toBe(5);
|
|
124
|
-
expect(stats.latestUpdatedAt).toBe(
|
|
124
|
+
expect(stats.latestUpdatedAt).toBe('2026-03-13T00:00:00.000Z');
|
|
125
125
|
const actionable = await loadActionableTasksFromStore(dbPath, 2);
|
|
126
126
|
expect(actionable).toHaveLength(2);
|
|
127
|
-
expect(actionable[0].id).toBe(
|
|
128
|
-
expect(actionable[1].id).toBe(
|
|
129
|
-
const tasksVersion = await getStoreVersion(dbPath,
|
|
127
|
+
expect(actionable[0].id).toBe('TASK-0002');
|
|
128
|
+
expect(actionable[1].id).toBe('TASK-0003');
|
|
129
|
+
const tasksVersion = await getStoreVersion(dbPath, 'tasks');
|
|
130
130
|
expect(tasksVersion).toBe(1);
|
|
131
131
|
});
|
|
132
|
-
it(
|
|
132
|
+
it('upserts/replaces roadmaps and updates versions', async () => {
|
|
133
133
|
const dbPath = await createTempDbPath();
|
|
134
134
|
await ensureStore(dbPath);
|
|
135
135
|
await upsertRoadmapInStore(dbPath, milestone({
|
|
136
|
-
id:
|
|
137
|
-
title:
|
|
138
|
-
status:
|
|
139
|
-
updatedAt:
|
|
136
|
+
id: 'ROADMAP-0001',
|
|
137
|
+
title: 'Phase 1',
|
|
138
|
+
status: 'active',
|
|
139
|
+
updatedAt: '2026-03-10T00:00:00.000Z',
|
|
140
140
|
}));
|
|
141
141
|
await upsertRoadmapInStore(dbPath, milestone({
|
|
142
|
-
id:
|
|
143
|
-
title:
|
|
144
|
-
status:
|
|
145
|
-
time:
|
|
146
|
-
updatedAt:
|
|
142
|
+
id: 'ROADMAP-0001',
|
|
143
|
+
title: 'Phase 1 done',
|
|
144
|
+
status: 'done',
|
|
145
|
+
time: '2026-Q1',
|
|
146
|
+
updatedAt: '2026-03-11T00:00:00.000Z',
|
|
147
147
|
}));
|
|
148
148
|
const list1 = await loadRoadmapsFromStore(dbPath);
|
|
149
149
|
expect(list1).toHaveLength(1);
|
|
150
|
-
expect(list1[0].title).toBe(
|
|
151
|
-
expect(list1[0].status).toBe(
|
|
152
|
-
expect(list1[0].time).toBe(
|
|
150
|
+
expect(list1[0].title).toBe('Phase 1 done');
|
|
151
|
+
expect(list1[0].status).toBe('done');
|
|
152
|
+
expect(list1[0].time).toBe('2026-Q1');
|
|
153
153
|
await replaceRoadmapsInStore(dbPath, [
|
|
154
|
-
milestone({ id:
|
|
155
|
-
milestone({ id:
|
|
154
|
+
milestone({ id: 'ROADMAP-0002', title: 'Phase 2', status: 'active', updatedAt: '2026-03-12T00:00:00.000Z' }),
|
|
155
|
+
milestone({ id: 'ROADMAP-0003', title: 'Phase 3', status: 'done', updatedAt: '2026-03-13T00:00:00.000Z' }),
|
|
156
156
|
]);
|
|
157
157
|
const ids = await loadRoadmapIdsFromStore(dbPath);
|
|
158
|
-
expect(ids).toEqual([
|
|
159
|
-
const roadmapsVersion = await getStoreVersion(dbPath,
|
|
158
|
+
expect(ids).toEqual(['ROADMAP-0003', 'ROADMAP-0002']);
|
|
159
|
+
const roadmapsVersion = await getStoreVersion(dbPath, 'roadmaps');
|
|
160
160
|
expect(roadmapsVersion).toBe(3);
|
|
161
|
-
const viewState = await getMarkdownViewState(dbPath,
|
|
161
|
+
const viewState = await getMarkdownViewState(dbPath, 'roadmaps_markdown');
|
|
162
162
|
expect(viewState.dirty).toBe(true);
|
|
163
163
|
});
|
|
164
164
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Common type definitions (simplified)
|
|
2
2
|
export const VALID_STATUS_TRANSITIONS = {
|
|
3
|
-
TODO: [
|
|
4
|
-
IN_PROGRESS: [
|
|
5
|
-
BLOCKED: [
|
|
3
|
+
TODO: ['IN_PROGRESS'],
|
|
4
|
+
IN_PROGRESS: ['TODO', 'BLOCKED', 'DONE'],
|
|
5
|
+
BLOCKED: ['IN_PROGRESS'],
|
|
6
6
|
DONE: [],
|
|
7
7
|
};
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
// Common utility functions
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
4
|
/**
|
|
5
5
|
* Safely read Markdown file content, return fallback if file doesn't exist or is empty
|
|
6
6
|
*/
|
|
7
7
|
export async function readMarkdownOrFallback(relativePath, fallbackTitle, repoRoot = process.cwd()) {
|
|
8
8
|
const absolutePath = path.resolve(repoRoot, relativePath);
|
|
9
9
|
try {
|
|
10
|
-
const content = await fs.readFile(absolutePath,
|
|
10
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
11
11
|
if (content.trim().length > 0) {
|
|
12
12
|
return content;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
catch (error) {
|
|
16
|
-
if (error.code
|
|
16
|
+
if (!(error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT')) {
|
|
17
17
|
console.error(`Failed to read file: ${absolutePath}`, error);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
return [
|
|
21
21
|
`# ${fallbackTitle}`,
|
|
22
|
-
|
|
22
|
+
'',
|
|
23
23
|
`- file: ${relativePath}`,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
].join(
|
|
24
|
+
'- status: missing-or-empty',
|
|
25
|
+
'- next: create this file or ensure it has readable markdown content',
|
|
26
|
+
].join('\n');
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
29
|
* Capitalize first letter
|
package/output/source/index.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import path from
|
|
3
|
-
import process from
|
|
4
|
-
import { fileURLToPath } from
|
|
5
|
-
import { McpServer } from
|
|
6
|
-
import { StdioServerTransport } from
|
|
7
|
-
import packageJson from
|
|
8
|
-
import { registerTools } from
|
|
9
|
-
import { registerPrompts } from
|
|
10
|
-
import { registerResources } from
|
|
11
|
-
const PROJITIVE_SPEC_VERSION =
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
|
+
import packageJson from '../package.json' with { type: 'json' };
|
|
8
|
+
import { registerTools } from './tools/index.js';
|
|
9
|
+
import { registerPrompts } from './prompts/index.js';
|
|
10
|
+
import { registerResources } from './resources/index.js';
|
|
11
|
+
const PROJITIVE_SPEC_VERSION = '1.1.0';
|
|
12
12
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
13
13
|
const sourceDir = path.dirname(currentFilePath);
|
|
14
|
-
const repoRoot = path.resolve(sourceDir,
|
|
15
|
-
const MCP_RUNTIME_VERSION = typeof packageJson.version ===
|
|
14
|
+
const repoRoot = path.resolve(sourceDir, '..', '..', '..');
|
|
15
|
+
const MCP_RUNTIME_VERSION = typeof packageJson.version === 'string' && packageJson.version.trim().length > 0
|
|
16
16
|
? packageJson.version.trim()
|
|
17
17
|
: PROJITIVE_SPEC_VERSION;
|
|
18
18
|
const server = new McpServer({
|
|
19
|
-
name:
|
|
19
|
+
name: 'projitive',
|
|
20
20
|
version: MCP_RUNTIME_VERSION,
|
|
21
|
-
description:
|
|
21
|
+
description: 'Semantic Projitive MCP for project/task discovery and agent guidance with governance-store-first outputs',
|
|
22
22
|
});
|
|
23
23
|
// 注册所有模块
|
|
24
24
|
registerTools(server);
|
|
25
25
|
registerPrompts(server);
|
|
26
26
|
registerResources(server, repoRoot);
|
|
27
27
|
async function main() {
|
|
28
|
-
console.error(
|
|
28
|
+
console.error('[projitive-mcp] starting server');
|
|
29
29
|
console.error(`[projitive-mcp] version=${MCP_RUNTIME_VERSION} spec=${PROJITIVE_SPEC_VERSION} transport=stdio pid=${process.pid}`);
|
|
30
30
|
const transport = new StdioServerTransport();
|
|
31
31
|
await server.connect(transport);
|
|
32
32
|
}
|
|
33
33
|
void main().catch((error) => {
|
|
34
|
-
console.error(
|
|
34
|
+
console.error('Server error:', error);
|
|
35
35
|
process.exit(1);
|
|
36
36
|
});
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll, vi, beforeEach } from
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
import os from
|
|
5
|
-
import { fileURLToPath } from
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, vi, beforeEach } from 'vitest';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
6
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
7
7
|
const sourceDir = path.dirname(currentFilePath);
|
|
8
8
|
// Mock McpServer and StdioServerTransport
|
|
9
|
-
vi.mock(
|
|
9
|
+
vi.mock('@modelcontextprotocol/sdk/server/mcp.js', () => ({
|
|
10
10
|
McpServer: vi.fn().mockImplementation(() => ({
|
|
11
11
|
registerResource: vi.fn(),
|
|
12
12
|
registerPrompt: vi.fn(),
|
|
13
13
|
connect: vi.fn().mockResolvedValue(undefined),
|
|
14
14
|
})),
|
|
15
15
|
}));
|
|
16
|
-
vi.mock(
|
|
16
|
+
vi.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({
|
|
17
17
|
StdioServerTransport: vi.fn().mockImplementation(() => ({})),
|
|
18
18
|
}));
|
|
19
19
|
// Mock the registration functions
|
|
20
|
-
vi.mock(
|
|
20
|
+
vi.mock('./tools/project.js', () => ({
|
|
21
21
|
registerProjectTools: vi.fn(),
|
|
22
22
|
}));
|
|
23
|
-
vi.mock(
|
|
23
|
+
vi.mock('./tools/task.js', () => ({
|
|
24
24
|
registerTaskTools: vi.fn(),
|
|
25
25
|
}));
|
|
26
|
-
vi.mock(
|
|
26
|
+
vi.mock('./tools/roadmap.js', () => ({
|
|
27
27
|
registerRoadmapTools: vi.fn(),
|
|
28
28
|
}));
|
|
29
|
-
vi.mock(
|
|
29
|
+
vi.mock('./resources/governance.js', () => ({
|
|
30
30
|
registerGovernanceResources: vi.fn(),
|
|
31
31
|
}));
|
|
32
|
-
vi.mock(
|
|
32
|
+
vi.mock('./resources/designs.js', () => ({
|
|
33
33
|
registerDesignFilesResources: vi.fn(),
|
|
34
34
|
}));
|
|
35
|
-
vi.mock(
|
|
35
|
+
vi.mock('./prompts/governance.js', () => ({
|
|
36
36
|
registerGovernancePrompts: vi.fn(),
|
|
37
37
|
}));
|
|
38
|
-
describe(
|
|
38
|
+
describe('index module', () => {
|
|
39
39
|
let tempDir;
|
|
40
40
|
let testProjectDir;
|
|
41
41
|
beforeAll(async () => {
|
|
42
|
-
tempDir = await fs.mkdtemp(path.join(os.tmpdir(),
|
|
43
|
-
testProjectDir = path.join(tempDir,
|
|
42
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'projitive-index-test-'));
|
|
43
|
+
testProjectDir = path.join(tempDir, 'test-project');
|
|
44
44
|
await fs.mkdir(testProjectDir);
|
|
45
45
|
});
|
|
46
46
|
afterAll(async () => {
|
|
@@ -49,62 +49,62 @@ describe("index module", () => {
|
|
|
49
49
|
beforeEach(() => {
|
|
50
50
|
vi.clearAllMocks();
|
|
51
51
|
});
|
|
52
|
-
it(
|
|
53
|
-
const indexPath = path.join(sourceDir,
|
|
52
|
+
it('should export the main server entry point', async () => {
|
|
53
|
+
const indexPath = path.join(sourceDir, 'index.ts');
|
|
54
54
|
const exists = await fs.access(indexPath).then(() => true).catch(() => false);
|
|
55
55
|
expect(exists).toBe(true);
|
|
56
56
|
});
|
|
57
|
-
it(
|
|
58
|
-
const indexPath = path.join(sourceDir,
|
|
59
|
-
const content = await fs.readFile(indexPath,
|
|
60
|
-
expect(content.startsWith(
|
|
61
|
-
expect(content).toContain(
|
|
62
|
-
expect(content).toContain(
|
|
63
|
-
expect(content).toContain(
|
|
64
|
-
expect(content).toContain(
|
|
65
|
-
expect(content).toContain(
|
|
57
|
+
it('should have the correct shebang and imports', async () => {
|
|
58
|
+
const indexPath = path.join(sourceDir, 'index.ts');
|
|
59
|
+
const content = await fs.readFile(indexPath, 'utf-8');
|
|
60
|
+
expect(content.startsWith('#!/usr/bin/env node')).toBe(true);
|
|
61
|
+
expect(content).toContain('McpServer');
|
|
62
|
+
expect(content).toContain('StdioServerTransport');
|
|
63
|
+
expect(content).toContain('registerTools');
|
|
64
|
+
expect(content).toContain('registerResources');
|
|
65
|
+
expect(content).toContain('registerPrompts');
|
|
66
66
|
});
|
|
67
|
-
it(
|
|
68
|
-
const indexPath = path.join(sourceDir,
|
|
69
|
-
const content = await fs.readFile(indexPath,
|
|
70
|
-
expect(content).toContain(
|
|
71
|
-
expect(content).toContain('
|
|
67
|
+
it('should define PROJITIVE_SPEC_VERSION as 1.1.0', async () => {
|
|
68
|
+
const indexPath = path.join(sourceDir, 'index.ts');
|
|
69
|
+
const content = await fs.readFile(indexPath, 'utf-8');
|
|
70
|
+
expect(content).toContain('PROJITIVE_SPEC_VERSION');
|
|
71
|
+
expect(content).toContain("'1.1.0'");
|
|
72
72
|
});
|
|
73
|
-
it(
|
|
74
|
-
const indexPath = path.join(sourceDir,
|
|
75
|
-
const content = await fs.readFile(indexPath,
|
|
76
|
-
expect(content).toContain(
|
|
77
|
-
expect(content).toContain(
|
|
78
|
-
expect(content).toContain(
|
|
73
|
+
it('should have main function with server startup', async () => {
|
|
74
|
+
const indexPath = path.join(sourceDir, 'index.ts');
|
|
75
|
+
const content = await fs.readFile(indexPath, 'utf-8');
|
|
76
|
+
expect(content).toContain('async function main');
|
|
77
|
+
expect(content).toContain('server.connect');
|
|
78
|
+
expect(content).toContain('StdioServerTransport');
|
|
79
79
|
});
|
|
80
|
-
it(
|
|
81
|
-
const indexPath = path.join(sourceDir,
|
|
82
|
-
const content = await fs.readFile(indexPath,
|
|
83
|
-
expect(content).toContain(
|
|
84
|
-
expect(content).toContain(
|
|
85
|
-
expect(content).toContain(
|
|
80
|
+
it('should register all tool categories', async () => {
|
|
81
|
+
const indexPath = path.join(sourceDir, 'index.ts');
|
|
82
|
+
const content = await fs.readFile(indexPath, 'utf-8');
|
|
83
|
+
expect(content).toContain('registerTools(server)');
|
|
84
|
+
expect(content).toContain('registerResources(server, repoRoot)');
|
|
85
|
+
expect(content).toContain('registerPrompts(server)');
|
|
86
86
|
});
|
|
87
|
-
it(
|
|
88
|
-
const indexPath = path.join(sourceDir,
|
|
89
|
-
const content = await fs.readFile(indexPath,
|
|
90
|
-
expect(content).toContain(
|
|
91
|
-
expect(content).toContain("console.error(
|
|
92
|
-
expect(content).toContain(
|
|
87
|
+
it('should have proper error handling in main', async () => {
|
|
88
|
+
const indexPath = path.join(sourceDir, 'index.ts');
|
|
89
|
+
const content = await fs.readFile(indexPath, 'utf-8');
|
|
90
|
+
expect(content).toContain('void main().catch');
|
|
91
|
+
expect(content).toContain("console.error('Server error:', error)");
|
|
92
|
+
expect(content).toContain('process.exit(1)');
|
|
93
93
|
});
|
|
94
|
-
it(
|
|
95
|
-
const indexPath = path.join(sourceDir,
|
|
96
|
-
const content = await fs.readFile(indexPath,
|
|
97
|
-
expect(content).toContain("console.error(
|
|
98
|
-
expect(content).toContain(
|
|
99
|
-
expect(content).toContain(
|
|
100
|
-
expect(content).toContain(
|
|
101
|
-
expect(content).toContain(
|
|
94
|
+
it('should log server startup information', async () => {
|
|
95
|
+
const indexPath = path.join(sourceDir, 'index.ts');
|
|
96
|
+
const content = await fs.readFile(indexPath, 'utf-8');
|
|
97
|
+
expect(content).toContain("console.error('[projitive-mcp] starting server')");
|
|
98
|
+
expect(content).toContain('console.error(`[projitive-mcp] version=');
|
|
99
|
+
expect(content).toContain('spec=');
|
|
100
|
+
expect(content).toContain('transport=stdio');
|
|
101
|
+
expect(content).toContain('pid=');
|
|
102
102
|
});
|
|
103
|
-
it(
|
|
104
|
-
const indexPath = path.join(sourceDir,
|
|
105
|
-
const content = await fs.readFile(indexPath,
|
|
106
|
-
expect(content).toContain("name:
|
|
107
|
-
expect(content).toContain(
|
|
108
|
-
expect(content).toContain(
|
|
103
|
+
it('should define MCP server with correct metadata', async () => {
|
|
104
|
+
const indexPath = path.join(sourceDir, 'index.ts');
|
|
105
|
+
const content = await fs.readFile(indexPath, 'utf-8');
|
|
106
|
+
expect(content).toContain("name: 'projitive'");
|
|
107
|
+
expect(content).toContain('version: MCP_RUNTIME_VERSION');
|
|
108
|
+
expect(content).toContain('description:');
|
|
109
109
|
});
|
|
110
110
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Governance workflow prompts
|
|
2
|
-
import { registerQuickStartPrompt } from
|
|
3
|
-
import { registerTaskDiscoveryPrompt } from
|
|
4
|
-
import { registerTaskExecutionPrompt } from
|
|
2
|
+
import { registerQuickStartPrompt } from './quickStart.js';
|
|
3
|
+
import { registerTaskDiscoveryPrompt } from './taskDiscovery.js';
|
|
4
|
+
import { registerTaskExecutionPrompt } from './taskExecution.js';
|
|
5
5
|
export function registerPrompts(server) {
|
|
6
6
|
registerQuickStartPrompt(server);
|
|
7
7
|
registerTaskDiscoveryPrompt(server);
|