@taiga-ai/mcp-server 0.25.0-rc.474 → 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.
- package/README.md +111 -0
- package/dist/index.js +292 -115
- package/package.json +15 -2
- package/scripts/build.mjs +0 -37
- package/scripts/prepare-publish.cjs +0 -49
- package/src/client.test.ts +0 -424
- package/src/client.ts +0 -977
- package/src/config.test.ts +0 -24
- package/src/config.ts +0 -28
- package/src/index.ts +0 -45
- package/src/modern.integration.test.ts +0 -153
- package/src/server.integration.test.ts +0 -9214
- package/src/server.ts +0 -18
- package/src/tools/annotations.ts +0 -38
- package/src/tools/autonomy.ts +0 -86
- package/src/tools/backlogs.ts +0 -802
- package/src/tools/builds.ts +0 -668
- package/src/tools/catalogue.test.ts +0 -170
- package/src/tools/catalogue.ts +0 -223
- package/src/tools/context.ts +0 -513
- package/src/tools/documents.ts +0 -375
- package/src/tools/errors.ts +0 -172
- package/src/tools/hints.ts +0 -99
- package/src/tools/inbox.ts +0 -546
- package/src/tools/index.ts +0 -239
- package/src/tools/inputs.ts +0 -47
- package/src/tools/instructions.ts +0 -41
- package/src/tools/jobs.ts +0 -90
- package/src/tools/knowledge.ts +0 -249
- package/src/tools/maintenance.ts +0 -547
- package/src/tools/markdown.test.ts +0 -249
- package/src/tools/markdown.ts +0 -151
- package/src/tools/observe.ts +0 -311
- package/src/tools/product-documents.ts +0 -184
- package/src/tools/products.ts +0 -529
- package/src/tools/prompts.ts +0 -265
- package/src/tools/queue.ts +0 -250
- package/src/tools/readiness.ts +0 -177
- package/src/tools/remote.test.ts +0 -44
- package/src/tools/remote.ts +0 -107
- package/src/tools/repositories.ts +0 -360
- package/src/tools/request-prompt.test.ts +0 -32
- package/src/tools/request-prompt.ts +0 -28
- package/src/tools/resources.ts +0 -217
- package/src/tools/sections.test.ts +0 -143
- package/src/tools/sections.ts +0 -287
- package/src/tools/setup.ts +0 -455
- package/src/tools/summarize.test.ts +0 -20
- package/src/tools/summarize.ts +0 -96
- package/src/tools/wait.ts +0 -490
- package/src/version.test.ts +0 -10
- package/src/version.ts +0 -14
- package/taiga-ai-mcp-server-0.25.0-rc.474.tgz +0 -0
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -9
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { McpServer, InMemoryTransport } from '@modelcontextprotocol/server';
|
|
3
|
-
import { Client } from '@modelcontextprotocol/client';
|
|
4
|
-
import {
|
|
5
|
-
FACTORY_DOCUMENT_TYPES,
|
|
6
|
-
POLICY_TYPES,
|
|
7
|
-
PRODUCT_DOCUMENT_TYPES,
|
|
8
|
-
PRODUCT_STATUSES,
|
|
9
|
-
TENANT_DOCUMENT_TYPES,
|
|
10
|
-
} from '@taiga/types';
|
|
11
|
-
import { TaigaClient } from '../client.js';
|
|
12
|
-
import { registerAllTools, writeNotice } from './index.js';
|
|
13
|
-
import { SERVER_INSTRUCTIONS, SERVER_INSTRUCTIONS_LIMIT } from './instructions.js';
|
|
14
|
-
import { PRODUCT_DOCUMENT_NAMES } from './catalogue.js';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* What the model reads must match what exists. The walk-through that led to
|
|
18
|
-
* this file found two policy types named in a description that no tenant
|
|
19
|
-
* has; a model that follows a description into a type the API refuses learns
|
|
20
|
-
* to stop asking.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const ALL_CAPABILITIES = [
|
|
24
|
-
'products:read',
|
|
25
|
-
'factories:read',
|
|
26
|
-
'factories:documents:write',
|
|
27
|
-
'tenant:documents:read',
|
|
28
|
-
'tenant:documents:write',
|
|
29
|
-
'backlog:read',
|
|
30
|
-
'backlog:write',
|
|
31
|
-
'products:write',
|
|
32
|
-
'customDocuments:read',
|
|
33
|
-
'customDocuments:write',
|
|
34
|
-
'customDocuments:delete',
|
|
35
|
-
'factories:write',
|
|
36
|
-
'vcs:read',
|
|
37
|
-
'vcs:manage',
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
async function connected() {
|
|
41
|
-
vi.stubGlobal(
|
|
42
|
-
'fetch',
|
|
43
|
-
vi.fn().mockResolvedValue({
|
|
44
|
-
ok: true,
|
|
45
|
-
status: 200,
|
|
46
|
-
json: async () => ({
|
|
47
|
-
data: { userId: 'u', tenantId: 't', email: 'e', role: 'owner', capabilities: ALL_CAPABILITIES },
|
|
48
|
-
}),
|
|
49
|
-
text: async () => '',
|
|
50
|
-
})
|
|
51
|
-
);
|
|
52
|
-
const taigaClient = new TaigaClient({ baseUrl: 'https://api.test.com', apiKey: 'k' });
|
|
53
|
-
await taigaClient.introspect();
|
|
54
|
-
const server = new McpServer({ name: 't', version: '0' });
|
|
55
|
-
registerAllTools(server, taigaClient);
|
|
56
|
-
const client = new Client({ name: 'c', version: '0' });
|
|
57
|
-
const [ct, st] = InMemoryTransport.createLinkedPair();
|
|
58
|
-
await Promise.all([server.connect(st), client.connect(ct)]);
|
|
59
|
-
return client;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function allTools() {
|
|
63
|
-
return (await (await connected()).listTools()).tools;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Every description a client sees: tools, resource templates, prompts and their arguments. */
|
|
67
|
-
async function everyDescription(): Promise<string> {
|
|
68
|
-
const client = await connected();
|
|
69
|
-
const [tools, templates, prompts] = await Promise.all([
|
|
70
|
-
client.listTools(),
|
|
71
|
-
client.listResourceTemplates(),
|
|
72
|
-
client.listPrompts(),
|
|
73
|
-
]);
|
|
74
|
-
return JSON.stringify([tools.tools, templates.resourceTemplates, prompts.prompts]);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Every `policy_*` token in a text. */
|
|
78
|
-
const policyTokens = (text: string) => [...new Set(text.match(/\bpolicy_[a-z_]+\b/g) ?? [])];
|
|
79
|
-
|
|
80
|
-
describe('tool descriptions against the catalogue', () => {
|
|
81
|
-
beforeEach(() => vi.restoreAllMocks());
|
|
82
|
-
|
|
83
|
-
it('names no policy type the catalogue lacks, in any description, schema, template or prompt', async () => {
|
|
84
|
-
const text = (await everyDescription()) + SERVER_INSTRUCTIONS;
|
|
85
|
-
|
|
86
|
-
for (const token of policyTokens(text)) {
|
|
87
|
-
expect(TENANT_DOCUMENT_TYPES as readonly string[], token).toContain(token);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('names no tool that is not registered, in any description, template, prompt or the instructions', async () => {
|
|
92
|
-
const client = await connected();
|
|
93
|
-
const registered = new Set((await client.listTools()).tools.map((t) => t.name));
|
|
94
|
-
const text = (await everyDescription()) + SERVER_INSTRUCTIONS;
|
|
95
|
-
|
|
96
|
-
for (const name of new Set(text.match(/\btaiga_[a-z_]+\b/g) ?? [])) {
|
|
97
|
-
// Prompts carry the same prefix; they are registered too.
|
|
98
|
-
const prompts = new Set((await client.listPrompts()).prompts.map((p) => p.name));
|
|
99
|
-
expect(registered.has(name) || prompts.has(name), name).toBe(true);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('names every policy where the company documents are explained', async () => {
|
|
104
|
-
const tools = await allTools();
|
|
105
|
-
// One tool lists and reads the company documents.
|
|
106
|
-
const get = tools.find((t) => t.name === 'taiga_get_document')!;
|
|
107
|
-
const list = get;
|
|
108
|
-
const typeEnum = (get.inputSchema.properties as Record<string, { enum?: string[] }>)['type']!.enum;
|
|
109
|
-
|
|
110
|
-
for (const type of POLICY_TYPES) {
|
|
111
|
-
expect(list.description, type).toContain(type);
|
|
112
|
-
}
|
|
113
|
-
expect(typeEnum).toEqual([...TENANT_DOCUMENT_TYPES]);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('offers exactly the product statuses, product document types and origins the API has', async () => {
|
|
117
|
-
const tools = await allTools();
|
|
118
|
-
const props = (name: string) =>
|
|
119
|
-
tools.find((t) => t.name === name)!.inputSchema.properties as Record<string, { enum?: string[] }>;
|
|
120
|
-
|
|
121
|
-
expect(props('taiga_list_products')['status']!.enum).toEqual([...PRODUCT_STATUSES]);
|
|
122
|
-
expect(props('taiga_get_product_document')['type']!.enum).toEqual([...PRODUCT_DOCUMENT_TYPES]);
|
|
123
|
-
expect(props('taiga_get_product_document')['origin']!.enum).toEqual(['plan', 'reality']);
|
|
124
|
-
expect(props('taiga_get_factory_document')['type']!.enum).toEqual([...FACTORY_DOCUMENT_TYPES]);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('has a display name for every product document type, the one the web uses', () => {
|
|
128
|
-
expect(Object.keys(PRODUCT_DOCUMENT_NAMES).sort()).toEqual([...PRODUCT_DOCUMENT_TYPES].sort());
|
|
129
|
-
// apps/web/src/lib/documentTypes.ts: "Look & Feel", not "UI Direction".
|
|
130
|
-
expect(PRODUCT_DOCUMENT_NAMES.ui_direction).toBe('Look & Feel');
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('explains both timelines where a product document is read', async () => {
|
|
134
|
-
const tools = await allTools();
|
|
135
|
-
const origin = (
|
|
136
|
-
tools.find((t) => t.name === 'taiga_get_product_document')!.inputSchema.properties as Record<
|
|
137
|
-
string,
|
|
138
|
-
{ description?: string }
|
|
139
|
-
>
|
|
140
|
-
)['origin']!.description;
|
|
141
|
-
expect(origin).toMatch(/plan: .*discovery/);
|
|
142
|
-
expect(origin).toMatch(/reality: .*merged pull request/);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('fits the whole instructions inside what Claude Code keeps, and says where to start and when to ask', () => {
|
|
146
|
-
// Claude Code loads only tool names and the instructions until the model
|
|
147
|
-
// searches, and cuts the instructions at 2,048 characters (PRO-2800).
|
|
148
|
-
expect(SERVER_INSTRUCTIONS.length).toBeLessThanOrEqual(SERVER_INSTRUCTIONS_LIMIT);
|
|
149
|
-
for (const must of ['taiga_get_product_context', 'ask-first', 'NOT_FOUND', 'initiative', 'Only in the web']) {
|
|
150
|
-
expect(SERVER_INSTRUCTIONS).toContain(must);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// PRO-2778: the notice is what tells the model a tool writes, so a tool must say whether it does.
|
|
156
|
-
describe('writeNotice', () => {
|
|
157
|
-
it('refuses a tool whose annotations do not say whether it is read-only', () => {
|
|
158
|
-
expect(() => writeNotice('taiga_x', undefined)).toThrow(/taiga_x: annotations must say/);
|
|
159
|
-
expect(() => writeNotice('taiga_x', {})).toThrow(/annotations must say/);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('says nothing for a read, and what a write does otherwise', () => {
|
|
163
|
-
expect(writeNotice('r', { readOnlyHint: true })).toBe('');
|
|
164
|
-
expect(writeNotice('w', { readOnlyHint: false })).toBe('Writes. ');
|
|
165
|
-
expect(writeNotice('o', { readOnlyHint: false, destructiveHint: true })).toBe('Ask first (others see it). ');
|
|
166
|
-
expect(writeNotice('taiga_queue_initiative', { readOnlyHint: false, destructiveHint: true })).toBe(
|
|
167
|
-
'Ask first (paid agent work). '
|
|
168
|
-
);
|
|
169
|
-
});
|
|
170
|
-
});
|
package/src/tools/catalogue.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What exists, from the one place that knows: the shared types package.
|
|
3
|
-
*
|
|
4
|
-
* Tool descriptions are what the model reads to decide what to call, and a
|
|
5
|
-
* description that names a document type the catalogue lacks sends it down a
|
|
6
|
-
* dead end (the walk-through that led here found `policy_acceptable_use` and
|
|
7
|
-
* `policy_ip` named in a description and nowhere else). Every list of types,
|
|
8
|
-
* origins and statuses a tool shows is derived from these constants, and a
|
|
9
|
-
* test holds the descriptions to them.
|
|
10
|
-
*/
|
|
11
|
-
import {
|
|
12
|
-
FACTORY_DOCUMENT_TYPES,
|
|
13
|
-
POLICY_TYPES,
|
|
14
|
-
PRODUCT_DOCUMENT_DISPLAY_NAMES,
|
|
15
|
-
PRODUCT_DOCUMENT_ORIGINS,
|
|
16
|
-
PRODUCT_DOCUMENT_TYPES,
|
|
17
|
-
PRODUCT_STATUSES,
|
|
18
|
-
REALITY_DERIVED_DOCUMENT_TYPES,
|
|
19
|
-
TENANT_DOCUMENT_TYPES,
|
|
20
|
-
type ProductDocumentOrigin,
|
|
21
|
-
type ProductDocumentType,
|
|
22
|
-
type TenantDocumentType,
|
|
23
|
-
} from '@taiga/types';
|
|
24
|
-
|
|
25
|
-
export {
|
|
26
|
-
FACTORY_DOCUMENT_TYPES,
|
|
27
|
-
POLICY_TYPES,
|
|
28
|
-
PRODUCT_DOCUMENT_ORIGINS,
|
|
29
|
-
PRODUCT_DOCUMENT_TYPES,
|
|
30
|
-
PRODUCT_STATUSES,
|
|
31
|
-
TENANT_DOCUMENT_TYPES,
|
|
32
|
-
};
|
|
33
|
-
export type { ProductDocumentOrigin, ProductDocumentType };
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Display names for every product document type. The reviewable nine come
|
|
37
|
-
* from the shared map the web uses; the three the web never asks a person to
|
|
38
|
-
* review are named here, and the record type keeps the two sets complete.
|
|
39
|
-
*/
|
|
40
|
-
export const PRODUCT_DOCUMENT_NAMES: Record<ProductDocumentType, string> = {
|
|
41
|
-
...PRODUCT_DOCUMENT_DISPLAY_NAMES,
|
|
42
|
-
// "Look & Feel" is what the web calls it (apps/web/src/lib/documentTypes.ts);
|
|
43
|
-
// an agent and a person should name the same document the same way.
|
|
44
|
-
ui_direction: 'Look & Feel',
|
|
45
|
-
codebase_snapshot: 'Codebase Snapshot',
|
|
46
|
-
product_instructions: 'Product Instructions',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export function productDocumentName(type: string): string {
|
|
50
|
-
return (PRODUCT_DOCUMENT_NAMES as Record<string, string>)[type] ?? type;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** The organization documents that are not policies: goals, standards, voice, instructions. */
|
|
54
|
-
export const OTHER_TENANT_DOCUMENT_TYPES: readonly TenantDocumentType[] = TENANT_DOCUMENT_TYPES.filter(
|
|
55
|
-
(type) => !(POLICY_TYPES as readonly string[]).includes(type)
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
/** `a, b, c` for a description. */
|
|
59
|
-
export function named(types: readonly string[]): string {
|
|
60
|
-
return types.join(', ');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* The two document timelines of a product (ADR-017), in the words the tools
|
|
65
|
-
* and the server instructions use. One place, so the list tool, the read
|
|
66
|
-
* tool and the instructions cannot explain them three different ways.
|
|
67
|
-
*/
|
|
68
|
-
export const ORIGIN_MEANING: Record<ProductDocumentOrigin, string> = {
|
|
69
|
-
plan: 'plan: the discovery documents, what is meant to be built.',
|
|
70
|
-
reality:
|
|
71
|
-
'reality: derived from the code on the main branch, what is built; an imported product has them from ' +
|
|
72
|
-
'its import, any other once its first initiative is done (a merged pull request) or code is pushed.',
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Which timeline is a product's own (the API's documentOriginForSettings):
|
|
77
|
-
* an imported product has no plan chain by design, so its documents are on
|
|
78
|
-
* the reality side; everything else starts on the plan side. Derived from the
|
|
79
|
-
* same settings key the API reads, so the two cannot disagree.
|
|
80
|
-
*/
|
|
81
|
-
export function isImportedProduct(product: ProductSettingsView | null | undefined): boolean {
|
|
82
|
-
const settings = product?.settings;
|
|
83
|
-
return (
|
|
84
|
-
typeof settings === 'object' &&
|
|
85
|
-
settings !== null &&
|
|
86
|
-
(settings as { projectType?: unknown }).projectType === 'brownfield'
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* The document types no derivation ever writes: a person records or writes
|
|
92
|
-
* them, so they live on the plan side of every product, imported or not.
|
|
93
|
-
*/
|
|
94
|
-
export const PLAN_ONLY_DOCUMENT_TYPES: readonly ProductDocumentType[] = PRODUCT_DOCUMENT_TYPES.filter(
|
|
95
|
-
(type) => !(REALITY_DERIVED_DOCUMENT_TYPES as readonly string[]).includes(type)
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* The timeline a read should default to: the plan side for a type only a
|
|
100
|
-
* person writes, else the product's own side (reality for an imported
|
|
101
|
-
* product, plan for every other).
|
|
102
|
-
*/
|
|
103
|
-
export function defaultOriginFor(
|
|
104
|
-
product: ProductSettingsView | null | undefined,
|
|
105
|
-
type?: ProductDocumentType
|
|
106
|
-
): ProductDocumentOrigin {
|
|
107
|
-
if (type !== undefined && PLAN_ONLY_DOCUMENT_TYPES.includes(type)) return 'plan';
|
|
108
|
-
return isImportedProduct(product) ? 'reality' : 'plan';
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** The product settings the notes read: the import marker and where the import got to. */
|
|
112
|
-
export interface ProductSettingsView {
|
|
113
|
-
settings?: unknown;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Where an imported product's import got to (`settings.importStatus`, written
|
|
118
|
-
* by the API: pending, analyzing, complete, cancelled, failed). Unknown values
|
|
119
|
-
* read as "not known", never as a promise.
|
|
120
|
-
*/
|
|
121
|
-
function importStatusOf(product: ProductSettingsView | null | undefined): string | null {
|
|
122
|
-
const settings = product?.settings;
|
|
123
|
-
if (typeof settings !== 'object' || settings === null) return null;
|
|
124
|
-
const status = (settings as { importStatus?: unknown }).importStatus;
|
|
125
|
-
return typeof status === 'string' ? status : null;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** Why a side of the timeline is empty, for a list or a miss that shows one. */
|
|
129
|
-
export function emptyOriginNote(
|
|
130
|
-
origin: ProductDocumentOrigin,
|
|
131
|
-
product: ProductSettingsView | null | undefined
|
|
132
|
-
): string {
|
|
133
|
-
const imported = isImportedProduct(product);
|
|
134
|
-
if (origin === 'plan') {
|
|
135
|
-
return imported
|
|
136
|
-
? 'This product was imported, so no discovery chain was generated for it: its derived documents are on ' +
|
|
137
|
-
`the reality side. Only the documents a person writes (${PLAN_ONLY_DOCUMENT_TYPES.map(productDocumentName).join(', ')}) ` +
|
|
138
|
-
'would appear here.'
|
|
139
|
-
: 'No plan documents are published yet: they are written during discovery in Taiga.';
|
|
140
|
-
}
|
|
141
|
-
if (!imported) {
|
|
142
|
-
return (
|
|
143
|
-
'No reality documents yet: they are derived from the code once the first initiative is done (a merged ' +
|
|
144
|
-
'pull request marks it done) or the repository is pushed to. Until then read the plan side.'
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
switch (importStatusOf(product)) {
|
|
148
|
-
case 'pending':
|
|
149
|
-
return 'No reality documents yet: the import has not started. Link the repository, then taiga_start_import.';
|
|
150
|
-
// generating_docs is the retired name for the same in-flight state
|
|
151
|
-
// (PRO-1161); rows that predate it can still carry it.
|
|
152
|
-
case 'analyzing':
|
|
153
|
-
case 'generating_docs':
|
|
154
|
-
return 'No reality documents yet: the import is running and will derive them from the code.';
|
|
155
|
-
case 'cancelled':
|
|
156
|
-
return 'No reality documents: the import was cancelled before it derived any. taiga_start_import runs it again.';
|
|
157
|
-
case 'failed':
|
|
158
|
-
return 'No reality documents: the import failed before it derived any. taiga_start_import runs it again.';
|
|
159
|
-
default:
|
|
160
|
-
return 'No reality documents: the import derives them from the code, and none have been published.';
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* What a product in setup waits for, as the next call to make. Nothing on a
|
|
166
|
-
* draft product says it otherwise: discoveryStep names the step and
|
|
167
|
-
* importStatus the import, and the way on from each lives in tool
|
|
168
|
-
* descriptions an agent reads only when it already knows which. Null once the
|
|
169
|
-
* product is active.
|
|
170
|
-
*/
|
|
171
|
-
export function setupNextStep(
|
|
172
|
-
product: (ProductSettingsView & { status?: unknown; discoveryStep?: unknown }) | null | undefined
|
|
173
|
-
): string | null {
|
|
174
|
-
if (product?.status !== 'draft') return null;
|
|
175
|
-
if (isImportedProduct(product)) {
|
|
176
|
-
switch (importStatusOf(product)) {
|
|
177
|
-
case 'analyzing':
|
|
178
|
-
case 'generating_docs':
|
|
179
|
-
return (
|
|
180
|
-
'The import is running and derives the documents from the code (taiga_wait_for_job follows its job). ' +
|
|
181
|
-
'Then review them, and taiga_finish_discovery opens the product.'
|
|
182
|
-
);
|
|
183
|
-
case 'complete':
|
|
184
|
-
return (
|
|
185
|
-
'The import is done: review the reality documents (taiga_get_product_document without type), then ' +
|
|
186
|
-
'taiga_finish_discovery opens the product.'
|
|
187
|
-
);
|
|
188
|
-
case 'failed':
|
|
189
|
-
case 'cancelled':
|
|
190
|
-
return 'The last import did not finish: taiga_start_import runs it again.';
|
|
191
|
-
default:
|
|
192
|
-
return (
|
|
193
|
-
'Link its repository if it has none (taiga_list_linkable_repositories, taiga_link_repository), then ' +
|
|
194
|
-
'taiga_start_import: the import derives the documents from the code and runs by itself.'
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
// Unknown is not "not started": a write's answer leaves the step out, and
|
|
199
|
-
// the default below would send a product at its documents back to the
|
|
200
|
-
// conversation.
|
|
201
|
-
if (product.discoveryStep === undefined) return null;
|
|
202
|
-
switch (product.discoveryStep) {
|
|
203
|
-
case 'documents':
|
|
204
|
-
return (
|
|
205
|
-
'The specification is done; the other documents are generated from it ' +
|
|
206
|
-
'(taiga_generate_product_documents starts the ones still missing). Once every required one is ' +
|
|
207
|
-
'published, taiga_finish_discovery opens the product.'
|
|
208
|
-
);
|
|
209
|
-
case 'checks':
|
|
210
|
-
return 'The documents are published; the checks are stamped in the web, then taiga_finish_discovery opens the product.';
|
|
211
|
-
default:
|
|
212
|
-
return (
|
|
213
|
-
'Discovery is a conversation held in the web: the person describes the product there and the ' +
|
|
214
|
-
'specification comes out of it. The other documents follow, then taiga_finish_discovery opens the product.'
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/** What discoveryStep says, for a description that shows it. */
|
|
220
|
-
export const DISCOVERY_STEP_MEANING =
|
|
221
|
-
'discoveryStep is where a product in setup is: context (not started), conversation or import (the ' +
|
|
222
|
-
'specification being written or derived from code), documents (generated and reviewed), checks (stamped in ' +
|
|
223
|
-
'the web); null once it is active.';
|