@taiga-ai/mcp-server 0.25.0-rc.472 → 0.26.0-dev.479

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.
Files changed (55) hide show
  1. package/README.md +111 -0
  2. package/dist/index.js +322 -122
  3. package/package.json +15 -2
  4. package/scripts/build.mjs +0 -37
  5. package/scripts/prepare-publish.cjs +0 -49
  6. package/src/client.test.ts +0 -424
  7. package/src/client.ts +0 -976
  8. package/src/config.test.ts +0 -24
  9. package/src/config.ts +0 -28
  10. package/src/index.ts +0 -45
  11. package/src/modern.integration.test.ts +0 -153
  12. package/src/server.integration.test.ts +0 -9075
  13. package/src/server.ts +0 -18
  14. package/src/tools/annotations.ts +0 -38
  15. package/src/tools/autonomy.ts +0 -86
  16. package/src/tools/backlogs.ts +0 -766
  17. package/src/tools/builds.ts +0 -668
  18. package/src/tools/catalogue.test.ts +0 -170
  19. package/src/tools/catalogue.ts +0 -223
  20. package/src/tools/context.ts +0 -513
  21. package/src/tools/documents.ts +0 -375
  22. package/src/tools/errors.ts +0 -172
  23. package/src/tools/hints.ts +0 -99
  24. package/src/tools/inbox.ts +0 -546
  25. package/src/tools/index.ts +0 -239
  26. package/src/tools/inputs.ts +0 -47
  27. package/src/tools/instructions.ts +0 -41
  28. package/src/tools/jobs.ts +0 -90
  29. package/src/tools/knowledge.ts +0 -249
  30. package/src/tools/maintenance.ts +0 -540
  31. package/src/tools/markdown.test.ts +0 -249
  32. package/src/tools/markdown.ts +0 -151
  33. package/src/tools/observe.ts +0 -311
  34. package/src/tools/product-documents.ts +0 -184
  35. package/src/tools/products.ts +0 -529
  36. package/src/tools/prompts.ts +0 -265
  37. package/src/tools/queue.ts +0 -250
  38. package/src/tools/readiness.ts +0 -177
  39. package/src/tools/remote.test.ts +0 -44
  40. package/src/tools/remote.ts +0 -107
  41. package/src/tools/repositories.ts +0 -360
  42. package/src/tools/request-prompt.test.ts +0 -32
  43. package/src/tools/request-prompt.ts +0 -28
  44. package/src/tools/resources.ts +0 -217
  45. package/src/tools/sections.test.ts +0 -143
  46. package/src/tools/sections.ts +0 -287
  47. package/src/tools/setup.ts +0 -455
  48. package/src/tools/summarize.test.ts +0 -20
  49. package/src/tools/summarize.ts +0 -96
  50. package/src/tools/wait.ts +0 -490
  51. package/src/version.test.ts +0 -10
  52. package/src/version.ts +0 -14
  53. package/taiga-ai-mcp-server-0.25.0-rc.472.tgz +0 -0
  54. package/tsconfig.json +0 -21
  55. package/vitest.config.ts +0 -9
@@ -1,249 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { fence, nestHeadings, renderDocument } from './markdown.js';
3
-
4
- describe('fence', () => {
5
- it('is one backtick longer than the longest run inside the body, four at least', () => {
6
- expect(fence('markdown', 'plain')).toBe('````markdown\nplain\n````');
7
- expect(fence('markdown', 'a ````` five-run inside')).toBe('``````markdown\na ````` five-run inside\n``````');
8
- // Two hundred thousand runs would overflow a spread into Math.max.
9
- expect(fence('text', '` '.repeat(200_000)).startsWith('````text\n')).toBe(true);
10
- });
11
- });
12
-
13
- describe('renderDocument', () => {
14
- it('renders a section map as a titled Markdown document with one heading per section', () => {
15
- const rendered = renderDocument({
16
- name: 'Security Policy',
17
- type: 'policy_security',
18
- version: 3,
19
- data: {
20
- sections: {
21
- summary: { source: 'template', priority: 'core', content: ' Controls for code. ' },
22
- scope: { title: 'Scope of the policy', content: 'Everything.' },
23
- legacy: 'bare text',
24
- },
25
- },
26
- });
27
-
28
- expect(rendered.mimeType).toBe('text/markdown');
29
- expect(rendered.text).toBe(
30
- [
31
- '# Security Policy',
32
- '',
33
- 'type: policy_security | version: 3 | core sections: summary',
34
- '',
35
- '## summary',
36
- '',
37
- 'Controls for code.',
38
- '',
39
- '## Scope of the policy',
40
- '',
41
- 'Everything.',
42
- '',
43
- '## legacy',
44
- '',
45
- 'bare text',
46
- '',
47
- ].join('\n')
48
- );
49
- });
50
-
51
- it('tracks fences as CommonMark does: indented, longer closers, info strings, and headings indented up to three spaces', () => {
52
- const body = [
53
- '````',
54
- '```',
55
- '# inside a four-tick fence: a three-tick line does not close it',
56
- '`````',
57
- '## after a five-tick closer',
58
- ' ```sh',
59
- '# indented opener',
60
- ' ```',
61
- ' ## indented heading',
62
- ' # four spaces is code, not a heading',
63
- '```',
64
- '```sh',
65
- '# an opener-with-info inside an open fence is not its closer',
66
- '```',
67
- '## out again',
68
- '```json``` is not a fence opener',
69
- '## still a heading',
70
- '~~~',
71
- '```',
72
- '# backticks inside a tilde fence',
73
- '~~~',
74
- '## last',
75
- ].join('\n');
76
-
77
- expect(nestHeadings(body)).toBe(
78
- [
79
- '````',
80
- '```',
81
- '# inside a four-tick fence: a three-tick line does not close it',
82
- '`````',
83
- '### after a five-tick closer',
84
- ' ```sh',
85
- '# indented opener',
86
- ' ```',
87
- ' ### indented heading',
88
- ' # four spaces is code, not a heading',
89
- '```',
90
- '```sh',
91
- '# an opener-with-info inside an open fence is not its closer',
92
- '```',
93
- '### out again',
94
- '```json``` is not a fence opener',
95
- '### still a heading',
96
- '~~~',
97
- '```',
98
- '# backticks inside a tilde fence',
99
- '~~~',
100
- '### last',
101
- ].join('\n')
102
- );
103
- });
104
-
105
- it("edits by the parser's offsets: CRLF and lone CR line endings, a tab after a container marker, an empty heading, an HTML block, deep nesting", () => {
106
- expect(nestHeadings('```\r\n# in\r\n```\r\n# out')).toBe('```\r\n# in\r\n```\r\n### out');
107
- expect(nestHeadings('x\ry\n# h\n## i')).toBe('x\ry\n### h\n### i');
108
- expect(nestHeadings('>\t# q\n-\t# x')).toBe('>\t### q\n-\t### x');
109
- expect(nestHeadings('#\n##\n# x')).toBe('###\n###\n### x');
110
- // A heading-shaped line inside an HTML block is HTML to a CommonMark reader, so it stays.
111
- expect(nestHeadings('<details>\n<summary>s</summary>\n## inside\n</details>\n\n## after')).toBe(
112
- '<details>\n<summary>s</summary>\n## inside\n</details>\n\n### after'
113
- );
114
- // Deeper than a recursive walk could take: nested quotes, which the
115
- // parser handles in milliseconds (nested list markers it does not).
116
- const deep = `${'> '.repeat(20_000)}# deep`;
117
- expect(nestHeadings(deep)).toBe(`${'> '.repeat(20_000)}### deep`);
118
- });
119
-
120
- it('turns a setext heading into the ATX heading one level down, inside a quote too, and leaves a thematic break alone', () => {
121
- expect(nestHeadings('> Quoted\n> ---\ntext\n\n---\n\nAfter\n===')).toBe('> ### Quoted\ntext\n\n---\n\n### After');
122
- // Serialized from the parsed content: a quote's prefix on a continuation
123
- // line, a hard line break, a leading # that is not an ATX marker, a
124
- // trailing # run that would read as a closing sequence, inline markup.
125
- expect(nestHeadings('> A\n> B\n> ---')).toBe('> ### A B');
126
- expect(nestHeadings('> > A\n> > B\n> > ===')).toBe('> > ### A B');
127
- expect(nestHeadings('Title\\\ntwo\n---')).toBe('### Title two');
128
- expect(nestHeadings('#1 priority\n===')).toBe('### #1 priority');
129
- expect(nestHeadings('Title ##\n---')).toBe('### Title #\\#');
130
- expect(nestHeadings('*em* and `code` [l](u)\n===')).toBe('### *em* and `code` [l](u)');
131
- });
132
-
133
- it('reads containers as CommonMark does: a fence in a list item, headings in list items and quotes, a setext heading', () => {
134
- const body = [
135
- '- ```md',
136
- ' # literal inside a fence that a list item opened',
137
- ' ```',
138
- '- ## in a list item',
139
- '> ## quoted',
140
- '> # indented code inside a quote',
141
- 'Setext',
142
- '======',
143
- 'Two line',
144
- 'setext two',
145
- '---',
146
- '## atx after',
147
- ].join('\n');
148
-
149
- expect(nestHeadings(body)).toBe(
150
- [
151
- '- ```md',
152
- ' # literal inside a fence that a list item opened',
153
- ' ```',
154
- '- ### in a list item',
155
- '> ### quoted',
156
- '> # indented code inside a quote',
157
- '### Setext',
158
- '### Two line setext two',
159
- '### atx after',
160
- ].join('\n')
161
- );
162
- });
163
-
164
- it("nests a section body's own headings under the section heading, leaving fenced code alone", () => {
165
- const rendered = renderDocument({
166
- name: 'Threat Model',
167
- type: 'threat_model',
168
- data: {
169
- sections: {
170
- overview: {
171
- content: [
172
- '# Overview',
173
- 'Text.',
174
- '## Attack vectors',
175
- '##### Deep',
176
- '###### Deepest stays',
177
- '```md',
178
- '# not a heading',
179
- '```',
180
- '~~~',
181
- '## nor this',
182
- '~~~',
183
- '## After the fences',
184
- '#hashtag is not a heading',
185
- ].join('\n'),
186
- },
187
- },
188
- },
189
- });
190
-
191
- expect(rendered.text).toBe(
192
- [
193
- '# Threat Model',
194
- '',
195
- 'type: threat_model',
196
- '',
197
- '## overview',
198
- '',
199
- '### Overview',
200
- 'Text.',
201
- '### Attack vectors',
202
- '###### Deep',
203
- '###### Deepest stays',
204
- '```md',
205
- '# not a heading',
206
- '```',
207
- '~~~',
208
- '## nor this',
209
- '~~~',
210
- '### After the fences',
211
- '#hashtag is not a heading',
212
- '',
213
- ].join('\n')
214
- );
215
- });
216
-
217
- it('carries what sits beside the sections as JSON, so nothing is lost', () => {
218
- const rendered = renderDocument({
219
- name: 'Design Standards',
220
- type: 'design_standards',
221
- data: { sections: { summary: { content: 'Colors.' } }, tokens: { primary: '#000' } },
222
- });
223
-
224
- expect(rendered.text).toContain('## Other data\n\n````json\n{\n "tokens": {\n "primary": "#000"\n }\n}\n````');
225
- });
226
-
227
- it('describes large data beside the sections instead of inlining it, and skips empty keys', () => {
228
- const rendered = renderDocument({
229
- name: 'Design Standards',
230
- type: 'design_standards',
231
- data: { sections: { summary: { content: 'Colors.' } }, tokens: null, components: { big: 'x'.repeat(25_000) } },
232
- });
233
-
234
- expect(rendered.text).toContain('## Other data');
235
- expect(rendered.text).toContain(
236
- 'Beside its sections this document carries components (25010 characters); read them with the get tool as JSON.'
237
- );
238
- expect(rendered.text).not.toContain('tokens');
239
- });
240
-
241
- it('hands over a document that is not a section map as JSON', () => {
242
- const doc = { type: 'ui_direction', version: 1, data: { picks: { palette: 'ocean' } } };
243
-
244
- const rendered = renderDocument(doc);
245
-
246
- expect(rendered.mimeType).toBe('application/json');
247
- expect(JSON.parse(rendered.text)).toEqual(doc);
248
- });
249
- });
@@ -1,151 +0,0 @@
1
- import { fromMarkdown } from 'mdast-util-from-markdown';
2
- import { toMarkdown } from 'mdast-util-to-markdown';
3
- import type { Heading, Node, Parent, PhrasingContent } from 'mdast';
4
- import { isRecord, isSectionObject, sectionContent, sectionsOf, type StoredDocument } from './sections.js';
5
-
6
- /** A document as rendered: its data, and the facts its title and header line name. */
7
- interface RenderedDocument extends StoredDocument {
8
- name?: unknown;
9
- type?: unknown;
10
- }
11
-
12
- /**
13
- * A document as text an IDE can attach: the name as the title, one line of
14
- * facts, each section as a heading with its text. Policies, instructions and
15
- * product documents are section maps; what a document carries beside its
16
- * sections (design tokens, components) follows as JSON or, past a size, is
17
- * described, and a document that is no section map at all is handed over as
18
- * JSON.
19
- */
20
- export function renderDocument(doc: RenderedDocument): { mimeType: string; text: string } {
21
- const data = isRecord(doc.data) ? doc.data : null;
22
- const sections = data ? sectionsOf(data) : null;
23
- if (!data || !sections) return { mimeType: 'application/json', text: JSON.stringify(doc, null, 2) };
24
-
25
- const title = typeof doc.name === 'string' ? doc.name : String(doc.type ?? 'Document');
26
- const lines = [`# ${title}`];
27
- const facts = (['type', 'origin', 'version', 'stage', 'derivationOutdated'] as const)
28
- .filter((key) => doc[key] !== undefined && doc[key] !== null)
29
- .map((key) => `${key}: ${String(doc[key])}`);
30
- // Which sections are core is stated here, from metadata, not marked in
31
- // the headings: a heading is text a section could forge, this line is not.
32
- const core = Object.keys(sections).filter((id) => {
33
- const section = sections[id];
34
- return isSectionObject(section) && section.priority === 'core';
35
- });
36
- if (core.length > 0) facts.push(`core sections: ${core.join(', ')}`);
37
- if (facts.length > 0) lines.push('', facts.join(' | '));
38
- for (const id of Object.keys(sections)) {
39
- const content = sectionContent(sections, id);
40
- if (content === null) continue;
41
- lines.push('', `## ${sectionTitle(sections[id], id)}`, '', nestHeadings(content.trim()));
42
- }
43
- const { sections: _sections, ...rest } = data;
44
- const other = Object.fromEntries(Object.entries(rest).filter(([, value]) => !isEmpty(value)));
45
- if (Object.keys(other).length > 0) {
46
- const json = JSON.stringify(other, null, 2);
47
- lines.push('', '## Other data', '');
48
- if (json.length > OTHER_DATA_LIMIT) {
49
- // Design tokens and component sources run to hundreds of kilobytes;
50
- // an attachment is not the place for them.
51
- const keys = Object.entries(other).map(([key, value]) => `${key} (${JSON.stringify(value).length} characters)`);
52
- lines.push(`Beside its sections this document carries ${keys.join(', ')}; read them with the get tool as JSON.`);
53
- } else {
54
- lines.push(fence('json', json));
55
- }
56
- }
57
- return { mimeType: 'text/markdown', text: `${lines.join('\n')}\n` };
58
- }
59
-
60
- /** Past this many characters, data beside the sections is described, not inlined. */
61
- const OTHER_DATA_LIMIT = 20_000;
62
-
63
- const isEmpty = (value: unknown) =>
64
- value === null ||
65
- value === undefined ||
66
- (Array.isArray(value) && value.length === 0) ||
67
- (isRecord(value) && Object.keys(value).length === 0);
68
-
69
- /**
70
- * A section body's own headings, one level down, so they sit under the
71
- * section heading instead of beside it: a derived document writes each
72
- * section with its own H2. A CommonMark parser says what is a heading, so
73
- * fenced code, indented code, list items and block quotes are read as a
74
- * reader's renderer reads them, and a heading-shaped line inside an HTML
75
- * block is HTML, not a heading, and stays. Every heading lands at least one
76
- * level under the section's H2, so an H1 and an H2 both become H3 and the
77
- * rest move one down, H6 at most: an ATX heading (the # kind, an empty one
78
- * included) takes the marks it needs; a setext heading (underlined)
79
- * becomes the ATX heading of that depth, serialized from its parsed
80
- * content (so a quote's prefix, a hard line break or a trailing # run are
81
- * handled as Markdown, not as text). The edits go by the parser's
82
- * character offsets, so no line counting of ours can disagree with its own.
83
- */
84
- export function nestHeadings(body: string): string {
85
- const edits: { start: number; end: number; text: string }[] = [];
86
- // An explicit stack, and no spread into push: a body of thousands of
87
- // nested quotes, or a list of very many items, must not overflow a walk.
88
- const pending: Node[] = [fromMarkdown(body)];
89
- while (pending.length > 0) {
90
- const node = pending.pop()!;
91
- if (
92
- node.type === 'heading' &&
93
- node.position?.start.offset !== undefined &&
94
- node.position.end.offset !== undefined
95
- ) {
96
- const start = node.position.start.offset;
97
- const end = node.position.end.offset;
98
- const heading = node as Heading;
99
- const target = nestedDepth(heading.depth);
100
- const atx = /^#{1,6}(\s|$)/.test(body.slice(start, start + 7));
101
- if (atx && target > heading.depth) edits.push({ start, end: start, text: '#'.repeat(target - heading.depth) });
102
- else if (!atx) edits.push({ start, end, text: atxOf(target, heading.children) });
103
- }
104
- if ('children' in node) for (const child of (node as Parent).children) pending.push(child);
105
- }
106
- let out = body;
107
- for (const edit of edits.sort((a, b) => b.start - a.start)) {
108
- out = `${out.slice(0, edit.start)}${edit.text}${out.slice(edit.end)}`;
109
- }
110
- return out;
111
- }
112
-
113
- /** Under the section's H2: at least H3, one level down otherwise, H6 at most. */
114
- const nestedDepth = (depth: number): Heading['depth'] => Math.min(6, Math.max(3, depth + 1)) as Heading['depth'];
115
-
116
- /** An ATX heading of this depth from parsed content, its line breaks turned into spaces: an ATX heading is one line. */
117
- function atxOf(depth: Heading['depth'], children: PhrasingContent[]): string {
118
- const oneLine = (nodes: PhrasingContent[]): PhrasingContent[] =>
119
- nodes.map((child) => {
120
- if (child.type === 'break') return { type: 'text', value: ' ' };
121
- if (child.type === 'text') return { ...child, value: child.value.replace(/\r\n|\r|\n/g, ' ') };
122
- return 'children' in child ? { ...child, children: oneLine(child.children as PhrasingContent[]) } : child;
123
- });
124
- return toMarkdown({ type: 'heading', depth, children: oneLine(children) }).trimEnd();
125
- }
126
-
127
- /** The section's title when it carries one, else its id. */
128
- function sectionTitle(section: unknown, id: string): string {
129
- return isSectionObject(section) && typeof section.title === 'string' && section.title.trim()
130
- ? section.title.trim()
131
- : id;
132
- }
133
-
134
- /** The mime type a read will answer with, for a list entry that must not disagree with it. */
135
- export function mimeTypeOf(doc: StoredDocument): string {
136
- const data = isRecord(doc.data) ? doc.data : null;
137
- return data && sectionsOf(data) ? 'text/markdown' : 'application/json';
138
- }
139
-
140
- /**
141
- * A fence the body cannot close: one backtick longer than the longest run
142
- * inside it, four at least.
143
- */
144
- export function fence(lang: string, body: string): string {
145
- // A loop, not a spread into Math.max: a document with very many backtick
146
- // runs would exceed the argument limit and throw.
147
- let longest = 0;
148
- for (const match of body.matchAll(/`+/g)) longest = Math.max(longest, match[0].length);
149
- const marks = '`'.repeat(Math.max(4, longest + 1));
150
- return `${marks}${lang}\n${body.trim()}\n${marks}`;
151
- }