draftgo-cli 3.0.39 → 3.0.43
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 +66 -15
- package/package.json +4 -2
- package/resources/skill/SKILL.md +53 -15
- package/resources/skill/init/SKILL.md +5 -3
- package/resources/skill/manifest.json +4 -2
- package/resources/skill/push/SKILL.md +2 -0
- package/resources/skill/references/architecture.md +1 -1
- package/resources/skill/references/checkout.md +3 -2
- package/resources/skill/references/frontend.md +18 -287
- package/resources/skill/references/mcp.md +6 -2
- package/resources/skill/references/parallel.md +11 -6
- package/resources/skill/references/runtime.md +1 -1
- package/resources/skill/references/ui-protocol.md +1 -1
- package/resources/skill/scripts/README.md +2 -0
- package/src/changelog.js +276 -0
- package/src/cli.js +2 -0
- package/src/commandRegistry.js +2 -0
- package/src/commands/changelog.js +24 -0
- package/src/commands/connect.js +21 -6
- package/src/commands/context.js +27 -0
- package/src/commands/help.js +16 -2
- package/src/commands/map.js +64 -15
- package/src/commands/mcp.js +3 -2
- package/src/commands/update.js +16 -1
- package/src/context/index.js +576 -0
- package/src/mcp/client.js +170 -14
- package/src/mcp/parallel.js +31 -0
- package/src/mcp/protocol.js +2 -1
- package/src/projectConfig.js +39 -5
- package/src/skill.js +65 -3
- package/src/timeout.js +18 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const pkg = require('../../package.json');
|
|
7
|
+
const { RESOURCES_DIR } = require('../paths');
|
|
8
|
+
const { loadProjectConfig } = require('../projectConfig');
|
|
9
|
+
const { TOOL_NAMES, openToolSession } = require('../mcp/tools');
|
|
10
|
+
const { readInstalledVersion } = require('../skill');
|
|
11
|
+
|
|
12
|
+
const SKILL_ROOT = path.join(RESOURCES_DIR, 'skill');
|
|
13
|
+
const MAX_STRUCTURED_TEXT_BYTES = 1024 * 1024;
|
|
14
|
+
const LONG_CONTENT_RESOURCE_TYPES = Object.freeze([
|
|
15
|
+
'pages',
|
|
16
|
+
'navigations',
|
|
17
|
+
'docs/articles',
|
|
18
|
+
]);
|
|
19
|
+
const DB_META_LIST_PATH = '/api/db-meta';
|
|
20
|
+
const DB_META_PAGE_SIZE = 100;
|
|
21
|
+
const ROOT_SECTIONS = [
|
|
22
|
+
'核心边界',
|
|
23
|
+
'强制预读:Reference 优先于 MCP',
|
|
24
|
+
'并行开发与所有权',
|
|
25
|
+
'标准工作流',
|
|
26
|
+
'安全规则',
|
|
27
|
+
'验证与交付',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const TASK_PROFILES = Object.freeze({
|
|
31
|
+
frontend: {
|
|
32
|
+
apiQueries: [
|
|
33
|
+
{ resource_type: 'pages' },
|
|
34
|
+
{ resource_type: 'navigations' },
|
|
35
|
+
{ resource_type: 'db' },
|
|
36
|
+
],
|
|
37
|
+
references: [
|
|
38
|
+
{ file: 'SKILL.md', headings: ROOT_SECTIONS },
|
|
39
|
+
{ file: 'references/architecture.md' },
|
|
40
|
+
{ file: 'references/modules.md' },
|
|
41
|
+
{ file: 'references/frontend.md' },
|
|
42
|
+
{ file: 'references/runtime.md' },
|
|
43
|
+
{ file: 'references/app-api.md' },
|
|
44
|
+
{ file: 'references/security.md' },
|
|
45
|
+
{ file: 'references/checkout.md' },
|
|
46
|
+
{ file: 'references/parallel.md' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
data: {
|
|
50
|
+
apiQueries: [{ resource_type: 'db' }],
|
|
51
|
+
references: [
|
|
52
|
+
{ file: 'SKILL.md', headings: ROOT_SECTIONS },
|
|
53
|
+
{ file: 'references/modules.md' },
|
|
54
|
+
{ file: 'references/data.md' },
|
|
55
|
+
{ file: 'references/db-relations.md' },
|
|
56
|
+
{ file: 'references/security.md' },
|
|
57
|
+
{ file: 'references/mcp.md', headings: ['边界', '协议与安全'] },
|
|
58
|
+
{ file: 'references/parallel.md' },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
'custom-service': {
|
|
62
|
+
apiQueries: [{ resource_type: 'custom_scripts' }],
|
|
63
|
+
references: [
|
|
64
|
+
{ file: 'SKILL.md', headings: ROOT_SECTIONS },
|
|
65
|
+
{ file: 'references/modules.md' },
|
|
66
|
+
{ file: 'references/custom-services.md' },
|
|
67
|
+
{ file: 'references/data.md' },
|
|
68
|
+
{ file: 'references/security.md' },
|
|
69
|
+
{ file: 'references/parallel.md' },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
aihub: {
|
|
73
|
+
apiQueries: [
|
|
74
|
+
{ resource_type: 'aihub' },
|
|
75
|
+
{ resource_type: 'agents' },
|
|
76
|
+
],
|
|
77
|
+
references: [
|
|
78
|
+
{ file: 'SKILL.md', headings: ROOT_SECTIONS },
|
|
79
|
+
{ file: 'references/aihub.md' },
|
|
80
|
+
{ file: 'references/chat-sdk.md' },
|
|
81
|
+
{ file: 'references/security.md' },
|
|
82
|
+
{ file: 'references/parallel.md' },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
content: {
|
|
86
|
+
apiQueries: [
|
|
87
|
+
{ resource_type: 'pages' },
|
|
88
|
+
{ resource_type: 'navigations' },
|
|
89
|
+
{ resource_type: 'docs/articles' },
|
|
90
|
+
],
|
|
91
|
+
references: [
|
|
92
|
+
{ file: 'SKILL.md', headings: ROOT_SECTIONS },
|
|
93
|
+
{ file: 'references/architecture.md' },
|
|
94
|
+
{ file: 'references/modules.md' },
|
|
95
|
+
{ file: 'references/checkout.md' },
|
|
96
|
+
{ file: 'references/security.md' },
|
|
97
|
+
{ file: 'references/parallel.md' },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
project: {
|
|
101
|
+
apiQueries: [
|
|
102
|
+
{ resource_type: 'pages' },
|
|
103
|
+
{ resource_type: 'navigations' },
|
|
104
|
+
{ resource_type: 'docs/articles' },
|
|
105
|
+
{ resource_type: 'db' },
|
|
106
|
+
],
|
|
107
|
+
references: [
|
|
108
|
+
{ file: 'SKILL.md', headings: ROOT_SECTIONS },
|
|
109
|
+
{ file: 'references/architecture.md' },
|
|
110
|
+
{ file: 'references/modules.md' },
|
|
111
|
+
{ file: 'references/mcp.md' },
|
|
112
|
+
{ file: 'references/checkout.md' },
|
|
113
|
+
{ file: 'references/parallel.md' },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const TASK_ALIASES = Object.freeze({
|
|
119
|
+
backend: 'custom-service',
|
|
120
|
+
service: 'custom-service',
|
|
121
|
+
ui: 'frontend',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
function normalizeTask(value) {
|
|
125
|
+
const requested = String(value || '').trim().toLowerCase();
|
|
126
|
+
const task = Object.prototype.hasOwnProperty.call(TASK_ALIASES, requested)
|
|
127
|
+
? TASK_ALIASES[requested]
|
|
128
|
+
: requested;
|
|
129
|
+
if (!Object.prototype.hasOwnProperty.call(TASK_PROFILES, task)) {
|
|
130
|
+
const supported = Object.keys(TASK_PROFILES).join(', ');
|
|
131
|
+
throw new Error(`Unsupported context task: ${requested || '(empty)'}. Supported tasks: ${supported}.`);
|
|
132
|
+
}
|
|
133
|
+
return task;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function lineNumberAt(source, offset) {
|
|
137
|
+
let line = 1;
|
|
138
|
+
for (let index = 0; index < offset; index += 1) {
|
|
139
|
+
if (source.charCodeAt(index) === 10) line += 1;
|
|
140
|
+
}
|
|
141
|
+
return line;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function sectionEndLine(content, startLine) {
|
|
145
|
+
let newlines = 0;
|
|
146
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
147
|
+
if (content.charCodeAt(index) === 10) newlines += 1;
|
|
148
|
+
}
|
|
149
|
+
return startLine + newlines - (content.endsWith('\n') ? 1 : 0);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function extractSections(source, requestedHeadings, sourcePath) {
|
|
153
|
+
const matches = [...source.matchAll(/^## ([^\r\n]+)\r?$/gm)];
|
|
154
|
+
const wanted = requestedHeadings ? new Set(requestedHeadings) : null;
|
|
155
|
+
const sections = [];
|
|
156
|
+
|
|
157
|
+
if (matches.length && matches[0].index > 0) {
|
|
158
|
+
const content = source.slice(0, matches[0].index);
|
|
159
|
+
const title = content.match(/^# ([^\r\n]+)\r?$/m);
|
|
160
|
+
sections.push({
|
|
161
|
+
source: {
|
|
162
|
+
kind: 'bundled_reference',
|
|
163
|
+
path: sourcePath,
|
|
164
|
+
heading: title ? title[1] : '(preamble)',
|
|
165
|
+
line_start: 1,
|
|
166
|
+
line_end: sectionEndLine(content, 1),
|
|
167
|
+
sha256: crypto.createHash('sha256').update(content, 'utf8').digest('hex'),
|
|
168
|
+
},
|
|
169
|
+
content,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (let index = 0; index < matches.length; index += 1) {
|
|
174
|
+
const match = matches[index];
|
|
175
|
+
const heading = match[1];
|
|
176
|
+
if (wanted && !wanted.has(heading)) continue;
|
|
177
|
+
const start = match.index;
|
|
178
|
+
const end = index + 1 < matches.length ? matches[index + 1].index : source.length;
|
|
179
|
+
const content = source.slice(start, end);
|
|
180
|
+
const startLine = lineNumberAt(source, start);
|
|
181
|
+
sections.push({
|
|
182
|
+
source: {
|
|
183
|
+
kind: 'bundled_reference',
|
|
184
|
+
path: sourcePath,
|
|
185
|
+
heading,
|
|
186
|
+
line_start: startLine,
|
|
187
|
+
line_end: sectionEndLine(content, startLine),
|
|
188
|
+
sha256: crypto.createHash('sha256').update(content, 'utf8').digest('hex'),
|
|
189
|
+
},
|
|
190
|
+
content,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (wanted) {
|
|
195
|
+
const found = new Set(matches.map((match) => match[1]));
|
|
196
|
+
const missing = requestedHeadings.filter((heading) => !found.has(heading));
|
|
197
|
+
if (missing.length) {
|
|
198
|
+
throw new Error(`${sourcePath} is missing required sections: ${missing.join(', ')}.`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!sections.length) throw new Error(`${sourcePath} contains no selectable reference sections.`);
|
|
202
|
+
return sections;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function readTaskReferences(taskValue, options = {}) {
|
|
206
|
+
const task = normalizeTask(taskValue);
|
|
207
|
+
const skillRoot = options.skillRoot || SKILL_ROOT;
|
|
208
|
+
return TASK_PROFILES[task].references.flatMap((entry) => {
|
|
209
|
+
const absolute = path.join(skillRoot, entry.file);
|
|
210
|
+
const source = fs.readFileSync(absolute, 'utf8');
|
|
211
|
+
const sourcePath = path.posix.join('resources/skill', entry.file.replace(/\\/g, '/'));
|
|
212
|
+
return extractSections(source, entry.headings, sourcePath);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function rawQuery(session, expectedName, args, server, options = {}) {
|
|
217
|
+
const tool = session.names[expectedName] || expectedName;
|
|
218
|
+
return session.client.toolsCall(tool, args, options).then((result) => ({
|
|
219
|
+
source: {
|
|
220
|
+
kind: 'mcp_tool',
|
|
221
|
+
server,
|
|
222
|
+
tool,
|
|
223
|
+
canonical_tool: expectedName,
|
|
224
|
+
arguments: args,
|
|
225
|
+
},
|
|
226
|
+
result,
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function structuredValueFromRaw(result) {
|
|
231
|
+
let value = result;
|
|
232
|
+
if (value && value.result) value = value.result;
|
|
233
|
+
if (value && value.structuredContent) value = value.structuredContent;
|
|
234
|
+
else if (value && value.structured_content) value = value.structured_content;
|
|
235
|
+
else if (value && Array.isArray(value.content)) {
|
|
236
|
+
const text = value.content.find((part) =>
|
|
237
|
+
part && part.type === 'text' && typeof part.text === 'string');
|
|
238
|
+
if (text) {
|
|
239
|
+
if (Buffer.byteLength(text.text, 'utf8') > MAX_STRUCTURED_TEXT_BYTES) {
|
|
240
|
+
throw new Error('DraftGo MCP pagination result exceeded the structured JSON limit.');
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
value = JSON.parse(text.text);
|
|
244
|
+
} catch {
|
|
245
|
+
throw new Error('DraftGo MCP pagination result did not contain structured JSON.');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (value && value.data) value = value.data;
|
|
250
|
+
if (value && Object.prototype.hasOwnProperty.call(value, 'value')) value = value.value;
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function nextCursorFromRaw(result) {
|
|
255
|
+
const value = structuredValueFromRaw(result);
|
|
256
|
+
if (!value || typeof value !== 'object') return null;
|
|
257
|
+
const hasMore = value.has_more === true || value.hasMore === true;
|
|
258
|
+
let cursor = null;
|
|
259
|
+
let hasCursor = false;
|
|
260
|
+
if (Object.prototype.hasOwnProperty.call(value, 'next_cursor')) {
|
|
261
|
+
cursor = value.next_cursor;
|
|
262
|
+
hasCursor = true;
|
|
263
|
+
} else if (Object.prototype.hasOwnProperty.call(value, 'nextCursor')) {
|
|
264
|
+
cursor = value.nextCursor;
|
|
265
|
+
hasCursor = true;
|
|
266
|
+
} else if (hasMore && Object.prototype.hasOwnProperty.call(value, 'cursor')) {
|
|
267
|
+
cursor = value.cursor;
|
|
268
|
+
hasCursor = true;
|
|
269
|
+
}
|
|
270
|
+
if (hasMore && (!hasCursor || cursor == null || cursor === '')) {
|
|
271
|
+
throw new Error('DraftGo MCP pagination reported more results without a cursor.');
|
|
272
|
+
}
|
|
273
|
+
return hasCursor ? cursor : null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function collectLongContentResources(session, server, options = {}) {
|
|
277
|
+
const sequences = await Promise.all(LONG_CONTENT_RESOURCE_TYPES.map((resourceType) =>
|
|
278
|
+
rawPagedQuery(
|
|
279
|
+
session,
|
|
280
|
+
TOOL_NAMES.resourceList,
|
|
281
|
+
{ resource_type: resourceType, limit: 100 },
|
|
282
|
+
server,
|
|
283
|
+
options,
|
|
284
|
+
)));
|
|
285
|
+
return {
|
|
286
|
+
source: {
|
|
287
|
+
kind: 'mcp_tool_batch',
|
|
288
|
+
server,
|
|
289
|
+
canonical_tool: TOOL_NAMES.resourceList,
|
|
290
|
+
arguments: LONG_CONTENT_RESOURCE_TYPES.map((resourceType) => ({
|
|
291
|
+
resource_type: resourceType,
|
|
292
|
+
limit: 100,
|
|
293
|
+
})),
|
|
294
|
+
},
|
|
295
|
+
pages: sequences.flatMap((sequence) => sequence.pages),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function collectTaskAPIs(session, queries, server, options = {}) {
|
|
300
|
+
const argumentsList = queries.map((query) => ({ ...query, limit: 100 }));
|
|
301
|
+
const sequences = await Promise.all(argumentsList.map((args) =>
|
|
302
|
+
rawPagedQuery(session, TOOL_NAMES.apiSearch, args, server, options)));
|
|
303
|
+
return {
|
|
304
|
+
source: {
|
|
305
|
+
kind: 'mcp_tool_batch',
|
|
306
|
+
server,
|
|
307
|
+
canonical_tool: TOOL_NAMES.apiSearch,
|
|
308
|
+
arguments: argumentsList,
|
|
309
|
+
},
|
|
310
|
+
pages: sequences.flatMap((sequence) => sequence.pages),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function itemsFromRawSequence(sequence) {
|
|
315
|
+
if (!sequence || !Array.isArray(sequence.pages)) return [];
|
|
316
|
+
return sequence.pages.flatMap((page) => {
|
|
317
|
+
const value = structuredValueFromRaw(page);
|
|
318
|
+
if (Array.isArray(value)) return value;
|
|
319
|
+
if (value && Array.isArray(value.items)) return value.items;
|
|
320
|
+
if (value && Array.isArray(value.operations)) return value.operations;
|
|
321
|
+
return [];
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function selectDBMetaListOperation(sequence) {
|
|
326
|
+
return itemsFromRawSequence(sequence).find((operation) => {
|
|
327
|
+
if (!operation || typeof operation !== 'object') return false;
|
|
328
|
+
const method = String(operation.method || '').trim().toUpperCase();
|
|
329
|
+
const resourceType = String(operation.resource_type || '').trim().toLowerCase();
|
|
330
|
+
const operationPath = String(operation.path || operation.path_template || '').trim();
|
|
331
|
+
return method === 'GET'
|
|
332
|
+
&& resourceType === 'db_meta'
|
|
333
|
+
&& operationPath === DB_META_LIST_PATH
|
|
334
|
+
&& operation.destructive !== true;
|
|
335
|
+
}) || null;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function optionalPageInteger(value, field, minimum) {
|
|
339
|
+
if (value == null) return null;
|
|
340
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
341
|
+
throw new Error(`DraftGo db_meta API returned invalid ${field}.`);
|
|
342
|
+
}
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function dbMetaPageInfo(response) {
|
|
347
|
+
let value = structuredValueFromRaw(response);
|
|
348
|
+
if (value && value.response) value = value.response;
|
|
349
|
+
if (value && value.data) value = value.data;
|
|
350
|
+
if (!value || typeof value !== 'object' || !Array.isArray(value.items)) return null;
|
|
351
|
+
return {
|
|
352
|
+
items: value.items,
|
|
353
|
+
total: optionalPageInteger(value.total, 'total', 0),
|
|
354
|
+
page: optionalPageInteger(value.page, 'page', 1),
|
|
355
|
+
pageSize: optionalPageInteger(value.page_size, 'page_size', 1),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function rawPagedQuery(session, expectedName, baseArgs, server, options = {}) {
|
|
360
|
+
const pages = [];
|
|
361
|
+
const seen = new Set();
|
|
362
|
+
const maxPages = options.maxPages == null ? 100 : Number(options.maxPages);
|
|
363
|
+
if (!Number.isSafeInteger(maxPages) || maxPages < 1) {
|
|
364
|
+
throw new TypeError('maxPages must be a positive integer.');
|
|
365
|
+
}
|
|
366
|
+
let cursor = null;
|
|
367
|
+
|
|
368
|
+
for (let page = 0; page < maxPages; page += 1) {
|
|
369
|
+
const args = cursor == null ? { ...baseArgs } : { ...baseArgs, cursor };
|
|
370
|
+
const response = await rawQuery(session, expectedName, args, server, options);
|
|
371
|
+
pages.push(response);
|
|
372
|
+
const next = nextCursorFromRaw(response.result);
|
|
373
|
+
if (next == null || next === '') {
|
|
374
|
+
return {
|
|
375
|
+
source: {
|
|
376
|
+
kind: 'mcp_tool_sequence',
|
|
377
|
+
server,
|
|
378
|
+
canonical_tool: expectedName,
|
|
379
|
+
arguments: baseArgs,
|
|
380
|
+
},
|
|
381
|
+
pages,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const key = String(next);
|
|
385
|
+
if (seen.has(key)) throw new Error(expectedName + ' repeated a cursor.');
|
|
386
|
+
seen.add(key);
|
|
387
|
+
cursor = next;
|
|
388
|
+
}
|
|
389
|
+
throw new Error(expectedName + ' exceeded ' + maxPages + ' pages.');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function collectDBMetaResources(session, operationID, server, options = {}) {
|
|
393
|
+
const pages = [];
|
|
394
|
+
let fetchedItems = 0;
|
|
395
|
+
let reportedTotal = null;
|
|
396
|
+
const maxPages = options.maxPages == null ? 100 : Number(options.maxPages);
|
|
397
|
+
if (!Number.isSafeInteger(maxPages) || maxPages < 1) {
|
|
398
|
+
throw new TypeError('maxPages must be a positive integer.');
|
|
399
|
+
}
|
|
400
|
+
for (let page = 1; page <= maxPages; page += 1) {
|
|
401
|
+
const args = {
|
|
402
|
+
operation_id: operationID,
|
|
403
|
+
query: { page, page_size: DB_META_PAGE_SIZE },
|
|
404
|
+
};
|
|
405
|
+
const response = await rawQuery(session, TOOL_NAMES.apiCall, args, server, options);
|
|
406
|
+
pages.push(response);
|
|
407
|
+
const info = dbMetaPageInfo(response);
|
|
408
|
+
if (!info) {
|
|
409
|
+
return {
|
|
410
|
+
source: {
|
|
411
|
+
kind: 'mcp_tool_sequence',
|
|
412
|
+
server,
|
|
413
|
+
canonical_tool: TOOL_NAMES.apiCall,
|
|
414
|
+
operation_id: operationID,
|
|
415
|
+
},
|
|
416
|
+
pages,
|
|
417
|
+
pagination: { complete: false, reason: 'response_shape_unavailable' },
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const responsePage = info.page || page;
|
|
421
|
+
const responsePageSize = info.pageSize || DB_META_PAGE_SIZE;
|
|
422
|
+
if (responsePage !== page) {
|
|
423
|
+
throw new Error(`DraftGo db_meta API returned page ${responsePage} while page ${page} was requested.`);
|
|
424
|
+
}
|
|
425
|
+
fetchedItems += info.items.length;
|
|
426
|
+
if (info.total != null) {
|
|
427
|
+
if (reportedTotal != null && info.total !== reportedTotal) {
|
|
428
|
+
throw new Error(`DraftGo db_meta API changed total from ${reportedTotal} to ${info.total} during pagination.`);
|
|
429
|
+
}
|
|
430
|
+
reportedTotal = info.total;
|
|
431
|
+
}
|
|
432
|
+
if (reportedTotal != null) {
|
|
433
|
+
if (fetchedItems >= reportedTotal) {
|
|
434
|
+
return {
|
|
435
|
+
source: {
|
|
436
|
+
kind: 'mcp_tool_sequence',
|
|
437
|
+
server,
|
|
438
|
+
canonical_tool: TOOL_NAMES.apiCall,
|
|
439
|
+
operation_id: operationID,
|
|
440
|
+
},
|
|
441
|
+
pages,
|
|
442
|
+
pagination: { complete: true, total: reportedTotal },
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
if (!info.items.length) {
|
|
446
|
+
throw new Error('DraftGo db_meta API ended before the reported total was reached.');
|
|
447
|
+
}
|
|
448
|
+
} else if (info.items.length < responsePageSize) {
|
|
449
|
+
return {
|
|
450
|
+
source: {
|
|
451
|
+
kind: 'mcp_tool_sequence',
|
|
452
|
+
server,
|
|
453
|
+
canonical_tool: TOOL_NAMES.apiCall,
|
|
454
|
+
operation_id: operationID,
|
|
455
|
+
},
|
|
456
|
+
pages,
|
|
457
|
+
pagination: { complete: true, total: null },
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
throw new Error(`DraftGo db_meta API exceeded ${maxPages} pages.`);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function collectDataStructures(session, server, options = {}) {
|
|
465
|
+
const api = await rawPagedQuery(
|
|
466
|
+
session,
|
|
467
|
+
TOOL_NAMES.apiSearch,
|
|
468
|
+
{ resource_type: 'db_meta', limit: 100 },
|
|
469
|
+
server,
|
|
470
|
+
options,
|
|
471
|
+
);
|
|
472
|
+
const operation = selectDBMetaListOperation(api);
|
|
473
|
+
if (!operation || !operation.operation_id) {
|
|
474
|
+
throw new Error('DraftGo MCP does not expose a read-only GET /api/db-meta operation.');
|
|
475
|
+
}
|
|
476
|
+
const operationID = String(operation.operation_id);
|
|
477
|
+
const [contract, resources] = await Promise.all([
|
|
478
|
+
rawQuery(
|
|
479
|
+
session,
|
|
480
|
+
TOOL_NAMES.apiDescribe,
|
|
481
|
+
{ operation_id: operationID },
|
|
482
|
+
server,
|
|
483
|
+
options,
|
|
484
|
+
),
|
|
485
|
+
collectDBMetaResources(session, operationID, server, options),
|
|
486
|
+
]);
|
|
487
|
+
return { api, contract, resources };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function collectContext(projectDir, taskValue, options = {}) {
|
|
491
|
+
const task = normalizeTask(taskValue);
|
|
492
|
+
const profile = TASK_PROFILES[task];
|
|
493
|
+
const references = readTaskReferences(task, options);
|
|
494
|
+
const installedSkillVersion = readInstalledSkillVersion(projectDir);
|
|
495
|
+
const config = options.config || loadProjectConfig(projectDir);
|
|
496
|
+
const expected = [
|
|
497
|
+
TOOL_NAMES.projectOverview,
|
|
498
|
+
TOOL_NAMES.resourceList,
|
|
499
|
+
TOOL_NAMES.apiSearch,
|
|
500
|
+
TOOL_NAMES.apiDescribe,
|
|
501
|
+
TOOL_NAMES.apiCall,
|
|
502
|
+
];
|
|
503
|
+
const openSession = options.openToolSession || openToolSession;
|
|
504
|
+
const session = options.session || await openSession(config, expected, options);
|
|
505
|
+
const controller = new AbortController();
|
|
506
|
+
const externalSignal = options.signal;
|
|
507
|
+
const forwardAbort = () => controller.abort(externalSignal.reason);
|
|
508
|
+
if (externalSignal) {
|
|
509
|
+
if (externalSignal.aborted) forwardAbort();
|
|
510
|
+
else externalSignal.addEventListener('abort', forwardAbort, { once: true });
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
let project;
|
|
514
|
+
let resources;
|
|
515
|
+
let api;
|
|
516
|
+
let dataStructures;
|
|
517
|
+
try {
|
|
518
|
+
const queryOptions = { ...options, signal: controller.signal };
|
|
519
|
+
[project, resources, api, dataStructures] = await Promise.all([
|
|
520
|
+
rawQuery(session, TOOL_NAMES.projectOverview, {}, config.server, queryOptions),
|
|
521
|
+
collectLongContentResources(session, config.server, queryOptions),
|
|
522
|
+
collectTaskAPIs(session, profile.apiQueries, config.server, queryOptions),
|
|
523
|
+
collectDataStructures(session, config.server, queryOptions),
|
|
524
|
+
]);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
if (!controller.signal.aborted) controller.abort(error);
|
|
527
|
+
throw error;
|
|
528
|
+
} finally {
|
|
529
|
+
if (externalSignal) externalSignal.removeEventListener('abort', forwardAbort);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return {
|
|
533
|
+
schema_version: '1.1',
|
|
534
|
+
task,
|
|
535
|
+
reference_bundle: {
|
|
536
|
+
source: 'draftgo-cli',
|
|
537
|
+
version: pkg.version,
|
|
538
|
+
installed_skill_version: installedSkillVersion,
|
|
539
|
+
synchronized: installedSkillVersion == null ? null : installedSkillVersion === pkg.version,
|
|
540
|
+
},
|
|
541
|
+
references,
|
|
542
|
+
live: {
|
|
543
|
+
session: {
|
|
544
|
+
server: config.server,
|
|
545
|
+
protocol_version: session.client.protocolVersion,
|
|
546
|
+
},
|
|
547
|
+
project,
|
|
548
|
+
resources,
|
|
549
|
+
api,
|
|
550
|
+
data_structures: dataStructures,
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function readInstalledSkillVersion(projectDir) {
|
|
556
|
+
return readInstalledVersion(projectDir);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
module.exports = {
|
|
560
|
+
TASK_PROFILES,
|
|
561
|
+
normalizeTask,
|
|
562
|
+
extractSections,
|
|
563
|
+
readTaskReferences,
|
|
564
|
+
structuredValueFromRaw,
|
|
565
|
+
nextCursorFromRaw,
|
|
566
|
+
rawPagedQuery,
|
|
567
|
+
collectLongContentResources,
|
|
568
|
+
collectTaskAPIs,
|
|
569
|
+
itemsFromRawSequence,
|
|
570
|
+
selectDBMetaListOperation,
|
|
571
|
+
dbMetaPageInfo,
|
|
572
|
+
collectDBMetaResources,
|
|
573
|
+
collectDataStructures,
|
|
574
|
+
readInstalledSkillVersion,
|
|
575
|
+
collectContext,
|
|
576
|
+
};
|