@projitive/mcp 2.0.3 → 2.1.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/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/errors.test.js +59 -0
- package/output/source/common/files.js +30 -19
- package/output/source/common/files.test.js +14 -14
- package/output/source/common/index.js +11 -10
- package/output/source/common/linter.js +29 -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 +91 -107
- 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/tool.js +43 -0
- package/output/source/common/types.js +3 -3
- package/output/source/common/utils.js +8 -8
- package/output/source/common/utils.test.js +48 -0
- package/output/source/index.js +16 -16
- package/output/source/index.runtime.test.js +57 -0
- package/output/source/index.test.js +64 -64
- package/output/source/prompts/index.js +3 -3
- package/output/source/prompts/index.test.js +23 -0
- package/output/source/prompts/quickStart.js +96 -96
- package/output/source/prompts/quickStart.test.js +24 -0
- package/output/source/prompts/taskDiscovery.js +184 -184
- package/output/source/prompts/taskDiscovery.test.js +24 -0
- package/output/source/prompts/taskExecution.js +164 -148
- package/output/source/prompts/taskExecution.test.js +27 -0
- package/output/source/resources/designs.js +26 -26
- package/output/source/resources/designs.resources.test.js +52 -0
- package/output/source/resources/designs.test.js +88 -88
- package/output/source/resources/governance.js +19 -19
- package/output/source/resources/governance.test.js +35 -0
- package/output/source/resources/index.js +2 -2
- package/output/source/resources/index.test.js +18 -0
- 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/index.test.js +23 -0
- package/output/source/tools/project.js +330 -377
- package/output/source/tools/project.test.js +308 -175
- package/output/source/tools/roadmap.js +236 -255
- package/output/source/tools/roadmap.test.js +241 -46
- package/output/source/tools/task.js +770 -652
- package/output/source/tools/task.test.js +433 -105
- package/output/source/types.js +28 -22
- package/package.json +8 -2
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { parseRequiredReading, } from
|
|
3
|
-
describe(
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { parseRequiredReading, } from './readme.js';
|
|
3
|
+
describe('readme module', () => {
|
|
4
|
+
describe('parseRequiredReading', () => {
|
|
5
|
+
it('parses local and external required reading items', () => {
|
|
6
6
|
const markdown = [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
].join(
|
|
7
|
+
'# Project README',
|
|
8
|
+
'',
|
|
9
|
+
'Some intro content',
|
|
10
|
+
'',
|
|
11
|
+
'## Required Reading for Agents',
|
|
12
|
+
'',
|
|
13
|
+
'- Local: ./designs/README.md',
|
|
14
|
+
'- Local: .projitive/tasks.md',
|
|
15
|
+
'- External: https://example.com/docs',
|
|
16
|
+
'',
|
|
17
|
+
'## Other Section',
|
|
18
|
+
'',
|
|
19
|
+
'More content here',
|
|
20
|
+
].join('\n');
|
|
21
21
|
const result = parseRequiredReading(markdown);
|
|
22
22
|
expect(result.length).toBe(3);
|
|
23
23
|
expect(result[0]).toEqual({
|
|
24
|
-
source:
|
|
25
|
-
value:
|
|
24
|
+
source: 'Local',
|
|
25
|
+
value: './designs/README.md',
|
|
26
26
|
});
|
|
27
27
|
expect(result[1]).toEqual({
|
|
28
|
-
source:
|
|
29
|
-
value:
|
|
28
|
+
source: 'Local',
|
|
29
|
+
value: '.projitive/tasks.md',
|
|
30
30
|
});
|
|
31
31
|
expect(result[2]).toEqual({
|
|
32
|
-
source:
|
|
33
|
-
value:
|
|
32
|
+
source: 'External',
|
|
33
|
+
value: 'https://example.com/docs',
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
|
-
it(
|
|
36
|
+
it('parses Chinese section header', () => {
|
|
37
37
|
const markdown = [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
].join(
|
|
38
|
+
'# 项目 README',
|
|
39
|
+
'',
|
|
40
|
+
'## Agent 必读',
|
|
41
|
+
'',
|
|
42
|
+
'- Local: ./docs/guide.md',
|
|
43
|
+
'',
|
|
44
|
+
'## 其他部分',
|
|
45
|
+
].join('\n');
|
|
46
46
|
const result = parseRequiredReading(markdown);
|
|
47
47
|
expect(result.length).toBe(1);
|
|
48
|
-
expect(result[0].source).toBe(
|
|
49
|
-
expect(result[0].value).toBe(
|
|
48
|
+
expect(result[0].source).toBe('Local');
|
|
49
|
+
expect(result[0].value).toBe('./docs/guide.md');
|
|
50
50
|
});
|
|
51
|
-
it(
|
|
51
|
+
it('returns empty array when no required reading section', () => {
|
|
52
52
|
const markdown = [
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
].join(
|
|
53
|
+
'# Simple README',
|
|
54
|
+
'',
|
|
55
|
+
'No required reading here',
|
|
56
|
+
'',
|
|
57
|
+
'## Other Section',
|
|
58
|
+
].join('\n');
|
|
59
59
|
const result = parseRequiredReading(markdown);
|
|
60
60
|
expect(result).toEqual([]);
|
|
61
61
|
});
|
|
62
|
-
it(
|
|
63
|
-
const result = parseRequiredReading(
|
|
62
|
+
it('returns empty array for empty string', () => {
|
|
63
|
+
const result = parseRequiredReading('');
|
|
64
64
|
expect(result).toEqual([]);
|
|
65
65
|
});
|
|
66
|
-
it(
|
|
66
|
+
it('ignores non-list items in required reading section', () => {
|
|
67
67
|
const markdown = [
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
].join(
|
|
68
|
+
'## Required Reading for Agents',
|
|
69
|
+
'',
|
|
70
|
+
'Some paragraph text here',
|
|
71
|
+
'- Local: valid-item.md',
|
|
72
|
+
'Another paragraph',
|
|
73
|
+
'- External: https://valid.com',
|
|
74
|
+
'',
|
|
75
|
+
'## Next Section',
|
|
76
|
+
].join('\n');
|
|
77
77
|
const result = parseRequiredReading(markdown);
|
|
78
78
|
expect(result.length).toBe(2);
|
|
79
|
-
expect(result[0].value).toBe(
|
|
80
|
-
expect(result[1].value).toBe(
|
|
79
|
+
expect(result[0].value).toBe('valid-item.md');
|
|
80
|
+
expect(result[1].value).toBe('https://valid.com');
|
|
81
81
|
});
|
|
82
|
-
it(
|
|
82
|
+
it('handles items with whitespace variations', () => {
|
|
83
83
|
const markdown = [
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
].join(
|
|
84
|
+
'## Required Reading for Agents',
|
|
85
|
+
'',
|
|
86
|
+
'- Local: ./path/with/spaces.md ',
|
|
87
|
+
'- External: https://example.com ',
|
|
88
|
+
].join('\n');
|
|
89
89
|
const result = parseRequiredReading(markdown);
|
|
90
90
|
expect(result.length).toBe(2);
|
|
91
|
-
expect(result[0].value).toBe(
|
|
92
|
-
expect(result[1].value).toBe(
|
|
91
|
+
expect(result[0].value).toBe('./path/with/spaces.md');
|
|
92
|
+
expect(result[1].value).toBe('https://example.com');
|
|
93
93
|
});
|
|
94
|
-
it(
|
|
94
|
+
it('ignores items without Local: or External: prefix', () => {
|
|
95
95
|
const markdown = [
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
].join(
|
|
96
|
+
'## Required Reading for Agents',
|
|
97
|
+
'',
|
|
98
|
+
'- Local: valid.md',
|
|
99
|
+
'- Invalid: this is ignored',
|
|
100
|
+
'- Just a plain list item',
|
|
101
|
+
'- External: https://valid.com',
|
|
102
|
+
].join('\n');
|
|
103
103
|
const result = parseRequiredReading(markdown);
|
|
104
104
|
expect(result.length).toBe(2);
|
|
105
|
-
expect(result.every((item) => item.source ===
|
|
105
|
+
expect(result.every((item) => item.source === 'Local' || item.source === 'External')).toBe(true);
|
|
106
106
|
});
|
|
107
|
-
it(
|
|
107
|
+
it('stops at next section header', () => {
|
|
108
108
|
const markdown = [
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
].join(
|
|
109
|
+
'## Required Reading for Agents',
|
|
110
|
+
'',
|
|
111
|
+
'- Local: before.md',
|
|
112
|
+
'',
|
|
113
|
+
'## Another Section',
|
|
114
|
+
'',
|
|
115
|
+
'- Local: after.md',
|
|
116
|
+
].join('\n');
|
|
117
117
|
const result = parseRequiredReading(markdown);
|
|
118
118
|
expect(result.length).toBe(1);
|
|
119
|
-
expect(result[0].value).toBe(
|
|
119
|
+
expect(result[0].value).toBe('before.md');
|
|
120
120
|
});
|
|
121
|
-
it(
|
|
121
|
+
it('handles multiple required reading sections (takes first one)', () => {
|
|
122
122
|
const markdown = [
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
].join(
|
|
123
|
+
'## Required Reading for Agents',
|
|
124
|
+
'',
|
|
125
|
+
'- Local: first.md',
|
|
126
|
+
'',
|
|
127
|
+
'## Other Section',
|
|
128
|
+
'',
|
|
129
|
+
'## Required Reading for Agents',
|
|
130
|
+
'',
|
|
131
|
+
'- Local: second.md',
|
|
132
|
+
].join('\n');
|
|
133
133
|
const result = parseRequiredReading(markdown);
|
|
134
134
|
expect(result.length).toBe(1);
|
|
135
|
-
expect(result[0].value).toBe(
|
|
135
|
+
expect(result[0].value).toBe('first.md');
|
|
136
136
|
});
|
|
137
137
|
});
|
|
138
|
-
describe(
|
|
139
|
-
it(
|
|
140
|
-
const markdown =
|
|
138
|
+
describe('edge cases', () => {
|
|
139
|
+
it('handles markdown with just the section', () => {
|
|
140
|
+
const markdown = '## Required Reading for Agents';
|
|
141
141
|
const result = parseRequiredReading(markdown);
|
|
142
142
|
expect(result).toEqual([]);
|
|
143
143
|
});
|
|
144
|
-
it(
|
|
144
|
+
it('handles section with empty lines', () => {
|
|
145
145
|
const markdown = [
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
].join(
|
|
146
|
+
'## Required Reading for Agents',
|
|
147
|
+
'',
|
|
148
|
+
'',
|
|
149
|
+
'- Local: item.md',
|
|
150
|
+
'',
|
|
151
|
+
'',
|
|
152
|
+
].join('\n');
|
|
153
153
|
const result = parseRequiredReading(markdown);
|
|
154
154
|
expect(result.length).toBe(1);
|
|
155
|
-
expect(result[0].value).toBe(
|
|
155
|
+
expect(result[0].value).toBe('item.md');
|
|
156
156
|
});
|
|
157
|
-
it(
|
|
157
|
+
it('handles mixed case section headers', () => {
|
|
158
158
|
const markdown = [
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
].join(
|
|
159
|
+
'## required reading for agents',
|
|
160
|
+
'',
|
|
161
|
+
'- Local: lowercase.md',
|
|
162
|
+
].join('\n');
|
|
163
163
|
const result = parseRequiredReading(markdown);
|
|
164
164
|
expect(result.length).toBe(1);
|
|
165
165
|
});
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { isValidRoadmapId } from
|
|
2
|
-
import { isValidTaskId } from
|
|
1
|
+
import { isValidRoadmapId } from '../tools/roadmap.js';
|
|
2
|
+
import { isValidTaskId } from '../tools/task.js';
|
|
3
3
|
export function parseReportMetadata(markdown) {
|
|
4
4
|
const lines = markdown.split(/\r?\n/);
|
|
5
5
|
const metadata = {};
|
|
6
6
|
for (const line of lines) {
|
|
7
7
|
// Remove markdown bold markers (**)
|
|
8
|
-
const cleanLine = line.replace(/\*\*/g,
|
|
9
|
-
const [rawKey, ...rawValue] = cleanLine.split(
|
|
8
|
+
const cleanLine = line.replace(/\*\*/g, '');
|
|
9
|
+
const [rawKey, ...rawValue] = cleanLine.split(':');
|
|
10
10
|
if (!rawKey || rawValue.length === 0) {
|
|
11
11
|
continue;
|
|
12
12
|
}
|
|
13
13
|
const key = rawKey.trim().toLowerCase();
|
|
14
|
-
const value = rawValue.join(
|
|
15
|
-
if (key ===
|
|
14
|
+
const value = rawValue.join(':').trim();
|
|
15
|
+
if (key === 'task')
|
|
16
16
|
metadata.task = value;
|
|
17
|
-
if (key ===
|
|
17
|
+
if (key === 'roadmap')
|
|
18
18
|
metadata.roadmap = value;
|
|
19
|
-
if (key ===
|
|
19
|
+
if (key === 'owner')
|
|
20
20
|
metadata.owner = value;
|
|
21
|
-
if (key ===
|
|
21
|
+
if (key === 'date')
|
|
22
22
|
metadata.date = value;
|
|
23
23
|
}
|
|
24
24
|
return metadata;
|
|
@@ -26,7 +26,7 @@ export function parseReportMetadata(markdown) {
|
|
|
26
26
|
export function validateReportMetadata(metadata) {
|
|
27
27
|
const errors = [];
|
|
28
28
|
if (!metadata.task) {
|
|
29
|
-
errors.push(
|
|
29
|
+
errors.push('Missing Task metadata');
|
|
30
30
|
}
|
|
31
31
|
else if (!isValidTaskId(metadata.task)) {
|
|
32
32
|
errors.push(`Invalid Task metadata format: ${metadata.task}`);
|
|
@@ -1,148 +1,148 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { parseReportMetadata, validateReportMetadata, } from
|
|
3
|
-
describe(
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { parseReportMetadata, validateReportMetadata, } from './reports.js';
|
|
3
|
+
describe('reports module', () => {
|
|
4
|
+
describe('parseReportMetadata', () => {
|
|
5
|
+
it('parses task metadata from markdown', () => {
|
|
6
6
|
const markdown = [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
].join(
|
|
7
|
+
'# Task Report',
|
|
8
|
+
'',
|
|
9
|
+
'**Task:** TASK-0001',
|
|
10
|
+
'**Owner:** ai-copilot',
|
|
11
|
+
'**Date:** 2026-02-22',
|
|
12
|
+
'',
|
|
13
|
+
'Some content here',
|
|
14
|
+
].join('\n');
|
|
15
15
|
const metadata = parseReportMetadata(markdown);
|
|
16
|
-
expect(metadata.task).toBe(
|
|
17
|
-
expect(metadata.owner).toBe(
|
|
18
|
-
expect(metadata.date).toBe(
|
|
16
|
+
expect(metadata.task).toBe('TASK-0001');
|
|
17
|
+
expect(metadata.owner).toBe('ai-copilot');
|
|
18
|
+
expect(metadata.date).toBe('2026-02-22');
|
|
19
19
|
});
|
|
20
|
-
it(
|
|
20
|
+
it('parses roadmap metadata from markdown', () => {
|
|
21
21
|
const markdown = [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
].join(
|
|
22
|
+
'# Roadmap Report',
|
|
23
|
+
'',
|
|
24
|
+
'**Roadmap:** ROADMAP-0001',
|
|
25
|
+
'',
|
|
26
|
+
'Some content here',
|
|
27
|
+
].join('\n');
|
|
28
28
|
const metadata = parseReportMetadata(markdown);
|
|
29
|
-
expect(metadata.roadmap).toBe(
|
|
29
|
+
expect(metadata.roadmap).toBe('ROADMAP-0001');
|
|
30
30
|
});
|
|
31
|
-
it(
|
|
31
|
+
it('returns empty object for markdown without metadata', () => {
|
|
32
32
|
const markdown = [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
].join(
|
|
33
|
+
'# Simple Report',
|
|
34
|
+
'',
|
|
35
|
+
'No metadata here',
|
|
36
|
+
].join('\n');
|
|
37
37
|
const metadata = parseReportMetadata(markdown);
|
|
38
38
|
expect(metadata).toEqual({});
|
|
39
39
|
});
|
|
40
|
-
it(
|
|
41
|
-
const metadata = parseReportMetadata(
|
|
40
|
+
it('handles empty string', () => {
|
|
41
|
+
const metadata = parseReportMetadata('');
|
|
42
42
|
expect(metadata).toEqual({});
|
|
43
43
|
});
|
|
44
|
-
it(
|
|
44
|
+
it('handles malformed metadata lines', () => {
|
|
45
45
|
const markdown = [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
].join(
|
|
46
|
+
'# Report',
|
|
47
|
+
'',
|
|
48
|
+
'Task without colon',
|
|
49
|
+
'Not a metadata line',
|
|
50
|
+
':',
|
|
51
|
+
'Task:',
|
|
52
|
+
].join('\n');
|
|
53
53
|
const metadata = parseReportMetadata(markdown);
|
|
54
54
|
expect(metadata).toBeDefined();
|
|
55
55
|
});
|
|
56
|
-
it(
|
|
56
|
+
it('parses metadata in different formats', () => {
|
|
57
57
|
const markdown = [
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
].join(
|
|
58
|
+
'Task: TASK-0001',
|
|
59
|
+
'task: TASK-0002',
|
|
60
|
+
'TASK: TASK-0003',
|
|
61
|
+
' task : TASK-0004 ',
|
|
62
|
+
].join('\n');
|
|
63
63
|
const metadata = parseReportMetadata(markdown);
|
|
64
64
|
expect(metadata.task).toBeDefined();
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
|
-
describe(
|
|
68
|
-
it(
|
|
67
|
+
describe('validateReportMetadata', () => {
|
|
68
|
+
it('validates correct task metadata', () => {
|
|
69
69
|
const metadata = {
|
|
70
|
-
task:
|
|
71
|
-
owner:
|
|
72
|
-
date:
|
|
70
|
+
task: 'TASK-0001',
|
|
71
|
+
owner: 'ai-copilot',
|
|
72
|
+
date: '2026-02-22',
|
|
73
73
|
};
|
|
74
74
|
const result = validateReportMetadata(metadata);
|
|
75
75
|
expect(result.ok).toBe(true);
|
|
76
76
|
expect(result.errors).toEqual([]);
|
|
77
77
|
});
|
|
78
|
-
it(
|
|
78
|
+
it('rejects missing task metadata', () => {
|
|
79
79
|
const metadata = {
|
|
80
|
-
owner:
|
|
80
|
+
owner: 'ai-copilot',
|
|
81
81
|
};
|
|
82
82
|
const result = validateReportMetadata(metadata);
|
|
83
83
|
expect(result.ok).toBe(false);
|
|
84
|
-
expect(result.errors).toContain(
|
|
84
|
+
expect(result.errors).toContain('Missing Task metadata');
|
|
85
85
|
});
|
|
86
|
-
it(
|
|
86
|
+
it('rejects invalid task ID format', () => {
|
|
87
87
|
const metadata = {
|
|
88
|
-
task:
|
|
88
|
+
task: 'invalid-format',
|
|
89
89
|
};
|
|
90
90
|
const result = validateReportMetadata(metadata);
|
|
91
91
|
expect(result.ok).toBe(false);
|
|
92
|
-
expect(result.errors.some((e) => e.includes(
|
|
92
|
+
expect(result.errors.some((e) => e.includes('Invalid Task'))).toBe(true);
|
|
93
93
|
});
|
|
94
|
-
it(
|
|
94
|
+
it('validates optional roadmap metadata', () => {
|
|
95
95
|
const metadata = {
|
|
96
|
-
task:
|
|
97
|
-
roadmap:
|
|
96
|
+
task: 'TASK-0001',
|
|
97
|
+
roadmap: 'ROADMAP-0001',
|
|
98
98
|
};
|
|
99
99
|
const result = validateReportMetadata(metadata);
|
|
100
100
|
expect(result.ok).toBe(true);
|
|
101
101
|
});
|
|
102
|
-
it(
|
|
102
|
+
it('rejects invalid roadmap ID format', () => {
|
|
103
103
|
const metadata = {
|
|
104
|
-
task:
|
|
105
|
-
roadmap:
|
|
104
|
+
task: 'TASK-0001',
|
|
105
|
+
roadmap: 'invalid-roadmap',
|
|
106
106
|
};
|
|
107
107
|
const result = validateReportMetadata(metadata);
|
|
108
108
|
expect(result.ok).toBe(false);
|
|
109
|
-
expect(result.errors.some((e) => e.includes(
|
|
109
|
+
expect(result.errors.some((e) => e.includes('Invalid Roadmap'))).toBe(true);
|
|
110
110
|
});
|
|
111
|
-
it(
|
|
111
|
+
it('handles empty metadata object', () => {
|
|
112
112
|
const metadata = {};
|
|
113
113
|
const result = validateReportMetadata(metadata);
|
|
114
114
|
expect(result.ok).toBe(false);
|
|
115
115
|
expect(result.errors.length).toBeGreaterThan(0);
|
|
116
116
|
});
|
|
117
|
-
it(
|
|
117
|
+
it('collects multiple validation errors', () => {
|
|
118
118
|
const metadata = {
|
|
119
|
-
task:
|
|
120
|
-
roadmap:
|
|
119
|
+
task: 'invalid-task',
|
|
120
|
+
roadmap: 'invalid-roadmap',
|
|
121
121
|
};
|
|
122
122
|
const result = validateReportMetadata(metadata);
|
|
123
123
|
expect(result.ok).toBe(false);
|
|
124
124
|
expect(result.errors.length).toBeGreaterThan(1);
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
|
-
describe(
|
|
128
|
-
it(
|
|
127
|
+
describe('integration', () => {
|
|
128
|
+
it('parses and validates complete report metadata', () => {
|
|
129
129
|
const markdown = [
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
].join(
|
|
130
|
+
'# Task Completion Report',
|
|
131
|
+
'',
|
|
132
|
+
'**Task:** TASK-0001',
|
|
133
|
+
'**Roadmap:** ROADMAP-0002',
|
|
134
|
+
'**Owner:** ai-copilot',
|
|
135
|
+
'**Date:** 2026-02-22',
|
|
136
|
+
'',
|
|
137
|
+
'## Summary',
|
|
138
|
+
'Task completed successfully',
|
|
139
|
+
].join('\n');
|
|
140
140
|
const metadata = parseReportMetadata(markdown);
|
|
141
141
|
const validation = validateReportMetadata(metadata);
|
|
142
|
-
expect(metadata.task).toBe(
|
|
143
|
-
expect(metadata.roadmap).toBe(
|
|
144
|
-
expect(metadata.owner).toBe(
|
|
145
|
-
expect(metadata.date).toBe(
|
|
142
|
+
expect(metadata.task).toBe('TASK-0001');
|
|
143
|
+
expect(metadata.roadmap).toBe('ROADMAP-0002');
|
|
144
|
+
expect(metadata.owner).toBe('ai-copilot');
|
|
145
|
+
expect(metadata.date).toBe('2026-02-22');
|
|
146
146
|
expect(validation.ok).toBe(true);
|
|
147
147
|
});
|
|
148
148
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { registerProjectTools } from
|
|
2
|
-
import { registerTaskTools } from
|
|
3
|
-
import { registerRoadmapTools } from
|
|
1
|
+
import { registerProjectTools } from './project.js';
|
|
2
|
+
import { registerTaskTools } from './task.js';
|
|
3
|
+
import { registerRoadmapTools } from './roadmap.js';
|
|
4
4
|
export function registerTools(server) {
|
|
5
5
|
registerProjectTools(server);
|
|
6
6
|
registerTaskTools(server);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
vi.mock('./project.js', () => ({
|
|
3
|
+
registerProjectTools: vi.fn(),
|
|
4
|
+
}));
|
|
5
|
+
vi.mock('./task.js', () => ({
|
|
6
|
+
registerTaskTools: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
vi.mock('./roadmap.js', () => ({
|
|
9
|
+
registerRoadmapTools: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
import { registerProjectTools } from './project.js';
|
|
12
|
+
import { registerTaskTools } from './task.js';
|
|
13
|
+
import { registerRoadmapTools } from './roadmap.js';
|
|
14
|
+
import { registerTools } from './index.js';
|
|
15
|
+
describe('tools index module', () => {
|
|
16
|
+
it('registers all tool groups', () => {
|
|
17
|
+
const server = {};
|
|
18
|
+
registerTools(server);
|
|
19
|
+
expect(registerProjectTools).toHaveBeenCalledWith(server);
|
|
20
|
+
expect(registerTaskTools).toHaveBeenCalledWith(server);
|
|
21
|
+
expect(registerRoadmapTools).toHaveBeenCalledWith(server);
|
|
22
|
+
});
|
|
23
|
+
});
|