@taiga-ai/mcp-server 0.18.0-rc.445 → 0.19.0-dev.451
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/dist/index.js +1128 -650
- package/package.json +1 -1
- package/src/client.test.ts +2 -2
- package/src/client.ts +201 -145
- package/src/modern.integration.test.ts +1 -1
- package/src/server.integration.test.ts +782 -243
- package/src/tools/autonomy.ts +3 -5
- package/src/tools/backlogs.ts +182 -72
- package/src/tools/builds.ts +142 -53
- package/src/tools/catalogue.test.ts +28 -22
- package/src/tools/context.ts +126 -56
- package/src/tools/documents.ts +12 -5
- package/src/tools/errors.ts +40 -2
- package/src/tools/hints.ts +37 -9
- package/src/tools/inbox.ts +149 -28
- package/src/tools/index.ts +15 -1
- package/src/tools/instructions.ts +32 -154
- package/src/tools/jobs.ts +6 -5
- package/src/tools/knowledge.ts +4 -6
- package/src/tools/maintenance.ts +53 -32
- package/src/tools/markdown.ts +13 -5
- package/src/tools/observe.ts +32 -14
- package/src/tools/product-documents.ts +4 -6
- package/src/tools/products.ts +32 -27
- package/src/tools/prompts.ts +14 -20
- package/src/tools/queue.ts +47 -26
- package/src/tools/readiness.ts +172 -0
- package/src/tools/repositories.ts +11 -9
- package/src/tools/resources.ts +16 -19
- package/src/tools/sections.test.ts +3 -3
- package/src/tools/sections.ts +57 -16
- package/src/tools/setup.ts +47 -51
- package/src/tools/summarize.test.ts +20 -0
- package/src/tools/summarize.ts +17 -13
- package/src/tools/wait.ts +39 -26
- package/taiga-ai-mcp-server-0.19.0-dev.451.tgz +0 -0
- package/tsconfig.json +1 -5
- package/taiga-ai-mcp-server-0.18.0-rc.445.tgz +0 -0
package/src/tools/builds.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import
|
|
3
|
+
import {
|
|
4
|
+
type BuildRunStep,
|
|
5
|
+
type BuildRunWithContext,
|
|
6
|
+
BUILD_RUN_STATUSES,
|
|
7
|
+
dispositionOf,
|
|
8
|
+
type BuildRunStatus,
|
|
9
|
+
} from '@taiga/types';
|
|
10
|
+
import type { TaigaClient, WithoutTenant } from '../client.js';
|
|
4
11
|
import { OPERATE, READ_ONLY } from './annotations.js';
|
|
5
12
|
import { idInput, pagination, productId } from './inputs.js';
|
|
6
13
|
import { isNetworkFailure, runTool, toolError, toolResult } from './errors.js';
|
|
@@ -43,7 +50,9 @@ const RUN_OUTCOME_FIELDS = [
|
|
|
43
50
|
'failedReason',
|
|
44
51
|
'completedSteps',
|
|
45
52
|
'totalSteps',
|
|
53
|
+
'prNumber',
|
|
46
54
|
'prUrl',
|
|
55
|
+
'prMerged',
|
|
47
56
|
'actions',
|
|
48
57
|
] as const;
|
|
49
58
|
|
|
@@ -54,7 +63,10 @@ const RUN_OUTCOME_FIELDS = [
|
|
|
54
63
|
* stalled in the hardening pass after the steps and the retry continues it.
|
|
55
64
|
* Null for a run still going or that finished.
|
|
56
65
|
*/
|
|
57
|
-
export function stoppedIn(
|
|
66
|
+
export function stoppedIn(
|
|
67
|
+
run: Pick<WithoutTenant<BuildRunWithContext>, 'prMerged' | 'status' | 'hardeningResult'>,
|
|
68
|
+
steps: readonly Pick<WithoutTenant<BuildRunStep>, 'status' | 'stepOrder' | 'title'>[]
|
|
69
|
+
): string | null {
|
|
58
70
|
// A merged run is finished, whatever its status still says: the merge is
|
|
59
71
|
// latched apart from the status, which can stay at a stale failed or stopped.
|
|
60
72
|
if (run.prMerged === true || (run.status !== 'failed' && run.status !== 'stopped')) return null;
|
|
@@ -77,10 +89,80 @@ export function stoppedIn(run: Record<string, unknown>, steps: Record<string, un
|
|
|
77
89
|
return 'after the steps and hardening, at the pull request';
|
|
78
90
|
}
|
|
79
91
|
|
|
92
|
+
/**
|
|
93
|
+
* The disposition with the merge latch authoritative: a run whose pull
|
|
94
|
+
* request merged landed, whatever settled status it kept when the completion
|
|
95
|
+
* write after the merge failed (build.ts allows a merged run to stay failed
|
|
96
|
+
* or stopped). A run still executing keeps its in-flight disposition.
|
|
97
|
+
*/
|
|
98
|
+
export function landedDisposition(status: BuildRunStatus, prMerged: boolean): string {
|
|
99
|
+
const disposition = dispositionOf(status, prMerged);
|
|
100
|
+
return prMerged && disposition !== 'in_flight' ? 'succeeded' : disposition;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** What each executing status is doing, in a phrase. */
|
|
104
|
+
const RUNNING: Partial<Record<BuildRunStatus, string>> = {
|
|
105
|
+
cloning: 'setting up the workspace',
|
|
106
|
+
implementing: 'implementing the steps',
|
|
107
|
+
hardening: 'hardening: running the security scanners and fixing what they find',
|
|
108
|
+
pushing: 'pushing the branch',
|
|
109
|
+
ci_checking: "waiting for the pull request's CI",
|
|
110
|
+
ci_fixing: 'fixing what CI found',
|
|
111
|
+
reviewing: 'answering review comments on the pull request',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* How a run stands, in one sentence, with its disposition: the meaning the
|
|
116
|
+
* status and the pull request facts carry between them
|
|
117
|
+
* (PRO-2800). An agent read `closed` with failedReason null, every step
|
|
118
|
+
* completed and hardening passed, and could not tell what had happened; the
|
|
119
|
+
* status alone cannot say whether a completed run merged (dispositionOf).
|
|
120
|
+
*/
|
|
121
|
+
export function runOutcome(run: Record<string, unknown>): { disposition: string; outcome: string } | null {
|
|
122
|
+
const status = run['status'] as BuildRunStatus;
|
|
123
|
+
if (!(BUILD_RUN_STATUSES as readonly string[]).includes(status)) return null;
|
|
124
|
+
const disposition = landedDisposition(status, run['prMerged'] === true);
|
|
125
|
+
const pr = typeof run['prNumber'] === 'number' ? `PR #${run['prNumber']}` : 'its pull request';
|
|
126
|
+
const progress =
|
|
127
|
+
typeof run['completedSteps'] === 'number' && typeof run['totalSteps'] === 'number'
|
|
128
|
+
? ` (${run['completedSteps']} of ${run['totalSteps']} steps done)`
|
|
129
|
+
: '';
|
|
130
|
+
const reason = typeof run['failedReason'] === 'string' && run['failedReason'] ? `: ${run['failedReason']}` : '';
|
|
131
|
+
const sentence: Record<string, string> = {
|
|
132
|
+
in_flight: status === 'pending' ? 'Queued: waiting to start.' : `Running: ${RUNNING[status] ?? status}${progress}.`,
|
|
133
|
+
gated: `Waiting for a person to review and merge ${pr}.`,
|
|
134
|
+
succeeded: `Merged: ${pr} is in ${typeof run['baseBranch'] === 'string' ? run['baseBranch'] : 'the base branch'}.`,
|
|
135
|
+
unmerged:
|
|
136
|
+
typeof run['prNumber'] === 'number' || run['prUrl']
|
|
137
|
+
? `Settled without merging: ${pr} was left unmerged.`
|
|
138
|
+
: 'Settled without a merged pull request.',
|
|
139
|
+
failed: `Failed${reason}${progress}.`,
|
|
140
|
+
stopped: `Stopped before it finished${progress}.`,
|
|
141
|
+
rejected: `${pr} was closed without merging, so the work did not land.`,
|
|
142
|
+
};
|
|
143
|
+
return { disposition, outcome: sentence[disposition] ?? status };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* A run row as an agent reads it: its outcome added, and prDraft only where
|
|
148
|
+
* there is a pull request. The column defaults to true, so a run that never
|
|
149
|
+
* opened one said it had a draft (PRO-2800).
|
|
150
|
+
*/
|
|
151
|
+
export function readableRun(run: Record<string, unknown>): Record<string, unknown> {
|
|
152
|
+
const { prDraft, ...rest } = run;
|
|
153
|
+
const said = runOutcome(run);
|
|
154
|
+
return {
|
|
155
|
+
...rest,
|
|
156
|
+
// A PR number proves a PR exists (isSettledWithoutPr); the URL is nullable apart from it.
|
|
157
|
+
...(typeof rest['prNumber'] === 'number' || rest['prUrl'] ? { prDraft } : {}),
|
|
158
|
+
...(said ?? {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
80
162
|
/** How many products the org-wide list names; past this a row keeps its productId only. */
|
|
81
163
|
const NAMED_PRODUCTS = 100;
|
|
82
164
|
|
|
83
|
-
const
|
|
165
|
+
const runId = idInput('run', 'taiga_list_runs gives it');
|
|
84
166
|
|
|
85
167
|
/** A step without its instructions: what it is, how it stands and what it changed. */
|
|
86
168
|
const STEP_SUMMARY_FIELDS = [
|
|
@@ -131,14 +213,16 @@ async function activityRows(
|
|
|
131
213
|
throw error;
|
|
132
214
|
}),
|
|
133
215
|
]);
|
|
134
|
-
const nameById = new Map<string, { name:
|
|
135
|
-
for (const product of
|
|
136
|
-
nameById.set(
|
|
216
|
+
const nameById = new Map<string, { name: string; slug: string }>();
|
|
217
|
+
for (const product of products?.data ?? []) {
|
|
218
|
+
nameById.set(product.id, { name: product.name, slug: product.slug });
|
|
137
219
|
}
|
|
138
|
-
const rows = pickFields(runs.data as Record<string, unknown>[], BUILD_SUMMARY_FIELDS)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
220
|
+
const rows = pickFields(runs.data as Record<string, unknown>[], BUILD_SUMMARY_FIELDS)
|
|
221
|
+
.map(readableRun)
|
|
222
|
+
.map((row) => {
|
|
223
|
+
const product = nameById.get(String(row['productId']));
|
|
224
|
+
return product ? { ...row, product } : row;
|
|
225
|
+
});
|
|
142
226
|
const unnamed = rows.some((row) => !('product' in row));
|
|
143
227
|
// Why a row stayed unnamed, by what the products read said: it failed, it
|
|
144
228
|
// was cut before that product, or it listed everything the key can see and
|
|
@@ -157,24 +241,23 @@ export function registerBuildTools(server: McpServer, client: TaigaClient) {
|
|
|
157
241
|
if (!client.hasCapability('products:read')) return;
|
|
158
242
|
|
|
159
243
|
server.registerTool(
|
|
160
|
-
'
|
|
244
|
+
'taiga_list_runs',
|
|
161
245
|
{
|
|
162
|
-
title: 'List
|
|
246
|
+
title: 'List runs',
|
|
163
247
|
description:
|
|
164
|
-
"List the coding agent's runs, newest first
|
|
165
|
-
'
|
|
166
|
-
'
|
|
167
|
-
'
|
|
168
|
-
'
|
|
169
|
-
'the runs of
|
|
170
|
-
'
|
|
171
|
-
'
|
|
172
|
-
'A run of a deleted initiative keeps its title.',
|
|
248
|
+
"List the coding agent's runs, newest first. A run is one attempt at building an initiative: its branch, " +
|
|
249
|
+
'pull request and step progress. Each row says how it stands in one sentence (outcome) and as a ' +
|
|
250
|
+
'disposition: in_flight (the agent is working), gated (a person owes a review and merge), succeeded ' +
|
|
251
|
+
'(merged), failed, stopped, rejected (the pull request was closed without merging) or unmerged. actions ' +
|
|
252
|
+
'name what a person may do with it. With productId, the runs of that product, optionally of one ' +
|
|
253
|
+
'initiative; without it, the runs of every product the key can see, each row naming its product, and ' +
|
|
254
|
+
'active: true narrows to the runs executing now. taiga_get_run reads one with its steps. Work is carried ' +
|
|
255
|
+
'on from the initiative (taiga_resume_initiative), not the run.',
|
|
173
256
|
inputSchema: z.object({
|
|
174
257
|
productId: productId.optional(),
|
|
175
|
-
|
|
258
|
+
initiativeId: idInput('initiative', 'taiga_list_initiatives (query finds one by title) gives it')
|
|
176
259
|
.optional()
|
|
177
|
-
.describe("Only this
|
|
260
|
+
.describe("Only this initiative's runs -- get from taiga_list_initiatives; needs productId"),
|
|
178
261
|
active: z
|
|
179
262
|
.boolean()
|
|
180
263
|
.optional()
|
|
@@ -188,10 +271,10 @@ export function registerBuildTools(server: McpServer, client: TaigaClient) {
|
|
|
188
271
|
},
|
|
189
272
|
(params) =>
|
|
190
273
|
runTool(async () => {
|
|
191
|
-
if (!params.productId && params.
|
|
274
|
+
if (!params.productId && params.initiativeId) {
|
|
192
275
|
return toolError(
|
|
193
276
|
'VALIDATION_ERROR',
|
|
194
|
-
'
|
|
277
|
+
'initiativeId narrows the runs of one product: pass its productId as well (the initiative carries it).'
|
|
195
278
|
);
|
|
196
279
|
}
|
|
197
280
|
// The product route has no such filter; a silently ignored one would
|
|
@@ -203,9 +286,16 @@ export function registerBuildTools(server: McpServer, client: TaigaClient) {
|
|
|
203
286
|
);
|
|
204
287
|
}
|
|
205
288
|
if (params.productId) {
|
|
206
|
-
const result = await client.listBuildRuns(params.productId,
|
|
289
|
+
const result = await client.listBuildRuns(params.productId, {
|
|
290
|
+
backlogItemId: params.initiativeId,
|
|
291
|
+
limit: params.limit,
|
|
292
|
+
cursor: params.cursor,
|
|
293
|
+
});
|
|
207
294
|
return toolResult(
|
|
208
|
-
page(
|
|
295
|
+
page(
|
|
296
|
+
pickFields(result.data as Record<string, unknown>[], BUILD_SUMMARY_FIELDS).map(readableRun),
|
|
297
|
+
result.nextCursor
|
|
298
|
+
)
|
|
209
299
|
);
|
|
210
300
|
}
|
|
211
301
|
return toolResult(await activityRows(client, params));
|
|
@@ -213,21 +303,21 @@ export function registerBuildTools(server: McpServer, client: TaigaClient) {
|
|
|
213
303
|
);
|
|
214
304
|
|
|
215
305
|
server.registerTool(
|
|
216
|
-
'
|
|
306
|
+
'taiga_get_run',
|
|
217
307
|
{
|
|
218
|
-
title: 'Get
|
|
308
|
+
title: 'Get run',
|
|
219
309
|
description:
|
|
220
|
-
|
|
221
|
-
'status, commit, files changed,
|
|
222
|
-
"reported.
|
|
223
|
-
'
|
|
224
|
-
'
|
|
225
|
-
|
|
226
|
-
'
|
|
227
|
-
'
|
|
228
|
-
'Build. A maintenance run has no initiative and is retried from its finding.',
|
|
310
|
+
'Get one run with its steps: how it stands (outcome, disposition), its pull request and hardening, and ' +
|
|
311
|
+
"each step's title, status, commit, files changed, verification and headline, the first paragraph of " +
|
|
312
|
+
"what the agent reported. A step's verification is Taiga's own check after the step (tests, lint, build), " +
|
|
313
|
+
'independent of what the agent says it ran: a docs-only step can show tests failed because the ' +
|
|
314
|
+
"repository's tests fail. A run that failed or was stopped says where (stoppedIn) and when it last made " +
|
|
315
|
+
"progress (lastProgressAt). Pass full: true for each step's complete instructions and report, several " +
|
|
316
|
+
'thousand tokens on a long run. Its actions name what the initiative accepts: resume and ' +
|
|
317
|
+
'retry_from_failed_step are taiga_resume_initiative; start_over is in the web only; to stop it, move the ' +
|
|
318
|
+
'initiative out of Build. A maintenance run has no initiative and is retried from its finding.',
|
|
229
319
|
inputSchema: z.object({
|
|
230
|
-
|
|
320
|
+
runId,
|
|
231
321
|
full: z
|
|
232
322
|
.boolean()
|
|
233
323
|
.optional()
|
|
@@ -240,16 +330,16 @@ export function registerBuildTools(server: McpServer, client: TaigaClient) {
|
|
|
240
330
|
// Two routes, one answer: fanned out here rather than in the model's
|
|
241
331
|
// context, like the six-tool pattern asks.
|
|
242
332
|
const [run, steps] = await Promise.all([
|
|
243
|
-
client.getBuildRun(params.
|
|
244
|
-
client.listBuildRunSteps(params.
|
|
333
|
+
client.getBuildRun(params.runId),
|
|
334
|
+
client.listBuildRunSteps(params.runId),
|
|
245
335
|
]);
|
|
246
|
-
const runRow = run.data
|
|
247
|
-
const rows = steps.data
|
|
336
|
+
const runRow = run.data;
|
|
337
|
+
const rows = steps.data;
|
|
248
338
|
const where = stoppedIn(runRow, rows);
|
|
249
339
|
const stop = where ? { stoppedIn: where } : {};
|
|
250
|
-
if (params.full) return toolResult({ ...runRow, ...stop, steps: steps.data });
|
|
340
|
+
if (params.full) return toolResult({ ...readableRun(runRow), ...stop, steps: steps.data });
|
|
251
341
|
return toolResult({
|
|
252
|
-
...runRow,
|
|
342
|
+
...readableRun(runRow),
|
|
253
343
|
...stop,
|
|
254
344
|
steps: pickFields(rows, STEP_SUMMARY_FIELDS).map((step, index) => ({
|
|
255
345
|
...step,
|
|
@@ -262,23 +352,22 @@ export function registerBuildTools(server: McpServer, client: TaigaClient) {
|
|
|
262
352
|
if (!client.hasCapability('products:write')) return;
|
|
263
353
|
|
|
264
354
|
server.registerTool(
|
|
265
|
-
'
|
|
355
|
+
'taiga_stop_run',
|
|
266
356
|
{
|
|
267
|
-
title: 'Stop a
|
|
357
|
+
title: 'Stop a run',
|
|
268
358
|
description:
|
|
269
359
|
'The emergency brake: stop a run the coding agent is executing when its initiative cannot be moved. The ' +
|
|
270
|
-
'way to stop work is on the initiative: move it out of Build (
|
|
360
|
+
'way to stop work is on the initiative: move it out of Build (taiga_move_initiative), which stops the ' +
|
|
271
361
|
'plan or run in flight. Use this only for a run whose initiative does not offer that (it is finished, ' +
|
|
272
362
|
'canceled or gone) yet the run is still going. The agent halts, its branch and any draft pull request ' +
|
|
273
|
-
'stay. Only a run whose actions include stop; the API refuses the rest with CONFLICT.
|
|
274
|
-
|
|
275
|
-
inputSchema: z.object({ buildId }),
|
|
363
|
+
'stay. Only a run whose actions include stop; the API refuses the rest with CONFLICT.',
|
|
364
|
+
inputSchema: z.object({ runId }),
|
|
276
365
|
annotations: OPERATE,
|
|
277
366
|
},
|
|
278
367
|
(params) =>
|
|
279
368
|
runTool(async () => {
|
|
280
|
-
const result = await client.stopBuildRun(params.
|
|
281
|
-
return toolResult(pickFields([result.data as Record<string, unknown>], RUN_OUTCOME_FIELDS)[0]);
|
|
369
|
+
const result = await client.stopBuildRun(params.runId);
|
|
370
|
+
return toolResult(readableRun(pickFields([result.data as Record<string, unknown>], RUN_OUTCOME_FIELDS)[0]!));
|
|
282
371
|
})
|
|
283
372
|
);
|
|
284
373
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '@taiga/types';
|
|
11
11
|
import { TaigaClient } from '../client.js';
|
|
12
12
|
import { registerAllTools, writeNotice } from './index.js';
|
|
13
|
-
import {
|
|
13
|
+
import { SERVER_INSTRUCTIONS, SERVER_INSTRUCTIONS_LIMIT } from './instructions.js';
|
|
14
14
|
import { PRODUCT_DOCUMENT_NAMES } from './catalogue.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -104,11 +104,10 @@ describe('tool descriptions against the catalogue', () => {
|
|
|
104
104
|
const tools = await allTools();
|
|
105
105
|
const list = tools.find((t) => t.name === 'taiga_list_documents')!;
|
|
106
106
|
const get = tools.find((t) => t.name === 'taiga_get_document')!;
|
|
107
|
-
const typeEnum = (get.inputSchema.properties as Record<string, { enum?: string[] }>)
|
|
107
|
+
const typeEnum = (get.inputSchema.properties as Record<string, { enum?: string[] }>)['type']!.enum;
|
|
108
108
|
|
|
109
109
|
for (const type of POLICY_TYPES) {
|
|
110
110
|
expect(list.description, type).toContain(type);
|
|
111
|
-
expect(SERVER_INSTRUCTIONS, type).toContain(type);
|
|
112
111
|
}
|
|
113
112
|
expect(typeEnum).toEqual([...TENANT_DOCUMENT_TYPES]);
|
|
114
113
|
});
|
|
@@ -118,10 +117,10 @@ describe('tool descriptions against the catalogue', () => {
|
|
|
118
117
|
const props = (name: string) =>
|
|
119
118
|
tools.find((t) => t.name === name)!.inputSchema.properties as Record<string, { enum?: string[] }>;
|
|
120
119
|
|
|
121
|
-
expect(props('taiga_list_products')
|
|
122
|
-
expect(props('taiga_get_product_document')
|
|
123
|
-
expect(props('taiga_get_product_document')
|
|
124
|
-
expect(props('taiga_get_factory_document')
|
|
120
|
+
expect(props('taiga_list_products')['status']!.enum).toEqual([...PRODUCT_STATUSES]);
|
|
121
|
+
expect(props('taiga_get_product_document')['type']!.enum).toEqual([...PRODUCT_DOCUMENT_TYPES]);
|
|
122
|
+
expect(props('taiga_get_product_document')['origin']!.enum).toEqual(['plan', 'reality']);
|
|
123
|
+
expect(props('taiga_get_factory_document')['type']!.enum).toEqual([...FACTORY_DOCUMENT_TYPES]);
|
|
125
124
|
});
|
|
126
125
|
|
|
127
126
|
it('has a display name for every product document type, the one the web uses', () => {
|
|
@@ -130,23 +129,30 @@ describe('tool descriptions against the catalogue', () => {
|
|
|
130
129
|
expect(PRODUCT_DOCUMENT_NAMES.ui_direction).toBe('Look & Feel');
|
|
131
130
|
});
|
|
132
131
|
|
|
133
|
-
it('explains both timelines
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
it('explains both timelines where a product document is read', async () => {
|
|
133
|
+
const tools = await allTools();
|
|
134
|
+
const origin = (
|
|
135
|
+
tools.find((t) => t.name === 'taiga_get_product_document')!.inputSchema.properties as Record<
|
|
136
|
+
string,
|
|
137
|
+
{ description?: string }
|
|
138
|
+
>
|
|
139
|
+
)['origin']!.description;
|
|
140
|
+
expect(origin).toMatch(/plan: .*discovery/);
|
|
141
|
+
expect(origin).toMatch(/reality: .*merged pull request/);
|
|
141
142
|
});
|
|
142
143
|
|
|
143
|
-
it('
|
|
144
|
-
// Claude Code
|
|
145
|
-
//
|
|
146
|
-
expect(SERVER_INSTRUCTIONS.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
it('fits the whole instructions inside what Claude Code keeps, and says where to start and when to ask', () => {
|
|
145
|
+
// Claude Code loads only tool names and the instructions until the model
|
|
146
|
+
// searches, and cuts the instructions at 2,048 characters (PRO-2800).
|
|
147
|
+
expect(SERVER_INSTRUCTIONS.length).toBeLessThanOrEqual(SERVER_INSTRUCTIONS_LIMIT);
|
|
148
|
+
for (const must of [
|
|
149
|
+
'taiga_get_product_context',
|
|
150
|
+
'Before you write',
|
|
151
|
+
'NOT_FOUND',
|
|
152
|
+
'initiative',
|
|
153
|
+
'Only in the web',
|
|
154
|
+
]) {
|
|
155
|
+
expect(SERVER_INSTRUCTIONS).toContain(must);
|
|
150
156
|
}
|
|
151
157
|
});
|
|
152
158
|
});
|